From 5d8b2a84fd151dad8bf06b8b085e430cf80b868a Mon Sep 17 00:00:00 2001 From: Sarthak160 Date: Mon, 10 Nov 2025 13:25:10 +0000 Subject: [PATCH 1/3] feat: integrate keploy for ci-cd Signed-off-by: Sarthak160 --- .github/workflows/keploy-e2e.yml | 89 + coverage/.coverage.order_service.combined | Bin 69632 -> 53248 bytes coverage/.coverage.user_service.combined | Bin 53248 -> 53248 bytes keploy.yml | 98 + keploy/.cz.toml | 5 + keploy/.github/CLA.md | 25 + keploy/.github/CODEOWNERS | 1 + keploy/.github/CONTRIBUTING.md | 101 + keploy/.github/FUNDING.yml | 1 + .../.github/ISSUE_TEMPLATE/--bug-report.yaml | 167 + .../--documentation-update.yaml | 48 + .../ISSUE_TEMPLATE/--feature-request.yaml | 60 + keploy/.github/ISSUE_TEMPLATE/config.yml | 7 + keploy/.github/License-Apache_2.0-blue.svg | 1 + keploy/.github/PULL_REQUEST_TEMPLATE.md | 70 + .../actions/download-binary/action.yml | 28 + .../.github/actions/download-image/action.yml | 12 + keploy/.github/actions/tester/action.yml | 112 + keploy/.github/actions/tester/install.sh | 172 + keploy/.github/docs.svg | 21 + keploy/.github/slack.svg | 20 + keploy/.github/workflows/bug.yml | 38 + keploy/.github/workflows/cla.yml | 42 + keploy/.github/workflows/codeql.yml | 84 + keploy/.github/workflows/coverage_stage.yml | 57 + keploy/.github/workflows/docker-publish.yml | 96 + keploy/.github/workflows/docs.yml | 38 + keploy/.github/workflows/feat.yml | 38 + keploy/.github/workflows/go.yml | 31 + keploy/.github/workflows/go_macos.yaml | 25 + keploy/.github/workflows/go_windows.yml | 31 + .../workflows/golang_docker-compose.yml | 49 + keploy/.github/workflows/golang_docker.yml | 49 + .../.github/workflows/golang_http_linux.yml | 53 + keploy/.github/workflows/golang_linux.yml | 46 + .../.github/workflows/golang_mysql_linux.yml | 46 + keploy/.github/workflows/golangci-lint.yml | 48 + keploy/.github/workflows/greetings.yml | 17 + keploy/.github/workflows/java_linux.yml | 58 + keploy/.github/workflows/main.yml | 26 + keploy/.github/workflows/node_docker.yml | 50 + keploy/.github/workflows/node_encoding.yaml | 48 + keploy/.github/workflows/node_linux.yml | 47 + keploy/.github/workflows/prepare_and_run.yml | 74 + keploy/.github/workflows/python_docker.yml | 49 + keploy/.github/workflows/python_linux.yml | 45 + keploy/.github/workflows/release.yml | 101 + keploy/.github/workflows/sample-run.yml | 25 + keploy/.github/workflows/test-go-mongo-1.yml | 60 + keploy/.github/workflows/test-go-mongo-2.yml | 58 + .../check-deprecated-deps.sh | 25 + .../golang-docker-compose.sh | 127 + .../test_workflow_scripts/golang-docker.sh | 134 + .../golang-http-linux.sh | 134 + .../test_workflow_scripts/golang-linux.sh | 144 + .../golang-mysql-linux.sh | 178 + .../test_workflow_scripts/java-linux.sh | 255 + .../test_workflow_scripts/node-docker.sh | 127 + .../test_workflow_scripts/node-encoding.sh | 165 + .../test_workflow_scripts/node-linux.sh | 203 + .../test_workflow_scripts/python-docker.sh | 118 + .../test_workflow_scripts/python-linux.sh | 128 + .../test_workflow_scripts/test-iid.sh | 4 + .../test_workflow_scripts/update-docker.sh | 38 + .../test_workflow_scripts/update-java.sh | 8 + keploy/.github/write-good.yml | 3 + keploy/.gitignore | 61 + keploy/.golangci.yml | 24 + keploy/.pre-commit-config.yaml | 7 + keploy/.releaserc.json | 13 + keploy/CITATION.cff | 7 + keploy/CODE_OF_CONDUCT.md | 126 + keploy/CONTRIBUTING.md | 122 + keploy/DEBUG.md | 79 + keploy/Dockerfile | 57 + keploy/HACKTOBERFEST_GUIDE.md | 82 + keploy/LICENSE | 201 + keploy/README-UnitGen.md | 278 + keploy/README.md | 172 + keploy/READMEes-Es.md | 243 + keploy/READMEja-JP.md | 160 + keploy/SECURITY.md | 9 + keploy/cli/README.md | 5 + keploy/cli/cli.go | 22 + keploy/cli/config.go | 75 + keploy/cli/contract.go | 134 + keploy/cli/examples.go | 47 + keploy/cli/export.go | 63 + keploy/cli/import.go | 72 + keploy/cli/load.go | 66 + keploy/cli/login.go | 41 + keploy/cli/mock.go | 94 + keploy/cli/normalise.go | 50 + keploy/cli/provider/cmd.go | 1079 + keploy/cli/provider/common.go | 20 + keploy/cli/provider/compat_linux.go | 27 + keploy/cli/provider/compat_others.go | 11 + keploy/cli/provider/core_service_linux.go | 125 + keploy/cli/provider/core_service_others.go | 83 + keploy/cli/provider/docker.go | 297 + keploy/cli/provider/service.go | 60 + keploy/cli/provider/util.go | 101 + keploy/cli/record.go | 55 + keploy/cli/report.go | 55 + keploy/cli/rerecord.go | 56 + keploy/cli/root.go | 55 + keploy/cli/secure.go | 286 + keploy/cli/service.go | 17 + keploy/cli/templatize.go | 53 + keploy/cli/test.go | 63 + keploy/cli/testsuite.go | 60 + keploy/cli/update.go | 51 + keploy/cli/utgen.go | 56 + keploy/config/config.go | 259 + keploy/config/default.go | 134 + keploy/entrypoint.sh | 7 + keploy/go.mod | 159 + keploy/go.sum | 470 + keploy/gon.json | 13 + keploy/goreleaser.yaml | 60 + keploy/gsoc25_performance&security-testing.md | 558 + keploy/keploy.sh | 295 + keploy/main.go | 117 + keploy/oss-pledge.json | 14 + keploy/package-lock.json | 6 + keploy/pkg/README.md | 6 + keploy/pkg/core/app/app.go | 582 + keploy/pkg/core/app/util.go | 103 + keploy/pkg/core/core_linux.go | 279 + keploy/pkg/core/core_others.go | 61 + keploy/pkg/core/core_others_test.go | 92 + keploy/pkg/core/hooks/README.md | 6 + keploy/pkg/core/hooks/bpf_arm64_bpfel.go | 289 + keploy/pkg/core/hooks/bpf_arm64_bpfel.o | Bin 0 -> 381808 bytes keploy/pkg/core/hooks/bpf_x86_bpfel.go | 289 + keploy/pkg/core/hooks/bpf_x86_bpfel.o | Bin 0 -> 381144 bytes keploy/pkg/core/hooks/conn/README.md | 5 + keploy/pkg/core/hooks/conn/conn.go | 125 + keploy/pkg/core/hooks/conn/factory.go | 154 + keploy/pkg/core/hooks/conn/socket.go | 264 + keploy/pkg/core/hooks/conn/tracker.go | 571 + keploy/pkg/core/hooks/conn/util.go | 329 + keploy/pkg/core/hooks/hooks.go | 713 + keploy/pkg/core/hooks/hooks_test.go | 273 + keploy/pkg/core/hooks/kernelComm.go | 131 + keploy/pkg/core/hooks/structs/structs.go | 78 + keploy/pkg/core/hooks/util.go | 126 + keploy/pkg/core/proxy/README.md | 5 + keploy/pkg/core/proxy/dns.go | 345 + keploy/pkg/core/proxy/integrations/README.md | 3 + .../core/proxy/integrations/generic/README.md | 0 .../core/proxy/integrations/generic/decode.go | 124 + .../core/proxy/integrations/generic/encode.go | 200 + .../proxy/integrations/generic/generic.go | 69 + .../core/proxy/integrations/generic/match.go | 155 + .../core/proxy/integrations/grpc/decode.go | 33 + .../core/proxy/integrations/grpc/encode.go | 86 + .../pkg/core/proxy/integrations/grpc/frame.go | 215 + .../pkg/core/proxy/integrations/grpc/grpc.go | 71 + .../pkg/core/proxy/integrations/grpc/match.go | 227 + .../core/proxy/integrations/grpc/stream.go | 160 + .../proxy/integrations/grpc/transcoder.go | 379 + .../pkg/core/proxy/integrations/grpc/util.go | 10 + .../core/proxy/integrations/grpcV2/codec.go | 76 + .../core/proxy/integrations/grpcV2/grpc.go | 92 + .../core/proxy/integrations/grpcV2/helper.go | 46 + .../proxy/integrations/grpcV2/listener.go | 64 + .../core/proxy/integrations/grpcV2/match.go | 291 + .../core/proxy/integrations/grpcV2/mock.go | 213 + .../core/proxy/integrations/grpcV2/record.go | 428 + .../proxy/integrations/grpcV2/replayconn.go | 28 + .../core/proxy/integrations/http/README.md | 6 + .../pkg/core/proxy/integrations/http/chunk.go | 348 + .../core/proxy/integrations/http/decode.go | 206 + .../core/proxy/integrations/http/encode.go | 262 + .../pkg/core/proxy/integrations/http/http.go | 212 + .../pkg/core/proxy/integrations/http/match.go | 349 + .../pkg/core/proxy/integrations/http/util.go | 46 + .../core/proxy/integrations/integrations.go | 54 + .../core/proxy/integrations/mongo/README.md | 6 + .../core/proxy/integrations/mongo/command.go | 98 + .../core/proxy/integrations/mongo/decode.go | 345 + .../core/proxy/integrations/mongo/encode.go | 288 + .../core/proxy/integrations/mongo/match.go | 244 + .../core/proxy/integrations/mongo/mongo.go | 128 + .../proxy/integrations/mongo/operation.go | 1575 + .../proxy/integrations/mongo/scramAuth.go | 512 + .../pkg/core/proxy/integrations/mongo/util.go | 36 + .../core/proxy/integrations/mysql/README.md | 52 + .../core/proxy/integrations/mysql/mysql.go | 62 + .../integrations/mysql/panic_fix_test.go | 192 + .../proxy/integrations/mysql/recorder/conn.go | 730 + .../integrations/mysql/recorder/query.go | 559 + .../integrations/mysql/recorder/record.go | 131 + .../proxy/integrations/mysql/replayer/conn.go | 719 + .../integrations/mysql/replayer/match.go | 749 + .../integrations/mysql/replayer/query.go | 184 + .../integrations/mysql/replayer/replay.go | 124 + .../proxy/integrations/mysql/utils/util.go | 360 + .../integrations/mysql/utils/util_test.go | 211 + .../proxy/integrations/mysql/wire/decode.go | 433 + .../proxy/integrations/mysql/wire/encode.go | 179 + .../wire/phase/conn/authMoreDataPacket.go | 43 + .../wire/phase/conn/authNextFactorPacket.go | 56 + .../phase/conn/authSwitchRequestPacket.go | 59 + .../phase/conn/authSwitchResponsePacket.go | 31 + .../phase/conn/handshakeResponse41Packet.go | 272 + .../conn/handshakeResponse41Packet_test.go | 234 + .../wire/phase/conn/handshakeV10Packet.go | 167 + .../integrations/mysql/wire/phase/generic.go | 205 + .../mysql/wire/phase/query/ResultSetPacket.go | 142 + .../query/preparedstmt/StmtPrepareOkPacket.go | 153 + .../query/preparedstmt/stmtClosePacket.go | 26 + .../query/preparedstmt/stmtExecutePacket.go | 237 + .../preparedstmt/stmtExecutePacket_test.go | 110 + .../query/preparedstmt/stmtFetchPacket.go | 29 + .../query/preparedstmt/stmtPreparePacket.go | 22 + .../query/preparedstmt/stmtResetPacket.go | 25 + .../preparedstmt/stmtSendLongDataPacket.go | 28 + .../mysql/wire/phase/query/queryPacket.go | 225 + .../query/rowscols/binaryProtocolRowPacket.go | 497 + .../phase/query/rowscols/columnCountPacket.go | 32 + .../wire/phase/query/rowscols/columnPacket.go | 201 + .../phase/query/rowscols/textRowPacket.go | 105 + .../wire/phase/query/utility/initDbPacket.go | 20 + .../phase/query/utility/setOptionPacket.go | 26 + .../proxy/integrations/mysql/wire/util.go | 175 + .../proxy/integrations/postgres/v1/decode.go | 152 + .../proxy/integrations/postgres/v1/encode.go | 401 + .../proxy/integrations/postgres/v1/match.go | 867 + .../integrations/postgres/v1/postgres.go | 87 + .../integrations/postgres/v1/transcoder.go | 313 + .../proxy/integrations/postgres/v1/util.go | 500 + .../core/proxy/integrations/redis/decode.go | 125 + .../core/proxy/integrations/redis/encode.go | 188 + .../core/proxy/integrations/redis/match.go | 151 + .../core/proxy/integrations/redis/redis.go | 78 + .../core/proxy/integrations/scram/scram.go | 137 + .../pkg/core/proxy/integrations/scram/util.go | 90 + .../pkg/core/proxy/integrations/util/util.go | 88 + keploy/pkg/core/proxy/mockmanager.go | 514 + keploy/pkg/core/proxy/options.go | 12 + keploy/pkg/core/proxy/parsers.go | 15 + keploy/pkg/core/proxy/proxy.go | 731 + keploy/pkg/core/proxy/tls/asset/ca.crt | 10 + keploy/pkg/core/proxy/tls/asset/ca.key | 5 + keploy/pkg/core/proxy/tls/asset/setup_ca.sh | 108 + keploy/pkg/core/proxy/tls/ca.go | 345 + keploy/pkg/core/proxy/tls/tls.go | 58 + keploy/pkg/core/proxy/treedb.go | 88 + keploy/pkg/core/proxy/util.go | 75 + keploy/pkg/core/proxy/util/util.go | 648 + keploy/pkg/core/record.go | 24 + keploy/pkg/core/replay.go | 19 + keploy/pkg/core/service.go | 142 + keploy/pkg/core/tester/tester.go | 134 + keploy/pkg/http2.go | 741 + keploy/pkg/matcher/grpc/canonical.go | 282 + keploy/pkg/matcher/grpc/match.go | 234 + keploy/pkg/matcher/http/absmatch.go | 472 + keploy/pkg/matcher/http/match.go | 536 + keploy/pkg/matcher/http/match_test.go | 236 + keploy/pkg/matcher/http/utils.go | 103 + keploy/pkg/matcher/schema/match.go | 262 + keploy/pkg/matcher/utils.go | 1207 + keploy/pkg/models/README.md | 3 + keploy/pkg/models/assertions.go | 17 + keploy/pkg/models/auth.go | 14 + keploy/pkg/models/config.go | 33 + keploy/pkg/models/const.go | 128 + keploy/pkg/models/errors.go | 27 + keploy/pkg/models/generic.go | 13 + keploy/pkg/models/github.go | 20 + keploy/pkg/models/grpc.go | 79 + keploy/pkg/models/http.go | 48 + keploy/pkg/models/instrument.go | 63 + keploy/pkg/models/mock.go | 92 + keploy/pkg/models/mock_Secret.go | 29 + keploy/pkg/models/mode.go | 52 + keploy/pkg/models/mongo.go | 285 + keploy/pkg/models/mysql/comm.go | 228 + keploy/pkg/models/mysql/conn.go | 68 + keploy/pkg/models/mysql/const.go | 267 + keploy/pkg/models/mysql/generic.go | 30 + keploy/pkg/models/mysql/mysql.go | 61 + keploy/pkg/models/openapi.go | 142 + keploy/pkg/models/postgres.go | 114 + keploy/pkg/models/redis.go | 13 + keploy/pkg/models/tele.go | 14 + keploy/pkg/models/testcase.go | 73 + keploy/pkg/models/testcompare.go | 42 + keploy/pkg/models/testrun.go | 155 + keploy/pkg/models/ut.go | 133 + keploy/pkg/platform/README.md | 12 + keploy/pkg/platform/auth/auth.go | 237 + keploy/pkg/platform/coverage/csharp/csharp.go | 130 + keploy/pkg/platform/coverage/csharp/utils.go | 140 + keploy/pkg/platform/coverage/golang/golang.go | 151 + keploy/pkg/platform/coverage/golang/utils.go | 56 + keploy/pkg/platform/coverage/java/java.go | 145 + keploy/pkg/platform/coverage/java/utils.go | 171 + .../coverage/javascript/javascript.go | 174 + .../pkg/platform/coverage/javascript/utils.go | 48 + keploy/pkg/platform/coverage/python/python.go | 173 + keploy/pkg/platform/coverage/python/utils.go | 166 + keploy/pkg/platform/coverage/service.go | 18 + keploy/pkg/platform/docker/docker.go | 585 + keploy/pkg/platform/docker/service.go | 38 + keploy/pkg/platform/docker/util.go | 53 + keploy/pkg/platform/storage/storage.go | 204 + keploy/pkg/platform/telemetry/telemetry.go | 185 + keploy/pkg/platform/telemetry/utils.go | 52 + .../pkg/platform/yaml/configdb/testset/db.go | 96 + keploy/pkg/platform/yaml/configdb/user/db.go | 107 + keploy/pkg/platform/yaml/mockdb/db.go | 319 + keploy/pkg/platform/yaml/mockdb/util.go | 714 + keploy/pkg/platform/yaml/openapidb/db.go | 152 + keploy/pkg/platform/yaml/reportdb/db.go | 138 + keploy/pkg/platform/yaml/testdb/db.go | 262 + keploy/pkg/platform/yaml/testdb/util.go | 384 + keploy/pkg/platform/yaml/utils.go | 403 + keploy/pkg/platform/yaml/yaml.go | 232 + keploy/pkg/service/README.md | 5 + .../pkg/service/contract/consumer/consumer.go | 298 + .../pkg/service/contract/consumer/service.go | 8 + keploy/pkg/service/contract/contract.go | 685 + .../pkg/service/contract/provider/provider.go | 127 + .../pkg/service/contract/provider/service.go | 10 + keploy/pkg/service/contract/schema.go | 111 + keploy/pkg/service/contract/service.go | 30 + keploy/pkg/service/contract/utils.go | 261 + keploy/pkg/service/export/export.go | 269 + keploy/pkg/service/import/import.go | 569 + keploy/pkg/service/load/dashboard_exposer.go | 118 + keploy/pkg/service/load/exporter.go | 205 + keploy/pkg/service/load/load.go | 239 + keploy/pkg/service/load/metrics_collector.go | 92 + keploy/pkg/service/load/out/404.html | 1 + keploy/pkg/service/load/out/404/index.html | 1 + .../S6A95ZgpgxS8RHL4D2qnv/_buildManifest.js | 1 + .../S6A95ZgpgxS8RHL4D2qnv/_ssgManifest.js | 1 + .../chunks/4bd1b696-cf72ae8a39fa05aa.js | 1 + .../static/chunks/547-586c3cf76649ec2c.js | 27 + .../static/chunks/964-69097a61540f27b4.js | 1 + .../app/_not-found/page-7c3ecdf160dc3360.js | 1 + .../chunks/app/layout-bc503d5738af696e.js | 1 + .../chunks/app/page-a3cd1a73fe99bbf3.js | 1 + .../chunks/framework-7c95b8e5103c9e90.js | 1 + .../static/chunks/main-a5da5fd7e32dc553.js | 1 + .../chunks/main-app-b3d2ebcb2857645a.js | 1 + .../chunks/pages/_app-0a0020ddd67f79cf.js | 1 + .../chunks/pages/_error-03529f2c21436739.js | 1 + .../chunks/polyfills-42372ed130431b0a.js | 1 + .../static/chunks/webpack-cb07c2fe276ae3fa.js | 1 + .../out/_next/static/css/bc1768f92951bee0.css | 3 + .../static/media/26a46d62cd723877-s.woff2 | Bin 0 -> 18820 bytes .../static/media/55c55f0601d81cf3-s.woff2 | Bin 0 -> 25908 bytes .../static/media/581909926a08bbc8-s.woff2 | Bin 0 -> 19072 bytes .../static/media/8e9860b6e62d6359-s.woff2 | Bin 0 -> 85272 bytes .../static/media/97e0cb1ae144a2a9-s.woff2 | Bin 0 -> 11220 bytes .../static/media/df0a9ae256c0569c-s.woff2 | Bin 0 -> 10280 bytes .../static/media/e4af272ccee01ff0-s.p.woff2 | Bin 0 -> 48432 bytes keploy/pkg/service/load/out/favicon.ico | Bin 0 -> 243774 bytes keploy/pkg/service/load/out/index.html | 1 + keploy/pkg/service/load/out/index.txt | 22 + keploy/pkg/service/load/out/manifest.json | 17 + keploy/pkg/service/load/out/sw.js | 188 + keploy/pkg/service/load/scheduler.go | 138 + keploy/pkg/service/load/service.go | 9 + .../pkg/service/load/threshold_evaluator.go | 299 + keploy/pkg/service/load/utils.go | 1 + keploy/pkg/service/load/vu_worker.go | 118 + .../pkg/service/orchestrator/orchestrator.go | 31 + keploy/pkg/service/orchestrator/rerecord.go | 334 + keploy/pkg/service/orchestrator/service.go | 7 + keploy/pkg/service/record/record.go | 390 + keploy/pkg/service/record/service.go | 51 + keploy/pkg/service/record/utils.go | 3 + keploy/pkg/service/replay/hooks.go | 282 + keploy/pkg/service/replay/integration_test.go | 272 + keploy/pkg/service/replay/mock.go | 297 + keploy/pkg/service/replay/replay.go | 1793 + keploy/pkg/service/replay/replay_test.go | 265 + keploy/pkg/service/replay/service.go | 103 + keploy/pkg/service/replay/service_test.go | 267 + keploy/pkg/service/replay/utils.go | 118 + keploy/pkg/service/report/report.go | 283 + keploy/pkg/service/report/service.go | 20 + keploy/pkg/service/secure/secure.go | 1222 + keploy/pkg/service/secure/service.go | 11 + keploy/pkg/service/secure/utils.go | 337 + keploy/pkg/service/service.go | 9 + keploy/pkg/service/testsuite/service.go | 11 + keploy/pkg/service/testsuite/testsuite.go | 501 + keploy/pkg/service/testsuite/utils.go | 109 + keploy/pkg/service/tools/mock_Service.go | 149 + keploy/pkg/service/tools/mock_TestDB.go | 143 + .../pkg/service/tools/mock_TestSetConfig.go | 107 + keploy/pkg/service/tools/service.go | 37 + keploy/pkg/service/tools/templatize.go | 1106 + keploy/pkg/service/tools/templatize_test.go | 162 + keploy/pkg/service/tools/tools.go | 330 + keploy/pkg/service/utgen/ai.go | 391 + keploy/pkg/service/utgen/assets/config.go | 58 + .../pkg/service/utgen/assets/indentation.toml | 41 + .../pkg/service/utgen/assets/insert_line.toml | 37 + keploy/pkg/service/utgen/assets/language.toml | 439 + .../service/utgen/assets/test_generation.toml | 355 + keploy/pkg/service/utgen/coverage.go | 249 + keploy/pkg/service/utgen/gen.go | 813 + keploy/pkg/service/utgen/injector.go | 830 + keploy/pkg/service/utgen/prompt.go | 207 + keploy/pkg/service/utgen/service.go | 13 + keploy/pkg/service/utgen/utils.go | 330 + keploy/pkg/util.go | 637 + keploy/pkg/util_test.go | 247 + keploy/utils/ctx.go | 69 + keploy/utils/inc.go | 23 + keploy/utils/log/colors.go | 48 + keploy/utils/log/logger.go | 99 + keploy/utils/log/time.go | 12 + keploy/utils/mask_others.go | 13 + keploy/utils/mask_windows.go | 11 + keploy/utils/signal_others.go | 104 + keploy/utils/signal_windows.go | 44 + keploy/utils/utils.go | 1292 + keploy/utils/utils_test.go | 348 + scripts/user_service_flow.sh | 175 + scripts/user_service_manual_tests.sh | 108 + user_service/keploy-logs.txt | 15 + user_service/keploy/atg/mocks.yaml | 121443 +++++++++++++++ user_service/keploy/atg/tests/test-1.yaml | 46 + user_service/keploy/atg/tests/test-10.yaml | 47 + user_service/keploy/atg/tests/test-100.yaml | 47 + user_service/keploy/atg/tests/test-101.yaml | 47 + user_service/keploy/atg/tests/test-102.yaml | 47 + user_service/keploy/atg/tests/test-103.yaml | 47 + user_service/keploy/atg/tests/test-104.yaml | 46 + user_service/keploy/atg/tests/test-105.yaml | 47 + user_service/keploy/atg/tests/test-106.yaml | 47 + user_service/keploy/atg/tests/test-107.yaml | 44 + user_service/keploy/atg/tests/test-108.yaml | 47 + user_service/keploy/atg/tests/test-109.yaml | 43 + user_service/keploy/atg/tests/test-11.yaml | 47 + user_service/keploy/atg/tests/test-110.yaml | 43 + user_service/keploy/atg/tests/test-111.yaml | 47 + user_service/keploy/atg/tests/test-112.yaml | 47 + user_service/keploy/atg/tests/test-113.yaml | 47 + user_service/keploy/atg/tests/test-114.yaml | 47 + user_service/keploy/atg/tests/test-115.yaml | 43 + user_service/keploy/atg/tests/test-116.yaml | 47 + user_service/keploy/atg/tests/test-117.yaml | 47 + user_service/keploy/atg/tests/test-118.yaml | 47 + user_service/keploy/atg/tests/test-119.yaml | 43 + user_service/keploy/atg/tests/test-12.yaml | 47 + user_service/keploy/atg/tests/test-120.yaml | 47 + user_service/keploy/atg/tests/test-121.yaml | 47 + user_service/keploy/atg/tests/test-122.yaml | 47 + user_service/keploy/atg/tests/test-123.yaml | 47 + user_service/keploy/atg/tests/test-124.yaml | 43 + user_service/keploy/atg/tests/test-125.yaml | 47 + user_service/keploy/atg/tests/test-126.yaml | 47 + user_service/keploy/atg/tests/test-127.yaml | 47 + user_service/keploy/atg/tests/test-128.yaml | 43 + user_service/keploy/atg/tests/test-129.yaml | 47 + user_service/keploy/atg/tests/test-13.yaml | 47 + user_service/keploy/atg/tests/test-130.yaml | 44 + user_service/keploy/atg/tests/test-131.yaml | 47 + user_service/keploy/atg/tests/test-132.yaml | 43 + user_service/keploy/atg/tests/test-133.yaml | 47 + user_service/keploy/atg/tests/test-134.yaml | 47 + user_service/keploy/atg/tests/test-135.yaml | 43 + user_service/keploy/atg/tests/test-136.yaml | 43 + user_service/keploy/atg/tests/test-137.yaml | 47 + user_service/keploy/atg/tests/test-138.yaml | 47 + user_service/keploy/atg/tests/test-139.yaml | 43 + user_service/keploy/atg/tests/test-14.yaml | 47 + user_service/keploy/atg/tests/test-140.yaml | 43 + user_service/keploy/atg/tests/test-141.yaml | 47 + user_service/keploy/atg/tests/test-142.yaml | 47 + user_service/keploy/atg/tests/test-143.yaml | 47 + user_service/keploy/atg/tests/test-144.yaml | 43 + user_service/keploy/atg/tests/test-145.yaml | 47 + user_service/keploy/atg/tests/test-146.yaml | 47 + user_service/keploy/atg/tests/test-147.yaml | 47 + user_service/keploy/atg/tests/test-148.yaml | 43 + user_service/keploy/atg/tests/test-149.yaml | 47 + user_service/keploy/atg/tests/test-15.yaml | 47 + user_service/keploy/atg/tests/test-150.yaml | 47 + user_service/keploy/atg/tests/test-151.yaml | 47 + user_service/keploy/atg/tests/test-152.yaml | 43 + user_service/keploy/atg/tests/test-153.yaml | 47 + user_service/keploy/atg/tests/test-154.yaml | 47 + user_service/keploy/atg/tests/test-155.yaml | 47 + user_service/keploy/atg/tests/test-156.yaml | 47 + user_service/keploy/atg/tests/test-157.yaml | 43 + user_service/keploy/atg/tests/test-158.yaml | 47 + user_service/keploy/atg/tests/test-159.yaml | 47 + user_service/keploy/atg/tests/test-16.yaml | 47 + user_service/keploy/atg/tests/test-160.yaml | 47 + user_service/keploy/atg/tests/test-161.yaml | 47 + user_service/keploy/atg/tests/test-162.yaml | 43 + user_service/keploy/atg/tests/test-163.yaml | 47 + user_service/keploy/atg/tests/test-164.yaml | 47 + user_service/keploy/atg/tests/test-165.yaml | 47 + user_service/keploy/atg/tests/test-166.yaml | 47 + user_service/keploy/atg/tests/test-167.yaml | 43 + user_service/keploy/atg/tests/test-168.yaml | 47 + user_service/keploy/atg/tests/test-169.yaml | 47 + user_service/keploy/atg/tests/test-17.yaml | 47 + user_service/keploy/atg/tests/test-170.yaml | 47 + user_service/keploy/atg/tests/test-171.yaml | 47 + user_service/keploy/atg/tests/test-172.yaml | 50 + user_service/keploy/atg/tests/test-173.yaml | 43 + user_service/keploy/atg/tests/test-174.yaml | 47 + user_service/keploy/atg/tests/test-175.yaml | 47 + user_service/keploy/atg/tests/test-176.yaml | 47 + user_service/keploy/atg/tests/test-177.yaml | 47 + user_service/keploy/atg/tests/test-178.yaml | 43 + user_service/keploy/atg/tests/test-179.yaml | 47 + user_service/keploy/atg/tests/test-18.yaml | 47 + user_service/keploy/atg/tests/test-180.yaml | 47 + user_service/keploy/atg/tests/test-181.yaml | 47 + user_service/keploy/atg/tests/test-182.yaml | 43 + user_service/keploy/atg/tests/test-183.yaml | 47 + user_service/keploy/atg/tests/test-184.yaml | 47 + user_service/keploy/atg/tests/test-185.yaml | 47 + user_service/keploy/atg/tests/test-186.yaml | 43 + user_service/keploy/atg/tests/test-187.yaml | 47 + user_service/keploy/atg/tests/test-188.yaml | 47 + user_service/keploy/atg/tests/test-189.yaml | 48 + user_service/keploy/atg/tests/test-19.yaml | 47 + user_service/keploy/atg/tests/test-190.yaml | 43 + user_service/keploy/atg/tests/test-191.yaml | 47 + user_service/keploy/atg/tests/test-192.yaml | 47 + user_service/keploy/atg/tests/test-193.yaml | 48 + user_service/keploy/atg/tests/test-194.yaml | 43 + user_service/keploy/atg/tests/test-195.yaml | 47 + user_service/keploy/atg/tests/test-196.yaml | 47 + user_service/keploy/atg/tests/test-197.yaml | 47 + user_service/keploy/atg/tests/test-198.yaml | 47 + user_service/keploy/atg/tests/test-199.yaml | 47 + user_service/keploy/atg/tests/test-2.yaml | 45 + user_service/keploy/atg/tests/test-20.yaml | 43 + user_service/keploy/atg/tests/test-200.yaml | 47 + user_service/keploy/atg/tests/test-201.yaml | 43 + user_service/keploy/atg/tests/test-202.yaml | 47 + user_service/keploy/atg/tests/test-203.yaml | 43 + user_service/keploy/atg/tests/test-204.yaml | 47 + user_service/keploy/atg/tests/test-205.yaml | 43 + user_service/keploy/atg/tests/test-206.yaml | 47 + user_service/keploy/atg/tests/test-207.yaml | 47 + user_service/keploy/atg/tests/test-208.yaml | 51 + user_service/keploy/atg/tests/test-209.yaml | 51 + user_service/keploy/atg/tests/test-21.yaml | 47 + user_service/keploy/atg/tests/test-210.yaml | 53 + user_service/keploy/atg/tests/test-211.yaml | 49 + user_service/keploy/atg/tests/test-212.yaml | 47 + user_service/keploy/atg/tests/test-213.yaml | 47 + user_service/keploy/atg/tests/test-214.yaml | 48 + user_service/keploy/atg/tests/test-215.yaml | 48 + user_service/keploy/atg/tests/test-216.yaml | 47 + user_service/keploy/atg/tests/test-217.yaml | 47 + user_service/keploy/atg/tests/test-218.yaml | 47 + user_service/keploy/atg/tests/test-219.yaml | 43 + user_service/keploy/atg/tests/test-22.yaml | 43 + user_service/keploy/atg/tests/test-220.yaml | 43 + user_service/keploy/atg/tests/test-221.yaml | 47 + user_service/keploy/atg/tests/test-222.yaml | 47 + user_service/keploy/atg/tests/test-223.yaml | 47 + user_service/keploy/atg/tests/test-224.yaml | 43 + user_service/keploy/atg/tests/test-225.yaml | 43 + user_service/keploy/atg/tests/test-226.yaml | 47 + user_service/keploy/atg/tests/test-227.yaml | 47 + user_service/keploy/atg/tests/test-228.yaml | 47 + user_service/keploy/atg/tests/test-229.yaml | 53 + user_service/keploy/atg/tests/test-23.yaml | 47 + user_service/keploy/atg/tests/test-230.yaml | 43 + user_service/keploy/atg/tests/test-231.yaml | 47 + user_service/keploy/atg/tests/test-232.yaml | 47 + user_service/keploy/atg/tests/test-233.yaml | 47 + user_service/keploy/atg/tests/test-234.yaml | 47 + user_service/keploy/atg/tests/test-235.yaml | 43 + user_service/keploy/atg/tests/test-236.yaml | 47 + user_service/keploy/atg/tests/test-237.yaml | 47 + user_service/keploy/atg/tests/test-238.yaml | 47 + user_service/keploy/atg/tests/test-239.yaml | 53 + user_service/keploy/atg/tests/test-24.yaml | 47 + user_service/keploy/atg/tests/test-240.yaml | 43 + user_service/keploy/atg/tests/test-241.yaml | 47 + user_service/keploy/atg/tests/test-242.yaml | 47 + user_service/keploy/atg/tests/test-243.yaml | 47 + user_service/keploy/atg/tests/test-244.yaml | 47 + user_service/keploy/atg/tests/test-245.yaml | 43 + user_service/keploy/atg/tests/test-246.yaml | 47 + user_service/keploy/atg/tests/test-247.yaml | 47 + user_service/keploy/atg/tests/test-248.yaml | 47 + user_service/keploy/atg/tests/test-249.yaml | 43 + user_service/keploy/atg/tests/test-25.yaml | 47 + user_service/keploy/atg/tests/test-250.yaml | 47 + user_service/keploy/atg/tests/test-251.yaml | 47 + user_service/keploy/atg/tests/test-252.yaml | 47 + user_service/keploy/atg/tests/test-253.yaml | 43 + user_service/keploy/atg/tests/test-254.yaml | 47 + user_service/keploy/atg/tests/test-255.yaml | 47 + user_service/keploy/atg/tests/test-256.yaml | 51 + user_service/keploy/atg/tests/test-257.yaml | 43 + user_service/keploy/atg/tests/test-258.yaml | 47 + user_service/keploy/atg/tests/test-259.yaml | 47 + user_service/keploy/atg/tests/test-26.yaml | 47 + user_service/keploy/atg/tests/test-260.yaml | 51 + user_service/keploy/atg/tests/test-261.yaml | 43 + user_service/keploy/atg/tests/test-262.yaml | 47 + user_service/keploy/atg/tests/test-263.yaml | 47 + user_service/keploy/atg/tests/test-264.yaml | 47 + user_service/keploy/atg/tests/test-265.yaml | 47 + user_service/keploy/atg/tests/test-266.yaml | 47 + user_service/keploy/atg/tests/test-267.yaml | 43 + user_service/keploy/atg/tests/test-268.yaml | 47 + user_service/keploy/atg/tests/test-269.yaml | 47 + user_service/keploy/atg/tests/test-27.yaml | 47 + user_service/keploy/atg/tests/test-270.yaml | 47 + user_service/keploy/atg/tests/test-271.yaml | 43 + user_service/keploy/atg/tests/test-272.yaml | 47 + user_service/keploy/atg/tests/test-273.yaml | 47 + user_service/keploy/atg/tests/test-274.yaml | 47 + user_service/keploy/atg/tests/test-275.yaml | 43 + user_service/keploy/atg/tests/test-276.yaml | 47 + user_service/keploy/atg/tests/test-277.yaml | 47 + user_service/keploy/atg/tests/test-278.yaml | 47 + user_service/keploy/atg/tests/test-279.yaml | 43 + user_service/keploy/atg/tests/test-28.yaml | 47 + user_service/keploy/atg/tests/test-280.yaml | 47 + user_service/keploy/atg/tests/test-281.yaml | 47 + user_service/keploy/atg/tests/test-282.yaml | 47 + user_service/keploy/atg/tests/test-283.yaml | 43 + user_service/keploy/atg/tests/test-284.yaml | 47 + user_service/keploy/atg/tests/test-285.yaml | 47 + user_service/keploy/atg/tests/test-286.yaml | 47 + user_service/keploy/atg/tests/test-287.yaml | 43 + user_service/keploy/atg/tests/test-288.yaml | 47 + user_service/keploy/atg/tests/test-289.yaml | 47 + user_service/keploy/atg/tests/test-29.yaml | 47 + user_service/keploy/atg/tests/test-290.yaml | 47 + user_service/keploy/atg/tests/test-291.yaml | 43 + user_service/keploy/atg/tests/test-292.yaml | 47 + user_service/keploy/atg/tests/test-293.yaml | 47 + user_service/keploy/atg/tests/test-294.yaml | 47 + user_service/keploy/atg/tests/test-295.yaml | 43 + user_service/keploy/atg/tests/test-296.yaml | 47 + user_service/keploy/atg/tests/test-297.yaml | 47 + user_service/keploy/atg/tests/test-298.yaml | 47 + user_service/keploy/atg/tests/test-299.yaml | 47 + user_service/keploy/atg/tests/test-3.yaml | 45 + user_service/keploy/atg/tests/test-30.yaml | 47 + user_service/keploy/atg/tests/test-300.yaml | 43 + user_service/keploy/atg/tests/test-301.yaml | 47 + user_service/keploy/atg/tests/test-302.yaml | 47 + user_service/keploy/atg/tests/test-303.yaml | 43 + user_service/keploy/atg/tests/test-304.yaml | 47 + user_service/keploy/atg/tests/test-305.yaml | 47 + user_service/keploy/atg/tests/test-306.yaml | 43 + user_service/keploy/atg/tests/test-307.yaml | 43 + user_service/keploy/atg/tests/test-308.yaml | 47 + user_service/keploy/atg/tests/test-309.yaml | 47 + user_service/keploy/atg/tests/test-31.yaml | 47 + user_service/keploy/atg/tests/test-310.yaml | 47 + user_service/keploy/atg/tests/test-311.yaml | 47 + user_service/keploy/atg/tests/test-312.yaml | 43 + user_service/keploy/atg/tests/test-313.yaml | 43 + user_service/keploy/atg/tests/test-314.yaml | 47 + user_service/keploy/atg/tests/test-315.yaml | 47 + user_service/keploy/atg/tests/test-316.yaml | 47 + user_service/keploy/atg/tests/test-317.yaml | 47 + user_service/keploy/atg/tests/test-318.yaml | 43 + user_service/keploy/atg/tests/test-319.yaml | 47 + user_service/keploy/atg/tests/test-32.yaml | 47 + user_service/keploy/atg/tests/test-320.yaml | 47 + user_service/keploy/atg/tests/test-321.yaml | 47 + user_service/keploy/atg/tests/test-322.yaml | 47 + user_service/keploy/atg/tests/test-323.yaml | 43 + user_service/keploy/atg/tests/test-324.yaml | 47 + user_service/keploy/atg/tests/test-325.yaml | 43 + user_service/keploy/atg/tests/test-326.yaml | 47 + user_service/keploy/atg/tests/test-327.yaml | 47 + user_service/keploy/atg/tests/test-328.yaml | 43 + user_service/keploy/atg/tests/test-329.yaml | 43 + user_service/keploy/atg/tests/test-33.yaml | 47 + user_service/keploy/atg/tests/test-330.yaml | 47 + user_service/keploy/atg/tests/test-331.yaml | 47 + user_service/keploy/atg/tests/test-332.yaml | 49 + user_service/keploy/atg/tests/test-333.yaml | 43 + user_service/keploy/atg/tests/test-334.yaml | 51 + user_service/keploy/atg/tests/test-335.yaml | 47 + user_service/keploy/atg/tests/test-336.yaml | 47 + user_service/keploy/atg/tests/test-337.yaml | 47 + user_service/keploy/atg/tests/test-338.yaml | 47 + user_service/keploy/atg/tests/test-339.yaml | 43 + user_service/keploy/atg/tests/test-34.yaml | 47 + user_service/keploy/atg/tests/test-340.yaml | 47 + user_service/keploy/atg/tests/test-341.yaml | 47 + user_service/keploy/atg/tests/test-342.yaml | 51 + user_service/keploy/atg/tests/test-343.yaml | 43 + user_service/keploy/atg/tests/test-344.yaml | 47 + user_service/keploy/atg/tests/test-345.yaml | 47 + user_service/keploy/atg/tests/test-346.yaml | 51 + user_service/keploy/atg/tests/test-347.yaml | 43 + user_service/keploy/atg/tests/test-348.yaml | 47 + user_service/keploy/atg/tests/test-349.yaml | 47 + user_service/keploy/atg/tests/test-35.yaml | 47 + user_service/keploy/atg/tests/test-350.yaml | 51 + user_service/keploy/atg/tests/test-351.yaml | 43 + user_service/keploy/atg/tests/test-352.yaml | 47 + user_service/keploy/atg/tests/test-353.yaml | 47 + user_service/keploy/atg/tests/test-354.yaml | 47 + user_service/keploy/atg/tests/test-355.yaml | 43 + user_service/keploy/atg/tests/test-356.yaml | 47 + user_service/keploy/atg/tests/test-357.yaml | 47 + user_service/keploy/atg/tests/test-358.yaml | 47 + user_service/keploy/atg/tests/test-359.yaml | 43 + user_service/keploy/atg/tests/test-36.yaml | 47 + user_service/keploy/atg/tests/test-360.yaml | 47 + user_service/keploy/atg/tests/test-361.yaml | 47 + user_service/keploy/atg/tests/test-362.yaml | 47 + user_service/keploy/atg/tests/test-363.yaml | 43 + user_service/keploy/atg/tests/test-364.yaml | 47 + user_service/keploy/atg/tests/test-365.yaml | 47 + user_service/keploy/atg/tests/test-366.yaml | 47 + user_service/keploy/atg/tests/test-367.yaml | 43 + user_service/keploy/atg/tests/test-368.yaml | 47 + user_service/keploy/atg/tests/test-369.yaml | 47 + user_service/keploy/atg/tests/test-37.yaml | 47 + user_service/keploy/atg/tests/test-370.yaml | 47 + user_service/keploy/atg/tests/test-371.yaml | 43 + user_service/keploy/atg/tests/test-372.yaml | 47 + user_service/keploy/atg/tests/test-373.yaml | 47 + user_service/keploy/atg/tests/test-374.yaml | 47 + user_service/keploy/atg/tests/test-375.yaml | 43 + user_service/keploy/atg/tests/test-376.yaml | 47 + user_service/keploy/atg/tests/test-377.yaml | 47 + user_service/keploy/atg/tests/test-378.yaml | 47 + user_service/keploy/atg/tests/test-379.yaml | 43 + user_service/keploy/atg/tests/test-38.yaml | 47 + user_service/keploy/atg/tests/test-380.yaml | 47 + user_service/keploy/atg/tests/test-381.yaml | 47 + user_service/keploy/atg/tests/test-382.yaml | 47 + user_service/keploy/atg/tests/test-383.yaml | 43 + user_service/keploy/atg/tests/test-384.yaml | 47 + user_service/keploy/atg/tests/test-385.yaml | 47 + user_service/keploy/atg/tests/test-386.yaml | 47 + user_service/keploy/atg/tests/test-387.yaml | 43 + user_service/keploy/atg/tests/test-388.yaml | 47 + user_service/keploy/atg/tests/test-389.yaml | 47 + user_service/keploy/atg/tests/test-39.yaml | 47 + user_service/keploy/atg/tests/test-390.yaml | 47 + user_service/keploy/atg/tests/test-391.yaml | 43 + user_service/keploy/atg/tests/test-392.yaml | 47 + user_service/keploy/atg/tests/test-393.yaml | 47 + user_service/keploy/atg/tests/test-394.yaml | 47 + user_service/keploy/atg/tests/test-395.yaml | 43 + user_service/keploy/atg/tests/test-396.yaml | 47 + user_service/keploy/atg/tests/test-397.yaml | 47 + user_service/keploy/atg/tests/test-398.yaml | 47 + user_service/keploy/atg/tests/test-399.yaml | 43 + user_service/keploy/atg/tests/test-4.yaml | 45 + user_service/keploy/atg/tests/test-40.yaml | 47 + user_service/keploy/atg/tests/test-400.yaml | 47 + user_service/keploy/atg/tests/test-401.yaml | 47 + user_service/keploy/atg/tests/test-402.yaml | 47 + user_service/keploy/atg/tests/test-403.yaml | 43 + user_service/keploy/atg/tests/test-404.yaml | 47 + user_service/keploy/atg/tests/test-405.yaml | 47 + user_service/keploy/atg/tests/test-406.yaml | 47 + user_service/keploy/atg/tests/test-407.yaml | 43 + user_service/keploy/atg/tests/test-408.yaml | 47 + user_service/keploy/atg/tests/test-409.yaml | 47 + user_service/keploy/atg/tests/test-41.yaml | 47 + user_service/keploy/atg/tests/test-410.yaml | 47 + user_service/keploy/atg/tests/test-411.yaml | 43 + user_service/keploy/atg/tests/test-412.yaml | 47 + user_service/keploy/atg/tests/test-413.yaml | 47 + user_service/keploy/atg/tests/test-414.yaml | 47 + user_service/keploy/atg/tests/test-415.yaml | 43 + user_service/keploy/atg/tests/test-416.yaml | 47 + user_service/keploy/atg/tests/test-417.yaml | 47 + user_service/keploy/atg/tests/test-418.yaml | 47 + user_service/keploy/atg/tests/test-419.yaml | 43 + user_service/keploy/atg/tests/test-42.yaml | 47 + user_service/keploy/atg/tests/test-420.yaml | 47 + user_service/keploy/atg/tests/test-421.yaml | 47 + user_service/keploy/atg/tests/test-422.yaml | 47 + user_service/keploy/atg/tests/test-423.yaml | 43 + user_service/keploy/atg/tests/test-424.yaml | 47 + user_service/keploy/atg/tests/test-425.yaml | 47 + user_service/keploy/atg/tests/test-426.yaml | 47 + user_service/keploy/atg/tests/test-427.yaml | 43 + user_service/keploy/atg/tests/test-428.yaml | 47 + user_service/keploy/atg/tests/test-429.yaml | 47 + user_service/keploy/atg/tests/test-43.yaml | 47 + user_service/keploy/atg/tests/test-430.yaml | 47 + user_service/keploy/atg/tests/test-431.yaml | 43 + user_service/keploy/atg/tests/test-432.yaml | 47 + user_service/keploy/atg/tests/test-433.yaml | 47 + user_service/keploy/atg/tests/test-434.yaml | 47 + user_service/keploy/atg/tests/test-435.yaml | 43 + user_service/keploy/atg/tests/test-436.yaml | 47 + user_service/keploy/atg/tests/test-437.yaml | 47 + user_service/keploy/atg/tests/test-438.yaml | 47 + user_service/keploy/atg/tests/test-439.yaml | 43 + user_service/keploy/atg/tests/test-44.yaml | 47 + user_service/keploy/atg/tests/test-440.yaml | 47 + user_service/keploy/atg/tests/test-441.yaml | 47 + user_service/keploy/atg/tests/test-442.yaml | 47 + user_service/keploy/atg/tests/test-443.yaml | 43 + user_service/keploy/atg/tests/test-444.yaml | 47 + user_service/keploy/atg/tests/test-445.yaml | 47 + user_service/keploy/atg/tests/test-446.yaml | 47 + user_service/keploy/atg/tests/test-447.yaml | 43 + user_service/keploy/atg/tests/test-448.yaml | 47 + user_service/keploy/atg/tests/test-449.yaml | 47 + user_service/keploy/atg/tests/test-45.yaml | 47 + user_service/keploy/atg/tests/test-450.yaml | 47 + user_service/keploy/atg/tests/test-451.yaml | 43 + user_service/keploy/atg/tests/test-452.yaml | 47 + user_service/keploy/atg/tests/test-453.yaml | 47 + user_service/keploy/atg/tests/test-454.yaml | 47 + user_service/keploy/atg/tests/test-455.yaml | 43 + user_service/keploy/atg/tests/test-456.yaml | 47 + user_service/keploy/atg/tests/test-457.yaml | 47 + user_service/keploy/atg/tests/test-458.yaml | 47 + user_service/keploy/atg/tests/test-459.yaml | 43 + user_service/keploy/atg/tests/test-46.yaml | 47 + user_service/keploy/atg/tests/test-460.yaml | 51 + user_service/keploy/atg/tests/test-461.yaml | 51 + user_service/keploy/atg/tests/test-462.yaml | 47 + user_service/keploy/atg/tests/test-463.yaml | 51 + user_service/keploy/atg/tests/test-464.yaml | 51 + user_service/keploy/atg/tests/test-465.yaml | 47 + user_service/keploy/atg/tests/test-466.yaml | 51 + user_service/keploy/atg/tests/test-467.yaml | 51 + user_service/keploy/atg/tests/test-468.yaml | 47 + user_service/keploy/atg/tests/test-469.yaml | 51 + user_service/keploy/atg/tests/test-47.yaml | 47 + user_service/keploy/atg/tests/test-470.yaml | 51 + user_service/keploy/atg/tests/test-471.yaml | 47 + user_service/keploy/atg/tests/test-472.yaml | 51 + user_service/keploy/atg/tests/test-473.yaml | 51 + user_service/keploy/atg/tests/test-474.yaml | 47 + user_service/keploy/atg/tests/test-475.yaml | 51 + user_service/keploy/atg/tests/test-476.yaml | 51 + user_service/keploy/atg/tests/test-477.yaml | 47 + user_service/keploy/atg/tests/test-478.yaml | 51 + user_service/keploy/atg/tests/test-479.yaml | 51 + user_service/keploy/atg/tests/test-48.yaml | 47 + user_service/keploy/atg/tests/test-480.yaml | 47 + user_service/keploy/atg/tests/test-481.yaml | 51 + user_service/keploy/atg/tests/test-482.yaml | 51 + user_service/keploy/atg/tests/test-483.yaml | 51 + user_service/keploy/atg/tests/test-484.yaml | 47 + user_service/keploy/atg/tests/test-485.yaml | 51 + user_service/keploy/atg/tests/test-486.yaml | 51 + user_service/keploy/atg/tests/test-487.yaml | 47 + user_service/keploy/atg/tests/test-488.yaml | 51 + user_service/keploy/atg/tests/test-489.yaml | 51 + user_service/keploy/atg/tests/test-49.yaml | 47 + user_service/keploy/atg/tests/test-490.yaml | 47 + user_service/keploy/atg/tests/test-491.yaml | 51 + user_service/keploy/atg/tests/test-492.yaml | 51 + user_service/keploy/atg/tests/test-493.yaml | 47 + user_service/keploy/atg/tests/test-494.yaml | 51 + user_service/keploy/atg/tests/test-495.yaml | 51 + user_service/keploy/atg/tests/test-496.yaml | 47 + user_service/keploy/atg/tests/test-497.yaml | 51 + user_service/keploy/atg/tests/test-498.yaml | 51 + user_service/keploy/atg/tests/test-499.yaml | 51 + user_service/keploy/atg/tests/test-5.yaml | 45 + user_service/keploy/atg/tests/test-50.yaml | 47 + user_service/keploy/atg/tests/test-500.yaml | 47 + user_service/keploy/atg/tests/test-501.yaml | 49 + user_service/keploy/atg/tests/test-502.yaml | 49 + user_service/keploy/atg/tests/test-503.yaml | 51 + user_service/keploy/atg/tests/test-504.yaml | 51 + user_service/keploy/atg/tests/test-505.yaml | 51 + user_service/keploy/atg/tests/test-506.yaml | 51 + user_service/keploy/atg/tests/test-507.yaml | 49 + user_service/keploy/atg/tests/test-508.yaml | 49 + user_service/keploy/atg/tests/test-509.yaml | 50 + user_service/keploy/atg/tests/test-51.yaml | 47 + user_service/keploy/atg/tests/test-510.yaml | 50 + user_service/keploy/atg/tests/test-511.yaml | 51 + user_service/keploy/atg/tests/test-512.yaml | 51 + user_service/keploy/atg/tests/test-513.yaml | 51 + user_service/keploy/atg/tests/test-514.yaml | 51 + user_service/keploy/atg/tests/test-515.yaml | 51 + user_service/keploy/atg/tests/test-516.yaml | 51 + user_service/keploy/atg/tests/test-517.yaml | 51 + user_service/keploy/atg/tests/test-518.yaml | 51 + user_service/keploy/atg/tests/test-519.yaml | 51 + user_service/keploy/atg/tests/test-52.yaml | 47 + user_service/keploy/atg/tests/test-520.yaml | 51 + user_service/keploy/atg/tests/test-521.yaml | 51 + user_service/keploy/atg/tests/test-522.yaml | 51 + user_service/keploy/atg/tests/test-523.yaml | 51 + user_service/keploy/atg/tests/test-524.yaml | 51 + user_service/keploy/atg/tests/test-525.yaml | 51 + user_service/keploy/atg/tests/test-526.yaml | 51 + user_service/keploy/atg/tests/test-527.yaml | 51 + user_service/keploy/atg/tests/test-528.yaml | 51 + user_service/keploy/atg/tests/test-529.yaml | 51 + user_service/keploy/atg/tests/test-53.yaml | 47 + user_service/keploy/atg/tests/test-530.yaml | 51 + user_service/keploy/atg/tests/test-531.yaml | 51 + user_service/keploy/atg/tests/test-532.yaml | 51 + user_service/keploy/atg/tests/test-533.yaml | 51 + user_service/keploy/atg/tests/test-534.yaml | 51 + user_service/keploy/atg/tests/test-535.yaml | 51 + user_service/keploy/atg/tests/test-536.yaml | 51 + user_service/keploy/atg/tests/test-537.yaml | 51 + user_service/keploy/atg/tests/test-538.yaml | 51 + user_service/keploy/atg/tests/test-539.yaml | 51 + user_service/keploy/atg/tests/test-54.yaml | 47 + user_service/keploy/atg/tests/test-540.yaml | 51 + user_service/keploy/atg/tests/test-541.yaml | 47 + user_service/keploy/atg/tests/test-542.yaml | 51 + user_service/keploy/atg/tests/test-55.yaml | 47 + user_service/keploy/atg/tests/test-56.yaml | 47 + user_service/keploy/atg/tests/test-57.yaml | 47 + user_service/keploy/atg/tests/test-58.yaml | 47 + user_service/keploy/atg/tests/test-59.yaml | 47 + user_service/keploy/atg/tests/test-6.yaml | 46 + user_service/keploy/atg/tests/test-60.yaml | 47 + user_service/keploy/atg/tests/test-61.yaml | 47 + user_service/keploy/atg/tests/test-62.yaml | 47 + user_service/keploy/atg/tests/test-63.yaml | 47 + user_service/keploy/atg/tests/test-64.yaml | 47 + user_service/keploy/atg/tests/test-65.yaml | 47 + user_service/keploy/atg/tests/test-66.yaml | 47 + user_service/keploy/atg/tests/test-67.yaml | 47 + user_service/keploy/atg/tests/test-68.yaml | 47 + user_service/keploy/atg/tests/test-69.yaml | 47 + user_service/keploy/atg/tests/test-7.yaml | 47 + user_service/keploy/atg/tests/test-70.yaml | 47 + user_service/keploy/atg/tests/test-71.yaml | 47 + user_service/keploy/atg/tests/test-72.yaml | 49 + user_service/keploy/atg/tests/test-73.yaml | 49 + user_service/keploy/atg/tests/test-74.yaml | 47 + user_service/keploy/atg/tests/test-75.yaml | 47 + user_service/keploy/atg/tests/test-76.yaml | 47 + user_service/keploy/atg/tests/test-77.yaml | 47 + user_service/keploy/atg/tests/test-78.yaml | 47 + user_service/keploy/atg/tests/test-79.yaml | 51 + user_service/keploy/atg/tests/test-8.yaml | 47 + user_service/keploy/atg/tests/test-80.yaml | 47 + user_service/keploy/atg/tests/test-81.yaml | 51 + user_service/keploy/atg/tests/test-82.yaml | 47 + user_service/keploy/atg/tests/test-83.yaml | 47 + user_service/keploy/atg/tests/test-84.yaml | 47 + user_service/keploy/atg/tests/test-85.yaml | 47 + user_service/keploy/atg/tests/test-86.yaml | 48 + user_service/keploy/atg/tests/test-87.yaml | 47 + user_service/keploy/atg/tests/test-88.yaml | 48 + user_service/keploy/atg/tests/test-89.yaml | 47 + user_service/keploy/atg/tests/test-9.yaml | 47 + user_service/keploy/atg/tests/test-90.yaml | 47 + user_service/keploy/atg/tests/test-91.yaml | 47 + user_service/keploy/atg/tests/test-92.yaml | 47 + user_service/keploy/atg/tests/test-93.yaml | 47 + user_service/keploy/atg/tests/test-94.yaml | 47 + user_service/keploy/atg/tests/test-95.yaml | 47 + user_service/keploy/atg/tests/test-96.yaml | 47 + user_service/keploy/atg/tests/test-97.yaml | 47 + user_service/keploy/atg/tests/test-98.yaml | 47 + user_service/keploy/atg/tests/test-99.yaml | 49 + .../load/reports/20251110_113538_suite-0.json | 56 + .../load/reports/20251110_113651_suite-0.json | 56 + .../load/reports/20251110_125123_suite-0.json | 128 + user_service/keploy/testsuite/keploy.yml | 74 + user_service/keploy/testsuite/suite-0.yaml | 122 + .../20251110112827_report_suite-0.yaml | 55 + 979 files changed, 216546 insertions(+) create mode 100644 .github/workflows/keploy-e2e.yml create mode 100755 keploy.yml create mode 100755 keploy/.cz.toml create mode 100755 keploy/.github/CLA.md create mode 100644 keploy/.github/CODEOWNERS create mode 100755 keploy/.github/CONTRIBUTING.md create mode 100755 keploy/.github/FUNDING.yml create mode 100755 keploy/.github/ISSUE_TEMPLATE/--bug-report.yaml create mode 100755 keploy/.github/ISSUE_TEMPLATE/--documentation-update.yaml create mode 100755 keploy/.github/ISSUE_TEMPLATE/--feature-request.yaml create mode 100755 keploy/.github/ISSUE_TEMPLATE/config.yml create mode 100755 keploy/.github/License-Apache_2.0-blue.svg create mode 100755 keploy/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 keploy/.github/actions/download-binary/action.yml create mode 100644 keploy/.github/actions/download-image/action.yml create mode 100644 keploy/.github/actions/tester/action.yml create mode 100644 keploy/.github/actions/tester/install.sh create mode 100755 keploy/.github/docs.svg create mode 100755 keploy/.github/slack.svg create mode 100755 keploy/.github/workflows/bug.yml create mode 100755 keploy/.github/workflows/cla.yml create mode 100755 keploy/.github/workflows/codeql.yml create mode 100644 keploy/.github/workflows/coverage_stage.yml create mode 100755 keploy/.github/workflows/docker-publish.yml create mode 100755 keploy/.github/workflows/docs.yml create mode 100755 keploy/.github/workflows/feat.yml create mode 100755 keploy/.github/workflows/go.yml create mode 100644 keploy/.github/workflows/go_macos.yaml create mode 100644 keploy/.github/workflows/go_windows.yml create mode 100644 keploy/.github/workflows/golang_docker-compose.yml create mode 100755 keploy/.github/workflows/golang_docker.yml create mode 100644 keploy/.github/workflows/golang_http_linux.yml create mode 100644 keploy/.github/workflows/golang_linux.yml create mode 100644 keploy/.github/workflows/golang_mysql_linux.yml create mode 100644 keploy/.github/workflows/golangci-lint.yml create mode 100755 keploy/.github/workflows/greetings.yml create mode 100644 keploy/.github/workflows/java_linux.yml create mode 100755 keploy/.github/workflows/main.yml create mode 100755 keploy/.github/workflows/node_docker.yml create mode 100644 keploy/.github/workflows/node_encoding.yaml create mode 100644 keploy/.github/workflows/node_linux.yml create mode 100644 keploy/.github/workflows/prepare_and_run.yml create mode 100644 keploy/.github/workflows/python_docker.yml create mode 100644 keploy/.github/workflows/python_linux.yml create mode 100755 keploy/.github/workflows/release.yml create mode 100755 keploy/.github/workflows/sample-run.yml create mode 100644 keploy/.github/workflows/test-go-mongo-1.yml create mode 100644 keploy/.github/workflows/test-go-mongo-2.yml create mode 100644 keploy/.github/workflows/test_workflow_scripts/check-deprecated-deps.sh create mode 100644 keploy/.github/workflows/test_workflow_scripts/golang-docker-compose.sh create mode 100755 keploy/.github/workflows/test_workflow_scripts/golang-docker.sh create mode 100644 keploy/.github/workflows/test_workflow_scripts/golang-http-linux.sh create mode 100644 keploy/.github/workflows/test_workflow_scripts/golang-linux.sh create mode 100644 keploy/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh create mode 100644 keploy/.github/workflows/test_workflow_scripts/java-linux.sh create mode 100755 keploy/.github/workflows/test_workflow_scripts/node-docker.sh create mode 100644 keploy/.github/workflows/test_workflow_scripts/node-encoding.sh create mode 100755 keploy/.github/workflows/test_workflow_scripts/node-linux.sh create mode 100755 keploy/.github/workflows/test_workflow_scripts/python-docker.sh create mode 100644 keploy/.github/workflows/test_workflow_scripts/python-linux.sh create mode 100644 keploy/.github/workflows/test_workflow_scripts/test-iid.sh create mode 100644 keploy/.github/workflows/test_workflow_scripts/update-docker.sh create mode 100644 keploy/.github/workflows/test_workflow_scripts/update-java.sh create mode 100755 keploy/.github/write-good.yml create mode 100755 keploy/.gitignore create mode 100644 keploy/.golangci.yml create mode 100755 keploy/.pre-commit-config.yaml create mode 100755 keploy/.releaserc.json create mode 100755 keploy/CITATION.cff create mode 100755 keploy/CODE_OF_CONDUCT.md create mode 100755 keploy/CONTRIBUTING.md create mode 100644 keploy/DEBUG.md create mode 100755 keploy/Dockerfile create mode 100644 keploy/HACKTOBERFEST_GUIDE.md create mode 100755 keploy/LICENSE create mode 100644 keploy/README-UnitGen.md create mode 100755 keploy/README.md create mode 100644 keploy/READMEes-Es.md create mode 100644 keploy/READMEja-JP.md create mode 100755 keploy/SECURITY.md create mode 100755 keploy/cli/README.md create mode 100644 keploy/cli/cli.go create mode 100644 keploy/cli/config.go create mode 100644 keploy/cli/contract.go create mode 100755 keploy/cli/examples.go create mode 100644 keploy/cli/export.go create mode 100644 keploy/cli/import.go create mode 100644 keploy/cli/load.go create mode 100644 keploy/cli/login.go create mode 100644 keploy/cli/mock.go create mode 100644 keploy/cli/normalise.go create mode 100644 keploy/cli/provider/cmd.go create mode 100644 keploy/cli/provider/common.go create mode 100644 keploy/cli/provider/compat_linux.go create mode 100644 keploy/cli/provider/compat_others.go create mode 100644 keploy/cli/provider/core_service_linux.go create mode 100644 keploy/cli/provider/core_service_others.go create mode 100644 keploy/cli/provider/docker.go create mode 100644 keploy/cli/provider/service.go create mode 100644 keploy/cli/provider/util.go create mode 100755 keploy/cli/record.go create mode 100644 keploy/cli/report.go create mode 100644 keploy/cli/rerecord.go create mode 100755 keploy/cli/root.go create mode 100644 keploy/cli/secure.go create mode 100644 keploy/cli/service.go create mode 100644 keploy/cli/templatize.go create mode 100755 keploy/cli/test.go create mode 100644 keploy/cli/testsuite.go create mode 100644 keploy/cli/update.go create mode 100644 keploy/cli/utgen.go create mode 100644 keploy/config/config.go create mode 100644 keploy/config/default.go create mode 100755 keploy/entrypoint.sh create mode 100755 keploy/go.mod create mode 100644 keploy/go.sum create mode 100755 keploy/gon.json create mode 100755 keploy/goreleaser.yaml create mode 100644 keploy/gsoc25_performance&security-testing.md create mode 100644 keploy/keploy.sh create mode 100755 keploy/main.go create mode 100644 keploy/oss-pledge.json create mode 100644 keploy/package-lock.json create mode 100755 keploy/pkg/README.md create mode 100644 keploy/pkg/core/app/app.go create mode 100644 keploy/pkg/core/app/util.go create mode 100644 keploy/pkg/core/core_linux.go create mode 100644 keploy/pkg/core/core_others.go create mode 100644 keploy/pkg/core/core_others_test.go create mode 100755 keploy/pkg/core/hooks/README.md create mode 100644 keploy/pkg/core/hooks/bpf_arm64_bpfel.go create mode 100644 keploy/pkg/core/hooks/bpf_arm64_bpfel.o create mode 100644 keploy/pkg/core/hooks/bpf_x86_bpfel.go create mode 100644 keploy/pkg/core/hooks/bpf_x86_bpfel.o create mode 100755 keploy/pkg/core/hooks/conn/README.md create mode 100644 keploy/pkg/core/hooks/conn/conn.go create mode 100755 keploy/pkg/core/hooks/conn/factory.go create mode 100644 keploy/pkg/core/hooks/conn/socket.go create mode 100755 keploy/pkg/core/hooks/conn/tracker.go create mode 100755 keploy/pkg/core/hooks/conn/util.go create mode 100644 keploy/pkg/core/hooks/hooks.go create mode 100644 keploy/pkg/core/hooks/hooks_test.go create mode 100644 keploy/pkg/core/hooks/kernelComm.go create mode 100755 keploy/pkg/core/hooks/structs/structs.go create mode 100644 keploy/pkg/core/hooks/util.go create mode 100755 keploy/pkg/core/proxy/README.md create mode 100644 keploy/pkg/core/proxy/dns.go create mode 100755 keploy/pkg/core/proxy/integrations/README.md create mode 100644 keploy/pkg/core/proxy/integrations/generic/README.md create mode 100644 keploy/pkg/core/proxy/integrations/generic/decode.go create mode 100644 keploy/pkg/core/proxy/integrations/generic/encode.go create mode 100755 keploy/pkg/core/proxy/integrations/generic/generic.go create mode 100755 keploy/pkg/core/proxy/integrations/generic/match.go create mode 100644 keploy/pkg/core/proxy/integrations/grpc/decode.go create mode 100644 keploy/pkg/core/proxy/integrations/grpc/encode.go create mode 100644 keploy/pkg/core/proxy/integrations/grpc/frame.go create mode 100644 keploy/pkg/core/proxy/integrations/grpc/grpc.go create mode 100644 keploy/pkg/core/proxy/integrations/grpc/match.go create mode 100644 keploy/pkg/core/proxy/integrations/grpc/stream.go create mode 100644 keploy/pkg/core/proxy/integrations/grpc/transcoder.go create mode 100644 keploy/pkg/core/proxy/integrations/grpc/util.go create mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/codec.go create mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/grpc.go create mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/helper.go create mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/listener.go create mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/match.go create mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/mock.go create mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/record.go create mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/replayconn.go create mode 100755 keploy/pkg/core/proxy/integrations/http/README.md create mode 100644 keploy/pkg/core/proxy/integrations/http/chunk.go create mode 100644 keploy/pkg/core/proxy/integrations/http/decode.go create mode 100644 keploy/pkg/core/proxy/integrations/http/encode.go create mode 100755 keploy/pkg/core/proxy/integrations/http/http.go create mode 100644 keploy/pkg/core/proxy/integrations/http/match.go create mode 100644 keploy/pkg/core/proxy/integrations/http/util.go create mode 100644 keploy/pkg/core/proxy/integrations/integrations.go create mode 100644 keploy/pkg/core/proxy/integrations/mongo/README.md create mode 100644 keploy/pkg/core/proxy/integrations/mongo/command.go create mode 100644 keploy/pkg/core/proxy/integrations/mongo/decode.go create mode 100644 keploy/pkg/core/proxy/integrations/mongo/encode.go create mode 100644 keploy/pkg/core/proxy/integrations/mongo/match.go create mode 100644 keploy/pkg/core/proxy/integrations/mongo/mongo.go create mode 100644 keploy/pkg/core/proxy/integrations/mongo/operation.go create mode 100644 keploy/pkg/core/proxy/integrations/mongo/scramAuth.go create mode 100644 keploy/pkg/core/proxy/integrations/mongo/util.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/README.md create mode 100644 keploy/pkg/core/proxy/integrations/mysql/mysql.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/panic_fix_test.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/recorder/conn.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/recorder/query.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/recorder/record.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/replayer/conn.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/replayer/match.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/replayer/query.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/replayer/replay.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/utils/util.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/utils/util_test.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/decode.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/encode.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authMoreDataPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authNextFactorPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchRequestPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchResponsePacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet_test.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeV10Packet.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/generic.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/ResultSetPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/StmtPrepareOkPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtClosePacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket_test.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtFetchPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtPreparePacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtResetPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtSendLongDataPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/queryPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/columnCountPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/columnPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/textRowPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/initDbPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/setOptionPacket.go create mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/util.go create mode 100644 keploy/pkg/core/proxy/integrations/postgres/v1/decode.go create mode 100755 keploy/pkg/core/proxy/integrations/postgres/v1/encode.go create mode 100644 keploy/pkg/core/proxy/integrations/postgres/v1/match.go create mode 100755 keploy/pkg/core/proxy/integrations/postgres/v1/postgres.go create mode 100644 keploy/pkg/core/proxy/integrations/postgres/v1/transcoder.go create mode 100755 keploy/pkg/core/proxy/integrations/postgres/v1/util.go create mode 100644 keploy/pkg/core/proxy/integrations/redis/decode.go create mode 100644 keploy/pkg/core/proxy/integrations/redis/encode.go create mode 100755 keploy/pkg/core/proxy/integrations/redis/match.go create mode 100755 keploy/pkg/core/proxy/integrations/redis/redis.go create mode 100644 keploy/pkg/core/proxy/integrations/scram/scram.go create mode 100644 keploy/pkg/core/proxy/integrations/scram/util.go create mode 100644 keploy/pkg/core/proxy/integrations/util/util.go create mode 100644 keploy/pkg/core/proxy/mockmanager.go create mode 100755 keploy/pkg/core/proxy/options.go create mode 100644 keploy/pkg/core/proxy/parsers.go create mode 100755 keploy/pkg/core/proxy/proxy.go create mode 100755 keploy/pkg/core/proxy/tls/asset/ca.crt create mode 100755 keploy/pkg/core/proxy/tls/asset/ca.key create mode 100755 keploy/pkg/core/proxy/tls/asset/setup_ca.sh create mode 100644 keploy/pkg/core/proxy/tls/ca.go create mode 100644 keploy/pkg/core/proxy/tls/tls.go create mode 100644 keploy/pkg/core/proxy/treedb.go create mode 100644 keploy/pkg/core/proxy/util.go create mode 100755 keploy/pkg/core/proxy/util/util.go create mode 100644 keploy/pkg/core/record.go create mode 100644 keploy/pkg/core/replay.go create mode 100644 keploy/pkg/core/service.go create mode 100644 keploy/pkg/core/tester/tester.go create mode 100644 keploy/pkg/http2.go create mode 100644 keploy/pkg/matcher/grpc/canonical.go create mode 100644 keploy/pkg/matcher/grpc/match.go create mode 100644 keploy/pkg/matcher/http/absmatch.go create mode 100644 keploy/pkg/matcher/http/match.go create mode 100644 keploy/pkg/matcher/http/match_test.go create mode 100644 keploy/pkg/matcher/http/utils.go create mode 100644 keploy/pkg/matcher/schema/match.go create mode 100644 keploy/pkg/matcher/utils.go create mode 100755 keploy/pkg/models/README.md create mode 100644 keploy/pkg/models/assertions.go create mode 100644 keploy/pkg/models/auth.go create mode 100644 keploy/pkg/models/config.go create mode 100755 keploy/pkg/models/const.go create mode 100644 keploy/pkg/models/errors.go create mode 100644 keploy/pkg/models/generic.go create mode 100644 keploy/pkg/models/github.go create mode 100644 keploy/pkg/models/grpc.go create mode 100755 keploy/pkg/models/http.go create mode 100644 keploy/pkg/models/instrument.go create mode 100755 keploy/pkg/models/mock.go create mode 100644 keploy/pkg/models/mock_Secret.go create mode 100755 keploy/pkg/models/mode.go create mode 100755 keploy/pkg/models/mongo.go create mode 100644 keploy/pkg/models/mysql/comm.go create mode 100644 keploy/pkg/models/mysql/conn.go create mode 100644 keploy/pkg/models/mysql/const.go create mode 100644 keploy/pkg/models/mysql/generic.go create mode 100644 keploy/pkg/models/mysql/mysql.go create mode 100644 keploy/pkg/models/openapi.go create mode 100755 keploy/pkg/models/postgres.go create mode 100644 keploy/pkg/models/redis.go create mode 100755 keploy/pkg/models/tele.go create mode 100755 keploy/pkg/models/testcase.go create mode 100644 keploy/pkg/models/testcompare.go create mode 100755 keploy/pkg/models/testrun.go create mode 100644 keploy/pkg/models/ut.go create mode 100755 keploy/pkg/platform/README.md create mode 100644 keploy/pkg/platform/auth/auth.go create mode 100644 keploy/pkg/platform/coverage/csharp/csharp.go create mode 100644 keploy/pkg/platform/coverage/csharp/utils.go create mode 100644 keploy/pkg/platform/coverage/golang/golang.go create mode 100644 keploy/pkg/platform/coverage/golang/utils.go create mode 100644 keploy/pkg/platform/coverage/java/java.go create mode 100644 keploy/pkg/platform/coverage/java/utils.go create mode 100644 keploy/pkg/platform/coverage/javascript/javascript.go create mode 100644 keploy/pkg/platform/coverage/javascript/utils.go create mode 100644 keploy/pkg/platform/coverage/python/python.go create mode 100644 keploy/pkg/platform/coverage/python/utils.go create mode 100644 keploy/pkg/platform/coverage/service.go create mode 100644 keploy/pkg/platform/docker/docker.go create mode 100644 keploy/pkg/platform/docker/service.go create mode 100644 keploy/pkg/platform/docker/util.go create mode 100644 keploy/pkg/platform/storage/storage.go create mode 100644 keploy/pkg/platform/telemetry/telemetry.go create mode 100644 keploy/pkg/platform/telemetry/utils.go create mode 100644 keploy/pkg/platform/yaml/configdb/testset/db.go create mode 100644 keploy/pkg/platform/yaml/configdb/user/db.go create mode 100644 keploy/pkg/platform/yaml/mockdb/db.go create mode 100644 keploy/pkg/platform/yaml/mockdb/util.go create mode 100644 keploy/pkg/platform/yaml/openapidb/db.go create mode 100755 keploy/pkg/platform/yaml/reportdb/db.go create mode 100644 keploy/pkg/platform/yaml/testdb/db.go create mode 100644 keploy/pkg/platform/yaml/testdb/util.go create mode 100755 keploy/pkg/platform/yaml/utils.go create mode 100755 keploy/pkg/platform/yaml/yaml.go create mode 100755 keploy/pkg/service/README.md create mode 100644 keploy/pkg/service/contract/consumer/consumer.go create mode 100644 keploy/pkg/service/contract/consumer/service.go create mode 100644 keploy/pkg/service/contract/contract.go create mode 100644 keploy/pkg/service/contract/provider/provider.go create mode 100644 keploy/pkg/service/contract/provider/service.go create mode 100644 keploy/pkg/service/contract/schema.go create mode 100644 keploy/pkg/service/contract/service.go create mode 100644 keploy/pkg/service/contract/utils.go create mode 100644 keploy/pkg/service/export/export.go create mode 100644 keploy/pkg/service/import/import.go create mode 100644 keploy/pkg/service/load/dashboard_exposer.go create mode 100644 keploy/pkg/service/load/exporter.go create mode 100644 keploy/pkg/service/load/load.go create mode 100644 keploy/pkg/service/load/metrics_collector.go create mode 100644 keploy/pkg/service/load/out/404.html create mode 100644 keploy/pkg/service/load/out/404/index.html create mode 100644 keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_buildManifest.js create mode 100644 keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_ssgManifest.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/547-586c3cf76649ec2c.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/964-69097a61540f27b4.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/app/_not-found/page-7c3ecdf160dc3360.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/app/layout-bc503d5738af696e.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/app/page-a3cd1a73fe99bbf3.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/framework-7c95b8e5103c9e90.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/main-a5da5fd7e32dc553.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/main-app-b3d2ebcb2857645a.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/pages/_app-0a0020ddd67f79cf.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/pages/_error-03529f2c21436739.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/polyfills-42372ed130431b0a.js create mode 100644 keploy/pkg/service/load/out/_next/static/chunks/webpack-cb07c2fe276ae3fa.js create mode 100644 keploy/pkg/service/load/out/_next/static/css/bc1768f92951bee0.css create mode 100644 keploy/pkg/service/load/out/_next/static/media/26a46d62cd723877-s.woff2 create mode 100644 keploy/pkg/service/load/out/_next/static/media/55c55f0601d81cf3-s.woff2 create mode 100644 keploy/pkg/service/load/out/_next/static/media/581909926a08bbc8-s.woff2 create mode 100644 keploy/pkg/service/load/out/_next/static/media/8e9860b6e62d6359-s.woff2 create mode 100644 keploy/pkg/service/load/out/_next/static/media/97e0cb1ae144a2a9-s.woff2 create mode 100644 keploy/pkg/service/load/out/_next/static/media/df0a9ae256c0569c-s.woff2 create mode 100644 keploy/pkg/service/load/out/_next/static/media/e4af272ccee01ff0-s.p.woff2 create mode 100644 keploy/pkg/service/load/out/favicon.ico create mode 100644 keploy/pkg/service/load/out/index.html create mode 100644 keploy/pkg/service/load/out/index.txt create mode 100644 keploy/pkg/service/load/out/manifest.json create mode 100644 keploy/pkg/service/load/out/sw.js create mode 100644 keploy/pkg/service/load/scheduler.go create mode 100644 keploy/pkg/service/load/service.go create mode 100644 keploy/pkg/service/load/threshold_evaluator.go create mode 100644 keploy/pkg/service/load/utils.go create mode 100644 keploy/pkg/service/load/vu_worker.go create mode 100644 keploy/pkg/service/orchestrator/orchestrator.go create mode 100644 keploy/pkg/service/orchestrator/rerecord.go create mode 100644 keploy/pkg/service/orchestrator/service.go create mode 100755 keploy/pkg/service/record/record.go create mode 100755 keploy/pkg/service/record/service.go create mode 100644 keploy/pkg/service/record/utils.go create mode 100644 keploy/pkg/service/replay/hooks.go create mode 100644 keploy/pkg/service/replay/integration_test.go create mode 100644 keploy/pkg/service/replay/mock.go create mode 100644 keploy/pkg/service/replay/replay.go create mode 100644 keploy/pkg/service/replay/replay_test.go create mode 100644 keploy/pkg/service/replay/service.go create mode 100644 keploy/pkg/service/replay/service_test.go create mode 100644 keploy/pkg/service/replay/utils.go create mode 100644 keploy/pkg/service/report/report.go create mode 100644 keploy/pkg/service/report/service.go create mode 100644 keploy/pkg/service/secure/secure.go create mode 100644 keploy/pkg/service/secure/service.go create mode 100644 keploy/pkg/service/secure/utils.go create mode 100644 keploy/pkg/service/service.go create mode 100644 keploy/pkg/service/testsuite/service.go create mode 100644 keploy/pkg/service/testsuite/testsuite.go create mode 100644 keploy/pkg/service/testsuite/utils.go create mode 100644 keploy/pkg/service/tools/mock_Service.go create mode 100644 keploy/pkg/service/tools/mock_TestDB.go create mode 100644 keploy/pkg/service/tools/mock_TestSetConfig.go create mode 100644 keploy/pkg/service/tools/service.go create mode 100644 keploy/pkg/service/tools/templatize.go create mode 100644 keploy/pkg/service/tools/templatize_test.go create mode 100644 keploy/pkg/service/tools/tools.go create mode 100644 keploy/pkg/service/utgen/ai.go create mode 100644 keploy/pkg/service/utgen/assets/config.go create mode 100644 keploy/pkg/service/utgen/assets/indentation.toml create mode 100644 keploy/pkg/service/utgen/assets/insert_line.toml create mode 100644 keploy/pkg/service/utgen/assets/language.toml create mode 100644 keploy/pkg/service/utgen/assets/test_generation.toml create mode 100644 keploy/pkg/service/utgen/coverage.go create mode 100644 keploy/pkg/service/utgen/gen.go create mode 100644 keploy/pkg/service/utgen/injector.go create mode 100644 keploy/pkg/service/utgen/prompt.go create mode 100644 keploy/pkg/service/utgen/service.go create mode 100644 keploy/pkg/service/utgen/utils.go create mode 100755 keploy/pkg/util.go create mode 100644 keploy/pkg/util_test.go create mode 100644 keploy/utils/ctx.go create mode 100644 keploy/utils/inc.go create mode 100644 keploy/utils/log/colors.go create mode 100644 keploy/utils/log/logger.go create mode 100644 keploy/utils/log/time.go create mode 100644 keploy/utils/mask_others.go create mode 100644 keploy/utils/mask_windows.go create mode 100644 keploy/utils/signal_others.go create mode 100644 keploy/utils/signal_windows.go create mode 100644 keploy/utils/utils.go create mode 100644 keploy/utils/utils_test.go create mode 100755 scripts/user_service_flow.sh create mode 100755 scripts/user_service_manual_tests.sh create mode 100755 user_service/keploy-logs.txt create mode 100755 user_service/keploy/atg/mocks.yaml create mode 100755 user_service/keploy/atg/tests/test-1.yaml create mode 100755 user_service/keploy/atg/tests/test-10.yaml create mode 100755 user_service/keploy/atg/tests/test-100.yaml create mode 100755 user_service/keploy/atg/tests/test-101.yaml create mode 100755 user_service/keploy/atg/tests/test-102.yaml create mode 100755 user_service/keploy/atg/tests/test-103.yaml create mode 100755 user_service/keploy/atg/tests/test-104.yaml create mode 100755 user_service/keploy/atg/tests/test-105.yaml create mode 100755 user_service/keploy/atg/tests/test-106.yaml create mode 100755 user_service/keploy/atg/tests/test-107.yaml create mode 100755 user_service/keploy/atg/tests/test-108.yaml create mode 100755 user_service/keploy/atg/tests/test-109.yaml create mode 100755 user_service/keploy/atg/tests/test-11.yaml create mode 100755 user_service/keploy/atg/tests/test-110.yaml create mode 100755 user_service/keploy/atg/tests/test-111.yaml create mode 100755 user_service/keploy/atg/tests/test-112.yaml create mode 100755 user_service/keploy/atg/tests/test-113.yaml create mode 100755 user_service/keploy/atg/tests/test-114.yaml create mode 100755 user_service/keploy/atg/tests/test-115.yaml create mode 100755 user_service/keploy/atg/tests/test-116.yaml create mode 100755 user_service/keploy/atg/tests/test-117.yaml create mode 100755 user_service/keploy/atg/tests/test-118.yaml create mode 100755 user_service/keploy/atg/tests/test-119.yaml create mode 100755 user_service/keploy/atg/tests/test-12.yaml create mode 100755 user_service/keploy/atg/tests/test-120.yaml create mode 100755 user_service/keploy/atg/tests/test-121.yaml create mode 100755 user_service/keploy/atg/tests/test-122.yaml create mode 100755 user_service/keploy/atg/tests/test-123.yaml create mode 100755 user_service/keploy/atg/tests/test-124.yaml create mode 100755 user_service/keploy/atg/tests/test-125.yaml create mode 100755 user_service/keploy/atg/tests/test-126.yaml create mode 100755 user_service/keploy/atg/tests/test-127.yaml create mode 100755 user_service/keploy/atg/tests/test-128.yaml create mode 100755 user_service/keploy/atg/tests/test-129.yaml create mode 100755 user_service/keploy/atg/tests/test-13.yaml create mode 100755 user_service/keploy/atg/tests/test-130.yaml create mode 100755 user_service/keploy/atg/tests/test-131.yaml create mode 100755 user_service/keploy/atg/tests/test-132.yaml create mode 100755 user_service/keploy/atg/tests/test-133.yaml create mode 100755 user_service/keploy/atg/tests/test-134.yaml create mode 100755 user_service/keploy/atg/tests/test-135.yaml create mode 100755 user_service/keploy/atg/tests/test-136.yaml create mode 100755 user_service/keploy/atg/tests/test-137.yaml create mode 100755 user_service/keploy/atg/tests/test-138.yaml create mode 100755 user_service/keploy/atg/tests/test-139.yaml create mode 100755 user_service/keploy/atg/tests/test-14.yaml create mode 100755 user_service/keploy/atg/tests/test-140.yaml create mode 100755 user_service/keploy/atg/tests/test-141.yaml create mode 100755 user_service/keploy/atg/tests/test-142.yaml create mode 100755 user_service/keploy/atg/tests/test-143.yaml create mode 100755 user_service/keploy/atg/tests/test-144.yaml create mode 100755 user_service/keploy/atg/tests/test-145.yaml create mode 100755 user_service/keploy/atg/tests/test-146.yaml create mode 100755 user_service/keploy/atg/tests/test-147.yaml create mode 100755 user_service/keploy/atg/tests/test-148.yaml create mode 100755 user_service/keploy/atg/tests/test-149.yaml create mode 100755 user_service/keploy/atg/tests/test-15.yaml create mode 100755 user_service/keploy/atg/tests/test-150.yaml create mode 100755 user_service/keploy/atg/tests/test-151.yaml create mode 100755 user_service/keploy/atg/tests/test-152.yaml create mode 100755 user_service/keploy/atg/tests/test-153.yaml create mode 100755 user_service/keploy/atg/tests/test-154.yaml create mode 100755 user_service/keploy/atg/tests/test-155.yaml create mode 100755 user_service/keploy/atg/tests/test-156.yaml create mode 100755 user_service/keploy/atg/tests/test-157.yaml create mode 100755 user_service/keploy/atg/tests/test-158.yaml create mode 100755 user_service/keploy/atg/tests/test-159.yaml create mode 100755 user_service/keploy/atg/tests/test-16.yaml create mode 100755 user_service/keploy/atg/tests/test-160.yaml create mode 100755 user_service/keploy/atg/tests/test-161.yaml create mode 100755 user_service/keploy/atg/tests/test-162.yaml create mode 100755 user_service/keploy/atg/tests/test-163.yaml create mode 100755 user_service/keploy/atg/tests/test-164.yaml create mode 100755 user_service/keploy/atg/tests/test-165.yaml create mode 100755 user_service/keploy/atg/tests/test-166.yaml create mode 100755 user_service/keploy/atg/tests/test-167.yaml create mode 100755 user_service/keploy/atg/tests/test-168.yaml create mode 100755 user_service/keploy/atg/tests/test-169.yaml create mode 100755 user_service/keploy/atg/tests/test-17.yaml create mode 100755 user_service/keploy/atg/tests/test-170.yaml create mode 100755 user_service/keploy/atg/tests/test-171.yaml create mode 100755 user_service/keploy/atg/tests/test-172.yaml create mode 100755 user_service/keploy/atg/tests/test-173.yaml create mode 100755 user_service/keploy/atg/tests/test-174.yaml create mode 100755 user_service/keploy/atg/tests/test-175.yaml create mode 100755 user_service/keploy/atg/tests/test-176.yaml create mode 100755 user_service/keploy/atg/tests/test-177.yaml create mode 100755 user_service/keploy/atg/tests/test-178.yaml create mode 100755 user_service/keploy/atg/tests/test-179.yaml create mode 100755 user_service/keploy/atg/tests/test-18.yaml create mode 100755 user_service/keploy/atg/tests/test-180.yaml create mode 100755 user_service/keploy/atg/tests/test-181.yaml create mode 100755 user_service/keploy/atg/tests/test-182.yaml create mode 100755 user_service/keploy/atg/tests/test-183.yaml create mode 100755 user_service/keploy/atg/tests/test-184.yaml create mode 100755 user_service/keploy/atg/tests/test-185.yaml create mode 100755 user_service/keploy/atg/tests/test-186.yaml create mode 100755 user_service/keploy/atg/tests/test-187.yaml create mode 100755 user_service/keploy/atg/tests/test-188.yaml create mode 100755 user_service/keploy/atg/tests/test-189.yaml create mode 100755 user_service/keploy/atg/tests/test-19.yaml create mode 100755 user_service/keploy/atg/tests/test-190.yaml create mode 100755 user_service/keploy/atg/tests/test-191.yaml create mode 100755 user_service/keploy/atg/tests/test-192.yaml create mode 100755 user_service/keploy/atg/tests/test-193.yaml create mode 100755 user_service/keploy/atg/tests/test-194.yaml create mode 100755 user_service/keploy/atg/tests/test-195.yaml create mode 100755 user_service/keploy/atg/tests/test-196.yaml create mode 100755 user_service/keploy/atg/tests/test-197.yaml create mode 100755 user_service/keploy/atg/tests/test-198.yaml create mode 100755 user_service/keploy/atg/tests/test-199.yaml create mode 100755 user_service/keploy/atg/tests/test-2.yaml create mode 100755 user_service/keploy/atg/tests/test-20.yaml create mode 100755 user_service/keploy/atg/tests/test-200.yaml create mode 100755 user_service/keploy/atg/tests/test-201.yaml create mode 100755 user_service/keploy/atg/tests/test-202.yaml create mode 100755 user_service/keploy/atg/tests/test-203.yaml create mode 100755 user_service/keploy/atg/tests/test-204.yaml create mode 100755 user_service/keploy/atg/tests/test-205.yaml create mode 100755 user_service/keploy/atg/tests/test-206.yaml create mode 100755 user_service/keploy/atg/tests/test-207.yaml create mode 100755 user_service/keploy/atg/tests/test-208.yaml create mode 100755 user_service/keploy/atg/tests/test-209.yaml create mode 100755 user_service/keploy/atg/tests/test-21.yaml create mode 100755 user_service/keploy/atg/tests/test-210.yaml create mode 100755 user_service/keploy/atg/tests/test-211.yaml create mode 100755 user_service/keploy/atg/tests/test-212.yaml create mode 100755 user_service/keploy/atg/tests/test-213.yaml create mode 100755 user_service/keploy/atg/tests/test-214.yaml create mode 100755 user_service/keploy/atg/tests/test-215.yaml create mode 100755 user_service/keploy/atg/tests/test-216.yaml create mode 100755 user_service/keploy/atg/tests/test-217.yaml create mode 100755 user_service/keploy/atg/tests/test-218.yaml create mode 100755 user_service/keploy/atg/tests/test-219.yaml create mode 100755 user_service/keploy/atg/tests/test-22.yaml create mode 100755 user_service/keploy/atg/tests/test-220.yaml create mode 100755 user_service/keploy/atg/tests/test-221.yaml create mode 100755 user_service/keploy/atg/tests/test-222.yaml create mode 100755 user_service/keploy/atg/tests/test-223.yaml create mode 100755 user_service/keploy/atg/tests/test-224.yaml create mode 100755 user_service/keploy/atg/tests/test-225.yaml create mode 100755 user_service/keploy/atg/tests/test-226.yaml create mode 100755 user_service/keploy/atg/tests/test-227.yaml create mode 100755 user_service/keploy/atg/tests/test-228.yaml create mode 100755 user_service/keploy/atg/tests/test-229.yaml create mode 100755 user_service/keploy/atg/tests/test-23.yaml create mode 100755 user_service/keploy/atg/tests/test-230.yaml create mode 100755 user_service/keploy/atg/tests/test-231.yaml create mode 100755 user_service/keploy/atg/tests/test-232.yaml create mode 100755 user_service/keploy/atg/tests/test-233.yaml create mode 100755 user_service/keploy/atg/tests/test-234.yaml create mode 100755 user_service/keploy/atg/tests/test-235.yaml create mode 100755 user_service/keploy/atg/tests/test-236.yaml create mode 100755 user_service/keploy/atg/tests/test-237.yaml create mode 100755 user_service/keploy/atg/tests/test-238.yaml create mode 100755 user_service/keploy/atg/tests/test-239.yaml create mode 100755 user_service/keploy/atg/tests/test-24.yaml create mode 100755 user_service/keploy/atg/tests/test-240.yaml create mode 100755 user_service/keploy/atg/tests/test-241.yaml create mode 100755 user_service/keploy/atg/tests/test-242.yaml create mode 100755 user_service/keploy/atg/tests/test-243.yaml create mode 100755 user_service/keploy/atg/tests/test-244.yaml create mode 100755 user_service/keploy/atg/tests/test-245.yaml create mode 100755 user_service/keploy/atg/tests/test-246.yaml create mode 100755 user_service/keploy/atg/tests/test-247.yaml create mode 100755 user_service/keploy/atg/tests/test-248.yaml create mode 100755 user_service/keploy/atg/tests/test-249.yaml create mode 100755 user_service/keploy/atg/tests/test-25.yaml create mode 100755 user_service/keploy/atg/tests/test-250.yaml create mode 100755 user_service/keploy/atg/tests/test-251.yaml create mode 100755 user_service/keploy/atg/tests/test-252.yaml create mode 100755 user_service/keploy/atg/tests/test-253.yaml create mode 100755 user_service/keploy/atg/tests/test-254.yaml create mode 100755 user_service/keploy/atg/tests/test-255.yaml create mode 100755 user_service/keploy/atg/tests/test-256.yaml create mode 100755 user_service/keploy/atg/tests/test-257.yaml create mode 100755 user_service/keploy/atg/tests/test-258.yaml create mode 100755 user_service/keploy/atg/tests/test-259.yaml create mode 100755 user_service/keploy/atg/tests/test-26.yaml create mode 100755 user_service/keploy/atg/tests/test-260.yaml create mode 100755 user_service/keploy/atg/tests/test-261.yaml create mode 100755 user_service/keploy/atg/tests/test-262.yaml create mode 100755 user_service/keploy/atg/tests/test-263.yaml create mode 100755 user_service/keploy/atg/tests/test-264.yaml create mode 100755 user_service/keploy/atg/tests/test-265.yaml create mode 100755 user_service/keploy/atg/tests/test-266.yaml create mode 100755 user_service/keploy/atg/tests/test-267.yaml create mode 100755 user_service/keploy/atg/tests/test-268.yaml create mode 100755 user_service/keploy/atg/tests/test-269.yaml create mode 100755 user_service/keploy/atg/tests/test-27.yaml create mode 100755 user_service/keploy/atg/tests/test-270.yaml create mode 100755 user_service/keploy/atg/tests/test-271.yaml create mode 100755 user_service/keploy/atg/tests/test-272.yaml create mode 100755 user_service/keploy/atg/tests/test-273.yaml create mode 100755 user_service/keploy/atg/tests/test-274.yaml create mode 100755 user_service/keploy/atg/tests/test-275.yaml create mode 100755 user_service/keploy/atg/tests/test-276.yaml create mode 100755 user_service/keploy/atg/tests/test-277.yaml create mode 100755 user_service/keploy/atg/tests/test-278.yaml create mode 100755 user_service/keploy/atg/tests/test-279.yaml create mode 100755 user_service/keploy/atg/tests/test-28.yaml create mode 100755 user_service/keploy/atg/tests/test-280.yaml create mode 100755 user_service/keploy/atg/tests/test-281.yaml create mode 100755 user_service/keploy/atg/tests/test-282.yaml create mode 100755 user_service/keploy/atg/tests/test-283.yaml create mode 100755 user_service/keploy/atg/tests/test-284.yaml create mode 100755 user_service/keploy/atg/tests/test-285.yaml create mode 100755 user_service/keploy/atg/tests/test-286.yaml create mode 100755 user_service/keploy/atg/tests/test-287.yaml create mode 100755 user_service/keploy/atg/tests/test-288.yaml create mode 100755 user_service/keploy/atg/tests/test-289.yaml create mode 100755 user_service/keploy/atg/tests/test-29.yaml create mode 100755 user_service/keploy/atg/tests/test-290.yaml create mode 100755 user_service/keploy/atg/tests/test-291.yaml create mode 100755 user_service/keploy/atg/tests/test-292.yaml create mode 100755 user_service/keploy/atg/tests/test-293.yaml create mode 100755 user_service/keploy/atg/tests/test-294.yaml create mode 100755 user_service/keploy/atg/tests/test-295.yaml create mode 100755 user_service/keploy/atg/tests/test-296.yaml create mode 100755 user_service/keploy/atg/tests/test-297.yaml create mode 100755 user_service/keploy/atg/tests/test-298.yaml create mode 100755 user_service/keploy/atg/tests/test-299.yaml create mode 100755 user_service/keploy/atg/tests/test-3.yaml create mode 100755 user_service/keploy/atg/tests/test-30.yaml create mode 100755 user_service/keploy/atg/tests/test-300.yaml create mode 100755 user_service/keploy/atg/tests/test-301.yaml create mode 100755 user_service/keploy/atg/tests/test-302.yaml create mode 100755 user_service/keploy/atg/tests/test-303.yaml create mode 100755 user_service/keploy/atg/tests/test-304.yaml create mode 100755 user_service/keploy/atg/tests/test-305.yaml create mode 100755 user_service/keploy/atg/tests/test-306.yaml create mode 100755 user_service/keploy/atg/tests/test-307.yaml create mode 100755 user_service/keploy/atg/tests/test-308.yaml create mode 100755 user_service/keploy/atg/tests/test-309.yaml create mode 100755 user_service/keploy/atg/tests/test-31.yaml create mode 100755 user_service/keploy/atg/tests/test-310.yaml create mode 100755 user_service/keploy/atg/tests/test-311.yaml create mode 100755 user_service/keploy/atg/tests/test-312.yaml create mode 100755 user_service/keploy/atg/tests/test-313.yaml create mode 100755 user_service/keploy/atg/tests/test-314.yaml create mode 100755 user_service/keploy/atg/tests/test-315.yaml create mode 100755 user_service/keploy/atg/tests/test-316.yaml create mode 100755 user_service/keploy/atg/tests/test-317.yaml create mode 100755 user_service/keploy/atg/tests/test-318.yaml create mode 100755 user_service/keploy/atg/tests/test-319.yaml create mode 100755 user_service/keploy/atg/tests/test-32.yaml create mode 100755 user_service/keploy/atg/tests/test-320.yaml create mode 100755 user_service/keploy/atg/tests/test-321.yaml create mode 100755 user_service/keploy/atg/tests/test-322.yaml create mode 100755 user_service/keploy/atg/tests/test-323.yaml create mode 100755 user_service/keploy/atg/tests/test-324.yaml create mode 100755 user_service/keploy/atg/tests/test-325.yaml create mode 100755 user_service/keploy/atg/tests/test-326.yaml create mode 100755 user_service/keploy/atg/tests/test-327.yaml create mode 100755 user_service/keploy/atg/tests/test-328.yaml create mode 100755 user_service/keploy/atg/tests/test-329.yaml create mode 100755 user_service/keploy/atg/tests/test-33.yaml create mode 100755 user_service/keploy/atg/tests/test-330.yaml create mode 100755 user_service/keploy/atg/tests/test-331.yaml create mode 100755 user_service/keploy/atg/tests/test-332.yaml create mode 100755 user_service/keploy/atg/tests/test-333.yaml create mode 100755 user_service/keploy/atg/tests/test-334.yaml create mode 100755 user_service/keploy/atg/tests/test-335.yaml create mode 100755 user_service/keploy/atg/tests/test-336.yaml create mode 100755 user_service/keploy/atg/tests/test-337.yaml create mode 100755 user_service/keploy/atg/tests/test-338.yaml create mode 100755 user_service/keploy/atg/tests/test-339.yaml create mode 100755 user_service/keploy/atg/tests/test-34.yaml create mode 100755 user_service/keploy/atg/tests/test-340.yaml create mode 100755 user_service/keploy/atg/tests/test-341.yaml create mode 100755 user_service/keploy/atg/tests/test-342.yaml create mode 100755 user_service/keploy/atg/tests/test-343.yaml create mode 100755 user_service/keploy/atg/tests/test-344.yaml create mode 100755 user_service/keploy/atg/tests/test-345.yaml create mode 100755 user_service/keploy/atg/tests/test-346.yaml create mode 100755 user_service/keploy/atg/tests/test-347.yaml create mode 100755 user_service/keploy/atg/tests/test-348.yaml create mode 100755 user_service/keploy/atg/tests/test-349.yaml create mode 100755 user_service/keploy/atg/tests/test-35.yaml create mode 100755 user_service/keploy/atg/tests/test-350.yaml create mode 100755 user_service/keploy/atg/tests/test-351.yaml create mode 100755 user_service/keploy/atg/tests/test-352.yaml create mode 100755 user_service/keploy/atg/tests/test-353.yaml create mode 100755 user_service/keploy/atg/tests/test-354.yaml create mode 100755 user_service/keploy/atg/tests/test-355.yaml create mode 100755 user_service/keploy/atg/tests/test-356.yaml create mode 100755 user_service/keploy/atg/tests/test-357.yaml create mode 100755 user_service/keploy/atg/tests/test-358.yaml create mode 100755 user_service/keploy/atg/tests/test-359.yaml create mode 100755 user_service/keploy/atg/tests/test-36.yaml create mode 100755 user_service/keploy/atg/tests/test-360.yaml create mode 100755 user_service/keploy/atg/tests/test-361.yaml create mode 100755 user_service/keploy/atg/tests/test-362.yaml create mode 100755 user_service/keploy/atg/tests/test-363.yaml create mode 100755 user_service/keploy/atg/tests/test-364.yaml create mode 100755 user_service/keploy/atg/tests/test-365.yaml create mode 100755 user_service/keploy/atg/tests/test-366.yaml create mode 100755 user_service/keploy/atg/tests/test-367.yaml create mode 100755 user_service/keploy/atg/tests/test-368.yaml create mode 100755 user_service/keploy/atg/tests/test-369.yaml create mode 100755 user_service/keploy/atg/tests/test-37.yaml create mode 100755 user_service/keploy/atg/tests/test-370.yaml create mode 100755 user_service/keploy/atg/tests/test-371.yaml create mode 100755 user_service/keploy/atg/tests/test-372.yaml create mode 100755 user_service/keploy/atg/tests/test-373.yaml create mode 100755 user_service/keploy/atg/tests/test-374.yaml create mode 100755 user_service/keploy/atg/tests/test-375.yaml create mode 100755 user_service/keploy/atg/tests/test-376.yaml create mode 100755 user_service/keploy/atg/tests/test-377.yaml create mode 100755 user_service/keploy/atg/tests/test-378.yaml create mode 100755 user_service/keploy/atg/tests/test-379.yaml create mode 100755 user_service/keploy/atg/tests/test-38.yaml create mode 100755 user_service/keploy/atg/tests/test-380.yaml create mode 100755 user_service/keploy/atg/tests/test-381.yaml create mode 100755 user_service/keploy/atg/tests/test-382.yaml create mode 100755 user_service/keploy/atg/tests/test-383.yaml create mode 100755 user_service/keploy/atg/tests/test-384.yaml create mode 100755 user_service/keploy/atg/tests/test-385.yaml create mode 100755 user_service/keploy/atg/tests/test-386.yaml create mode 100755 user_service/keploy/atg/tests/test-387.yaml create mode 100755 user_service/keploy/atg/tests/test-388.yaml create mode 100755 user_service/keploy/atg/tests/test-389.yaml create mode 100755 user_service/keploy/atg/tests/test-39.yaml create mode 100755 user_service/keploy/atg/tests/test-390.yaml create mode 100755 user_service/keploy/atg/tests/test-391.yaml create mode 100755 user_service/keploy/atg/tests/test-392.yaml create mode 100755 user_service/keploy/atg/tests/test-393.yaml create mode 100755 user_service/keploy/atg/tests/test-394.yaml create mode 100755 user_service/keploy/atg/tests/test-395.yaml create mode 100755 user_service/keploy/atg/tests/test-396.yaml create mode 100755 user_service/keploy/atg/tests/test-397.yaml create mode 100755 user_service/keploy/atg/tests/test-398.yaml create mode 100755 user_service/keploy/atg/tests/test-399.yaml create mode 100755 user_service/keploy/atg/tests/test-4.yaml create mode 100755 user_service/keploy/atg/tests/test-40.yaml create mode 100755 user_service/keploy/atg/tests/test-400.yaml create mode 100755 user_service/keploy/atg/tests/test-401.yaml create mode 100755 user_service/keploy/atg/tests/test-402.yaml create mode 100755 user_service/keploy/atg/tests/test-403.yaml create mode 100755 user_service/keploy/atg/tests/test-404.yaml create mode 100755 user_service/keploy/atg/tests/test-405.yaml create mode 100755 user_service/keploy/atg/tests/test-406.yaml create mode 100755 user_service/keploy/atg/tests/test-407.yaml create mode 100755 user_service/keploy/atg/tests/test-408.yaml create mode 100755 user_service/keploy/atg/tests/test-409.yaml create mode 100755 user_service/keploy/atg/tests/test-41.yaml create mode 100755 user_service/keploy/atg/tests/test-410.yaml create mode 100755 user_service/keploy/atg/tests/test-411.yaml create mode 100755 user_service/keploy/atg/tests/test-412.yaml create mode 100755 user_service/keploy/atg/tests/test-413.yaml create mode 100755 user_service/keploy/atg/tests/test-414.yaml create mode 100755 user_service/keploy/atg/tests/test-415.yaml create mode 100755 user_service/keploy/atg/tests/test-416.yaml create mode 100755 user_service/keploy/atg/tests/test-417.yaml create mode 100755 user_service/keploy/atg/tests/test-418.yaml create mode 100755 user_service/keploy/atg/tests/test-419.yaml create mode 100755 user_service/keploy/atg/tests/test-42.yaml create mode 100755 user_service/keploy/atg/tests/test-420.yaml create mode 100755 user_service/keploy/atg/tests/test-421.yaml create mode 100755 user_service/keploy/atg/tests/test-422.yaml create mode 100755 user_service/keploy/atg/tests/test-423.yaml create mode 100755 user_service/keploy/atg/tests/test-424.yaml create mode 100755 user_service/keploy/atg/tests/test-425.yaml create mode 100755 user_service/keploy/atg/tests/test-426.yaml create mode 100755 user_service/keploy/atg/tests/test-427.yaml create mode 100755 user_service/keploy/atg/tests/test-428.yaml create mode 100755 user_service/keploy/atg/tests/test-429.yaml create mode 100755 user_service/keploy/atg/tests/test-43.yaml create mode 100755 user_service/keploy/atg/tests/test-430.yaml create mode 100755 user_service/keploy/atg/tests/test-431.yaml create mode 100755 user_service/keploy/atg/tests/test-432.yaml create mode 100755 user_service/keploy/atg/tests/test-433.yaml create mode 100755 user_service/keploy/atg/tests/test-434.yaml create mode 100755 user_service/keploy/atg/tests/test-435.yaml create mode 100755 user_service/keploy/atg/tests/test-436.yaml create mode 100755 user_service/keploy/atg/tests/test-437.yaml create mode 100755 user_service/keploy/atg/tests/test-438.yaml create mode 100755 user_service/keploy/atg/tests/test-439.yaml create mode 100755 user_service/keploy/atg/tests/test-44.yaml create mode 100755 user_service/keploy/atg/tests/test-440.yaml create mode 100755 user_service/keploy/atg/tests/test-441.yaml create mode 100755 user_service/keploy/atg/tests/test-442.yaml create mode 100755 user_service/keploy/atg/tests/test-443.yaml create mode 100755 user_service/keploy/atg/tests/test-444.yaml create mode 100755 user_service/keploy/atg/tests/test-445.yaml create mode 100755 user_service/keploy/atg/tests/test-446.yaml create mode 100755 user_service/keploy/atg/tests/test-447.yaml create mode 100755 user_service/keploy/atg/tests/test-448.yaml create mode 100755 user_service/keploy/atg/tests/test-449.yaml create mode 100755 user_service/keploy/atg/tests/test-45.yaml create mode 100755 user_service/keploy/atg/tests/test-450.yaml create mode 100755 user_service/keploy/atg/tests/test-451.yaml create mode 100755 user_service/keploy/atg/tests/test-452.yaml create mode 100755 user_service/keploy/atg/tests/test-453.yaml create mode 100755 user_service/keploy/atg/tests/test-454.yaml create mode 100755 user_service/keploy/atg/tests/test-455.yaml create mode 100755 user_service/keploy/atg/tests/test-456.yaml create mode 100755 user_service/keploy/atg/tests/test-457.yaml create mode 100755 user_service/keploy/atg/tests/test-458.yaml create mode 100755 user_service/keploy/atg/tests/test-459.yaml create mode 100755 user_service/keploy/atg/tests/test-46.yaml create mode 100755 user_service/keploy/atg/tests/test-460.yaml create mode 100755 user_service/keploy/atg/tests/test-461.yaml create mode 100755 user_service/keploy/atg/tests/test-462.yaml create mode 100755 user_service/keploy/atg/tests/test-463.yaml create mode 100755 user_service/keploy/atg/tests/test-464.yaml create mode 100755 user_service/keploy/atg/tests/test-465.yaml create mode 100755 user_service/keploy/atg/tests/test-466.yaml create mode 100755 user_service/keploy/atg/tests/test-467.yaml create mode 100755 user_service/keploy/atg/tests/test-468.yaml create mode 100755 user_service/keploy/atg/tests/test-469.yaml create mode 100755 user_service/keploy/atg/tests/test-47.yaml create mode 100755 user_service/keploy/atg/tests/test-470.yaml create mode 100755 user_service/keploy/atg/tests/test-471.yaml create mode 100755 user_service/keploy/atg/tests/test-472.yaml create mode 100755 user_service/keploy/atg/tests/test-473.yaml create mode 100755 user_service/keploy/atg/tests/test-474.yaml create mode 100755 user_service/keploy/atg/tests/test-475.yaml create mode 100755 user_service/keploy/atg/tests/test-476.yaml create mode 100755 user_service/keploy/atg/tests/test-477.yaml create mode 100755 user_service/keploy/atg/tests/test-478.yaml create mode 100755 user_service/keploy/atg/tests/test-479.yaml create mode 100755 user_service/keploy/atg/tests/test-48.yaml create mode 100755 user_service/keploy/atg/tests/test-480.yaml create mode 100755 user_service/keploy/atg/tests/test-481.yaml create mode 100755 user_service/keploy/atg/tests/test-482.yaml create mode 100755 user_service/keploy/atg/tests/test-483.yaml create mode 100755 user_service/keploy/atg/tests/test-484.yaml create mode 100755 user_service/keploy/atg/tests/test-485.yaml create mode 100755 user_service/keploy/atg/tests/test-486.yaml create mode 100755 user_service/keploy/atg/tests/test-487.yaml create mode 100755 user_service/keploy/atg/tests/test-488.yaml create mode 100755 user_service/keploy/atg/tests/test-489.yaml create mode 100755 user_service/keploy/atg/tests/test-49.yaml create mode 100755 user_service/keploy/atg/tests/test-490.yaml create mode 100755 user_service/keploy/atg/tests/test-491.yaml create mode 100755 user_service/keploy/atg/tests/test-492.yaml create mode 100755 user_service/keploy/atg/tests/test-493.yaml create mode 100755 user_service/keploy/atg/tests/test-494.yaml create mode 100755 user_service/keploy/atg/tests/test-495.yaml create mode 100755 user_service/keploy/atg/tests/test-496.yaml create mode 100755 user_service/keploy/atg/tests/test-497.yaml create mode 100755 user_service/keploy/atg/tests/test-498.yaml create mode 100755 user_service/keploy/atg/tests/test-499.yaml create mode 100755 user_service/keploy/atg/tests/test-5.yaml create mode 100755 user_service/keploy/atg/tests/test-50.yaml create mode 100755 user_service/keploy/atg/tests/test-500.yaml create mode 100755 user_service/keploy/atg/tests/test-501.yaml create mode 100755 user_service/keploy/atg/tests/test-502.yaml create mode 100755 user_service/keploy/atg/tests/test-503.yaml create mode 100755 user_service/keploy/atg/tests/test-504.yaml create mode 100755 user_service/keploy/atg/tests/test-505.yaml create mode 100755 user_service/keploy/atg/tests/test-506.yaml create mode 100755 user_service/keploy/atg/tests/test-507.yaml create mode 100755 user_service/keploy/atg/tests/test-508.yaml create mode 100755 user_service/keploy/atg/tests/test-509.yaml create mode 100755 user_service/keploy/atg/tests/test-51.yaml create mode 100755 user_service/keploy/atg/tests/test-510.yaml create mode 100755 user_service/keploy/atg/tests/test-511.yaml create mode 100755 user_service/keploy/atg/tests/test-512.yaml create mode 100755 user_service/keploy/atg/tests/test-513.yaml create mode 100755 user_service/keploy/atg/tests/test-514.yaml create mode 100755 user_service/keploy/atg/tests/test-515.yaml create mode 100755 user_service/keploy/atg/tests/test-516.yaml create mode 100755 user_service/keploy/atg/tests/test-517.yaml create mode 100755 user_service/keploy/atg/tests/test-518.yaml create mode 100755 user_service/keploy/atg/tests/test-519.yaml create mode 100755 user_service/keploy/atg/tests/test-52.yaml create mode 100755 user_service/keploy/atg/tests/test-520.yaml create mode 100755 user_service/keploy/atg/tests/test-521.yaml create mode 100755 user_service/keploy/atg/tests/test-522.yaml create mode 100755 user_service/keploy/atg/tests/test-523.yaml create mode 100755 user_service/keploy/atg/tests/test-524.yaml create mode 100755 user_service/keploy/atg/tests/test-525.yaml create mode 100755 user_service/keploy/atg/tests/test-526.yaml create mode 100755 user_service/keploy/atg/tests/test-527.yaml create mode 100755 user_service/keploy/atg/tests/test-528.yaml create mode 100755 user_service/keploy/atg/tests/test-529.yaml create mode 100755 user_service/keploy/atg/tests/test-53.yaml create mode 100755 user_service/keploy/atg/tests/test-530.yaml create mode 100755 user_service/keploy/atg/tests/test-531.yaml create mode 100755 user_service/keploy/atg/tests/test-532.yaml create mode 100755 user_service/keploy/atg/tests/test-533.yaml create mode 100755 user_service/keploy/atg/tests/test-534.yaml create mode 100755 user_service/keploy/atg/tests/test-535.yaml create mode 100755 user_service/keploy/atg/tests/test-536.yaml create mode 100755 user_service/keploy/atg/tests/test-537.yaml create mode 100755 user_service/keploy/atg/tests/test-538.yaml create mode 100755 user_service/keploy/atg/tests/test-539.yaml create mode 100755 user_service/keploy/atg/tests/test-54.yaml create mode 100755 user_service/keploy/atg/tests/test-540.yaml create mode 100755 user_service/keploy/atg/tests/test-541.yaml create mode 100755 user_service/keploy/atg/tests/test-542.yaml create mode 100755 user_service/keploy/atg/tests/test-55.yaml create mode 100755 user_service/keploy/atg/tests/test-56.yaml create mode 100755 user_service/keploy/atg/tests/test-57.yaml create mode 100755 user_service/keploy/atg/tests/test-58.yaml create mode 100755 user_service/keploy/atg/tests/test-59.yaml create mode 100755 user_service/keploy/atg/tests/test-6.yaml create mode 100755 user_service/keploy/atg/tests/test-60.yaml create mode 100755 user_service/keploy/atg/tests/test-61.yaml create mode 100755 user_service/keploy/atg/tests/test-62.yaml create mode 100755 user_service/keploy/atg/tests/test-63.yaml create mode 100755 user_service/keploy/atg/tests/test-64.yaml create mode 100755 user_service/keploy/atg/tests/test-65.yaml create mode 100755 user_service/keploy/atg/tests/test-66.yaml create mode 100755 user_service/keploy/atg/tests/test-67.yaml create mode 100755 user_service/keploy/atg/tests/test-68.yaml create mode 100755 user_service/keploy/atg/tests/test-69.yaml create mode 100755 user_service/keploy/atg/tests/test-7.yaml create mode 100755 user_service/keploy/atg/tests/test-70.yaml create mode 100755 user_service/keploy/atg/tests/test-71.yaml create mode 100755 user_service/keploy/atg/tests/test-72.yaml create mode 100755 user_service/keploy/atg/tests/test-73.yaml create mode 100755 user_service/keploy/atg/tests/test-74.yaml create mode 100755 user_service/keploy/atg/tests/test-75.yaml create mode 100755 user_service/keploy/atg/tests/test-76.yaml create mode 100755 user_service/keploy/atg/tests/test-77.yaml create mode 100755 user_service/keploy/atg/tests/test-78.yaml create mode 100755 user_service/keploy/atg/tests/test-79.yaml create mode 100755 user_service/keploy/atg/tests/test-8.yaml create mode 100755 user_service/keploy/atg/tests/test-80.yaml create mode 100755 user_service/keploy/atg/tests/test-81.yaml create mode 100755 user_service/keploy/atg/tests/test-82.yaml create mode 100755 user_service/keploy/atg/tests/test-83.yaml create mode 100755 user_service/keploy/atg/tests/test-84.yaml create mode 100755 user_service/keploy/atg/tests/test-85.yaml create mode 100755 user_service/keploy/atg/tests/test-86.yaml create mode 100755 user_service/keploy/atg/tests/test-87.yaml create mode 100755 user_service/keploy/atg/tests/test-88.yaml create mode 100755 user_service/keploy/atg/tests/test-89.yaml create mode 100755 user_service/keploy/atg/tests/test-9.yaml create mode 100755 user_service/keploy/atg/tests/test-90.yaml create mode 100755 user_service/keploy/atg/tests/test-91.yaml create mode 100755 user_service/keploy/atg/tests/test-92.yaml create mode 100755 user_service/keploy/atg/tests/test-93.yaml create mode 100755 user_service/keploy/atg/tests/test-94.yaml create mode 100755 user_service/keploy/atg/tests/test-95.yaml create mode 100755 user_service/keploy/atg/tests/test-96.yaml create mode 100755 user_service/keploy/atg/tests/test-97.yaml create mode 100755 user_service/keploy/atg/tests/test-98.yaml create mode 100755 user_service/keploy/atg/tests/test-99.yaml create mode 100644 user_service/keploy/load/reports/20251110_113538_suite-0.json create mode 100644 user_service/keploy/load/reports/20251110_113651_suite-0.json create mode 100644 user_service/keploy/load/reports/20251110_125123_suite-0.json create mode 100755 user_service/keploy/testsuite/keploy.yml create mode 100644 user_service/keploy/testsuite/suite-0.yaml create mode 100644 user_service/keploy/testsuite/ts_reports/20251110112827_report_suite-0.yaml diff --git a/.github/workflows/keploy-e2e.yml b/.github/workflows/keploy-e2e.yml new file mode 100644 index 0000000..20697dc --- /dev/null +++ b/.github/workflows/keploy-e2e.yml @@ -0,0 +1,89 @@ +name: Keploy E2E on PR + +on: + pull_request: + branches: [ main ] + +permissions: + contents: read + +concurrency: + group: keploy-e2e-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + keploy-e2e: + name: Install Keploy, build, and run tests + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Show Docker and Compose versions + run: | + docker --version + docker compose version + + - name: Install Keploy CLI + shell: bash + run: | + set -euo pipefail + curl --silent -O -L https://keploy.io/install.sh + # Source in current shell so the script can modify PATH for this step if needed + source install.sh + # Verify installation + command -v keploy + keploy version || true + + - name: Build Docker images (compose) + run: | + docker compose build --pull + + - name: Run Keploy tests against docker compose + env: + # Increase Docker's default logs size to keep useful output (optional) + COMPOSE_HTTP_TIMEOUT: "200" + run: | + # Run keploy with the provided command and options + keploy test -c "docker compose up" \ + --container-name="user_service" \ + --path="./user_service" \ + --config-path="./user_service" \ + --delay 15 \ + -t test-set-0 + + - name: Upload Keploy artifacts (user_service) + if: always() + uses: actions/upload-artifact@v4 + with: + name: keploy-user_service-artifacts + path: | + user_service/keploy/** + if-no-files-found: warn + retention-days: 7 + + - name: Upload service coverage (optional) + if: always() + uses: actions/upload-artifact@v4 + with: + name: service-coverage + path: | + coverage/** + apigateway/coverage/** + user_service/coverage/** + product_service/coverage/** + order_service/coverage/** + if-no-files-found: ignore + retention-days: 7 + + - name: Teardown docker compose + if: always() + run: | + docker compose ps + docker compose logs --no-color | tail -n 200 || true + docker compose down -v --remove-orphans diff --git a/coverage/.coverage.order_service.combined b/coverage/.coverage.order_service.combined index ec3d1fa24f8951a956cb9b1d763cb3e9015c1946..f8d77e960a98ca26e308930b90cf5cf28490f3d6 100644 GIT binary patch delta 1518 zcmXxjPiP!<6u|L$t7d%3@4fCWDB3?sn)F|0(`>qJvrYOpX*T_r=8r;@9%cr?9t1@^ zh#+Pbg?KC0T$(>ki)||OA}ROn6eD1xxFh^X}-h*CrpK{gxT&TroBvu_>)!+u}& z=EK#S553a`^kMhUH(YWYXV%mf-9KlZUg9hK7Qea?#`kQ{E zAL%>#lJ3z*^ggZ9Ra&6e>1BGJF3~iN(=ZKCFFip-8S}6C!`wHuk8|066XEP5h?xwm z!yTNlybbF(ZFviBUa(w&RXm@tc^OvloSiPiEj(+v1j~5F z@)|7RM2k@dBB)};j;nAD$1Ue&dRnIuj#)-9kE6N};VH{drY9|Z2=Rnv06reqg#eG~ zG{mF28(=hIotNcd%Pi#ah-D74Sk{Fc4(T+HhjllHhY~tXX9EvfZa7UWSvF*PAYG7` z4eCMziwPsAA4 zH9_pTPbTOqEqa?Z)1DN(;F#}ubFu&1!I!om|HHrYeUABS{(?W{4|tv5;J0~&uktH= zS*jl6OFYFM{l}f0rw8;4w^5DmNY`J{XS7UL$fp+{e{bh)$~|_%Zc7M5Ag+{*+ppKdF}JPpCr(j(4<0 z_Ui)(kLd#+f}?5)e?%?i5342pBkDZl%IX~Chtyff9ZucK|BzbxKd5d*&PGXpG$DIH zz2P(l)p7$xwH(2KTCQNfTF#(fU3cPwI(F)wy5_`v?ecd+2ILg>VT)1g81^Ov2>d;% z!eF<~!dkJ5=8GR9;~lw&hyq2(h;{9m;9s Ke|qp literal 69632 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 Ui{)aPh?pAuFc33E#MIySKktxyp8x;= diff --git a/coverage/.coverage.user_service.combined b/coverage/.coverage.user_service.combined index 95fbddccceb85796b88a52ed661b3e877d63ed79..956af0ee1ec60eccdb765152b7b868a47a028c08 100644 GIT binary patch delta 3884 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 J@w3CD{s&>mW%K|5 delta 1899 zcmYM!Uuc_E7{~E*QrqLRo^#TFTavDAlKn}SbV-_S?Y4I9x~~7zv`d?I)r)~i6qMnG z$OJEZ$Dm%MRg|iTyJ)KDjo_xckcsGpqBLa)yRaZAR1n$R#QUTXmDJ)m7>r^}G5- z{h+>6U#olSBlVtoTfL%gsu$EX^@N&JN!719RYdt!g971R(>u&mLJGl z@-?||x3=bfwFUE)04^8L@L9NGIKwkAXE@DgVAe3jCm~}v!Ba3}nB)^MZ8*-8khb~o zQ$7xt%{s=XVajlnkHMtj2p@%1nU;&U@e!CXa~qF9(lEl?VBFA$>zJX3`(V`2%RMkc z2VNMa)dxd#>xDs^Y@hAHCBr?o4+Dm~xLOyT3w*bJy4Zyabg>V8hC6H*dJVVO4xBgK zW?Rrh2iwq1>kgcw+if^&xQ@4H4AnqRE~TNpaXl zdqOnYh_?%$jaXcGZA4?jV`C#K4%rB&#X%dPP0?T@7!vgc0a0fo5EQjGHUpx@hCeLa zm9T8xUN|r>V@%$3H@hEOZn@*Pa_%Yjkh{m7aJRba-Jt)~f9hZLrryx^^;i0yeqG&IL;a<-4(UeiQakE1^^N*TJ-{~KRnMuH)LW{co>E=vaW#ou zwx|JBCx4c|sVe!m{9cyi7xEMA@*VlAye{Q4@~WJc!?Ih(Fw*PQR&|x#XgSh(GFEzp zjG4}nvC~;HhMFN`sWW6ub=q;c)imuG>t!<5I_22n*y|)cz+O}TJ2FAWVv}S{cASjO zj*&6iQL>MFN62388zy_WcgS%$+`;N9{F2~*D2C}?q6gUS0C}H#E|T}yZa;aK?Oz~c zzI|lux0j3opC|9I?H=+r+vz56S#0|p{eTspC1b{C$e3{#d7YIy$r$o!@)|3hB4f%O zWNbM>#+cj5tE?C=+bVzhB3q5o0}MJ!#-dM>G3hokCLJL!vBfZXku9AdW7Vw(@E33w z$E-t8X1VL*j&j*!Wm!1pb4O{+AECos&`~BEaFovY9i=u}93@lDjuOcxN9~Ejj^gc& yj$(12qiD?QXd~)@LGyQo(}$cfw0Y1`Fw{T_)H@0U>l|$cY90B*cdvc: + needs: [build-and-upload, upload-latest] + uses: ./.github/workflows/my_.yml + ``` + Replace `` with the name of your workflow. + **Note:** *If your sample needs the pre‑built Docker image add `build-docker-image` to the `needs` list.* + +--- + +## 5. Troubleshooting + +When a workflow fails, and log isn't visible, then it might be due to the error happening not in record/replay step, but somewhere else, could be that some referenced binary was not found (exit code 127), could be some permission issues, could be file not found. Look for the exit code to determine what failed (https://tldp.org/LDP/abs/html/exitcodes.html), and check the previous successful step to pipe out the line in bash script related to the workflow run. + +--- + diff --git a/keploy/.github/FUNDING.yml b/keploy/.github/FUNDING.yml new file mode 100755 index 0000000..24b566c --- /dev/null +++ b/keploy/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [keploy] diff --git a/keploy/.github/ISSUE_TEMPLATE/--bug-report.yaml b/keploy/.github/ISSUE_TEMPLATE/--bug-report.yaml new file mode 100755 index 0000000..fc9d7bb --- /dev/null +++ b/keploy/.github/ISSUE_TEMPLATE/--bug-report.yaml @@ -0,0 +1,167 @@ +name: Bug report +description: Create a bug report to help us improve Keploy +title: "[bug]: " +labels: [bug] +body: +- type: markdown + attributes: + value: | + Thanks for taking the time to fill out our bug report form 🙏 + +- type: checkboxes + attributes: + label: "👀 Is there an existing issue for this?" + description: Please search to see if an issue already exists for the bug you encountered + options: + - label: I have searched and didn't find similar issue + required: true + +- type: textarea + attributes: + label: 👍 Current behavior + description: A concise description of what you're experiencing and what you expect + placeholder: | + When I do , happens and I see the error message attached below: + ```...``` + What I expect is + validations: + required: true + +- type: textarea + attributes: + label: 👟 Steps to Replicate + description: How do you trigger this bug? Please walk us through it step by step. + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + +- type: textarea + id: logs + attributes: + label: 📜 Logs (if any) + description: Paste any relevant logs here. You can also upload `.log` files via drag & drop. + placeholder: | + ``` + paste stack trace, application log, or keploy logs here + ``` + validations: + required: false + +- type: dropdown + id: operating-system + attributes: + label: "💻 Operating system" + description: What OS is your server/device running on? + options: + - Linux + - MacOS + - Windows + - Something else + validations: + required: true + +- type: textarea + id: uname + attributes: + label: 🧾 System Info (`uname -a`) + description: Run "uname -a" on your terminal and paste the output (macOS/Linux only). + placeholder: | + Darwin MacBook-Pro.local 22.6.0 Darwin Kernel Version ... + validations: + required: false + +- type: textarea + id: os-release + attributes: + label: 📦 OS Release Info (`cat /etc/os-release`) + description: Run "cat /etc/os-release" (Linux only) and paste the full output here. + placeholder: | + NAME="Ubuntu" + VERSION="22.04.4 LTS (Jammy Jellyfish)" + ... + validations: + required: false + +- type: textarea + id: docker + attributes: + label: 🐳 Docker Info (if applicable) + description: | + Are you running inside Docker? If yes, please mention: + - Docker version + - Docker Desktop version (if used) + - Build image + - Runtime image + placeholder: | + Yes, running inside Docker. + - Docker version: 24.0.2 + - Docker Desktop version: 4.24.2 + - Build image: golang:1.21-alpine + - Runtime image: alpine:3.18 + validations: + required: false + +- type: textarea + id: environment + attributes: + label: "🧱 Your Environment" + description: Include any infrastructure info (e.g., local machine, VM, cloud, Kubernetes etc.) + placeholder: | + - Running on Kubernetes (GKE) + - 3-node cluster + validations: + required: false + +- type: textarea + id: version + attributes: + label: 🎲 Version + description: | + You can find your current Keploy version by running "keploy -v" or "keploy --version" in your terminal. + placeholder: | + I am using latest version v2.5.2 + validations: + required: true + +- type: dropdown + id: repository + attributes: + label: 📦 Repository + options: + - keploy + - go-sdk + - java-sdk + - python-sdk + - typescript-sdk + - docs + - website + - writers-program + - blog-website + - ui + - vscode-extension + - jetbrains-plugin + - samples-go + - samples-java + - samples-rust + - samples-python + - samples-csharp + - samples-typescript + validations: + required: true + +- type: textarea + id: use-case + attributes: + label: 🤔 What use case were you trying? (optional) + description: Tell us briefly what you were trying to achieve when this bug occurred. + placeholder: | + I was trying to capture test cases during a login API call... + +- type: markdown + attributes: + value: | + I have read the [Code of Conduct](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) and followed [Contribution Guide](https://keploy.io/docs/keploy-explained/contribution-guide/) \ No newline at end of file diff --git a/keploy/.github/ISSUE_TEMPLATE/--documentation-update.yaml b/keploy/.github/ISSUE_TEMPLATE/--documentation-update.yaml new file mode 100755 index 0000000..17b9460 --- /dev/null +++ b/keploy/.github/ISSUE_TEMPLATE/--documentation-update.yaml @@ -0,0 +1,48 @@ +name: Documentation Update📄 +description: Suggest an improvement/addition in the Keploy Server Docs. +title: "[docs]: " +labels: [Documentation] + +body: + - type: markdown + attributes: + value: Thank you for taking the time to our documentation better 🙏 + - type: checkboxes + attributes: + label: "👀 Is there an existing issue for this?" + description: Please search to see if an issue already exists for the bug you encountered + options: + - label: I have searched and didn't find similar issue + required: true + - type: textarea + attributes: + label: "💭 Description" + description: "A clear and concise description of what the issue is." + placeholder: "Documentation is ..." + - type: dropdown + id: repository + attributes: + label: 💻 Repository + options: + - keploy + - go-sdk + - java-sdk + - python-sdk + - typescript-sdk + - docs + - website + - writers-program + - blog-website + - ui + - samples-go + - samples-java + - samples-rust + - samples-python + - samples-csharp + - samples-typescript + validations: + required: true + - type: markdown + attributes: + value: | + I have read the [Code of Conduct](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) \ No newline at end of file diff --git a/keploy/.github/ISSUE_TEMPLATE/--feature-request.yaml b/keploy/.github/ISSUE_TEMPLATE/--feature-request.yaml new file mode 100755 index 0000000..272386c --- /dev/null +++ b/keploy/.github/ISSUE_TEMPLATE/--feature-request.yaml @@ -0,0 +1,60 @@ +name: Feature request +description: Suggest a feature to improve Keploy +title: "[feature]: " +labels: [Enhancement] +body: +- type: markdown + attributes: + value: | + Thank you for taking the time to request a feature for Keploy🙏 +- type: checkboxes + attributes: + label: 👀 Is there an existing feature request for this? + description: Please search to see if an issue related to this feature request/feature request already exists + options: + - label: I have searched the existing issues + required: true +- type: textarea + attributes: + label: 🔖 Enhancement description + description: A clear and concise description of what the enhancement is. + placeholder: | + "I would like to see ..." + validations: + required: true +- type: textarea + attributes: + label: 🎤 Why should this be worked on? + description: A concise description of the problems or use cases for this feature request + placeholder: "In my use-case, ..." + validations: + required: true +- type: dropdown + id: repository + attributes: + label: 💻 Repository + options: + - keploy + - go-sdk + - java-sdk + - python-sdk + - typescript-sdk + - docs + - website + - writers-program + - blog-website + - ui + - vscode-extension + - jetbrains-plugin + - samples-go + - samples-java + - samples-rust + - samples-python + - samples-csharp + - samples-typescript + validations: + required: true +- type: markdown + attributes: + value: | + I have read the [Code of Conduct](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) diff --git a/keploy/.github/ISSUE_TEMPLATE/config.yml b/keploy/.github/ISSUE_TEMPLATE/config.yml new file mode 100755 index 0000000..6d0c5b2 --- /dev/null +++ b/keploy/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +contact_links: + - name: Help and support + url: https://github.com/keploy/keploy#community-support + about: Reach out to us on our Slack channel or Discourse discussions or GitHub discussions. + - name: Dedicated support + url: mailto:hello@keploy.io + about: Write to us if you'd like dedicated support using Keploy \ No newline at end of file diff --git a/keploy/.github/License-Apache_2.0-blue.svg b/keploy/.github/License-Apache_2.0-blue.svg new file mode 100755 index 0000000..ca466ca --- /dev/null +++ b/keploy/.github/License-Apache_2.0-blue.svg @@ -0,0 +1 @@ +License: Apache 2.0LicenseApache 2.0 \ No newline at end of file diff --git a/keploy/.github/PULL_REQUEST_TEMPLATE.md b/keploy/.github/PULL_REQUEST_TEMPLATE.md new file mode 100755 index 0000000..7fdf2b3 --- /dev/null +++ b/keploy/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,70 @@ +## Describe the changes that are made +- + +## Links & References + +**Closes:** #[issue number that will be closed through this PR] +- NA (if very small change like typo, linting, etc.) + +### 🔗 Related PRs +- NA +### 🐞 Related Issues +- NA +### 📄 Related Documents +- NA + +## What type of PR is this? (check all applicable) +- [ ] 📦 Chore +- [ ] 🍕 Feature +- [ ] 🐞 Bug Fix +- [ ] 📝 Documentation Update +- [ ] 🎨 Style +- [ ] 🧑‍💻 Code Refactor +- [ ] 🔥 Performance Improvements +- [ ] ✅ Test +- [ ] 🔁 CI +- [ ] ⏩ Revert + +## Added e2e test pipeline? +- [ ] 👍 yes +- [ ] 🙅 no, because they aren't needed +- [ ] 🙋 no, because I need help + +## Added comments for hard-to-understand areas? +- [ ] 👍 yes +- [ ] 🙅 no, because the code is self-explanatory + +## Added to documentation? +- [ ] 📜 README.md +- [ ] 📓 Wiki +- [ ] 🙅 no documentation needed + +## Are there any sample code or steps to test the changes? +- [ ] 👍 yes, mentioned below +- [ ] 🙅 no, because it is not needed + +## Self Review done? +- [ ] ✅ yes +- [ ] ❌ no, because I need help + +## Any relevant screenshots, recordings or logs? +- NA + +## 🧠 Semantics for PR Title & Branch Name + +Please ensure your PR title and branch name follow the Keploy semantics: + +📌 [PR Semantics Guide](https://github.com/keploy/keploy/wiki/PR-Semantics) +📌 [Branch Semantics Guide](https://github.com/keploy/keploy/wiki/Branch-Semantics) + +**Examples:** + +- **PR Title**: `fix: patch MongoDB document update bug` +- **Branch Name**: `feat/#1-login-flow` (You may skip mentioning the issue number in the branch name if the change is small and the PR description clearly explains it.) + +--- + +## Additional checklist: +- [ ] Have you read the [Contributing Guidelines on issues](https://keploy.io/docs/keploy-explained/contribution-guide/)? +- [ ] Have you followed the [PR Semantics guide](https://github.com/keploy/keploy/wiki/PR-Semantics) for naming this PR? +- [ ] Have you followed the [Branch Semantics guide](https://github.com/keploy/keploy/wiki/Branch-Semantics) for naming your branch? \ No newline at end of file diff --git a/keploy/.github/actions/download-binary/action.yml b/keploy/.github/actions/download-binary/action.yml new file mode 100644 index 0000000..9ec0942 --- /dev/null +++ b/keploy/.github/actions/download-binary/action.yml @@ -0,0 +1,28 @@ +name: get-binary +description: "Download build or latest Keploy and expose its absolute path" + +inputs: + src: + required: true + description: "build | latest" + +outputs: + path: + description: "binary path" + value: ${{ steps.set-path.outputs.path }} + +runs: + using: composite + steps: + - uses: actions/download-artifact@v4 + with: + name: ${{ inputs.src }} + path: ${{ inputs.src }} + + - name: Set binary path + id: set-path + shell: bash + run: | + ls -l + chmod +x ${{ inputs.src }}/keploy + echo "path=$PWD/${{ inputs.src }}/keploy" >> $GITHUB_OUTPUT diff --git a/keploy/.github/actions/download-image/action.yml b/keploy/.github/actions/download-image/action.yml new file mode 100644 index 0000000..bccc056 --- /dev/null +++ b/keploy/.github/actions/download-image/action.yml @@ -0,0 +1,12 @@ +name: pull-image +description: "Pull Image" +runs: + using: composite + steps: + - name: Pull Image + shell: bash + run: | + docker pull ttl.sh/keploy/keploy:1h + # since keploy expects the image to be tagged as ghcr.io/keploy/keploy:v2-dev so we need to rename it + # https://stackoverflow.com/questions/70531871/how-pull-a-docker-image-and-give-it-a-diferente-name + docker tag ttl.sh/keploy/keploy:1h ghcr.io/keploy/keploy:v2-dev \ No newline at end of file diff --git a/keploy/.github/actions/tester/action.yml b/keploy/.github/actions/tester/action.yml new file mode 100644 index 0000000..2ae1353 --- /dev/null +++ b/keploy/.github/actions/tester/action.yml @@ -0,0 +1,112 @@ +name: 'Keploy test Keploy' +description: "An action to test keploy with keploy" +author: gouravkrosx +branding: + icon: 'refresh-cw' + color: 'orange' + +inputs: + working-directory: + description: Relative path where your application code is located + required: true + command: + description: Command to run the application + required: true + keploy-path: + description: Path to keploy pre-recorded test cases + required: true + default: . + config-path: + description: Path to the config file + required: true + default: . + delay: + description: Time to start application + required: true + default: 10 + keploy-record-bin: + description: Path to keploy record binary (Build or Released) + required: true + keploy-test-bin: + description: Path to keploy test binary (Build or Released) + required: true + mode: + description: Mode to identify the workflow type + required: true + +runs: + using: "composite" + steps: + - name: Print the input data. + run: | + echo "Working Directory: ${{ inputs.working-directory }}" + echo "Command: ${{ inputs.command }}" + echo "Keploy Path: ${{ inputs.keploy-path }}" + echo "Config Path: ${{ inputs.config-path }}" + echo "Delay: ${{ inputs.delay }}" + echo "Keploy Record Binary: ${{ inputs.keploy-record-bin }}" + echo "Keploy Test Binary: ${{ inputs.keploy-test-bin }}" + echo "Mode: ${{ inputs.mode }}" + shell: bash + + - name: Grant permissions + run: chmod +x ${GITHUB_ACTION_PATH}/install.sh + shell: sh + - id: keploy-test-keploy + name: Run Script + run: | + ${GITHUB_ACTION_PATH}/install.sh + + shell: bash + env: + WORKDIR: ${{ inputs.working-directory }} + COMMAND : ${{ inputs.command }} + KEPLOY_PATH: ${{inputs.keploy-path}} + CONFIG_PATH: ${{inputs.config-path}} + DELAY: ${{ inputs.delay }} + KEPLOY_RECORD_BIN: ${{ inputs.keploy-record-bin }} + KEPLOY_TEST_BIN: ${{ inputs.keploy-test-bin }} + MODE: ${{ inputs.mode }} + + - name: Get coverage data and zip it + if: success() + id: coverage-zip # Unique ID for the step + run: | + echo "Output from script: ${{ steps.keploy-test-keploy.outputs.script_output }}" + workdir="${{ inputs.working-directory }}/" + workdir="${workdir//\//-}" + workdir="${workdir%-}" + + coverage="coverage-reports-${workdir}-${{ inputs.mode }}" + mv ${{ inputs.working-directory }}/coverage-reports ./$coverage + echo "coverage=$coverage" + echo "::set-output name=coverage_name::$coverage" + zip -r ${coverage}.zip $coverage + shell: bash + + - name: Upload coverage zip as artifact + if: steps.coverage-zip.outputs.coverage_name != '' + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.coverage-zip.outputs.coverage_name }} + path: ${{ steps.coverage-zip.outputs.coverage_name }}.zip + + # - name: Comment on PR + # if: success() + # uses: actions/github-script@v6 + # env: + # KEPLOY_REPORT: ${{ steps.keploy-test-report.outputs.KEPLOY_REPORT }} + # with: + # github-token: ${{ github.token }} + # script: | + # if (!process.env.KEPLOY_REPORT) { + # console.error('Error: KEPLOY_REPORT not found.'); + # process.exit(1); + # } + # github.rest.issues.createComment({ + # issue_number: context.issue.number, + # owner: context.repo.owner, + # repo: context.repo.repo, + # body: process.env.KEPLOY_REPORT + # }) + # shell: bash \ No newline at end of file diff --git a/keploy/.github/actions/tester/install.sh b/keploy/.github/actions/tester/install.sh new file mode 100644 index 0000000..5bff962 --- /dev/null +++ b/keploy/.github/actions/tester/install.sh @@ -0,0 +1,172 @@ +#Print the current github workspace +echo Github_workspace "${GITHUB_WORKSPACE}" + +# Add fake installation-id for the workflow. +source ${GITHUB_WORKSPACE}/.github/workflows/test_workflow_scripts/test-iid.sh + +delete_if_exists() { + local path=$1 + if [ -e "$path" ]; then + sudo rm -rf "$path" + fi +} + + +check_test_status() { + local path=$1 + local fixed_index=$2 # Boolean to determine if index should be fixed to 0 + local overallStatus=1 # true + local idx=0 # Initialize index + for dir in $test_sets; do + if [ "$fixed_index" -eq 1 ]; then + local report_file="$path/keploy/reports/test-run-0/$dir-report.yaml" + else + local report_file="$path/keploy/reports/test-run-$idx/$dir-report.yaml" + idx=$((idx + 1)) + fi + + local test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') + + if [ "$test_status" != "PASSED" ]; then + overallStatus=0 # false + fi + done + echo $overallStatus +} + + +# Go to the working directory +echo "Working directory: ${WORKDIR}" +cd "${WORKDIR}" + +# Make a directory to dump coverage +mkdir coverage-reports + +# Set GOCOVERDIR to the coverage directory +export GOCOVERDIR="./coverage-reports" + +#### Recording Phase of test-bench #### +pre_rec="${KEPLOY_PATH}" + +# Delete the reports directory if it exists +delete_if_exists "$pre_rec/keploy/reports" + +# Get all directories except the 'reports' directory and maintain order +test_sets=$(find "$pre_rec/keploy/" -mindepth 1 -maxdepth 1 -type d ! -name "reports" | sort | xargs -n 1 basename) + + +# Check the exit status of the find command +if [ $? -ne 0 ]; then + echo "Error: No such file or directory." + echo "::set-output name=script_output::failure" + exit 1 +fi + +# Count the number of directories found +num_test_sets=$(echo "$test_sets" | wc -l) + +# Check if the number of directories is zero +if [ "$num_test_sets" -eq 0 ]; then + echo "No test sets found." + echo "::set-output name=script_output::failure" + exit 1 +fi + +# Loop over each directory stored in 'test_sets' +for dir in $test_sets; do + echo "Recording and replaying for (test-set): $dir" +# MODE (0, recordHosted,testBuild) , (1, recordBuild, testHosted) + if [ "$MODE" -eq 0 ]; then + echo "Latest version of keploy is being used for recording, Build version of keploy is being used for testing" + else + echo "Build version of keploy is being used for recording, Latest version of keploy is being used for testing" + fi + + sudo -E env PATH=$PATH ${KEPLOY_RECORD_BIN} record -c "sudo -E env PATH=$PATH ${KEPLOY_TEST_BIN} test -c '${COMMAND}' --proxyPort 56789 --dnsPort 46789 --delay=${DELAY} --testsets $dir --configPath '${CONFIG_PATH}' --path '$pre_rec' --enableTesting " --path "./test-bench/" --proxyPort=36789 --dnsPort 26789 --configPath "${CONFIG_PATH}" --enableTesting + # Wait for 1 second before new test-set + sleep 1 +done + +sleep 5 + +# Check whether the original tests passed or failed +overallStatus=$(check_test_status "$pre_rec" 0) +echo "Overall TestRun status for pre-recorded testscase ran via test-bench: $overallStatus" +if [ "$overallStatus" -eq 0 ]; then + echo "Pre-recorded testcases failed. Exiting..." + delete_if_exists "$pre_rec/keploy/reports" + echo "::set-output name=script_output::failure" + exit 1 +fi + +echo "Successfully recorded tests and mocks via test-bench 🎉" + +#### Testing Phase of test-bench #### +test_bench_rec="./test-bench" + +## Test assertion +pilot -test-assert -preRecPath $pre_rec -testBenchPath $test_bench_rec +exit_status=$? +echo "Test assertion exit status: $exit_status" +if [ $exit_status -eq 1 ]; then + echo "Test assertion failed with exit status $exit_status." + echo "::set-output name=script_output::failure" + exit 1 +fi + +echo "Tests are asserted successfully 🎉" + + +## Mock assertion preparation + +pilot -mock-assert -preRecPath $pre_rec -testBenchPath $test_bench_rec +exit_status=$? +echo "Mock assertion preparation exit status: $exit_status" +if [ $exit_status -eq 1 ]; then + echo "Mock assertion preparation failed with exit status $exit_status." + echo "::set-output name=script_output::failure" + exit 1 +fi + +echo "Mock assertion prepared successfully 🎉" + +## Now run the tests both for pre-recorded test cases and test-bench recorded test cases to compare the mocks (mock assertion) +delete_if_exists "$pre_rec/keploy/reports" + +## Run tests for pre-recorded test cases +sudo -E env PATH=$PATH keployR test -c "${COMMAND}" --delay ${DELAY} --path "$pre_rec" + +sleep 5 + +overallStatus=$(check_test_status "$pre_rec" 1) +echo "Overall TestRun status for pre-recorded testscase (after mock assertion): $overallStatus" +if [ "$overallStatus" -eq 0 ]; then + echo "Newly recorded mocks are not consistent with the pre-recorded mocks." + echo "::set-output name=script_output::failure" + exit 1 +fi +echo "New mocks are consistent with the pre-recorded mocks 🎉" + + +## Run tests for test-bench-recorded test cases +sudo -E env PATH=$PATH keployR test -c "${COMMAND}" --delay ${DELAY} --path "$test_bench_rec" + +sleep 5 + +overallStatus=$(check_test_status "$test_bench_rec" 1) +echo "Overall TestRun status for test-bench-recorded testscase (after mock assertion): $overallStatus" +if [ "$overallStatus" -eq 0 ]; then + echo "Old recorded mocks are not consistent with the test-bench-recorded mocks." + delete_if_exists "$test_bench_rec" + echo "::set-output name=script_output::failure" + exit 1 +fi +echo "Old mocks are consistent with the test-bench-recorded mocks 🎉" + +# Delete the tests and mocks generated via test-bench. +delete_if_exists "$test_bench_rec" + +echo "Tests and mocks are consistent for this application 🎉" +echo "::set-output name=script_output::success" + +exit 0 \ No newline at end of file diff --git a/keploy/.github/docs.svg b/keploy/.github/docs.svg new file mode 100755 index 0000000..79e1deb --- /dev/null +++ b/keploy/.github/docs.svg @@ -0,0 +1,21 @@ + + 📖: docs + + + + + + + + + + + + + + + 📖 + + docs + + \ No newline at end of file diff --git a/keploy/.github/slack.svg b/keploy/.github/slack.svg new file mode 100755 index 0000000..6cb641a --- /dev/null +++ b/keploy/.github/slack.svg @@ -0,0 +1,20 @@ + + slack + + + + + + + + + + + + + \ No newline at end of file diff --git a/keploy/.github/workflows/bug.yml b/keploy/.github/workflows/bug.yml new file mode 100755 index 0000000..d187639 --- /dev/null +++ b/keploy/.github/workflows/bug.yml @@ -0,0 +1,38 @@ +name: Issue labeler +on: + issues: + types: [ opened ] + +permissions: + contents: read + +jobs: + label-component: + runs-on: ubuntu-latest + + permissions: + # required for all workflows + issues: write + + # only required for workflows in private repositories + actions: read + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Parse issue form + uses: stefanbuck/github-issue-parser@v3 + id: issue-parser + with: + template-path: .github/ISSUE_TEMPLATE/--bug-report.yaml + + - name: Set labels based on repository field + uses: redhat-plumbers-in-action/advanced-issue-labeler@v3 + with: + issue-form: ${{ steps.issue-parser.outputs.jsonString }} + section: repository + block-list: | + None + Other + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/keploy/.github/workflows/cla.yml b/keploy/.github/workflows/cla.yml new file mode 100755 index 0000000..f734d9b --- /dev/null +++ b/keploy/.github/workflows/cla.yml @@ -0,0 +1,42 @@ +name: "CLA Assistant" + +on: + issue_comment: + types: + - "created" + pull_request_target: + types: + - "opened" + - "closed" + - "synchronize" + +jobs: + cla-assistant: + runs-on: "ubuntu-latest" + steps: + - uses: actions/checkout@v4 + + - name: "CLA Assistant" + if: "(github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'" + uses: "cla-assistant/github-action@v2.1.3-beta" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + PERSONAL_ACCESS_TOKEN: "${{ secrets.PRO_ACCESS_TOKEN }}" + with: + remote-organization-name: "keploy" + remote-repository-name: "keploy" + path-to-signatures: "etc/cla-signatures/signatures.json" + path-to-document: "https://github.com/keploy/keploy/tree/main/.github/CLA.md" + branch: "cla-signatures" + allowlist: "keploy-bot,renovate" + + - name: "Post Failure Instructions" + if: failure() && github.event_name == 'pull_request_target' + run: | + gh pr comment ${{ github.event.pull_request.number }} --body "**The CLA check failed.** Please ensure you have: + - Signed the CLA by commenting **'I have read the CLA Document and I hereby sign the CLA.'** + - Used the correct email address in your commits (matches the one you used to sign the CLA). + + After fixing these issues, comment **'recheck'** to trigger the workflow again." + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/keploy/.github/workflows/codeql.yml b/keploy/.github/workflows/codeql.yml new file mode 100755 index 0000000..6ae28be --- /dev/null +++ b/keploy/.github/workflows/codeql.yml @@ -0,0 +1,84 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '34 8 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + + - name: Create a folder for security reports + run: mkdir -p reports + + # - name: Generate Security Report + # uses: peter-murray/github-security-report-action@v2 + # with: + # token: ${{ secrets.CODEQL_EXPORT_PAT_TOKEN }} + # outputDir: ./reports + # - name: Upload artifact + # uses: actions/upload-artifact@v3 + # with: + # name: security-report + # path: /home/runner/work/keploy/results diff --git a/keploy/.github/workflows/coverage_stage.yml b/keploy/.github/workflows/coverage_stage.yml new file mode 100644 index 0000000..53c7495 --- /dev/null +++ b/keploy/.github/workflows/coverage_stage.yml @@ -0,0 +1,57 @@ +name: Calculate Coverage +on: + workflow_call: + +jobs: + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create directories to store coverage data + run: | + mkdir -p unzipped-cov + mkdir -p final-cov + + - name: Download first coverage data of gin-mongo application + uses: actions/download-artifact@v4 + with: + name: coverage-reports-samples-go-gin-mongo-0 + path: ./temp-coverage-dir + + - name: Download second coverage data of gin-mongo application + uses: actions/download-artifact@v4 + with: + name: coverage-reports-samples-go-gin-mongo-1 + path: ./temp-coverage-dir + + - name: Unzip all coverage data zip files + run: | + unzip temp-coverage-dir/coverage-reports-samples-go-gin-mongo-0.zip -d unzipped-cov + unzip temp-coverage-dir/coverage-reports-samples-go-gin-mongo-1.zip -d unzipped-cov + + - name: Combine all coverage data + run: | + find unzipped-cov -mindepth 1 -maxdepth 1 -type d -exec echo {} \; >> cov-dirs.txt + go tool covdata merge -o final-cov -i=`cat cov-dirs.txt | paste -sd ","` + + - name: Calculate total coverage data and remove generated files + run: | + go tool covdata textfmt -i="./final-cov" -o="./unfiltered-cov.txt" + grep -v "go.keploy.io/server/v2/pkg/graph/generated.go" unfiltered-cov.txt > total-coverage.txt + + - name: Display the coverage data + run: | + go tool cover -func total-coverage.txt + + # - name: Generate the HTML coverage report + # run: | + # go tool cover -html=total-coverage.txt -o=coverage.html + + # - name: Upload the HTML coverage report + # id: artifact-upload-step + # uses: actions/upload-artifact@v4 + # with: + # name: coverage-report-html + # path: coverage.html diff --git a/keploy/.github/workflows/docker-publish.yml b/keploy/.github/workflows/docker-publish.yml new file mode 100755 index 0000000..1049ebb --- /dev/null +++ b/keploy/.github/workflows/docker-publish.yml @@ -0,0 +1,96 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + push: + tags: [ 'v*.*.*' ] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@v3.4.0 + + + # Workaround: https://github.com/docker/build-push-action/issues/461 + - name: Setup Docker buildx + uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Set version + shell: bash + run: | + echo "VERSION=$(echo ${GITHUB_REF#refs/tags/v})" >> $GITHUB_ENV + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + platforms: linux/amd64, linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + SENTRY_DSN_DOCKER=${{secrets.SENTRY_DSN_DOCKER}} + VERSION=${{ env.VERSION }} + SERVER_URL=https://api.keploy.io + GITTHUB_APP_CLIENT_ID=${{ secrets.CLIENT_ID_GITHUB_APP }} + + # Sign the resulting Docker image digest except on PRs. + # This will only write to the public Rekor transparency log when the Docker + # repository is public to avoid leaking data. If you would like to publish + # transparency data even for private images, pass --force to cosign below. + # https://github.com/sigstore/cosign + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + COSIGN_EXPERIMENTAL: "true" + # This step uses the identity token to provision an ephemeral certificate + # against the sigstore community Fulcio instance. + run: cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} diff --git a/keploy/.github/workflows/docs.yml b/keploy/.github/workflows/docs.yml new file mode 100755 index 0000000..08304de --- /dev/null +++ b/keploy/.github/workflows/docs.yml @@ -0,0 +1,38 @@ +name: Issue labeler +on: + issues: + types: [ opened ] + +permissions: + contents: read + +jobs: + label-component: + runs-on: ubuntu-latest + + permissions: + # required for all workflows + issues: write + + # only required for workflows in private repositories + actions: read + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Parse issue form + uses: stefanbuck/github-issue-parser@v3 + id: issue-parser + with: + template-path: .github/ISSUE_TEMPLATE/--documentation-tools.yaml + + - name: Set labels based on repository field + uses: redhat-plumbers-in-action/advanced-issue-labeler@v3 + with: + issue-form: ${{ steps.issue-parser.outputs.jsonString }} + section: repository + block-list: | + None + Other + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/keploy/.github/workflows/feat.yml b/keploy/.github/workflows/feat.yml new file mode 100755 index 0000000..e61053b --- /dev/null +++ b/keploy/.github/workflows/feat.yml @@ -0,0 +1,38 @@ +name: Issue labeler +on: + issues: + types: [ opened ] + +permissions: + contents: read + +jobs: + label-component: + runs-on: ubuntu-latest + + permissions: + # required for all workflows + issues: write + + # only required for workflows in private repositories + actions: read + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Parse issue form + uses: stefanbuck/github-issue-parser@v3 + id: issue-parser + with: + template-path: .github/ISSUE_TEMPLATE/--feature-request.yaml + + - name: Set labels based on repository field + uses: redhat-plumbers-in-action/advanced-issue-labeler@v3 + with: + issue-form: ${{ steps.issue-parser.outputs.jsonString }} + section: repository + block-list: | + None + Other + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/keploy/.github/workflows/go.yml b/keploy/.github/workflows/go.yml new file mode 100755 index 0000000..c0f0983 --- /dev/null +++ b/keploy/.github/workflows/go.yml @@ -0,0 +1,31 @@ +name: Go on Linux + +on: + pull_request: + branches: [ main ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ">=1.23" + + - name: Build + run: go build -v ./... + + - name: Build arm + run: GOOS=linux GOARCH=arm64 go build -v ./... + + - uses: codfish/semantic-release-action@v3 + with: + dry-run: true + additional-packages: '["@semantic-release/exec@7.0.3"]' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/keploy/.github/workflows/go_macos.yaml b/keploy/.github/workflows/go_macos.yaml new file mode 100644 index 0000000..77d6885 --- /dev/null +++ b/keploy/.github/workflows/go_macos.yaml @@ -0,0 +1,25 @@ +name: Go on macOS + +on: + pull_request: + branches: [ main ] + +jobs: + + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ">=1.23" + + - name: Build amd64 + run: GOARCH=amd64 go build -v ./... + + - name: Build arm64 + run: GOARCH=arm64 go build -v ./... + diff --git a/keploy/.github/workflows/go_windows.yml b/keploy/.github/workflows/go_windows.yml new file mode 100644 index 0000000..48a03c9 --- /dev/null +++ b/keploy/.github/workflows/go_windows.yml @@ -0,0 +1,31 @@ +name: Go on Windows + +on: + pull_request: + branches: [ main ] + +jobs: + + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ">=1.23" + + - name: Build amd64 + run: | + set GOARCH=amd64 + go build -v ./... + shell: cmd + + - name: Build arm64 + run: | + set GOARCH=arm64 + go build -v ./... + shell: cmd + diff --git a/keploy/.github/workflows/golang_docker-compose.yml b/keploy/.github/workflows/golang_docker-compose.yml new file mode 100644 index 0000000..ffe386d --- /dev/null +++ b/keploy/.github/workflows/golang_docker-compose.yml @@ -0,0 +1,49 @@ +name: Golang On Docker Compose +on: + workflow_call: +jobs: + golang_docker_compose: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: image + uses: ./.github/actions/download-image + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + + - name: Checkout the samples-go repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-go + path: samples-go + - name: Run echo-sql application + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd samples-go/echo-sql + source ./../../.github/workflows/test_workflow_scripts/golang-docker-compose.sh diff --git a/keploy/.github/workflows/golang_docker.yml b/keploy/.github/workflows/golang_docker.yml new file mode 100755 index 0000000..b8e31b7 --- /dev/null +++ b/keploy/.github/workflows/golang_docker.yml @@ -0,0 +1,49 @@ +name: Golang On Docker +on: + workflow_call: +jobs: + golang_docker: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: image + uses: ./.github/actions/download-image + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + + - name: Checkout the samples-go repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-go + path: samples-go + - name: Run gin-mongo application + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd samples-go/gin-mongo + source ./../../.github/workflows/test_workflow_scripts/golang-docker.sh diff --git a/keploy/.github/workflows/golang_http_linux.yml b/keploy/.github/workflows/golang_http_linux.yml new file mode 100644 index 0000000..322d40a --- /dev/null +++ b/keploy/.github/workflows/golang_http_linux.yml @@ -0,0 +1,53 @@ +name: Golang(http) On Linux +on: + workflow_call: +jobs: + http_golang_linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + + - name: Checkout the samples-go repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-go + path: samples-go + + - name: echo record and replay binary + run: | + echo "Record binary path: ${{ steps.record.outputs.path }}" + echo "Replay binary path: ${{ steps.replay.outputs.path }}" + + - name: Run go-http application + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd samples-go/http-pokeapi + chmod +x ./../../.github/workflows/test_workflow_scripts/golang-http-linux.sh + source ./../../.github/workflows/test_workflow_scripts/golang-http-linux.sh diff --git a/keploy/.github/workflows/golang_linux.yml b/keploy/.github/workflows/golang_linux.yml new file mode 100644 index 0000000..c34dc28 --- /dev/null +++ b/keploy/.github/workflows/golang_linux.yml @@ -0,0 +1,46 @@ +name: Golang On Linux +on: + workflow_call: +jobs: + golang_linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + + - name: Checkout the samples-go repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-go + path: samples-go + - name: Run samples-go application + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd samples-go/gin-mongo + source ./../../.github/workflows/test_workflow_scripts/golang-linux.sh diff --git a/keploy/.github/workflows/golang_mysql_linux.yml b/keploy/.github/workflows/golang_mysql_linux.yml new file mode 100644 index 0000000..2568400 --- /dev/null +++ b/keploy/.github/workflows/golang_mysql_linux.yml @@ -0,0 +1,46 @@ +name: MySQL-Golang On Linux +on: + workflow_call: +jobs: + mysql_golang_linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + + - name: Checkout the samples-go repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-go + path: samples-go + - name: Run echo-mysql application + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd samples-go/echo-mysql + source ./../../.github/workflows/test_workflow_scripts/golang-mysql-linux.sh \ No newline at end of file diff --git a/keploy/.github/workflows/golangci-lint.yml b/keploy/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..9d94af0 --- /dev/null +++ b/keploy/.github/workflows/golangci-lint.yml @@ -0,0 +1,48 @@ +name: golangci-lint +on: + push: + branches: + - master + - main + pull_request: + +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + pull-requests: read + +# cancel the in-progress workflow when PR is refreshed. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: ">=1.23" + cache: false + + - name: golangci-lint + uses: golangci/golangci-lint-action@v7 + with: + only-new-issues: true + args: --timeout=5m + + - name: Check gofmt (diff with line numbers) + run: | + output=$(gofmt -l -d .) + if [ -n "$output" ]; then + echo "🚨 gofmt found issues:" + echo "$output" + exit 1 + fi + + - name: Check for Deprecated Dependencies + run: | + source ./.github/workflows/test_workflow_scripts/check-deprecated-deps.sh \ No newline at end of file diff --git a/keploy/.github/workflows/greetings.yml b/keploy/.github/workflows/greetings.yml new file mode 100755 index 0000000..d1bcbc2 --- /dev/null +++ b/keploy/.github/workflows/greetings.yml @@ -0,0 +1,17 @@ +name: Greetings + +on: [pull_request_target, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/first-interaction@v1 + continue-on-error: true + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: 'Thank you and congratulations 🎉 for opening your very first issue in keploy' + pr-message: 'Thank you and congratulations 🎉 for opening your very first pull request in keploy' diff --git a/keploy/.github/workflows/java_linux.yml b/keploy/.github/workflows/java_linux.yml new file mode 100644 index 0000000..604b3c3 --- /dev/null +++ b/keploy/.github/workflows/java_linux.yml @@ -0,0 +1,58 @@ +name: Java on Linux +on: + workflow_call: +jobs: + java_linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + + - name: Checkout samples-java repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-java + path: samples-java + + - name: Installing the necessary dependencies + run: | + cd samples-java/spring-petclinic/spring-petclinic-rest + ./mvnw dependency:resolve + + - name: Compile the project + run: | + cd samples-java/spring-petclinic/spring-petclinic-rest + source ./../../../.github/workflows/test_workflow_scripts/update-java.sh + ./mvnw compile + + - name: Run the spring-petclinic-rest app + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd samples-java/spring-petclinic/spring-petclinic-rest + source ./../../../.github/workflows/test_workflow_scripts/java-linux.sh diff --git a/keploy/.github/workflows/main.yml b/keploy/.github/workflows/main.yml new file mode 100755 index 0000000..50cc354 --- /dev/null +++ b/keploy/.github/workflows/main.yml @@ -0,0 +1,26 @@ +name: Go + +on: + push: + branches: [ main ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ">=1.23" + + - name: Build + run: | + go build -v ./... + + - name: Build arm + run: GOOS=linux GOARCH=arm64 go build -v ./... diff --git a/keploy/.github/workflows/node_docker.yml b/keploy/.github/workflows/node_docker.yml new file mode 100755 index 0000000..e523661 --- /dev/null +++ b/keploy/.github/workflows/node_docker.yml @@ -0,0 +1,50 @@ +name: Node on Docker +on: + workflow_call: +jobs: + node_docker: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: image + uses: ./.github/actions/download-image + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + + - name: Checkout samples-typescript repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-typescript + path: samples-typescript + + - name: Run the express-mongoose app + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd samples-typescript/express-mongoose + source ./../../.github/workflows/test_workflow_scripts/node-docker.sh diff --git a/keploy/.github/workflows/node_encoding.yaml b/keploy/.github/workflows/node_encoding.yaml new file mode 100644 index 0000000..960dbb6 --- /dev/null +++ b/keploy/.github/workflows/node_encoding.yaml @@ -0,0 +1,48 @@ +name: Node Encoding Workflow +description: "Run the Node Encoding Sample application with Keploy" +on: + workflow_call: +jobs: + node_encoding: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + + - name: Checkout Encoding sample repository + uses: actions/checkout@v4 + with: + repository: ayush3160/http-br + path: http-br + + - name: Run the Encoding Sample application + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd http-br + source ./../.github/workflows/test_workflow_scripts/node-encoding.sh diff --git a/keploy/.github/workflows/node_linux.yml b/keploy/.github/workflows/node_linux.yml new file mode 100644 index 0000000..5a9194a --- /dev/null +++ b/keploy/.github/workflows/node_linux.yml @@ -0,0 +1,47 @@ +name: Node on Linux +on: + workflow_call: +jobs: + node_linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + + - name: Checkout samples-typescript repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-typescript + path: samples-typescript + + - name: Run the express-mongoose app + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd samples-typescript/express-mongoose + source ./../../.github/workflows/test_workflow_scripts/node-linux.sh diff --git a/keploy/.github/workflows/prepare_and_run.yml b/keploy/.github/workflows/prepare_and_run.yml new file mode 100644 index 0000000..31b9d51 --- /dev/null +++ b/keploy/.github/workflows/prepare_and_run.yml @@ -0,0 +1,74 @@ +name: Prepare Binary and Run Workflows +on: + pull_request: + branches: [main] +jobs: + build-and-upload: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build Keploy (PR) + run: go build -race -tags=viper_bind_struct -o keploy + - uses: actions/upload-artifact@v4 + with: { name: build, path: keploy } + + upload-latest: + runs-on: ubuntu-latest + steps: + - name: Upload latest release + run: | + LATEST=$(curl -s https://api.github.com/repos/keploy/keploy/releases/latest | jq -r .tag_name) + URL="https://github.com/keploy/keploy/releases/download/${LATEST}/keploy_linux_amd64.tar.gz" + curl -L "$URL" -o keploy.tar.gz + tar -xzf keploy.tar.gz + chmod +x keploy + - uses: actions/upload-artifact@v4 + with: { name: latest, path: keploy } + + build-docker-image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build image + run: | + source ./.github/workflows/test_workflow_scripts/update-docker.sh + + - name: Push image + run: | + docker push ttl.sh/keploy/keploy:1h + + run_golang_linux: + needs: [build-and-upload, upload-latest] + uses: ./.github/workflows/golang_linux.yml + run_golang_http_linux: + needs: [build-and-upload, upload-latest] + uses: ./.github/workflows/golang_http_linux.yml + run_golang_mysql_linux: + needs: [build-and-upload, upload-latest] + uses: ./.github/workflows/golang_mysql_linux.yml + run_golang_docker: + needs: [build-and-upload, upload-latest,build-docker-image] + uses: ./.github/workflows/golang_docker.yml + run_golang_docker-compose: + needs: [build-and-upload, upload-latest, build-docker-image] + uses: ./.github/workflows/golang_docker-compose.yml + run_java_linux: + needs: [build-and-upload, upload-latest] + uses: ./.github/workflows/java_linux.yml + run_node_linux: + needs: [build-and-upload, upload-latest] + uses: ./.github/workflows/node_linux.yml + run_node_docker: + needs: [build-and-upload, upload-latest, build-docker-image] + uses: ./.github/workflows/node_docker.yml + run_python_docker: + needs: [build-and-upload, upload-latest, build-docker-image] + uses: ./.github/workflows/python_docker.yml + run_python_linux: + needs: [build-and-upload, upload-latest] + uses: ./.github/workflows/python_linux.yml + run_node_encoding: + needs: [build-and-upload, upload-latest] + uses: ./.github/workflows/node_encoding.yaml \ No newline at end of file diff --git a/keploy/.github/workflows/python_docker.yml b/keploy/.github/workflows/python_docker.yml new file mode 100644 index 0000000..8cbb85a --- /dev/null +++ b/keploy/.github/workflows/python_docker.yml @@ -0,0 +1,49 @@ +name: Python On Docker +on: + workflow_call: +jobs: + python_docker: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: image + uses: ./.github/actions/download-image + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + + - name: Checkout the samples-python repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-python + path: samples-python + - name: Run the flask-mongo application + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd samples-python/flask-mongo + source ./../../.github/workflows/test_workflow_scripts/python-docker.sh diff --git a/keploy/.github/workflows/python_linux.yml b/keploy/.github/workflows/python_linux.yml new file mode 100644 index 0000000..b9ce0a7 --- /dev/null +++ b/keploy/.github/workflows/python_linux.yml @@ -0,0 +1,45 @@ +name: Python On Linux +on: + workflow_call: +jobs: + python_linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job: record_latest_replay_build + record_src: latest + replay_src: build + - job: record_build_replay_latest + record_src: build + replay_src: latest + - job: record_build_replay_build + record_src: build + replay_src: build + name: ${{ matrix.job }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - id: record + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.record_src }} + + - id: replay + uses: ./.github/actions/download-binary + with: + src: ${{ matrix.replay_src }} + - name: Checkout the samples-python repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-python + path: samples-python + - name: Run the sample python application + env: + RECORD_BIN: ${{ steps.record.outputs.path }} + REPLAY_BIN: ${{ steps.replay.outputs.path }} + run: | + cd samples-python/django-postgres/django_postgres + source ../../../.github/workflows/test_workflow_scripts/python-linux.sh diff --git a/keploy/.github/workflows/release.yml b/keploy/.github/workflows/release.yml new file mode 100755 index 0000000..374acf9 --- /dev/null +++ b/keploy/.github/workflows/release.yml @@ -0,0 +1,101 @@ +name: Release +on: + push: + tags: + - 'v*.*.*' + +permissions: + contents: write + +jobs: +# build-ui: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# with: +# fetch-depth: 0 +# +# - name: Set up Go +# uses: actions/setup-go@v2 +# with: +# go-version: "1.20" + +# - name: Checkout UI +# uses: actions/checkout@v4 +# with: +# repository: keploy/ui +# path: ui +# +# - name: Cache UI +# id: cache-ui +# uses: actions/cache@v3 +# with: +# path: web +# key: ${{ hashFiles('ui/src') }} +# +# - name: Set up Node +# if: steps.cache-ui.outputs.cache-hit != 'true' +# uses: actions/setup-node@v3 +# with: +# node-version: '14' +# +# - name: Build web app +# if: steps.cache-ui.outputs.cache-hit != 'true' +# run: | +# cd $GITHUB_WORKSPACE/ui +# npm install +# npm run build +# cp -r public $GITHUB_WORKSPACE/web +# rm -rf $GITHUB_WORKSPACE/ui + + build-go: + runs-on: macos-latest + # runs-on: ubuntu-latest +# needs: build-ui + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ">=1.23" + +# - name: Checkout UI +# uses: actions/checkout@v4 +# with: +# repository: keploy/ui +# path: ui +# +# - name: Cache UI +# id: cache-ui +# uses: actions/cache@v3 +# with: +# path: web +# key: ${{ hashFiles('ui/src') }} + - name: Import Code-Signing Certificates + uses: Apple-Actions/import-codesign-certs@v5 + with: + # The certificates in a PKCS12 file encoded as a base64 string created with "openssl base64 -in cert.p12 -out cert-base64.txt" + p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }} + # The password used to import the PKCS12 file. + p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }} + - name: Install gon for code signing and app notarization + run: | + wget -q https://github.com/Bearer/gon/releases/download/v0.0.37/gon_macos.zip + unzip gon_macos.zip -d /usr/local/bin + rm -rf gon_macos.zip + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: v1.26.2 + args: release --rm-dist + env: + AC_PROVIDER: ${{ secrets.APPLE_ACCOUNT_TEAM_ID }} + AC_PASSWORD: ${{ secrets.APPLE_ACCOUNT_PASSWORD }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SENTRY_DSN_BINARY: ${{ secrets.SENTRY_DSN_BINARY }} + GITTHUB_APP_CLIENT_ID: ${{ secrets.CLIENT_ID_GITHUB_APP }} + SERVER_URL: https://api.keploy.io diff --git a/keploy/.github/workflows/sample-run.yml b/keploy/.github/workflows/sample-run.yml new file mode 100755 index 0000000..870b80e --- /dev/null +++ b/keploy/.github/workflows/sample-run.yml @@ -0,0 +1,25 @@ +name: sample-run + +on: + push: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ">=1.23" + + - name: Build Keploy + run: | + go build -v ./... + + - name: Build arm + run: GOOS=linux GOARCH=arm64 go build -v ./... \ No newline at end of file diff --git a/keploy/.github/workflows/test-go-mongo-1.yml b/keploy/.github/workflows/test-go-mongo-1.yml new file mode 100644 index 0000000..7164ea9 --- /dev/null +++ b/keploy/.github/workflows/test-go-mongo-1.yml @@ -0,0 +1,60 @@ +name: Test GinApp 1 +on: + workflow_call: + +jobs: + test-go-mongo: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download Keploy Build Binary + uses: actions/download-artifact@v4 + with: + name: keploy-binary + path: /usr/local/bin + + - name: Giving permission to binary + run: | + sudo chmod +x /usr/local/bin/keployB + + - name: Download the latest released binary of keploy + run: | + curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz -C /tmp + sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin/keployR + + # Get the pilot for tests and mocks assertion + - name: Get the pilot for tests and mocks assertion + run: | + curl --silent -o pilot --location "https://github.com/keploy/pilot/releases/latest/download/pilot_linux_amd64" && + sudo chmod a+x pilot && sudo mkdir -p /usr/local/bin && sudo mv pilot /usr/local/bin + + - name: Checkout to the samples-go repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-go + ref: native-linux + path: samples-go + + - name: Build the gin-mongo application + run: | + cd samples-go/gin-mongo + go build -o ginApp . + + # add the noisy fields in the config file if any. + - name: Add noisy fields in the config file + run: | + echo "You can add the noisy fields in the config file if any." + + - name: Run testing script for gin-mongo application + uses: ./.github/actions/tester + with: + working-directory: samples-go/gin-mongo + command: ./ginApp + delay: 7 + keploy-record-bin: /usr/local/bin/keployR + keploy-test-bin: /usr/local/bin/keployB + mode: 0 + + diff --git a/keploy/.github/workflows/test-go-mongo-2.yml b/keploy/.github/workflows/test-go-mongo-2.yml new file mode 100644 index 0000000..43daa46 --- /dev/null +++ b/keploy/.github/workflows/test-go-mongo-2.yml @@ -0,0 +1,58 @@ +name: Test GinApp 2 +on: + workflow_call: + +jobs: + test-go-mongo: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download Keploy Build Binary + uses: actions/download-artifact@v4 + with: + name: keploy-binary + path: /usr/local/bin + + - name: Giving permission to binary + run: | + sudo chmod +x /usr/local/bin/keployB + + - name: Download the latest released binary of keploy + run: | + curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz -C /tmp + sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin/keployR + + # Get the pilot for tests and mocks assertion + - name: Get the pilot for tests and mocks assertion + run: | + curl --silent -o pilot --location "https://github.com/keploy/pilot/releases/latest/download/pilot_linux_amd64" && + sudo chmod a+x pilot && sudo mkdir -p /usr/local/bin && sudo mv pilot /usr/local/bin + + - name: Checkout to the samples-go repository + uses: actions/checkout@v4 + with: + repository: keploy/samples-go + ref: native-linux + path: samples-go + + - name: Build the gin-mongo application + run: | + cd samples-go/gin-mongo + go build -o ginApp . + + # add the noisy fields in the config file if any. + - name: Add noisy fields in the config file + run: | + echo "You can add the noisy fields in the config file if any." + + - name: Run testing script for gin-mongo application + uses: ./.github/actions/tester + with: + working-directory: samples-go/gin-mongo + command: ./ginApp + delay: 7 + keploy-record-bin: /usr/local/bin/keployB + keploy-test-bin: /usr/local/bin/keployR + mode: 1 \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/check-deprecated-deps.sh b/keploy/.github/workflows/test_workflow_scripts/check-deprecated-deps.sh new file mode 100644 index 0000000..fbcc414 --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/check-deprecated-deps.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Extract direct dependencies from go.mod using go mod edit -json +direct_deps=$(go mod edit -json | jq -r '.Require[] | select(.Indirect == null) | .Path') + +# List all modules with their update status +output=$(go list -m -u all) + +found_deprecated=false + +while IFS= read -r line; do + mod_path=$(echo "$line" | awk '{print $1}') + + if echo "$direct_deps" | grep -q "$mod_path"; then + if [[ "$line" == *"deprecated"* || "$line" == *"retracted"* ]]; then + echo "Deprecated/retracted direct dependency found: $line" + found_deprecated=true + fi + fi +done <<< "$output" + +if [ "$found_deprecated" = true ]; then + echo "Exiting with failure due to deprecated direct dependencies." + exit 1 +fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/golang-docker-compose.sh b/keploy/.github/workflows/test_workflow_scripts/golang-docker-compose.sh new file mode 100644 index 0000000..a196679 --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/golang-docker-compose.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +source ./../../.github/workflows/test_workflow_scripts/test-iid.sh + +# Build Docker Image +docker compose build + +# Remove any preexisting keploy tests and mocks. +sudo rm -rf keploy/ + +# Generate the keploy-config file. +sudo -E env PATH=$PATH $RECORD_BIN config --generate + +# Update the global noise to ts in the config file. +config_file="./keploy.yml" +sed -i 's/global: {}/global: {"body": {"ts":[]}}/' "$config_file" + +container_kill() { + pid=$(pgrep -n keploy) + echo "$pid Keploy PID" + echo "Killing keploy" + sudo kill $pid +} + +send_request(){ + sleep 10 + app_started=false + while [ "$app_started" = false ]; do + if curl -X GET http://localhost:8082/health; then + app_started=true + fi + sleep 3 + done + echo "App started" + # Make curl calls to record the test cases and mocks. + curl --request POST \ + --url http://localhost:8082/url \ + --header 'content-type: application/json' \ + --data '{ + "url": "https://google.com" + }' + + curl --request POST \ + --url http://localhost:8082/url \ + --header 'content-type: application/json' \ + --data '{ + "url": "https://facebook.com" + }' + + curl -X GET http://localhost:8082/health + + # Wait for 5 seconds for keploy to record the test cases and mocks. + sleep 5 + container_kill + wait +} + +for i in {1..2}; do + container_name="echoApp" + send_request & + sudo -E env PATH=$PATH $RECORD_BIN record -c "docker compose up" --container-name "$container_name" --generateGithubActions=false &> "${container_name}.txt" + + if grep "WARNING: DATA RACE" "${container_name}.txt"; then + echo "Race condition detected in recording, stopping pipeline..." + cat "${container_name}.txt" + exit 1 + fi + if grep "ERROR" "${container_name}.txt"; then + echo "Error found in pipeline..." + cat "${container_name}.txt" + exit 1 + fi + sleep 5 + + echo "Recorded test case and mocks for iteration ${i}" +done + +# Shutdown services before test mode - Keploy should use mocks for dependencies +echo "Shutting down docker compose services before test mode..." +docker compose down +echo "Services stopped - Keploy should now use mocks for dependency interactions" + +# Start keploy in test mode. +test_container="echoApp" +sudo -E env PATH=$PATH $REPLAY_BIN test -c 'docker compose up' --containerName "$test_container" --apiTimeout 60 --delay 20 --generate-github-actions=false &> "${test_container}.txt" + +if grep "ERROR" "${test_container}.txt"; then + echo "Error found in pipeline..." + cat "${test_container}.txt" + exit 1 +fi + +if grep "WARNING: DATA RACE" "${test_container}.txt"; then + echo "Race condition detected in test, stopping pipeline..." + cat "${test_container}.txt" + exit 1 +fi + +all_passed=true + +for i in {0..1} +do + # Define the report file for each test set + report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" + + # Extract the test status + test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') + + # Print the status for debugging + echo "Test status for test-set-$i: $test_status" + + # Check if any test set did not pass + if [ "$test_status" != "PASSED" ]; then + all_passed=false + echo "Test-set-$i did not pass." + break # Exit the loop early as all tests need to pass + fi +done + +# Check the overall test status and exit accordingly +if [ "$all_passed" = true ]; then + echo "All tests passed" + exit 0 +else + cat "${test_container}.txt" + exit 1 +fi diff --git a/keploy/.github/workflows/test_workflow_scripts/golang-docker.sh b/keploy/.github/workflows/test_workflow_scripts/golang-docker.sh new file mode 100755 index 0000000..5f66285 --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/golang-docker.sh @@ -0,0 +1,134 @@ +#!/bin/bash + +source ./../../.github/workflows/test_workflow_scripts/test-iid.sh + +# Start mongo before starting keploy. +docker network create keploy-network +docker run --name mongoDb --rm --net keploy-network -p 27017:27017 -d mongo + +# Generate the keploy-config file. +sudo -E env PATH=$PATH $RECORD_BIN config --generate + +# Update the global noise to ts. +config_file="./keploy.yml" +sed -i 's/global: {}/global: {"body": {"ts":[]}}/' "$config_file" + +# Remove any preexisting keploy tests and mocks. +sudo rm -rf keploy/ +docker logs mongoDb & + +# Start keploy in record mode. +docker build -t gin-mongo . +docker rm -f ginApp 2>/dev/null || true + +container_kill() { + pid=$(pgrep -n keploy) + echo "$pid Keploy PID" + echo "Killing keploy" + sudo kill $pid +} + +send_request(){ + sleep 10 + app_started=false + while [ "$app_started" = false ]; do + if curl -X GET http://localhost:8080/CJBKJd92; then + app_started=true + fi + sleep 3 + done + echo "App started" + # Start making curl calls to record the testcases and mocks. + curl --request POST \ + --url http://localhost:8080/url \ + --header 'content-type: application/json' \ + --data '{ + "url": "https://google.com" + }' + + curl --request POST \ + --url http://localhost:8080/url \ + --header 'content-type: application/json' \ + --data '{ + "url": "https://facebook.com" + }' + + curl -X GET http://localhost:8080/CJBKJd92 + + # Wait for 5 seconds for keploy to record the tcs and mocks. + sleep 5 + container_kill + wait +} + +for i in {1..2}; do + container_name="ginApp_${i}" + send_request & + sudo -E env PATH=$PATH $RECORD_BIN record -c "docker run -p8080:8080 --net keploy-network --rm --name $container_name gin-mongo" --container-name "$container_name" &> "${container_name}.txt" + + if grep "WARNING: DATA RACE" "${container_name}.txt"; then + echo "Race condition detected in recording, stopping pipeline..." + cat "${container_name}.txt" + exit 1 + fi + if grep "ERROR" "${container_name}.txt"; then + echo "Error found in pipeline..." + cat "${container_name}.txt" + exit 1 + fi + sleep 5 + + echo "Recorded test case and mocks for iteration ${i}" +done + +# Shutdown mongo before test mode - Keploy should use mocks for database interactions +echo "Shutting down mongo before test mode..." +docker stop mongoDb || true +docker rm mongoDb || true +echo "MongoDB stopped - Keploy should now use mocks for database interactions" + +# Start the keploy in test mode. +test_container="ginApp_test" +sudo -E env PATH=$PATH $REPLAY_BIN test -c 'docker run -p8080:8080 --net keploy-network --name ginApp_test gin-mongo' --containerName "$test_container" --apiTimeout 60 --delay 20 --generate-github-actions=false &> "${test_container}.txt" + +if grep "ERROR" "${test_container}.txt"; then + echo "Error found in pipeline..." + cat "${test_container}.txt" + exit 1 +fi + +if grep "WARNING: DATA RACE" "${test_container}.txt"; then + echo "Race condition detected in test, stopping pipeline..." + cat "${test_container}.txt" + exit 1 +fi + +all_passed=true + +for i in {0..1} +do + # Define the report file for each test set + report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" + + # Extract the test status + test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') + + # Print the status for debugging + echo "Test status for test-set-$i: $test_status" + + # Check if any test set did not pass + if [ "$test_status" != "PASSED" ]; then + all_passed=false + echo "Test-set-$i did not pass." + break # Exit the loop early as all tests need to pass + fi +done + +# Check the overall test status and exit accordingly +if [ "$all_passed" = true ]; then + echo "All tests passed" + exit 0 +else + cat "${test_container}.txt" + exit 1 +fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/golang-http-linux.sh b/keploy/.github/workflows/test_workflow_scripts/golang-http-linux.sh new file mode 100644 index 0000000..668fc02 --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/golang-http-linux.sh @@ -0,0 +1,134 @@ +#!/bin/bash + +echo "$RECORD_BIN" +echo "$REPLAY_BIN" + +source ./../../.github/workflows/test_workflow_scripts/test-iid.sh +echo "iid.sh executed" + +# Checkout a different branch +git fetch origin +#git checkout native-linux + +# Check if there is a keploy-config file, if there is, delete it. +if [ -f "./keploy.yml" ]; then + rm ./keploy.yml +fi + +rm -rf keploy/ + +# Build go binary +go build -o http-pokeapi +echo "go binary built" + +# Generate the keploy-config file. +sudo "$RECORD_BIN" config --generate + +# Update the global noise to updated_at. +config_file="./keploy.yml" +sed -i 's/global: {}/global: {"body": {"updated_at":[]}}/' "$config_file" + +send_request() { + local index=$1 + + sleep 10 + app_started=false + while [ "$app_started" = false ]; do + if curl -X GET http://localhost:8080/api/locations; then + app_started=true + fi + sleep 3 + done + + echo "App started" + + response=$(curl -s -X GET http://localhost:8080/api/locations) + + # Extract any location from the reponse + location=$(echo "$response" | jq -r ".location[$index]") + + response=$(curl -s -X GET http://localhost:8080/api/locations/$location) + + # Extract any pokemon from the response + pokemon=$(echo "$response" | jq -r ".[$index]") + + curl -s -X GET http://localhost:8080/api/pokemon/$pokemon + + curl -s -X GET http://localhost:8080/api/greet + + curl -s -X GET http://localhost:8080/api/greet?format=html + + curl -s -X GET http://localhost:8080/api/greet?format=xml + + # Wait for 10 seconds for Keploy to record the tcs and mocks. + sleep 10 + pid=$(pgrep keploy) + echo "$pid Keploy PID" + echo "Killing Keploy" + sudo kill $pid +} + +for i in {1..2}; do + app_name="http-pokeapi_${i}" + send_request $i & + sudo -E env PATH="$PATH" "$RECORD_BIN" record -c "./http-pokeapi" --generateGithubActions=false &> "${app_name}.txt" + if grep "ERROR" "${app_name}.txt"; then + echo "Error found in pipeline..." + cat "${app_name}.txt" + exit 1 + fi + if grep "WARNING: DATA RACE" "${app_name}.txt"; then + echo "Race condition detected in recording, stopping pipeline..." + cat "${app_name}.txt" + exit 1 + fi + sleep 5 + wait + echo "Recorded test case and mocks for iteration ${i}" +done + +# Start the go-http app in test mode. +sudo -E env PATH="$PATH" "$REPLAY_BIN" test -c "./http-pokeapi" --delay 7 --generateGithubActions=false &> test_logs.txt + +if grep "ERROR" "test_logs.txt"; then + echo "Error found in pipeline..." + cat "test_logs.txt" + exit 1 +fi + +if grep "WARNING: DATA RACE" "test_logs.txt"; then + echo "Race condition detected in test, stopping pipeline..." + cat "test_logs.txt" + exit 1 +fi + +all_passed=true + +# Get the test results from the testReport file. +for i in {0..1} +do + # Define the report file for each test set + report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" + + # Extract the test status + test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') + + # Print the status for debugging + echo "Test status for test-set-$i: $test_status" + + # Check if any test set did not pass + if [ "$test_status" != "PASSED" ]; then + all_passed=false + echo "Test-set-$i did not pass." + break # Exit the loop early as all tests need to pass + fi +done + +# Check the overall test status and exit accordingly +if [ "$all_passed" = true ]; then + echo "All tests passed" + exit 0 +else + cat "test_logs.txt" + exit 1 +fi diff --git a/keploy/.github/workflows/test_workflow_scripts/golang-linux.sh b/keploy/.github/workflows/test_workflow_scripts/golang-linux.sh new file mode 100644 index 0000000..693d5ab --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/golang-linux.sh @@ -0,0 +1,144 @@ +#!/bin/bash + +source ./../../.github/workflows/test_workflow_scripts/test-iid.sh + +# Checkout a different branch +git fetch origin +git checkout native-linux + +# Start mongo before starting keploy. +docker run --rm -d -p27017:27017 --name mongoDb mongo + +# Check if there is a keploy-config file, if there is, delete it. +if [ -f "./keploy.yml" ]; then + rm ./keploy.yml +fi + +# Generate the keploy-config file. +sudo $RECORD_BIN config --generate + +# Update the global noise to ts. +config_file="./keploy.yml" +sed -i 's/global: {}/global: {"body": {"ts":[]}}/' "$config_file" + +sed -i 's/ports: 0/ports: 27017/' "$config_file" + +# Remove any preexisting keploy tests and mocks. +rm -rf keploy/ + +# Build the binary. +go build -o ginApp + + +send_request(){ + sleep 10 + app_started=false + while [ "$app_started" = false ]; do + if curl --request POST \ + --url http://localhost:8080/url \ + --header 'content-type: application/json' \ + --data '{ + "url": "https://facebook.com" + }'; then + app_started=true + fi + sleep 3 # wait for 3 seconds before checking again. + done + echo "App started" + # Start making curl calls to record the testcases and mocks. + curl --request POST \ + --url http://localhost:8080/url \ + --header 'content-type: application/json' \ + --data '{ + "url": "https://google.com" + }' + + curl --request POST \ + --url http://localhost:8080/url \ + --header 'content-type: application/json' \ + --data '{ + "url": "https://facebook.com" + }' + + curl -X GET http://localhost:8080/CJBKJd92 + + # Wait for 10 seconds for keploy to record the tcs and mocks. + sleep 10 + pid=$(pgrep keploy) + echo "$pid Keploy PID" + echo "Killing keploy" + sudo kill $pid +} + + +for i in {1..2}; do + app_name="javaApp_${i}" + send_request & + sudo -E env PATH="$PATH" $RECORD_BIN record -c "./ginApp" &> "${app_name}.txt" + if grep "ERROR" "${app_name}.txt"; then + echo "Error found in pipeline..." + cat "${app_name}.txt" + exit 1 + fi + if grep "WARNING: DATA RACE" "${app_name}.txt"; then + echo "Race condition detected in recording, stopping pipeline..." + cat "${app_name}.txt" + exit 1 + fi + sleep 5 + wait + echo "Recorded test case and mocks for iteration ${i}" +done + +# Shutdown mongo before test mode - Keploy should use mocks for database interactions +echo "Shutting down mongo before test mode..." +docker stop mongoDb || true +docker rm mongoDb || true +echo "MongoDB stopped - Keploy should now use mocks for database interactions" + +# Start the gin-mongo app in test mode. +sudo -E env PATH="$PATH" $REPLAY_BIN test -c "./ginApp" --delay 7 &> test_logs.txt + +if grep "ERROR" "test_logs.txt"; then + echo "Error found in pipeline..." + cat "test_logs.txt" + exit 1 +fi + +if grep "WARNING: DATA RACE" "test_logs.txt"; then + echo "Race condition detected in test, stopping pipeline..." + cat "test_logs.txt" + exit 1 +fi + +all_passed=true + + +# Get the test results from the testReport file. +for i in {0..1} +do + # Define the report file for each test set + report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" + + # Extract the test status + test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') + + # Print the status for debugging + echo "Test status for test-set-$i: $test_status" + + # Check if any test set did not pass + if [ "$test_status" != "PASSED" ]; then + all_passed=false + echo "Test-set-$i did not pass." + break # Exit the loop early as all tests need to pass + fi +done + +# Check the overall test status and exit accordingly +if [ "$all_passed" = true ]; then + echo "All tests passed" + exit 0 +else + cat "test_logs.txt" + exit 1 +fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh b/keploy/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh new file mode 100644 index 0000000..f4f85d4 --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash + +# safer bash, but we’ll locally disable -e around commands we want to inspect +set -Eeuo pipefail + +# ----- helpers ----- +section() { echo "::group::$*"; } +endsec() { echo "::endgroup::"; } +die() { + rc=$? + echo "::error::Pipeline failed (exit=$rc). Dumping context…" + echo "== docker ps =="; docker ps || true + echo "== mysql logs (complete) =="; docker logs mysql-container || true + echo "== workspace tree (depth 3) =="; find . -maxdepth 3 -type d -print | sort || true + echo "== keploy tree (depth 4) =="; find ./keploy -maxdepth 4 -type f -print 2>/dev/null | sort || true + echo "== *.txt logs (complete) =="; for f in ./*.txt; do [[ -f "$f" ]] && { echo "--- $f ---"; cat "$f"; }; done + [[ -f test_logs.txt ]] && { echo "== test_logs.txt (complete) =="; cat test_logs.txt; } + exit "$rc" +} +trap die ERR + +wait_for_mysql() { + section "Wait for MySQL readiness" + # ping until mysqld accepts connections + for i in {1..60}; do + if docker exec mysql-container mysql -uroot -ppassword -e "SELECT 1" >/dev/null 2>&1; then + echo "MySQL is ready." + endsec; return 0 + fi + sleep 1 + done + echo "::error::MySQL did not become ready in time" + endsec; return 1 +} + +send_request() { + local kp_pid="$1" + + # Wait for the app to report healthy + for i in {1..60}; do + if curl -fsS http://localhost:9090/healthcheck >/dev/null; then + echo "good!App started" + break + fi + sleep 1 + done + + curl -sS -X POST http://localhost:9090/shorten -H "Content-Type: application/json" \ + -d '{"url": "https://github.com"}' || true + + curl -sS http://localhost:9090/resolve/4KepjkTT || true + + # Give Keploy a moment to persist artifacts, then stop it cleanly + sleep 10 + echo "$kp_pid Keploy PID" + echo "Killing Keploy" + sudo kill "$kp_pid" 2>/dev/null || true +} + +run_record_iteration() { + local idx="$1" + local app_name="urlShort_${idx}" + + section "Record iteration $idx" + + # Clean slate per run + rm -rf keploy/ keploy.yml || true + + # Start mysql (once) only for first iteration + if ! docker ps --format '{{.Names}}' | grep -q '^mysql-container$'; then + docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=uss \ + -p 3306:3306 --rm -d mysql:latest + wait_for_mysql + fi + + # Generate config + sudo "$RECORD_BIN" config --generate + sed -i 's/global: {}/global: {"body": {"updated_at":[]}}/' ./keploy.yml + + # Build app + go build -o urlShort + + # Start recording in background so we capture its PID explicitly + sudo -E env PATH="$PATH" "$RECORD_BIN" record -c "./urlShort" --generateGithubActions=false \ + > "${app_name}.txt" 2>&1 & + local KEPLOY_PID=$! + + # Drive traffic + stop keploy + send_request "$KEPLOY_PID" + + # Wait for keploy exit and capture code + set +e + wait "$KEPLOY_PID" + local rc=$? + set -e + echo "Record exit code: $rc" + if [[ $rc -ne 0 ]]; then + echo "::error::Keploy record exited with $rc (iteration $idx)" + fi + + # Quick sanity: ensure something was written + echo "== keploy artifacts after record ==" + find ./keploy -maxdepth 3 -type f | sort || true + + # Fail on obvious errors/races in log + if grep -q "WARNING: DATA RACE" "${app_name}.txt"; then + echo "::error::Data race detected in ${app_name}.txt" + cat "${app_name}.txt" + return 1 + fi + if grep -q "ERROR" "${app_name}.txt"; then + echo "::warning::Errors found in ${app_name}.txt (not fatal unless record failed)" + cat "${app_name}.txt" + fi + + endsec +} + +# ----- main flow ----- + +section "Environment" +echo "RECORD_BIN: $RECORD_BIN" +echo "REPLAY_BIN : $REPLAY_BIN" +"$RECORD_BIN" version 2>/dev/null || true +"$REPLAY_BIN" version 2>/dev/null || true +endsec + +for i in 1 2; do + run_record_iteration "$i" + echo "Recorded test case and mocks for iteration $i" +done + +section "Shutdown MySQL before test mode" +# Stop MySQL container - Keploy should use mocks for database interactions +docker stop mysql-container || true +docker rm mysql-container || true +echo "MySQL stopped - Keploy should now use mocks for database interactions" +endsec + +section "Replay" +# Run replay but DON'T crash the step; capture rc and print logs +set +e +sudo -E env PATH="$PATH" "$REPLAY_BIN" test -c "./urlShort" --delay 7 --generateGithubActions=false \ + > test_logs.txt 2>&1 +REPLAY_RC=$? +set -e +echo "Replay exit code: $REPLAY_RC" +cat test_logs.txt || true +endsec + +# If replay failed, still try to read reports to say which set failed +section "Check reports" +# Find the most recent test-run dir (don’t assume test-run-0) +RUN_DIR=$(ls -1dt ./keploy/reports/test-run-* 2>/dev/null | head -n1 || true) +if [[ -z "${RUN_DIR:-}" ]]; then + echo "::error::No test-run directory found under ./keploy/reports" + [[ $REPLAY_RC -ne 0 ]] && exit "$REPLAY_RC" || exit 1 +fi + +echo "Using reports from: $RUN_DIR" +all_passed=true +for rpt in "$RUN_DIR"/test-set-*-report.yaml; do + [[ -f "$rpt" ]] || continue + status=$(awk '/^status:/{print $2; exit}' "$rpt") + echo "Test status for $(basename "$rpt"): ${status:-}" + if [[ "$status" != "PASSED" ]]; then + all_passed=false + fi +done +endsec + +if [[ "$all_passed" == "true" && $REPLAY_RC -eq 0 ]]; then + echo "All tests passed" + exit 0 +fi + +echo "::error::Some tests failed or replay exited non-zero" +exit 1 \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/java-linux.sh b/keploy/.github/workflows/test_workflow_scripts/java-linux.sh new file mode 100644 index 0000000..f3a36ba --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/java-linux.sh @@ -0,0 +1,255 @@ +#!/usr/bin/env bash +# Safe, chatty CI script for Java + Postgres + Keploy with auto API-prefix detection + +set -Eeuo pipefail + +section() { echo "::group::$*"; } +endsec() { echo "::endgroup::"; } + +die() { + rc=$? + echo "::error::Pipeline failed (exit=$rc). Dumping context…" + echo "== docker ps =="; docker ps || true + echo "== postgres logs (complete) =="; docker logs mypostgres || true + echo "== workspace tree (depth 3) =="; find . -maxdepth 3 -type d -print | sort || true + echo "== keploy tree (depth 4) =="; find ./keploy -maxdepth 4 -type f -print 2>/dev/null | sort || true + echo "== *.txt logs (complete) =="; for f in ./*.txt; do [[ -f "$f" ]] && { echo "--- $f ---"; cat "$f"; }; done + [[ -f test_logs.txt ]] && { echo "== test_logs.txt (complete) =="; cat test_logs.txt; } + exit "$rc" +} +trap die ERR + +http_code() { + # prints HTTP status code or 000 on error + curl -s -o /dev/null -w "%{http_code}" "$1" 2>/dev/null || echo 000 +} + +wait_for_postgres() { + section "Wait for Postgres readiness" + for i in {1..120}; do + if docker exec mypostgres pg_isready -U petclinic -d petclinic >/dev/null 2>&1; then + echo "Postgres is ready." + endsec; return 0 + fi + # Fallback probe + docker exec mypostgres psql -U petclinic -d petclinic -c "SELECT 1" >/dev/null 2>&1 && { echo "Postgres responded."; endsec; return 0; } + sleep 1 + done + echo "::error::Postgres did not become ready in time" + endsec; return 1 +} + +wait_for_http_port() { + # waits for *any* HTTP response from root or actuator (not strict 200) + local base="http://localhost:9966" + section "Wait for app HTTP port" + for i in {1..180}; do + if curl -sS "${base}/" -o /dev/null || curl -sS "${base}/actuator/health" -o /dev/null; then + echo "HTTP port responded." + endsec; return 0 + fi + sleep 1 + done + echo "::error::App did not open HTTP port on 9966" + endsec; return 1 +} + +detect_api_prefix() { + # returns either /petclinic/api or /api (echo to stdout), otherwise empty + local base="http://localhost:9966" + local candidates=( "/petclinic/api" "/api" ) + for p in "${candidates[@]}"; do + local code + code=$(http_code "${base}${p}/pettypes") + if [[ "$code" =~ ^(200|201|202|204)$ ]]; then + echo "$p"; return 0 + fi + done + # If no 2xx, still check which gives *any* non-404 (e.g., 401/403 if security toggled) + for p in "${candidates[@]}"; do + local code + code=$(http_code "${base}${p}/pettypes") + if [[ "$code" != "404" && "$code" != "000" ]]; then + echo "$p"; return 0 + fi + done + # Fallback to actuator presence: assume /api if actuator exists + if [[ "$(http_code "${base}/actuator/health")" == "200" ]]; then + echo "/api"; return 0 + fi + echo "" + return 1 +} + +send_request() { + local kp_pid="$1" + local base="http://localhost:9966" + + wait_for_http_port + + # Try to detect API prefix dynamically + local API_PREFIX + API_PREFIX=$(detect_api_prefix || true) + + if [[ -z "${API_PREFIX}" ]]; then + echo "::warning::Could not auto-detect API prefix. Trying both /petclinic/api and /api." + # Try both paths to maximize coverage + local paths=( "/petclinic/api" "/api" ) + for pref in "${paths[@]}"; do + curl -sS "${base}${pref}/pettypes" || true + curl -sS --request POST \ + --url "${base}${pref}/pettypes" \ + --header 'content-type: application/json' \ + --data '{"name":"John Doe"}' || true + curl -sS "${base}${pref}/pettypes" || true + curl -sS --request POST \ + --url "${base}${pref}/pettypes" \ + --header 'content-type: application/json' \ + --data '{"name":"Alice Green"}' || true + curl -sS "${base}${pref}/pettypes" || true + curl -sS --request DELETE "${base}${pref}/pettypes/1" || true + curl -sS "${base}${pref}/pettypes" || true + done + else + echo "Detected API prefix: ${API_PREFIX}" + echo "good!App started" + curl -sS "${base}${API_PREFIX}/pettypes" || true + + curl -sS --request POST \ + --url "${base}${API_PREFIX}/pettypes" \ + --header 'content-type: application/json' \ + --data '{"name":"John Doe"}' || true + + curl -sS "${base}${API_PREFIX}/pettypes" || true + + curl -sS --request POST \ + --url "${base}${API_PREFIX}/pettypes" \ + --header 'content-type: application/json' \ + --data '{"name":"Alice Green"}' || true + + curl -sS "${base}${API_PREFIX}/pettypes" || true + + curl -sS --request DELETE "${base}${API_PREFIX}/pettypes/1" || true + + curl -sS "${base}${API_PREFIX}/pettypes" || true + fi + + # Let keploy persist, then stop it + sleep 10 + echo "$kp_pid Keploy PID" + echo "Killing keploy" + sudo kill "$kp_pid" 2>/dev/null || true +} + +# ----- main ----- + +source ./../../../.github/workflows/test_workflow_scripts/test-iid.sh + +section "Git branch" +git fetch origin +git checkout native-linux +endsec + +section "Start Postgres" +docker run -d --name mypostgres -e POSTGRES_USER=petclinic -e POSTGRES_PASSWORD=petclinic \ + -e POSTGRES_DB=petclinic -p 5432:5432 postgres:15.2 +wait_for_postgres +# seed DB +docker cp ./src/main/resources/db/postgresql/initDB.sql mypostgres:/initDB.sql +docker exec mypostgres psql -U petclinic -d petclinic -f /initDB.sql +endsec + +section "Java setup" +source ./../../../.github/workflows/test_workflow_scripts/update-java.sh +endsec + +# Clean once (keep artifacts across iterations) +sudo rm -rf keploy/ + +for i in 1 2; do + section "Record iteration $i" + + # Build app (captured to log) + mvn clean install -Dmaven.test.skip=true | tee -a mvn_build.log + + app_name="javaApp_${i}" + + # Start keploy in background, capture PID + sudo -E env PATH="$PATH" "$RECORD_BIN" record \ + -c 'java -jar target/spring-petclinic-rest-3.0.2.jar' \ + > "${app_name}.txt" 2>&1 & + KEPLOY_PID=$! + + # Drive traffic and stop keploy + send_request "$KEPLOY_PID" + + # Wait for keploy exit and capture code + set +e + wait "$KEPLOY_PID" + rc=$? + set -e + echo "Record exit code: $rc" + [[ $rc -ne 0 ]] && echo "::warning::Keploy record exited non-zero (iteration $i)" + + # Quick sanity: ensure something was written + echo "== keploy artifacts after record ==" + find ./keploy -maxdepth 3 -type f | sort || true + + # Surface issues from record logs + if grep -q "WARNING: DATA RACE" "${app_name}.txt"; then + echo "::error::Data race detected in ${app_name}.txt" + cat "${app_name}.txt" + exit 1 + fi + if grep -q "ERROR" "${app_name}.txt"; then + echo "::warning::Errors found in ${app_name}.txt" + cat "${app_name}.txt" + fi + + endsec + echo "Recorded test case and mocks for iteration ${i}" +done + +section "Shutdown Postgres before test mode" +# Stop Postgres container - Keploy should use mocks for database interactions +docker stop mypostgres || true +docker rm mypostgres || true +echo "Postgres stopped - Keploy should now use mocks for database interactions" +endsec + +section "Replay" +set +e +sudo -E env PATH="$PATH" "$REPLAY_BIN" test \ + -c 'java -jar target/spring-petclinic-rest-3.0.2.jar' \ + --delay 20 \ + > test_logs.txt 2>&1 +REPLAY_RC=$? +set -e +echo "Replay exit code: $REPLAY_RC" +cat test_logs.txt || true +endsec + +section "Check reports" +RUN_DIR=$(ls -1dt ./keploy/reports/test-run-* 2>/dev/null | head -n1 || true) +if [[ -z "${RUN_DIR:-}" ]]; then + echo "::error::No test-run directory found under ./keploy/reports" + [[ $REPLAY_RC -ne 0 ]] && exit "$REPLAY_RC" || exit 1 +fi +echo "Using reports from: $RUN_DIR" + +all_passed=true +for rpt in "$RUN_DIR"/test-set-*-report.yaml; do + [[ -f "$rpt" ]] || continue + status=$(awk '/^status:/{print $2; exit}' "$rpt") + echo "Test status for $(basename "$rpt"): ${status:-}" + [[ "$status" == "PASSED" ]] || all_passed=false +done +endsec + +if [[ "$all_passed" == "true" && $REPLAY_RC -eq 0 ]]; then + echo "All tests passed" + exit 0 +fi + +echo "::error::Some tests failed or replay exited non-zero" +exit 1 \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/node-docker.sh b/keploy/.github/workflows/test_workflow_scripts/node-docker.sh new file mode 100755 index 0000000..10da181 --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/node-docker.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +source ./../../.github/workflows/test_workflow_scripts/test-iid.sh + +# Start the docker container. +docker network create keploy-network +docker run --name mongoDb --rm --net keploy-network -p 27017:27017 -d mongo + +# Remove any preexisting keploy tests. +sudo rm -rf keploy/ + +# Build the image of the application. +docker build -t node-app:1.0 . + +container_kill() { + pid=$(pgrep -n keploy) + echo "$pid Keploy PID" + echo "Killing keploy" + sudo kill $pid +} + +send_request(){ + sleep 10 + # Wait for the application to start. + app_started=false + while [ "$app_started" = false ]; do + if curl -X GET http://localhost:8000/students; then + app_started=true + fi + sleep 3 # wait for 3 seconds before checking again. + done + + # Start making curl calls to record the testcases and mocks. + curl --request POST \ + --url http://localhost:8000/students \ + --header 'content-type: application/json' \ + --data '{ + "name":"John Doe", + "email":"john@xyz.com", + "phone":"0123456799" + }' + + curl --request POST \ + --url http://localhost:8000/students \ + --header 'content-type: application/json' \ + --data '{ + "name":"Alice Green", + "email":"green@alice.com", + "phone":"3939201584" + }' + + curl -X GET http://localhost:8000/students + # Wait for 5 seconds for keploy to record the tcs and mocks. + sleep 5 + container_kill + wait +} + +for i in {1..2}; do + # Start keploy in record mode. + container_name="nodeApp_${i}" + send_request & + sudo -E env PATH=$PATH $RECORD_BIN record -c "docker run -p 8000:8000 --name "${container_name}" --network keploy-network node-app:1.0" --container-name "${container_name}" &> "${container_name}.txt" + if grep "ERROR" "${container_name}.txt"; then + echo "Error found in pipeline..." + cat "${container_name}.txt" + exit 1 + fi + if grep "WARNING: DATA RACE" "${container_name}.txt"; then + echo "Race condition detected in recording, stopping pipeline..." + cat "${container_name}.txt" + exit 1 + fi + sleep 5 + + echo "Recorded test case and mocks for iteration ${i}" +done + +# Shutdown mongo before test mode - Keploy should use mocks for database interactions +echo "Shutting down mongo before test mode..." +docker stop mongoDb || true +docker rm mongoDb || true +echo "MongoDB stopped - Keploy should now use mocks for database interactions" + +# Start keploy in test mode. +test_container="nodeApp_test" +sudo -E env PATH=$PATH $REPLAY_BIN test -c "docker run -p8000:8000 --rm --name $test_container --network keploy-network node-app:1.0" --containerName "$test_container" --apiTimeout 30 --delay 30 --generate-github-actions=false &> "${test_container}.txt" +if grep "ERROR" "${test_container}.txt"; then + echo "Error found in pipeline..." + cat "${test_container}.txt" + exit 1 +fi +# Monitor Docker logs for race conditions during testing. +if grep "WARNING: DATA RACE" "${test_container}.txt"; then + echo "Race condition detected in test, stopping pipeline..." + cat "${test_container}.txt" + exit 1 +fi +all_passed=true + +for i in {0..1} +do + # Define the report file for each test set + report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" + + # Extract the test status + test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') + + # Print the status for debugging + echo "Test status for test-set-$i: $test_status" + + # Check if any test set did not pass + if [ "$test_status" != "PASSED" ]; then + all_passed=false + echo "Test-set-$i did not pass." + break # Exit the loop early as all tests need to pass + fi +done + +# Check the overall test status and exit accordingly +if [ "$all_passed" = true ]; then + echo "All tests passed" + exit 0 +else + cat "${test_container}.txt" + exit 1 +fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/node-encoding.sh b/keploy/.github/workflows/test_workflow_scripts/node-encoding.sh new file mode 100644 index 0000000..bdaf008 --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/node-encoding.sh @@ -0,0 +1,165 @@ +#!/bin/bash + +# Load test scripts and start MongoDB container +source ./../.github/workflows/test_workflow_scripts/test-iid.sh + +# Prepare environment +npm install +rm -rf keploy/ + +# Check if there is a keploy-config file, if there is, delete it. +if [ -f "./keploy.yml" ]; then + rm ./keploy.yml +fi + +echo "Record Bin: $RECORD_BIN" +echo "Record Version:" +sudo $RECORD_BIN --version +echo "Replay Bin: $REPLAY_BIN" +echo "Replay Version:" +sudo $REPLAY_BIN --version + +# Generate the keploy-config file. +sudo $RECORD_BIN config --generate + +# Update the global noise to ts. +config_file="./keploy.yml" +sed -i 's/global: {}/global: {"header": {"Etag":""}}/' "$config_file" + +send_request(){ + node server.js & + node_pid=$! + sleep 10 + app_started=false + while [ "$app_started" = false ]; do + if curl http://localhost:3000/; then + app_started=true + fi + sleep 3 # wait for 3 seconds before checking again. + done + echo "App started" + # Start making curl calls to record the testcases and mocks. + curl -v -H "Accept-Encoding: br" -i http://localhost:3000/ --output - + curl -v -H "Accept-Encoding: gzip" -i http://localhost:3000/ --output - + curl -v -H "Accept-Encoding: br" -i http://localhost:3000/proxy --output - + curl -v -H "Accept-Encoding: gzip" -i http://localhost:3000/proxy --output - + node request.js & + request_pid=$! + # Wait for 10 seconds for keploy to record the tcs and mocks. + sleep 10 + echo "Killing request.js" + if kill -0 $request_pid 2>/dev/null; then + kill $request_pid + wait $request_pid 2>/dev/null + fi + echo "Killing node server.js" + kill $node_pid + wait $node_pid 2>/dev/null + pid=$(pgrep keploy) + echo "$pid Keploy PID" + echo "Killing keploy" + sudo kill $pid +} + +# Record and test sessions in a loop +for i in {1..2}; do + app_name="nodeApp_${i}" + send_request & + sudo -E env PATH=$PATH $RECORD_BIN record -c 'node app.js' &> "${app_name}.txt" + cat "${app_name}.txt" + if grep "ERROR" "${app_name}.txt"; then + echo "Error found in pipeline..." + cat "${app_name}.txt" + exit 1 + fi + if grep "WARNING: DATA RACE" "${app_name}.txt"; then + echo "Race condition detected in recording, stopping pipeline..." + cat "${app_name}.txt" + exit 1 + fi + sleep 5 + wait + echo "Recorded test case and mocks for iteration ${i}" +done + +# Test modes and result checking +sudo -E env PATH=$PATH $REPLAY_BIN test -c 'node app.js' --delay 10 &> test_logs1.txt +cat test_logs1.txt +if grep "ERROR" "test_logs1.txt"; then + echo "Error found in pipeline..." + cat "test_logs1.txt" + exit 1 +fi +if grep "WARNING: DATA RACE" "test_logs1.txt"; then + echo "Race condition detected in test, stopping pipeline..." + cat "test_logs1.txt" + exit 1 +fi + +sudo -E env PATH=$PATH $REPLAY_BIN test -c 'node app.js' --delay 10 --testsets test-set-0 &> test_logs2.txt +cat test_logs2.txt +if grep "ERROR" "test_logs2.txt"; then + echo "Error found in pipeline..." + cat "test_logs2.txt" + exit 1 +fi +if grep "WARNING: DATA RACE" "test_logs2.txt"; then + echo "Race condition detected in test, stopping pipeline..." + cat "test_logs2.txt" + exit 1 +fi + +sudo -E env PATH=$PATH $REPLAY_BIN test -c 'node app.js' --apiTimeout 30 --delay 10 &> test_logs3.txt +cat test_logs3.txt +if grep "ERROR" "test_logs3.txt"; then + echo "Error found in pipeline..." + cat "test_logs3.txt" + exit 1 +fi +if grep "WARNING: DATA RACE" "test_logs3.txt"; then + echo "Race condition detected in test, stopping pipeline..." + cat "test_logs3.txt" + exit 1 +fi + +all_passed=true + +for i in {0..2} +do + report_file="./keploy/reports/test-run-$i/test-set-0-report.yaml" + # Extract the test status + test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') + + # Print the status for debugging + echo "Test status for test-set-$i: $test_status" + + # Check if any test set did not pass + if [ "$test_status" != "PASSED" ]; then + all_passed=false + echo "Test-set-$i did not pass." + cat "test_logs${i+1}.txt" + break # Exit the loop early as all tests need to pass + fi +done + + +# Check the overall test status and exit accordingly +if [ "$all_passed" = true ]; then + report_file="./keploy/reports/test-run-0/test-set-1-report.yaml" + test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') + + # Print the status for debugging + echo "Test status for test-set-0: $test_status" + + # Check if any test set did not pass + if [ "$test_status" != "PASSED" ]; then + all_passed=false + echo "Test-set-1 did not pass." + cat "test_logs1.txt" + exit 1 + fi + echo "All tests passed" + exit 0 +else + exit 1 +fi diff --git a/keploy/.github/workflows/test_workflow_scripts/node-linux.sh b/keploy/.github/workflows/test_workflow_scripts/node-linux.sh new file mode 100755 index 0000000..474bcbf --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/node-linux.sh @@ -0,0 +1,203 @@ +#!/usr/bin/env bash +# Safe, chatty CI script for Node + Mongo + Keploy + +set -Eeuo pipefail + +section() { echo "::group::$*"; } +endsec() { echo "::endgroup::"; } + +die() { + rc=$? + echo "::error::Pipeline failed (exit=$rc). Dumping context…" + echo "== docker ps =="; docker ps || true + echo "== mongo logs (complete) =="; docker logs mongoDb || true + echo "== workspace tree (depth 3) =="; find . -maxdepth 3 -type d -print | sort || true + echo "== keploy tree (depth 4) =="; find ./keploy -maxdepth 4 -type f -print 2>/dev/null | sort || true + echo "== *.txt logs (complete) =="; for f in ./*.txt; do [[ -f "$f" ]] && { echo "--- $f ---"; cat "$f"; }; done + for f in test_logs*.txt; do [[ -f "$f" ]] && { echo "== $f (complete) =="; cat "$f"; }; done + exit "$rc" +} +trap die ERR + +wait_for_mongo() { + section "Wait for Mongo readiness" + for i in {1..90}; do + if docker exec mongoDb mongosh --quiet --eval "db.adminCommand('ping').ok" >/dev/null 2>&1; then + echo "Mongo responds to ping." + endsec; return 0 + fi + if (echo > /dev/tcp/127.0.0.1/27017) >/dev/null 2>&1; then + echo "Mongo TCP port open." + endsec; return 0 + fi + sleep 1 + done + echo "::error::Mongo did not become ready in time" + endsec; return 1 +} + +wait_for_http() { + local url="$1" tries="${2:-60}" + for _ in $(seq 1 "$tries"); do + if curl -fsS "$url" >/dev/null; then return 0; fi + sleep 1 + done + return 1 +} + +send_request() { + local kp_pid="$1" + + if ! wait_for_http "http://localhost:8000/students" 120; then + echo "::error::App did not become healthy at /students" + else + echo "good!App started" + fi + + curl -sS --request POST --url http://localhost:8000/students \ + --header 'content-type: application/json' \ + --data '{"name":"John Doe","email":"john@xyiz.com","phone":"0123456799"}' || true + + curl -sS --request POST --url http://localhost:8000/students \ + --header 'content-type: application/json' \ + --data '{"name":"Alice Green","email":"green@alice.com","phone":"3939201584"}' || true + + curl -sS http://localhost:8000/students || true + curl -sS http://localhost:8000/get || true + + sleep 10 + echo "$kp_pid Keploy PID" + echo "Killing keploy" + sudo kill "$kp_pid" 2>/dev/null || true +} + +# ----- main ----- + +# Load test scripts and start MongoDB container +source ./../../.github/workflows/test_workflow_scripts/test-iid.sh + +section "Start Mongo" +docker run --name mongoDb --rm -p 27017:27017 -d mongo +wait_for_mongo +endsec + +section "Prepare app" +npm ci || npm install +sed -i "s/mongoDb:27017/localhost:27017/" "src/db/connection.js" +rm -rf keploy/ +[[ -f "./keploy.yml" ]] && rm ./keploy.yml + +# Generate the keploy-config file. +sudo "$RECORD_BIN" config --generate + +# Update the global noise to page (ignore changes to this field) +config_file="./keploy.yml" +sed -i 's/global: {}/global: {"body": {"page":[]}}/' "$config_file" +endsec + +for i in 1 2; do + section "Record iteration $i" + app_name="nodeApp_${i}" + + # Start keploy recording in background, capture PID + sudo -E env PATH="$PATH" "$RECORD_BIN" record -c 'npm start' \ + > "${app_name}.txt" 2>&1 & + KEPLOY_PID=$! + + # Drive traffic and stop keploy + send_request "$KEPLOY_PID" + + # Wait + capture rc + set +e + wait "$KEPLOY_PID" + rc=$? + set -e + echo "Record exit code: $rc" + [[ $rc -ne 0 ]] && echo "::warning::Keploy record exited non-zero (iteration $i)" + + echo "== keploy artifacts (depth 3) ==" + find ./keploy -maxdepth 3 -type f | sort || true + + if grep -q "WARNING: DATA RACE" "${app_name}.txt"; then + echo "::error::Data race detected in ${app_name}.txt" + cat "${app_name}.txt" + exit 1 + fi + if grep -q "ERROR" "${app_name}.txt"; then + echo "::warning::Errors found in ${app_name}.txt" + cat "${app_name}.txt" + fi + + endsec + echo "Recorded test case and mocks for iteration ${i}" +done + +# Optional tweak to a mock; guard if file exists +mocks_file="keploy/test-set-0/tests/test-5.yaml" +if [[ -f "$mocks_file" ]]; then + sed -i 's/"page":1/"page":4/' "$mocks_file" +else + echo "::warning::$mocks_file not found; skipping page change" +fi + +# ---- Replays ---- +# Shutdown MongoDB before test mode to verify Keploy mocking works correctly +section "Shutdown MongoDB before test mode" +docker stop mongoDb || true +docker rm mongoDb || true +echo "MongoDB stopped - Keploy should now use mocks for database interactions" +endsec + +run_replay() { + local idx="$1" + local extra_args="${2:-}" + local logfile="test_logs${idx}.txt" + + section "Replay #$idx (args: ${extra_args:-})" + set +e + sudo -E env PATH="$PATH" "$REPLAY_BIN" test -c 'npm start' --delay 10 $extra_args \ + > "$logfile" 2>&1 + local rc=$? + set -e + echo "Replay #$idx exit code: $rc" + cat "$logfile" || true + + # Find newest run dir and print set statuses + local RUN_DIR + RUN_DIR=$(ls -1dt ./keploy/reports/test-run-* 2>/dev/null | head -n1 || true) + if [[ -z "${RUN_DIR:-}" ]]; then + echo "::error::No test-run directory found after replay #$idx" + return "$rc" + fi + echo "Using reports from: $RUN_DIR" + local any_fail=false + for rpt in "$RUN_DIR"/test-set-*-report.yaml; do + [[ -f "$rpt" ]] || continue + local status + status=$(awk '/^status:/{print $2; exit}' "$rpt") + echo "Test status for $(basename "$rpt"): ${status:-}" + if [[ "$status" != "PASSED" ]]; then any_fail=true; fi + done + endsec + + if $any_fail; then + return 1 + else + return "$rc" + fi +} + +run_replay 1 +run_replay 2 "--testsets test-set-0" + +# enable selected tests in keploy.yml (guarded) +if [[ -f "./keploy.yml" ]]; then + sed -i 's/selectedTests: {}/selectedTests: {"test-set-0": ["test-1", "test-2"]}/' "./keploy.yml" || true +else + echo "::warning::keploy.yml missing; cannot set selectedTests" +fi + +run_replay 3 "--apiTimeout 30" + +echo "All replays completed. If no errors above, CI can PASS." +exit 0 \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/python-docker.sh b/keploy/.github/workflows/test_workflow_scripts/python-docker.sh new file mode 100755 index 0000000..3bc3eea --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/python-docker.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +source ./../../.github/workflows/test_workflow_scripts/test-iid.sh + +# Start mongo before starting keploy. +docker network create keploy-network +docker run --name mongo --rm --net keploy-network -p 27017:27017 -d mongo + +# Set up environment +rm -rf keploy/ # Clean up old test data +docker build -t flask-app:1.0 . # Build the Docker image + +# Configure keploy +sed -i 's/global: {}/global: {"header": {"Allow":[]}}/' "./keploy.yml" +sleep 5 # Allow time for configuration to apply + + +container_kill() { + pid=$(pgrep -n keploy) + echo "$pid Keploy PID" + echo "Killing keploy" + sudo kill $pid +} + +send_request(){ + local container_name=$1 + sleep 10 + app_started=false + while [ "$app_started" = false ]; do + if curl --silent http://localhost:6000/students; then + app_started=true + else + sleep 3 # Check every 3 seconds + fi + done + # Start making curl calls to record the testcases and mocks. + curl -X POST -H "Content-Type: application/json" -d '{"student_id": "12345", "name": "John Doe", "age": 20}' http://localhost:6000/students + curl -X POST -H "Content-Type: application/json" -d '{"student_id": "12346", "name": "Alice Green", "age": 22}' http://localhost:6000/students + curl http://localhost:6000/students + curl -X PUT -H "Content-Type: application/json" -d '{"name": "Jane Smith", "age": 21}' http://localhost:6000/students/12345 + curl http://localhost:6000/students + curl -X DELETE http://localhost:6000/students/12345 + + # Wait for 5 seconds for keploy to record the tcs and mocks. + sleep 5 + container_kill + wait +} + +# Record sessions +for i in {1..2}; do + container_name="flaskApp_${i}" + send_request & + sudo -E env PATH=$PATH $RECORD_BIN record -c "docker run -p6000:6000 --net keploy-network --rm --name $container_name flask-app:1.0" --container-name "$container_name" &> "${container_name}.txt" + if grep "ERROR" "${container_name}.txt"; then + echo "Error found in pipeline..." + cat "${container_name}.txt" + exit 1 + fi + if grep "WARNING: DATA RACE" "${container_name}.txt"; then + echo "Race condition detected in recording, stopping pipeline..." + cat "${container_name}.txt" + exit 1 + fi + sleep 5 + + echo "Recorded test case and mocks for iteration ${i}" +done + +# Shutdown mongo before test mode - Keploy should use mocks for database interactions +echo "Shutting down mongo before test mode..." +docker stop mongo || true +docker rm mongo || true +echo "MongoDB stopped - Keploy should now use mocks for database interactions" + +# Testing phase +test_container="flashApp_test" +sudo -E env PATH=$PATH $REPLAY_BIN test -c "docker run -p8080:8080 --net keploy-network --name $test_container flask-app:1.0" --containerName "$test_container" --apiTimeout 60 --delay 20 --generate-github-actions=false &> "${test_container}.txt" +if grep "ERROR" "${test_container}.txt"; then + echo "Error found in pipeline..." + cat "${test_container}.txt" + exit 1 +fi +if grep "WARNING: DATA RACE" "${test_container}.txt"; then + echo "Race condition detected in test, stopping pipeline..." + cat "${test_container}.txt" + exit 1 +fi + +all_passed=true + +for i in {0..1} +do + # Define the report file for each test set + report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" + + # Extract the test status + test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') + + # Print the status for debugging + echo "Test status for test-set-$i: $test_status" + + # Check if any test set did not pass + if [ "$test_status" != "PASSED" ]; then + all_passed=false + echo "Test-set-$i did not pass." + break # Exit the loop early as all tests need to pass + fi +done + +# Check the overall test status and exit accordingly +if [ "$all_passed" = true ]; then + echo "All tests passed" + exit 0 +else + cat "${test_container}.txt" + exit 1 +fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/python-linux.sh b/keploy/.github/workflows/test_workflow_scripts/python-linux.sh new file mode 100644 index 0000000..c064607 --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/python-linux.sh @@ -0,0 +1,128 @@ +#!/bin/bash + +source ./../../../.github/workflows/test_workflow_scripts/test-iid.sh + +# Checkout to the specified branch +git fetch origin +git checkout native-linux + +# Start the postgres database +docker compose up -d + +# Install dependencies +pip3 install -r requirements.txt + +# Setup environment +export PYTHON_PATH=./venv/lib/python3.10/site-packages/django + +# Database migrations +python3 manage.py makemigrations +python3 manage.py migrate + +# Configuration and cleanup +sudo $RECORD_BIN config --generate +sudo rm -rf keploy/ # Clean old test data +config_file="./keploy.yml" +sed -i 's/global: {}/global: {"header": {"Allow":[],}}/' "$config_file" +sleep 5 # Allow time for configuration changes + +send_request(){ + sleep 10 + app_started=false + while [ "$app_started" = false ]; do + if curl -X GET http://127.0.0.1:8000/; then + app_started=true + fi + sleep 3 # wait for 3 seconds before checking again. + done + echo "App started" + # Start making curl calls to record the testcases and mocks. + curl --location 'http://127.0.0.1:8000/user/' --header 'Content-Type: application/json' --data-raw '{ + "name": "Jane Smith", + "email": "jane.smith@example.com", + "password": "smith567", + "website": "www.janesmith.com" + }' + curl --location 'http://127.0.0.1:8000/user/' --header 'Content-Type: application/json' --data-raw '{ + "name": "John Doe", + "email": "john.doe@example.com", + "password": "john567", + "website": "www.johndoe.com" + }' + curl --location 'http://127.0.0.1:8000/user/' + # Wait for 10 seconds for keploy to record the tcs and mocks. + sleep 10 + pid=$(pgrep keploy) + echo "$pid Keploy PID" + echo "Killing keploy" + sudo kill $pid +} + +# Record and Test cycles +for i in {1..2}; do + app_name="flaskApp_${i}" + send_request & + sudo -E env PATH="$PATH" $RECORD_BIN record -c "python3 manage.py runserver" &> "${app_name}.txt" + if grep "ERROR" "${app_name}.txt"; then + echo "Error found in pipeline..." + cat "${app_name}.txt" + exit 1 + fi + if grep "WARNING: DATA RACE" "${app_name}.txt"; then + echo "Race condition detected in recording, stopping pipeline..." + cat "${app_name}.txt" + exit 1 + fi + sleep 5 + wait + echo "Recorded test case and mocks for iteration ${i}" +done + +# Shutdown postgres before test mode - Keploy should use mocks for database interactions +echo "Shutting down postgres before test mode..." +docker compose down +echo "Postgres stopped - Keploy should now use mocks for database interactions" + +# Testing phase +sudo -E env PATH="$PATH" $REPLAY_BIN test -c "python3 manage.py runserver" --delay 10 &> test_logs.txt + +if grep "ERROR" "test_logs.txt"; then + echo "Error found in pipeline..." + cat "test_logs.txt" + exit 1 +fi +if grep "WARNING: DATA RACE" "test_logs.txt"; then + echo "Race condition detected in test, stopping pipeline..." + cat "test_logs.txt" + exit 1 +fi + +all_passed=true + +for i in {0..1} +do + # Define the report file for each test set + report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" + + # Extract the test status + test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') + + # Print the status for debugging + echo "Test status for test-set-$i: $test_status" + + # Check if any test set did not pass + if [ "$test_status" != "PASSED" ]; then + all_passed=false + echo "Test-set-$i did not pass." + break # Exit the loop early as all tests need to pass + fi +done + +# Check the overall test status and exit accordingly +if [ "$all_passed" = true ]; then + echo "All tests passed" + exit 0 +else + cat "test_logs.txt" + exit 1 +fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/test-iid.sh b/keploy/.github/workflows/test_workflow_scripts/test-iid.sh new file mode 100644 index 0000000..45faacf --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/test-iid.sh @@ -0,0 +1,4 @@ +# Add fake installation-id for the workflow. +sudo mkdir ~/.keploy +sudo touch ~/.keploy/installation-id.yaml +echo "ObjectID('123456789')" | sudo tee ~/.keploy/installation-id.yaml > /dev/null \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/update-docker.sh b/keploy/.github/workflows/test_workflow_scripts/update-docker.sh new file mode 100644 index 0000000..ca5907d --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/update-docker.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Define the Dockerfile path +DOCKERFILE_PATH="./Dockerfile" + +# Function to add the -race flag to the go build command in the Dockerfile +update_dockerfile() { + echo "Updating Dockerfile to include the -race flag in the go build command..." + + # Use sed to update the Dockerfile + sed -i 's/RUN go build -tags=viper_bind_struct -ldflags="-X main.dsn=$SENTRY_DSN_DOCKER -X main.version=$VERSION" -o keploy ./RUN go build -race -tags=viper_bind_struct -ldflags="-X main.dsn=$SENTRY_DSN_DOCKER -X main.version=$VERSION" -o keploy ./' "$DOCKERFILE_PATH" + + echo "Dockerfile updated successfully." +} + +# Function to build the Docker image +build_docker_image() { + echo "Building Docker image..." + + # Build the Docker image + docker image build -t ttl.sh/keploy/keploy:1h . + + if [ $? -eq 0 ]; then + echo "Docker image built successfully." + else + echo "Failed to build Docker image." + exit 1 + fi +} + +# Main function to update the Dockerfile and build the Docker image +main() { + update_dockerfile + build_docker_image +} + +# Run the main function +main diff --git a/keploy/.github/workflows/test_workflow_scripts/update-java.sh b/keploy/.github/workflows/test_workflow_scripts/update-java.sh new file mode 100644 index 0000000..f744fbb --- /dev/null +++ b/keploy/.github/workflows/test_workflow_scripts/update-java.sh @@ -0,0 +1,8 @@ +#! /bin/bash + +# Update the java version +sudo apt update +sudo apt install openjdk-17-jre -y +export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 +export PATH=$JAVA_HOME/bin:$PATH +source ~/.bashrc \ No newline at end of file diff --git a/keploy/.github/write-good.yml b/keploy/.github/write-good.yml new file mode 100755 index 0000000..78accbc --- /dev/null +++ b/keploy/.github/write-good.yml @@ -0,0 +1,3 @@ +writeGood: true +alex: true +spellchecker: true \ No newline at end of file diff --git a/keploy/.gitignore b/keploy/.gitignore new file mode 100755 index 0000000..c0795f6 --- /dev/null +++ b/keploy/.gitignore @@ -0,0 +1,61 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Ignore the server binary output by `go build` but not any folders with the name server +/server +!/server/ + +# Ignore the config and log files +keploy-config.yaml +keploy.yml +keploy-logs.txt + + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +.deploy.sh + +### Go Patch ### +/vendor/ +/Godeps/ + +# Dependency directories (remove the comment below to include it) +# vendor/ +.idea/ +web/public/* +!web/public/README.md +!web/public/index.html + + +.vscode +.env +ui + +coverage.tmp.txt +coverage.txt +test-reports +.DS_Store +._.DS_Store +**/.DS_Store +**/._.DS_Store + +#Ignore the zip files +*.zip + +#Ignore the test reports +test-reports + +#Ignore the c and header files +*.c +*.h + +# Ignore the debug_bin +__debug_bin* +keploy \ No newline at end of file diff --git a/keploy/.golangci.yml b/keploy/.golangci.yml new file mode 100644 index 0000000..88b3898 --- /dev/null +++ b/keploy/.golangci.yml @@ -0,0 +1,24 @@ +# This is the configuration for golangci-lint for the restic project. +# +# A sample config with all settings is here: +# https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml +version: "2" + +linters: + enable: + - govet + - staticcheck + - errcheck + - ineffassign + - unused + exclusions: + rules: [] + paths-except: + - pkg/core/hooks/bpf_arm64_bpfel.go + - pkg/core/hooks/bpf_x86_bpfel.go + - pkg/service/utgen + +formatters: + enable: + - gofmt + - goimports \ No newline at end of file diff --git a/keploy/.pre-commit-config.yaml b/keploy/.pre-commit-config.yaml new file mode 100755 index 0000000..03cb10b --- /dev/null +++ b/keploy/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: +- hooks: + - id: commitizen + stages: + - commit-msg + repo: https://github.com/commitizen-tools/commitizen + rev: v2.21.2 diff --git a/keploy/.releaserc.json b/keploy/.releaserc.json new file mode 100755 index 0000000..eac6d3c --- /dev/null +++ b/keploy/.releaserc.json @@ -0,0 +1,13 @@ +{ + "branches": ["main"], + "repositoryUrl": "git@github.com:keploy/keploy.git", + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/github", + ["@semantic-release/exec", { + "prepareCmd" : "set-version ${nextRelease.version}", + "publishCmd" : "publish-package" + }] + ] +} \ No newline at end of file diff --git a/keploy/CITATION.cff b/keploy/CITATION.cff new file mode 100755 index 0000000..0d5b322 --- /dev/null +++ b/keploy/CITATION.cff @@ -0,0 +1,7 @@ +cff-version: 1.2.0 +title: 'Keploy: No code API testing platform. Create unit tests and data mocks from API calls.' +message: 'To cite Keploy in publications use:' +type: software +author: Keploy Inc +repository-code: 'https://github.com/keploy/keploy' +license: Apache License 2.0 \ No newline at end of file diff --git a/keploy/CODE_OF_CONDUCT.md b/keploy/CODE_OF_CONDUCT.md new file mode 100755 index 0000000..8b5b1bc --- /dev/null +++ b/keploy/CODE_OF_CONDUCT.md @@ -0,0 +1,126 @@ +# Contributor Code of Conduct for Keploy + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Expected Behavior + +The following behaviors are expected and requested of all community members: + + * Act authentically and participate actively in the community to help maintain a positive and productive environment. + * Show consideration and respect in all your actions and speech. Avoid any behavior that is demeaning, discriminatory, or harassing. + * Seek collaboration as an initial step instead of conflict. + * Refrain from demeaning, discriminatory, or harassing behavior and speech. + * Report any unsafe situations, distress or violations of the code of conduct to the maintainers through [Slack](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg). +* Practice empathy and kindness towards other community members. +* Respect diverse opinions, perspectives, and experiences. +* Give and receive constructive feedback in a gracious manner. +* Take responsibility for your actions and apologize for mistakes. Use them as learning opportunities. +* Prioritize the well-being and success of the community as a whole over individual gain. + +Examples of unacceptable behavior include: + +* Violence, threats of violence or any language that incites violence towards any individual or group is prohibited. + * Discriminatory jokes and language, such as those based on gender, race, sexual orientation, religion, ability, or any other characteristic, is strictly forbidden. + * Displaying or sharing sexually explicit or violent content is prohibited. + * Any form of harassment, including but not limited to "doxing" (posting or threatening to post other people's personally identifying information) is prohibited. + * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. +* Publishing any personal information of others without their explicit consent is strictly forbidden. +* Using sexualized language or imagery, or making any sexual advances towards another person is prohibited. +* Harassment, whether it be public or private, will not be tolerated. + +## Enforcement Responsibilities + +Organizations' maintainers are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +They have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces related to Keploy, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +support@keploy.io. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Contact info + +* [Slack](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) +* [Mail](hello@keploy.io) + +## Support 🙏 + +This project needs a ⭐️ from you. Don't forget to leave a star ⭐️ + +## Happy Contributions !! diff --git a/keploy/CONTRIBUTING.md b/keploy/CONTRIBUTING.md new file mode 100755 index 0000000..e30112d --- /dev/null +++ b/keploy/CONTRIBUTING.md @@ -0,0 +1,122 @@ +# Contributing to Keploy + +Thank you for your interest in Keploy and for taking the time to contribute to this project. 🙌 Keploy is a project by developers for developers and there are a lot of ways you can contribute. + +If you don't know where to start contributing, ask us on our [Slack channel](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg). + +## Code of conduct + +Contributors are expected to adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). + +## Prerequisites for the contributors + +Contributors should have knowledge of git, go, and markdown for most projects since the project work heavily depends on them. +We encourage Contributors to set up Keploy for local development and play around with the code and tests to get more comfortable with the project. + +Sections + +- General Contribution Flow + - Developer Certificate of Origin +- Keploy Contribution Flow + - Keploy Server + - Keploy Documentation + +# General Contribution Flow + +## Signing-off on Commits (Developer Certificate of Origin) + +To contribute to this project, you must agree to the Developer Certificate of +Origin (DCO) for each commit you make. The DCO is a simple statement that you, +as a contributor, have the legal right to make the contribution. + +See the [DCO](https://developercertificate.org) file for the full text of what you must agree to +and how it works [here](https://github.com/probot/dco#how-it-works). +To signify that you agree to the DCO for contributions, you simply add a line to each of your +git commit messages: + +``` +Signed-off-by: Jane Smith +``` + +In most cases, you can add this signoff to your commit automatically with the +`-s` or `--signoff` flag to `git commit`. You must use your real name and a reachable email +address (sorry, no pseudonyms or anonymous contributions). An example of signing off on a commit: + +``` +$ commit -s -m “my commit message w/signoff” +``` + +To ensure all your commits are signed, you may choose to add this alias to your global `.gitconfig`: + +_~/.gitconfig_ + +``` +[alias] + amend = commit -s --amend + cm = commit -s -m + commit = commit -s +``` + +# How to contribute ? + +We encourage contributions from the community. + +**Create a [GitHub issue](https://github.com/keploy/keploy/issues) for any changes beyond typos and small fixes.** + +We review GitHub issues and PRs on a regular schedule. + +To ensure that each change is relevant and properly peer reviewed, please adhere to best practices for open-source contributions. +This means that if you are outside the Keploy organization, you must fork the repository and create PRs from branches on your own fork. +The README in GitHub's [first-contributions repo](https://github.com/firstcontributions/first-contributions) provides an example. + +## ## How to set up the docs website locally? + +1. Fork the repository + +
+ +2. Clone the repository with the following command. Replace the with your username + +```sh +git clone https://github.com//keploy.git +``` + +
+ +3. Go into the directory containing the project and edit the changes. + + +When we merge your PR, a new build automatically occurs and your changes publish to [https://keploy.io](https://github.com/keploy/keploy). + +## Keploy Contribution Flow + +Keploy is written in `Go` (Golang) and leverages Go Modules. Relevant coding style guidelines are the [Go Code Review Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) and the _Formatting and style_ section of Peter Bourgon's [Go: Best +Practices for Production Environments](https://peter.bourgon.org/go-in-production/#formatting-and-style). + +There are many ways in which you can contribute to Keploy. + +### Keploy Server + +#### Report a Bug +Report all issues through GitHub Issues using the [Report a Bug](https://github.com/keploy/keploy/issues/new?assignees=&labels=&template=bug_report.md&title=) template. +To help resolve your issue as quickly as possible, read the template and provide all the requested information. + +#### Feature request +We welcome all feature requests, whether it's to add new functionality to an existing extension or to offer an idea for a brand new extension. +File your feature request through GitHub Issues using the [Feature Request](https://github.com/keploy/keploy/issues/new?assignees=&labels=&template=feature_request.md&title=) template. + +#### Close a Bug +We welcome contributions that help make keploy bug free & improve the experience of our users. You can also find issues tagged [Good First Issues](https://github.com/keploy/keploy/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). + +### Keploy Documentation + +The Keploy documentation site uses Docusaurus 2, which is a static website generator, you can make changes locally without previewing them in the browser. + +In the process of shipping features quickly, we may forget to keep our docs up to date. You can help by suggesting improvements to our documentation using the [Documentation Improvement](https://github.com/keploy/docs/issues) template. + +Please refer to [Keploy Docs Contributing Guide](https://github.com/keploy/docs/blob/main/CONTRIBUTING.md#-how-to-set-up-the-docs-website-locally) for setting up your development environment and the follow [Keploy Style Guide](https://github.com/keploy/docs/blob/main/STYLE.md). + + +# Contact + +Feel free to join [slack](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) to start a conversation with us. diff --git a/keploy/DEBUG.md b/keploy/DEBUG.md new file mode 100644 index 0000000..210ef44 --- /dev/null +++ b/keploy/DEBUG.md @@ -0,0 +1,79 @@ +# 🐰 Keploy Debugging Guide: + +## 1) Capturing Stack Traces with `SIGQUIT` + +If Keploy appears stuck or unresponsive, and you want a quick insight into what it's doing (e.g., which goroutines are running), you can send a `SIGQUIT` signal. This is a fast alternative to setting up `pprof` and will make the Go runtime dump a full stack trace to `stderr`. + +--- + +## 🔧 **When Keploy is Running Natively (on the Host)** + +### 1. **Find the PID of the Keploy process** + +You can use the proxy port Keploy listens on (`16789` by default) to locate the process: + +```bash +sudo lsof -i:16789 +``` +Example output: + +```bash +keploy 12345 root ... TCP *:16789 ... +``` + +In this case, 12345 is the PID of the keploy process. + +### 2. Send a SIGQUIT signal to the Keploy process +```bash +sudo kill -SIGQUIT 12345 +``` + +The Go runtime will then print a full goroutine stack trace to the terminal or to wherever your logs are configured to go. + + + +## 🐳 When Keploy is Running in Docker (with --pid=host) + +If Keploy is running in a Docker container with the --pid=host flag, docker kill --signal=SIGQUIT won't work because Keploy is not PID 1 inside the container. You'll need to send the signal to the actual host PID. + +### 1. Find the host PID of the /app/keploy process + +Use docker top to inspect the container's process tree: + +```bash +docker top keploy-v2 +``` + +Look for a line where the command starts with /app/keploy: +```bash +UID PID PPID C STIME TTY TIME CMD +root 341003 ... ... ... ... ... /app/keploy record -c ... +``` +In this case, 341003 is the host PID of the running Go binary inside the container. + +### 2. Send SIGQUIT to that PID from the host + +```bash +sudo kill -SIGQUIT 341003 +``` + +This will cause the Go runtime to emit a full stack trace from the running keploy process. + +You should see a stack trace similar to: +```bash +SIGQUIT: quit +goroutine 1 [running]: +main.main() + /app/main.go:42 +0x123 +... +``` + +⸻ + +🧠 Notes + • This is a quick way to inspect a hung or slow keploy run without setting up full profiling. + • Make sure the Go binary is not built with -ldflags="-s -w" — that strips debug symbols, making stack traces useless. + • Do not intercept SIGQUIT in your code using signal.Notify(..., syscall.SIGQUIT) — it prevents the Go runtime from printing the trace. + • When using --pid=host, ensure you signal the actual host PID, not via docker kill. + +⸻ \ No newline at end of file diff --git a/keploy/Dockerfile b/keploy/Dockerfile new file mode 100755 index 0000000..eff8195 --- /dev/null +++ b/keploy/Dockerfile @@ -0,0 +1,57 @@ +# === Build Stage === +FROM golang:1.24 AS build + +# Set the working directory +WORKDIR /app + +# Define build arguments for ldflags +ARG SENTRY_DSN_DOCKER +ARG VERSION +ARG SERVER_URL + +# Copy the Go module files and download dependencies +COPY go.mod go.sum /app/ +RUN go mod download + +# Copy the contents of the current directory into the build container +COPY . /app + +# Build the keploy binary +# setting GOMAXPROCS to avoid crashing qemu while building different arch with docker buildx +# ref - https://github.com/golang/go/issues/70329#issuecomment-2559049444 +RUN GOMAXPROCS=2 go build -tags=viper_bind_struct -ldflags="-X main.dsn=$SENTRY_DSN_DOCKER -X main.version=$VERSION -X main.apiServerURI=$SERVER_URL -X main.gitHubClientID=$GITTHUB_APP_CLIENT_ID" -o keploy . + +# === Runtime Stage === +FROM debian:bookworm-slim + +ENV KEPLOY_INDOCKER=true + +# Update the package lists and install required packages +RUN apt-get update +RUN apt-get install -y ca-certificates curl sudo && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Install Docker engine +RUN curl -fsSL https://get.docker.com -o get-docker.sh && \ + sh get-docker.sh && \ + rm get-docker.sh + +# Install docker-compose to PATH +# Install specific version of Docker Compose plugin (v2.29.1) +RUN mkdir -p /usr/lib/docker/cli-plugins && \ + curl -SL "https://github.com/docker/compose/releases/download/v2.29.1/docker-compose-linux-$(uname -m)" -o /usr/lib/docker/cli-plugins/docker-compose && \ + chmod +x /usr/lib/docker/cli-plugins/docker-compose + +# Copy the keploy binary and the entrypoint script from the build container +COPY --from=build /app/keploy /app/keploy +COPY --from=build /app/entrypoint.sh /app/entrypoint.sh + +# windows comapatibility +RUN sed -i 's/\r$//' /app/entrypoint.sh + +# Make the entrypoint.sh file executable +RUN chmod +x /app/entrypoint.sh + +# Set the entrypoint +ENTRYPOINT ["/app/entrypoint.sh", "/app/keploy"] diff --git a/keploy/HACKTOBERFEST_GUIDE.md b/keploy/HACKTOBERFEST_GUIDE.md new file mode 100644 index 0000000..6311476 --- /dev/null +++ b/keploy/HACKTOBERFEST_GUIDE.md @@ -0,0 +1,82 @@ +

+ +

Celebrate +Open Source with Hacktoberfest 2023

+ +![image](https://github.com/keploy/docs/blob/main/static/img/hacktoberfest-2023.png?raw=true) + +

𝑶𝒏𝒆 𝒄𝒐𝒏𝒕𝒓𝒊𝒃𝒖𝒕𝒊𝒐𝒏 𝒂𝒕 𝒂 𝒕𝒊𝒎𝒆

+ +

+ +--- + + +## Our Journey with Hacktoberfest ❤️ + +[Hacktoberfest](https://hacktoberfest.com/) is an initiative that matters very deeply to us. We launched the first iteration of Keploy as a mere open-source project in December 2021. Hacktoberfest 2022 was truly a game-changer for us, as we saw over 200 contributions from some lovely members of the open-source community that October. + +There are many different ways you can contribute to [Keploy](https://keploy.io). If you’ve ever wanted to contribute to open-source now is your chance! + +All backgrounds and skill levels are encouraged to participate. [Learn How to Contribute?](https://opensource.guide/how-to-contribute) + +# About Keploy + +Keploy is a next-gen E2E testing tool that provides an easy way to capture and generate tests(KTests) and data-mocks(KMocks) from real API calls. It automatically generates mocks and stubs, making the testing process simpler and more efficient. + +**- Automatically Mocks Dependencies**
+**- Zero Code Change Integration**
+**- Language-Agnostic Support**
+**- Native Docker/Kubernetes Support**
+**- Asynchronous Processes Support**
+ +
+ +[
⭐ Star and try Out Keploy ➜
](https://keploy.io) + +
+ +___ + +### Learn more about projects and contributing + +👨🏻‍💻 **Code Contribubtion** + +Code contributions are a great way to get involved in supporting open source, and learn new skills. Here are some examples of ways you can contribute to open-source projects: + +- 👉 Bug fixes :- If you'd like to break and build software, fix a current issue reported by the community and be a hero! + +- 👉 Implement features :- You can choose from a carefully curated selection of Hacktoberfest requests that have been made by community members. + +- 👉 Build a demo app :- Build a demo app with Keploy and share it with the community. + + +📝 **Low Code and No-Code Contribution** + +You can choose from a carefully curated selection of Hacktoberfest requests that have been made by community members. + +- Technical documentation +- User experience testing +- Case studies +- Technical blog post or tutorial +- Translating to Other Languages +- Give Talks or presentations +- Organize event with community + +# Community Support + +The open source community needs you. Do you have what it takes to join the community and build a better future? We’re here to help you. + +

+ +   + +   + +   + +   +

+ +
+ diff --git a/keploy/LICENSE b/keploy/LICENSE new file mode 100755 index 0000000..c20e7bd --- /dev/null +++ b/keploy/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Keploy Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/keploy/README-UnitGen.md b/keploy/README-UnitGen.md new file mode 100644 index 0000000..ff86a43 --- /dev/null +++ b/keploy/README-UnitGen.md @@ -0,0 +1,278 @@ +

+ keploy logo +

+

+ +⚡️ Generate unit tests with LLMs, that actually works ⚡️ + +

+

+🌟 The must-have tool for developers in the AI-Gen era 🌟 +

+ +--- + +

+ + + Keploy X + + + + Help us reach 20k stars! + + + + Keploy CNCF Landscape + + +[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) +[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) +[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/keployio) + +

+ +--- + +Keploy-gen uses LLMs to understand code semantics and generates meaningful **unit tests**. It's inspired by the [Automated Unit Test Improvement using LLM at Meta](https://arxiv.org/pdf/2402.09171). + +### Objectives + +- **Automate unit test generation (UTG)**: Quickly generate comprehensive unit tests and reduce the redundant manual effort. + +- **Improve edge cases**: Extend and improve the scope of tests to cover more complex scenarios that are often missed manually. + +- **Boost test coverage**: As codebase grows, ensuring exhaustive coverage should become feasible. + +## Core Components + +| **Phase** | **Activities** | **Tools/Technologies** | +| ----------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------- | +| **Code Analysis** | Analyze the code structure and dependencies. | Static analysis tools, LLMs | +| **Prompt Engineering** | Generation of targeted prompts to guide the LLM in producing relevant tests. | LLMs, Custom scripts | +| **Iterative Test Refinement** | Cyclic process of refining tests by running them, analyzing coverage, and incorporating feedback. | Testing frameworks (e.g., JUnit, pytest) | + +### Process Overview + +Referred from [Meta's research](https://arxiv.org/pdf/2402.09171), TestGen-LLM top level architecture. + +Test refinement process of unit test generator + +## Prerequisites + +**AI model Setup** - Set the environment variable **API_KEY**. +``` +export API_KEY=xxxx +``` + +**API_KEY** can be from either of one these: +- **OpenAI's GPT-4o** directly **[preferred]**. + +- Alternative LLMs via [litellm](https://github.com/BerriAI/litellm?tab=readme-ov-file#quick-start-proxy---cli). + +- Azure OpenAI + +## Installation + +Install Keploy locally by running the following command: + +#### ➡ Linux/Mac + +```shell + curl --silent -O -L https://keploy.io/install.sh && source install.sh +``` + +#### ➡ Windows + +- [Download](https://github.com/keploy/keploy/releases/latest/download/keploy_windows_amd64.tar.gz) and **move the keploy.exe file** to `C:\Windows\System32` + +### ![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) ➡ Running with Node.js/TypeScript applications + +- Ensure you've set the API key, as mentioned in pre-requisites above: + + ```shell + export API_KEY=xxxx + ``` + +- Ensure **Cobertura** formatted coverage reports, edit `jest.config.js` or `package.json`: +
+ + ```json + // package.json + "jest": { + "coverageReporters": ["text", "cobertura"], + } + ``` + or + + ```javascript + // jest.config.js + module.exports = { + coverageReporters: ["text", "cobertura"], + }; + ``` + +#### Generating Unit Tests + +- Run the following command in the root of your application. +
+ + - **For Single Test File:** If you prefer to test a smaller section of your application or to control costs, consider generating tests for a single source and its corresponding test file: + + ```shell + keploy gen --sourceFilePath="" --testFilePath="" --testCommand="npm test" --coverageReportPath="" + ``` + +
+ + - **For Entire Application** use the following command to generate tests across: + + ⚠️ **Warning:** Executing this command will generate unit tests for all files in the application. Depending on the size of the codebase, this process may take between 20 minutes to an hour and will incur costs related to LLM usage. + + ```bash + keploy gen --testCommand="npm test" --testDir="test" --coverageReportPath="" + ``` + + 🎉 You should see improved test cases and code-coverage. ✅ Enjoy coding with enhanced unit test coverage! 🫰 + +### ![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) → Running with Golang applications + +- Ensure you've set the API key, as mentioned in pre-requisites above: + + ```shell + export API_KEY=xxxx + ``` + +- To ensure **Cobertura** formatted coverage reports, add: + ```bash + go install github.com/axw/gocov/gocov@v1.1.0 + go install github.com/AlekSi/gocov-xml@v1.1.0 + ``` +#### Generating Unit Tests +- Run the following command in the root of your application. +
+ + - **For Single Test File:** If you prefer to test a smaller section of your application or to control costs, consider generating tests for a single source and its corresponding test file: + + ```shell + keploy gen --sourceFilePath="" --testFilePath="" --testCommand="go test -v ./... -coverprofile=coverage.out && gocov convert coverage.out | gocov-xml > coverage.xml" --coverageReportPath="" + ``` + +
+ + - **For Entire Application** use the following command to generate tests across: + + ⚠️ **Warning:** Executing this command will generate unit tests for all files in the application. Depending on the size of the codebase, this process may take between 20 minutes to an hour and will incur costs related to LLM usage. + + ```bash + keploy gen --testDir="." --testCommand="go test -v ./... -coverprofile=coverage.out && gocov convert coverage.out | gocov-xml > coverage.xml" --coverageReportPath="" + ``` + + 🎉 You should see improved test cases and code-coverage. ✅ Enjoy coding with enhanced unit test coverage! 🫰 + +### → Setup for Other Languages + +- Ensure you've set the API key, as mentioned in pre-requisites above: + + ```shell + export API_KEY=xxxx + ``` + +- Ensure that your unit test report format is **Cobertura**(it's very common). +- Generate tests using keploy-gen: + ```bash + keploy gen --sourceFilePath="" --testFilePath="" --testCommand="" --coverageReportPath="" + ``` + +## Configuration + +Configure Keploy using command-line flags: + +```bash + + --sourceFilePath "" + --testFilePath "" + --coverageReportPath "coverage.xml" + --testCommand "" + --coverageFormat "cobertura" + --expectedCoverage 100 + --maxIterations 5 + --testDir "" + --llmBaseUrl "https://api.openai.com/v1" + --model "gpt-4o" + --llmApiVersion " +``` + +- `sourceFilePath`: Path to the source file for which tests are to be generated. +- `testFilePath`: Path where the generated tests will be saved. +- `coverageReportPath`: Path to generate the coverage report. +- `testCommand` (required): Command to execute tests and generate the coverage report. +- `coverageFormat`: Type of the coverage report (default "cobertura"). +- `expectedCoverage`: Desired coverage percentage (default 100%). +- `maxIterations`: Maximum number of iterations for refining tests (default 5). +- `testDir`: Directory where tests will be written. +- `llmBaseUrl`: Base url of the llm. +- `model`: Specifies the AI model to use (default "gpt-4o"). +- `llmApiVersion`: API version of the llm if any (default "") + +# Frequently Asked Questions + +1. What is Keploy's Unit Test Generator (UTG)?
+ - Keploy's UTG automates the creation of unit tests based on code semantics, enhancing test coverage and reliability. + +2. Does Keploy send your private data to any cloud server for test generation?
+ - No, Keploy does not send any user code to remote systems, except when using the unit test generation feature. When using the UT gen feature, only the source code and the unit test code will be sent to the Large Language Model (LLM) you are using. By default, Keploy uses - litellm to support vast number of LLM backends. Yes, if your organization has its own LLM(a private one), you can use it with Keploy. This ensures that data is not sent to any external systems. + +3. How does Keploy contribute to improving unit test coverage?
+ - By providing a zero code platform for automated testing, Keploy empowers developers to scale up their unit test coverage without extensive coding knowledge. This integration enhances testing reports, ultimately boosting confidence in the product's quality. + +4. Is Keploy cost-effective for automated unit testing?
+ - Yes, Keploy optimizes costs by automating repetitive testing tasks and improving overall test efficiency. + +5. How does Keploy generate coverage reports?
+ - Keploy generates detailed Cobertura format reports, offering insights into test effectiveness and code quality. + +6. Can Keploy handle large codebases efficiently?
+ - Yes, Keploy is designed to handle large codebases efficiently, though processing time may vary based on project size and complexity. + +# 🙋🏻‍♀️ Questions? 🙋🏻‍♂️ + +Reach out to us. We're here to answer! + +[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) +[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) +[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) +[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) + + +# 📝 Sample QuickStarts +- ![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) : Try a unit-gen on [Mux-SQL](https://github.com/keploy/samples-go/tree/main/mux-sql#create-unit-testcase-with-keploy) app + +- ![Node](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node&logoColor=white) : Try a unit-gen on [Express-Mongoose](https://github.com/keploy/samples-typescript/tree/main/express-mongoose#create-unit-testcase-with-keploy) app + +## 🌐 Language Support + +![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) +![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) + +Other language may be supported, we've not tested them yet. If your **coverage reports** are of **Cobertura format** then you should be able to use keploy-gen in any language. + +## Dev Support + +Keploy-gen is not just a project but an attempt to make developers life easier testing applications. +It aims to simplify the creation and maintenance of tests, ensuring high coverage, and adapts to the complexity of modern software development. + +#### Prompt Generation + +Referred from [Meta's research](https://arxiv.org/pdf/2402.09171), the four primary prompts used in the deployment for the December 2023 Instagram and Facebook app test-a-thons + +| Prompt Name | Template | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| extend_test | Here is a Kotlin unit test class: {`existing_test_class`}. Write an extended version of the test class that includes additional tests to cover some extra corner cases. | +| extend_coverage | Here is a Kotlin unit test class and the class that it tests: {`existing_test_class`} {`class_under_test`}. Write an extended version of the test class that includes additional unit tests that will increase the test coverage of the class under test. | +| corner_cases | Here is a Kotlin unit test class and the class that it tests: {`existing_test_class`} {`class_under_test`}. Write an extended version of the test class that includes additional unit tests that will cover corner cases missed by the original and will increase the test coverage of the class under test. | +| statement_to_complete | Here is a Kotlin class under test {`class_under_test`} This class under test can be tested with this Kotlin unit test class {`existing_test_class`}. Here is an extended version of the unit test class that includes additional unit test cases that will cover methods, edge cases, corner cases, and other features of the class under test that were missed by the original unit test class: | + +Limitation: This project currently doesn't generate quality fresh tests if there are no existing tests to learn from. + +Enjoy coding with enhanced unit test coverage! 🫰 diff --git a/keploy/README.md b/keploy/README.md new file mode 100755 index 0000000..ea0d5b6 --- /dev/null +++ b/keploy/README.md @@ -0,0 +1,172 @@ + +

+ keploy logo +

+

+ +⚡️ API tests faster than unit tests, from user traffic ⚡️ + +

+

+🌟 The must-have tool for developers in the AI-Gen era 🌟 +

+ +--- + +

+ + + Keploy X! + + + + Help us reach 20k stars! + + + + Keploy CNCF Landscape + + +[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) +[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) +[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) +[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) + +keploy%2Fkeploy | Trendshift +

+ + +[Keploy](https://keploy.io) is **developer-centric** API testing tool that creates **tests along with built-in-mocks**, faster than unit tests. + +Keploy not only records API calls, but also records database calls and replays them during testing, making it **easy to use, powerful, and extensible**. + +Convert API calls to test cases + +> 🐰 **Fun fact:** Keploy uses itself for testing! Check out our swanky coverage badge: [![Coverage Status](https://coveralls.io/repos/github/keploy/keploy/badge.svg?branch=main&kill_cache=1)](https://coveralls.io/github/keploy/keploy?branch=main&kill_cache=1)   + +## 🚨 Here for [Unit Test Generator](README-UnitGen.md) (ut-gen)? +Keploy has newly launched the world's first unit test generator(ut-gen) implementation of [Meta LLM research paper](https://arxiv.org/pdf/2402.09171), it understands code semantics and generates meaningful unit tests, aiming to: + +- **Automate unit test generation (UTG)**: Quickly generate comprehensive unit tests and reduce redundant manual effort. + +- **Improve edge cases**: Extend and improve the scope of automated tests to cover more complex scenarios, often missed manually. + +- **Boost test coverage**: As codebases grow, ensuring exhaustive coverage should become feasible, aligning with our mission. + +### 📜 Follow [Unit Test Generator README](README-UnitGen.md)! ✅ + +## 📘 Documentation! +Become a Keploy pro with **[Keploy Documentation](https://keploy.io/docs/)**. + +Record Replay Testing + +# 🚀 Quick Installation (API test generator) + +Integrate Keploy by installing the agent locally. No code-changes required. + +```shell +curl --silent -O -L https://keploy.io/install.sh && source install.sh +``` + +## 🎬 Recording Testcases + +Start your app with Keploy to convert API calls as Tests and Mocks/Stubs. + +```zsh +keploy record -c "CMD_TO_RUN_APP" +``` +For example, if you're using a simple Python app the `CMD_TO_RUN_APP` would resemble to `python main.py`, for Golang `go run main.go`, for java `java -jar xyz.jar`, for node `npm start`.. + +```zsh +keploy record -c "python main.py" +``` + +## 🧪 Running Tests +Shut down the databases, redis, kafka or any other services your application uses. Keploy doesn't need those during test. +```zsh +keploy test -c "CMD_TO_RUN_APP" --delay 10 +``` + +## ✅ Test Coverage Integration +To integrate with your unit-testing library and see combine test coverage, follow this [test-coverage guide](https://keploy.io/docs/server/sdk-installation/go/). + +> #### **If You Had Fun:** Please leave a 🌟 star on this repo! It's free and will bring a smile. 😄 👏 + +## One-Click Setup 🚀 + +Setup and run keploy quickly, with no local machine installation required: + +[![GitHub Codescape](https://img.shields.io/badge/GH%20codespace-3670A0?style=for-the-badge&logo=github&logoColor=fff)]([https://github.dev/Sonichigo/mux-sql](https://github.dev/Sonichigo/mux-sql)) + +## 🤔 Questions? +Reach out to us. We're here to help! + +[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) +[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) +[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) +[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) + + +## 🌐 Language Support +From Go's gopher 🐹 to Python's snake 🐍, we support: + +![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) +![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=for-the-badge&logo=java&logoColor=white) +![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) +![Rust](https://img.shields.io/badge/Rust-darkred?style=for-the-badge&logo=rust&logoColor=white) +![C#](https://img.shields.io/badge/csharp-purple?style=for-the-badge&logo=csharp&logoColor=white) +![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) + +## 🫰 Keploy Adopters 🧡 + +So you and your organisation are using Keploy? That’s great. Please add yourselves to [**this list,**](https://github.com/orgs/keploy/discussions/1765) and we'll send you goodies! 💖 + + +We are happy and proud to have you all as part of our community! 💖 + +## 🎩 How's the Magic Happen? +Keploy proxy captures and replays **ALL** (CRUD operations, including non-idempotent APIs) of your app's network interactions. + + +Take a journey to **[How Keploy Works?](https://keploy.io/docs/keploy-explained/how-keploy-works/)** to discover the tricks behind the curtain! + + ## 🔧 Core Features + +- ♻️ **Combined Test Coverage:** Merge your Keploy Tests with your fave testing libraries(JUnit, go-test, py-test, jest) to see a combined test coverage. + + +- 🤖 **EBPF Instrumentation:** Keploy uses EBPF like a secret sauce to make integration code-less, language-agnostic, and oh-so-lightweight. + + +- 🌐 **CI/CD Integration:** Run tests with mocks anywhere you like—locally on the CLI, in your CI pipeline (Jenkins, Github Actions..) , or even across a Kubernetes cluster. + + +- 📽️ **Record-Replay Complex Flows:** Keploy can record and replay complex, distributed API flows as mocks and stubs. It's like having a time machine for your tests—saving you tons of time! + + +- 🎭 **Multi-Purpose Mocks:** You can also use Keploy-generated Mocks, as server Tests! + + +👉 **Explore the code on GitHub**: [github.com/keploy/keploy](https://github.com/keploy/keploy) + + +## 👨🏻‍💻 Let's Build Together! 👩🏻‍💻 +Whether you're a newbie coder or a wizard 🧙‍♀️, your perspective is golden. Take a peek at our: + +📜 [Contribution Guidelines](https://github.com/keploy/keploy/blob/main/CONTRIBUTING.md) + +❤️ [Code of Conduct](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) + + +## 🐲 Current Limitations! +- **Unit Testing:** While Keploy is designed to run alongside unit testing frameworks (Go test, JUnit..) and can add to the overall code coverage, it still generates integration tests. +- **Production Lands**: Keploy is currently focused on generating tests for developers. These tests can be captured from any environment, but we have not tested it on high volume production environments. This would need robust deduplication to avoid too many redundant tests being captured. We do have ideas on building a robust deduplication system [#27](https://github.com/keploy/keploy/issues/27) + +## ✨ Resources! +🤔 [FAQs](https://keploy.io/docs/keploy-explained/faq/) + +🕵️‍️ [Why Keploy](https://keploy.io/docs/keploy-explained/why-keploy/) + +⚙️ [Installation Guide](https://keploy.io/docs/application-development/) + +📖 [Contribution Guide](https://keploy.io/docs/keploy-explained/contribution-guide/) diff --git a/keploy/READMEes-Es.md b/keploy/READMEes-Es.md new file mode 100644 index 0000000..8503289 --- /dev/null +++ b/keploy/READMEes-Es.md @@ -0,0 +1,243 @@ +

+ keploy logo +

+

+ +⚡️ Backend tests faster than unit-tests, from user traffic ⚡️ + +

+

+🌟 The must-have tool for developers in the AI-Gen era 🌟 +

+ +--- + +

+ + + + + + + + + + + + Keploy is released under the Apache License + + + + + + + + + + + + + + + + + + + + PRs welcome! + + + Help us reach 20k stars! + + + Join our Community! + + + + Keploy X + +

+ +## 🎤 Presentando Keploy 🐰 +Keploy es una herramienta de prueba de backend centrada en el **desarrollador**. Realiza pruebas de backend con **mocks incorporados**, más rápido que las pruebas unitarias, a partir del tráfico del usuario, lo que lo hace **fácil de usar, potente y extensible**. 🛠 + +¿Listo para la magia? Aquí están las características principales de Keploy: + +- ♻️ **Cobertura de prueba combinada:** Fusiona tus pruebas de Keploy con tus bibliotecas de pruebas favoritas (junit, go-test, py-test, jest) para ver una cobertura de prueba combinada. + +- 🤖 **Instrumentación EBPF:** Keploy utiliza EBPF como un ingrediente secreto para hacer que la integración sea sin código, independiente del lenguaje y muy ligera. + +- 🌐 **Integración CI/CD:** Ejecuta pruebas con mocks donde quieras, ya sea localmente en la CLI, en tu canal de integración continua o incluso en un clúster de Kubernetes. ¡Es prueba donde la necesitas! + +- 🎭 **Mocks multipropósito:** Úsalos en pruebas existentes, como pruebas de servidor o simplemente para impresionar a tus amigos. + +- 📽️ **Grabación y reproducción de flujos complejos:** Keploy puede grabar y reproducir flujos de API complejos y distribuidos como mocks y stubs. Es como tener una máquina del tiempo para tus pruebas, ¡ahorrándote mucho tiempo! + +![Generar caso de prueba a partir de una llamada API](https://raw.githubusercontent.com/keploy/docs/main/static/gif/how-keploy-works.gif) + +> 🐰 **Dato curioso:** ¡Keploy se utiliza a sí mismo para realizar pruebas! Echa un vistazo a nuestra elegante insignia de cobertura: [![Estado de cobertura](https://coveralls.io/repos/github/keploy/keploy/badge.svg?branch=main&kill_cache=1)](https://coveralls.io/github/keploy/keploy?branch=main&kill_cache=1)   + +## 🌐 Soporte de idiomas +Desde el gopher de Go 🐹 hasta la serpiente de Python 🐍, ofrecemos soporte para: + +![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) +![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=for-the-badge&logo=java&logoColor=white) +![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) +![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) + +## 🎩 ¿Cómo funciona la magia? +Nuestro mágico 🧙‍♂️ proxy de Keploy captura y reproduce **TODAS** las interacciones de red de tu aplicación (operaciones CRUD, incluyendo APIs no idempotentes). + +Realiza un viaje a **[¿Cómo funciona Keploy?](https://docs.keploy.io/docs/keploy-explained/how-keploy-works)** para descubrir los trucos detrás del telón. + +![Generar caso de prueba a partir de una llamada API](https://raw.githubusercontent.com/keploy/docs/main/static/gif/record-replay.gif) + +## 📘 ¡Aprende más! +Conviértete en un profesional de Keploy con nuestra **[Documentación](https://docs.keploy.io/)**. + +# Instalación rápida + +Usando **Binario** ( Linux / WSL) +- + +Keploy se puede utilizar en Linux nativamente y a través de WSL en Windows. + +### Descarga el binario de Keploy. + +```zsh +curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz -C /tmp + +sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin && keploy + +
+ Arquitectura ARM +curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_arm64.tar.gz" | tar xz -C /tmp + +sudo mkdir-p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin && keploy +
+ +### Captura de casos de prueba +Para iniciar la grabación de llamadas a la API, ejecuta este comando en tu terminal donde normalmente ejecutas tu aplicación. Si necesitas configurar variables de entorno, hazlo de la manera habitual: + +```zsh +sudo -E env PATH=$PATH keploy record -c "CMD_PARA_EJECUTAR_LA_APP" +``` + +Por ejemplo, si estás utilizando un programa sencillo de Golang, el comando se vería así: + +```zsh +sudo -E env PATH=$PATH keploy record -c "CMD_PARA_EJECUTAR_LA_APP" +``` + +### Ejecución de casos de prueba + +Para ejecutar los casos de prueba y generar un informe de cobertura de pruebas, utiliza este comando en la terminal donde normalmente ejecutas tu aplicación. Si necesitas configurar variables de entorno, hazlo de la manera habitual: + +```zsh +sudo -E env PATH=$PATH keploy test -c "CMD_PARA_EJECUTAR_LA_APP" --delay 10 + ``` + + Por ejemplo, si estás utilizando un framework de Golang, el comando sería: + + ```zsh + sudo -E env PATH=$PATH keploy test -c "go run main.go" --delay 10 + ``` + + Instalación de Docker + +Keploy se puede utilizar en Linux y Windows a través de Docker. + +> **️ Nota:** MacOS necesitan instalar [Colima](https://github.com/abiosoft/colima#installation). Usuarios de Windows necesitan installar [WSL](https://learn.microsoft.com/en-us/windows/wsl/install#install-wsl-command). + +### Creación de alias + +Creemos un alias para Keploy: + +```shell +alias keploy='sudo docker run --pull always --name keploy-v2 -p 16789:16789 --privileged --pid=host -it -v $(pwd):$(pwd) -w $(pwd) -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/keploy/keploy' +``` + +### Grabación de Casos de Prueba y Datos Simulados + +Aquí tienes algunos puntos a considerar antes de la grabación: +- Si estás ejecutando mediante **docker-compose**, asegúrate de incluir el `` en el servicio de tu aplicación en el archivo docker-compose.yaml [como se muestra aquí](https://github.com/keploy/samples-python/blob/9d6cf40da2eb75f6e035bedfb30e54564785d5c9/flask-mongo/docker-compose.yml#L14). +- Debes ejecutar los contenedores en una red, si no es así, asegúrate de que todos tus contenedores estén en la misma red con la propiedad externa activada - [como se muestra aquí](https://github.com/keploy/samples-python/blob/9d6cf40da2eb75f6e035bedfb30e54564785d5c9/flask-mongo/docker-compose.yml#L24). Reemplaza el **nombre de la red** (bandera `--network`) por tu red personalizada si la cambiaste anteriormente, como la red en el ejemplo dado. +- `Docker_CMD_to_run_user_container` se refiere al **comando de Docker para iniciar** la aplicación. + +Utiliza el alias de keploy que creamos para capturar casos de prueba. **Ejecuta** el siguiente comando dentro del **directorio raíz** de tu aplicación. + +```shell +keploy record -c "Docker_CMD_to_run_user_container --network " --containerName "" +``` + +Realiza llamadas API utilizando herramientas como [Hoppscotch](https://hoppscotch.io/), [Postman](https://www.postman.com/) o comandos cURL. + +Keploy capturará las llamadas API que hayas realizado, generando suites de pruebas que comprenden **casos de prueba (KTests) y simulaciones de datos (KMocks)** en formato `YAML`. + +### Ejecución de Casos de Prueba + +Ahora, utiliza el alias keployV2 que creamos para ejecutar los casos de prueba. Sigue estos pasos en el **directorio raíz** de tu aplicación. + +Cuando utilices **docker-compose** para iniciar la aplicación, es importante asegurarse de que el parámetro `--containerName` coincida con el nombre del contenedor en tu archivo `docker-compose.yaml`. + +```shell +keploy test -c "Docker_CMD_to_run_user_container --network " --containerName "" --delay 20 +``` + +## 🤔 Preguntas? +¡Contáctanos! Estamos aquí para ayudarte. + +[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) +[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) +[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) +[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) + +## 💖 ¡Construyamos Juntos! +Ya seas un principiante o un mago 🧙‍♀️ en la programación, tu perspectiva es valiosa. Echa un vistazo a nuestras: + +📜 [Directrices de Contribución](https://github.com/keploy/keploy/blob/main/CONTRIBUTING.md) + +❤️ [Código de Conducta](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) + +## 🌟 Características + +### **🚀 Exporta, mantiene y muestra pruebas y simulaciones!** + +Genera Casos de Prueba desde Llamadas API + +### **🤝 Saluda a los populares frameworks de pruebas - Go-Test, JUnit, Py-Test, Jest y más!** + +Genera Casos de Prueba desde Llamadas API + +### **🕵️ Detecta ruido con precisión de cirujano!** +Filtra campos ruidosos en las respuestas de las API como (marcas de tiempo, valores aleatorios) para asegurar pruebas de alta calidad. + +### **📊 ¡Saluda a una mayor cobertura!** +Keploy se asegura de que no se generen casos de prueba redundantes. + +## 🐲 Los Desafíos que Enfrentamos! +- **Pruebas Unitarias:** Aunque Keploy está diseñado para funcionar junto con los marcos de pruebas unitarias (Go test, JUnit, etc.) y puede contribuir a la cobertura de código general, todavía genera pruebas de extremo a extremo (E2E). +- **Entornos de Producción:** Keploy actualmente se centra en generar pruebas para desarrolladores. Estas pruebas se pueden capturar desde cualquier entorno, pero no las hemos probado en entornos de producción de alto volumen. Esto requeriría una sólida deduplicación para evitar la captura de pruebas redundantes en exceso. Tenemos ideas para construir un sistema de deduplicación sólido [#27](https://github.com/keploy/keploy/issues/27) + +## ✨ Recursos! +🤔 [Preguntas Frecuentes](https://docs.keploy.io/docs/keploy-explained/faq) + +🕵️‍️ [¿Por Qué Keploy?](https://docs.keploy.io/docs/keploy-explained/why-keploy) + +⚙️ [Guía de Instalación](https://docs.keploy.io/docs/server/server-installation) + +📖 [Guía de Contribución](https://docs.keploy.io/docs/devtools/server-contrib-guide/) + +## 🌟 Salón de Contribuyentes +

+ contribuyentes +

+ +### Premios Disponibles + +| Nombre | Icono | Descripción | +| ---- | ---- | ----------- | +| Creador de Documentos | icono-de-docs | ¡Premiado por ayudar a mejorar la documentación de Keploy! | +| Cada Bit Cuenta | icono-de-commit | ¡Ningún commit es demasiado pequeño! | +| Héroe de Solicitudes de Extracción | icono-de-PR-hero | ¡Eres un héroe de solicitudes de extracción, sigue así! | +| Cercano| icono-de-closer | ¡Solo los cercanos consiguen café! | diff --git a/keploy/READMEja-JP.md b/keploy/READMEja-JP.md new file mode 100644 index 0000000..20a5067 --- /dev/null +++ b/keploy/READMEja-JP.md @@ -0,0 +1,160 @@ +

+ keploy logo +

+

+ +⚡️ ユーザートラフィックからのユニットテストよりも速いAPIテスト ⚡️ + +

+

+🌟 AI-Gen時代の開発者に必須のツール 🌟 +

+ +--- + +

+ + + Keploy X + + + + Help us reach 20k stars! + + + + Keploy CNCF Landscape + + +[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) +[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) +[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) +[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) + +

+ + +[Keploy](https://keploy.io) は、**開発者中心**のAPIテストツールで、**組み込みモック**を使用してユニットテストよりも速くテストを作成します。 + +KeployはAPI呼び出しだけでなく、データベース呼び出しも記録し、テスト中に再生するため、**使いやすく、強力で、拡張性があります**。 + +Convert API calls to test cases + +> 🐰 **面白い事実:** Keployは自分自身をテストに使用しています!私たちの素晴らしいカバレッジバッジをチェックしてください: [![Coverage Status](https://coveralls.io/repos/github/keploy/keploy/badge.svg?branch=main&kill_cache=1)](https://coveralls.io/github/keploy/keploy?branch=main&kill_cache=1)   + +## 🚨 [ユニットテストジェネレーター](README-UnitGen.md) (ut-gen) のためにここにいますか? +Keployは、[Meta LLM研究論文](https://arxiv.org/pdf/2402.09171)の世界初のユニットテストジェネレーター(ut-gen)実装を新たに発表しました。これはコードのセマンティクスを理解し、意味のあるユニットテストを生成します。目指すのは: + +- **ユニットテスト生成の自動化 (UTG)**: 包括的なユニットテストを迅速に生成し、冗長な手動作業を削減します。 + +- **エッジケースの改善**: 自動テストの範囲を拡張し、手動で見逃されがちな複雑なシナリオをカバーします。 + +- **テストカバレッジの向上**: コードベースが成長するにつれて、徹底的なカバレッジを確保することが可能になります。 + +### 📜 [ユニットテストジェネレーター README](README-UnitGen.md) をフォローしてください! ✅ + +## 📘 ドキュメント! +**[Keploy Documentation](https://keploy.io/docs/)** でKeployのプロフェッショナルになりましょう。 + +Record Replay Testing + +# 🚀 クイックインストール (APIテストジェネレーター) + +エージェントをローカルにインストールしてKeployを統合します。コード変更は不要です。 + +```shell +curl --silent -O -L https://keploy.io/install.sh && source install.sh +``` + +## 🎬 テストケースの記録 + +API呼び出しをテストとモック/スタブに変換するために、Keployを使用してアプリを開始します。 + +```zsh +keploy record -c "CMD_TO_RUN_APP" +``` +例えば、シンプルなPythonアプリを使用している場合、`CMD_TO_RUN_APP`は`python main.py`、Golangの場合は`go run main.go`、Javaの場合は`java -jar xyz.jar`、Nodeの場合は`npm start`のようになります。 + +```zsh +keploy record -c "python main.py" +``` + +## 🧪 テストの実行 +データベース、Redis、Kafka、またはアプリケーションが使用する他のサービスをシャットダウンします。Keployはテスト中にそれらを必要としません。 +```zsh +keploy test -c "CMD_TO_RUN_APP" --delay 10 +``` + +## ✅ テストカバレッジの統合 +ユニットテストライブラリと統合して、結合テストカバレッジを表示するには、この[テストカバレッジガイド](https://keploy.io/docs/server/sdk-installation/go/)に従ってください。 + +> #### **楽しんでいただけましたか:** このリポジトリに🌟スターを残してください!無料で笑顔をもたらします。😄 👏 + +## ワンクリックセットアップ 🚀 + +ローカルマシンのインストールなしでKeployを迅速にセットアップして実行します: + +[![GitHub Codescape](https://img.shields.io/badge/GH%20codespace-3670A0?style=for-the-badge&logo=github&logoColor=fff)]([https://github.dev/Sonichigo/mux-sql](https://github.dev/Sonichigo/mux-sql)) + +## 🤔 質問がありますか? +私たちに連絡してください。お手伝いします! + +[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) +[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) +[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) +[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) + + +## 🌐 言語サポート +Goのゴーファー 🐹 からPythonのスネーク 🐍 まで、以下の言語をサポートしています: + +![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) +![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=for-the-badge&logo=java&logoColor=white) +![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) +![Rust](https://img.shields.io/badge/Rust-darkred?style=for-the-badge&logo=rust&logoColor=white) +![C#](https://img.shields.io/badge/csharp-purple?style=for-the-badge&logo=csharp&logoColor=white) +![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) + +## 🫰 Keployの採用者 🧡 + +あなたとあなたの組織がKeployを使用しているのですか?それは素晴らしいことです。 [**このリスト**](https://github.com/orgs/keploy/discussions/1765) に追加してください。グッズをお送りします!💖 + +私たちは、あなたたち全員が私たちのコミュニティの一員であることを誇りに思います!💖 + +## 🎩 魔法はどのように起こるのか? +Keployプロキシは、アプリの**すべての**ネットワークインタラクション(CRUD操作、非冪等なAPIを含む)をキャプチャして再生します。 + +**[Keployの仕組み](https://keploy.io/docs/keploy-explained/how-keploy-works/)** の旅に出て、カーテンの裏にあるトリックを発見してください! + +ここにKeployの主な機能があります: 🛠 + +- ♻️ **結合テストカバレッジ:** Keployテストをお気に入りのテストライブラリ(JUnit、go-test、py-test、jest)と統合して、結合テストカバレッジを表示します。 + +- 🤖 **EBPFインストルメンテーション:** KeployはEBPFを使用して、コードレス、言語非依存、非常に軽量な統合を実現します。 + +- 🌐 **CI/CD統合:** テストをローカルCLI、CIパイプライン(Jenkins、Github Actions..)、またはKubernetesクラスター全体で実行します。 + +- 📽️ **複雑なフローの記録と再生:** Keployは、複雑で分散したAPIフローをモックとスタブとして記録して再生できます。これは、テストのためのタイムマシンを持っているようなもので、たくさんの時間を節約できます! + +- 🎭 **多目的モック:** Keployモックをサーバーテストとしても使用できます! + +## 👨🏻‍💻 一緒に構築しましょう! 👩🏻‍💻 +初心者のコーダーでもウィザードでも 🧙‍♀️、あなたの視点は貴重です。以下をチェックしてください: + +📜 [貢献ガイドライン](https://github.com/keploy/keploy/blob/main/CONTRIBUTING.md) + +❤️ [行動規範](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) + + +## 🐲 現在の制限事項! +- **ユニットテスト:** Keployはユニットテストフレームワーク(Go test、JUnit..)と一緒に実行するように設計されており、全体的なコードカバレッジに追加することができますが、それでも統合テストを生成します。 +- **プロダクション環境:** Keployは現在、開発者向けのテスト生成に焦点を当てています。これらのテストは任意の環境からキャプチャできますが、高ボリュームのプロダクション環境ではテストしていません。これは、過剰な冗長テストのキャプチャを避けるために堅牢な重複排除が必要です。堅牢な重複排除システムの構築についてのアイデアがあります [#27](https://github.com/keploy/keploy/issues/27) + +## ✨ リソース! +🤔 [FAQ](https://keploy.io/docs/keploy-explained/faq/) + +🕵️‍️ [なぜKeploy](https://keploy.io/docs/keploy-explained/why-keploy/) + +⚙️ [インストールガイド](https://keploy.io/docs/application-development/) + +📖 [貢献ガイド](https://keploy.io/docs/keploy-explained/contribution-guide/) diff --git a/keploy/SECURITY.md b/keploy/SECURITY.md new file mode 100755 index 0000000..3eafe68 --- /dev/null +++ b/keploy/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +## Reporting a Vulnerability + +We value security for the project very highly. We encourage all users to report any vulnerabilities they discover to us. +If you find a security vulnerability in the Keploy project, please report it responsibly by sending an email to hello@keploy.io + +At this juncture, we don't have a bug bounty program. We are a small team trying to solve a big problem. We urge you to report any vulnerabilities responsibly +so that we can continue building a secure application for the entire community. \ No newline at end of file diff --git a/keploy/cli/README.md b/keploy/cli/README.md new file mode 100755 index 0000000..71c21c5 --- /dev/null +++ b/keploy/cli/README.md @@ -0,0 +1,5 @@ +# CMD Package Documentation + +In this package, the `root` command and its `subcommands` are defined +for the CLI. This package, which is called from the main package, utilizes the +`pkg` services to execute commands. diff --git a/keploy/cli/cli.go b/keploy/cli/cli.go new file mode 100644 index 0000000..d7ae3aa --- /dev/null +++ b/keploy/cli/cli.go @@ -0,0 +1,22 @@ +// Package cli provides functionality for the command-line interface of the application. +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + "go.uber.org/zap" +) + +type HookFunc func(context.Context, *zap.Logger, *config.Config, ServiceFactory, CmdConfigurator) *cobra.Command + +// Registered holds the registered command hooks +var Registered map[string]HookFunc + +func Register(name string, f HookFunc) { + if Registered == nil { + Registered = make(map[string]HookFunc) + } + Registered[name] = f +} diff --git a/keploy/cli/config.go b/keploy/cli/config.go new file mode 100644 index 0000000..8a7b0f5 --- /dev/null +++ b/keploy/cli/config.go @@ -0,0 +1,75 @@ +package cli + +import ( + "context" + "errors" + "path/filepath" + + "go.keploy.io/server/v2/config" + + toolsSvc "go.keploy.io/server/v2/pkg/service/tools" + "go.keploy.io/server/v2/utils" + + "github.com/spf13/cobra" + "go.uber.org/zap" +) + +func init() { + Register("config", Config) +} + +func Config(ctx context.Context, logger *zap.Logger, cfg *config.Config, servicefactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "config", + Short: "manage keploy configuration file", + Example: "keploy config --generate --path /path/to/localdir", + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.ValidateFlags(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + isGenerate, err := cmd.Flags().GetBool("generate") + if err != nil { + utils.LogError(logger, err, "failed to get generate flag") + return err + } + + if isGenerate { + filePath := filepath.Join(cfg.Path, "keploy.yml") + if !cfg.InCi && utils.CheckFileExists(filePath) { + override, err := utils.AskForConfirmation(ctx, "Config file already exists. Do you want to override it?") + if err != nil { + utils.LogError(logger, err, "failed to ask for confirmation") + return err + } + if !override { + logger.Info("Skipping config file override") + return nil + } + } + svc, err := servicefactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return err + } + var tools toolsSvc.Service + var ok bool + if tools, ok = svc.(toolsSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy tools service interface") + return err + } + if err := tools.CreateConfig(ctx, filePath, ""); err != nil { + utils.LogError(logger, err, "failed to create config") + return err + } + logger.Info("Config file generated successfully") + return nil + } + return errors.New("only generate flag is supported in the config command") + }, + } + if err := cmdConfigurator.AddFlags(cmd); err != nil { + utils.LogError(logger, err, "failed to add flags") + return nil + } + return cmd +} diff --git a/keploy/cli/contract.go b/keploy/cli/contract.go new file mode 100644 index 0000000..e1e0eb7 --- /dev/null +++ b/keploy/cli/contract.go @@ -0,0 +1,134 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + contractSvc "go.keploy.io/server/v2/pkg/service/contract" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("contract", Contract) +} + +func Contract(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "contract", + Short: "Manage keploy contracts", + } + + cmd.AddCommand(Generate(ctx, logger, serviceFactory, cmdConfigurator)) + cmd.AddCommand(Download(ctx, logger, serviceFactory, cmdConfigurator)) + cmd.AddCommand(Validate(ctx, logger, serviceFactory, cmdConfigurator)) + for _, subCmd := range cmd.Commands() { + err := cmdConfigurator.AddFlags(subCmd) + if err != nil { + utils.LogError(logger, err, "failed to add flags to command", zap.String("command", subCmd.Name())) + } + } + return cmd +} + +func Generate(ctx context.Context, logger *zap.Logger, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "generate", + Short: "Generate contract for specified services", + Example: `keploy contract generate --service="email,notify"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, "contract") + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var contract contractSvc.Service + var ok bool + if contract, ok = svc.(contractSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy contract service interface") + return nil + } + // Extract services from the flag + + err = contract.Generate(ctx, true) + + if err != nil { + utils.LogError(logger, err, "failed to generate contract") + return nil + } + + return nil + }, + } + + return cmd + +} + +func Download(ctx context.Context, logger *zap.Logger, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "download", + Short: "Download contract for specified services", + Example: `keploy contract download --service="email,notify" --path /local/path`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, "contract") + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var contract contractSvc.Service + var ok bool + if contract, ok = svc.(contractSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy contract service interface") + return nil + } + err = contract.Download(ctx, true) + + if err != nil { + utils.LogError(logger, err, "failed to download contract") + } + return nil + }, + } + + return cmd +} + +func Validate(ctx context.Context, logger *zap.Logger, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "test", + Short: "Validate contract for specified services", + Example: `keploy contract test --service="email,notify" --path /local/path`, + + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, "contract") + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var contract contractSvc.Service + var ok bool + if contract, ok = svc.(contractSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy contract service interface") + return nil + } + err = contract.Validate(ctx) + if err != nil { + utils.LogError(logger, err, "failed to validate contract") + } + return nil + }, + } + + return cmd +} diff --git a/keploy/cli/examples.go b/keploy/cli/examples.go new file mode 100755 index 0000000..d8f3baf --- /dev/null +++ b/keploy/cli/examples.go @@ -0,0 +1,47 @@ +package cli + +import ( + "context" + "fmt" + "os" + + "go.keploy.io/server/v2/cli/provider" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + + "github.com/spf13/cobra" +) + +func init() { + Register("example", Example) +} + +func Example(_ context.Context, logger *zap.Logger, _ *config.Config, _ ServiceFactory, _ CmdConfigurator) *cobra.Command { + var customSetup bool + var cmd = &cobra.Command{ + Use: "example", + Short: "Example to record and test via keploy", + RunE: func(cmd *cobra.Command, _ []string) error { + disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) + provider.PrintLogo(os.Stdout, disableAnsi) + customSetup, err := cmd.Flags().GetBool("customSetup") + if err != nil { + utils.LogError(logger, nil, "failed to read the customSetup flag") + return err + } + if customSetup { + fmt.Println(provider.Examples) + return nil + } + fmt.Println(provider.ExampleOneClickInstall) + fmt.Println(provider.WithoutexampleOneClickInstall) + return nil + }, + } + cmd.SetHelpTemplate(provider.CustomHelpTemplate) + + cmd.Flags().Bool("customSetup", customSetup, "Check if the user is using one click install") + + return cmd +} diff --git a/keploy/cli/export.go b/keploy/cli/export.go new file mode 100644 index 0000000..854ab91 --- /dev/null +++ b/keploy/cli/export.go @@ -0,0 +1,63 @@ +package cli + +import ( + "context" + "os" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/cli/provider" + "go.keploy.io/server/v2/config" + toolsSvc "go.keploy.io/server/v2/pkg/service/tools" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("export", Export) +} + +func Export(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + + var exportCmd = &cobra.Command{ + Use: "export", + Short: "export Keploy tests as postman collection", + Example: "keploy export", + RunE: func(cmd *cobra.Command, _ []string) error { + disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) + provider.PrintLogo(os.Stdout, disableAnsi) + return cmd.Help() + }, + } + var postmanCmd = &cobra.Command{ + Use: "postman", + Short: "export Keploy tests as Postman collection", + Example: "keploy export postman", + RunE: func(cmd *cobra.Command, _ []string) error { + disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) + provider.PrintLogo(os.Stdout, disableAnsi) + svc, err := serviceFactory.GetService(ctx, "export") + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var tools toolsSvc.Service + var ok bool + if tools, ok = svc.(toolsSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy tools service interface") + return nil + } + err = tools.Export(ctx) // Assuming ExportPostmanCollection is a method in tools service + if err != nil { + utils.LogError(logger, err, "failed to export Postman collection") + } + return nil + }, + } + exportCmd.AddCommand(postmanCmd) + + if err := cmdConfigurator.AddFlags(exportCmd); err != nil { + utils.LogError(logger, err, "failed to add export cmd flags") + return nil + } + return exportCmd +} diff --git a/keploy/cli/import.go b/keploy/cli/import.go new file mode 100644 index 0000000..e589e29 --- /dev/null +++ b/keploy/cli/import.go @@ -0,0 +1,72 @@ +package cli + +import ( + "context" + "os" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/cli/provider" + "go.keploy.io/server/v2/config" + toolsSvc "go.keploy.io/server/v2/pkg/service/tools" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("import", Import) +} + +func Import(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + + var importCmd = &cobra.Command{ + Use: "import", + Short: "import postman collection to Keploy tests", + Example: "keploy import", + RunE: func(cmd *cobra.Command, _ []string) error { + disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) + provider.PrintLogo(os.Stdout, disableAnsi) + return cmd.Help() + }, + } + + var postmanCmd = &cobra.Command{ + Use: "postman", + Short: "import postman collection to Keploy tests", + Example: "keploy import postman", + RunE: func(cmd *cobra.Command, _ []string) error { + disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) + provider.PrintLogo(os.Stdout, disableAnsi) + path, _ := cmd.Flags().GetString("path") + if path == "" { + path = "output.json" + } + basePath, _ := cmd.Flags().GetString("base-path") + svc, err := serviceFactory.GetService(ctx, "import") + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var tools toolsSvc.Service + var ok bool + if tools, ok = svc.(toolsSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy tools service interface") + return nil + } + err = tools.Import(ctx, path, basePath) + if err != nil { + utils.LogError(logger, err, "failed to import Postman collection") + } + return nil + }, + } + importCmd.AddCommand(postmanCmd) + + for _, subCmd := range importCmd.Commands() { + err := cmdConfigurator.AddFlags(subCmd) + if err != nil { + utils.LogError(logger, err, "failed to add flags to command", zap.String("command", subCmd.Name())) + } + } + + return importCmd +} diff --git a/keploy/cli/load.go b/keploy/cli/load.go new file mode 100644 index 0000000..5787e34 --- /dev/null +++ b/keploy/cli/load.go @@ -0,0 +1,66 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + loadSvc "go.keploy.io/server/v2/pkg/service/load" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("load", Load) +} + +func Load(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "load", + Short: "load test a given testsuite.", + Example: `keploy load -f test_suite.yaml --out json > report.json`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service") + return nil + } + + // Get CLI parameters + vus, _ := cmd.Flags().GetInt("vus") + duration, _ := cmd.Flags().GetString("duration") + rps, _ := cmd.Flags().GetInt("rps") + + // values comming from CLI flags to override the spec.load options. + ctx := context.WithValue(ctx, "vus", vus) + ctx = context.WithValue(ctx, "duration", duration) + ctx = context.WithValue(ctx, "rps", rps) + + var ltSvc loadSvc.Service + var ok bool + if ltSvc, ok = svc.(loadSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy load service interface") + return nil + } + + err = ltSvc.Start(ctx) + if err != nil { + utils.LogError(logger, err, "failed to start the load tester") + return nil + } + + return nil + }, + } + + err := cmdConfigurator.AddFlags(cmd) + if err != nil { + utils.LogError(logger, err, "failed to add load flags") + return nil + } + + return cmd +} diff --git a/keploy/cli/login.go b/keploy/cli/login.go new file mode 100644 index 0000000..6d41466 --- /dev/null +++ b/keploy/cli/login.go @@ -0,0 +1,41 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + toolsSvc "go.keploy.io/server/v2/pkg/service/tools" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("login", Login) +} + +func Login(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, _ CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "login", + Short: "login to keploy via github", + Example: `keploy login`, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var tools toolsSvc.Service + var ok bool + if tools, ok = svc.(toolsSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy record service interface") + return nil + } + tools.Login(ctx) + return nil + }, + } + + return cmd +} diff --git a/keploy/cli/mock.go b/keploy/cli/mock.go new file mode 100644 index 0000000..65150ba --- /dev/null +++ b/keploy/cli/mock.go @@ -0,0 +1,94 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + replaySvc "go.keploy.io/server/v2/pkg/service/replay" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("mock", Mock) +} + +func Mock(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "mock", + Short: "Managing mocks", + } + + cmd.AddCommand(DownloadMocks(ctx, logger, serviceFactory, cmdConfigurator)) + cmd.AddCommand(UploadMocks(ctx, logger, serviceFactory, cmdConfigurator)) + for _, subCmd := range cmd.Commands() { + err := cmdConfigurator.AddFlags(subCmd) + if err != nil { + utils.LogError(logger, err, "failed to add flags to command", zap.String("command", subCmd.Name())) + } + } + return cmd +} + +func DownloadMocks(ctx context.Context, logger *zap.Logger, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "download", + Short: "Download mocks from the keploy registry", + Example: `keploy mock download`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Parent().Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Parent().Name())) + return nil + } + var replay replaySvc.Service + var ok bool + if replay, ok = svc.(replaySvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy replay service interface") + return nil + } + if err := replay.DownloadMocks(ctx); err != nil { + utils.LogError(logger, err, "failed to download mocks from keploy registry") + return nil + } + return nil + }, + } + + return cmd +} + +func UploadMocks(ctx context.Context, logger *zap.Logger, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "upload", + Short: "Upload mocks to the keploy registry", + Example: `keploy mock upload`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Parent().Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Parent().Name())) + return nil + } + var replay replaySvc.Service + var ok bool + if replay, ok = svc.(replaySvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy replay service interface") + return nil + } + if err := replay.UploadMocks(ctx); err != nil { + utils.LogError(logger, err, "failed to upload mocks to the keploy registry") + return nil + } + return nil + }, + } + + return cmd +} diff --git a/keploy/cli/normalise.go b/keploy/cli/normalise.go new file mode 100644 index 0000000..ff5a33b --- /dev/null +++ b/keploy/cli/normalise.go @@ -0,0 +1,50 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + replaySvc "go.keploy.io/server/v2/pkg/service/replay" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("normalize", Normalize) +} + +// Normalize retrieves the command to normalize Keploy +func Normalize(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var normalizeCmd = &cobra.Command{ + Use: "normalize", + Short: "Normalize Keploy", + Example: "keploy normalize --test-run testrun --tests test-set-1:test-case-1 test-case-2,test-set-2:test-case-1 test-case-2 ", + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.ValidateFlags(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var replay replaySvc.Service + var ok bool + if replay, ok = svc.(replaySvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy replay service interface") + return nil + } + if err := replay.Normalize(ctx); err != nil { + utils.LogError(logger, err, "failed to normalize test cases") + return nil + } + return nil + }, + } + if err := cmdConfigurator.AddFlags(normalizeCmd); err != nil { + utils.LogError(logger, err, "failed to add normalize cmd flags") + return nil + } + return normalizeCmd +} diff --git a/keploy/cli/provider/cmd.go b/keploy/cli/provider/cmd.go new file mode 100644 index 0000000..41622e8 --- /dev/null +++ b/keploy/cli/provider/cmd.go @@ -0,0 +1,1079 @@ +// Package provider provides functionality for the keploy provider. +package provider + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + + "strings" + "time" + + "gopkg.in/yaml.v3" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/service/tools" + "go.keploy.io/server/v2/utils" + "go.keploy.io/server/v2/utils/log" + "go.uber.org/zap" +) + +func LogExample(example string) string { + return fmt.Sprintf("Example usage: %s", example) +} + +var CustomHelpTemplate = ` +{{if .Example}}Examples: +{{.Example}} +{{end}} +{{if .HasAvailableSubCommands}}Guided Commands:{{range .Commands}}{{if .IsAvailableCommand}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}} +{{end}} +{{if .HasAvailableFlags}}Flags: +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}} +{{end}} +Use "{{.CommandPath}} [command] --help" for more information about a command. +` + +var WithoutexampleOneClickInstall = ` +Note: If installed keploy without One Click Install, use "keploy example --customSetup true" +` +var Examples = ` +Golang Application + Record: + sudo -E env PATH=$PATH keploy record -c "/path/to/user/app/binary" + + Test: + sudo -E env PATH=$PATH keploy test -c "/path/to/user/app/binary" --delay 10 + +Node Application + Record: + sudo -E env PATH=$PATH keploy record -c “npm start --prefix /path/to/node/app" + + Test: + sudo -E env PATH=$PATH keploy test -c “npm start --prefix /path/to/node/app" --delay 10 + +Java + Record: + sudo -E env PATH=$PATH keploy record -c "java -jar /path/to/java-project/target/jar" + + Test: + sudo -E env PATH=$PATH keploy test -c "java -jar /path/to/java-project/target/jar" --delay 10 + +Docker + Alias: + alias keploy='sudo docker run --name keploy-ebpf -p 16789:16789 --privileged --pid=host -it -v $(pwd):$(pwd) -w $(pwd) -v /sys/fs/cgroup:/sys/fs/cgroup + -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/keploy/keploy' + + Record: + keploy record -c "docker run -p 8080:8080 --name --network " --buildDelay 60 + + Test: + keploy test -c "docker run -p 8080:8080 --name --network " --delay 10 --buildDelay 60 + +` + +var ExampleOneClickInstall = ` +Golang Application + Record: + keploy record -c "/path/to/user/app/binary" + + Test: + keploy test -c "/path/to/user/app/binary" --delay 10 + +Node Application + Record: + keploy record -c “npm start --prefix /path/to/node/app" + + Test: + keploy test -c “npm start --prefix /path/to/node/app" --delay 10 + +Java + Record: + keploy record -c "java -jar /path/to/java-project/target/jar" + + Test: + keploy test -c "java -jar /path/to/java-project/target/jar" --delay 10 + +Docker + Record: + keploy record -c "docker run -p 8080:8080 --name --network " --buildDelay 60 + + Test: + keploy test -c "docker run -p 8080:8080 --name --network " --delay 1 --buildDelay 60 +` + +var RootCustomHelpTemplate = `{{.Short}} + +Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Available Commands:{{range .Commands}}{{if .IsAvailableCommand}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableFlags}} + +Flags: +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableLocalFlags}} + +Guided Commands:{{range .Commands}}{{if and (not .IsAvailableCommand) (not .Hidden)}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}} + +Examples: +{{.Example}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +` + +var RootExamples = ` + Record: + keploy record -c "docker run -p 8080:8080 --name --network keploy-network " --container-name "" --buildDelay 60 + + Test: + keploy test --c "docker run -p 8080:8080 --name --network keploy-network " --delay 10 --buildDelay 60 + + Config: + keploy config --generate -p "/path/to/localdir" +` + +var VersionTemplate = `{{with .Version}}{{printf "Keploy %s" .}}{{end}}{{"\n"}}` +var IsConfigFileFound = true + +type CmdConfigurator struct { + logger *zap.Logger + cfg *config.Config +} + +func NewCmdConfigurator(logger *zap.Logger, config *config.Config) *CmdConfigurator { + return &CmdConfigurator{ + logger: logger, + cfg: config, + } +} + +func (c *CmdConfigurator) AddFlags(cmd *cobra.Command) error { + //sets the displayment of flag-related errors + cmd.SilenceErrors = true + cmd.SetFlagErrorFunc(func(_ *cobra.Command, err error) error { + PrintLogo(os.Stdout, true) + color.Red(fmt.Sprintf("❌ error: %v", err)) + fmt.Println() + return err + }) + + //add flags + var err error + cmd.Flags().SetNormalizeFunc(aliasNormalizeFunc) + cmd.Flags().String("configPath", ".", "Path to the local directory where keploy configuration file is stored") + + // Flags for secure subcommands + if cmd.Parent() != nil && cmd.Parent().Name() == "secure" { + switch cmd.Name() { + case "add": + cmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") + case "remove": + cmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") + cmd.Flags().String("id", "", "ID of the custom check to remove") + case "update": + cmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") + cmd.Flags().String("id", "", "ID of the custom check to update") + case "list": + cmd.Flags().String("rule-set", "basic", "Specify which checks to list: 'basic' (built-in), 'custom'") + cmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") + } + return nil + } + + switch cmd.Name() { + case "secure": + cmd.Flags().String("base-url", "", "Base URL of the application to be tested.") + cmd.Flags().String("ts-path", "keploy/testsuite", "Directory path containing test suite YAML files.") + cmd.Flags().String("ts-file", "suite-0.yaml", "Name of the testsuite YAML file.") + cmd.Flags().String("rule-set", "basic", "Specify which checks to execute: 'basic' (built-in), 'custom'") + cmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") + case "load": + cmd.Flags().String("base-url", "", "Base URL of the application to be tested.") + cmd.Flags().String("ts-path", "keploy/testsuite", "Directory path containing test suite YAML files.") + cmd.Flags().String("ts-file", "suite-0.yaml", "Name of the testsuite YAML file.") + cmd.Flags().Int("vus", 1, "Number of virtual users to run.") + cmd.Flags().String("duration", "", "Time for the load test to keep running.") + cmd.Flags().Int("rps", 0, "Number of requests per second to run.") + case "testsuite": + cmd.Flags().String("base-url", "", "Base URL of the application to be tested.") + cmd.Flags().String("ts-path", "keploy/testsuite", "Directory path containing test suite YAML files.") + cmd.Flags().String("ts-file", "suite-0.yaml", "Name of the testsuite YAML file.") + + case "upload": //for uploading mocks + cmd.Flags().StringP("path", "p", ".", "Path to local keploy directory where generated mocks are stored") + cmd.Flags().StringSliceP("test-sets", "t", utils.Keys(c.cfg.Test.SelectedTests), "Testsets to consider e.g. -t \"test-set-1, test-set-2\"") + case "generate", "download": + + if cmd.Name() == "download" && cmd.Parent() != nil && cmd.Parent().Name() == "mock" { // for downloading mocks + cmd.Flags().StringP("path", "p", ".", "Path to local keploy directory where generated mocks are stored") + cmd.Flags().StringSliceP("test-sets", "t", utils.Keys(c.cfg.Test.SelectedTests), "Testsets to consider e.g. -t \"test-set-1, test-set-2\"") + return nil + } + + cmd.Flags().StringSliceP("services", "s", c.cfg.Contract.Services, "Specify the services for which to generate/download contracts") + cmd.Flags().StringSliceP("tests", "t", c.cfg.Contract.Tests, "Specify the tests for which to generate/download contracts") + cmd.Flags().StringP("path", "p", ".", "Specify the path to generate/download contracts") + if cmd.Name() == "download" { // for downloading contracts + cmd.Flags().String("driven", c.cfg.Contract.Driven, "Specify the path to download contracts") + } + + case "update", "export", "import": + return nil + case "postman": + cmd.Flags().StringP("path", "p", "", "Specify the path to the postman collection") + cmd.Flags().String("base-path", c.cfg.Test.BasePath, "basePath to hit the server while importing keploy tests from postman collection with no response in the collection") + case "normalize": + cmd.Flags().StringP("path", "p", ".", "Path to local directory where generated testcases/mocks/reports are stored") + cmd.Flags().String("test-run", "", "Test Run to be normalized") + cmd.Flags().String("tests", "", "Test Sets to be normalized") + case "config": + cmd.Flags().StringP("path", "p", ".", "Path to local directory where generated config is stored") + cmd.Flags().Bool("generate", false, "Generate a new keploy configuration file") + case "templatize": + cmd.Flags().StringP("path", "p", ".", "Path to local directory where generated testcases/mocks are stored") + cmd.Flags().StringSliceP("testsets", "t", c.cfg.Templatize.TestSets, "Testsets to run e.g. --testsets \"test-set-1, test-set-2\"") + case "gen": + cmd.Flags().String("source-file-path", "", "Path to the source file.") + cmd.Flags().String("test-file-path", "", "Path to the input test file.") + cmd.Flags().String("coverage-report-path", "coverage.xml", "Path to the code coverage report file.") + cmd.Flags().String("test-command", "", "The command to run tests and generate coverage report.") + cmd.Flags().String("coverage-format", "cobertura", "Type of coverage report.") + cmd.Flags().Int("expected-coverage", 80, "The desired coverage percentage.") + cmd.Flags().Int("max-iterations", 5, "The maximum number of iterations.") + cmd.Flags().String("test-dir", "", "Path to the test directory.") + cmd.Flags().String("llm-base-url", "", "Base URL for the AI model.") + cmd.Flags().String("model", "gpt-4o", "Model to use for the AI.") + cmd.Flags().String("llm-api-version", "", "API version of the llm") + cmd.Flags().String("additional-prompt", "", "Additional prompt to be used for the AI model.") + cmd.Flags().String("function-under-test", "", "The specific function for which tests will be generated.") + cmd.Flags().Bool("flakiness", false, "The flakiness check to run the passed tests for flakiness") + err := cmd.MarkFlagRequired("test-command") + if err != nil { + errMsg := "failed to mark testCommand as required flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + case "record", "test", "rerecord": + if cmd.Parent() != nil && cmd.Parent().Name() == "contract" { + cmd.Flags().StringSliceP("services", "s", c.cfg.Contract.Services, "Specify the services for which to generate contracts") + cmd.Flags().StringP("path", "p", ".", "Specify the path to generate contracts") + cmd.Flags().Bool("download", true, "Specify whether to download contracts or not") + cmd.Flags().Bool("generate", true, "Specify whether to generate schemas for the current service or not") + cmd.Flags().String("driven", c.cfg.Contract.Driven, "Specify the driven flag to validate contracts") + return nil + } + + cmd.Flags().StringP("path", "p", ".", "Path to local directory where generated testcases/mocks are stored") + cmd.Flags().Uint32("proxy-port", c.cfg.ProxyPort, "Port used by the Keploy proxy server to intercept the outgoing dependency calls") + cmd.Flags().Uint32("dns-port", c.cfg.DNSPort, "Port used by the Keploy DNS server to intercept the DNS queries") + cmd.Flags().StringP("command", "c", c.cfg.Command, "Command to start the user application") + cmd.Flags().String("cmd-type", c.cfg.CommandType, "Type of command to start the user application (native/docker/docker-compose)") + cmd.Flags().Uint64P("build-delay", "b", c.cfg.BuildDelay, "User provided time to wait docker container build") + cmd.Flags().String("container-name", c.cfg.ContainerName, "Name of the application's docker container") + cmd.Flags().StringP("network-name", "n", c.cfg.NetworkName, "Name of the application's docker network") + cmd.Flags().UintSlice("pass-through-ports", config.GetByPassPorts(c.cfg), "Ports to bypass the proxy server and ignore the traffic") + cmd.Flags().Uint64P("app-id", "a", c.cfg.AppID, "A unique name for the user's application") + cmd.Flags().String("app-name", c.cfg.AppName, "Name of the user's application") + cmd.Flags().Bool("generate-github-actions", c.cfg.GenerateGithubActions, "Generate Github Actions workflow file") + cmd.Flags().Bool("in-ci", c.cfg.InCi, "is CI Running or not") + //add rest of the uncommon flags for record, test, rerecord commands + c.AddUncommonFlags(cmd) + + case "report": + cmd.Flags().StringSliceP("test-sets", "t", utils.Keys(c.cfg.Test.SelectedTests), "Testsets to report e.g. --testsets \"test-set-1, test-set-2\"") + cmd.Flags().StringP("path", "p", ".", "Path to local directory where generated testcases/mocks are stored") + + case "keploy": + cmd.PersistentFlags().Bool("debug", c.cfg.Debug, "Run in debug mode") + cmd.PersistentFlags().Bool("disable-tele", c.cfg.DisableTele, "Run in telemetry mode") + cmd.PersistentFlags().Bool("disable-ansi", c.cfg.DisableANSI, "Disable ANSI color in logs") + err = cmd.PersistentFlags().MarkHidden("disable-tele") + if err != nil { + errMsg := "failed to mark telemetry as hidden flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + cmd.PersistentFlags().Bool("enable-testing", c.cfg.EnableTesting, "Enable testing keploy with keploy") + err = cmd.PersistentFlags().MarkHidden("enable-testing") + if err != nil { + errMsg := "failed to mark enableTesting as hidden flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + default: + return errors.New("unknown command name") + } + + return nil +} + +func (c *CmdConfigurator) AddUncommonFlags(cmd *cobra.Command) { + switch cmd.Name() { + case "record": + cmd.Flags().Duration("record-timer", 0, "User provided time to record its application (e.g., \"5s\" for 5 seconds, \"1m\" for 1 minute)") + cmd.Flags().String("base-path", c.cfg.Record.BasePath, "Base URL to hit the server while recording the testcases") + cmd.Flags().String("metadata", c.cfg.Record.Metadata, "Metadata to be stored in config.yaml as key-value pairs (e.g., \"key1=value1,key2=value2\")") + case "test", "rerecord": + cmd.Flags().StringSliceP("test-sets", "t", utils.Keys(c.cfg.Test.SelectedTests), "Testsets to run e.g. --testsets \"test-set-1, test-set-2\"") + cmd.Flags().String("host", c.cfg.Test.Host, "Custom host to replace the actual host in the testcases") + cmd.Flags().Uint32("port", c.cfg.Test.Port, "Custom port to replace the actual port in the testcases") + cmd.Flags().Uint64P("delay", "d", 5, "User provided time to run its application") + if cmd.Name() == "test" { + cmd.Flags().Uint64("api-timeout", c.cfg.Test.APITimeout, "User provided timeout for calling its application") + cmd.Flags().String("mongo-password", c.cfg.Test.MongoPassword, "Authentication password for mocking MongoDB conn") + cmd.Flags().String("coverage-report-path", c.cfg.Test.CoverageReportPath, "Write a go coverage profile to the file in the given directory.") + cmd.Flags().VarP(&c.cfg.Test.Language, "language", "l", "Application programming language") + cmd.Flags().Bool("ignore-ordering", c.cfg.Test.IgnoreOrdering, "Ignore ordering of array in response") + cmd.Flags().Bool("skip-coverage", c.cfg.Test.SkipCoverage, "skip code coverage computation while running the test cases") + cmd.Flags().Bool("remove-unused-mocks", c.cfg.Test.RemoveUnusedMocks, "Clear the unused mocks for the passed test-sets") + cmd.Flags().Bool("fallBack-on-miss", c.cfg.Test.FallBackOnMiss, "Enable connecting to actual service if mock not found during test mode") + cmd.Flags().String("jacoco-agent-path", c.cfg.Test.JacocoAgentPath, "Only applicable for test coverage for Java projects. You can override the jacoco agent jar by proving its path") + cmd.Flags().String("base-path", c.cfg.Test.BasePath, "Custom api basePath/origin to replace the actual basePath/origin in the testcases; App flag is ignored and app will not be started & instrumented when this is set since the application running on a different machine") + cmd.Flags().Bool("update-template", c.cfg.Test.UpdateTemplate, "Update the template with the result of the testcases.") + cmd.Flags().Bool("mocking", true, "enable/disable mocking for the testcases") + cmd.Flags().Bool("disableMockUpload", c.cfg.Test.DisableMockUpload, "Store/Fetch mocks locally") + cmd.Flags().Bool("useLocalMock", false, "Use local mocks instead of fetching from the cloud") + cmd.Flags().Bool("disable-line-coverage", c.cfg.Test.DisableLineCoverage, "Disable line coverage generation.") + cmd.Flags().Bool("must-pass", c.cfg.Test.MustPass, "enforces that the tests must pass, if it doesn't, remove failing testcases") + cmd.Flags().Uint32Var(&c.cfg.Test.MaxFailAttempts, "max-fail-attempts", 5, "maximum number of testset failure that can be allowed during must-pass mode") + cmd.Flags().Uint32Var(&c.cfg.Test.MaxFlakyChecks, "flaky-check-retry", 1, "maximum number of retries to check for flakiness") + } + } +} + +func aliasNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName { + var flagNameMapping = map[string]string{ + "testsets": "test-sets", + "delay": "delay", + "apiTimeout": "api-timeout", + "mongoPassword": "mongo-password", + "coverageReportPath": "coverage-report-path", + "language": "language", + "ignoreOrdering": "ignore-ordering", + "coverage": "coverage", + "removeUnusedMocks": "remove-unused-mocks", + "goCoverage": "go-coverage", + "fallBackOnMiss": "fallBack-on-miss", + "basePath": "base-path", + "updateTemplate": "update-template", + "mocking": "mocking", + "sourceFilePath": "source-file-path", + "testFilePath": "test-file-path", + "testCommand": "test-command", + "coverageFormat": "coverage-format", + "expectedCoverage": "expected-coverage", + "maxIterations": "max-iterations", + "testDir": "test-dir", + "llmBaseUrl": "llm-base-url", + "model": "model", + "llmApiVersion": "llm-api-version", + "configPath": "config-path", + "path": "path", + "port": "port", + "proxyPort": "proxy-port", + "dnsPort": "dns-port", + "command": "command", + "cmdType": "cmd-type", + "buildDelay": "build-delay", + "containerName": "container-name", + "networkName": "network-name", + "passThroughPorts": "pass-through-ports", + "appId": "app-id", + "appName": "app-name", + "generateGithubActions": "generate-github-actions", + "disableTele": "disable-tele", + "disableANSI": "disable-ansi", + "selectedTests": "selected-tests", + "testReport": "test-report", + "enableTesting": "enable-testing", + "inDocker": "in-docker", + "keployContainer": "keploy-container", + "keployNetwork": "keploy-network", + "recordTimer": "record-timer", + "urlMethods": "url-methods", + "inCi": "in-ci", + } + + if newName, ok := flagNameMapping[name]; ok { + name = newName + } + return pflag.NormalizedName(name) +} + +func (c *CmdConfigurator) Validate(ctx context.Context, cmd *cobra.Command) error { + err := isCompatible(c.logger) + if err != nil { + return err + } + defaultCfg := *c.cfg + err = c.PreProcessFlags(cmd) + if err != nil { + c.logger.Error("failed to preprocess flags", zap.Error(err)) + return err + } + err = c.ValidateFlags(ctx, cmd) + if err != nil { + if err == c.noCommandError() { + utils.LogError(c.logger, nil, "missing required -c flag or appCmd in config file") + if c.cfg.InDocker { + c.logger.Info(`Example usage: keploy test -c "docker run -p 8080:8080 --network myNetworkName myApplicationImageName" --delay 6`) + } else { + c.logger.Info(LogExample(RootExamples)) + } + } + c.logger.Error("failed to validate flags", zap.Error(err)) + return err + } + + appName, err := utils.GetLastDirectory() + if err != nil { + return fmt.Errorf("failed to get the last directory for appName: %v", err) + } + + if c.cfg.AppName == "" { + c.logger.Info("Using the last directory name as appName : " + appName) + c.cfg.AppName = appName + } else if c.cfg.AppName != appName { + c.logger.Warn("AppName in config (" + c.cfg.AppName + ") does not match current directory name (" + appName + "). using current directory name as appName") + c.cfg.AppName = appName + } + + if !IsConfigFileFound { + err := c.CreateConfigFile(ctx, defaultCfg) + if err != nil { + c.logger.Error("failed to create config file", zap.Error(err)) + return err + } + } + return nil +} + +func (c *CmdConfigurator) PreProcessFlags(cmd *cobra.Command) error { + // used to bind common flags for commands like record, test. For eg: PATH, PORT, COMMAND etc. + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + errMsg := "failed to bind flags to config" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + // used to bind flags with environment variables + viper.AutomaticEnv() + viper.SetEnvPrefix("KEPLOY") + + //used to bind flags specific to the command for eg: testsets, delay, recordTimer etc. (nested flags) + err = utils.BindFlagsToViper(c.logger, cmd, "") + if err != nil { + errMsg := "failed to bind cmd specific flags to viper" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + configPath, err := cmd.Flags().GetString("configPath") + if err != nil { + utils.LogError(c.logger, nil, "failed to read the config path") + return err + } + viper.SetConfigName("keploy") + viper.SetConfigType("yml") + viper.AddConfigPath(configPath) + if err := viper.ReadInConfig(); err != nil { + var configFileNotFoundError viper.ConfigFileNotFoundError + if !errors.As(err, &configFileNotFoundError) { + errMsg := "failed to read config file" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + IsConfigFileFound = false + c.logger.Info("config file not found; proceeding with flags only") + } + + if err := viper.Unmarshal(c.cfg); err != nil { + errMsg := "failed to unmarshal the config" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + c.cfg.ConfigPath = configPath + return nil +} +func (c *CmdConfigurator) ValidateFlags(ctx context.Context, cmd *cobra.Command) error { + disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) + PrintLogo(os.Stdout, disableAnsi) + if c.cfg.Debug { + logger, err := log.ChangeLogLevel(zap.DebugLevel) + *c.logger = *logger + if err != nil { + errMsg := "failed to change log level" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + } + + if c.cfg.Record.BasePath != "" { + port, err := pkg.ExtractPort(c.cfg.Record.BasePath) + if err != nil { + errMsg := "failed to extract port from base URL" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.Port = port + c.cfg.E2E = true + } + + if c.cfg.EnableTesting { + // Add mode to logger to debug the keploy during testing + logger, err := log.AddMode(cmd.Name()) + *c.logger = *logger + if err != nil { + errMsg := "failed to add mode to logger" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.DisableTele = true + } + + if c.cfg.DisableANSI { + logger, err := log.ChangeColorEncoding() + models.IsAnsiDisabled = true + *c.logger = *logger + if err != nil { + errMsg := "failed to change color encoding" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.logger.Info("Color encoding is disabled") + } + + c.logger.Debug("config has been initialised", zap.Any("for cmd", cmd.Name()), zap.Any("config", c.cfg)) + + switch cmd.Name() { + + case "secure": + baseURL, err := cmd.Flags().GetString("base-url") + if err != nil { + errMsg := "failed to get base-url flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.TestSuite.BaseURL = baseURL + + tsPath, err := cmd.Flags().GetString("ts-path") + if err != nil { + errMsg := "failed to get ts-path flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.TestSuite.TSPath = tsPath + + file, err := cmd.Flags().GetString("ts-file") + if err != nil { + errMsg := "failed to get ts-file flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.TestSuite.TSFile = file + + case "load": + baseURL, err := cmd.Flags().GetString("base-url") + if err != nil { + errMsg := "failed to get base-url flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.TestSuite.BaseURL = baseURL + + tsPath, err := cmd.Flags().GetString("ts-path") + if err != nil { + errMsg := "failed to get ts-path flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.TestSuite.TSPath = tsPath + + file, err := cmd.Flags().GetString("ts-file") + if err != nil { + errMsg := "failed to get ts-file flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.TestSuite.TSFile = file + + _, err = cmd.Flags().GetInt("vus") + if err != nil { + errMsg := "failed to get vus flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + _, err = cmd.Flags().GetString("duration") + if err != nil { + errMsg := "failed to get duration flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + _, err = cmd.Flags().GetInt("rps") + if err != nil { + errMsg := "failed to get rps flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + case "testsuite": + baseURL, err := cmd.Flags().GetString("base-url") + if err != nil { + errMsg := "failed to get base-url flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.TestSuite.BaseURL = baseURL + + tsPath, err := cmd.Flags().GetString("ts-path") + if err != nil { + errMsg := "failed to get ts-path flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.TestSuite.TSPath = tsPath + + tsFile, err := cmd.Flags().GetString("ts-file") + if err != nil { + errMsg := "failed to get ts-file flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.TestSuite.TSFile = tsFile + case "upload": //for uploading mocks + path, err := cmd.Flags().GetString("path") + if err != nil { + errMsg := "failed to get the path" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.Path = utils.ToAbsPath(c.logger, path) + + testSets, err := cmd.Flags().GetStringSlice("test-sets") + if err != nil { + errMsg := "failed to get the test-sets" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + config.SetSelectedTests(c.cfg, testSets) + + case "report": + path, err := cmd.Flags().GetString("path") + if err != nil { + errMsg := "failed to get the path" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.Path = utils.ToAbsPath(c.logger, path) + + testSets, err := cmd.Flags().GetStringSlice("test-sets") + if err != nil { + errMsg := "failed to get the test-sets" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + config.SetSelectedTestSets(c.cfg, testSets) + + case "generate", "download": + + if cmd.Name() == "download" && cmd.Parent() != nil && cmd.Parent().Name() == "mock" { + path, err := cmd.Flags().GetString("path") + if err != nil { + errMsg := "failed to get the path" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.Path = utils.ToAbsPath(c.logger, path) + + testSets, err := cmd.Flags().GetStringSlice("testsets") + if err != nil { + errMsg := "failed to get the testsets" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + config.SetSelectedTests(c.cfg, testSets) + return nil + } + + path, err := cmd.Flags().GetString("path") + if err != nil { + errMsg := "failed to get the path" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + c.cfg.Contract.Path = utils.ToAbsPath(c.logger, path) + + services, err := cmd.Flags().GetStringSlice("services") + if err != nil { + errMsg := "failed to get the services" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + config.SetSelectedServices(c.cfg, services) + + selectedTests, err := cmd.Flags().GetStringSlice("tests") + if err != nil { + errMsg := "failed to get the tests" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + config.SetSelectedContractTests(c.cfg, selectedTests) + + if cmd.Name() == "download" { + c.cfg.Contract.Driven, err = cmd.Flags().GetString("driven") + if err != nil { + errMsg := "failed to get the driven flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + } + + c.cfg.Path = utils.ToAbsPath(c.logger, path) + + case "config": + path, err := cmd.Flags().GetString("path") + if err != nil { + errMsg := "failed to get the path" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.Path, err = utils.GetAbsPath(path) + if err != nil { + errMsg := "failed to get the absolute path" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + case "record", "test", "rerecord": + + if cmd.Parent() != nil && cmd.Parent().Name() == "contract" { + path, err := cmd.Flags().GetString("path") + if err != nil { + errMsg := "failed to get the path" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + c.cfg.Contract.Path = utils.ToAbsPath(c.logger, path) + + services, err := cmd.Flags().GetStringSlice("services") + if err != nil { + errMsg := "failed to get the services" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + config.SetSelectedServices(c.cfg, services) + + c.cfg.Contract.Download, err = cmd.Flags().GetBool("download") + if err != nil { + errMsg := "failed to get the download flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.Contract.Generate, err = cmd.Flags().GetBool("generate") + if err != nil { + errMsg := "failed to get the generate flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.Contract.Driven, err = cmd.Flags().GetString("driven") + if err != nil { + errMsg := "failed to get the driven flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + c.cfg.Path = utils.ToAbsPath(c.logger, path) + return nil + } + + // set the command type + c.cfg.CommandType = string(utils.FindDockerCmd(c.cfg.Command)) + + // empty the command if base path is provided, because no need of command even if provided + if c.cfg.Test.BasePath != "" { + c.cfg.CommandType = string(utils.Empty) + c.cfg.Command = "" + } + + if c.cfg.GenerateGithubActions && utils.CmdType(c.cfg.CommandType) != utils.Empty { + defer utils.GenerateGithubActions(c.logger, c.cfg.Command) + } + if c.cfg.InDocker { + c.logger.Info("detected that Keploy is running in a docker container") + if len(c.cfg.Path) > 0 { + curDir, err := os.Getwd() + if err != nil { + errMsg := "failed to get current working directory" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + if strings.Contains(c.cfg.Path, "..") { + + c.cfg.Path, err = utils.GetAbsPath(filepath.Clean(c.cfg.Path)) + if err != nil { + return fmt.Errorf("failed to get the absolute path from relative path: %w", err) + } + + relativePath, err := filepath.Rel(curDir, c.cfg.Path) + if err != nil { + errMsg := "failed to get the relative path from absolute path" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + if relativePath == ".." || strings.HasPrefix(relativePath, "../") { + errMsg := "path provided is not a subdirectory of current directory. Keploy only supports recording testcases in the current directory or its subdirectories" + utils.LogError(c.logger, err, errMsg, zap.String("path:", c.cfg.Path)) + return errors.New(errMsg) + } + } + } + // check if the buildDelay is less than 30 seconds + if time.Duration(c.cfg.BuildDelay)*time.Second <= 30*time.Second { + c.logger.Warn(fmt.Sprintf("buildDelay is set to %v, incase your docker container takes more time to build use --buildDelay to set custom delay", c.cfg.BuildDelay)) + c.logger.Info(`Example usage: keploy record -c "docker-compose up --build" --buildDelay 35`) + } + if utils.CmdType(c.cfg.Command) == utils.DockerCompose { + if c.cfg.ContainerName == "" { + utils.LogError(c.logger, nil, "Couldn't find containerName") + c.logger.Info(`Example usage: keploy record -c "docker run -p 8080:8080 --network myNetworkName myApplicationImageName" --delay 6`) + return errors.New("missing required --container-name flag or containerName in config file") + } + } + } + err := StartInDocker(ctx, c.logger, c.cfg) + if err != nil { + return err + } + + absPath, err := utils.GetAbsPath(c.cfg.Path) + if err != nil { + utils.LogError(c.logger, err, "error while getting absolute path") + return errors.New("failed to get the absolute path") + } + c.cfg.Path = absPath + "/keploy" + + // handle the app command + if c.cfg.Command == "" { + if !alreadyRunning(cmd.Name(), c.cfg.Test.BasePath) { + return c.noCommandError() + } + } + + bypassPorts, err := cmd.Flags().GetUintSlice("passThroughPorts") + if err != nil { + errMsg := "failed to read the ports of outgoing calls to be ignored" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + config.SetByPassPorts(c.cfg, bypassPorts) + + if cmd.Name() == "record" { + metadata, err := cmd.Flags().GetString("metadata") + if err != nil { + errMsg := "failed to get the metadata flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.Record.Metadata = metadata + } + + if cmd.Name() == "test" || cmd.Name() == "rerecord" { + //check if the keploy folder exists + if _, err := os.Stat(c.cfg.Path); os.IsNotExist(err) { + recordCmd := models.HighlightGrayString("keploy record") + errMsg := fmt.Sprintf("No test-sets found. Please record testcases using %s command", recordCmd) + utils.LogError(c.logger, nil, errMsg) + return errors.New(errMsg) + } + + testSets, err := cmd.Flags().GetStringSlice("testsets") + if err != nil { + errMsg := "failed to get the testsets" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + config.SetSelectedTests(c.cfg, testSets) + if cmd.Name() == "rerecord" { + c.cfg.Test.SkipCoverage = true + host, err := cmd.Flags().GetString("host") + if err != nil { + errMsg := "failed to get the provided host" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.ReRecord.Host = host + port, err := cmd.Flags().GetUint32("port") + if err != nil { + errMsg := "failed to get the provided port" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + c.cfg.ReRecord.Port = port + c.cfg.Test.Delay, err = cmd.Flags().GetUint64("delay") + if err != nil { + errMsg := "failed to get the provided delay" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + return nil + } + + // enforce that the test-sets are provided when --must-pass is set to true + // to prevent accidental deletion of failed testcases in testsets which was due to application changes + // and not due to flakiness or our internal issue. + mustPass, err := cmd.Flags().GetBool("must-pass") + if err != nil { + errMsg := "failed to get the must-pass flag" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + if mustPass { + c.cfg.Test.SkipCoverage = true + c.cfg.Test.DisableMockUpload = true + } + + // in mustpass mode, set the maxFlakyChecks count to 3 explicitly, + // if it is not set through cmd flag. + if mustPass && !cmd.Flags().Changed("flaky-check-retry") { + c.cfg.Test.MaxFlakyChecks = 3 + } + + // if the user passes a value for this field, store it + if cmd.Flags().Changed("flaky-check-retry") { + c.cfg.Test.MaxFlakyChecks, err = cmd.Flags().GetUint32("flaky-check-retry") + if err != nil { + errMsg := "failed to get the provided flaky-check-retry count" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + } + + // if the user passes a value for this field, store it + if cmd.Flags().Changed("max-fail-attempts") { + c.cfg.Test.MaxFailAttempts, err = cmd.Flags().GetUint32("max-fail-attempts") + if err != nil { + errMsg := "failed to get the provided max-fail-attempts count" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + } + + // don't allow zero maxFlakyChecks and if must pass mode is enabled, then maxFailAttempts can't be zero. + if c.cfg.Test.MaxFlakyChecks == 0 { + return fmt.Errorf("value for maxFlakyChecks cannot be zero") + } + if mustPass && c.cfg.Test.MaxFailAttempts == 0 { + return fmt.Errorf("in must pass mode, value for maxFailAttempts cannot be zero") + } + + if mustPass && !cmd.Flags().Changed("test-sets") { + return fmt.Errorf("--test-sets flag must be set to use --must-pass=true") + } + + // skip coverage by default if command is of type docker + if utils.CmdType(c.cfg.CommandType) != "native" && !cmd.Flags().Changed("skip-coverage") { + c.cfg.Test.SkipCoverage = true + } + + if c.cfg.Test.Delay <= 5 { + c.logger.Warn(fmt.Sprintf("Delay is set to %d seconds, incase your app takes more time to start use --delay to set custom delay", c.cfg.Test.Delay)) + if c.cfg.InDocker { + c.logger.Info(`Example usage: keploy test -c "docker run -p 8080:8080 --network myNetworkName myApplicationImageName" --delay 6`) + } else { + c.logger.Info("Example usage: " + cmd.Example) + } + } + } + + case "normalize": + c.cfg.Path = utils.ToAbsPath(c.logger, c.cfg.Path) + tests, err := cmd.Flags().GetString("tests") + if err != nil { + errMsg := "failed to read tests to be normalized" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + err = config.SetSelectedTestsNormalize(c.cfg, tests) + if err != nil { + errMsg := "failed to normalize the selected tests" + utils.LogError(c.logger, err, errMsg) + return errors.New(errMsg) + } + + case "templatize": + c.cfg.Path = utils.ToAbsPath(c.logger, c.cfg.Path) + case "gen": + if os.Getenv("API_KEY") == "" { + utils.LogError(c.logger, nil, "API_KEY is not set") + return errors.New("API_KEY is not set") + } + if (c.cfg.Gen.SourceFilePath == "" && c.cfg.Gen.TestFilePath != "") || c.cfg.Gen.SourceFilePath != "" && c.cfg.Gen.TestFilePath == "" { + utils.LogError(c.logger, nil, "One of the SourceFilePath and TestFilePath is mentioned. Either provide both or neither") + return errors.New("sourceFilePath and testFilePath misconfigured") + } else if c.cfg.Gen.SourceFilePath == "" && c.cfg.Gen.TestFilePath == "" { + if c.cfg.Gen.TestDir == "" { + utils.LogError(c.logger, nil, "TestDir is not set, Please specify the test directory") + return errors.New("TestDir is not set") + } + } + } + + return nil +} + +func (c *CmdConfigurator) CreateConfigFile(ctx context.Context, defaultCfg config.Config) error { + defaultCfg = c.UpdateConfigData(defaultCfg) + toolSvc := tools.NewTools(c.logger, nil, nil, nil, nil, nil) + configData := defaultCfg + configDataBytes, err := yaml.Marshal(configData) + if err != nil { + utils.LogError(c.logger, err, "failed to marshal config data") + return errors.New("failed to marshal config data") + } + err = toolSvc.CreateConfig(ctx, c.cfg.ConfigPath+"/keploy.yml", string(configDataBytes)) + if err != nil { + utils.LogError(c.logger, err, "failed to create config file") + return errors.New("failed to create config file") + } + c.logger.Info("Generated config file based on the flags that are used") + return nil +} + +func (c *CmdConfigurator) UpdateConfigData(defaultCfg config.Config) config.Config { + defaultCfg.Command = c.cfg.Command + defaultCfg.Test.Delay = c.cfg.Test.Delay + defaultCfg.AppName = c.cfg.AppName + defaultCfg.Test.APITimeout = c.cfg.Test.APITimeout + defaultCfg.ContainerName = c.cfg.ContainerName + defaultCfg.Test.IgnoreOrdering = c.cfg.Test.IgnoreOrdering + defaultCfg.Test.Language = c.cfg.Test.Language + defaultCfg.DisableANSI = c.cfg.DisableANSI + defaultCfg.Test.SkipCoverage = c.cfg.Test.SkipCoverage + defaultCfg.Test.Mocking = c.cfg.Test.Mocking + defaultCfg.Test.DisableLineCoverage = c.cfg.Test.DisableLineCoverage + + defaultCfg.TestSuite.TSPath = c.cfg.TestSuite.TSPath + defaultCfg.TestSuite.TSFile = c.cfg.TestSuite.TSFile + return defaultCfg +} diff --git a/keploy/cli/provider/common.go b/keploy/cli/provider/common.go new file mode 100644 index 0000000..807d8da --- /dev/null +++ b/keploy/cli/provider/common.go @@ -0,0 +1,20 @@ +package provider + +import ( + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/storage" + "go.keploy.io/server/v2/pkg/platform/yaml/configdb/testset" + mockdb "go.keploy.io/server/v2/pkg/platform/yaml/mockdb" + openapidb "go.keploy.io/server/v2/pkg/platform/yaml/openapidb" + reportdb "go.keploy.io/server/v2/pkg/platform/yaml/reportdb" + testdb "go.keploy.io/server/v2/pkg/platform/yaml/testdb" +) + +type commonPlatformServices struct { + YamlTestDB *testdb.TestYaml + YamlMockDb *mockdb.MockYaml + YamlOpenAPIDb *openapidb.OpenAPIYaml + YamlReportDb *reportdb.TestReport + YamlTestSetDB *testset.Db[*models.TestSet] + Storage *storage.Storage +} diff --git a/keploy/cli/provider/compat_linux.go b/keploy/cli/provider/compat_linux.go new file mode 100644 index 0000000..1951cd5 --- /dev/null +++ b/keploy/cli/provider/compat_linux.go @@ -0,0 +1,27 @@ +//go:build linux + +package provider + +import ( + "errors" + + "github.com/moby/moby/pkg/parsers/kernel" + "go.uber.org/zap" +) + +func isCompatible(logger *zap.Logger) error { + //check if the version of the kernel is above 5.10 for eBPF support + isValid := kernel.CheckKernelVersion(5, 10, 0) + if !isValid { + c, err := kernel.GetKernelVersion() + if err != nil { + logger.Error("Error getting kernel version", zap.Error(err)) + return err + } + errMsg := "detected linux kernel version" + c.String() + ". Keploy requires linux kernel version 5.10 or above. Please upgrade your kernel or docker version.\n" + logger.Error(errMsg) + return errors.New(errMsg) + } + // TODO check for cgroup v2 support + return nil +} diff --git a/keploy/cli/provider/compat_others.go b/keploy/cli/provider/compat_others.go new file mode 100644 index 0000000..5d47879 --- /dev/null +++ b/keploy/cli/provider/compat_others.go @@ -0,0 +1,11 @@ +//go:build !linux + +// This is a placeholder file for other OSes. + +package provider + +import "go.uber.org/zap" + +func isCompatible(logger *zap.Logger) error { + return nil +} diff --git a/keploy/cli/provider/core_service_linux.go b/keploy/cli/provider/core_service_linux.go new file mode 100644 index 0000000..3f1773c --- /dev/null +++ b/keploy/cli/provider/core_service_linux.go @@ -0,0 +1,125 @@ +//go:build linux + +package provider + +import ( + "context" + "errors" + "fmt" + "path/filepath" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/core" + "go.keploy.io/server/v2/pkg/core/hooks" + "go.keploy.io/server/v2/pkg/core/proxy" + "go.keploy.io/server/v2/pkg/core/tester" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/docker" + "go.keploy.io/server/v2/pkg/platform/storage" + "go.keploy.io/server/v2/pkg/platform/telemetry" + "go.keploy.io/server/v2/pkg/platform/yaml/configdb/testset" + mockdb "go.keploy.io/server/v2/pkg/platform/yaml/mockdb" + openapidb "go.keploy.io/server/v2/pkg/platform/yaml/openapidb" + reportdb "go.keploy.io/server/v2/pkg/platform/yaml/reportdb" + testdb "go.keploy.io/server/v2/pkg/platform/yaml/testdb" + "go.keploy.io/server/v2/pkg/service" + "go.keploy.io/server/v2/pkg/service/contract" + "go.keploy.io/server/v2/pkg/service/orchestrator" + "go.keploy.io/server/v2/pkg/service/record" + "go.keploy.io/server/v2/pkg/service/replay" + "go.keploy.io/server/v2/pkg/service/report" + "go.keploy.io/server/v2/pkg/service/tools" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type CommonInternalService struct { + commonPlatformServices + Instrumentation *core.Core +} + +func Get(ctx context.Context, cmd string, cfg *config.Config, logger *zap.Logger, tel *telemetry.Telemetry, auth service.Auth) (interface{}, error) { + commonServices, err := GetCommonServices(ctx, cfg, logger) + if err != nil { + return nil, err + } + contractSvc := contract.New(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlOpenAPIDb, cfg) + recordSvc := record.New(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, tel, commonServices.Instrumentation, commonServices.YamlTestSetDB, cfg) + replaySvc := replay.NewReplayer(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlReportDb, commonServices.YamlTestSetDB, tel, commonServices.Instrumentation, auth, commonServices.Storage, cfg) + toolsSvc := tools.NewTools(logger, commonServices.YamlTestSetDB, commonServices.YamlTestDB, tel, auth, cfg) + reportSvc := report.New(logger, cfg, commonServices.YamlReportDb, commonServices.YamlTestDB) + switch cmd { + case "rerecord": + return orchestrator.New(logger, recordSvc, toolsSvc, replaySvc, cfg), nil + case "record": + return recordSvc, nil + case "test", "normalize", "mock": + return replaySvc, nil + case "templatize", "config", "update", "login", "export", "import": + return toolsSvc, nil + case "contract": + return contractSvc, nil + case "report": + return reportSvc, nil + default: + return nil, errors.New("invalid command") + } + +} + +func GetCommonServices(_ context.Context, c *config.Config, logger *zap.Logger) (*CommonInternalService, error) { + + h := hooks.NewHooks(logger, c) + p := proxy.New(logger, h, c) + //for keploy test bench + t := tester.New(logger, h) + + var client docker.Client + var err error + if utils.IsDockerCmd(utils.CmdType(c.CommandType)) { + client, err = docker.New(logger) + if err != nil { + utils.LogError(logger, err, "failed to create docker client") + } + + //parse docker command only in case of docker start or docker run commands + if utils.CmdType(c.CommandType) != utils.DockerCompose { + cont, net, err := docker.ParseDockerCmd(c.Command, utils.CmdType(c.CommandType), client) + logger.Debug("container and network parsed from command", zap.String("container", cont), zap.String("network", net), zap.String("command", c.Command)) + if err != nil { + utils.LogError(logger, err, "failed to parse container name from given docker command", zap.String("cmd", c.Command)) + } + if c.ContainerName != "" && c.ContainerName != cont { + logger.Warn(fmt.Sprintf("given app container:(%v) is different from parsed app container:(%v), taking parsed value", c.ContainerName, cont)) + } + c.ContainerName = cont + + if c.NetworkName != "" && c.NetworkName != net { + logger.Warn(fmt.Sprintf("given docker network:(%v) is different from parsed docker network:(%v), taking parsed value", c.NetworkName, net)) + } + c.NetworkName = net + + logger.Debug("Using container and network", zap.String("container", c.ContainerName), zap.String("network", c.NetworkName)) + } + } + + instrumentation := core.New(logger, h, p, t, client) + + testDB := testdb.New(logger, c.Path) + mockDB := mockdb.New(logger, c.Path, "") + openAPIdb := openapidb.New(logger, filepath.Join(c.Path, "schema")) + reportDB := reportdb.New(logger, c.Path+"/reports") + testSetDb := testset.New[*models.TestSet](logger, c.Path) + storage := storage.New(c.APIServerURL, logger) + return &CommonInternalService{ + commonPlatformServices{ + YamlTestDB: testDB, + YamlMockDb: mockDB, + YamlOpenAPIDb: openAPIdb, + YamlReportDb: reportDB, + YamlTestSetDB: testSetDb, + Storage: storage, + }, + instrumentation, + }, nil +} diff --git a/keploy/cli/provider/core_service_others.go b/keploy/cli/provider/core_service_others.go new file mode 100644 index 0000000..4b982aa --- /dev/null +++ b/keploy/cli/provider/core_service_others.go @@ -0,0 +1,83 @@ +//go:build !linux + +package provider + +import ( + "context" + "errors" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/core" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/storage" + "go.keploy.io/server/v2/pkg/platform/telemetry" + "go.keploy.io/server/v2/pkg/platform/yaml/configdb/testset" + mockdb "go.keploy.io/server/v2/pkg/platform/yaml/mockdb" + openapidb "go.keploy.io/server/v2/pkg/platform/yaml/openapidb" + reportdb "go.keploy.io/server/v2/pkg/platform/yaml/reportdb" + testdb "go.keploy.io/server/v2/pkg/platform/yaml/testdb" + + "go.keploy.io/server/v2/pkg/service" + "go.keploy.io/server/v2/pkg/service/contract" + "go.keploy.io/server/v2/pkg/service/replay" + "go.keploy.io/server/v2/pkg/service/report" + "go.keploy.io/server/v2/pkg/service/tools" + "go.uber.org/zap" +) + +type CommonInternalService struct { + commonPlatformServices + Instrumentation *core.Core +} + +func Get(ctx context.Context, cmd string, c *config.Config, logger *zap.Logger, tel *telemetry.Telemetry, auth service.Auth) (interface{}, error) { + commonServices, err := GetCommonServices(ctx, c, logger) + if err != nil { + return nil, err + } + contractSvc := contract.New(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlOpenAPIDb, c) + + replaySvc := replay.NewReplayer(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlReportDb, commonServices.YamlTestSetDB, tel, commonServices.Instrumentation, auth, commonServices.Storage, c) + + toolsSvc := tools.NewTools(logger, commonServices.YamlTestSetDB, commonServices.YamlTestDB, tel, auth, c) + reportSvc := report.New(logger, c, commonServices.YamlReportDb, commonServices.YamlTestDB) + + if (cmd == "test" && c.Test.BasePath != "") || cmd == "normalize" || cmd == "mock" { + return replaySvc, nil + } + + if cmd == "templatize" || cmd == "config" || cmd == "update" || cmd == "login" || cmd == "export" || cmd == "import" { + return toolsSvc, nil + } + + if cmd == "contract" { + return contractSvc, nil + } + + if cmd == "report" { + return reportSvc, nil + } + + return nil, errors.New("command not supported in non linux os. if you are on windows or mac, please use the dockerized version of your application") +} + +func GetCommonServices(_ context.Context, c *config.Config, logger *zap.Logger) (*CommonInternalService, error) { + instrumentation := core.New(logger) + testDB := testdb.New(logger, c.Path) + mockDB := mockdb.New(logger, c.Path, "") + openAPIdb := openapidb.New(logger, c.Path) + reportDB := reportdb.New(logger, c.Path+"/reports") + testSetDb := testset.New[*models.TestSet](logger, c.Path) + storage := storage.New(c.APIServerURL, logger) + return &CommonInternalService{ + commonPlatformServices{ + YamlTestDB: testDB, + YamlMockDb: mockDB, + YamlOpenAPIDb: openAPIdb, + YamlReportDb: reportDB, + YamlTestSetDB: testSetDb, + Storage: storage, + }, + instrumentation, + }, nil +} diff --git a/keploy/cli/provider/docker.go b/keploy/cli/provider/docker.go new file mode 100644 index 0000000..be16a52 --- /dev/null +++ b/keploy/cli/provider/docker.go @@ -0,0 +1,297 @@ +package provider + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "syscall" + + "github.com/docker/docker/api/types" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/platform/docker" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "golang.org/x/term" +) + +type DockerConfigStruct struct { + DockerImage string + Envs map[string]string +} + +var DockerConfig = DockerConfigStruct{ + DockerImage: "ghcr.io/keploy/keploy", +} + +func GenerateDockerEnvs(config DockerConfigStruct) string { + var envs []string + for key, value := range config.Envs { + if runtime.GOOS == "windows" { + envs = append(envs, fmt.Sprintf("-e %s=%s", key, value)) + } else { + envs = append(envs, fmt.Sprintf("-e %s='%s'", key, value)) + } + } + return strings.Join(envs, " ") +} + +// StartInDocker will check if the docker command is provided as an input +// then start the Keploy as a docker container and run the command +// should also return a boolean if the execution is moved to docker +func StartInDocker(ctx context.Context, logger *zap.Logger, conf *config.Config) error { + + if DockerConfig.Envs == nil { + DockerConfig.Envs = map[string]string{ + "INSTALLATION_ID": conf.InstallationID, + } + } else { + DockerConfig.Envs["INSTALLATION_ID"] = conf.InstallationID + } + + //Check if app command starts with docker or docker-compose. + // If it does, then we would run the docker version of keploy and + // pass the command and control to it. + cmdType := utils.FindDockerCmd(conf.Command) + if conf.InDocker || !(utils.IsDockerCmd(cmdType)) { + return nil + } + // pass the all the commands and args to the docker version of Keploy + err := RunInDocker(ctx, logger) + if err != nil { + utils.LogError(logger, err, "failed to run the command in docker") + return err + } + // gracefully exit the current process + logger.Info("exiting the current process as the command is moved to docker") + + if utils.LogFile != nil { + err := utils.LogFile.Close() + if err != nil { + utils.LogError(logger, err, "Failed to close Keploy Logs") + } + if err := utils.DeleteFileIfNotExists(logger, "keploy-logs.txt"); err != nil { + return nil + } + if err := utils.DeleteFileIfNotExists(logger, "docker-compose-tmp.yaml"); err != nil { + return nil + } + } + + os.Exit(0) + return nil +} + +func RunInDocker(ctx context.Context, logger *zap.Logger) error { + //Get the correct keploy alias. + keployAlias, err := getAlias(ctx, logger) + if err != nil { + return err + } + + var quotedArgs []string + + for _, arg := range os.Args[1:] { + quotedArgs = append(quotedArgs, strconv.Quote(arg)) + } + client, err := docker.New(logger) + if err != nil { + utils.LogError(logger, err, "failed to initalise docker") + return err + } + addKeployNetwork(ctx, logger, client) + err = client.CreateVolume(ctx, "debugfs", true) + if err != nil { + utils.LogError(logger, err, "failed to debugfs volume") + return err + } + + var cmd *exec.Cmd + + // Detect the operating system + if runtime.GOOS == "windows" { + var args []string + args = append(args, "/C") + args = append(args, strings.Split(keployAlias, " ")...) + args = append(args, os.Args[1:]...) + // Use cmd.exe /C for Windows + cmd = exec.CommandContext( + ctx, + "cmd.exe", + args..., + ) + } else { + // Use sh -c for Unix-like systems + cmd = exec.CommandContext( + ctx, + "sh", + "-c", + keployAlias+" "+strings.Join(quotedArgs, " "), + ) + } + + cmd.Cancel = func() error { + err := utils.SendSignal(logger, -cmd.Process.Pid, syscall.SIGINT) + if err != nil { + utils.LogError(logger, err, "failed to start stop docker") + return err + } + return nil + } + + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + + logger.Debug("running the following command in docker", zap.String("command", cmd.String())) + err = cmd.Run() + if err != nil { + if ctx.Err() == context.Canceled { + return ctx.Err() + } + utils.LogError(logger, err, "failed to start keploy in docker") + return err + } + return nil +} + +func getAlias(ctx context.Context, logger *zap.Logger) (string, error) { + // Get the name of the operating system. + osName := runtime.GOOS + //TODO: configure the hardcoded port mapping + img := DockerConfig.DockerImage + ":v" + utils.Version + logger.Info("Starting keploy in docker with image", zap.String("image:", img)) + envs := GenerateDockerEnvs(DockerConfig) + if envs != "" { + envs = envs + " " + } + var ttyFlag string + + if term.IsTerminal(int(os.Stdin.Fd())) { + ttyFlag = " -it " + } else { + ttyFlag = " " + } + + switch osName { + case "linux": + alias := "sudo docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + " -v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img + return alias, nil + case "windows": + // Get the current working directory + pwd, err := os.Getwd() + if err != nil { + utils.LogError(logger, err, "failed to get the current working directory") + } + dpwd := convertPathToUnixStyle(pwd) + cmd := exec.CommandContext(ctx, "docker", "context", "ls", "--format", "{{.Name}}\t{{.Current}}") + out, err := cmd.Output() + if err != nil { + utils.LogError(logger, err, "failed to get the current docker context") + return "", errors.New("failed to get alias") + } + dockerContext := strings.Split(strings.TrimSpace(string(out)), "\n")[0] + if len(dockerContext) == 0 { + utils.LogError(logger, nil, "failed to get the current docker context") + return "", errors.New("failed to get alias") + } + dockerContext = strings.Split(dockerContext, "\n")[0] + if dockerContext == "colima" { + logger.Info("Starting keploy in docker with colima context, as that is the current context.") + alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + pwd + ":" + dpwd + " -w " + dpwd + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("USERPROFILE") + "\\.keploy-config:/root/.keploy-config -v " + os.Getenv("USERPROFILE") + "\\.keploy:/root/.keploy --rm " + img + return alias, nil + } + // if default docker context is used + logger.Info("Starting keploy in docker with default context, as that is the current context.") + alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + pwd + ":" + dpwd + " -w " + dpwd + " -v /sys/fs/cgroup:/sys/fs/cgroup -v debugfs:/sys/kernel/debug:rw -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("USERPROFILE") + "\\.keploy-config:/root/.keploy-config -v " + os.Getenv("USERPROFILE") + "\\.keploy:/root/.keploy --rm " + img + return alias, nil + case "darwin": + // Get the context and docker daemon endpoint. + cmd := exec.CommandContext(ctx, "docker", "context", "inspect", "--format", "{{if .Metadata}}Name={{.Name}} {{end}}{{if .Endpoints.docker}}Endpoint={{.Endpoints.docker.Host}}{{end}}") + out, err := cmd.Output() + if err != nil { + utils.LogError(logger, err, "failed to inspect the docker context") + return "", errors.New("failed to get alias") + } + + output := strings.TrimSpace(string(out)) + var currentContext, dockerEndpoint string + + // Parse the output for current context and endpoint + for _, part := range strings.Fields(output) { + if strings.HasPrefix(part, "Name=") { + currentContext = strings.TrimPrefix(part, "Name=") + } else if strings.HasPrefix(part, "Endpoint=") { + dockerEndpoint = strings.TrimPrefix(part, "Endpoint=") + } + } + + // Check if we found a current context + if currentContext == "" { + utils.LogError(logger, nil, "failed to find the current docker context") + return "", errors.New("failed to get alias") + } + + // Construct the alias command based on context-specific `debugfs` mount + var alias string + if currentContext == "colima" { + + // To allow docker client to connect to the colima daemon because by default it uses the default docker daemon + err := os.Setenv("DOCKER_HOST", dockerEndpoint) + if err != nil { + utils.LogError(logger, err, "failed to set DOCKER_HOST environment variable for colima context") + return "", errors.New("failed to get alias") + } + logger.Info("Starting keploy in docker with colima context, as that is the current context.") + alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img + return alias, nil + } + // if default docker context is used + logger.Info("Starting keploy in docker with default context, as that is the current context.") + alias = "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v debugfs:/sys/kernel/debug:rw -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img + return alias, nil + } + return "", errors.New("failed to get alias") +} + +func addKeployNetwork(ctx context.Context, logger *zap.Logger, client docker.Client) { + + // Check if the 'keploy-network' network exists + networks, err := client.NetworkList(ctx, types.NetworkListOptions{}) + if err != nil { + logger.Debug("failed to list docker networks") + return + } + + for _, network := range networks { + if network.Name == "keploy-network" { + logger.Debug("keploy network already exists") + return + } + } + + // Create the 'keploy' network if it doesn't exist + _, err = client.NetworkCreate(ctx, "keploy-network", types.NetworkCreate{ + CheckDuplicate: true, + }) + if err != nil { + logger.Debug("failed to create keploy network") + return + } + + logger.Debug("keploy network created") +} + +func convertPathToUnixStyle(path string) string { + // Replace backslashes with forward slashes + unixPath := strings.ReplaceAll(path, "\\", "/") + // Remove 'C:' + if len(unixPath) > 1 && unixPath[1] == ':' { + unixPath = unixPath[2:] + } + return unixPath +} diff --git a/keploy/cli/provider/service.go b/keploy/cli/provider/service.go new file mode 100644 index 0000000..1a38790 --- /dev/null +++ b/keploy/cli/provider/service.go @@ -0,0 +1,60 @@ +package provider + +import ( + "context" + "errors" + "sync" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/platform/telemetry" + "go.keploy.io/server/v2/pkg/service" + "go.keploy.io/server/v2/utils" + + "go.keploy.io/server/v2/pkg/service/load" + "go.keploy.io/server/v2/pkg/service/secure" + "go.keploy.io/server/v2/pkg/service/testsuite" + "go.keploy.io/server/v2/pkg/service/utgen" + "go.uber.org/zap" +) + +var TeleGlobalMap sync.Map + +type ServiceProvider struct { + logger *zap.Logger + cfg *config.Config + auth service.Auth +} + +func NewServiceProvider(logger *zap.Logger, cfg *config.Config, auth service.Auth) *ServiceProvider { + return &ServiceProvider{ + logger: logger, + cfg: cfg, + auth: auth, + } +} + +func (n *ServiceProvider) GetService(ctx context.Context, cmd string) (interface{}, error) { + + tel := telemetry.NewTelemetry(n.logger, telemetry.Options{ + Enabled: !n.cfg.DisableTele, + Version: utils.Version, + GlobalMap: TeleGlobalMap, + InstallationID: n.cfg.InstallationID, + }) + tel.Ping() + + switch cmd { + case "secure": + return secure.NewSecurityChecker(n.cfg, n.logger) + case "load": + return load.NewLoadTester(n.cfg, n.logger) + case "testsuite": + return testsuite.NewTSExecutor(n.cfg, n.logger, false) + case "gen": + return utgen.NewUnitTestGenerator(n.cfg, tel, n.auth, n.logger) + case "record", "test", "mock", "normalize", "rerecord", "contract", "config", "update", "login", "export", "import", "templatize", "report": + return Get(ctx, cmd, n.cfg, n.logger, tel, n.auth) + default: + return nil, errors.New("invalid command") + } +} diff --git a/keploy/cli/provider/util.go b/keploy/cli/provider/util.go new file mode 100644 index 0000000..eb743c7 --- /dev/null +++ b/keploy/cli/provider/util.go @@ -0,0 +1,101 @@ +package provider + +import ( + "errors" + "fmt" + "io" + "log" + "os" + "strings" + + "go.keploy.io/server/v2/utils" +) + +func (c *CmdConfigurator) noCommandError() error { + return errors.New("missing required -c flag or appCmd in config file") +} + +// alreadyRunning checks that during test mode, if user provides the basePath, then it implies that the application is already running somewhere. +func alreadyRunning(cmd, basePath string) bool { + return (cmd == "test" && basePath != "") +} + +var Logo = ` + ▓██▓▄ + ▓▓▓▓██▓█▓▄ + ████████▓▒ + ▀▓▓███▄ ▄▄ ▄ ▌ + ▄▌▌▓▓████▄ ██ ▓█▀ ▄▌▀▄ ▓▓▌▄ ▓█ ▄▌▓▓▌▄ ▌▌ ▓ + ▓█████████▌▓▓ ██▓█▄ ▓█▄▓▓ ▐█▌ ██ ▓█ █▌ ██ █▌ █▓ + ▓▓▓▓▀▀▀▀▓▓▓▓▓▓▌ ██ █▓ ▓▌▄▄ ▐█▓▄▓█▀ █▓█ ▀█▄▄█▀ █▓█ + ▓▌ ▐█▌ █▌ + ▓ +` + +func PrintLogo(wr io.Writer, disableANSI bool) { + if os.Getenv("BINARY_TO_DOCKER") != "true" { + printKeployLogo(wr, disableANSI, Logo) + // print version to the same writer + _, err := fmt.Fprintf(wr, "%s: %v\n\n", utils.VersionIdenitfier, utils.Version) + if err != nil { + log.Fatalf("Error printing version: %v", err) + } + } +} + +func printKeployLogo(wr io.Writer, disableANSI bool, logo string) { + const reset = "\033[0m" + lines := strings.Split(logo, "\n") + + if !disableANSI { + for i, line := range lines { + for j, ch := range line { + color := getLogoColor(i, j) + // wrapper now uses fmt.Fprint, so this will correctly print color + char + reset + FprintWrapper(false, wr, color, string(ch), reset) + } + FprintWrapper(true, wr) // newline after each line + } + } else { + // plain logo (no per-char coloring) + FprintWrapper(false, wr, logo) + FprintWrapper(true, wr) + } +} + +// FprintWrapper prints all its args (like fmt.Fprint) and optionally a leading newline. +func FprintWrapper(newLine bool, wr io.Writer, a ...interface{}) { + if newLine { + if _, err := fmt.Fprintln(wr); err != nil { + log.Fatalf("Error printing newline: %v", err) + } + } + if len(a) > 0 { + if _, err := fmt.Fprint(wr, a...); err != nil { + log.Fatalf("Error printing output: %v", err) + } + } +} + +// Get the color for the logo at position (i, j) +func getLogoColor(i, j int) string { + gradientColors := []string{ + "\033[38;5;202m", // Dark Orange + "\033[38;5;208m", + "\033[38;5;214m", // Light Orange + "\033[38;5;226m", // Light Yellow + } + + switch { + case i <= 5: + return gradientColors[0] + case i == 6 && j <= 42: + return gradientColors[1] + case i == 7 && j <= 49: + return gradientColors[2] + case j <= 38: + return gradientColors[3] + default: + return gradientColors[0] + } +} diff --git a/keploy/cli/record.go b/keploy/cli/record.go new file mode 100755 index 0000000..8349543 --- /dev/null +++ b/keploy/cli/record.go @@ -0,0 +1,55 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + recordSvc "go.keploy.io/server/v2/pkg/service/record" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("record", Record) +} + +func Record(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "record", + Short: "record the keploy testcases from the API calls", + Example: `keploy record -c "/path/to/user/app"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var record recordSvc.Service + var ok bool + if record, ok = svc.(recordSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy record service interface") + return nil + } + + err = record.Start(ctx, false) + if err != nil { + utils.LogError(logger, err, "failed to record") + return nil + } + + return nil + }, + } + + err := cmdConfigurator.AddFlags(cmd) + if err != nil { + utils.LogError(logger, err, "failed to add record flags") + return nil + } + + return cmd +} diff --git a/keploy/cli/report.go b/keploy/cli/report.go new file mode 100644 index 0000000..4a01a56 --- /dev/null +++ b/keploy/cli/report.go @@ -0,0 +1,55 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + reportSvc "go.keploy.io/server/v2/pkg/service/report" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("report", Report) +} + +func Report(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "report", + Short: "report the keploy test results from the API calls", + Example: `keploy report -t "test-set-id"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var report reportSvc.Service + var ok bool + if report, ok = svc.(reportSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy report service interface") + return nil + } + + err = report.GenerateReport(ctx) + if err != nil { + utils.LogError(logger, err, "failed to generate report") + return nil + } + + return nil + }, + } + + err := cmdConfigurator.AddFlags(cmd) + if err != nil { + utils.LogError(logger, err, "failed to add report flags") + return nil + } + + return cmd +} diff --git a/keploy/cli/rerecord.go b/keploy/cli/rerecord.go new file mode 100644 index 0000000..17ffda8 --- /dev/null +++ b/keploy/cli/rerecord.go @@ -0,0 +1,56 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/service/orchestrator" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("rerecord", ReRecord) +} + +func ReRecord(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "rerecord", + Short: "ReRecord new keploy testcases/mocks from the existing test cases for the given testset(s)", + Example: `keploy rerecord -c "user app cmd" -t "test-set-1,teset-set-3"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + + var orch orchestrator.Service + var ok bool + if orch, ok = svc.(orchestrator.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy orchestrator service interface") + return nil + } + + err = orch.ReRecord(ctx) + if err != nil { + utils.LogError(logger, err, "failed to re-record") + return nil + } + + return nil + }, + } + + err := cmdConfigurator.AddFlags(cmd) + if err != nil { + utils.LogError(logger, err, "failed to add rerecord flags") + return nil + } + + return cmd +} diff --git a/keploy/cli/root.go b/keploy/cli/root.go new file mode 100755 index 0000000..ce7b8a4 --- /dev/null +++ b/keploy/cli/root.go @@ -0,0 +1,55 @@ +package cli + +import ( + "context" + "os" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/cli/provider" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func Root(ctx context.Context, logger *zap.Logger, svcFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + conf := config.New() + + var rootCmd = &cobra.Command{ + Use: "keploy", + Short: "Keploy CLI", + Example: provider.RootExamples, + Version: utils.Version, + PreRun: func(cmd *cobra.Command, _ []string) { + disableAnsi, _ := cmd.Flags().GetBool("disable-ansi") + provider.PrintLogo(os.Stdout, disableAnsi) + }, + } + + defaultHelpFunc := rootCmd.HelpFunc() + + rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { + disableAnsi, _ := cmd.Flags().GetBool("disable-ansi") + provider.PrintLogo(os.Stdout, disableAnsi) + + // Use the default help function instead of calling the parent's HelpFunc + defaultHelpFunc(cmd, args) + }) + + rootCmd.CompletionOptions.DisableDefaultCmd = true + + rootCmd.SetHelpTemplate(provider.RootCustomHelpTemplate) + + rootCmd.SetVersionTemplate(provider.VersionTemplate) + + err := cmdConfigurator.AddFlags(rootCmd) + if err != nil { + utils.LogError(logger, err, "failed to set flags") + return nil + } + + for _, cmd := range Registered { + c := cmd(ctx, logger, conf, svcFactory, cmdConfigurator) + rootCmd.AddCommand(c) + } + return rootCmd +} diff --git a/keploy/cli/secure.go b/keploy/cli/secure.go new file mode 100644 index 0000000..ede4010 --- /dev/null +++ b/keploy/cli/secure.go @@ -0,0 +1,286 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + secureSvc "go.keploy.io/server/v2/pkg/service/secure" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("secure", Secure) +} + +func Secure(ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "secure", + Short: "check security vulnerabilities against a given API url (--base-url)", + Example: `keploy secure --base-url "http://localhost:8080/path/to/user/app"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service") + return nil + } + + var secSvc secureSvc.Service + var ok bool + if secSvc, ok = svc.(secureSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy secure service interface") + return nil + } + + // Get flag values + ruleSet, _ := cmd.Flags().GetString("rule-set") + checksPath, _ := cmd.Flags().GetString("checks-path") + + // Add flag values to context + ctx = context.WithValue(ctx, "rule-set", ruleSet) + ctx = context.WithValue(ctx, "checks-path", checksPath) + + _, err = secSvc.Start(ctx) + if err != nil { + utils.LogError(logger, err, "failed to start secure") + return nil + } + + return nil + }, + } + + // Add flags for the main secure command + err := cmdConfigurator.AddFlags(cmd) + if err != nil { + utils.LogError(logger, err, "failed to add secure flags") + return nil + } + + if addCmd := AddCRCommand(cmd, ctx, logger, cfg, serviceFactory, cmdConfigurator); addCmd != nil { + cmd.AddCommand(addCmd) + // Add flags for the subcommand + err := cmdConfigurator.AddFlags(addCmd) + if err != nil { + utils.LogError(logger, err, "failed to add secure add flags") + } + } + + if removeCmd := RemoveCRCommand(cmd, ctx, logger, cfg, serviceFactory, cmdConfigurator); removeCmd != nil { + cmd.AddCommand(removeCmd) + // Add flags for the subcommand + err := cmdConfigurator.AddFlags(removeCmd) + if err != nil { + utils.LogError(logger, err, "failed to add secure remove flags") + } + } + + if updateCmd := UpdateCRCommand(cmd, ctx, logger, cfg, serviceFactory, cmdConfigurator); updateCmd != nil { + cmd.AddCommand(updateCmd) + // Add flags for the subcommand + err := cmdConfigurator.AddFlags(updateCmd) + if err != nil { + utils.LogError(logger, err, "failed to add secure update flags") + } + } + + if listCmd := ListCRsCommand(cmd, ctx, logger, cfg, serviceFactory, cmdConfigurator); listCmd != nil { + cmd.AddCommand(listCmd) + // Add flags for the subcommand + err := cmdConfigurator.AddFlags(listCmd) + if err != nil { + utils.LogError(logger, err, "failed to add secure list flags") + } + } + + return cmd +} + +func AddCRCommand(cmd *cobra.Command, ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var addCmd = &cobra.Command{ + Use: "add", + Short: "add a custom security check", + Example: `keploy secure add --checks-path "./custom-checks.yaml"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + svc, err := serviceFactory.GetService(ctx, "secure") + if err != nil { + utils.LogError(logger, err, "failed to get secure service") + return nil + } + + var secSvc secureSvc.Service + var ok bool + if secSvc, ok = svc.(secureSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy secure service interface") + return nil + } + + // Get flag values + checksPath, _ := cmd.Flags().GetString("checks-path") + + // Add flag values to context + ctx = context.WithValue(ctx, "checks-path", checksPath) + + err = secSvc.AddCustomCheck(ctx) + if err != nil { + utils.LogError(logger, err, "failed to add custom check") + return nil + } + + return nil + }, + } + + // addCmd.Flags().String("configPath", ".", "Path to the local directory where keploy configuration file is stored") + // addCmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") + + return addCmd +} + +func RemoveCRCommand(cmd *cobra.Command, ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var removeCmd = &cobra.Command{ + Use: "remove", + Short: "remove a custom security check", + Example: `keploy secure remove --id --checks-path "./custom-checks.yaml"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + svc, err := serviceFactory.GetService(ctx, "secure") + if err != nil { + utils.LogError(logger, err, "failed to get secure service") + return nil + } + + var secSvc secureSvc.Service + var ok bool + if secSvc, ok = svc.(secureSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy secure service interface") + return nil + } + + // Get flag values + checksPath, _ := cmd.Flags().GetString("checks-path") + id, _ := cmd.Flags().GetString("id") + + // Add flag values to context + ctx = context.WithValue(ctx, "checks-path", checksPath) + ctx = context.WithValue(ctx, "id", id) + + err = secSvc.RemoveCustomCheck(ctx) + if err != nil { + utils.LogError(logger, err, "failed to remove custom check") + return nil + } + + return nil + }, + } + + // removeCmd.Flags().String("configPath", ".", "Path to the local directory where keploy configuration file is stored") + // removeCmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") + // removeCmd.Flags().String("id", "", "ID of the custom check to remove") + + return removeCmd +} + +func UpdateCRCommand(cmd *cobra.Command, ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var updateCmd = &cobra.Command{ + Use: "update", + Short: "update a custom security check", + Example: `keploy secure update --id --checks-path "./custom-checks.yaml"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + svc, err := serviceFactory.GetService(ctx, "secure") + if err != nil { + utils.LogError(logger, err, "failed to get secure service") + return nil + } + + var secSvc secureSvc.Service + var ok bool + if secSvc, ok = svc.(secureSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy secure service interface") + return nil + } + + // Get flag values + checksPath, _ := cmd.Flags().GetString("checks-path") + id, _ := cmd.Flags().GetString("id") + + // Add flag values to context + ctx = context.WithValue(ctx, "checks-path", checksPath) + ctx = context.WithValue(ctx, "id", id) + + err = secSvc.UpdateCustomCheck(ctx) + if err != nil { + utils.LogError(logger, err, "failed to update custom check") + return nil + } + + return nil + }, + } + + // updateCmd.Flags().String("configPath", ".", "Path to the local directory where keploy configuration file is stored") + // updateCmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") + // updateCmd.Flags().String("id", "", "ID of the custom check to update") + + return updateCmd +} + +func ListCRsCommand(cmd *cobra.Command, ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var listCmd = &cobra.Command{ + Use: "list", + Short: "list all built-in or custom security checks", + Example: `keploy secure list --rule-set custom --checks-path "./custom-checks.yaml"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + svc, err := serviceFactory.GetService(ctx, "secure") + if err != nil { + utils.LogError(logger, err, "failed to get secure service") + return nil + } + + var secSvc secureSvc.Service + var ok bool + if secSvc, ok = svc.(secureSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy secure service interface") + return nil + } + + // Get flag values + ruleSet, _ := cmd.Flags().GetString("rule-set") + checksPath, _ := cmd.Flags().GetString("checks-path") + + // Add flag values to context + ctx = context.WithValue(ctx, "rule-set", ruleSet) + ctx = context.WithValue(ctx, "checks-path", checksPath) + + err = secSvc.ListChecks(ctx) + if err != nil { + utils.LogError(logger, err, "failed to list checks") + return nil + } + + return nil + }, + } + + // listCmd.Flags().String("configPath", ".", "Path to the local directory where keploy configuration file is stored") + // listCmd.Flags().String("rule-set", "basic", "Specify which checks to list: 'basic' (built-in), 'custom'") + // listCmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") + + return listCmd +} diff --git a/keploy/cli/service.go b/keploy/cli/service.go new file mode 100644 index 0000000..8e0f815 --- /dev/null +++ b/keploy/cli/service.go @@ -0,0 +1,17 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" +) + +type ServiceFactory interface { + GetService(ctx context.Context, cmd string) (interface{}, error) +} + +type CmdConfigurator interface { + AddFlags(cmd *cobra.Command) error + ValidateFlags(ctx context.Context, cmd *cobra.Command) error + Validate(ctx context.Context, cmd *cobra.Command) error +} diff --git a/keploy/cli/templatize.go b/keploy/cli/templatize.go new file mode 100644 index 0000000..6e63688 --- /dev/null +++ b/keploy/cli/templatize.go @@ -0,0 +1,53 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + toolsSvc "go.keploy.io/server/v2/pkg/service/tools" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("Templatize", Templatize) +} + +func Templatize(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "templatize", + Short: "templatize the keploy testcases for re-record", + Example: `keploy templatize -t "test-set-1,teset-set-3" for particular testsets and keploy templatize for all testsets`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + // Get the replay service. + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var tools toolsSvc.Service + var ok bool + if tools, ok = svc.(toolsSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy tools service interface") + return nil + } + if err := tools.Templatize(ctx); err != nil { + utils.LogError(logger, err, "failed to templatize test cases") + return nil + } + return nil + }, + } + + err := cmdConfigurator.AddFlags(cmd) + if err != nil { + utils.LogError(logger, err, "failed to add templatize flags") + return nil + } + + return cmd +} diff --git a/keploy/cli/test.go b/keploy/cli/test.go new file mode 100755 index 0000000..6b91330 --- /dev/null +++ b/keploy/cli/test.go @@ -0,0 +1,63 @@ +package cli + +import ( + "context" + + "go.keploy.io/server/v2/utils" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + replaySvc "go.keploy.io/server/v2/pkg/service/replay" + "go.uber.org/zap" +) + +func init() { + Register("test", Test) +} + +func Test(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var testCmd = &cobra.Command{ + Use: "test", + Short: "run the recorded testcases and execute assertions", + Example: `keploy test -c "/path/to/user/app" --delay 6`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var replay replaySvc.Service + var ok bool + if replay, ok = svc.(replaySvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy replay service interface") + return nil + } + // defering the stop function to stop keploy in case of any error in test or in case of context cancellation + defer func() { + select { + case <-ctx.Done(): + break + default: + utils.ExecCancel() + } + }() + err = replay.Start(ctx) + if err != nil { + utils.LogError(logger, err, "failed to replay") + } + + return nil + }, + } + + err := cmdConfigurator.AddFlags(testCmd) + if err != nil { + utils.LogError(logger, err, "failed to add test flags") + return nil + } + + return testCmd +} diff --git a/keploy/cli/testsuite.go b/keploy/cli/testsuite.go new file mode 100644 index 0000000..501f3c5 --- /dev/null +++ b/keploy/cli/testsuite.go @@ -0,0 +1,60 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + testsuiteSvc "go.keploy.io/server/v2/pkg/service/testsuite" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("testsuite", TestSuite) +} + +func TestSuite(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "testsuite", + Short: "execute a testsuite against a given url (--base-url)", + Example: `keploy testsuite --base-url "http://localhost:8080/path/to/user/app"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service") + return nil + } + + var tsSvc testsuiteSvc.Service + var ok bool + if tsSvc, ok = svc.(testsuiteSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy testsuite service interface") + return nil + } + + // If any other command is using the TSExecutor there is no need to report to the screen. + // for example, the load command uses the TSExecutor to execute the testsuite. It does not need to report the execution to the screen. + ctx = context.WithValue(ctx, "command", cmd.Name()) + + _, err = tsSvc.Execute(ctx, nil) + if err != nil { + utils.LogError(logger, err, "failed to execute testsuite") + return nil + } + + return nil + }, + } + + err := cmdConfigurator.AddFlags(cmd) + if err != nil { + utils.LogError(logger, err, "failed to add testsuite flags") + return nil + } + + return cmd +} diff --git a/keploy/cli/update.go b/keploy/cli/update.go new file mode 100644 index 0000000..64cbf3f --- /dev/null +++ b/keploy/cli/update.go @@ -0,0 +1,51 @@ +package cli + +import ( + "context" + "os" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/cli/provider" + "go.keploy.io/server/v2/config" + toolsSvc "go.keploy.io/server/v2/pkg/service/tools" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + Register("update", Update) +} + +// Update retrieves the command to tools Keploy +func Update(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update Keploy ", + Example: "keploy update", + RunE: func(cmd *cobra.Command, _ []string) error { + disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) + provider.PrintLogo(os.Stdout, disableAnsi) + svc, err := serviceFactory.GetService(ctx, "update") + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var tools toolsSvc.Service + var ok bool + if tools, ok = svc.(toolsSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy tools service interface") + return nil + } + err = tools.Update(ctx) + if err != nil { + utils.LogError(logger, err, "failed to update") + } + return nil + }, + } + if err := cmdConfigurator.AddFlags(updateCmd); err != nil { + utils.LogError(logger, err, "failed to add update cmd flags") + return nil + } + return updateCmd +} diff --git a/keploy/cli/utgen.go b/keploy/cli/utgen.go new file mode 100644 index 0000000..5143225 --- /dev/null +++ b/keploy/cli/utgen.go @@ -0,0 +1,56 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.keploy.io/server/v2/config" + utgenSvc "go.keploy.io/server/v2/pkg/service/utgen" + "go.keploy.io/server/v2/utils" + + "go.uber.org/zap" +) + +func init() { + Register("gen", GenerateUT) +} + +func GenerateUT(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "gen", + Short: "generate unit tests using AI", + Example: `keploy gen"`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return cmdConfigurator.Validate(ctx, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) + return nil + } + var utg utgenSvc.Service + var ok bool + if utg, ok = svc.(utgenSvc.Service); !ok { + utils.LogError(logger, nil, "service doesn't satisfy unit test generation service interface") + return nil + } + + err = utg.Start(ctx) + if err != nil { + utils.LogError(logger, err, "failed to generate unit tests") + return nil + } + + return nil + }, + } + + err := cmdConfigurator.AddFlags(cmd) + if err != nil { + utils.LogError(logger, err, "failed to add unit test generation flags") + return nil + } + + return cmd +} diff --git a/keploy/config/config.go b/keploy/config/config.go new file mode 100644 index 0000000..7d00e0c --- /dev/null +++ b/keploy/config/config.go @@ -0,0 +1,259 @@ +// Package config provides configuration structures for the application. +package config + +import ( + "errors" + "fmt" + "strings" + "time" +) + +type Config struct { + Path string `json:"path" yaml:"path" mapstructure:"path"` + AppID uint64 `json:"appId" yaml:"appId" mapstructure:"appId"` + AppName string `json:"appName" yaml:"appName" mapstructure:"appName"` + Command string `json:"command" yaml:"command" mapstructure:"command"` + Templatize Templatize `json:"templatize" yaml:"templatize" mapstructure:"templatize"` + Port uint32 `json:"port" yaml:"port" mapstructure:"port"` + E2E bool `json:"e2e" yaml:"e2e" mapstructure:"e2e"` + DNSPort uint32 `json:"dnsPort" yaml:"dnsPort" mapstructure:"dnsPort"` + ProxyPort uint32 `json:"proxyPort" yaml:"proxyPort" mapstructure:"proxyPort"` + Debug bool `json:"debug" yaml:"debug" mapstructure:"debug"` + DisableTele bool `json:"disableTele" yaml:"disableTele" mapstructure:"disableTele"` + DisableANSI bool `json:"disableANSI" yaml:"disableANSI" mapstructure:"disableANSI"` + InDocker bool `json:"inDocker" yaml:"-" mapstructure:"inDocker"` + ContainerName string `json:"containerName" yaml:"containerName" mapstructure:"containerName"` + NetworkName string `json:"networkName" yaml:"networkName" mapstructure:"networkName"` + BuildDelay uint64 `json:"buildDelay" yaml:"buildDelay" mapstructure:"buildDelay"` + Test Test `json:"test" yaml:"test" mapstructure:"test"` + Record Record `json:"record" yaml:"record" mapstructure:"record"` + Report Report `json:"report" yaml:"report" mapstructure:"report"` + Gen UtGen `json:"gen" yaml:"-" mapstructure:"gen"` + Normalize Normalize `json:"normalize" yaml:"-" mapstructure:"normalize"` + ReRecord ReRecord `json:"rerecord" yaml:"-" mapstructure:"rerecord"` + ConfigPath string `json:"configPath" yaml:"configPath" mapstructure:"configPath"` + BypassRules []BypassRule `json:"bypassRules" yaml:"bypassRules" mapstructure:"bypassRules"` + EnableTesting bool `json:"enableTesting" yaml:"-" mapstructure:"enableTesting"` + GenerateGithubActions bool `json:"generateGithubActions" yaml:"generateGithubActions" mapstructure:"generateGithubActions"` + KeployContainer string `json:"keployContainer" yaml:"keployContainer" mapstructure:"keployContainer"` + KeployNetwork string `json:"keployNetwork" yaml:"keployNetwork" mapstructure:"keployNetwork"` + CommandType string `json:"cmdType" yaml:"cmdType" mapstructure:"cmdType"` + Contract Contract `json:"contract" yaml:"contract" mapstructure:"contract"` + TestSuite TestSuite `json:"testSuite" yaml:"testSuite" mapstructure:"testSuite"` + + InCi bool `json:"inCi" yaml:"inCi" mapstructure:"inCi"` + InstallationID string `json:"-" yaml:"-" mapstructure:"-"` + Version string `json:"-" yaml:"-" mapstructure:"-"` + APIServerURL string `json:"-" yaml:"-" mapstructure:"-"` + GitHubClientID string `json:"-" yaml:"-" mapstructure:"-"` +} + +type TestSuite struct { + TSPath string `json:"tsPath" yaml:"tsPath" mapstructure:"tsPath"` + TSFile string `json:"tsFile" yaml:"tsFile" mapstructure:"tsFile"` + BaseURL string `json:"baseUrl" yaml:"baseUrl" mapstructure:"baseUrl"` +} + +type UtGen struct { + SourceFilePath string `json:"sourceFilePath" yaml:"sourceFilePath" mapstructure:"sourceFilePath"` + TestFilePath string `json:"testFilePath" yaml:"testFilePath" mapstructure:"testFilePath"` + CoverageReportPath string `json:"coverageReportPath" yaml:"coverageReportPath" mapstructure:"coverageReportPath"` + TestCommand string `json:"testCommand" yaml:"testCommand" mapstructure:"testCommand"` + CoverageFormat string `json:"coverageFormat" yaml:"coverageFormat" mapstructure:"coverageFormat"` + DesiredCoverage float64 `json:"expectedCoverage" yaml:"expectedCoverage" mapstructure:"expectedCoverage"` + MaxIterations int `json:"maxIterations" yaml:"maxIterations" mapstructure:"maxIterations"` + TestDir string `json:"testDir" yaml:"testDir" mapstructure:"testDir"` + APIBaseURL string `json:"llmBaseUrl" yaml:"llmBaseUrl" mapstructure:"llmBaseUrl"` + Model string `json:"model" yaml:"model" mapstructure:"model"` + APIVersion string `json:"llmApiVersion" yaml:"llmApiVersion" mapstructure:"llmApiVersion"` + AdditionalPrompt string `json:"additionalPrompt" yaml:"additionalPrompt" mapstructure:"additionalPrompt"` + FunctionUnderTest string `json:"functionUnderTest" yaml:"-" mapstructure:"functionUnderTest"` + Flakiness bool `json:"flakiness" yaml:"flakiness" mapstructure:"flakiness"` +} +type Templatize struct { + TestSets []string `json:"testSets" yaml:"testSets" mapstructure:"testSets"` +} + +type Record struct { + Filters []Filter `json:"filters" yaml:"filters" mapstructure:"filters"` + BasePath string `json:"basePath" yaml:"basePath" mapstructure:"basePath"` + RecordTimer time.Duration `json:"recordTimer" yaml:"recordTimer" mapstructure:"recordTimer"` + Metadata string `json:"metadata" yaml:"metadata" mapstructure:"metadata"` +} + +type ReRecord struct { + SelectedTests []string `json:"selectedTests" yaml:"selectedTests" mapstructure:"selectedTests"` + Filters []Filter `json:"filters" yaml:"filters" mapstructure:"filters"` + Host string `json:"host" yaml:"host" mapstructure:"host"` + Port uint32 `json:"port" yaml:"port" mapstructure:"port"` +} +type Contract struct { + Services []string `json:"services" yaml:"services" mapstructure:"services"` + Tests []string `json:"tests" yaml:"tests" mapstructure:"tests"` + Path string `json:"path" yaml:"path" mapstructure:"path"` + Download bool `json:"download" yaml:"download" mapstructure:"download"` + Generate bool `json:"generate" yaml:"generate" mapstructure:"generate"` + Driven string `json:"driven" yaml:"driven" mapstructure:"driven"` + Mappings Mappings `json:"mappings" yaml:"mappings" mapstructure:"mappings"` +} +type Mappings struct { + ServicesMapping map[string][]string `json:"servicesMapping" yaml:"servicesMapping" mapstructure:"servicesMapping"` + Self string `json:"self" yaml:"self" mapstructure:"self"` +} + +type Normalize struct { + SelectedTests []SelectedTests `json:"selectedTests" yaml:"selectedTests" mapstructure:"selectedTests"` + TestRun string `json:"testReport" yaml:"testReport" mapstructure:"testReport"` +} + +type BypassRule struct { + Path string `json:"path" yaml:"path" mapstructure:"path"` + Host string `json:"host" yaml:"host" mapstructure:"host"` + Port uint `json:"port" yaml:"port" mapstructure:"port"` +} +type MatchType string + +const ( + OR MatchType = "OR" + AND MatchType = "AND" +) + +type Filter struct { + BypassRule `mapstructure:",squash"` + URLMethods []string `json:"urlMethods" yaml:"urlMethods" mapstructure:"urlMethods"` + Headers map[string]string `json:"headers" yaml:"headers" mapstructure:"headers"` + MatchType MatchType `json:"matchType"` +} +type Test struct { + SelectedTests map[string][]string `json:"selectedTests" yaml:"selectedTests" mapstructure:"selectedTests"` + GlobalNoise Globalnoise `json:"globalNoise" yaml:"globalNoise" mapstructure:"globalNoise"` + Delay uint64 `json:"delay" yaml:"delay" mapstructure:"delay"` + Host string `json:"host" yaml:"host" mapstructure:"host"` + Port uint32 `json:"port" yaml:"port" mapstructure:"port"` + APITimeout uint64 `json:"apiTimeout" yaml:"apiTimeout" mapstructure:"apiTimeout"` + SkipCoverage bool `json:"skipCoverage" yaml:"skipCoverage" mapstructure:"skipCoverage"` // boolean to capture the coverage in test + CoverageReportPath string `json:"coverageReportPath" yaml:"coverageReportPath" mapstructure:"coverageReportPath"` // directory path to store the coverage files + IgnoreOrdering bool `json:"ignoreOrdering" yaml:"ignoreOrdering" mapstructure:"ignoreOrdering"` + MongoPassword string `json:"mongoPassword" yaml:"mongoPassword" mapstructure:"mongoPassword"` + Language Language `json:"language" yaml:"language" mapstructure:"language"` + RemoveUnusedMocks bool `json:"removeUnusedMocks" yaml:"removeUnusedMocks" mapstructure:"removeUnusedMocks"` + FallBackOnMiss bool `json:"fallBackOnMiss" yaml:"fallBackOnMiss" mapstructure:"fallBackOnMiss"` + JacocoAgentPath string `json:"jacocoAgentPath" yaml:"jacocoAgentPath" mapstructure:"jacocoAgentPath"` + BasePath string `json:"basePath" yaml:"basePath" mapstructure:"basePath"` + Mocking bool `json:"mocking" yaml:"mocking" mapstructure:"mocking"` + IgnoredTests map[string][]string `json:"ignoredTests" yaml:"ignoredTests" mapstructure:"ignoredTests"` + DisableLineCoverage bool `json:"disableLineCoverage" yaml:"disableLineCoverage" mapstructure:"disableLineCoverage"` + DisableMockUpload bool `json:"disableMockUpload" yaml:"disableMockUpload" mapstructure:"disableMockUpload"` + UseLocalMock bool `json:"useLocalMock" yaml:"useLocalMock" mapstructure:"useLocalMock"` + UpdateTemplate bool `json:"updateTemplate" yaml:"updateTemplate" mapstructure:"updateTemplate"` + MustPass bool `json:"mustPass" yaml:"mustPass" mapstructure:"mustPass"` + MaxFailAttempts uint32 `json:"maxFailAttempts" yaml:"maxFailAttempts" mapstructure:"maxFailAttempts"` + MaxFlakyChecks uint32 `json:"maxFlakyChecks" yaml:"maxFlakyChecks" mapstructure:"maxFlakyChecks"` +} + +type Report struct { + SelectedTestSets map[string][]string `json:"selectedTestSets" yaml:"selectedTestSets" mapstructure:"selectedTestSets"` +} + +type Language string + +// String is used both by fmt.Print and by Cobra in help text +func (e *Language) String() string { + return string(*e) +} + +// Set must have pointer receiver so it doesn't change the value of a copy +func (e *Language) Set(v string) error { + switch v { + case "go", "java", "python", "javascript": + *e = Language(v) + return nil + default: + return errors.New(`must be one of "go", "java", "python" or "javascript"`) + } +} + +// Type is only used in help text +func (e *Language) Type() string { + return "myEnum" +} + +type Globalnoise struct { + Global GlobalNoise `json:"global" yaml:"global" mapstructure:"global"` + Testsets TestsetNoise `json:"test-sets" yaml:"test-sets" mapstructure:"test-sets"` +} + +type SelectedTests struct { + TestSet string `json:"testSet" yaml:"testSet" mapstructure:"testSet"` + Tests []string `json:"tests" yaml:"tests" mapstructure:"tests"` +} + +type ( + Noise map[string][]string + GlobalNoise map[string]map[string][]string + TestsetNoise map[string]map[string]map[string][]string +) + +func SetByPassPorts(conf *Config, ports []uint) { + for _, port := range ports { + conf.BypassRules = append(conf.BypassRules, BypassRule{ + Path: "", + Host: "", + Port: port, + }) + } +} + +func GetByPassPorts(conf *Config) []uint { + var ports []uint + for _, rule := range conf.BypassRules { + ports = append(ports, rule.Port) + } + return ports +} + +func SetSelectedTests(conf *Config, testSets []string) { + conf.Test.SelectedTests = make(map[string][]string) + for _, testSet := range testSets { + conf.Test.SelectedTests[testSet] = []string{} + } +} +func SetSelectedServices(conf *Config, services []string) { + // string is "s1,s2" so i want to get s1,s2 + conf.Contract.Services = services +} +func SetSelectedContractTests(conf *Config, tests []string) { + + conf.Contract.Tests = tests +} + +func SetSelectedTestSets(conf *Config, testSets []string) { + conf.Report.SelectedTestSets = make(map[string][]string) + for _, testSet := range testSets { + conf.Report.SelectedTestSets[testSet] = []string{} + } +} + +func SetSelectedTestsNormalize(conf *Config, value string) error { + testSets := strings.FieldsFunc(value, func(r rune) bool { + return r == ',' || r == ' ' + }) + var tests []SelectedTests + if len(testSets) == 0 { + conf.Normalize.SelectedTests = tests + return nil + } + for _, ts := range testSets { + parts := strings.Split(ts, ":") + if len(parts) != 2 { + return fmt.Errorf("invalid format: %s", ts) + } + testCases := strings.Split(parts[1], " ") + tests = append(tests, SelectedTests{ + TestSet: parts[0], + Tests: testCases, + }) + } + conf.Normalize.SelectedTests = tests + return nil +} diff --git a/keploy/config/default.go b/keploy/config/default.go new file mode 100644 index 0000000..1d7b0e3 --- /dev/null +++ b/keploy/config/default.go @@ -0,0 +1,134 @@ +package config + +import ( + yaml3 "gopkg.in/yaml.v3" + "sigs.k8s.io/kustomize/kyaml/yaml" + "sigs.k8s.io/kustomize/kyaml/yaml/merge2" + "sigs.k8s.io/kustomize/kyaml/yaml/walk" +) + +// defaultConfig is a variable to store the default configuration of the Keploy CLI. It is not a constant because enterprise need update the default configuration. +var defaultConfig = ` +path: "" +appId: 0 +appName: "" +command: "" +templatize: + testSets: [] +port: 0 +proxyPort: 16789 +dnsPort: 26789 +debug: false +disableANSI: false +disableTele: false +generateGithubActions: false +containerName: "" +networkName: "" +buildDelay: 30 +test: + selectedTests: {} + ignoredTests: {} + globalNoise: + global: {} + test-sets: {} + delay: 5 + host: "" + port: 0 + apiTimeout: 5 + skipCoverage: false + coverageReportPath: "" + ignoreOrdering: true + mongoPassword: "default@123" + language: "" + removeUnusedMocks: false + fallBackOnMiss: false + jacocoAgentPath: "" + basePath: "" + mocking: true + disableLineCoverage: false + disableMockUpload: true + useLocalMock: false + updateTemplate: false + mustPass: false + maxFailAttempts: 5 + maxFlakyChecks: 1 +record: + recordTimer: 0s + filters: [] +configPath: "" +bypassRules: [] +contract: + driven: "consumer" + mappings: + servicesMapping: {} + self: "s1" + services: [] + tests: [] + path: "" + download: false + generate: false +inCi: false +` + +func GetDefaultConfig() string { + return defaultConfig +} + +func SetDefaultConfig(cfgStr string) { + defaultConfig = cfgStr +} + +const InternalConfig = ` +enableTesting: false +keployContainer: "keploy-v2" +keployNetwork: "keploy-network" +inDocker: false +cmdType: "native" +` + +var config = &Config{} + +func New() *Config { + // merge default config with internal config + mergedConfig, err := Merge(defaultConfig, InternalConfig) + if err != nil { + panic(err) + } + err = yaml3.Unmarshal([]byte(mergedConfig), config) + if err != nil { + panic(err) + } + return config +} + +func Merge(srcStr, destStr string) (string, error) { + return mergeStrings(srcStr, destStr, false, yaml.MergeOptions{}) +} + +// Reference: https://github.com/kubernetes-sigs/kustomize/blob/537c4fa5c2bf3292b273876f50c62ce1c81714d7/kyaml/yaml/merge2/merge2.go#L24 +// VisitKeysAsScalars is set to true to enable merging comments. +// inferAssociativeLists is set to fasle to disable merging associative lists. +func mergeStrings(srcStr, destStr string, infer bool, mergeOptions yaml.MergeOptions) (string, error) { + src, err := yaml.Parse(srcStr) + if err != nil { + return "", err + } + + dest, err := yaml.Parse(destStr) + if err != nil { + return "", err + } + + result, err := walk.Walker{ + Sources: []*yaml.RNode{dest, src}, + Visitor: merge2.Merger{}, + InferAssociativeLists: infer, + VisitKeysAsScalars: true, + MergeOptions: mergeOptions, + }.Walk() + if err != nil { + return "", err + } + + return result.String() +} diff --git a/keploy/entrypoint.sh b/keploy/entrypoint.sh new file mode 100755 index 0000000..532f13f --- /dev/null +++ b/keploy/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +if ! mountpoint -q /sys/kernel/debug; then + sudo mount -t debugfs debugfs /sys/kernel/debug +fi + +sudo -E "$@" diff --git a/keploy/go.mod b/keploy/go.mod new file mode 100755 index 0000000..1e3898a --- /dev/null +++ b/keploy/go.mod @@ -0,0 +1,159 @@ +module go.keploy.io/server/v2 + +go 1.24.4 + +toolchain go1.24.6 + +replace github.com/jackc/pgproto3/v2 => github.com/keploy/pgproto3/v2 v2.0.5 + +require ( + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cilium/ebpf v0.19.0 + github.com/cloudflare/cfssl v1.6.4 + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v24.0.4+incompatible + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/fatih/color v1.18.0 + github.com/k0kubun/pp/v3 v3.2.0 + github.com/miekg/dns v1.1.57 + github.com/morikuni/aec v1.0.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/spf13/cobra v1.9.1 + go.mongodb.org/mongo-driver v1.11.6 + go.uber.org/zap v1.27.0 + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/sys v0.33.0 + google.golang.org/protobuf v1.36.5 +) + +require ( + github.com/containerd/log v0.1.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/getkin/kin-openapi v0.126.0 + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/google/certificate-transparency-go v1.2.1 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/invopop/yaml v0.3.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/magiconair/properties v1.8.9 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) + +require ( + gopkg.in/yaml.v3 v3.0.1 + gotest.tools/v3 v3.5.0 // indirect +) + +require ( + github.com/go-logr/logr v1.4.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.0 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9 + github.com/rivo/uniseg v0.4.7 // indirect + github.com/spf13/pflag v1.0.6 + github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b // indirect + github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc // indirect + github.com/zmap/zlint/v3 v3.1.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/net v0.40.0 + golang.org/x/text v0.26.0 + golang.org/x/tools v0.33.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect +) + +require ( + facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb + github.com/7sDream/geko v0.1.1 + github.com/agnivade/levenshtein v1.1.1 + github.com/andybalholm/brotli v1.2.0 + github.com/charmbracelet/glamour v0.6.0 + github.com/denisbrodbeck/machineid v1.0.1 + github.com/emirpasic/gods v1.18.1 + github.com/getsentry/sentry-go v0.28.1 + github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/google/uuid v1.6.0 + github.com/jackc/pgproto3/v2 v2.3.2 + github.com/keploy/jsonDiff v1.0.8 + github.com/shirou/gopsutil/v3 v3.24.3 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.10.0 + github.com/wI2L/jsondiff v0.5.0 + github.com/xdg-go/pbkdf2 v1.0.0 + github.com/xdg-go/scram v1.1.1 + github.com/xdg-go/stringprep v1.0.4 + golang.org/x/sync v0.15.0 + golang.org/x/term v0.32.0 + google.golang.org/grpc v1.71.0 + gopkg.in/yaml.v2 v2.4.0 + helm.sh/helm/v3 v3.18.3 + sigs.k8s.io/kustomize/kyaml v0.19.0 + vitess.io/vitess v0.22.1 +) + +require github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + +require ( + github.com/golang/glog v1.2.4 // indirect + github.com/gorilla/mux v1.8.1 + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20241121165744-79df5c4772f2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/stretchr/objx v0.5.2 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/time v0.12.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect +) + +require ( + github.com/alecthomas/chroma v0.10.0 // indirect + github.com/aymanbagabas/go-osc52 v1.0.3 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect + github.com/gorilla/css v1.0.1 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect + github.com/moby/moby v26.0.2+incompatible + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.13.0 // indirect + github.com/yuin/goldmark v1.5.2 // indirect + github.com/yuin/goldmark-emoji v1.0.1 // indirect +) diff --git a/keploy/go.sum b/keploy/go.sum new file mode 100644 index 0000000..b0b510c --- /dev/null +++ b/keploy/go.sum @@ -0,0 +1,470 @@ +facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:1pSweJFeR3Pqx7uoelppkzeegfUBXL6I2FFAbfXw570= +facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:npRYmtaITVom7rcSo+pRURltHSG2r4TQM1cdqJ2dUB0= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/7sDream/geko v0.1.1 h1:Ms9RVcUJDPsUuRlk3T3sGmSGAMuttqh2/3okZ4yWUpU= +github.com/7sDream/geko v0.1.1/go.mod h1:NcSQXSUpcoqNP8BkOLP2TsinxRVjnMkDYCKl9L1Czxk= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= +github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= +github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao= +github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY= +github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8= +github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= +github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.4+incompatible h1:s/LVDftw9hjblvqIeTiGYXBCD95nOEEl7qRsRrIOuQI= +github.com/docker/docker v24.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/getkin/kin-openapi v0.126.0 h1:c2cSgLnAsS0xYfKsgt5oBV6MYRM/giU8/RtwUY4wyfY= +github.com/getkin/kin-openapi v0.126.0/go.mod h1:7mONz8IwmSRg6RttPu6v8U/OJ+gr+J99qSFNjPGSQqw= +github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= +github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/certificate-transparency-go v1.2.1 h1:4iW/NwzqOqYEEoCBEFP+jPbBXbLqMpq3CifMyOnDUME= +github.com/google/certificate-transparency-go v1.2.1/go.mod h1:bvn/ytAccv+I6+DGkqpvSsEdiVGramgaSC6RD3tEmeE= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= +github.com/jackc/chunkreader/v2 v2.0.0 h1:DUwgMQuuPnS0rhMXenUtZpqZqrR/30NWY+qQvTpSvEs= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= +github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= +github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= +github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= +github.com/keploy/jsonDiff v1.0.8 h1:B/75cfLNZ7kayAjF1Jox70Iwa/nwCjBYNOuSt+RHH5A= +github.com/keploy/jsonDiff v1.0.8/go.mod h1:wUuLbVs3Oe3mIQ61C7G88bppP//ArLtoDU0S9Awwv+s= +github.com/keploy/pgproto3/v2 v2.0.5 h1:8spdNKZ+nOnHVxiimDsqulBRN6viPXPghkA7xppnzJ8= +github.com/keploy/pgproto3/v2 v2.0.5/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/moby v26.0.2+incompatible h1:t41TD3nRvK8E6bZFJdKrmNlH8Xe3epTmdNXf/mnfLKk= +github.com/moby/moby v26.0.2+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20241121165744-79df5c4772f2 h1:1sLMdKq4gNANTj0dUibycTLzpIEKVnLnbaEkxws78nw= +github.com/planetscale/vtprotobuf v0.6.1-0.20241121165744-79df5c4772f2/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9 h1:arwj11zP0yJIxIRiDn22E0H8PxfF7TsTrc2wIPFIsf4= +github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9/go.mod h1:SKZx6stCn03JN3BOWTwvVIO2ajMkb/zQdTceXYhKw/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= +github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/wI2L/jsondiff v0.5.0 h1:RRMTi/mH+R2aXcPe1VYyvGINJqQfC3R+KSEakuU1Ikw= +github.com/wI2L/jsondiff v0.5.0/go.mod h1:qqG6hnK0Lsrz2BpIVCxWiK9ItsBCpIZQiv0izJjOZ9s= +github.com/weppos/publicsuffix-go v0.13.1-0.20210123135404-5fd73613514e/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= +github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b h1:FsyNrX12e5BkplJq7wKOLk0+C6LZ+KGXvuEcKUYm5ss= +github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= +github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= +github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= +github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= +github.com/zmap/zcrypto v0.0.0-20210123152837-9cf5beac6d91/go.mod h1:R/deQh6+tSWlgI9tb4jNmXxn8nSCabl5ZQsBX9//I/E= +github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc h1:zkGwegkOW709y0oiAraH/3D8njopUR/pARHv4tZZ6pw= +github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc/go.mod h1:FM4U1E3NzlNMRnSUTU3P1UdukWhYGifqEsjk9fn7BCk= +github.com/zmap/zlint/v3 v3.1.0 h1:WjVytZo79m/L1+/Mlphl09WBob6YTGljN5IGWZFpAv0= +github.com/zmap/zlint/v3 v3.1.0/go.mod h1:L7t8s3sEKkb0A2BxGy1IWrxt1ZATa1R4QfJZaQOD3zU= +go.mongodb.org/mongo-driver v1.11.6 h1:XM7G6PjiGAO5betLF13BIa5TlLUUE3uJ/2Ox3Lz1K+o= +go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +helm.sh/helm/v3 v3.18.3 h1:+cvyGKgs7Jt7BN3Klmb4SsG4IkVpA7GAZVGvMz6VO4I= +helm.sh/helm/v3 v3.18.3/go.mod h1:wUc4n3txYBocM7S9RjTeZBN9T/b5MjffpcSsWEjSIpw= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= +sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +vitess.io/vitess v0.22.1 h1:nCA0v6tt3YVPf8qWMQJktzxZlsLYwdPxplo2TbSEm9A= +vitess.io/vitess v0.22.1/go.mod h1:JF7nQQ+XUP7og7ZNzOw0p+zKvUTwYd6TTKfFyzyfE9o= diff --git a/keploy/gon.json b/keploy/gon.json new file mode 100755 index 0000000..29d89bd --- /dev/null +++ b/keploy/gon.json @@ -0,0 +1,13 @@ +{ + "source" : ["./dist/keploy-mac-universal_darwin_all/keploy"], + "bundle_id" : "io.keploy.server", + "apple_id": { + "username" : "shubhamkjain@outlook.com" + }, + "sign" :{ + "application_identity" : "Developer ID Application: Shubham Jain" + }, + "zip" :{ + "output_path" : "keploy.zip" + } +} \ No newline at end of file diff --git a/keploy/goreleaser.yaml b/keploy/goreleaser.yaml new file mode 100755 index 0000000..869d254 --- /dev/null +++ b/keploy/goreleaser.yaml @@ -0,0 +1,60 @@ +# GoReleaser configuration +archives: + - + name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" + +builds: + - binary: keploy + id: keploy + main: ./main.go + ldflags: + - -s -w -X main.dsn={{.Env.SENTRY_DSN_BINARY}} + - -s -w -X main.version={{.Version}} + - -s -w -X main.apiServerURI={{.Env.SERVER_URL}} + - -s -w -X main.gitHubClientID={{.Env.GITTHUB_APP_CLIENT_ID}} + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + tags: + - viper_bind_struct + + # - binary: keploy + # id: keploy-macos + # main: ./cli/server/main.go + # env: + # - CGO_ENABLED=0 + # goos: + # - darwin + # goarch: + # - amd64 + # - arm64 + +universal_binaries: +- + # ID of resulting universal binary. + # + # Defaults to the project name. + id: keploy-mac-universal + + # IDs to use to filter the built binaries. + # + # Defaults to the `id` field. + # Since: v1.3. + ids: + - keploy + + # Whether to remove the previous single-arch binaries from the artifact list. + # If left as false, your end release might have both several macOS archives: + # amd64, arm64 and all. + # + # Defaults to false. + replace: true + hooks: + post: gon -log-level=info -log-json ./gon.json + diff --git a/keploy/gsoc25_performance&security-testing.md b/keploy/gsoc25_performance&security-testing.md new file mode 100644 index 0000000..b7145a3 --- /dev/null +++ b/keploy/gsoc25_performance&security-testing.md @@ -0,0 +1,558 @@ +# Keploy GSoC'25_Performance&Security Features Documentation + +This document provides a comprehensive overview of the three advanced features implemented in Keploy: **TestSuite Execution**, **Load Testing**, and **Security Checking**. + +## Table of Contents +1. [Overview](#overview) +2. [TestSuite Feature](#testsuite-feature) +3. [Load Testing Feature](#load-testing-feature) +4. [Security Checking Feature](#security-checking-feature) +5. [Integration and Architecture](#integration-and-architecture) +6. [Usage Examples](#usage-examples) +7. [Configuration](#configuration) + +--- + +## Overview + +These three features work together to provide a comprehensive testing solution: + +- **TestSuite**: Core execution engine for running API test suites defined in YAML +- **Load Testing**: Performance testing capability that executes test suites under load +- **Security Checking**: Security vulnerability scanning for API endpoints + +All features are integrated into the Keploy CLI and share common configuration patterns and data structures. + +--- + +## TestSuite Feature + +### Purpose +The TestSuite feature provides a declarative way to define and execute API test sequences using YAML configuration files. It serves as the foundation for both load testing and security checking. + +### Architecture + +#### Core Components + +1. **TSExecutor** (`pkg/service/testsuite/testsuite.go`) + - Main execution engine for test suites + - Handles HTTP requests, response validation, and variable extraction + - Supports rate limiting for controlled execution + +2. **TestSuite Data Structures** + ```go + type TestSuite struct { + Version string `yaml:"version"` + Kind string `yaml:"kind"` + Name string `yaml:"name"` + Spec TestSuiteSpec `yaml:"spec"` + } + + type TestSuiteSpec struct { + Metadata TestSuiteMetadata `yaml:"metadata"` + Security Security `yaml:"security,omitempty"` + Load LoadOptions `yaml:"load,omitempty"` + Steps []TestStep `yaml:"steps"` + } + ``` + +3. **TestStep Execution** + - Each step represents an HTTP request with assertions + - Supports variable extraction and interpolation + - Provides detailed execution results + +#### Key Features + +- **Variable Extraction**: Extract values from responses using JSON path notation +- **Variable Interpolation**: Use extracted variables in subsequent requests +- **Assertions**: Validate response status codes, headers, and body content +- **Rate Limiting**: Control request rate for load testing scenarios +- **Detailed Reporting**: Comprehensive execution reports with timing and error information + +### Implementation Details + +#### CLI Integration (`cli/testsuite.go`) +```go +func TestSuite(ctx context.Context, logger *zap.Logger, _ *config.Config, + serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "testsuite", + Short: "execute a testsuite against a given url (--base-url)", + Example: `keploy testsuite --base-url "http://localhost:8080/path/to/user/app"`, + RunE: func(cmd *cobra.Command, args []string) error { + // Service factory pattern for dependency injection + svc, err := serviceFactory.GetService(ctx, cmd.Name()) + if err != nil { + utils.LogError(logger, err, "failed to get service") + return nil + } + + // Execute the test suite + _, err = tsSvc.Execute(ctx, nil) + return err + }, + } +} +``` + +#### Service Factory Integration (`cli/provider/service.go`) +The service factory provides dependency injection and service instantiation: +```go +func (n *ServiceProvider) GetService(ctx context.Context, cmd string) (interface{}, error) { + switch cmd { + case "testsuite": + return testsuite.NewTSExecutor(n.cfg, n.logger, false) + // ... other services + } +} +``` + +### Usage +```bash +# Execute a test suite +keploy testsuite --base-url "http://localhost:8080" --ts-path ./keploy/testsuite --ts-file suite-0.yaml + +# Or simply passing the base URL keeping the defalut value. +keploy testsuite --base-url "http://localhost:8080" +``` + +--- + +## Load Testing Feature + +### Purpose +The Load Testing feature enables performance testing by executing test suites under various load profiles. It provides comprehensive metrics collection and threshold-based pass/fail criteria. + +### Architecture + +#### Core Components + +1. **LoadTester** (`pkg/service/load/load.go`) + - Main coordinator for load test execution + - Manages test suite parsing and load configuration + - Generates comprehensive load test reports + +2. **Scheduler** (`pkg/service/load/scheduler.go`) + - Orchestrates virtual user (VU) execution + - Supports multiple load profiles (constant, ramping) + - Manages timing and synchronization + +3. **VUWorker** (`pkg/service/load/vu_worker.go`) + - Individual virtual user implementation + - Executes test suite repeatedly under load + - Collects per-VU metrics and timing data + +4. **MetricsCollector** (`pkg/service/load/metrics_collector.go`) + - Aggregates metrics from all virtual users + - Provides centralized data collection point + - Calculates aggregate statistics + +5. **ThresholdEvaluator** (`pkg/service/load/threshold_evaluator.go`) + - Evaluates performance thresholds + - Determines pass/fail status for load tests + - Supports various metric types and conditions + +#### Load Profiles + +1. **Constant VUs Profile** + - Maintains constant number of virtual users + - Consistent load throughout test duration + ```yaml + load: + profile: constant_vus + vus: 10 + duration: 5m + rps: 50 + ``` + +2. **Ramping VUs Profile** + - Gradually increases/decreases virtual users + - Supports multiple stages with different targets + ```yaml + load: + profile: ramping_vus + vus: 30 + duration: 3m + stages: + - duration: 30s + target: 10 + - duration: 2m + target: 30 + ``` + +#### Metrics and Thresholds + +The system collects comprehensive metrics: +- **Request metrics**: Count, failure rate, response times +- **Performance metrics**: P90,95,99 latency, throughput +- **Data transfer**: Bytes sent/received + +Thresholds provide pass/fail criteria: +```yaml +thresholds: + - metric: http_req_duration_p95 + condition: "< 500ms" + severity: high + comment: "Ensure 95% of requests are below 500ms latency" + - metric: http_req_failed_rate + condition: "<= 1%" + severity: critical + comment: "Error rate must stay under 1%" +``` + +### Implementation Details + +#### CLI Integration (`cli/load.go`) +```go +func Load(ctx context.Context, logger *zap.Logger, _ *config.Config, + serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { + var cmd = &cobra.Command{ + Use: "load", + Short: "load test a given testsuite.", + Example: `keploy load -f test_suite.yaml --out json > report.json`, + RunE: func(cmd *cobra.Command, args []string) error { + // CLI parameter extraction + vus, _ := cmd.Flags().GetInt("vus") + duration, _ := cmd.Flags().GetString("duration") + rps, _ := cmd.Flags().GetInt("rps") + + // Context with CLI overrides + ctx := context.WithValue(ctx, "vus", vus) + ctx = context.WithValue(ctx, "duration", duration) + ctx = context.WithValue(ctx, "rps", rps) + + return ltSvc.Start(ctx) + }, + } +} +``` + +#### Integrated Dashboard +- **Built-in Web Server**: Embedded dashboard server integrated directly into Keploy binary +- **Auto-Browser Launch**: Automatically opens browser to dashboard URL when load test starts +- **DashboardExposer** (`pkg/service/load/dashboard_exposer.go`): + - Serves embedded React dashboard from Go binary + - Provides real-time metrics API endpoints +- **Exporter** (`pkg/service/load/exporter.go`): Real-time metrics streaming to dashboard +- **Live Monitoring**: Real-time updates of VU count, RPS, response times, and threshold status +- **Zero Configuration**: No external dependencies or separate dashboard setup required + +#### Dashboard Architecture + +The dashboard system is fully integrated into the Keploy binary using Go's embed feature: + +```go +//go:embed out/* +var content embed.FS + +func (de *DashboardExposer) fileSystem() http.FileSystem { + fsys, err := fs.Sub(content, "out") + return http.FS(fsys) +} +``` + +**Key Features:** +- **Embedded Static Files**: Frontend assets bundled into Go binary at compile time +- **Smart Browser Detection**: Cross-platform browser launching with WSL support + +### Advanced Features + +1. **Rate Limiting**: Precise RPS control using token bucket algorithm +2. **Integrated Dashboard**: Built-in web dashboard that automatically launches in browser + - **Embedded Server**: Dashboard server runs within Keploy binary (no external dependencies) + - **Auto-Launch**: Browser automatically opens to `http://localhost:3000` when load test starts + - **Real-time Monitoring**: Live metrics updates including VU count, RPS, response times + - **Threshold Visualization**: Real-time pass/fail status of performance thresholds +3. **Report Generation**: JSON and CLI-formatted comprehensive reports +4. **CLI Overrides**: Command-line parameters override YAML configuration +5. **Threshold Evaluation**: Automated pass/fail determination with live status updates + +### Usage +```bash +# Basic load test (automatically opens dashboard in browser) +keploy load -f ./keploy/suite-0.yaml --base-url "http://localhost:8080" + +# With CLI overrides (dashboard launches at http://localhost:3000) +keploy load -f ./keploy/suite-0.yaml --base-url "http://localhost:8080" --vus 20 --duration 10m --rps 100 +``` + +--- + +## Security Checking Feature + +### Purpose +The Security Checking feature provides automated security vulnerability scanning for API endpoints. It executes both built-in and custom security checks against API responses and requests. + +### Architecture + +#### Core Components + +1. **SecurityChecker** (`pkg/service/secure/secure.go`) + - Main security scanning engine + - Manages built-in and custom security checks + - Executes checks against test suite results + +2. **Security Checks System** + - Built-in checks for common vulnerabilities + - Custom check support via YAML configuration + - Severity-based filtering and reporting + +3. **AllowList System** + - Filter false positives + - Customizable per header, key, or pattern + - Integration with test suite configuration + +#### Security Check Types + +The system includes various built-in security checks: + +1. **Header Security Checks** + - Missing security headers (HSTS, CSP, X-Frame-Options) + - Insecure header values + - Information disclosure in headers + +2. **Response Body Checks** + - Sensitive data exposure + - Error message leakage + - Debug information disclosure + +3. **Cookie Security** + - Missing secure flags + - HttpOnly flag validation + - SameSite attribute checking + +4. **General Security** + - HTTP vs HTTPS enforcement + - Server information disclosure + - Version information exposure + +#### Custom Security Checks + +Users can define custom security checks: +```yaml +# custom-checks.yaml +- id: custom_header_check + name: Custom Header Validation + description: Check for custom security header + severity: MEDIUM + type: header + target: response + key: X-Custom-Security + operation: exists + status: enabled +``` + +### Implementation Details + +#### CLI Integration (`cli/secure.go`) +The security feature provides multiple subcommands: +- `keploy secure` - Run security checks +- `keploy secure add` - Add custom security check +- `keploy secure remove` - Remove custom security check +- `keploy secure update` - Update custom security check +- `keploy secure list` - List available security checks + +#### Security Check Execution +```go +type SecurityCheck struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Severity string `json:"severity"` // "CRITICAL", "HIGH", "MEDIUM", "LOW" + Type string `json:"type"` // "header", "body", "cookie", "url" + Target string `json:"target"` // "request", "response" + Key string `json:"key"` // Header name, JSON path, etc. + Value string `json:"value,omitempty"` // Expected value or pattern + Operation string `json:"operation"` // "exists", "equals", "contains", "regex" + Status string `json:"status"` // "enabled", "disabled" +} +``` + +#### Integration with TestSuite +Security checks are executed against test suite results: +1. Test suite executes API calls +2. Security checker analyzes requests/responses +3. Checks are applied based on configuration +4. Results are filtered by severity and allowlists +5. Comprehensive security report is generated + +### Usage +```bash +# Run security checks +keploy secure --base-url "http://localhost:8080" + +# List available checks +keploy secure list --rule-set basic + +# Add custom check +keploy secure add + +# Run with custom severity threshold +keploy secure --severity-threshold HIGH +``` + +--- + +## Integration and Architecture + +### Service Factory Pattern + +All three features are integrated through a unified service factory pattern: + +```go +// cli/provider/service.go +func (n *ServiceProvider) GetService(ctx context.Context, cmd string) (interface{}, error) { + switch cmd { + case "secure": + return secure.NewSecurityChecker(n.cfg, n.logger) + case "load": + return load.NewLoadTester(n.cfg, n.logger) + case "testsuite": + return testsuite.NewTSExecutor(n.cfg, n.logger, false) + // ... other services + } +} +``` + +### Shared Configuration + +All features share common configuration patterns: + +```yaml +# keploy.yml +testSuite: + tsPath: keploy/testsuite + tsFile: suite-0.yaml + baseUrl: "" + +load: + vus: 0 + duration: "" + rps: 0 + output: "" + +# Suite configuration (suite-0.yaml) +spec: + security: + ruleset: basic + severity_threshold: MEDIUM + disable: [] + allowlist: + headers: ["Server"] + keys: ["data.debug"] + + load: + profile: constant_vus + vus: 10 + duration: 5m + thresholds: + - metric: http_req_duration_p95 + condition: "< 500ms" + severity: high +``` + +### Cross-Feature Integration + +1. **Load Testing + Security**: Load tests can include security checking +2. **TestSuite Foundation**: Both load and security features build on testsuite execution +3. **Shared Reporting**: Common report formats and output options +4. **Configuration Inheritance**: Features inherit and extend base configurations + +--- + +## Usage Examples + +### Complete Test Suite Example + +```yaml +# suite-0.yaml +version: api.keploy.io/v2beta1 +kind: TestSuite +name: Todo_CRUD_Operations +spec: + metadata: + description: Test CRUD operations for Todo API + + security: + ruleset: basic + severity_threshold: MEDIUM + allowlist: + headers: ["Server"] + keys: ["data.debug"] + + load: + profile: ramping_vus + vus: 30 + duration: 3m + stages: + - duration: 30s + target: 10 + - duration: 2m + target: 30 + thresholds: + - metric: http_req_duration_p95 + condition: "< 500ms" + severity: high + + steps: + - name: Create_todo + method: POST + url: /todos + headers: + Content-Type: application/json + body: | + { + "title": "Test Todo", + "completed": false + } + extract: + todo_id: id + assert: + - type: status_code + expected_string: "201" + + - name: Get_todo + method: GET + url: /todos/{{todo_id}} + assert: + - type: status_code + expected_string: "200" +``` + +### Command Usage Examples + +```bash +# 1. Test functionality +keploy testsuite --base-url "http://localhost:8080" + +# 2. Perform load testing (dashboard launches for real-time monitoring) +keploy load --base-url "http://localhost:8080" + +# 3. Security scanning +keploy secure --base-url "http://localhost:8080" +``` + +--- + +## Configuration + +### Global Configuration (`keploy.yml`) + +```yaml +testSuite: + tsPath: keploy/testsuite # Path to test suite directory + tsFile: suite-0.yaml # Test suite file name + baseUrl: "" # Base URL for API endpoints +``` + +### Feature-Specific Configuration + +Each feature supports extensive configuration through both YAML files and CLI flags, providing flexibility for different testing scenarios and environments. + +--- + +## Conclusion + +These three features provide a comprehensive testing solution that covers functional testing, performance testing, and security scanning. The modular architecture and shared configuration patterns make them easy to use individually or in combination, while the integration with Keploy's existing ecosystem ensures seamless workflow integration. + +The features are designed with extensibility in mind, supporting custom security checks, flexible load profiles, and comprehensive reporting options to meet diverse testing requirements. diff --git a/keploy/keploy.sh b/keploy/keploy.sh new file mode 100644 index 0000000..2fe4f30 --- /dev/null +++ b/keploy/keploy.sh @@ -0,0 +1,295 @@ +#!/bin/bash + +installKeploy (){ + version="latest" + IS_CI=false + NO_ROOT=false + PLATFORM="$(basename "$SHELL")" + for arg in "$@" + do + case $arg in + -isCI) + IS_CI=true + shift + ;; + -v) + if [[ "$2" =~ ^v[0-9]+.* ]]; then + version="$2" + shift 2 + else + echo "Invalid version format. Please use '-v v'." + return 1 + fi + ;; + -noRoot) + NO_ROOT=true + shift 1 + ;; + -platform) + PLATFORM="$2" + shift 2 + ;; + *) + ;; + esac + done + if [ "$version" != "latest" ]; then + echo "Installing Keploy version: $version......" + fi + + move_keploy_binary() { + # Check if NO_ROOT is set to true + if [ "$NO_ROOT" = "true" ]; then + # Move without sudo + target_dir="$HOME/.keploy/bin" + source_dir="/tmp/keploy" # Default source directory + + # Create the target directory in the user's home directory + mkdir -p "$target_dir" + if [ $? -ne 0 ]; then + echo "Error: Failed to create directory $target_dir" + exit 1 + fi + + # Check if the OS is macOS (Darwin) to set the correct source path + OS_NAME=$(uname) # Get the operating system name + if [ "$OS_NAME" = "Darwin" ]; then + source_dir="/tmp/keploy/keploy" # Set source directory to the binary inside the extracted folder + fi + + # Move the keploy binary to the user's home directory bin + if [ -f "$source_dir" ]; then + mv "$source_dir" "$target_dir/keploy" + if [ $? -ne 0 ]; then + echo "Error: Failed to move the keploy binary from $source_dir to $target_dir" + exit 1 + fi + else + echo "Error: $source_dir does not exist." + exit 1 + fi + + # Make sure the binary is executable + chmod +x "$target_dir/keploy" + if [ $? -ne 0 ]; then + echo "Error: Failed to make the keploy binary executable" + exit 1 + fi + else + source_dir="/tmp/keploy" + OS_NAME=$(uname) # Get the operating system name + if [ "$OS_NAME" = "Darwin" ]; then + source_dir="/tmp/keploy/keploy" # Set source directory to the binary inside the extracted folder + fi + sudo mkdir -p /usr/local/bin && sudo mv "$source_dir" /usr/local/bin/keploy + fi + set_alias + } + + install_keploy_darwin_all() { + if [ "$version" != "latest" ]; then + download_url="https://github.com/keploy/keploy/releases/download/$version/keploy_darwin_all.tar.gz" + else + download_url="https://github.com/keploy/keploy/releases/latest/download/keploy_darwin_all.tar.gz" + fi + # macOS tar does not support --overwrite option so we need to remove the directory first + # to avoid the "File exists" error + rm -rf /tmp/keploy + mkdir -p /tmp/keploy + curl --silent --location "$download_url" | tar xz -C /tmp/keploy/ + move_keploy_binary + delete_keploy_alias + } + + install_keploy_arm() { + if [ "$version" != "latest" ]; then + download_url="https://github.com/keploy/keploy/releases/download/$version/keploy_linux_arm64.tar.gz" + else + download_url="https://github.com/keploy/keploy/releases/latest/download/keploy_linux_arm64.tar.gz" + fi + curl --silent --location "$download_url" | tar xz --overwrite -C /tmp + move_keploy_binary + } + + + install_keploy_amd() { + if [ "$version" != "latest" ]; then + download_url="https://github.com/keploy/keploy/releases/download/$version/keploy_linux_amd64.tar.gz" + else + download_url="https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" + fi + curl --silent --location "$download_url" | tar xz --overwrite -C /tmp + move_keploy_binary + } + + append_to_rc() { + last_byte=$(tail -c 1 $2) + if [[ "$last_byte" != "" ]]; then + echo -e "\n$1" >> $2 + else + echo "$1" >> $2 + fi + source $2 + } + + update_path() { + PATH_CMD="export PATH=\"\$HOME/.keploy/bin:\$PATH\"" + rc_file="$1" + if [ -f "$rc_file" ]; then + if ! grep -q "$PATH_CMD" "$rc_file"; then + append_to_rc "$PATH_CMD" "$rc_file" + fi + else + export PATH="$PATH_CMD" + fi + } + + # Get the alias to set and set it + set_alias() { + current_shell="$PLATFORM" + if [ "$NO_ROOT" = "true" ]; then + # Just update the PATH in .zshrc or .bashrc, no alias needed + if [[ "$current_shell" = "zsh" || "$current_shell" = "-zsh" ]]; then + update_path "$HOME/.zshrc" + elif [[ "$current_shell" = "bash" || "$current_shell" = "-bash" ]]; then + update_path "$HOME/.bashrc" + else + update_path "$HOME/.profile" + fi + else + ALIAS_CMD="alias keploy='sudo -E env PATH=\"\$PATH\" keploy'" + # Handle zsh or bash for non-macOS systems + if [[ "$current_shell" = "zsh" || "$current_shell" = "-zsh" ]]; then + if [ -f "$HOME/.zshrc" ]; then + if grep -q "alias keploy=" "$HOME/.zshrc"; then + sed -i '/alias keploy/d' "$HOME/.zshrc" + fi + append_to_rc "$ALIAS_CMD" ~/.zshrc + else + alias keploy="$ALIAS_CMD" + fi + elif [[ "$current_shell" = "bash" || "$current_shell" = "-bash" ]]; then + if [ -f "$HOME/.bashrc" ]; then + if grep -q "alias keploy=" "$HOME/.bashrc"; then + sed -i '/alias keploy/d' "$HOME/.bashrc" + fi + append_to_rc "$ALIAS_CMD" ~/.bashrc + else + alias keploy="$ALIAS_CMD" + fi + else + if [ -f "$HOME/.profile" ]; then + if grep -q "alias keploy=" "$HOME/.profile"; then + sed -i '/alias keploy/d' "$HOME/.profile" + fi + append_to_rc "$ALIAS_CMD" ~/.profile + else + alias keploy="$ALIAS_CMD" + fi + fi + + fi + + } + + delete_keploy_alias() { + current_shell="$PLATFORM" + shell_rc_file="" + # Determine the shell configuration file based on the current shell + if [[ "$current_shell" = "zsh" || "$current_shell" = "-zsh" ]]; then + shell_rc_file="$HOME/.zshrc" + elif [[ "$current_shell" = "bash" || "$current_shell" = "-bash" ]]; then + shell_rc_file="$HOME/.bashrc" + else + echo "Unsupported shell: $current_shell" + return + fi + # Delete alias from the shell configuration file if it exists + if [ -f "$shell_rc_file" ]; then + if grep -q "alias keploy=" "$shell_rc_file"; then + if [[ "$(uname)" = "Darwin" ]]; then + sed -i '' '/alias keploy/d' "$shell_rc_file" + else + sed -i '/alias keploy/d' "$shell_rc_file" + fi + fi + fi + # Unset the alias in the current shell session if it exists + if alias keploy &>/dev/null; then + unalias keploy + fi + } + + cleanup_tmp() { + # Remove extracted files /tmp directory + tmp_files=("LICENSE" "README.md" "READMEes-Es.md" "README-UnitGen.md") + for file in "${tmp_files[@]}"; do + if [ -f "/tmp/$file" ]; then + if [ "$NO_ROOT" = "true" ]; then + rm -rf "/tmp/$file" + else + sudo rm -rf "/tmp/$file" + fi + + fi + done + } + + ARCH=$(uname -m) + + OS_NAME="$(uname -s)" + if [ "$OS_NAME" = "Darwin" ]; then + NO_ROOT=true + fi + + if [ "$IS_CI" = false ]; then + OS_NAME="$(uname -s)" + if [ "$OS_NAME" = "Darwin" ]; then + cleanup_tmp + install_keploy_darwin_all + return + elif [ "$OS_NAME" = "Linux" ]; then + if [ "$NO_ROOT" = false ]; then + if ! mountpoint -q /sys/kernel/debug; then + sudo mount -t debugfs debugfs /sys/kernel/debug + fi + fi + if [ "$ARCH" = "x86_64" ]; then + cleanup_tmp + install_keploy_amd + elif [ "$ARCH" = "aarch64" ]; then + cleanup_tmp + install_keploy_arm + else + echo "Unsupported architecture: $ARCH" + return + fi + elif [[ "$OS_NAME" == MINGW32_NT* ]]; then + echo "\e]8;; https://pureinfotech.com/install-windows-subsystem-linux-2-windows-10\aWindows not supported please run on WSL2\e]8;;\a" + elif [[ "$OS_NAME" == MINGW64_NT* ]]; then + echo "\e]8;; https://pureinfotech.com/install-windows-subsystem-linux-2-windows-10\aWindows not supported please run on WSL2\e]8;;\a" + else + echo "Unknown OS, install Linux to run Keploy" + fi + else + if [ "$ARCH" = "x86_64" ]; then + cleanup_tmp + install_keploy_amd + elif [ "$ARCH" = "aarch64" ]; then + cleanup_tmp + install_keploy_arm + else + echo "Unsupported architecture: $ARCH" + return + fi + fi +} + +installKeploy "$@" + +if command -v keploy &> /dev/null; then + keploy example + cleanup_tmp + rm -rf keploy.sh + rm -rf install.sh +fi \ No newline at end of file diff --git a/keploy/main.go b/keploy/main.go new file mode 100755 index 0000000..acea761 --- /dev/null +++ b/keploy/main.go @@ -0,0 +1,117 @@ +// Package main is the entry point for the keploy application. +package main + +import ( + "context" + "fmt" + "os" + "strings" + + "go.keploy.io/server/v2/cli" + "go.keploy.io/server/v2/cli/provider" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/platform/auth" + userDb "go.keploy.io/server/v2/pkg/platform/yaml/configdb/user" + "go.keploy.io/server/v2/utils" + "go.keploy.io/server/v2/utils/log" + //pprof for debugging + // "net/http" + // _ "net/http/pprof" +) + +// version is the version of the server and will be injected during build by ldflags, same with dsn +// see https://goreleaser.com/customization/build/ + +var version string +var dsn string +var apiServerURI = "http://localhost:8083" +var gitHubClientID = "Iv23liFBvIVhL29i9BAp" + +func main() { + // Uncomment the following code to enable pprof for debugging + // go func() { + // fmt.Println("Starting pprof server for debugging...") + // err := http.ListenAndServe("localhost:6060", nil) + // if err != nil { + // fmt.Println("Failed to start the pprof server for debugging", err) + // return + // } + // }() + setVersion() + ctx := utils.NewCtx() + start(ctx) + os.Exit(utils.ErrCode) +} + +func setVersion() { + if version == "" { + version = "2-dev" + } + utils.Version = version + utils.VersionIdenitfier = "version" +} + +func start(ctx context.Context) { + logger, logFile, err := log.New() + if err != nil { + fmt.Println("Failed to start the logger for the CLI", err) + return + } + utils.LogFile = logFile + + defer func() { + inDocker := os.Getenv("KEPLOY_INDOCKER") + if inDocker != "true" { + if utils.LogFile != nil { + err := utils.LogFile.Close() + if err != nil { + utils.LogError(logger, err, "Failed to close Keploy Logs") + } + } + if err := utils.DeleteFileIfNotExists(logger, "keploy-logs.txt"); err != nil { + return + } + if err := utils.DeleteFileIfNotExists(logger, "docker-compose-tmp.yaml"); err != nil { + return + } + } + }() + defer utils.Recover(logger) + + // The 'umask' command is commonly used in various operating systems to regulate the permissions of newly created files. + // These 'umask' values subtract from the permissions assigned by the process, effectively lowering the permissions. + // For example, if a file is created with permissions '777' and the 'umask' is '022', the resulting permissions will be '755', + // reducing certain permissions for security purposes. + // Setting 'umask' to '0' ensures that 'keploy' can precisely control the permissions of the files it creates. + // However, it's important to note that this approach may not work in scenarios involving mounted volumes, + // as the 'umask' is set by the host system, and cannot be overridden by 'keploy' or individual processes. + oldMask := utils.SetUmask() + defer utils.RestoreUmask(oldMask) + + if dsn != "" { + utils.SentryInit(logger, dsn) + //logger = utils.ModifyToSentryLogger(ctx, logger, sentry.CurrentHub().Client(), configDb) + } + conf := config.New() + conf.APIServerURL = apiServerURI + conf.GitHubClientID = gitHubClientID + userDb := userDb.New(logger, conf) + conf.InstallationID, err = userDb.GetInstallationID(ctx) + if err != nil { + errMsg := "failed to get installation id" + utils.LogError(logger, err, errMsg) + os.Exit(1) + } + auth := auth.New(conf.APIServerURL, conf.InstallationID, logger, conf.GitHubClientID) + + svcProvider := provider.NewServiceProvider(logger, conf, auth) + cmdConfigurator := provider.NewCmdConfigurator(logger, conf) + rootCmd := cli.Root(ctx, logger, svcProvider, cmdConfigurator) + if err := rootCmd.Execute(); err != nil { + if strings.HasPrefix(err.Error(), "unknown command") || strings.HasPrefix(err.Error(), "unknown shorthand") { + fmt.Println("Error: ", err.Error()) + fmt.Println("Run 'keploy --help' for usage.") + os.Exit(1) + } + } +} diff --git a/keploy/oss-pledge.json b/keploy/oss-pledge.json new file mode 100644 index 0000000..c3d34d1 --- /dev/null +++ b/keploy/oss-pledge.json @@ -0,0 +1,14 @@ +{ + "name": "Keploy Inc", + "description": "Keploy is an open-source, zero-code testing tool that makes API and microservices testing simplify and helps developers boost code coverage. As an open-source project itself, we use a lot of open-source projects and we think it's fit to give it back to community.", + "urlLearnMore": "https://keploy.io/blog/community/introducing-the-keploy-oss-fund", + "urlSquareLogoWithBackground": "https://avatars.githubusercontent.com/u/92252339", + "annualReports": [ + { + "url": "https://keploy.io/blog/community/introducing-the-keploy-oss-fund", + "dateYearEnding": "2024-12-31", + "averageNumberOfDevs": 12, + "payments": 12500 + } + ] +} diff --git a/keploy/package-lock.json b/keploy/package-lock.json new file mode 100644 index 0000000..1ef61ac --- /dev/null +++ b/keploy/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "keploy", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/keploy/pkg/README.md b/keploy/pkg/README.md new file mode 100755 index 0000000..7d0c1a5 --- /dev/null +++ b/keploy/pkg/README.md @@ -0,0 +1,6 @@ +# PKG Documentation + +This package comprises interface methods and Go structs, +which can be utilized by external applications. It's +employed to execute logic and store data for the CLI +commands. \ No newline at end of file diff --git a/keploy/pkg/core/app/app.go b/keploy/pkg/core/app/app.go new file mode 100644 index 0000000..de959c2 --- /dev/null +++ b/keploy/pkg/core/app/app.go @@ -0,0 +1,582 @@ +//go:build linux + +// Package app provides functionality for managing applications. +package app + +import ( + "context" + "errors" + "fmt" + "os/exec" + "syscall" + "time" + + "golang.org/x/sync/errgroup" + + "go.keploy.io/server/v2/pkg/models" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" + "go.keploy.io/server/v2/pkg/platform/docker" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func NewApp(logger *zap.Logger, id uint64, cmd string, client docker.Client, opts Options) *App { + app := &App{ + logger: logger, + id: id, + cmd: cmd, + docker: client, + kind: utils.FindDockerCmd(cmd), + keployContainer: "keploy-v2", + container: opts.Container, + containerDelay: opts.DockerDelay, + containerNetwork: opts.DockerNetwork, + } + return app +} + +type App struct { + logger *zap.Logger + docker docker.Client + id uint64 + cmd string + kind utils.CmdType + containerDelay uint64 + container string + containerNetwork string + containerIPv4 chan string + keployNetwork string + keployContainer string + keployIPv4 string + inodeChan chan uint64 + EnableTesting bool + Mode models.Mode +} + +type Options struct { + // canExit disables any error returned if the app exits by itself. + //CanExit bool + Container string + DockerDelay uint64 + DockerNetwork string +} + +func (a *App) Setup(_ context.Context) error { + + if utils.IsDockerCmd(a.kind) && isDetachMode(a.logger, a.cmd, a.kind) { + return fmt.Errorf("application could not be started in detached mode") + } + + switch a.kind { + case utils.DockerRun, utils.DockerStart: + err := a.SetupDocker() + if err != nil { + return err + } + case utils.DockerCompose: + err := a.SetupCompose() + if err != nil { + return err + } + default: + // setup native binary + } + return nil +} + +func (a *App) KeployIPv4Addr() string { + return a.keployIPv4 +} + +func (a *App) ContainerIPv4Addr() string { + return <-a.containerIPv4 +} +func (a *App) SetContainerIPv4Addr(ipAddr string) { + a.logger.Debug("setting container IPv4 address", zap.String("ipAddr", ipAddr)) + a.containerIPv4 <- ipAddr +} + +func (a *App) SetupDocker() error { + + if a.kind == utils.DockerStart { + running, err := a.docker.IsContainerRunning(a.container) + if err != nil { + return err + } + if running { + return fmt.Errorf("docker container is already in running state") + } + } + + //injecting appNetwork to keploy. + err := a.injectNetwork(a.containerNetwork) + if err != nil { + utils.LogError(a.logger, err, fmt.Sprintf("failed to inject network:%v to the keploy container", a.containerNetwork)) + return err + } + return nil +} + +func (a *App) SetupCompose() error { + if a.container == "" { + utils.LogError(a.logger, nil, "container name not found", zap.String("AppCmd", a.cmd)) + return errors.New("container name not found") + } + a.logger.Info("keploy requires docker compose containers to be run with external network") + //finding the user docker-compose file in the current directory. + // TODO currently we just return the first default docker-compose file found in the current directory + // we should add support for multiple docker-compose files by either parsing cmd for path + // or by asking the user to provide the path + // kdocker-compose.yaml file will be run instead of the user docker-compose.yaml file acc to below cases + + path := findComposeFile(a.cmd) + if path == "" { + return errors.New("can't find the docker compose file of user. Are you in the right directory? ") + } + + a.logger.Info(fmt.Sprintf("Found docker compose file path: %s", path)) + + newPath := "docker-compose-tmp.yaml" + + compose, err := a.docker.ReadComposeFile(path) + if err != nil { + utils.LogError(a.logger, err, "failed to read the compose file") + return err + } + composeChanged := false + + // Check if docker compose file uses relative file names for bind mounts + ok := a.docker.HasRelativePath(compose) + if ok { + err = a.docker.ForceAbsolutePath(compose, path) + if err != nil { + utils.LogError(a.logger, nil, "failed to convert relative paths to absolute paths in volume mounts in docker compose file") + return err + } + composeChanged = true + } + + // Checking info about the network and whether its external:true + info := a.docker.GetNetworkInfo(compose) + + if info == nil { + info, err = a.docker.SetKeployNetwork(compose) + if err != nil { + utils.LogError(a.logger, nil, "failed to set default network in the compose file", zap.String("network", a.keployNetwork)) + return err + } + composeChanged = true + } + + if !info.External { + err = a.docker.MakeNetworkExternal(compose) + if err != nil { + utils.LogError(a.logger, nil, "failed to make the network external in the compose file", zap.String("network", info.Name)) + return fmt.Errorf("error while updating network to external: %v", err) + } + composeChanged = true + } + + a.keployNetwork = info.Name + + ok, err = a.docker.NetworkExists(a.keployNetwork) + if err != nil { + utils.LogError(a.logger, nil, "failed to find default network", zap.String("network", a.keployNetwork)) + return err + } + + //if keploy-network doesn't exist locally then create it + if !ok { + err = a.docker.CreateNetwork(a.keployNetwork) + if err != nil { + utils.LogError(a.logger, nil, "failed to create default network", zap.String("network", a.keployNetwork)) + return err + } + } + + if composeChanged { + err = a.docker.WriteComposeFile(compose, newPath) + if err != nil { + utils.LogError(a.logger, nil, "failed to write the compose file", zap.String("path", newPath)) + } + a.logger.Info("Created new docker-compose for keploy internal use", zap.String("path", newPath)) + //Now replace the running command to run the kdocker-compose.yaml file instead of user docker compose file. + a.cmd = modifyDockerComposeCommand(a.cmd, newPath) + } + + if a.containerNetwork == "" { + a.containerNetwork = a.keployNetwork + } + err = a.injectNetwork(a.containerNetwork) + if err != nil { + utils.LogError(a.logger, err, fmt.Sprintf("failed to inject network:%v to the keploy container", a.containerNetwork)) + return err + } + return nil +} + +func (a *App) SetAppCommand(appCommand string) { + a.logger.Debug("Setting App Command", zap.String("cmd", appCommand)) + a.cmd = appCommand +} + +func (a *App) GetAppCommand() string { + return a.cmd +} + +func (a *App) Kind(_ context.Context) utils.CmdType { + return a.kind +} + +// injectNetwork attaches the given network to the keploy container +// and also sends the keploy container ip of the new network interface to the kernel space +func (a *App) injectNetwork(network string) error { + // inject the network to the keploy container + a.logger.Info(fmt.Sprintf("trying to inject network:%v to the keploy container", network)) + err := a.docker.AttachNetwork(a.keployContainer, []string{network}) + if err != nil { + utils.LogError(a.logger, nil, "failed to inject network to the keploy container") + return err + } + + a.keployNetwork = network + + //sending new proxy ip to kernel, since dynamically injected new network has different ip for keploy. + inspect, err := a.docker.ContainerInspect(context.Background(), a.keployContainer) + if err != nil { + utils.LogError(a.logger, nil, fmt.Sprintf("failed to get inspect keploy container:%v", inspect)) + return err + } + + keployNetworks := inspect.NetworkSettings.Networks + //Here we considering that the application would use only one custom network. + //TODO: handle for application having multiple custom networks + //TODO: check the logic for correctness + for n, settings := range keployNetworks { + if n == network { + a.keployIPv4 = settings.IPAddress + a.logger.Info("Successfully injected network to the keploy container", zap.Any("Keploy container", a.keployContainer), zap.Any("appNetwork", network), zap.String("keploy container ip", a.keployIPv4)) + return nil + } + //if networkName != "bridge" { + // network = networkName + // newProxyIpString = networkSettings.IPAddress + // a.logger.Debug(fmt.Sprintf("Network Name: %s, New Proxy IP: %s\n", networkName, networkSettings.IPAddress)) + //} + } + return fmt.Errorf("failed to find the network:%v in the keploy container", network) +} +func (a *App) extractMeta(ctx context.Context, e events.Message) (bool, error) { + + if e.Action != "start" { + return false, nil + } + // Fetch container details by inspecting using container ID to check if container is created + info, err := a.docker.ContainerInspect(ctx, e.ID) + if err != nil { + a.logger.Debug("failed to inspect container by container Id", zap.Error(err)) + return false, err + } + + // Check if the container's name matches the desired name + if info.Name != "/"+a.container { + a.logger.Debug("ignoring container creation for unrelated container", zap.String("containerName", info.Name)) + return false, nil + } + + // Set Docker Container ID + a.docker.SetContainerID(e.ID) + a.logger.Debug("checking for container pid", zap.Any("containerDetails.State.Pid", info.State.Pid)) + if info.State.Pid == 0 { + return false, errors.New("failed to get the pid of the container") + } + a.logger.Debug("", zap.Any("containerDetails.State.Pid", info.State.Pid), zap.String("containerName", a.container)) + inode, err := getInode(info.State.Pid) + if err != nil { + return false, err + } + + a.inodeChan <- inode + a.logger.Debug("container started and successfully extracted inode", zap.Any("inode", inode)) + if info.NetworkSettings == nil || info.NetworkSettings.Networks == nil { + a.logger.Debug("container network settings not available", zap.Any("containerDetails.NetworkSettings", info.NetworkSettings)) + return false, nil + } + + n, ok := info.NetworkSettings.Networks[a.containerNetwork] + if !ok || n == nil { + a.logger.Debug("container network not found", zap.Any("containerDetails.NetworkSettings.Networks", info.NetworkSettings.Networks)) + return false, fmt.Errorf("container network not found: %s", fmt.Sprintf("%+v", info.NetworkSettings.Networks)) + } + a.SetContainerIPv4Addr(n.IPAddress) + return inode != 0 && n.IPAddress != "", nil +} + +func (a *App) getDockerMeta(ctx context.Context) <-chan error { + // listen for the docker daemon events + defer a.logger.Debug("exiting from goroutine of docker daemon event listener") + + errCh := make(chan error, 1) + timer := time.NewTimer(time.Duration(a.containerDelay) * time.Second) + logTicker := time.NewTicker(1 * time.Second) + defer logTicker.Stop() + + eventFilter := filters.NewArgs( + filters.KeyValuePair{Key: "type", Value: "container"}, + filters.KeyValuePair{Key: "type", Value: "network"}, + filters.KeyValuePair{Key: "action", Value: "create"}, + filters.KeyValuePair{Key: "action", Value: "connect"}, + filters.KeyValuePair{Key: "action", Value: "start"}, + ) + + messages, errCh2 := a.docker.Events(ctx, types.EventsOptions{ + Filters: eventFilter, + }) + + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + errCh <- errors.New("failed to get the error group from the context") + return errCh + } + + g.Go(func() error { + defer utils.Recover(a.logger) + // closing the channels in any case when returning. + defer func() { + a.logger.Debug("closing err, containerIPv4 and inode channels ") + close(errCh) + close(a.containerIPv4) + close(a.inodeChan) + }() + for { + select { + case <-timer.C: + errCh <- errors.New("timeout waiting for the container to start") + return nil + case <-ctx.Done(): + a.logger.Debug("context cancelled, stopping the listener for container creation event.") + errCh <- ctx.Err() + return nil + case e := <-messages: + done, err := a.extractMeta(ctx, e) + if err != nil { + errCh <- err + return nil + } + + if done { + return nil + } + // for debugging purposes + case <-logTicker.C: + a.logger.Debug("still waiting for the container to start.", zap.String("containerName", a.container)) + return nil + case err := <-errCh2: + errCh <- err + return nil + } + } + }) + return errCh +} + +func (a *App) runDocker(ctx context.Context) models.AppError { + // if a.cmd is empty, it means the user wants to run the application manually, + // so we don't need to run the application in a goroutine + if a.cmd == "" { + return models.AppError{} + } + + g, ctx := errgroup.WithContext(ctx) + ctx = context.WithValue(ctx, models.ErrGroupKey, g) + + dockerMetaCtx, cancel := context.WithCancel(ctx) + + defer func() { + cancel() + err := g.Wait() + if err != nil { + utils.LogError(a.logger, err, "failed to run dockerized app") + } + }() + + errCh := make(chan error, 1) + + // listen for the "create container" event in order to send the inode of the container to the kernel + errCh2 := a.getDockerMeta(dockerMetaCtx) + + g.Go(func() error { + defer utils.Recover(a.logger) + defer close(errCh) + err := a.run(ctx) + if err.Err != nil { + utils.LogError(a.logger, err.Err, "Application stopped with the error") + errCh <- err.Err + } + return nil + }) + + select { + case err := <-errCh: + if err != nil && errors.Is(err, context.Canceled) { + return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: ctx.Err()} + } + return models.AppError{AppErrorType: models.ErrInternal, Err: err} + case err := <-errCh2: + if err != nil && errors.Is(err, context.Canceled) { + return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: ctx.Err()} + } + return models.AppError{AppErrorType: models.ErrInternal, Err: err} + case <-ctx.Done(): + return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: ctx.Err()} + } +} + +func (a *App) Run(ctx context.Context, inodeChan chan uint64) models.AppError { + a.inodeChan = inodeChan + a.containerIPv4 = make(chan string, 1) + if utils.IsDockerCmd(a.kind) { + return a.runDocker(ctx) + } + return a.run(ctx) +} +func (a *App) waitTillExit() { + timeout := time.NewTimer(30 * time.Second) + logTicker := time.NewTicker(1 * time.Second) + defer logTicker.Stop() + defer timeout.Stop() + + containerID := a.container + for { + select { + case <-logTicker.C: + // Inspect the container status + containerJSON, err := a.docker.ContainerInspect(context.Background(), containerID) + if err != nil { + a.logger.Debug("failed to inspect container", zap.String("containerID", containerID), zap.Error(err)) + return + } + + a.logger.Debug("container status", zap.String("status", containerJSON.State.Status), zap.String("containerName", a.container)) + // Check if container is stopped or dead + if containerJSON.State.Status == "exited" || containerJSON.State.Status == "dead" { + return + } + case <-timeout.C: + a.logger.Warn("timeout waiting for the container to stop", zap.String("containerID", containerID)) + return + } + } +} + +func (a *App) run(ctx context.Context) models.AppError { + userCmd := a.cmd + + if utils.FindDockerCmd(a.cmd) == utils.DockerRun { + userCmd = utils.EnsureRmBeforeName(userCmd) + } + + // Define the function to cancel the command + cmdCancel := func(cmd *exec.Cmd) func() error { + return func() error { + if utils.IsDockerCmd(a.kind) { + a.logger.Debug("sending SIGINT to the container", zap.Any("cmd.Process.Pid", cmd.Process.Pid)) + err := utils.SendSignal(a.logger, -cmd.Process.Pid, syscall.SIGINT) + + return err + } + return utils.InterruptProcessTree(a.logger, cmd.Process.Pid, syscall.SIGINT) + } + } + + var err error + cmdErr := utils.ExecuteCommand(ctx, a.logger, userCmd, cmdCancel, 25*time.Second) + if cmdErr.Err != nil { + switch cmdErr.Type { + case utils.Init: + return models.AppError{AppErrorType: models.ErrCommandError, Err: cmdErr.Err} + case utils.Runtime: + err = cmdErr.Err + } + } + + if utils.IsDockerCmd(a.kind) { + a.waitTillExit() + } + + select { + case <-ctx.Done(): + a.logger.Debug("context cancelled, error while waiting for the app to exit", zap.Error(ctx.Err())) + return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: ctx.Err()} + default: + if a.Mode == models.MODE_RECORD && a.EnableTesting { + a.logger.Info("waiting for some time before returning the error to allow recording of test cases when testing keploy with itself") + time.Sleep(3 * time.Second) + a.logger.Debug("test binary stopped", zap.Error(err)) + return models.AppError{AppErrorType: models.ErrTestBinStopped, Err: context.Canceled} + } + + if err != nil { + return models.AppError{AppErrorType: models.ErrUnExpected, Err: err} + } + return models.AppError{AppErrorType: models.ErrAppStopped, Err: nil} + } +} + +//if a.docker.GetContainerID() == "" { +// a.logger.Debug("still waiting for the container to start.", zap.String("containerName", a.container)) +// continue +//} +////Inspecting the application container again since the ip and pid takes some time to be linked to the container. +//info, err := a.docker.ContainerInspect(ctx, a.container) +//if err != nil { +// return err +//} +// +//a.logger.Debug("checking for container pid", zap.Any("containerDetails.State.Pid", info.State.Pid)) +//if info.State.Pid == 0 { +// a.logger.Debug("container not yet started", zap.Any("containerDetails.State.Pid", info.State.Pid)) +// continue +//} +//a.logger.Debug("", zap.Any("containerDetails.State.Pid", info.State.Pid), zap.String("containerName", a.container)) +//a.inode,err = getInode(info.State.Pid) +//if err != nil { +// return err +//} +//if info.NetworkSettings == nil || info.NetworkSettings.Networks == nil { +// a.logger.Debug("container network settings not available", zap.Any("containerDetails.NetworkSettings", info.NetworkSettings)) +// continue +//} +// +//n, ok := info.NetworkSettings.Networks[a.containerNetwork] +//if !ok || n == nil { +// return errors.New("container network not found") +//} +//a.keployIPv4 = n.IPAddress +//a.logger.Info("container started successfully", zap.Any("", info.NetworkSettings.Networks)) +//return + +//case e := <-messages: +// if e.Type != events.ContainerEventType || e.Action != "start" { +// continue +// } +// +// // Fetch container details by inspecting using container ID to check if container is created +// c, err := a.docker.ContainerInspect(ctx, e.ID) +// if err != nil { +// a.logger.Debug("failed to inspect container by container Id", zap.Error(err)) +// return err +// } +// +// // Check if the container's name matches the desired name +// if c.Name != "/"+a.container { +// a.logger.Debug("ignoring container creation for unrelated container", zap.String("containerName", c.Name)) +// continue +// } +// // Set Docker Container ID +// a.docker.SetContainerID(e.ID) +// +// a.logger.Debug("container created for desired app", zap.Any("ID", e.ID)) diff --git a/keploy/pkg/core/app/util.go b/keploy/pkg/core/app/util.go new file mode 100644 index 0000000..6d374fe --- /dev/null +++ b/keploy/pkg/core/app/util.go @@ -0,0 +1,103 @@ +//go:build linux + +package app + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "slices" + "strconv" + "strings" + "syscall" + + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func findComposeFile(cmd string) string { + + cmdArgs := strings.Fields(cmd) + + for i := 0; i < len(cmdArgs); i++ { + if cmdArgs[i] == "-f" && i+1 < len(cmdArgs) { + return cmdArgs[i+1] + } + } + + filenames := []string{"docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"} + + for _, filename := range filenames { + if _, err := os.Stat(filename); !os.IsNotExist(err) { + return filename + } + } + + return "" +} + +func modifyDockerComposeCommand(appCmd, newComposeFile string) string { + // Ensure newComposeFile starts with ./ + if !strings.HasPrefix(newComposeFile, "./") { + newComposeFile = "./" + newComposeFile + } + + // Define a regular expression pattern to match "-f " + pattern := `(-f\s+("[^"]+"|'[^']+'|\S+))` + re := regexp.MustCompile(pattern) + + // Check if the "-f " pattern exists in the appCmd + if re.MatchString(appCmd) { + // Replace it with the new Compose file + return re.ReplaceAllString(appCmd, fmt.Sprintf("-f %s", newComposeFile)) + } + + // If the pattern doesn't exist, inject the new Compose file right after "docker-compose" or "docker compose" + upIdx := strings.Index(appCmd, " up") + if upIdx != -1 { + return fmt.Sprintf("%s -f %s%s", appCmd[:upIdx], newComposeFile, appCmd[upIdx:]) + } + + return fmt.Sprintf("%s -f %s", appCmd, newComposeFile) +} + +func getInode(pid int) (uint64, error) { + path := filepath.Join("/proc", strconv.Itoa(pid), "ns", "pid") + + f, err := os.Stat(path) + if err != nil { + return 0, err + } + // Dev := (f.Sys().(*syscall.Stat_t)).Dev + i := (f.Sys().(*syscall.Stat_t)).Ino + if i == 0 { + return 0, fmt.Errorf("failed to get the inode of the process") + } + return i, nil +} + +func isDetachMode(logger *zap.Logger, command string, kind utils.CmdType) bool { + args := strings.Fields(command) + + if kind == utils.DockerStart { + flags := []string{"-a", "--attach", "-i", "--interactive"} + + for _, arg := range args { + if slices.Contains(flags, arg) { + return false + } + } + utils.LogError(logger, fmt.Errorf("docker start require --attach/-a or --interactive/-i flag"), "failed to start command") + return true + } + + for _, arg := range args { + if arg == "-d" || arg == "--detach" { + utils.LogError(logger, fmt.Errorf("detach mode is not allowed in Keploy command"), "failed to start command") + return true + } + } + + return false +} diff --git a/keploy/pkg/core/core_linux.go b/keploy/pkg/core/core_linux.go new file mode 100644 index 0000000..3ac1296 --- /dev/null +++ b/keploy/pkg/core/core_linux.go @@ -0,0 +1,279 @@ +//go:build linux + +// Package core provides functionality for managing core functionalities in Keploy. +package core + +import ( + "context" + "errors" + "fmt" + "sync" + + "golang.org/x/sync/errgroup" + + "go.keploy.io/server/v2/pkg/core/app" + "go.keploy.io/server/v2/pkg/core/hooks/structs" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/docker" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type Core struct { + Proxy // embedding the Proxy interface to transfer the proxy methods to the core object + Hooks // embedding the Hooks interface to transfer the hooks methods to the core object + Tester // embedding the Tester interface to transfer the tester methods to the core object + dockerClient docker.Client //embedding the docker client to transfer the docker client methods to the core object + logger *zap.Logger + id utils.AutoInc + apps sync.Map + proxyStarted bool +} + +func New(logger *zap.Logger, hook Hooks, proxy Proxy, tester Tester, client docker.Client) *Core { + return &Core{ + logger: logger, + Hooks: hook, + Proxy: proxy, + Tester: tester, + dockerClient: client, + } +} + +func (c *Core) Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) { + // create a new app and store it in the map + id := uint64(c.id.Next()) + a := app.NewApp(c.logger, id, cmd, c.dockerClient, app.Options{ + DockerNetwork: opts.DockerNetwork, + Container: opts.Container, + DockerDelay: opts.DockerDelay, + }) + c.apps.Store(id, a) + + err := a.Setup(ctx) + if err != nil { + utils.LogError(c.logger, err, "failed to setup app") + return 0, err + } + return id, nil +} + +func (c *Core) getApp(id uint64) (*app.App, error) { + a, ok := c.apps.Load(id) + if !ok { + return nil, fmt.Errorf("app with id:%v not found", id) + } + + // type assertion on the app + h, ok := a.(*app.App) + if !ok { + return nil, fmt.Errorf("failed to type assert app with id:%v", id) + } + + return h, nil +} + +func (c *Core) Hook(ctx context.Context, id uint64, opts models.HookOptions) error { + hookErr := errors.New("failed to hook into the app") + + a, err := c.getApp(id) + if err != nil { + utils.LogError(c.logger, err, "failed to get app") + return hookErr + } + + isDocker := utils.IsDockerCmd(a.Kind(ctx)) + + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context") + } + + // Create a new error group for the hooks (Always required) + hookErrGrp, _ := errgroup.WithContext(ctx) + hookCtx := context.WithoutCancel(ctx) //so that main context doesn't cancel the hookCtx to control the lifecycle of the hooks + hookCtx, hookCtxCancel := context.WithCancel(hookCtx) + hookCtx = context.WithValue(hookCtx, models.ErrGroupKey, hookErrGrp) + + // create a new error group for the proxy + proxyErrGrp, _ := errgroup.WithContext(ctx) + proxyCtx := context.WithoutCancel(ctx) //so that main context doesn't cancel the proxyCtx to control the lifecycle of the proxy + proxyCtx, proxyCtxCancel := context.WithCancel(proxyCtx) + proxyCtx = context.WithValue(proxyCtx, models.ErrGroupKey, proxyErrGrp) + + g.Go(func() error { + <-ctx.Done() + proxyCtxCancel() + err = proxyErrGrp.Wait() + if err != nil { + utils.LogError(c.logger, err, "failed to stop the proxy") + } + + hookCtxCancel() + err := hookErrGrp.Wait() + if err != nil { + utils.LogError(c.logger, err, "failed to unload the hooks") + } + + //deleting in order to free the memory in case of rerecord. otherwise different app id will be created for the same app. + c.apps.Delete(id) + c.id.Reset() + + return nil + }) + + // Load hooks + err = c.Load(hookCtx, id, HookCfg{ + AppID: id, + Pid: 0, + IsDocker: isDocker, + KeployIPV4: a.KeployIPv4Addr(), + Mode: opts.Mode, + Rules: opts.Rules, + E2E: opts.E2E, + Port: opts.Port, + }) + if err != nil { + utils.LogError(c.logger, err, "failed to load hooks") + return hookErr + } + + if c.proxyStarted { + c.logger.Debug("Proxy already started") + // return nil + } + + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + // TODO: Hooks can be loaded multiple times but proxy should be started only once + // if there is another containerized app, then we need to pass new (ip:port) of proxy to the eBPF + // as the network namespace is different for each container and so is the keploy/proxy IP to communicate with the app. + // start proxy + err = c.StartProxy(proxyCtx, ProxyOptions{ + DNSIPv4Addr: a.KeployIPv4Addr(), + //DnsIPv6Addr: "" + }) + if err != nil { + utils.LogError(c.logger, err, "failed to start proxy") + return hookErr + } + + c.proxyStarted = true + + // For keploy test bench + if opts.EnableTesting { + + // enable testing in the app + a.EnableTesting = true + a.Mode = opts.Mode + + // Setting up the test bench + err := c.Tester.Setup(ctx, models.TestingOptions{Mode: opts.Mode}) + if err != nil { + utils.LogError(c.logger, err, "error while setting up the test bench environment") + return errors.New("failed to setup the test bench") + } + } + + return nil +} + +// GetHookUnloadDone returns a channel that signals when hooks are completely unloaded +func (c *Core) GetHookUnloadDone(id uint64) <-chan struct{} { + return c.GetUnloadDone() +} + +func (c *Core) Run(ctx context.Context, id uint64, opts models.RunOptions) models.AppError { + a, err := c.getApp(id) + if err != nil { + utils.LogError(c.logger, err, "failed to get app") + return models.AppError{AppErrorType: models.ErrInternal, Err: err} + } + + runAppErrGrp, runAppCtx := errgroup.WithContext(ctx) + + inodeErrCh := make(chan error, 1) + appErrCh := make(chan models.AppError, 1) + inodeChan := make(chan uint64, 1) //send inode to the hook + + defer func() { + err := runAppErrGrp.Wait() + defer close(inodeErrCh) + if err != nil { + utils.LogError(c.logger, err, "failed to stop the app") + } + }() + + runAppErrGrp.Go(func() error { + defer utils.Recover(c.logger) + if a.Kind(ctx) == utils.Native { + close(inodeChan) // since we are not using inode in native mode + return nil + } + select { + case inode := <-inodeChan: + err := c.SendDockerAppInfo(id, structs.DockerAppInfo{AppInode: inode, ClientID: id}) + if err != nil { + utils.LogError(c.logger, err, "") + + inodeErrCh <- errors.New("failed to send inode to the kernel") + } + case <-ctx.Done(): + return nil + } + return nil + }) + + originalApp := a.GetAppCommand() + runAppErrGrp.Go(func() error { + defer utils.Recover(c.logger) + defer close(appErrCh) + defer a.SetAppCommand(originalApp) + if opts.AppCommand != "" { + a.SetAppCommand(opts.AppCommand) + } + appErr := a.Run(runAppCtx, inodeChan) + if appErr.Err != nil { + utils.LogError(c.logger, appErr.Err, "error while running the app") + appErrCh <- appErr + } + return nil + }) + + select { + case <-runAppCtx.Done(): + c.logger.Debug("Run context cancelled, stopping the app and reverting the app command") + return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: nil} + case appErr := <-appErrCh: + return appErr + case inodeErr := <-inodeErrCh: + return models.AppError{AppErrorType: models.ErrInternal, Err: inodeErr} + } +} + +func (c *Core) GetContainerIP(_ context.Context, id uint64) (string, error) { + + a, err := c.getApp(id) + if err != nil { + utils.LogError(c.logger, err, "failed to get app") + return "", err + } + + ip := a.ContainerIPv4Addr() + c.logger.Debug("ip address of the target app container", zap.Any("ip", ip)) + if ip == "" { + return "", fmt.Errorf("failed to get the IP address of the app container. Try increasing --delay (in seconds)") + } + + return ip, nil +} diff --git a/keploy/pkg/core/core_others.go b/keploy/pkg/core/core_others.go new file mode 100644 index 0000000..bc205ca --- /dev/null +++ b/keploy/pkg/core/core_others.go @@ -0,0 +1,61 @@ +//go:build !linux + +// Package core provides functionality for managing core functionalities in Keploy. +package core + +import ( + "context" + "errors" + "runtime" + + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +type Core struct { + logger *zap.Logger +} + +var errUnsupported = errors.New("instrumentation only supported on linux. Detected OS: " + runtime.GOOS) + +func New(logger *zap.Logger) *Core { + return &Core{ + logger: logger, + } +} + +func (c *Core) Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) { + return 0, errUnsupported +} + +func (c *Core) Hook(ctx context.Context, id uint64, opts models.HookOptions) error { + return errUnsupported +} + +func (c *Core) GetHookUnloadDone(id uint64) <-chan struct{} { + ch := make(chan struct{}) + close(ch) // Immediately close since no actual hooks are loaded + return ch +} + +func (c *Core) MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error { + return errUnsupported +} + +func (c *Core) SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error { + return errUnsupported +} + +func (c *Core) GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) { + return nil, errUnsupported +} + +func (c *Core) Run(ctx context.Context, id uint64, _ models.RunOptions) models.AppError { + return models.AppError{ + Err: errUnsupported, + } +} + +func (c *Core) GetContainerIP(_ context.Context, id uint64) (string, error) { + return "", errUnsupported +} diff --git a/keploy/pkg/core/core_others_test.go b/keploy/pkg/core/core_others_test.go new file mode 100644 index 0000000..b8aaeea --- /dev/null +++ b/keploy/pkg/core/core_others_test.go @@ -0,0 +1,92 @@ +//go:build !linux + +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCore_GetHookUnloadDone_NonLinux(t *testing.T) { + logger := zap.NewNop() + + // Create a new Core instance using the non-Linux constructor + core := New(logger) + + appID := uint64(12345) + + // Test that GetHookUnloadDone returns a channel + ch := core.GetHookUnloadDone(appID) + assert.NotNil(t, ch, "GetHookUnloadDone should return a channel") + + // For non-Linux platforms, the channel should be immediately closed + // since no actual hooks are loaded + select { + case <-ch: + // Expected behavior for non-Linux - channel is immediately closed + case <-time.After(100 * time.Millisecond): + t.Error("Channel should be immediately closed on non-Linux platforms") + } +} + +func TestCore_GetHookUnloadDone_NonLinux_MultipleCalls(t *testing.T) { + logger := zap.NewNop() + core := New(logger) + + appID := uint64(12345) + + // Get multiple channels - each should be immediately closed + ch1 := core.GetHookUnloadDone(appID) + ch2 := core.GetHookUnloadDone(appID) + + assert.NotNil(t, ch1, "First call should return a channel") + assert.NotNil(t, ch2, "Second call should return a channel") + + // Both channels should be immediately closed + select { + case <-ch1: + // Expected behavior + case <-time.After(100 * time.Millisecond): + t.Error("First channel should be immediately closed") + } + + select { + case <-ch2: + // Expected behavior + case <-time.After(100 * time.Millisecond): + t.Error("Second channel should be immediately closed") + } +} + +func TestCore_GetHookUnloadDone_NonLinux_DifferentApps(t *testing.T) { + logger := zap.NewNop() + core := New(logger) + + appID1 := uint64(12345) + appID2 := uint64(67890) + + // Get channels for different app IDs + ch1 := core.GetHookUnloadDone(appID1) + ch2 := core.GetHookUnloadDone(appID2) + + assert.NotNil(t, ch1, "First app should return a channel") + assert.NotNil(t, ch2, "Second app should return a channel") + + // Both should be immediately closed + select { + case <-ch1: + // Expected behavior + case <-time.After(100 * time.Millisecond): + t.Error("Channel for app1 should be immediately closed") + } + + select { + case <-ch2: + // Expected behavior + case <-time.After(100 * time.Millisecond): + t.Error("Channel for app2 should be immediately closed") + } +} diff --git a/keploy/pkg/core/hooks/README.md b/keploy/pkg/core/hooks/README.md new file mode 100755 index 0000000..24c2075 --- /dev/null +++ b/keploy/pkg/core/hooks/README.md @@ -0,0 +1,6 @@ +# Hooks Package Documentation + +The `hooks` package contains the user-space Go code responsible for +loading eBPF hooks and eBPF maps, which are used to instrument the user +API. This package is utilized by the CLI commands. Additionally, it +launches proxy on a defined port to capture egress calls. \ No newline at end of file diff --git a/keploy/pkg/core/hooks/bpf_arm64_bpfel.go b/keploy/pkg/core/hooks/bpf_arm64_bpfel.go new file mode 100644 index 0000000..bca2d35 --- /dev/null +++ b/keploy/pkg/core/hooks/bpf_arm64_bpfel.go @@ -0,0 +1,289 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build arm64 + +package hooks + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// loadBpf returns the embedded CollectionSpec for bpf. +func loadBpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_BpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load bpf: %w", err) + } + + return spec, err +} + +// loadBpfObjects loads bpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *bpfObjects +// *bpfPrograms +// *bpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadBpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// bpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfSpecs struct { + bpfProgramSpecs + bpfMapSpecs + bpfVariableSpecs +} + +// bpfProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfProgramSpecs struct { + K_connect4 *ebpf.ProgramSpec `ebpf:"k_connect4"` + K_connect6 *ebpf.ProgramSpec `ebpf:"k_connect6"` + K_getpeername4 *ebpf.ProgramSpec `ebpf:"k_getpeername4"` + K_getpeername6 *ebpf.ProgramSpec `ebpf:"k_getpeername6"` + SyscallProbeEntryAccept *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_accept"` + SyscallProbeEntryAccept4 *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_accept4"` + SyscallProbeEntryClose *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_close"` + SyscallProbeEntryRead *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_read"` + SyscallProbeEntryReadv *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_readv"` + SyscallProbeEntryRecvfrom *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_recvfrom"` + SyscallProbeEntrySendto *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_sendto"` + SyscallProbeEntryTcpV4Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v4_connect"` + SyscallProbeEntryTcpV4PreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v4_pre_connect"` + SyscallProbeEntryTcpV6Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v6_connect"` + SyscallProbeEntryTcpV6PreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v6_pre_connect"` + SyscallProbeEntryUdpPreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_udp_pre_connect"` + SyscallProbeEntryWrite *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_write"` + SyscallProbeEntryWritev *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_writev"` + SyscallProbeRetAccept *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_accept"` + SyscallProbeRetAccept4 *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_accept4"` + SyscallProbeRetClose *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_close"` + SyscallProbeRetConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_connect"` + SyscallProbeRetRead *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_read"` + SyscallProbeRetReadv *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_readv"` + SyscallProbeRetRecvfrom *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_recvfrom"` + SyscallProbeRetSendto *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_sendto"` + SyscallProbeRetTcpV4Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_tcp_v4_connect"` + SyscallProbeRetTcpV6Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_tcp_v6_connect"` + SyscallProbeRetWrite *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_write"` + SyscallProbeRetWritev *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_writev"` + SyscallProbeEntryConnect *ebpf.ProgramSpec `ebpf:"syscall_probe_entry_connect"` + SyscallProbeEntrySocket *ebpf.ProgramSpec `ebpf:"syscall_probe_entry_socket"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { + ActiveAcceptArgsMap *ebpf.MapSpec `ebpf:"active_accept_args_map"` + ActiveCloseArgsMap *ebpf.MapSpec `ebpf:"active_close_args_map"` + ActiveReadArgsMap *ebpf.MapSpec `ebpf:"active_read_args_map"` + ActiveWriteArgsMap *ebpf.MapSpec `ebpf:"active_write_args_map"` + AppChildKernelPidMap *ebpf.MapSpec `ebpf:"app_child_kernel_pid_map"` + ConnInfoMap *ebpf.MapSpec `ebpf:"conn_info_map"` + CurrentSockMap *ebpf.MapSpec `ebpf:"current_sock_map"` + DestInfoMap *ebpf.MapSpec `ebpf:"dest_info_map"` + DockerAppRegistrationMap *ebpf.MapSpec `ebpf:"docker_app_registration_map"` + E2eInfoMap *ebpf.MapSpec `ebpf:"e2e_info_map"` + KeployAgentKernelPidMap *ebpf.MapSpec `ebpf:"keploy_agent_kernel_pid_map"` + KeployAgentRegistrationMap *ebpf.MapSpec `ebpf:"keploy_agent_registration_map"` + KeployClientKernelPidMap *ebpf.MapSpec `ebpf:"keploy_client_kernel_pid_map"` + KeployClientRegistrationMap *ebpf.MapSpec `ebpf:"keploy_client_registration_map"` + OutgoingConnCheckMap *ebpf.MapSpec `ebpf:"outgoing_conn_check_map"` + OutgoingConnectArgsMap *ebpf.MapSpec `ebpf:"outgoing_connect_args_map"` + RedirectProxyMap *ebpf.MapSpec `ebpf:"redirect_proxy_map"` + SocketCloseEvents *ebpf.MapSpec `ebpf:"socket_close_events"` + SocketDataEventBufferHeap *ebpf.MapSpec `ebpf:"socket_data_event_buffer_heap"` + SocketDataEvents *ebpf.MapSpec `ebpf:"socket_data_events"` + SocketOpenEvents *ebpf.MapSpec `ebpf:"socket_open_events"` + TaskStructMap *ebpf.MapSpec `ebpf:"task_struct_map"` +} + +// bpfVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfVariableSpecs struct { +} + +// bpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfObjects struct { + bpfPrograms + bpfMaps + bpfVariables +} + +func (o *bpfObjects) Close() error { + return _BpfClose( + &o.bpfPrograms, + &o.bpfMaps, + ) +} + +// bpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfMaps struct { + ActiveAcceptArgsMap *ebpf.Map `ebpf:"active_accept_args_map"` + ActiveCloseArgsMap *ebpf.Map `ebpf:"active_close_args_map"` + ActiveReadArgsMap *ebpf.Map `ebpf:"active_read_args_map"` + ActiveWriteArgsMap *ebpf.Map `ebpf:"active_write_args_map"` + AppChildKernelPidMap *ebpf.Map `ebpf:"app_child_kernel_pid_map"` + ConnInfoMap *ebpf.Map `ebpf:"conn_info_map"` + CurrentSockMap *ebpf.Map `ebpf:"current_sock_map"` + DestInfoMap *ebpf.Map `ebpf:"dest_info_map"` + DockerAppRegistrationMap *ebpf.Map `ebpf:"docker_app_registration_map"` + E2eInfoMap *ebpf.Map `ebpf:"e2e_info_map"` + KeployAgentKernelPidMap *ebpf.Map `ebpf:"keploy_agent_kernel_pid_map"` + KeployAgentRegistrationMap *ebpf.Map `ebpf:"keploy_agent_registration_map"` + KeployClientKernelPidMap *ebpf.Map `ebpf:"keploy_client_kernel_pid_map"` + KeployClientRegistrationMap *ebpf.Map `ebpf:"keploy_client_registration_map"` + OutgoingConnCheckMap *ebpf.Map `ebpf:"outgoing_conn_check_map"` + OutgoingConnectArgsMap *ebpf.Map `ebpf:"outgoing_connect_args_map"` + RedirectProxyMap *ebpf.Map `ebpf:"redirect_proxy_map"` + SocketCloseEvents *ebpf.Map `ebpf:"socket_close_events"` + SocketDataEventBufferHeap *ebpf.Map `ebpf:"socket_data_event_buffer_heap"` + SocketDataEvents *ebpf.Map `ebpf:"socket_data_events"` + SocketOpenEvents *ebpf.Map `ebpf:"socket_open_events"` + TaskStructMap *ebpf.Map `ebpf:"task_struct_map"` +} + +func (m *bpfMaps) Close() error { + return _BpfClose( + m.ActiveAcceptArgsMap, + m.ActiveCloseArgsMap, + m.ActiveReadArgsMap, + m.ActiveWriteArgsMap, + m.AppChildKernelPidMap, + m.ConnInfoMap, + m.CurrentSockMap, + m.DestInfoMap, + m.DockerAppRegistrationMap, + m.E2eInfoMap, + m.KeployAgentKernelPidMap, + m.KeployAgentRegistrationMap, + m.KeployClientKernelPidMap, + m.KeployClientRegistrationMap, + m.OutgoingConnCheckMap, + m.OutgoingConnectArgsMap, + m.RedirectProxyMap, + m.SocketCloseEvents, + m.SocketDataEventBufferHeap, + m.SocketDataEvents, + m.SocketOpenEvents, + m.TaskStructMap, + ) +} + +// bpfVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfVariables struct { +} + +// bpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfPrograms struct { + K_connect4 *ebpf.Program `ebpf:"k_connect4"` + K_connect6 *ebpf.Program `ebpf:"k_connect6"` + K_getpeername4 *ebpf.Program `ebpf:"k_getpeername4"` + K_getpeername6 *ebpf.Program `ebpf:"k_getpeername6"` + SyscallProbeEntryAccept *ebpf.Program `ebpf:"syscall__probe_entry_accept"` + SyscallProbeEntryAccept4 *ebpf.Program `ebpf:"syscall__probe_entry_accept4"` + SyscallProbeEntryClose *ebpf.Program `ebpf:"syscall__probe_entry_close"` + SyscallProbeEntryRead *ebpf.Program `ebpf:"syscall__probe_entry_read"` + SyscallProbeEntryReadv *ebpf.Program `ebpf:"syscall__probe_entry_readv"` + SyscallProbeEntryRecvfrom *ebpf.Program `ebpf:"syscall__probe_entry_recvfrom"` + SyscallProbeEntrySendto *ebpf.Program `ebpf:"syscall__probe_entry_sendto"` + SyscallProbeEntryTcpV4Connect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v4_connect"` + SyscallProbeEntryTcpV4PreConnect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v4_pre_connect"` + SyscallProbeEntryTcpV6Connect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v6_connect"` + SyscallProbeEntryTcpV6PreConnect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v6_pre_connect"` + SyscallProbeEntryUdpPreConnect *ebpf.Program `ebpf:"syscall__probe_entry_udp_pre_connect"` + SyscallProbeEntryWrite *ebpf.Program `ebpf:"syscall__probe_entry_write"` + SyscallProbeEntryWritev *ebpf.Program `ebpf:"syscall__probe_entry_writev"` + SyscallProbeRetAccept *ebpf.Program `ebpf:"syscall__probe_ret_accept"` + SyscallProbeRetAccept4 *ebpf.Program `ebpf:"syscall__probe_ret_accept4"` + SyscallProbeRetClose *ebpf.Program `ebpf:"syscall__probe_ret_close"` + SyscallProbeRetConnect *ebpf.Program `ebpf:"syscall__probe_ret_connect"` + SyscallProbeRetRead *ebpf.Program `ebpf:"syscall__probe_ret_read"` + SyscallProbeRetReadv *ebpf.Program `ebpf:"syscall__probe_ret_readv"` + SyscallProbeRetRecvfrom *ebpf.Program `ebpf:"syscall__probe_ret_recvfrom"` + SyscallProbeRetSendto *ebpf.Program `ebpf:"syscall__probe_ret_sendto"` + SyscallProbeRetTcpV4Connect *ebpf.Program `ebpf:"syscall__probe_ret_tcp_v4_connect"` + SyscallProbeRetTcpV6Connect *ebpf.Program `ebpf:"syscall__probe_ret_tcp_v6_connect"` + SyscallProbeRetWrite *ebpf.Program `ebpf:"syscall__probe_ret_write"` + SyscallProbeRetWritev *ebpf.Program `ebpf:"syscall__probe_ret_writev"` + SyscallProbeEntryConnect *ebpf.Program `ebpf:"syscall_probe_entry_connect"` + SyscallProbeEntrySocket *ebpf.Program `ebpf:"syscall_probe_entry_socket"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.K_connect4, + p.K_connect6, + p.K_getpeername4, + p.K_getpeername6, + p.SyscallProbeEntryAccept, + p.SyscallProbeEntryAccept4, + p.SyscallProbeEntryClose, + p.SyscallProbeEntryRead, + p.SyscallProbeEntryReadv, + p.SyscallProbeEntryRecvfrom, + p.SyscallProbeEntrySendto, + p.SyscallProbeEntryTcpV4Connect, + p.SyscallProbeEntryTcpV4PreConnect, + p.SyscallProbeEntryTcpV6Connect, + p.SyscallProbeEntryTcpV6PreConnect, + p.SyscallProbeEntryUdpPreConnect, + p.SyscallProbeEntryWrite, + p.SyscallProbeEntryWritev, + p.SyscallProbeRetAccept, + p.SyscallProbeRetAccept4, + p.SyscallProbeRetClose, + p.SyscallProbeRetConnect, + p.SyscallProbeRetRead, + p.SyscallProbeRetReadv, + p.SyscallProbeRetRecvfrom, + p.SyscallProbeRetSendto, + p.SyscallProbeRetTcpV4Connect, + p.SyscallProbeRetTcpV6Connect, + p.SyscallProbeRetWrite, + p.SyscallProbeRetWritev, + p.SyscallProbeEntryConnect, + p.SyscallProbeEntrySocket, + ) +} + +func _BpfClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed bpf_arm64_bpfel.o +var _BpfBytes []byte diff --git a/keploy/pkg/core/hooks/bpf_arm64_bpfel.o b/keploy/pkg/core/hooks/bpf_arm64_bpfel.o new file mode 100644 index 0000000000000000000000000000000000000000..9f31745df889223e01702d9fdd27821d58253d5d GIT binary patch literal 381808 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 I&BOZt4cn>TzW@LL literal 0 HcmV?d00001 diff --git a/keploy/pkg/core/hooks/bpf_x86_bpfel.go b/keploy/pkg/core/hooks/bpf_x86_bpfel.go new file mode 100644 index 0000000..2137686 --- /dev/null +++ b/keploy/pkg/core/hooks/bpf_x86_bpfel.go @@ -0,0 +1,289 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 + +package hooks + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// loadBpf returns the embedded CollectionSpec for bpf. +func loadBpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_BpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load bpf: %w", err) + } + + return spec, err +} + +// loadBpfObjects loads bpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *bpfObjects +// *bpfPrograms +// *bpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadBpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// bpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfSpecs struct { + bpfProgramSpecs + bpfMapSpecs + bpfVariableSpecs +} + +// bpfProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfProgramSpecs struct { + K_connect4 *ebpf.ProgramSpec `ebpf:"k_connect4"` + K_connect6 *ebpf.ProgramSpec `ebpf:"k_connect6"` + K_getpeername4 *ebpf.ProgramSpec `ebpf:"k_getpeername4"` + K_getpeername6 *ebpf.ProgramSpec `ebpf:"k_getpeername6"` + SyscallProbeEntryAccept *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_accept"` + SyscallProbeEntryAccept4 *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_accept4"` + SyscallProbeEntryClose *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_close"` + SyscallProbeEntryRead *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_read"` + SyscallProbeEntryReadv *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_readv"` + SyscallProbeEntryRecvfrom *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_recvfrom"` + SyscallProbeEntrySendto *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_sendto"` + SyscallProbeEntryTcpV4Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v4_connect"` + SyscallProbeEntryTcpV4PreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v4_pre_connect"` + SyscallProbeEntryTcpV6Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v6_connect"` + SyscallProbeEntryTcpV6PreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v6_pre_connect"` + SyscallProbeEntryUdpPreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_udp_pre_connect"` + SyscallProbeEntryWrite *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_write"` + SyscallProbeEntryWritev *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_writev"` + SyscallProbeRetAccept *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_accept"` + SyscallProbeRetAccept4 *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_accept4"` + SyscallProbeRetClose *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_close"` + SyscallProbeRetConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_connect"` + SyscallProbeRetRead *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_read"` + SyscallProbeRetReadv *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_readv"` + SyscallProbeRetRecvfrom *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_recvfrom"` + SyscallProbeRetSendto *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_sendto"` + SyscallProbeRetTcpV4Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_tcp_v4_connect"` + SyscallProbeRetTcpV6Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_tcp_v6_connect"` + SyscallProbeRetWrite *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_write"` + SyscallProbeRetWritev *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_writev"` + SyscallProbeEntryConnect *ebpf.ProgramSpec `ebpf:"syscall_probe_entry_connect"` + SyscallProbeEntrySocket *ebpf.ProgramSpec `ebpf:"syscall_probe_entry_socket"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { + ActiveAcceptArgsMap *ebpf.MapSpec `ebpf:"active_accept_args_map"` + ActiveCloseArgsMap *ebpf.MapSpec `ebpf:"active_close_args_map"` + ActiveReadArgsMap *ebpf.MapSpec `ebpf:"active_read_args_map"` + ActiveWriteArgsMap *ebpf.MapSpec `ebpf:"active_write_args_map"` + AppChildKernelPidMap *ebpf.MapSpec `ebpf:"app_child_kernel_pid_map"` + ConnInfoMap *ebpf.MapSpec `ebpf:"conn_info_map"` + CurrentSockMap *ebpf.MapSpec `ebpf:"current_sock_map"` + DestInfoMap *ebpf.MapSpec `ebpf:"dest_info_map"` + DockerAppRegistrationMap *ebpf.MapSpec `ebpf:"docker_app_registration_map"` + E2eInfoMap *ebpf.MapSpec `ebpf:"e2e_info_map"` + KeployAgentKernelPidMap *ebpf.MapSpec `ebpf:"keploy_agent_kernel_pid_map"` + KeployAgentRegistrationMap *ebpf.MapSpec `ebpf:"keploy_agent_registration_map"` + KeployClientKernelPidMap *ebpf.MapSpec `ebpf:"keploy_client_kernel_pid_map"` + KeployClientRegistrationMap *ebpf.MapSpec `ebpf:"keploy_client_registration_map"` + OutgoingConnCheckMap *ebpf.MapSpec `ebpf:"outgoing_conn_check_map"` + OutgoingConnectArgsMap *ebpf.MapSpec `ebpf:"outgoing_connect_args_map"` + RedirectProxyMap *ebpf.MapSpec `ebpf:"redirect_proxy_map"` + SocketCloseEvents *ebpf.MapSpec `ebpf:"socket_close_events"` + SocketDataEventBufferHeap *ebpf.MapSpec `ebpf:"socket_data_event_buffer_heap"` + SocketDataEvents *ebpf.MapSpec `ebpf:"socket_data_events"` + SocketOpenEvents *ebpf.MapSpec `ebpf:"socket_open_events"` + TaskStructMap *ebpf.MapSpec `ebpf:"task_struct_map"` +} + +// bpfVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfVariableSpecs struct { +} + +// bpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfObjects struct { + bpfPrograms + bpfMaps + bpfVariables +} + +func (o *bpfObjects) Close() error { + return _BpfClose( + &o.bpfPrograms, + &o.bpfMaps, + ) +} + +// bpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfMaps struct { + ActiveAcceptArgsMap *ebpf.Map `ebpf:"active_accept_args_map"` + ActiveCloseArgsMap *ebpf.Map `ebpf:"active_close_args_map"` + ActiveReadArgsMap *ebpf.Map `ebpf:"active_read_args_map"` + ActiveWriteArgsMap *ebpf.Map `ebpf:"active_write_args_map"` + AppChildKernelPidMap *ebpf.Map `ebpf:"app_child_kernel_pid_map"` + ConnInfoMap *ebpf.Map `ebpf:"conn_info_map"` + CurrentSockMap *ebpf.Map `ebpf:"current_sock_map"` + DestInfoMap *ebpf.Map `ebpf:"dest_info_map"` + DockerAppRegistrationMap *ebpf.Map `ebpf:"docker_app_registration_map"` + E2eInfoMap *ebpf.Map `ebpf:"e2e_info_map"` + KeployAgentKernelPidMap *ebpf.Map `ebpf:"keploy_agent_kernel_pid_map"` + KeployAgentRegistrationMap *ebpf.Map `ebpf:"keploy_agent_registration_map"` + KeployClientKernelPidMap *ebpf.Map `ebpf:"keploy_client_kernel_pid_map"` + KeployClientRegistrationMap *ebpf.Map `ebpf:"keploy_client_registration_map"` + OutgoingConnCheckMap *ebpf.Map `ebpf:"outgoing_conn_check_map"` + OutgoingConnectArgsMap *ebpf.Map `ebpf:"outgoing_connect_args_map"` + RedirectProxyMap *ebpf.Map `ebpf:"redirect_proxy_map"` + SocketCloseEvents *ebpf.Map `ebpf:"socket_close_events"` + SocketDataEventBufferHeap *ebpf.Map `ebpf:"socket_data_event_buffer_heap"` + SocketDataEvents *ebpf.Map `ebpf:"socket_data_events"` + SocketOpenEvents *ebpf.Map `ebpf:"socket_open_events"` + TaskStructMap *ebpf.Map `ebpf:"task_struct_map"` +} + +func (m *bpfMaps) Close() error { + return _BpfClose( + m.ActiveAcceptArgsMap, + m.ActiveCloseArgsMap, + m.ActiveReadArgsMap, + m.ActiveWriteArgsMap, + m.AppChildKernelPidMap, + m.ConnInfoMap, + m.CurrentSockMap, + m.DestInfoMap, + m.DockerAppRegistrationMap, + m.E2eInfoMap, + m.KeployAgentKernelPidMap, + m.KeployAgentRegistrationMap, + m.KeployClientKernelPidMap, + m.KeployClientRegistrationMap, + m.OutgoingConnCheckMap, + m.OutgoingConnectArgsMap, + m.RedirectProxyMap, + m.SocketCloseEvents, + m.SocketDataEventBufferHeap, + m.SocketDataEvents, + m.SocketOpenEvents, + m.TaskStructMap, + ) +} + +// bpfVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfVariables struct { +} + +// bpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfPrograms struct { + K_connect4 *ebpf.Program `ebpf:"k_connect4"` + K_connect6 *ebpf.Program `ebpf:"k_connect6"` + K_getpeername4 *ebpf.Program `ebpf:"k_getpeername4"` + K_getpeername6 *ebpf.Program `ebpf:"k_getpeername6"` + SyscallProbeEntryAccept *ebpf.Program `ebpf:"syscall__probe_entry_accept"` + SyscallProbeEntryAccept4 *ebpf.Program `ebpf:"syscall__probe_entry_accept4"` + SyscallProbeEntryClose *ebpf.Program `ebpf:"syscall__probe_entry_close"` + SyscallProbeEntryRead *ebpf.Program `ebpf:"syscall__probe_entry_read"` + SyscallProbeEntryReadv *ebpf.Program `ebpf:"syscall__probe_entry_readv"` + SyscallProbeEntryRecvfrom *ebpf.Program `ebpf:"syscall__probe_entry_recvfrom"` + SyscallProbeEntrySendto *ebpf.Program `ebpf:"syscall__probe_entry_sendto"` + SyscallProbeEntryTcpV4Connect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v4_connect"` + SyscallProbeEntryTcpV4PreConnect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v4_pre_connect"` + SyscallProbeEntryTcpV6Connect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v6_connect"` + SyscallProbeEntryTcpV6PreConnect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v6_pre_connect"` + SyscallProbeEntryUdpPreConnect *ebpf.Program `ebpf:"syscall__probe_entry_udp_pre_connect"` + SyscallProbeEntryWrite *ebpf.Program `ebpf:"syscall__probe_entry_write"` + SyscallProbeEntryWritev *ebpf.Program `ebpf:"syscall__probe_entry_writev"` + SyscallProbeRetAccept *ebpf.Program `ebpf:"syscall__probe_ret_accept"` + SyscallProbeRetAccept4 *ebpf.Program `ebpf:"syscall__probe_ret_accept4"` + SyscallProbeRetClose *ebpf.Program `ebpf:"syscall__probe_ret_close"` + SyscallProbeRetConnect *ebpf.Program `ebpf:"syscall__probe_ret_connect"` + SyscallProbeRetRead *ebpf.Program `ebpf:"syscall__probe_ret_read"` + SyscallProbeRetReadv *ebpf.Program `ebpf:"syscall__probe_ret_readv"` + SyscallProbeRetRecvfrom *ebpf.Program `ebpf:"syscall__probe_ret_recvfrom"` + SyscallProbeRetSendto *ebpf.Program `ebpf:"syscall__probe_ret_sendto"` + SyscallProbeRetTcpV4Connect *ebpf.Program `ebpf:"syscall__probe_ret_tcp_v4_connect"` + SyscallProbeRetTcpV6Connect *ebpf.Program `ebpf:"syscall__probe_ret_tcp_v6_connect"` + SyscallProbeRetWrite *ebpf.Program `ebpf:"syscall__probe_ret_write"` + SyscallProbeRetWritev *ebpf.Program `ebpf:"syscall__probe_ret_writev"` + SyscallProbeEntryConnect *ebpf.Program `ebpf:"syscall_probe_entry_connect"` + SyscallProbeEntrySocket *ebpf.Program `ebpf:"syscall_probe_entry_socket"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.K_connect4, + p.K_connect6, + p.K_getpeername4, + p.K_getpeername6, + p.SyscallProbeEntryAccept, + p.SyscallProbeEntryAccept4, + p.SyscallProbeEntryClose, + p.SyscallProbeEntryRead, + p.SyscallProbeEntryReadv, + p.SyscallProbeEntryRecvfrom, + p.SyscallProbeEntrySendto, + p.SyscallProbeEntryTcpV4Connect, + p.SyscallProbeEntryTcpV4PreConnect, + p.SyscallProbeEntryTcpV6Connect, + p.SyscallProbeEntryTcpV6PreConnect, + p.SyscallProbeEntryUdpPreConnect, + p.SyscallProbeEntryWrite, + p.SyscallProbeEntryWritev, + p.SyscallProbeRetAccept, + p.SyscallProbeRetAccept4, + p.SyscallProbeRetClose, + p.SyscallProbeRetConnect, + p.SyscallProbeRetRead, + p.SyscallProbeRetReadv, + p.SyscallProbeRetRecvfrom, + p.SyscallProbeRetSendto, + p.SyscallProbeRetTcpV4Connect, + p.SyscallProbeRetTcpV6Connect, + p.SyscallProbeRetWrite, + p.SyscallProbeRetWritev, + p.SyscallProbeEntryConnect, + p.SyscallProbeEntrySocket, + ) +} + +func _BpfClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed bpf_x86_bpfel.o +var _BpfBytes []byte diff --git a/keploy/pkg/core/hooks/bpf_x86_bpfel.o b/keploy/pkg/core/hooks/bpf_x86_bpfel.o new file mode 100644 index 0000000000000000000000000000000000000000..24075fcc53324340026b5950e74fc33b49aaf4a5 GIT binary patch literal 381144 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 x^M73yRgw5tKUz 0 { + factory.logger.Debug("Deleting inactive trackers", + zap.Int("count", len(trackersToDelete))) + } + for _, key := range trackersToDelete { + delete(factory.connections, key) + } +} + +// GetOrCreate returns a tracker that related to the given conn and transaction ids. If there is no such tracker +// we create a new one. +func (factory *Factory) GetOrCreate(connectionID ID) *Tracker { + factory.mutex.Lock() + defer factory.mutex.Unlock() + tracker, ok := factory.connections[connectionID] + if !ok { + tracker = NewTracker(connectionID, factory.logger) + factory.connections[connectionID] = tracker + } + return tracker +} diff --git a/keploy/pkg/core/hooks/conn/socket.go b/keploy/pkg/core/hooks/conn/socket.go new file mode 100644 index 0000000..062552a --- /dev/null +++ b/keploy/pkg/core/hooks/conn/socket.go @@ -0,0 +1,264 @@ +//go:build linux + +package conn + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + "os" + "time" + "unsafe" + + "golang.org/x/sync/errgroup" + "golang.org/x/sys/unix" + + "github.com/cilium/ebpf" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + + "github.com/cilium/ebpf/perf" + "github.com/cilium/ebpf/ringbuf" + "go.uber.org/zap" +) + +var eventAttributesSize = int(unsafe.Sizeof(SocketDataEvent{})) + +// ListenSocket starts the socket event listeners +func ListenSocket(ctx context.Context, l *zap.Logger, openMap, dataMap, closeMap *ebpf.Map, opts models.IncomingOptions) (<-chan *models.TestCase, error) { + t := make(chan *models.TestCase, 500) + err := initRealTimeOffset() + if err != nil { + utils.LogError(l, err, "failed to initialize real time offset") + return nil, errors.New("failed to start socket listeners") + } + c := NewFactory(5*time.Minute, l, opts) + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return nil, errors.New("failed to get the error group from the context") + } + g.Go(func() error { + defer utils.Recover(l) + go func() { + defer utils.Recover(l) + for { + select { + case <-ctx.Done(): + return + default: + // TODO refactor this to directly consume the events from the maps + c.ProcessActiveTrackers(ctx, t, opts) + time.Sleep(100 * time.Millisecond) + } + } + }() + <-ctx.Done() + close(t) + return nil + }) + + err = open(ctx, c, l, openMap) + if err != nil { + utils.LogError(l, err, "failed to start open socket listener") + return nil, errors.New("failed to start socket listeners") + } + err = data(ctx, c, l, dataMap) + if err != nil { + utils.LogError(l, err, "failed to start data socket listener") + return nil, errors.New("failed to start socket listeners") + } + err = exit(ctx, c, l, closeMap) + if err != nil { + utils.LogError(l, err, "failed to start close socket listener") + return nil, errors.New("failed to start socket listeners") + } + return t, err +} + +func open(ctx context.Context, c *Factory, l *zap.Logger, m *ebpf.Map) error { + + r, err := perf.NewReader(m, os.Getpagesize()) + if err != nil { + utils.LogError(l, nil, "failed to create perf event reader of socketOpenEvent") + return err + } + + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context") + } + g.Go(func() error { + defer utils.Recover(l) + go func() { + defer utils.Recover(l) + for { + rec, err := r.Read() + if err != nil { + if errors.Is(err, perf.ErrClosed) { + return + } + utils.LogError(l, err, "failed to read from perf socketOpenEvent reader") + continue + } + + if rec.LostSamples != 0 { + l.Debug("Unable to add samples to the socketOpenEvent array due to its full capacity", zap.Any("samples", rec.LostSamples)) + continue + } + data := rec.RawSample + var event SocketOpenEvent + + if err := binary.Read(bytes.NewReader(data), binary.LittleEndian, &event); err != nil { + utils.LogError(l, err, "failed to decode the received data from perf socketOpenEvent reader") + continue + } + + event.TimestampNano += getRealTimeOffset() + c.GetOrCreate(event.ConnID).AddOpenEvent(event) + } + }() + <-ctx.Done() // Check for context cancellation + err := r.Close() + if err != nil { + utils.LogError(l, err, "failed to close perf socketOpenEvent reader") + } + return nil + }) + return nil +} + +func data(ctx context.Context, c *Factory, l *zap.Logger, m *ebpf.Map) error { + r, err := ringbuf.NewReader(m) + if err != nil { + utils.LogError(l, nil, "failed to create ring buffer of socketDataEvent") + return err + } + + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context") + } + g.Go(func() error { + defer utils.Recover(l) + go func() { + defer utils.Recover(l) + for { + record, err := r.Read() + if err != nil { + if !errors.Is(err, ringbuf.ErrClosed) { + utils.LogError(l, err, "failed to receive signal from ringbuf socketDataEvent reader") + return + } + continue + } + + bin := record.RawSample + if len(bin) < eventAttributesSize { + l.Debug(fmt.Sprintf("Buffer's for SocketDataEvent is smaller (%d) than the minimum required (%d)", len(bin), eventAttributesSize)) + continue + } else if len(bin) > EventBodyMaxSize+eventAttributesSize { + l.Debug(fmt.Sprintf("Buffer's for SocketDataEvent is bigger (%d) than the maximum for the struct (%d)", len(bin), EventBodyMaxSize+eventAttributesSize)) + continue + } + + var event SocketDataEvent + + if err := binary.Read(bytes.NewReader(bin), binary.LittleEndian, &event); err != nil { + utils.LogError(l, err, "failed to decode the received data from ringbuf socketDataEvent reader") + continue + } + + event.TimestampNano += getRealTimeOffset() + + if event.Direction == IngressTraffic { + event.EntryTimestampNano += getRealTimeOffset() + l.Debug(fmt.Sprintf("Request EntryTimestamp :%v\n", convertUnixNanoToTime(event.EntryTimestampNano))) + } + + c.GetOrCreate(event.ConnID).AddDataEvent(event) + } + }() + <-ctx.Done() // Check for context cancellation + err := r.Close() + if err != nil { + utils.LogError(l, err, "failed to close ringbuf socketDataEvent reader") + } + return nil + }) + return nil +} + +func exit(ctx context.Context, c *Factory, l *zap.Logger, m *ebpf.Map) error { + + r, err := perf.NewReader(m, os.Getpagesize()) + if err != nil { + utils.LogError(l, nil, "failed to create perf event reader of socketCloseEvent") + return err + } + + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context") + } + g.Go(func() error { + defer utils.Recover(l) + go func() { + defer utils.Recover(l) + for { + rec, err := r.Read() + if err != nil { + if errors.Is(err, perf.ErrClosed) { + return + } + utils.LogError(l, err, "failed to read from perf socketCloseEvent reader") + continue + } + if rec.LostSamples != 0 { + l.Debug(fmt.Sprintf("perf socketCloseEvent array full, dropped %d samples", rec.LostSamples)) + continue + } + data := rec.RawSample + + var event SocketCloseEvent + if err := binary.Read(bytes.NewReader(data), binary.LittleEndian, &event); err != nil { + l.Debug(fmt.Sprintf("Failed to decode received data: %+v", err)) + continue + } + + event.TimestampNano += getRealTimeOffset() + c.GetOrCreate(event.ConnID).AddCloseEvent(event) + } + }() + + <-ctx.Done() // Check for context cancellation + err := r.Close() + if err != nil { + utils.LogError(l, err, "failed to close perf socketCloseEvent reader") + return err + } + return nil + }) + return nil +} + +// InitRealTimeOffset calculates the offset between the real clock and the monotonic clock used in the BPF. +func initRealTimeOffset() error { + var monotonicTime, realTime unix.Timespec + if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &monotonicTime); err != nil { + return fmt.Errorf("failed getting monotonic clock due to: %v", err) + } + if err := unix.ClockGettime(unix.CLOCK_REALTIME, &realTime); err != nil { + return fmt.Errorf("failed getting real clock time due to: %v", err) + } + realTimeOffset = uint64(time.Second)*(uint64(realTime.Sec)-uint64(monotonicTime.Sec)) + uint64(realTime.Nsec) - uint64(monotonicTime.Nsec) + // realTimeCopy := time.Unix(int64(realTimeOffset/1e9), int64(realTimeOffset%1e9)) + // log.Debug(fmt.Sprintf("%s real time offset is: %v", Emoji, realTimeCopy)) + return nil +} + +// GetRealTimeOffset is a getter for the real-time-offset. +func getRealTimeOffset() uint64 { + return realTimeOffset +} diff --git a/keploy/pkg/core/hooks/conn/tracker.go b/keploy/pkg/core/hooks/conn/tracker.go new file mode 100755 index 0000000..b8b0913 --- /dev/null +++ b/keploy/pkg/core/hooks/conn/tracker.go @@ -0,0 +1,571 @@ +//go:build linux + +package conn + +import ( + "bytes" + "fmt" + "strings" + "sync" + "sync/atomic" + "time" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "golang.org/x/net/http2" + // "log" +) + +// Protocol represents the type of protocol being used +type Protocol int + +// Protocol constants for supported HTTP protocol versions +const ( + // HTTP1 represents HTTP/1.x protocol + HTTP1 Protocol = 1 + // HTTP2 represents HTTP/2 protocol + HTTP2 Protocol = 2 +) + +// StreamManager interface for managing protocol streams +type StreamManager interface { + HandleFrame(frame http2.Frame, isOutgoing bool, timestamp time.Time) error + GetCompleteStreams() []*pkg.HTTP2Stream + CleanupStream(streamID uint32) +} + +// Tracker is a routine-safe container that holds a conn with unique ID, and able to create new conn. +type Tracker struct { + connID ID + addr SockAddrIn + openTimestamp uint64 + closeTimestamp uint64 + + // Indicates the tracker stopped tracking due to closing the session. + lastActivityTimestamp uint64 + + // Queues to handle multiple ingress traffic on the same conn (keep-alive) + + // kernelRespSizes is a slice of the total number of Response bytes received in the kernel side + kernelRespSizes []uint64 + + // kernelReqSizes is a slice of the total number of Request bytes received in the kernel side + kernelReqSizes []uint64 + + // userRespSizes is a slice of the total number of Response bytes received in the user side + userRespSizes []uint64 + + // userReqSizes is a slice of the total number of Request bytes received in the user side + userReqSizes []uint64 + // userRespBufs is a slice of the Response data received in the user side on this conn + userResps [][]byte + // userReqBufs is a slice of the Request data received in the user side on this conn + userReqs [][]byte + + // req and resp are the buffers to store the request and response data for the current request + // reset after 2 seconds of inactivity + respSize uint64 + reqSize uint64 + resp []byte + req []byte + + // Additional fields to know when to capture request or response info + // reset after 2 seconds of inactivity + + lastChunkWasResp bool + lastChunkWasReq bool + recTestCounter int32 //atomic counter + // firstRequest is used to indicate if the current request is the first request on the conn + // reset after 2 seconds of inactivity + // Note: This is used to handle multiple requests on the same conn (keep-alive) + // Its different from isNewRequest which is used to indicate if the current request chunk is the first chunk of the request + firstRequest bool + + mutex sync.RWMutex + logger *zap.Logger + + reqTimestamps []time.Time + isNewRequest bool + + // New fields to support protocol detection and http2 stream management + protocol Protocol + streamMgr StreamManager + protocolDetected bool + buffer []byte +} + +// NewTracker creates a new connection tracker +func NewTracker(connID ID, logger *zap.Logger) *Tracker { + t := &Tracker{ + connID: connID, + logger: logger, + mutex: sync.RWMutex{}, + // Initialize HTTP/1 fields + req: []byte{}, + resp: []byte{}, + kernelRespSizes: []uint64{}, + kernelReqSizes: []uint64{}, + userRespSizes: []uint64{}, + userReqSizes: []uint64{}, + userResps: [][]byte{}, + userReqs: [][]byte{}, + firstRequest: true, + isNewRequest: true, + buffer: make([]byte, 0, pkg.DefaultMaxFrameSize), + } + + // Always start with HTTP/1 + t.protocol = HTTP1 + t.protocolDetected = false // Allow protocol detection + + return t +} + +func (conn *Tracker) ToBytes() ([]byte, []byte) { + conn.mutex.RLock() + defer conn.mutex.RUnlock() + return conn.req, conn.resp +} + +func (conn *Tracker) IsInactive(duration time.Duration) bool { + conn.mutex.RLock() + defer conn.mutex.RUnlock() + return uint64(time.Now().UnixNano())-conn.lastActivityTimestamp > uint64(duration.Nanoseconds()) +} + +func (conn *Tracker) incRecordTestCount() { + atomic.AddInt32(&conn.recTestCounter, 1) +} + +func (conn *Tracker) decRecordTestCount() { + atomic.AddInt32(&conn.recTestCounter, -1) +} + +// reset resets the conn's request and response data buffers. +func (conn *Tracker) reset() { + conn.firstRequest = true + conn.lastChunkWasResp = false + conn.lastChunkWasReq = false + conn.reqSize = 0 + conn.respSize = 0 + conn.resp = []byte{} + conn.req = []byte{} +} + +func (conn *Tracker) verifyRequestData(expectedRecvBytes, actualRecvBytes uint64) bool { + return expectedRecvBytes == actualRecvBytes +} + +func (conn *Tracker) verifyResponseData(expectedSentBytes, actualSentBytes uint64) bool { + return expectedSentBytes == actualSentBytes +} + +// func (conn *Tracker) Malformed() bool { +// conn.mutex.RLock() +// defer conn.mutex.RUnlock() +// // conn.log.Debug("data loss of ingress request message", zap.Any("bytes read in ebpf", conn.totalReadBytes), zap.Any("bytes received in userspace", conn.reqSize)) +// // conn.log.Debug("data loss of ingress response message", zap.Any("bytes written in ebpf", conn.totalWrittenBytes), zap.Any("bytes sent to user", conn.respSize)) +// // conn.log.Debug("", zap.Any("Request buffer", string(conn.req))) +// // conn.log.Debug("", zap.Any("Response buffer", string(conn.resp))) +// return conn.closeTimestamp != 0 && +// conn.totalReadBytes != conn.reqSize && +// conn.totalWrittenBytes != conn.respSize +// } + +func (conn *Tracker) AddOpenEvent(event SocketOpenEvent) { + conn.mutex.Lock() + defer conn.mutex.Unlock() + conn.UpdateTimestamps() + conn.addr = event.Addr + if conn.openTimestamp != 0 && conn.openTimestamp != event.TimestampNano { + conn.logger.Debug("Changed open info timestamp due to new request", zap.Any("from", conn.openTimestamp), zap.Any("to", event.TimestampNano)) + } + conn.openTimestamp = event.TimestampNano +} + +func (conn *Tracker) AddDataEvent(event SocketDataEvent) { + conn.mutex.Lock() + defer conn.mutex.Unlock() + conn.UpdateTimestamps() + + data := event.Msg[:event.MsgSize] + + // Check for HTTP/2 preface if we haven't detected protocol yet + if !conn.protocolDetected { + conn.logger.Debug("Connection check") + if isHTTP2Request(data) { + // Create HTTP/2 parser and stream manager + conn.protocol = HTTP2 + conn.streamMgr = pkg.NewStreamManager(conn.logger) + conn.protocolDetected = true + conn.logger.Debug("Detected HTTP/2 protocol (preface received)") + + // If there's more data after preface, process it as HTTP/2 + if len(data) > 24 { + // Create new event with remaining data + newEvent := event + copy(newEvent.Msg[:], data[24:]) + newEvent.MsgSize = uint32(len(data) - 24) + conn.handleHTTP2Data(newEvent) + } + return + } + + // If we see a valid HTTP/1 request line, mark as HTTP/1 + if isHTTP1Request(data) { + conn.protocolDetected = true + conn.logger.Debug("Detected HTTP/1.x protocol") + } + } + + // Process based on current protocol + switch conn.protocol { + case HTTP2: + conn.handleHTTP2Data(event) + default: + conn.handleHTTP1Data(event) + } +} + +// isHTTP1Request checks if the data starts with a valid HTTP/1 request line +func isHTTP1Request(data []byte) bool { + // Convert to string for easier checking + s := string(data) + + // Check for common HTTP methods + return strings.HasPrefix(s, "GET ") || + strings.HasPrefix(s, "POST ") || + strings.HasPrefix(s, "PUT ") || + strings.HasPrefix(s, "DELETE ") || + strings.HasPrefix(s, "HEAD ") || + strings.HasPrefix(s, "OPTIONS ") || + strings.HasPrefix(s, "PATCH ") +} + +// isHTTP2Request checks if the data starts with the HTTP/2 connection preface +func isHTTP2Request(data []byte) bool { + return len(data) >= 24 && bytes.Equal(data[:24], []byte(pkg.HTTP2Preface)) +} + +func (conn *Tracker) AddCloseEvent(event SocketCloseEvent) { + conn.mutex.Lock() + defer conn.mutex.Unlock() + conn.UpdateTimestamps() + if conn.closeTimestamp != 0 && conn.closeTimestamp != event.TimestampNano { + conn.logger.Debug("Changed close info timestamp due to new request", zap.Any("from", conn.closeTimestamp), zap.Any("to", event.TimestampNano)) + } + conn.closeTimestamp = event.TimestampNano + conn.logger.Debug(fmt.Sprintf("Got a close event from eBPF on connectionId:%v\n", event.ConnID)) +} + +func (conn *Tracker) UpdateTimestamps() { + conn.lastActivityTimestamp = uint64(time.Now().UnixNano()) +} + +// ConvertUnixNanoToTime takes a Unix timestamp in nanoseconds as a uint64 and returns the corresponding time.Time +func ConvertUnixNanoToTime(unixNano uint64) time.Time { + // Unix time is the number of seconds since January 1, 1970 UTC, + // so convert nanoseconds to seconds for time.Unix function + seconds := int64(unixNano / uint64(time.Second)) + nanoRemainder := int64(unixNano % uint64(time.Second)) + return time.Unix(seconds, nanoRemainder) +} + +// getHTTP2CompletedStream returns a completed HTTP2/gRPC stream if available +func (conn *Tracker) getHTTP2CompletedStream() *pkg.HTTP2Stream { + conn.mutex.RLock() + defer conn.mutex.RUnlock() + + if conn.streamMgr == nil { + return nil + } + + streams := conn.streamMgr.GetCompleteStreams() + if len(streams) == 0 { + return nil + } + + // Return the first completed stream + stream := streams[0] + + // Cleanup the processed stream + conn.streamMgr.CleanupStream(stream.ID) + + return stream +} + +// Existing HTTP/1 completion check +func (conn *Tracker) isHTTP1Complete() (bool, []byte, []byte, time.Time, time.Time) { + conn.mutex.RLock() + defer conn.mutex.RUnlock() + + // Get the current timestamp in nanoseconds. + currentTimestamp := uint64(time.Now().UnixNano()) + + // Calculate the time elapsed since the last activity in nanoseconds. + elapsedTime := currentTimestamp - conn.lastActivityTimestamp + + //Caveat: Added a timeout of 4 seconds, after this duration we assume that the last response data event would have come. + // This will ensure that we capture the requests responses where Connection:keep-alive is enabled. + + recordTraffic := false + + requestBuf, responseBuf := []byte{}, []byte{} + + var reqTimestamps, respTimestamp time.Time + + //if recTestCounter > 0, it means that we have num(recTestCounter) of request and response present in the queues to record. + if conn.recTestCounter > 0 { + if (len(conn.userReqSizes) > 0 && len(conn.kernelReqSizes) > 0) && + (len(conn.userRespSizes) > 0 && len(conn.kernelRespSizes) > 0) { + validReq, validRes := false, false + + expectedRecvBytes := conn.userReqSizes[0] + actualRecvBytes := conn.kernelReqSizes[0] + + if expectedRecvBytes == 0 || actualRecvBytes == 0 { + conn.logger.Warn("Malformed request", zap.Any("ExpectedRecvBytes", expectedRecvBytes), zap.Any("ActualRecvBytes", actualRecvBytes)) + } + + //popping out the current request info + conn.userReqSizes = conn.userReqSizes[1:] + conn.kernelReqSizes = conn.kernelReqSizes[1:] + + if conn.verifyRequestData(expectedRecvBytes, actualRecvBytes) { + validReq = true + } else { + conn.logger.Debug("Malformed request", zap.Any("ExpectedRecvBytes", expectedRecvBytes), zap.Any("ActualRecvBytes", actualRecvBytes)) + } + + expectedSentBytes := conn.userRespSizes[0] + actualSentBytes := conn.kernelRespSizes[0] + + //popping out the current response info + conn.userRespSizes = conn.userRespSizes[1:] + conn.kernelRespSizes = conn.kernelRespSizes[1:] + + if conn.verifyResponseData(expectedSentBytes, actualSentBytes) { + validRes = true + respTimestamp = time.Now() + } else { + conn.logger.Debug("Malformed response", zap.Any("ExpectedSentBytes", expectedSentBytes), zap.Any("ActualSentBytes", actualSentBytes)) + } + + if len(conn.userReqs) > 0 && len(conn.userResps) > 0 { //validated request, response + requestBuf = conn.userReqs[0] + responseBuf = conn.userResps[0] + + //popping out the current request & response data + conn.userReqs = conn.userReqs[1:] + conn.userResps = conn.userResps[1:] + } else { + conn.logger.Debug("no data buffer for request or response", zap.Any("Length of RecvBufQueue", len(conn.userReqs)), zap.Any("Length of SentBufQueue", len(conn.userResps))) + } + + recordTraffic = validReq && validRes + } else { + utils.LogError(conn.logger, nil, "malformed request or response") + recordTraffic = false + } + + conn.logger.Debug(fmt.Sprintf("recording traffic after verifying the request and reponse data:%v", recordTraffic)) + + // // decrease the recTestCounter + conn.decRecordTestCount() + conn.logger.Debug("verified recording", zap.Any("recordTraffic", recordTraffic)) + } else if conn.lastChunkWasResp && elapsedTime >= uint64(time.Second*2) { // Check if 2 seconds has passed since the last activity. + conn.logger.Debug("might be last request on the conn") + + if len(conn.userReqSizes) > 0 && len(conn.kernelReqSizes) > 0 { + + expectedRecvBytes := conn.userReqSizes[0] + actualRecvBytes := conn.kernelReqSizes[0] + + //popping out the current request info + conn.userReqSizes = conn.userReqSizes[1:] + conn.kernelReqSizes = conn.kernelReqSizes[1:] + + if expectedRecvBytes == 0 || actualRecvBytes == 0 { + conn.logger.Warn("Malformed request", zap.Any("ExpectedRecvBytes", expectedRecvBytes), zap.Any("ActualRecvBytes", actualRecvBytes)) + } + + if conn.verifyRequestData(expectedRecvBytes, actualRecvBytes) { + recordTraffic = true + } else { + conn.logger.Debug("Malformed request", zap.Any("ExpectedRecvBytes", expectedRecvBytes), zap.Any("ActualRecvBytes", actualRecvBytes)) + recordTraffic = false + } + + if len(conn.userReqs) > 0 { //validated request, invalided response + requestBuf = conn.userReqs[0] + //popping out the current request data + conn.userReqs = conn.userReqs[1:] + + responseBuf = conn.resp + respTimestamp = time.Now() + } else { + conn.logger.Debug("no data buffer for request", zap.Any("Length of RecvBufQueue", len(conn.userReqs))) + recordTraffic = false + } + + } else { + utils.LogError(conn.logger, nil, "malformed request") + recordTraffic = false + } + + conn.logger.Debug(fmt.Sprintf("recording traffic after verifying the request data (but not response data):%v", recordTraffic)) + //treat immediate next request as first request (2 seconds after last activity) + // this can be to avoid potential corruption in the conn + conn.reset() + + conn.logger.Debug("unverified recording", zap.Any("recordTraffic", recordTraffic)) + } + + // Checking if record traffic is recorded and request & response timestamp is captured or not. + if recordTraffic { + if len(conn.reqTimestamps) > 0 { + // Get the timestamp of current request + reqTimestamps = conn.reqTimestamps[0] + // Pop the timestamp of current request + conn.reqTimestamps = conn.reqTimestamps[1:] + } else { + conn.logger.Debug("no request timestamp found") + if len(requestBuf) > 0 { + reqLine := strings.Split(string(requestBuf), "\n") + if models.GetMode() == models.MODE_RECORD && len(reqLine) > 0 && reqLine[0] != "" { + conn.logger.Warn(fmt.Sprintf("failed to capture request timestamp for a request. Please record it again if important:%v", reqLine[0])) + } + } + recordTraffic = false + } + + conn.logger.Debug(fmt.Sprintf("TestRequestTimestamp:%v || TestResponseTimestamp:%v", reqTimestamps, respTimestamp)) + } + + return recordTraffic, requestBuf, responseBuf, reqTimestamps, respTimestamp +} + +// Add HTTP/2 specific handling +func (conn *Tracker) handleHTTP2Data(event SocketDataEvent) { + // Convert fixed-size array to slice + data := event.Msg[:event.MsgSize] + + // Append new data to the buffer + conn.buffer = append(conn.buffer, data...) + + // Process as many complete frames as possible + for len(conn.buffer) >= 9 { // Minimum frame size + frame, consumed, err := pkg.ExtractHTTP2Frame(conn.buffer) + if err != nil { + if strings.Contains(err.Error(), "incomplete frame") { + conn.logger.Debug("Incomplete frame", zap.Any("error", err)) + // Not enough data yet, wait for more + break + } + // Real error, log and remove the problematic data + conn.logger.Error("Failed to extract HTTP/2 frame", zap.Error(err)) + if len(conn.buffer) > 9 { + // Try to recover by removing the first byte and trying again next time + conn.buffer = conn.buffer[1:] + } else { + conn.buffer = nil + } + break + } + + // Handle the frame + if err := conn.streamMgr.HandleFrame(frame, event.Direction == EgressTraffic, ConvertUnixNanoToTime(event.TimestampNano)); err != nil { + conn.logger.Error("Failed to handle HTTP/2 frame", zap.Error(err)) + } + + // Remove processed data from buffer + conn.buffer = conn.buffer[consumed:] + } + + // Store timestamps for requests + if event.Direction == IngressTraffic { + conn.reqTimestamps = append(conn.reqTimestamps, ConvertUnixNanoToTime(event.EntryTimestampNano)) + } +} + +// Existing HTTP/1 handling +func (conn *Tracker) handleHTTP1Data(event SocketDataEvent) { + conn.logger.Debug(fmt.Sprintf("Got a data event from eBPF, Direction:%v || current Event Size:%v || ConnectionID:%v\n", event.Direction, event.MsgSize, event.ConnID)) + + switch event.Direction { + case EgressTraffic: + // Capturing the timestamp of response as the response just started to come. + // This is to ensure that we capture the response timestamp for the first chunk of the response. + if !conn.isNewRequest { + conn.isNewRequest = true + } + + // Assign the size of the message to the variable msgLength + msgLength := event.MsgSize + // If the size of the message exceeds the maximum allowed size, + // set msgLength to the maximum allowed size instead + if event.MsgSize > EventBodyMaxSize { + msgLength = EventBodyMaxSize + } + // Append the message (up to msgLength) to the conn's sent buffer + conn.resp = append(conn.resp, event.Msg[:msgLength]...) + conn.respSize += uint64(event.MsgSize) + + //Handling multiple request on same conn to support conn:keep-alive + if conn.firstRequest || conn.lastChunkWasReq { + conn.userReqSizes = append(conn.userReqSizes, conn.reqSize) + conn.reqSize = 0 + + conn.userReqs = append(conn.userReqs, conn.req) + conn.req = []byte{} + + conn.lastChunkWasReq = false + conn.lastChunkWasResp = true + + conn.kernelReqSizes = append(conn.kernelReqSizes, uint64(event.ValidateReadBytes)) + conn.firstRequest = false + } + + case IngressTraffic: + conn.logger.Debug("isNewRequest", zap.Any("isNewRequest", conn.isNewRequest), zap.Any("connID", conn.connID)) + // Capturing the timestamp of request as the request just started to come. + if conn.isNewRequest { + conn.reqTimestamps = append(conn.reqTimestamps, ConvertUnixNanoToTime(event.EntryTimestampNano)) + conn.isNewRequest = false + } + + // Assign the size of the message to the variable msgLength + msgLength := event.MsgSize + // If the size of the message exceeds the maximum allowed size, + // set msgLength to the maximum allowed size instead + if event.MsgSize > EventBodyMaxSize { + msgLength = EventBodyMaxSize + } + // Append the message (up to msgLength) to the conn's receive buffer + conn.req = append(conn.req, event.Msg[:msgLength]...) + conn.reqSize += uint64(event.MsgSize) + + //Handling multiple request on same conn to support conn:keep-alive + if conn.lastChunkWasResp { + // conn.userRespSizes is the total numner of bytes received in the user side + // consumer for the last response. + conn.userRespSizes = append(conn.userRespSizes, conn.respSize) + conn.respSize = 0 + + conn.userResps = append(conn.userResps, conn.resp) + conn.resp = []byte{} + + conn.lastChunkWasReq = true + conn.lastChunkWasResp = false + + conn.kernelRespSizes = append(conn.kernelRespSizes, uint64(event.ValidateWrittenBytes)) + + //Record a test case for the current request/ + conn.incRecordTestCount() + } + + default: + } +} diff --git a/keploy/pkg/core/hooks/conn/util.go b/keploy/pkg/core/hooks/conn/util.go new file mode 100755 index 0000000..4c480d4 --- /dev/null +++ b/keploy/pkg/core/hooks/conn/util.go @@ -0,0 +1,329 @@ +package conn + +import ( + "bytes" + "context" + "encoding/base64" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + "time" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +var ( + realTimeOffset uint64 +) + +// convertUnixNanoToTime takes a Unix timestamp in nanoseconds as a uint64 and returns the corresponding time.Time +func convertUnixNanoToTime(unixNano uint64) time.Time { + // Unix time is the number of seconds since January 1, 1970 UTC, + // so convert nanoseconds to seconds for time.Unix function + seconds := int64(unixNano / uint64(time.Second)) + nanoRemainder := int64(unixNano % uint64(time.Second)) + return time.Unix(seconds, nanoRemainder) +} + +func isFiltered(logger *zap.Logger, req *http.Request, opts models.IncomingOptions) bool { + dstPort := 0 + var err error + if p := req.URL.Port(); p != "" { + dstPort, err = strconv.Atoi(p) + if err != nil { + utils.LogError(logger, err, "failed to obtain destination port from request") + return false + } + } + + var passThrough bool + + type cond struct { + eligible bool + match bool + } + + for _, filter := range opts.Filters { + + // 1. bypass rule + bypassEligible := !(filter.BypassRule.Host == "" && + filter.BypassRule.Path == "" && + filter.BypassRule.Port == 0) + + opts := models.OutgoingOptions{Rules: []config.BypassRule{filter.BypassRule}} + byPassMatch := utils.IsPassThrough(logger, req, uint(dstPort), opts) + + // 2. URL-method rule + urlMethodEligible := len(filter.URLMethods) > 0 + urlMethodMatch := false + if urlMethodEligible { + for _, m := range filter.URLMethods { + if m == req.Method { + urlMethodMatch = true + break + } + } + } + + // 3. header rule + headerEligible := len(filter.Headers) > 0 + headerMatch := false + if headerEligible { + for key, vals := range filter.Headers { + rx, err := regexp.Compile(vals) + if err != nil { + utils.LogError(logger, err, "bad header regex") + continue + } + for _, v := range req.Header.Values(key) { + if rx.MatchString(v) { + headerMatch = true + break + } + } + if headerMatch { + break + } + } + } + + conds := []cond{ + {bypassEligible, byPassMatch}, + {urlMethodEligible, urlMethodMatch}, + {headerEligible, headerMatch}, + } + + switch filter.MatchType { + case config.AND: + pass := true + seen := false + for _, c := range conds { + if !c.eligible { + continue + } // ignore ineligible ones + seen = true + if !c.match { + pass = false + break + } + } + if seen && pass { + passThrough = true + return passThrough + } + + case config.OR: + fallthrough + default: + for _, c := range conds { + if c.eligible && c.match { + passThrough = true + return passThrough + } + } + } + } + + return passThrough +} + +//// LogAny appends input of any type to a logs.txt file in the current directory +//func LogAny(value string) error { +// +// logMessage := value +// +// // Add a timestamp to the log message +// timestamp := time.Now().Format("2006-01-02 15:04:05") +// logLine := fmt.Sprintf("%s - %s\n", timestamp, logMessage) +// +// // Open logs.txt in append mode, create it if it doesn't exist +// file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) +// if err != nil { +// return err +// } +// defer file.Close() +// +// // Write the log line to the file +// _, err = file.WriteString(logLine) +// if err != nil { +// return err +// } +// +// return nil +//} + +func Capture(_ context.Context, logger *zap.Logger, t chan *models.TestCase, req *http.Request, resp *http.Response, reqTimeTest time.Time, resTimeTest time.Time, opts models.IncomingOptions) { + + var reqBody []byte + if req.Body != nil { // Read + var err error + reqBody, err = io.ReadAll(req.Body) + if err != nil { + logger.Warn("failed to read the http request body", zap.Any("metadata", utils.GetReqMeta(req)), zap.Int64("of size", int64(len(reqBody))), zap.String("body", base64.StdEncoding.EncodeToString(reqBody)), zap.Error(err)) + } + + if req.Header.Get("Content-Encoding") != "" { + reqBody, err = pkg.Decompress(logger, req.Header.Get("Content-Encoding"), reqBody) + if err != nil { + utils.LogError(logger, err, "failed to decode the http request body", zap.Any("metadata", utils.GetReqMeta(req))) + return + } + } + } + + defer func() { + err := resp.Body.Close() + if err != nil { + utils.LogError(logger, err, "failed to close the http response body") + } + }() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + utils.LogError(logger, err, "failed to read the http response body") + return + } + + if isFiltered(logger, req, opts) { + logger.Debug("The request is a filtered request") + return + } + var formData []models.FormData + if contentType := req.Header.Get("Content-Type"); strings.HasPrefix(contentType, "multipart/form-data") { + parts := strings.Split(contentType, ";") + if len(parts) > 1 { + req.Header.Set("Content-Type", strings.TrimSpace(parts[0])) + } + formData = extractFormData(logger, reqBody, contentType) + reqBody = []byte{} + } else if contentType := req.Header.Get("Content-Type"); contentType == "application/x-www-form-urlencoded" { + decodedBody, err := url.QueryUnescape(string(reqBody)) + if err != nil { + utils.LogError(logger, err, "failed to decode the url-encoded request body") + return + } + reqBody = []byte(decodedBody) + } + + if resp.Header.Get("Content-Encoding") != "" { + respBody, err = pkg.Decompress(logger, resp.Header.Get("Content-Encoding"), respBody) + if err != nil { + utils.LogError(logger, err, "failed to decompress the response body") + return + } + } + + t <- &models.TestCase{ + Version: models.GetVersion(), + Name: pkg.ToYamlHTTPHeader(req.Header)["Keploy-Test-Name"], + Kind: models.HTTP, + Created: time.Now().Unix(), + HTTPReq: models.HTTPReq{ + Method: models.Method(req.Method), + ProtoMajor: req.ProtoMajor, + ProtoMinor: req.ProtoMinor, + // URL: req.URL.String(), + // URL: fmt.Sprintf("%s://%s%s?%s", req.URL.Scheme, req.Host, req.URL.Path, req.URL.RawQuery), + URL: fmt.Sprintf("http://%s%s", req.Host, req.URL.RequestURI()), + // URL: string(b), + Form: formData, + Header: pkg.ToYamlHTTPHeader(req.Header), + Body: string(reqBody), + URLParams: pkg.URLParams(req), + Timestamp: reqTimeTest, + }, + HTTPResp: models.HTTPResp{ + StatusCode: resp.StatusCode, + Header: pkg.ToYamlHTTPHeader(resp.Header), + Body: string(respBody), + Timestamp: resTimeTest, + StatusMessage: http.StatusText(resp.StatusCode), + }, + Noise: map[string][]string{}, + // Mocks: mocks, + } +} + +func extractFormData(logger *zap.Logger, body []byte, contentType string) []models.FormData { + boundary := "" + if strings.HasPrefix(contentType, "multipart/form-data") { + parts := strings.Split(contentType, "boundary=") + if len(parts) > 1 { + boundary = strings.TrimSpace(parts[1]) + } else { + utils.LogError(logger, nil, "Invalid multipart/form-data content type") + return nil + } + } + reader := multipart.NewReader(bytes.NewReader(body), boundary) + var formData []models.FormData + + for { + part, err := reader.NextPart() + if err == io.EOF { + break + } + if err != nil { + utils.LogError(logger, err, "Error reading part") + continue + } + key := part.FormName() + if key == "" { + continue + } + + value, err := io.ReadAll(part) + if err != nil { + utils.LogError(logger, err, "Error reading part value") + continue + } + + formData = append(formData, models.FormData{ + Key: key, + Values: []string{string(value)}, + }) + } + + return formData +} + +// CaptureGRPC captures a gRPC request/response pair and sends it to the test case channel +func CaptureGRPC(ctx context.Context, logger *zap.Logger, t chan *models.TestCase, http2Stream *pkg.HTTP2Stream) { + if http2Stream == nil { + logger.Error("Stream is nil") + return + } + + if http2Stream.GRPCReq == nil || http2Stream.GRPCResp == nil { + logger.Error("gRPC request or response is nil") + return + } + + // Create test case from stream data + testCase := &models.TestCase{ + Version: models.GetVersion(), + Name: http2Stream.GRPCReq.Headers.OrdinaryHeaders["Keploy-Test-Name"], + Kind: models.GRPC_EXPORT, + Created: time.Now().Unix(), + GrpcReq: *http2Stream.GRPCReq, + GrpcResp: *http2Stream.GRPCResp, + Noise: map[string][]string{}, + } + + select { + case <-ctx.Done(): + return + case t <- testCase: + logger.Debug("Captured gRPC test case", + zap.String("path", http2Stream.GRPCReq.Headers.PseudoHeaders[":path"])) + } +} diff --git a/keploy/pkg/core/hooks/hooks.go b/keploy/pkg/core/hooks/hooks.go new file mode 100644 index 0000000..b29f24c --- /dev/null +++ b/keploy/pkg/core/hooks/hooks.go @@ -0,0 +1,713 @@ +//go:build linux + +// Package hooks provides functionality for managing hooks. +package hooks + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + "sync" + + "golang.org/x/sync/errgroup" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/utils" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/rlimit" + + "go.keploy.io/server/v2/pkg/core" + "go.keploy.io/server/v2/pkg/core/hooks/conn" + "go.keploy.io/server/v2/pkg/core/hooks/structs" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +func NewHooks(logger *zap.Logger, cfg *config.Config) *Hooks { + return &Hooks{ + logger: logger, + sess: core.NewSessions(), + m: sync.Mutex{}, + objectsMutex: sync.RWMutex{}, + proxyIP4: "127.0.0.1", + proxyIP6: [4]uint32{0000, 0000, 0000, 0001}, + proxyPort: cfg.ProxyPort, + dnsPort: cfg.DNSPort, + conf: cfg, + retprobeMaxActive: 1024, + unloadDone: make(chan struct{}), + } +} + +type Hooks struct { + logger *zap.Logger + sess *core.Sessions + proxyIP4 string + proxyIP6 [4]uint32 + proxyPort uint32 + dnsPort uint32 + m sync.Mutex + objectsMutex sync.RWMutex // Protects eBPF objects during load/unload operations + conf *config.Config + // Channel to signal when unload is complete + unloadDone chan struct{} + unloadDoneMutex sync.Mutex // Protects unloadDone channel operations + // eBPF C shared maps + clientRegistrationMap *ebpf.Map + agentRegistartionMap *ebpf.Map + dockerAppRegistrationMap *ebpf.Map + redirectProxyMap *ebpf.Map + e2eAppRegistrationMap *ebpf.Map + //-------------- + + // eBPF C shared objectsobjects + // ebpf objects and events + socket link.Link + connect4 link.Link + gp4 link.Link + udpp4 link.Link + tcppv4 link.Link + tcpv4 link.Link + tcpv4Ret link.Link + connect6 link.Link + gp6 link.Link + tcppv6 link.Link + tcpv6 link.Link + tcpv6Ret link.Link + + connect link.Link + connectRet link.Link + + accept link.Link + acceptRet link.Link + accept4 link.Link + accept4Ret link.Link + read link.Link + readRet link.Link + write link.Link + writeRet link.Link + close link.Link + closeRet link.Link + sendto link.Link + sendtoRet link.Link + recvfrom link.Link + recvfromRet link.Link + objects bpfObjects + writev link.Link + writevRet link.Link + readv link.Link + readvRet link.Link + retprobeMaxActive int + appID uint64 +} + +func (h *Hooks) Load(ctx context.Context, id uint64, opts core.HookCfg) error { + + h.sess.Set(id, &core.Session{ + ID: id, + }) + + // Set the app ID for this session with proper synchronization + h.m.Lock() + h.appID = id + h.m.Unlock() + + // Reset the unload done channel for this new load + h.unloadDoneMutex.Lock() + h.unloadDone = make(chan struct{}) + h.unloadDoneMutex.Unlock() + + err := h.load(ctx, opts) + if err != nil { + return err + } + + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context") + } + + g.Go(func() error { + defer utils.Recover(h.logger) + <-ctx.Done() + h.unLoad(ctx, opts) + + //deleting in order to free the memory in case of rerecord. + h.sess.Delete(id) + + // Signal that unload is complete + h.unloadDoneMutex.Lock() + close(h.unloadDone) + h.unloadDoneMutex.Unlock() + return nil + }) + + return nil +} + +// GetUnloadDone returns a channel that will be closed when the hooks are completely unloaded +func (h *Hooks) GetUnloadDone() <-chan struct{} { + h.unloadDoneMutex.Lock() + defer h.unloadDoneMutex.Unlock() + return h.unloadDone +} + +func (h *Hooks) load(ctx context.Context, opts core.HookCfg) error { + // Allow the current process to lock memory for eBPF resources. + if err := rlimit.RemoveMemlock(); err != nil { + utils.LogError(h.logger, err, "failed to lock memory for eBPF resources") + return err + } + + // Load pre-compiled programs and maps into the kernel. + objs := bpfObjects{} + if err := loadBpfObjects(&objs, nil); err != nil { + var ve *ebpf.VerifierError + if errors.As(err, &ve) { + errString := strings.Join(ve.Log, "\n") + h.logger.Debug("verifier log: ", zap.String("err", errString)) + } + utils.LogError(h.logger, err, "failed to load eBPF objects") + return err + } + + //getting all the ebpf maps with proper synchronization + h.objectsMutex.Lock() + h.clientRegistrationMap = objs.KeployClientRegistrationMap + h.agentRegistartionMap = objs.KeployAgentRegistrationMap + h.e2eAppRegistrationMap = objs.E2eInfoMap + h.dockerAppRegistrationMap = objs.DockerAppRegistrationMap + h.objects = objs + h.objectsMutex.Unlock() + + // --------------- + + // ----- used in case of wsl ----- + socket, err := link.Kprobe("sys_socket", objs.SyscallProbeEntrySocket, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_socket") + return err + } + h.socket = socket + + if !opts.E2E { + h.redirectProxyMap = objs.RedirectProxyMap + h.objects = objs + // ------------ For Egress ------------- + udppC4, err := link.Kprobe("udp_pre_connect", objs.SyscallProbeEntryUdpPreConnect, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on udp_pre_connect") + return err + } + h.udpp4 = udppC4 + + // FOR IPV4 + tcppC4, err := link.Kprobe("tcp_v4_pre_connect", objs.SyscallProbeEntryTcpV4PreConnect, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on tcp_v4_pre_connect") + return err + } + h.tcppv4 = tcppC4 + + tcpC4, err := link.Kprobe("tcp_v4_connect", objs.SyscallProbeEntryTcpV4Connect, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on tcp_v4_connect") + return err + } + h.tcpv4 = tcpC4 + + tcpRC4, err := link.Kretprobe("tcp_v4_connect", objs.SyscallProbeRetTcpV4Connect, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on tcp_v4_connect") + return err + } + h.tcpv4Ret = tcpRC4 + + // Get the first-mounted cgroupv2 path. + cGroupPath, err := detectCgroupPath(h.logger) + if err != nil { + utils.LogError(h.logger, err, "failed to detect the cgroup path") + return err + } + + c4, err := link.AttachCgroup(link.CgroupOptions{ + Path: cGroupPath, + Attach: ebpf.AttachCGroupInet4Connect, + Program: objs.K_connect4, + }) + + if err != nil { + utils.LogError(h.logger, err, "failed to attach the connect4 cgroup hook") + return err + } + h.connect4 = c4 + + gp4, err := link.AttachCgroup(link.CgroupOptions{ + Path: cGroupPath, + Attach: ebpf.AttachCgroupInet4GetPeername, + Program: objs.K_getpeername4, + }) + + if err != nil { + utils.LogError(h.logger, err, "failed to attach the GetPeername4 cgroup hook") + return err + } + h.gp4 = gp4 + + // FOR IPV6 + + tcpPreC6, err := link.Kprobe("tcp_v6_pre_connect", objs.SyscallProbeEntryTcpV6PreConnect, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on tcp_v6_pre_connect") + return err + } + h.tcppv6 = tcpPreC6 + + tcpC6, err := link.Kprobe("tcp_v6_connect", objs.SyscallProbeEntryTcpV6Connect, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on tcp_v6_connect") + return err + } + h.tcpv6 = tcpC6 + + tcpRC6, err := link.Kretprobe("tcp_v6_connect", objs.SyscallProbeRetTcpV6Connect, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on tcp_v6_connect") + return err + } + h.tcpv6Ret = tcpRC6 + + c6, err := link.AttachCgroup(link.CgroupOptions{ + Path: cGroupPath, + Attach: ebpf.AttachCGroupInet6Connect, + Program: objs.K_connect6, + }) + + if err != nil { + utils.LogError(h.logger, err, "failed to attach the connect6 cgroup hook") + return err + } + h.connect6 = c6 + + gp6, err := link.AttachCgroup(link.CgroupOptions{ + Path: cGroupPath, + Attach: ebpf.AttachCgroupInet6GetPeername, + Program: objs.K_getpeername6, + }) + + if err != nil { + utils.LogError(h.logger, err, "failed to attach the GetPeername6 cgroup hook") + return err + } + h.gp6 = gp6 + } + + // The hook sys_connect is used to identify outgoing connections to avoid misclassifying reused FDs + // as incoming, especially when analyzing `write` syscalls. + + //Open a kprobe at the entry of connect syscall + cnt, err := link.Kprobe("sys_connect", objs.SyscallProbeEntryConnect, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_connect") + return err + } + h.connect = cnt + + //Opening a kretprobe at the exit of connect syscall + cntr, err := link.Kretprobe("sys_connect", objs.SyscallProbeRetConnect, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_connect") + return err + } + h.connectRet = cntr + + // ------------ For Ingress using Kprobes -------------- + + //Open a kprobe at the entry of sendto syscall + snd, err := link.Kprobe("sys_sendto", objs.SyscallProbeEntrySendto, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_sendto") + return err + } + h.sendto = snd + + //Opening a kretprobe at the exit of sendto syscall + sndr, err := link.Kretprobe("sys_sendto", objs.SyscallProbeRetSendto, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_sendto") + return err + } + h.sendtoRet = sndr + + // Open a Kprobe at the entry point of the kernel function and attach the + // pre-compiled program. + ac, err := link.Kprobe("sys_accept", objs.SyscallProbeEntryAccept, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_accept") + return err + } + h.accept = ac + + // Open a Kprobe at the exit point of the kernel function and attach the + // pre-compiled program. + acRet, err := link.Kretprobe("sys_accept", objs.SyscallProbeRetAccept, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_accept") + return err + } + h.acceptRet = acRet + + // Open a Kprobe at the entry point of the kernel function and attach the + // pre-compiled program. + ac4, err := link.Kprobe("sys_accept4", objs.SyscallProbeEntryAccept4, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_accept4") + return err + } + h.accept4 = ac4 + + // Open a Kprobe at the exit point of the kernel function and attach the + // pre-compiled program. + ac4Ret, err := link.Kretprobe("sys_accept4", objs.SyscallProbeRetAccept4, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_accept4") + return err + } + h.accept4Ret = ac4Ret + + // Open a Kprobe at the entry point of the kernel function and attach the + // pre-compiled program. + rd, err := link.Kprobe("sys_read", objs.SyscallProbeEntryRead, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_read") + return err + } + h.read = rd + + // Open a Kprobe at the exit point of the kernel function and attach the + // pre-compiled program. + rdRet, err := link.Kretprobe("sys_read", objs.SyscallProbeRetRead, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_read") + return err + } + h.readRet = rdRet + + // Open a Kprobe at the entry point of the kernel function and attach the + // pre-compiled program. + wt, err := link.Kprobe("sys_write", objs.SyscallProbeEntryWrite, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_write") + return err + } + h.write = wt + + // Open a Kprobe at the exit point of the kernel function and attach the + // pre-compiled program. + wtRet, err := link.Kretprobe("sys_write", objs.SyscallProbeRetWrite, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_write") + return err + } + h.writeRet = wtRet + + // Open a Kprobe at the entry point of the kernel function and attach the + // pre-compiled program for readv. + readv, err := link.Kprobe("sys_readv", objs.SyscallProbeEntryReadv, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_readv") + return err + } + h.readv = readv + + // Open a Kprobe at the exit point of the kernel function and attach the + // pre-compiled program for readv. + readvRet, err := link.Kretprobe("sys_readv", objs.SyscallProbeRetReadv, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_readv") + return err + } + h.readvRet = readvRet + + // Open a Kprobe at the entry point of the kernel function and attach the + // pre-compiled program for writev. + wtv, err := link.Kprobe("sys_writev", objs.SyscallProbeEntryWritev, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_writev") + return err + } + h.writev = wtv + + // Open a Kprobe at the exit point of the kernel function and attach the + // pre-compiled program for writev. + wtvRet, err := link.Kretprobe("sys_writev", objs.SyscallProbeRetWritev, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_writev") + return err + } + h.writevRet = wtvRet + + // Open a Kprobe at the entry point of the kernel function and attach the + // pre-compiled program. + cl, err := link.Kprobe("sys_close", objs.SyscallProbeEntryClose, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_close") + return err + } + h.close = cl + + //Attaching a kprobe at the entry of recvfrom syscall + rcv, err := link.Kprobe("sys_recvfrom", objs.SyscallProbeEntryRecvfrom, nil) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_recvfrom") + return err + } + h.recvfrom = rcv + + //Attaching a kretprobe at the exit of recvfrom syscall + rcvr, err := link.Kretprobe("sys_recvfrom", objs.SyscallProbeRetRecvfrom, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_recvfrom") + return err + } + h.recvfromRet = rcvr + + // Open a Kprobe at the exit point of the kernel function and attach the + // pre-compiled program. + clRet, err := link.Kretprobe("sys_close", objs.SyscallProbeRetClose, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) + if err != nil { + utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_close") + return err + } + h.closeRet = clRet + + h.logger.Info("keploy initialized and probes added to the kernel.") + + var clientInfo = structs.ClientInfo{} + + switch opts.Mode { + case models.MODE_RECORD: + clientInfo.Mode = uint32(1) + case models.MODE_TEST: + clientInfo.Mode = uint32(2) + default: + clientInfo.Mode = uint32(0) + } + + //sending keploy pid to kernel to get filtered + inode, err := getSelfInodeNumber() + if err != nil { + utils.LogError(h.logger, err, "failed to get inode of the keploy process") + return err + } + + clientInfo.KeployClientInode = inode + clientInfo.KeployClientNsPid = uint32(os.Getpid()) + if opts.E2E { + pid, err := utils.GetPIDFromPort(ctx, h.logger, int(opts.Port)) + if err != nil { + utils.LogError(h.logger, err, "failed to get the keploy pid from the port in case of e2e") + return err + } + err = h.SendE2EInfo(pid) + if err != nil { + h.logger.Error("failed to send e2e info to the ebpf program", zap.Error(err)) + } + } + + clientInfo.IsKeployClientRegistered = uint32(0) + + if opts.IsDocker { + h.proxyIP4 = opts.KeployIPV4 + ipv6, err := ToIPv4MappedIPv6(opts.KeployIPV4) + if err != nil { + return fmt.Errorf("failed to convert ipv4:%v to ipv4 mapped ipv6 in docker env:%v", opts.KeployIPV4, err) + } + h.logger.Debug(fmt.Sprintf("IPv4-mapped IPv6 for %s is: %08x:%08x:%08x:%08x\n", h.proxyIP4, ipv6[0], ipv6[1], ipv6[2], ipv6[3])) + h.proxyIP6 = ipv6 + } + + h.logger.Debug("proxy ips", zap.String("ipv4", h.proxyIP4), zap.Any("ipv6", h.proxyIP6)) + + proxyIP, err := IPv4ToUint32(h.proxyIP4) + if err != nil { + return fmt.Errorf("failed to convert ip string:[%v] to 32-bit integer", opts.KeployIPV4) + } + + var agentInfo = structs.AgentInfo{} + + agentInfo.ProxyInfo = structs.ProxyInfo{ + IP4: proxyIP, + IP6: h.proxyIP6, + Port: h.proxyPort, + } + + agentInfo.DNSPort = int32(h.dnsPort) + + if opts.IsDocker { + clientInfo.IsDockerApp = uint32(1) + } else { + clientInfo.IsDockerApp = uint32(0) + } + + ports := GetPortToSendToKernel(ctx, opts.Rules) + for i := 0; i < 10; i++ { + if len(ports) <= i { + clientInfo.PassThroughPorts[i] = -1 + continue + } + clientInfo.PassThroughPorts[i] = int32(ports[i]) + } + + err = h.SendClientInfo(opts.AppID, clientInfo) + if err != nil { + h.logger.Error("failed to send app info to the ebpf program", zap.Error(err)) + return err + } + err = h.SendAgentInfo(agentInfo) + if err != nil { + h.logger.Error("failed to send agent info to the ebpf program", zap.Error(err)) + return err + } + + return nil +} + +func (h *Hooks) Record(ctx context.Context, _ uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) { + // TODO use the session to get the app id + // and then use the app id to get the test cases chan + // and pass that to eBPF consumers/listeners + return conn.ListenSocket(ctx, h.logger, h.objects.SocketOpenEvents, h.objects.SocketDataEvents, h.objects.SocketCloseEvents, opts) +} + +func (h *Hooks) unLoad(_ context.Context, opts core.HookCfg) { + // closing all events + //other + if err := h.socket.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the socket") + } + + // Reset the app ID with proper synchronization + h.m.Lock() + h.appID = 0 + h.m.Unlock() + + if !opts.E2E { + if err := h.udpp4.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the udpp4") + } + + if err := h.connect4.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the connect4") + } + + if err := h.gp4.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the gp4") + } + + if err := h.tcppv4.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the tcppv4") + } + + if err := h.tcpv4.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the tcpv4") + } + + if err := h.tcpv4Ret.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the tcpv4Ret") + } + + if err := h.connect6.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the connect6") + } + if err := h.gp6.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the gp6") + } + if err := h.tcppv6.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the tcppv6") + } + if err := h.tcpv6.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the tcpv6") + } + if err := h.tcpv6Ret.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the tcpv6Ret") + } + } + if err := h.accept.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the accept") + } + if err := h.acceptRet.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the acceptRet") + } + if err := h.accept4.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the accept4") + } + if err := h.accept4Ret.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the accept4Ret") + } + if err := h.read.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the read") + } + if err := h.readRet.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the readRet") + } + if err := h.write.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the write") + } + if err := h.writeRet.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the writeRet") + } + if err := h.writev.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the writev") + } + if err := h.writevRet.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the writevRet") + } + + if err := h.readv.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the readv") + } + if err := h.readvRet.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the readvRet") + } + + if err := h.close.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the close") + } + if err := h.closeRet.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the closeRet") + } + if err := h.sendto.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the sendto") + } + if err := h.sendtoRet.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the sendtoRet") + } + if err := h.recvfrom.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the recvfrom") + } + if err := h.recvfromRet.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the recvfromRet") + } + + // Close eBPF objects with proper synchronization + h.objectsMutex.Lock() + if err := h.objects.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the objects") + } + h.objectsMutex.Unlock() + + if err := h.connect.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the connect") + } + + if err := h.connectRet.Close(); err != nil { + utils.LogError(h.logger, err, "failed to close the connectRet") + } + + h.logger.Info("eBPF resources released successfully...") +} diff --git a/keploy/pkg/core/hooks/hooks_test.go b/keploy/pkg/core/hooks/hooks_test.go new file mode 100644 index 0000000..82e35bc --- /dev/null +++ b/keploy/pkg/core/hooks/hooks_test.go @@ -0,0 +1,273 @@ +//go:build linux + +package hooks + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" +) + +func TestNewHooks_UnloadDoneChannel(t *testing.T) { + logger := zap.NewNop() + cfg := &config.Config{ + ProxyPort: 6789, + DNSPort: 26789, + } + + hooks := NewHooks(logger, cfg) + + // Verify that the unloadDone channel is initialized + assert.NotNil(t, hooks.unloadDone, "unloadDone channel should be initialized") + + // Verify the channel is not closed initially + select { + case <-hooks.unloadDone: + t.Error("unloadDone channel should not be closed initially") + default: + // Expected behavior - channel is not closed + } +} + +func TestHooks_GetUnloadDone(t *testing.T) { + logger := zap.NewNop() + cfg := &config.Config{ + ProxyPort: 6789, + DNSPort: 26789, + } + + hooks := NewHooks(logger, cfg) + + // Get the channel + ch := hooks.GetUnloadDone() + assert.NotNil(t, ch, "GetUnloadDone should return a channel") + + // Verify it's the same channel + ch2 := hooks.GetUnloadDone() + assert.Equal(t, ch, ch2, "GetUnloadDone should return the same channel instance") + + // Verify the channel is not closed initially + select { + case <-ch: + t.Error("Channel should not be closed initially") + default: + // Expected behavior + } +} + +func TestHooks_GetUnloadDone_ThreadSafety(t *testing.T) { + logger := zap.NewNop() + cfg := &config.Config{ + ProxyPort: 6789, + DNSPort: 26789, + } + + hooks := NewHooks(logger, cfg) + + // Test concurrent access to GetUnloadDone + var wg sync.WaitGroup + channels := make([]<-chan struct{}, 10) + + for i := 0; i < 10; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + channels[index] = hooks.GetUnloadDone() + }(i) + } + + wg.Wait() + + // All should return the same channel + for i := 1; i < 10; i++ { + assert.Equal(t, channels[0], channels[i], "All GetUnloadDone calls should return the same channel") + } +} + +func TestHooks_Load_ResetsUnloadDoneChannel(t *testing.T) { + logger := zap.NewNop() + cfg := &config.Config{ + ProxyPort: 6789, + DNSPort: 26789, + } + + hooks := NewHooks(logger, cfg) + + // Create a context with errgroup for Load method + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + g := &errgroup.Group{} + ctx = context.WithValue(ctx, models.ErrGroupKey, g) + + // Mock the load method to avoid eBPF dependency + // We'll test the channel reset logic by examining the struct directly + hooks.unloadDoneMutex.Lock() + oldCh := hooks.unloadDone + hooks.unloadDone = make(chan struct{}) + newCh := hooks.unloadDone + hooks.unloadDoneMutex.Unlock() + + // Verify that a new channel was created + assert.NotEqual(t, oldCh, newCh, "Load should create a new unloadDone channel") + + // Verify the new channel is not closed + select { + case <-newCh: + t.Error("New channel should not be closed") + default: + // Expected behavior + } +} + +func TestHooks_UnloadDoneChannel_ClosedOnUnload(t *testing.T) { + logger := zap.NewNop() + cfg := &config.Config{ + ProxyPort: 6789, + DNSPort: 26789, + } + + hooks := NewHooks(logger, cfg) + + // Get the channel before simulating unload + ch := hooks.GetUnloadDone() + + // Simulate the unload completion by manually closing the channel + // (This mimics what happens in the goroutine when context is cancelled) + hooks.unloadDoneMutex.Lock() + close(hooks.unloadDone) + hooks.unloadDoneMutex.Unlock() + + // Verify the channel is now closed + select { + case <-ch: + // Expected behavior - channel is closed + case <-time.After(100 * time.Millisecond): + t.Error("Channel should be closed after unload") + } +} + +func TestHooks_UnloadDoneChannel_ThreadSafetyOnClose(t *testing.T) { + logger := zap.NewNop() + cfg := &config.Config{ + ProxyPort: 6789, + DNSPort: 26789, + } + + hooks := NewHooks(logger, cfg) + + // Get multiple references to the channel + channels := make([]<-chan struct{}, 5) + for i := 0; i < 5; i++ { + channels[i] = hooks.GetUnloadDone() + } + + // Close the channel from one goroutine + go func() { + time.Sleep(50 * time.Millisecond) + hooks.unloadDoneMutex.Lock() + close(hooks.unloadDone) + hooks.unloadDoneMutex.Unlock() + }() + + // All channels should be closed + for i, ch := range channels { + select { + case <-ch: + // Expected behavior + case <-time.After(200 * time.Millisecond): + t.Errorf("Channel %d should be closed", i) + } + } +} + +func TestHooks_UnloadDoneChannel_MultipleLoads(t *testing.T) { + logger := zap.NewNop() + cfg := &config.Config{ + ProxyPort: 6789, + DNSPort: 26789, + } + + hooks := NewHooks(logger, cfg) + + // Simulate multiple load cycles + var channels []<-chan struct{} + + for i := 0; i < 3; i++ { + // Reset channel (simulating Load call) + hooks.unloadDoneMutex.Lock() + hooks.unloadDone = make(chan struct{}) + hooks.unloadDoneMutex.Unlock() + + // Get the channel + ch := hooks.GetUnloadDone() + channels = append(channels, ch) + + // Verify each channel is different + if i > 0 { + assert.NotEqual(t, channels[i-1], channels[i], "Each load should create a new channel") + } + + // Verify channel is not closed + select { + case <-ch: + t.Errorf("Channel %d should not be closed initially", i) + default: + // Expected behavior + } + } +} + +func TestHooks_UnloadDoneChannel_SequentialLoadAndUnload(t *testing.T) { + logger := zap.NewNop() + cfg := &config.Config{ + ProxyPort: 6789, + DNSPort: 26789, + } + + hooks := NewHooks(logger, cfg) + + // First load cycle + hooks.unloadDoneMutex.Lock() + hooks.unloadDone = make(chan struct{}) + hooks.unloadDoneMutex.Unlock() + + ch1 := hooks.GetUnloadDone() + + // Simulate unload + hooks.unloadDoneMutex.Lock() + close(hooks.unloadDone) + hooks.unloadDoneMutex.Unlock() + + // Verify first channel is closed + select { + case <-ch1: + // Expected behavior + case <-time.After(100 * time.Millisecond): + t.Error("First channel should be closed") + } + + // Second load cycle + hooks.unloadDoneMutex.Lock() + hooks.unloadDone = make(chan struct{}) + hooks.unloadDoneMutex.Unlock() + + ch2 := hooks.GetUnloadDone() + + // Verify second channel is different and not closed + assert.NotEqual(t, ch1, ch2, "Second load should create a new channel") + + select { + case <-ch2: + t.Error("Second channel should not be closed initially") + default: + // Expected behavior + } +} diff --git a/keploy/pkg/core/hooks/kernelComm.go b/keploy/pkg/core/hooks/kernelComm.go new file mode 100644 index 0000000..7a71e9b --- /dev/null +++ b/keploy/pkg/core/hooks/kernelComm.go @@ -0,0 +1,131 @@ +//go:build linux + +package hooks + +import ( + "context" + "fmt" + + "github.com/cilium/ebpf" + "go.keploy.io/server/v2/pkg/core" + "go.keploy.io/server/v2/pkg/core/hooks/structs" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +//TODO: rename this file. + +// Get Used by proxy +func (h *Hooks) Get(_ context.Context, srcPort uint16) (*core.NetworkAddress, error) { + d, err := h.GetDestinationInfo(srcPort) + if err != nil { + return nil, err + } + + // Use the current app ID with proper synchronization + h.m.Lock() + currentAppID := h.appID + h.m.Unlock() + + s, ok := h.sess.Get(currentAppID) + if !ok { + return nil, fmt.Errorf("session not found") + } + + return &core.NetworkAddress{ + AppID: s.ID, + Version: d.IPVersion, + IPv4Addr: d.DestIP4, + IPv6Addr: d.DestIP6, + Port: d.DestPort, + }, nil +} + +// GetDestinationInfo retrieves destination information associated with a source port. +func (h *Hooks) GetDestinationInfo(srcPort uint16) (*structs.DestInfo, error) { + h.m.Lock() + defer h.m.Unlock() + destInfo := structs.DestInfo{} + if err := h.redirectProxyMap.Lookup(srcPort, &destInfo); err != nil { + return nil, err + } + return &destInfo, nil +} + +func (h *Hooks) Delete(_ context.Context, srcPort uint16) error { + return h.CleanProxyEntry(srcPort) +} + +func (h *Hooks) CleanProxyEntry(srcPort uint16) error { + h.m.Lock() + defer h.m.Unlock() + err := h.redirectProxyMap.Delete(srcPort) + if err != nil { + utils.LogError(h.logger, err, "failed to remove entry from redirect proxy map") + return err + } + h.logger.Debug("successfully removed entry from redirect proxy map", zap.Any("(Key)/SourcePort", srcPort)) + return nil +} + +func (h *Hooks) SendClientInfo(id uint64, appInfo structs.ClientInfo) error { + err := h.clientRegistrationMap.Update(id, appInfo, ebpf.UpdateAny) + if err != nil { + utils.LogError(h.logger, err, "failed to send the app info to the ebpf program") + return err + } + return nil +} + +func (h *Hooks) SendAgentInfo(agentInfo structs.AgentInfo) error { + key := 0 + err := h.agentRegistartionMap.Update(uint32(key), agentInfo, ebpf.UpdateAny) + if err != nil { + utils.LogError(h.logger, err, "failed to send the agent info to the ebpf program") + return err + } + return nil +} + +func (h *Hooks) SendE2EInfo(pid uint32) error { + key := 0 + err := h.e2eAppRegistrationMap.Update(uint64(key), pid, ebpf.UpdateAny) + if err != nil { + utils.LogError(h.logger, err, "failed to send the E2E info to the ebpf program") + return err + } + return nil +} + +func (h *Hooks) SendDockerAppInfo(appID uint64, dockerAppInfo structs.DockerAppInfo) error { + h.m.Lock() + defer h.m.Unlock() + + // Use the provided app ID or the current app ID, don't generate a random one + dockerAppID := appID + if dockerAppID == 0 { + dockerAppID = h.appID + } + + if h.appID != 0 { + err := h.dockerAppRegistrationMap.Delete(h.appID) + if err != nil { + utils.LogError(h.logger, err, "failed to remove entry from dockerAppRegistrationMap", zap.Any("(Key)/AppID", h.appID)) + return err + } + } + + // Don't override the app ID with a random number - use the real app ID + err := h.dockerAppRegistrationMap.Update(dockerAppID, dockerAppInfo, ebpf.UpdateAny) + if err != nil { + utils.LogError(h.logger, err, "failed to send the dockerAppInfo info to the ebpf program", zap.Uint64("appID", dockerAppID)) + return err + } + + // Update the app ID only if we received a valid one + if appID != 0 { + h.appID = appID + } + + return nil +} diff --git a/keploy/pkg/core/hooks/structs/structs.go b/keploy/pkg/core/hooks/structs/structs.go new file mode 100755 index 0000000..a0e7010 --- /dev/null +++ b/keploy/pkg/core/hooks/structs/structs.go @@ -0,0 +1,78 @@ +//go:build linux + +// Package structs provides data structures for hooks. +package structs + +type BpfSpinLock struct{ Val uint32 } + +// struct dest_info_t +// { +// u32 ip_version; +// u32 dest_ip4; +// u32 dest_ip6[4]; +// u32 dest_port; +// u32 kernelPid; +// }; + +type DestInfo struct { + IPVersion uint32 + DestIP4 uint32 + DestIP6 [4]uint32 + DestPort uint32 + KernelPid uint32 + ClientID uint64 +} + +// struct proxy_info +// { +// u32 ip4; +// u32 ip6[4]; +// u32 port; +// }; + +type ProxyInfo struct { + IP4 uint32 + IP6 [4]uint32 + Port uint32 +} + +type DockerAppInfo struct { + AppInode uint64 + ClientID uint64 +} + +// struct app_info +// { +// u32 keploy_client_ns_pid; +// u64 keploy_client_inode; +// u64 app_inode; +// u32 mode; +// u32 is_docker_app; +// u32 is_keploy_client_registered; // whether the client is registered or not +// s32 pass_through_ports[PASS_THROUGH_ARRAY_SIZE]; +// }; + +type ClientInfo struct { + KeployClientInode uint64 // 8 bytes + KeployClientNsPid uint32 // 4 bytes + Mode uint32 // 4 bytes + IsDockerApp uint32 // 4 bytes + + IsKeployClientRegistered uint32 // 4 bytes + PassThroughPorts [10]int32 // 40 bytes +} + +// struct agent_info +// { +// u32 keploy_agent_ns_pid; +// u32 keploy_agent_inode; +// struct proxy_info proxy_info; +// s32 dns_port; +// }; + +type AgentInfo struct { + KeployAgentNsPid uint32 + DNSPort int32 + KeployAgentInode uint64 + ProxyInfo ProxyInfo +} diff --git a/keploy/pkg/core/hooks/util.go b/keploy/pkg/core/hooks/util.go new file mode 100644 index 0000000..f28a9ff --- /dev/null +++ b/keploy/pkg/core/hooks/util.go @@ -0,0 +1,126 @@ +//go:build linux + +package hooks + +import ( + "bufio" + "context" + "encoding/binary" + "errors" + "net" + "os" + "path/filepath" + "strings" + "syscall" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// IPv4ToUint32 converts a string representation of an IPv4 address to a 32-bit integer. +func IPv4ToUint32(ipStr string) (uint32, error) { + ipAddr := net.ParseIP(ipStr) + if ipAddr != nil { + ipAddr = ipAddr.To4() + if ipAddr != nil { + return binary.BigEndian.Uint32(ipAddr), nil + } + return 0, errors.New("not a valid IPv4 address") + } + return 0, errors.New("failed to parse IP address") +} + +// ToIPv4MappedIPv6 converts an IPv4 address to an IPv4-mapped IPv6 address. +func ToIPv4MappedIPv6(ipv4 string) ([4]uint32, error) { + var result [4]uint32 + + // Parse the input IPv4 address + ip := net.ParseIP(ipv4) + if ip == nil { + return result, errors.New("invalid IPv4 address") + } + + // Check if the input is an IPv4 address + ip = ip.To4() + if ip == nil { + return result, errors.New("not a valid IPv4 address") + } + + // Convert IPv4 address to IPv4-mapped IPv6 address + // IPv4-mapped IPv6 address is ::ffff:a.b.c.d + ipv6 := "::ffff:" + ipv4 + + // Parse the resulting IPv6 address + ip6 := net.ParseIP(ipv6) + if ip6 == nil { + return result, errors.New("failed to parse IPv4-mapped IPv6 address") + } + + // Convert the IPv6 address to a 16-byte representation + ip6Bytes := ip6.To16() + if ip6Bytes == nil { + return result, errors.New("failed to convert IPv6 address to bytes") + } + + // Populate the result array + for i := 0; i < 4; i++ { + result[i] = uint32(ip6Bytes[i*4])<<24 | uint32(ip6Bytes[i*4+1])<<16 | uint32(ip6Bytes[i*4+2])<<8 | uint32(ip6Bytes[i*4+3]) + } + + return result, nil +} + +// detectCgroupPath returns the first-found mount point of type cgroup2 +// and stores it in the cgroupPath global variable. +func detectCgroupPath(logger *zap.Logger) (string, error) { + f, err := os.Open("/proc/mounts") + if err != nil { + return "", err + } + defer func() { + err := f.Close() + if err != nil { + utils.LogError(logger, err, "failed to close /proc/mounts file") + } + }() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + // example fields: cgroup2 /sys/fs/cgroup/unified cgroup2 rw,nosuid,nodev,noexec,relatime 0 0 + fields := strings.Split(scanner.Text(), " ") + if len(fields) >= 3 && fields[2] == "cgroup2" { + return fields[1], nil + } + } + + return "", errors.New("cgroup2 not mounted") +} + +func getSelfInodeNumber() (uint64, error) { + p := filepath.Join("/proc", "self", "ns", "pid") + + f, err := os.Stat(p) + if err != nil { + return 0, errors.New("failed to get inode of the keploy process") + } + // Dev := (f.Sys().(*syscall.Stat_t)).Dev + Ino := (f.Sys().(*syscall.Stat_t)).Ino + if Ino != 0 { + return Ino, nil + } + return 0, nil +} + +func GetPortToSendToKernel(_ context.Context, rules []config.BypassRule) []uint { + // if the rule only contains port, then it should be sent to kernel + ports := []uint{} + for _, rule := range rules { + if rule.Host == "" && rule.Path == "" { + if rule.Port != 0 { + ports = append(ports, rule.Port) + } + } + } + return ports +} diff --git a/keploy/pkg/core/proxy/README.md b/keploy/pkg/core/proxy/README.md new file mode 100755 index 0000000..ef79802 --- /dev/null +++ b/keploy/pkg/core/proxy/README.md @@ -0,0 +1,5 @@ +# Proxy Package Documentation + +This package includes modules that the `hooks` package utilizes to +redirect the outgoing calls of the user API. This redirection is +done with the aim to record or stub the outputs of dependency calls. \ No newline at end of file diff --git a/keploy/pkg/core/proxy/dns.go b/keploy/pkg/core/proxy/dns.go new file mode 100644 index 0000000..ab944c0 --- /dev/null +++ b/keploy/pkg/core/proxy/dns.go @@ -0,0 +1,345 @@ +//go:build linux + +package proxy + +import ( + "context" + "errors" + "fmt" + "net" + "os" + "strings" + "sync" + + "github.com/miekg/dns" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func (p *Proxy) startTCPDNSServer(_ context.Context) error { + addr := fmt.Sprintf(":%v", p.DNSPort) + + handler := p + server := &dns.Server{ + Addr: addr, + Net: "tcp", + Handler: handler, + ReusePort: true, + } + + p.TCPDNSServer = server + + p.logger.Info(fmt.Sprintf("starting TCP DNS server at addr %v", server.Addr)) + err := server.ListenAndServe() + if err != nil { + utils.LogError(p.logger, err, "failed to start tcp dns server", zap.Any("addr", server.Addr)) + } + return nil +} + +func (p *Proxy) startUDPDNSServer(_ context.Context) error { + + addr := fmt.Sprintf(":%v", p.DNSPort) + + handler := p + server := &dns.Server{ + Addr: addr, + Net: "udp", + Handler: handler, + ReusePort: true, + // DisableBackground: true, + } + + p.UDPDNSServer = server + + p.logger.Info(fmt.Sprintf("starting UDP DNS server at addr %v", server.Addr)) + err := server.ListenAndServe() + if err != nil { + utils.LogError(p.logger, err, "failed to start udp dns server", zap.Any("addr", server.Addr)) + return err + } + return nil +} + +// For DNS caching +var cache = struct { + sync.RWMutex + m map[string][]dns.RR +}{m: make(map[string][]dns.RR)} + +func generateCacheKey(name string, qtype uint16) string { + // For MongoDB SRV queries, include "mongodb" in the cache key to differentiate from other SRV queries + if strings.HasPrefix(name, "_mongodb._tcp.") { + return fmt.Sprintf("mongodb-%s-%s", name, dns.TypeToString[qtype]) + } + return fmt.Sprintf("%s-%s", name, dns.TypeToString[qtype]) +} + +func (p *Proxy) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { + + p.logger.Debug("", zap.Any("Source socket info", w.RemoteAddr().String())) + msg := new(dns.Msg) + msg.SetReply(r) + msg.Authoritative = true + p.logger.Debug("Got some Dns queries") + for _, question := range r.Question { + p.logger.Debug("", zap.Any("Record Type", question.Qtype), zap.Any("Received Query", question.Name)) + + key := generateCacheKey(question.Name, question.Qtype) + + // Clear cache for MongoDB SRV queries to ensure fresh resolution + if strings.HasPrefix(question.Name, "_mongodb._tcp.") { + cache.Lock() + delete(cache.m, key) + cache.Unlock() + } + + // Check if the answer is cached + cache.RLock() + answers, found := cache.m[key] + cache.RUnlock() + + if !found { + // If not found in cache, resolve the DNS query only in case of record mode + //TODO: Add support for passThrough here using the src<->dst mapping + if models.GetMode() == models.MODE_RECORD { + answers = resolveDNSQuery(p.logger, question.Name) + } + + if len(answers) == 0 { + + switch question.Qtype { + // If the resolution failed, return a default A record with Proxy IP + // or AAAA record with Proxy IP6 + case dns.TypeA: + answers = []dns.RR{&dns.A{ + Hdr: dns.RR_Header{Name: question.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600}, + A: net.ParseIP(p.IP4), + }} + p.logger.Debug("failed to resolve dns query hence sending proxy ip4", zap.Any("proxy Ip", p.IP4)) + case dns.TypeAAAA: + answers = []dns.RR{&dns.AAAA{ + Hdr: dns.RR_Header{Name: question.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600}, + AAAA: net.ParseIP(p.IP6), + }} + p.logger.Debug("failed to resolve dns query hence sending proxy ip6", zap.Any("proxy Ip", p.IP6)) + case dns.TypeSRV: + // Special handling for MongoDB SRV queries + if strings.HasPrefix(question.Name, "_mongodb._tcp.") { + baseDomain := strings.TrimPrefix(question.Name, "_mongodb._tcp.") + answers = []dns.RR{&dns.SRV{ + Hdr: dns.RR_Header{Name: dns.Fqdn(question.Name), Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: 3600}, + Priority: 0, + Weight: 0, + Port: 27017, + Target: dns.Fqdn("mongodb." + baseDomain), + }} + } else { + answers = []dns.RR{&dns.SRV{ + Hdr: dns.RR_Header{Name: question.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: 3600}, + Priority: 0, + Weight: 0, + Port: 8080, + Target: dns.Fqdn("keploy.proxy"), + }} + } + p.logger.Debug("sending default SRV record response") + default: + p.logger.Error("Unsupported DNS query type", zap.Any("query type", question.Qtype)) + } + + } + p.logger.Debug(fmt.Sprintf("Answers[when resolution failed for query:%v]:\n%v\n", question.Qtype, answers)) + + // Cache the answer + cache.Lock() + cache.m[key] = answers + cache.Unlock() + p.logger.Debug(fmt.Sprintf("Answers[after caching it]:\n%v\n", answers)) + } + + p.logger.Debug(fmt.Sprintf("Answers[before appending to msg]:\n%v\n", answers)) + msg.Answer = append(msg.Answer, answers...) + p.logger.Debug(fmt.Sprintf("Answers[After appending to msg]:\n%v\n", msg.Answer)) + } + + p.logger.Debug(fmt.Sprintf("dns msg sending back:\n%v\n", msg)) + p.logger.Debug(fmt.Sprintf("dns msg RCODE sending back:\n%v\n", msg.Rcode)) + p.logger.Debug("Writing dns info back to the client...") + err := w.WriteMsg(msg) + if err != nil { + utils.LogError(p.logger, err, "failed to write dns info back to the client") + } +} + +// TODO: passThrough the dns queries rather than resolving them. +func resolveDNSQuery(logger *zap.Logger, domain string) []dns.RR { + // Remove the last dot from the domain name if it exists + domain = strings.TrimSuffix(domain, ".") + + // Use the default system resolver + resolver := net.DefaultResolver + + ctx := context.Background() + + var answers []dns.RR + + // For SRV records, handle MongoDB specific queries + if strings.HasPrefix(domain, "_mongodb._tcp.") { + baseDomain := strings.TrimPrefix(domain, "_mongodb._tcp.") + _, addrs, err := resolver.LookupSRV(ctx, "mongodb", "tcp", baseDomain) + if err == nil && len(addrs) > 0 { + for _, addr := range addrs { + answers = append(answers, &dns.SRV{ + Hdr: dns.RR_Header{Name: dns.Fqdn(domain), Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: 3600}, + Priority: addr.Priority, + Weight: addr.Weight, + Port: addr.Port, + Target: dns.Fqdn(addr.Target), + }) + } + return answers + } + // If resolution fails, return a default SRV record with a target that matches the domain suffix + return []dns.RR{&dns.SRV{ + Hdr: dns.RR_Header{Name: dns.Fqdn(domain), Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: 3600}, + Priority: 0, + Weight: 0, + Port: 27017, // Default MongoDB port + Target: dns.Fqdn("mongodb." + baseDomain), + }} + } + + // For TXT records, try to resolve them directly + txtRecords, err := resolver.LookupTXT(ctx, domain) + if err == nil && len(txtRecords) > 0 { + for _, txt := range txtRecords { + answers = append(answers, &dns.TXT{ + Hdr: dns.RR_Header{Name: dns.Fqdn(domain), Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 3600}, + Txt: []string{txt}, + }) + } + return answers + } + + // For A/AAAA records + ips, err := resolver.LookupIPAddr(ctx, domain) + // Perform the lookup with the context + // ips, err := resolver.LookupIPAddr(context.Background(), domain) + + if err != nil { + logger.Debug(fmt.Sprintf("failed to resolve the dns query for:%v", domain), zap.Error(err)) + return nil + } + + // Convert the resolved IPs to dns.RR + // var answers []dns.RR + for _, ip := range ips { + if ipv4 := ip.IP.To4(); ipv4 != nil { + answers = append(answers, &dns.A{ + Hdr: dns.RR_Header{Name: dns.Fqdn(domain), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600}, + A: ipv4, + }) + } else { + answers = append(answers, &dns.AAAA{ + Hdr: dns.RR_Header{Name: dns.Fqdn(domain), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600}, + AAAA: ip.IP, + }) + } + } + + if len(answers) > 0 { + logger.Debug("resolved the dns records successfully") + } + + return answers +} + +func (p *Proxy) stopDNSServers(_ context.Context) error { + // stop tcp dns server + if err := p.stopTCPDNSServer(); err != nil { + return err + } + // stop udp dns server + err := p.stopUDPDNSServer() + return err +} + +func (p *Proxy) stopTCPDNSServer() error { + if p.TCPDNSServer != nil { + err := p.TCPDNSServer.Shutdown() + if err != nil { + utils.LogError(p.logger, err, "failed to stop tcp dns server") + return err + } + p.logger.Info("Tcp Dns server stopped successfully") + } + return nil +} + +func (p *Proxy) stopUDPDNSServer() error { + if p.UDPDNSServer != nil { + err := p.UDPDNSServer.Shutdown() + if err != nil { + utils.LogError(p.logger, err, "failed to stop udp dns server") + return err + } + p.logger.Info("Udp Dns server stopped successfully") + } + return nil +} + +const ( + nsSwitchConfig = "/etc/nsswitch.conf" + nsSwitchPerm = 0644 +) + +// setting up the dns routing for the linux system +func (p *Proxy) setupNsswitchConfig() error { + + // Check if the nsswitch.conf present for the system + if _, err := os.Stat(nsSwitchConfig); err == nil { + // Read the current nsswitch.conf + data, err := os.ReadFile(nsSwitchConfig) + if err != nil { + utils.LogError(p.logger, err, "failed to read the nsswitch.conf file from system") + return errors.New("failed to setup the nsswitch.conf file to redirect the DNS queries to proxy") + } + // copy the data of the nsswitch.conf file in order to reset it back to the original state in the end + p.nsswitchData = data + + // Replace the hosts field value if it exists + lines := strings.Split(string(data), "\n") + for i, line := range lines { + if strings.HasPrefix(line, "hosts:") { + lines[i] = "hosts: files dns" + } + } + + data = []byte(strings.Join(lines, "\n")) + + // Write the modified nsswitch.conf back to the file + err = writeNsswitchConfig(p.logger, nsSwitchConfig, data, nsSwitchPerm) + if err != nil { + return errors.New("failed to setup the nsswitch.conf file to redirect the DNS queries to proxy") + } + + p.logger.Debug("Successfully written to nsswitch config of linux") + } + return nil +} + +// resetNsSwitchConfig resets the hosts config of nsswitch of the system +func (p *Proxy) resetNsSwitchConfig() error { + data := p.nsswitchData + + // Write the original data back to the nsswitch.conf file + err := writeNsswitchConfig(p.logger, nsSwitchConfig, data, nsSwitchPerm) + if err != nil { + return errors.New("failed to reset the nsswitch.conf back to the original state") + } + + p.logger.Debug("Successfully reset the nsswitch config of linux") + return nil +} diff --git a/keploy/pkg/core/proxy/integrations/README.md b/keploy/pkg/core/proxy/integrations/README.md new file mode 100755 index 0000000..b5c3e39 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/README.md @@ -0,0 +1,3 @@ +# Integrations Package Documentation + +This package includes modules that are used for parsing different protocols. \ No newline at end of file diff --git a/keploy/pkg/core/proxy/integrations/generic/README.md b/keploy/pkg/core/proxy/integrations/generic/README.md new file mode 100644 index 0000000..e69de29 diff --git a/keploy/pkg/core/proxy/integrations/generic/decode.go b/keploy/pkg/core/proxy/integrations/generic/decode.go new file mode 100644 index 0000000..b32a95f --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/generic/decode.go @@ -0,0 +1,124 @@ +//go:build linux + +// Package generic provides functionality for decoding generic dependencies. +package generic + +import ( + "context" + "io" + "net" + "time" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func decodeGeneric(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, _ models.OutgoingOptions) error { + genericRequests := [][]byte{reqBuf} + logger.Debug("Into the generic parser in test mode") + errCh := make(chan error, 1) + go func(errCh chan error, genericRequests [][]byte) { + defer pUtil.Recover(logger, clientConn, nil) + defer close(errCh) + for { + // Since protocol packets have to be parsed for checking stream end, + // clientConnection have deadline for read to determine the end of stream. + err := clientConn.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) + if err != nil { + utils.LogError(logger, err, "failed to set the read deadline for the client conn") + return + } + + // To read the stream of request packets from the client + for { + buffer, err := pUtil.ReadBytes(ctx, logger, clientConn) + // Applied this nolint to ignore the staticcheck error here because of readability + // nolint:staticcheck + if netErr, ok := err.(net.Error); !(ok && netErr.Timeout()) && err != nil && err.Error() != "EOF" { + utils.LogError(logger, err, "failed to read the request message in proxy for generic dependency") + return + } + if netErr, ok := err.(net.Error); (ok && netErr.Timeout()) || (err != nil && err.Error() == "EOF") { + logger.Debug("the timeout for the client read in generic or EOF") + break + } + genericRequests = append(genericRequests, buffer) + } + + if len(genericRequests) == 0 { + logger.Debug("the generic request buffer is empty") + continue + } + + // bestMatchedIndx := 0 + // fuzzy match gives the index for the best matched generic mock + matched, genericResponses, err := fuzzyMatch(ctx, logger, genericRequests, mockDb) + if err != nil { + utils.LogError(logger, err, "error while matching generic mocks") + } + + if !matched { + err := clientConn.SetReadDeadline(time.Time{}) + if err != nil { + utils.LogError(logger, err, "failed to set the read deadline for the client conn") + return + } + + logger.Debug("the genericRequests before pass through are", zap.Any("length", len(genericRequests))) + for _, genReq := range genericRequests { + logger.Debug("the genericRequests are:", zap.Any("h", string(genReq))) + } + + reqBuffer, err := pUtil.PassThrough(ctx, logger, clientConn, dstCfg, genericRequests) + if err != nil { + utils.LogError(logger, err, "failed to passthrough the generic request") + return + } + + genericRequests = [][]byte{} + logger.Debug("the request buffer after pass through in generic", zap.Any("buffer", string(reqBuffer))) + if len(reqBuffer) > 0 { + genericRequests = [][]byte{reqBuffer} + } + logger.Debug("the length of genericRequests after passThrough ", zap.Any("length", len(genericRequests))) + continue + } + for _, genericResponse := range genericResponses { + encoded := []byte(genericResponse.Message[0].Data) + if genericResponse.Message[0].Type != models.String { + encoded, err = util.DecodeBase64(genericResponse.Message[0].Data) + if err != nil { + utils.LogError(logger, err, "failed to decode the base64 response") + return + } + } + _, err := clientConn.Write(encoded) + if err != nil { + if ctx.Err() != nil { + return + } + utils.LogError(logger, err, "failed to write the response message to the client application") + return + } + } + + // Clear the genericRequests buffer for the next dependency call + genericRequests = [][]byte{} + logger.Debug("the genericRequests after the iteration", zap.Any("length", len(genericRequests))) + } + }(errCh, genericRequests) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + return nil + } + return err + } +} diff --git a/keploy/pkg/core/proxy/integrations/generic/encode.go b/keploy/pkg/core/proxy/integrations/generic/encode.go new file mode 100644 index 0000000..8c31f7a --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/generic/encode.go @@ -0,0 +1,200 @@ +//go:build linux + +package generic + +import ( + "context" + "encoding/base64" + "fmt" + "io" + "net" + "strconv" + "time" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func encodeGeneric(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, _ models.OutgoingOptions) error { + + var genericRequests []models.Payload + + bufStr := string(reqBuf) + dataType := models.String + if !util.IsASCII(string(reqBuf)) { + bufStr = util.EncodeBase64(reqBuf) + dataType = "binary" + } + + if bufStr != "" { + genericRequests = append(genericRequests, models.Payload{ + Origin: models.FromClient, + Message: []models.OutputBinary{ + { + Type: dataType, + Data: bufStr, + }, + }, + }) + } + _, err := destConn.Write(reqBuf) + if err != nil { + utils.LogError(logger, err, "failed to write request message to the destination server") + return err + } + var genericResponses []models.Payload + + clientBuffChan := make(chan []byte) + destBuffChan := make(chan []byte) + errChan := make(chan error) + //TODO: where to close the error channel since it is used in both the go routines + //close(errChan) + + // read requests from client + err = pUtil.ReadFromPeer(ctx, logger, clientConn, clientBuffChan, errChan, pUtil.Client) + if err != nil { + return fmt.Errorf("error reading from client:%v", err) + } + + // read responses from destination + err = pUtil.ReadFromPeer(ctx, logger, destConn, destBuffChan, errChan, pUtil.Destination) + if err != nil { + return fmt.Errorf("error reading from destination:%v", err) + } + + prevChunkWasReq := false + var reqTimestampMock = time.Now() + var resTimestampMock time.Time + + // ticker := time.NewTicker(1 * time.Second) + logger.Debug("the iteration for the generic request starts", zap.Any("genericReqs", len(genericRequests)), zap.Any("genericResps", len(genericResponses))) + for { + select { + case <-ctx.Done(): + if !prevChunkWasReq && len(genericRequests) > 0 && len(genericResponses) > 0 { + genericRequestsCopy := make([]models.Payload, len(genericRequests)) + genericResponsesCopy := make([]models.Payload, len(genericResponses)) + copy(genericResponsesCopy, genericResponses) + copy(genericRequestsCopy, genericRequests) + + metadata := make(map[string]string) + metadata["type"] = "config" + metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) + // Save the mock + mocks <- &models.Mock{ + Version: models.GetVersion(), + Name: "mocks", + Kind: models.GENERIC, + Spec: models.MockSpec{ + GenericRequests: genericRequestsCopy, + GenericResponses: genericResponsesCopy, + ReqTimestampMock: reqTimestampMock, + ResTimestampMock: resTimestampMock, + Metadata: metadata, + }, + } + return ctx.Err() + } + case buffer := <-clientBuffChan: + // Write the request message to the destination + _, err := destConn.Write(buffer) + if err != nil { + utils.LogError(logger, err, "failed to write request message to the destination server") + return err + } + + logger.Debug("the iteration for the generic request ends with no of genericReqs:" + strconv.Itoa(len(genericRequests)) + " and genericResps: " + strconv.Itoa(len(genericResponses))) + if !prevChunkWasReq && len(genericRequests) > 0 && len(genericResponses) > 0 { + genericRequestsCopy := make([]models.Payload, len(genericRequests)) + genericResponseCopy := make([]models.Payload, len(genericResponses)) + copy(genericResponseCopy, genericResponses) + copy(genericRequestsCopy, genericRequests) + go func(reqs []models.Payload, resps []models.Payload) { + metadata := make(map[string]string) + metadata["type"] = "config" + metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) + // Save the mock + mocks <- &models.Mock{ + Version: models.GetVersion(), + Name: "mocks", + Kind: models.GENERIC, + Spec: models.MockSpec{ + GenericRequests: reqs, + GenericResponses: resps, + ReqTimestampMock: reqTimestampMock, + ResTimestampMock: resTimestampMock, + Metadata: metadata, + }, + } + + }(genericRequestsCopy, genericResponseCopy) + genericRequests = []models.Payload{} + genericResponses = []models.Payload{} + } + + bufStr := string(buffer) + buffDataType := models.String + if !util.IsASCII(string(buffer)) { + bufStr = util.EncodeBase64(buffer) + buffDataType = "binary" + } + + if bufStr != "" { + genericRequests = append(genericRequests, models.Payload{ + Origin: models.FromClient, + Message: []models.OutputBinary{ + { + Type: buffDataType, + Data: bufStr, + }, + }, + }) + } + + prevChunkWasReq = true + case buffer := <-destBuffChan: + if prevChunkWasReq { + // store the request timestamp + reqTimestampMock = time.Now() + } + // Write the response message to the client + _, err := clientConn.Write(buffer) + if err != nil { + utils.LogError(logger, err, "failed to write response message to the client") + return err + } + + bufStr := string(buffer) + buffDataType := models.String + if !util.IsASCII(string(buffer)) { + bufStr = base64.StdEncoding.EncodeToString(buffer) + buffDataType = "binary" + } + + if bufStr != "" { + genericResponses = append(genericResponses, models.Payload{ + Origin: models.FromServer, + Message: []models.OutputBinary{ + { + Type: buffDataType, + Data: bufStr, + }, + }, + }) + } + + resTimestampMock = time.Now() + + logger.Debug("the iteration for the generic response ends with no of genericReqs:" + strconv.Itoa(len(genericRequests)) + " and genericResps: " + strconv.Itoa(len(genericResponses))) + prevChunkWasReq = false + case err := <-errChan: + if err == io.EOF { + return nil + } + return err + } + } +} diff --git a/keploy/pkg/core/proxy/integrations/generic/generic.go b/keploy/pkg/core/proxy/integrations/generic/generic.go new file mode 100755 index 0000000..1d56a62 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/generic/generic.go @@ -0,0 +1,69 @@ +//go:build linux + +package generic + +import ( + "context" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func init() { + integrations.Register(integrations.GENERIC, &integrations.Parsers{ + Initializer: New, + Priority: 100, + }) +} + +type Generic struct { + logger *zap.Logger +} + +func New(logger *zap.Logger) integrations.Integrations { + return &Generic{ + logger: logger, + } +} + +func (g *Generic) MatchType(_ context.Context, _ []byte) bool { + // generic is checked explicitly in the proxy + return false +} + +func (g *Generic) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + logger := g.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial generic message") + return err + } + + err = encodeGeneric(ctx, logger, reqBuf, src, dst, mocks, opts) + if err != nil { + utils.LogError(logger, err, "failed to encode the generic message into the yaml") + return err + } + return nil +} + +func (g *Generic) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { + logger := g.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial generic message") + return err + } + + err = decodeGeneric(ctx, logger, reqBuf, src, dstCfg, mockDb, opts) + if err != nil { + utils.LogError(logger, err, "failed to decode the generic message") + return err + } + return nil +} diff --git a/keploy/pkg/core/proxy/integrations/generic/match.go b/keploy/pkg/core/proxy/integrations/generic/match.go new file mode 100755 index 0000000..6fd0f1f --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/generic/match.go @@ -0,0 +1,155 @@ +//go:build linux + +package generic + +import ( + "context" + "encoding/base64" + "fmt" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.uber.org/zap" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.keploy.io/server/v2/pkg/models" +) + +// fuzzyMatch performs a fuzzy matching algorithm to find the best matching mock for the given request. +// It takes a context, a request buffer, and a mock database as input parameters. +// The function iterates over the mocks in the database and applies the fuzzy matching algorithm to find the best match. +// If a match is found, it returns the corresponding response mock and a boolean value indicating success. +// If no match is found, it returns false and a nil response. +// If an error occurs during the matching process, it returns an error. +func fuzzyMatch(ctx context.Context, logger *zap.Logger, reqBuff [][]byte, mockDb integrations.MockMemDb) (bool, []models.Payload, error) { + for { + select { + case <-ctx.Done(): + return false, nil, ctx.Err() + default: + mocks, err := mockDb.GetUnFilteredMocks() + if err != nil { + return false, nil, fmt.Errorf("error while getting unfiltered mocks %v", err) + } + + var filteredMocks []*models.Mock + var unfilteredMocks []*models.Mock + + for _, mock := range mocks { + if mock.Kind != "Generic" { + continue + } + if mock.TestModeInfo.IsFiltered { + filteredMocks = append(filteredMocks, mock) + } else { + unfilteredMocks = append(unfilteredMocks, mock) + } + } + + logger.Debug("List of mocks in the database", zap.Any("Filtered Mocks", len(filteredMocks)), zap.Any("Unfiltered Mocks", len(unfilteredMocks))) + for i, mock := range filteredMocks { + logger.Debug("Filtered Mocks", zap.Any(fmt.Sprintf("Mock[%d]", i), mock.Name), zap.Any("sortOrder", mock.TestModeInfo.SortOrder)) + } + for i, mock := range unfilteredMocks { + logger.Debug("Unfiltered Mocks", zap.Any(fmt.Sprintf("Mock[%d]", i), mock.Name), zap.Any("sortOrder", mock.TestModeInfo.SortOrder)) + } + + index := findExactMatch(filteredMocks, reqBuff) + + if index == -1 { + index = findBinaryMatch(filteredMocks, reqBuff, 0.9) + } + + if index != -1 { + responseMock := make([]models.Payload, len(filteredMocks[index].Spec.GenericResponses)) + copy(responseMock, filteredMocks[index].Spec.GenericResponses) + originalFilteredMock := *filteredMocks[index] + filteredMocks[index].TestModeInfo.IsFiltered = false + filteredMocks[index].TestModeInfo.SortOrder = pkg.GetNextSortNum() + isUpdated := mockDb.UpdateUnFilteredMock(&originalFilteredMock, filteredMocks[index]) + if !isUpdated { + continue + } + logger.Debug("Filtered mock found for generic request", zap.Any("Mock", filteredMocks[index].Name), zap.Any("sortOrder", filteredMocks[index].TestModeInfo.SortOrder)) + return true, responseMock, nil + } + + index = findExactMatch(unfilteredMocks, reqBuff) + + if index == -1 { + index = findBinaryMatch(unfilteredMocks, reqBuff, 0.4) + } + if index != -1 { + responseMock := make([]models.Payload, len(unfilteredMocks[index].Spec.GenericResponses)) + copy(responseMock, unfilteredMocks[index].Spec.GenericResponses) + originalFilteredMock := *unfilteredMocks[index] + unfilteredMocks[index].TestModeInfo.IsFiltered = false + unfilteredMocks[index].TestModeInfo.SortOrder = pkg.GetNextSortNum() + isUpdated := mockDb.UpdateUnFilteredMock(&originalFilteredMock, unfilteredMocks[index]) + if !isUpdated { + continue + } + logger.Debug("Unfiltered mock found for generic request", zap.Any("Mock", unfilteredMocks[index].Name), zap.Any("sortOrder", unfilteredMocks[index].TestModeInfo.SortOrder)) + return true, responseMock, nil + } + return false, nil, nil + } + } +} + +// TODO: need to generalize this function for different types of integrations. +func findBinaryMatch(tcsMocks []*models.Mock, reqBuffs [][]byte, mxSim float64) int { + // TODO: need find a proper similarity index to set a benchmark for matching or need to find another way to do approximate matching + mxIdx := -1 + for idx, mock := range tcsMocks { + if len(mock.Spec.GenericRequests) == len(reqBuffs) { + for requestIndex, reqBuff := range reqBuffs { + _ = base64.StdEncoding.EncodeToString(reqBuff) + encoded, _ := util.DecodeBase64(mock.Spec.GenericRequests[requestIndex].Message[0].Data) + + similarity := fuzzyCheck(encoded, reqBuff) + + if mxSim < similarity { + mxSim = similarity + mxIdx = idx + } + } + } + } + return mxIdx +} + +func fuzzyCheck(encoded, reqBuf []byte) float64 { + k := util.AdaptiveK(len(reqBuf), 3, 8, 5) + shingles1 := util.CreateShingles(encoded, k) + shingles2 := util.CreateShingles(reqBuf, k) + similarity := util.JaccardSimilarity(shingles1, shingles2) + return similarity +} + +func findExactMatch(tcsMocks []*models.Mock, reqBuffs [][]byte) int { + for idx, mock := range tcsMocks { + if len(mock.Spec.GenericRequests) == len(reqBuffs) { + matched := true // Flag to track if all requests match + + for requestIndex, reqBuff := range reqBuffs { + + bufStr := string(reqBuff) + if !util.IsASCII(string(reqBuff)) { + bufStr = util.EncodeBase64(reqBuff) + } + + // Compare the encoded data + if mock.Spec.GenericRequests[requestIndex].Message[0].Data != bufStr { + matched = false + break // Exit the loop if any request doesn't match + } + } + + if matched { + return idx + } + } + } + return -1 +} diff --git a/keploy/pkg/core/proxy/integrations/grpc/decode.go b/keploy/pkg/core/proxy/integrations/grpc/decode.go new file mode 100644 index 0000000..20ada3a --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpc/decode.go @@ -0,0 +1,33 @@ +//go:build linux + +// Package grpc provides functionality for integrating with gRPC outgoing calls. +package grpc + +import ( + "context" + "io" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "golang.org/x/net/http2" +) + +func decodeGrpc(ctx context.Context, logger *zap.Logger, _ []byte, clientConn net.Conn, _ *models.ConditionalDstCfg, mockDb integrations.MockMemDb, _ models.OutgoingOptions) error { + framer := http2.NewFramer(clientConn, clientConn) + srv := NewTranscoder(logger, framer, mockDb) + // fake server in the test mode + err := srv.ListenAndServe(ctx) + if err != nil { + if err == io.EOF { + // EOF is expected when the server closes the connection. + logger.Debug("EOF while serving grpc request") + return nil + } + utils.LogError(logger, nil, "could not serve grpc request") + return err + } + return nil +} diff --git a/keploy/pkg/core/proxy/integrations/grpc/encode.go b/keploy/pkg/core/proxy/integrations/grpc/encode.go new file mode 100644 index 0000000..f541636 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpc/encode.go @@ -0,0 +1,86 @@ +//go:build linux + +package grpc + +import ( + "context" + "io" + "net" + + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" +) + +func encodeGrpc(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, _ models.OutgoingOptions) error { + + // Send the client preface to the server. This should be the first thing sent from the client. + _, err := destConn.Write(reqBuf) + if err != nil { + utils.LogError(logger, err, "Could not write preface onto the destination server") + return err + } + + if ctx.Err() != nil { + return ctx.Err() + } + + streamInfoCollection := NewStreamInfoCollection() + reqFromClient := true + + serverSideDecoder := NewDecoder() + + // get the error group from the context + g := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + errCh := make(chan error, 2) + defer close(errCh) + + // Route requests from the client to the server. + g.Go(func() error { + defer pUtil.Recover(logger, clientConn, destConn) + err := transferFrame(ctx, logger, destConn, clientConn, streamInfoCollection, reqFromClient, serverSideDecoder, mocks) + if err != nil { + // check for EOF error + if err == io.EOF { + logger.Debug("EOF error received from client. Closing conn") + return nil + } + utils.LogError(logger, err, "failed to transfer frame from client to server") + if ctx.Err() != nil { //to avoid sending error to the closed channel if the context is cancelled + return ctx.Err() + } + errCh <- err + } + return nil + }) + + // Route response from the server to the client. + clientSideDecoder := NewDecoder() + g.Go(func() error { + defer pUtil.Recover(logger, clientConn, destConn) + err := transferFrame(ctx, logger, clientConn, destConn, streamInfoCollection, !reqFromClient, clientSideDecoder, mocks) + if err != nil { + utils.LogError(logger, err, "failed to transfer frame from server to client") + if ctx.Err() != nil { //to avoid sending error to the closed channel if the context is cancelled + return ctx.Err() + } + errCh <- err + } + return nil + }) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + return nil + } + return err + } + // This would practically be an infinite loop, unless the client closes the grpc conn + // during the runtime of the application. + // A grpc server/client terminating after some time maybe intentional. +} diff --git a/keploy/pkg/core/proxy/integrations/grpc/frame.go b/keploy/pkg/core/proxy/integrations/grpc/frame.go new file mode 100644 index 0000000..65af4f8 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpc/frame.go @@ -0,0 +1,215 @@ +//go:build linux + +package grpc + +import ( + "context" + "fmt" + "io" + "net" + "strings" + "time" + + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" +) + +// transferFrame reads one frame from rhs and writes it to lhs. +func transferFrame(ctx context.Context, _ *zap.Logger, lhs net.Conn, rhs net.Conn, sic *StreamInfoCollection, reqFromClient bool, decoder *hpack.Decoder, mocks chan<- *models.Mock) error { + respFromServer := !reqFromClient + framer := http2.NewFramer(lhs, rhs) + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + frame, err := framer.ReadFrame() + if err != nil { + if err == io.EOF { + return err + } + return fmt.Errorf("error reading frame %v", err) + } + + switch frame := frame.(type) { + case *http2.SettingsFrame: + settingsFrame := frame + if settingsFrame.IsAck() { + // Transfer Ack. + if err := framer.WriteSettingsAck(); err != nil { + return fmt.Errorf("could not write ack for settings frame: %v", err) + } + } else { + var settingsCollection []http2.Setting + err = settingsFrame.ForeachSetting(func(setting http2.Setting) error { + settingsCollection = append(settingsCollection, setting) + return nil + }) + if err != nil { + return fmt.Errorf("could not read settings from settings frame: %v", err) + } + + if err := framer.WriteSettings(settingsCollection...); err != nil { + return fmt.Errorf("could not write settings fraame: %v", err) + } + } + case *http2.HeadersFrame: + headersFrame := frame + streamID := headersFrame.StreamID + err := framer.WriteHeaders(http2.HeadersFrameParam{ + StreamID: streamID, + BlockFragment: headersFrame.HeaderBlockFragment(), + EndStream: headersFrame.StreamEnded(), + EndHeaders: headersFrame.HeadersEnded(), + PadLength: 0, + Priority: headersFrame.Priority, + }) + if err != nil { + return fmt.Errorf("could not write headers frame: %v", err) + } + pseudoHeaders, ordinaryHeaders, err := extractHeaders(headersFrame, decoder) + if err != nil { + return fmt.Errorf("could not extract headers from frame: %v", err) + } + + if reqFromClient { + sic.AddHeadersForRequest(streamID, pseudoHeaders, true) + sic.AddHeadersForRequest(streamID, ordinaryHeaders, false) + + } else if respFromServer { + if headersFrame.StreamEnded() { + // Trailers — filter grpc-* as trailer, rest as normal headers + pseudoNormal, pseudoTrailer := splitGrpcTrailerHeaders(pseudoHeaders) + ordinaryNormal, ordinaryTrailer := splitGrpcTrailerHeaders(ordinaryHeaders) + + // Add "normal" parts as headers (still appears in trailers, but your system might need this distinction) + sic.AddHeadersForResponse(streamID, pseudoNormal, true, false) + sic.AddHeadersForResponse(streamID, ordinaryNormal, false, false) + + // Add "grpc-" keys as actual trailers + sic.AddHeadersForResponse(streamID, pseudoTrailer, true, true) + sic.AddHeadersForResponse(streamID, ordinaryTrailer, false, true) + + } else { + // Just regular headers + sic.AddHeadersForResponse(streamID, pseudoHeaders, true, false) + sic.AddHeadersForResponse(streamID, ordinaryHeaders, false, false) + } + } + // The trailers frame has been received. The stream has been closed by the server. + // Capture the mock and clear the map, as the stream ID can be reused by client. + if respFromServer && headersFrame.StreamEnded() { + sic.PersistMockForStream(ctx, streamID, mocks) + sic.ResetStream(streamID) + } + + case *http2.DataFrame: + dataFrame := frame + err := framer.WriteData(dataFrame.StreamID, dataFrame.StreamEnded(), dataFrame.Data()) + if err != nil { + return fmt.Errorf("could not write data frame: %v", err) + } + if reqFromClient { + // Capturing the request timestamp + sic.ReqTimestampMock = time.Now() + + sic.AddPayloadForRequest(dataFrame.StreamID, dataFrame.Data()) + } else if respFromServer { + // Capturing the response timestamp + sic.ResTimestampMock = time.Now() + + sic.AddPayloadForResponse(dataFrame.StreamID, dataFrame.Data()) + } + case *http2.PingFrame: + pingFrame := frame + err := framer.WritePing(pingFrame.IsAck(), pingFrame.Data) + if err != nil { + return fmt.Errorf("could not write ACK for ping: %v", err) + } + case *http2.WindowUpdateFrame: + windowUpdateFrame := frame + err := framer.WriteWindowUpdate(windowUpdateFrame.StreamID, windowUpdateFrame.Increment) + if err != nil { + return fmt.Errorf("could not write window tools frame: %v", err) + } + case *http2.ContinuationFrame: + continuationFrame := frame + err := framer.WriteContinuation(continuationFrame.StreamID, continuationFrame.HeadersEnded(), + continuationFrame.HeaderBlockFragment()) + if err != nil { + return fmt.Errorf("could not write continuation frame: %v", err) + } + case *http2.PriorityFrame: + priorityFrame := frame + err := framer.WritePriority(priorityFrame.StreamID, priorityFrame.PriorityParam) + if err != nil { + return fmt.Errorf("could not write priority frame: %v", err) + } + case *http2.RSTStreamFrame: + rstStreamFrame := frame + err := framer.WriteRSTStream(rstStreamFrame.StreamID, rstStreamFrame.ErrCode) + if err != nil { + return fmt.Errorf("could not write reset stream frame: %v", err) + } + case *http2.GoAwayFrame: + goAwayFrame := frame + err := framer.WriteGoAway(goAwayFrame.StreamID, goAwayFrame.ErrCode, goAwayFrame.DebugData()) + if err != nil { + return fmt.Errorf("could not write GoAway frame: %v", err) + } + case *http2.PushPromiseFrame: + pushPromiseFrame := frame + err := framer.WritePushPromise(http2.PushPromiseParam{ + StreamID: pushPromiseFrame.StreamID, + PromiseID: pushPromiseFrame.PromiseID, + BlockFragment: pushPromiseFrame.HeaderBlockFragment(), + EndHeaders: pushPromiseFrame.HeadersEnded(), + PadLength: 0, + }) + if err != nil { + return fmt.Errorf("could not write PushPromise frame: %v", err) + } + } + } + } +} + +func splitGrpcTrailerHeaders(headers map[string]string) (normal map[string]string, trailer map[string]string) { + normal = make(map[string]string) + trailer = make(map[string]string) + for k, v := range headers { + if strings.HasPrefix(k, "grpc-") { + trailer[k] = v + } else { + normal[k] = v + } + } + return +} + +// constants for dynamic table size +const ( + KmaxDynamicTableSize = 4096 +) + +func extractHeaders(frame *http2.HeadersFrame, decoder *hpack.Decoder) (pseudoHeaders, ordinaryHeaders map[string]string, err error) { + hf, err := decoder.DecodeFull(frame.HeaderBlockFragment()) + if err != nil { + return nil, nil, fmt.Errorf("could not decode headers: %v", err) + } + + pseudoHeaders = make(map[string]string) + ordinaryHeaders = make(map[string]string) + + for _, header := range hf { + if header.IsPseudo() { + pseudoHeaders[header.Name] = header.Value + } else { + ordinaryHeaders[header.Name] = header.Value + } + } + + return pseudoHeaders, ordinaryHeaders, nil +} diff --git a/keploy/pkg/core/proxy/integrations/grpc/grpc.go b/keploy/pkg/core/proxy/integrations/grpc/grpc.go new file mode 100644 index 0000000..0c545c4 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpc/grpc.go @@ -0,0 +1,71 @@ +//go:build linux + +package grpc + +import ( + "bytes" + "context" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// func init() { +// integrations.Register(integrations.GRPC, &integrations.Parsers{ +// Initializer: New, +// Priority: 100, +// }) +// } + +type Grpc struct { + logger *zap.Logger +} + +func New(logger *zap.Logger) integrations.Integrations { + return &Grpc{ + logger: logger, + } +} + +// MatchType function determines if the outgoing network call is gRPC by comparing the +// message format with that of an gRPC text message. +func (g *Grpc) MatchType(_ context.Context, reqBuf []byte) bool { + return bytes.HasPrefix(reqBuf[:], []byte("PRI * HTTP/2")) +} + +func (g *Grpc) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + logger := g.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial grpc message") + return err + } + + err = encodeGrpc(ctx, logger, reqBuf, src, dst, mocks, opts) + if err != nil { + utils.LogError(logger, err, "failed to encode the grpc message into the yaml") + return err + } + return nil +} + +func (g *Grpc) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { + logger := g.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial grpc message") + return err + } + + err = decodeGrpc(ctx, logger, reqBuf, src, dstCfg, mockDb, opts) + if err != nil { + utils.LogError(logger, err, "failed to decode the grpc message from the yaml") + return err + } + return nil +} diff --git a/keploy/pkg/core/proxy/integrations/grpc/match.go b/keploy/pkg/core/proxy/integrations/grpc/match.go new file mode 100644 index 0000000..4ece5b9 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpc/match.go @@ -0,0 +1,227 @@ +//go:build linux + +package grpc + +import ( + "context" + "fmt" + + "github.com/agnivade/levenshtein" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.uber.org/zap" + + "go.keploy.io/server/v2/pkg/models" +) + +func FilterMocksRelatedToGrpc(mocks []*models.Mock) []*models.Mock { + var res []*models.Mock + for _, mock := range mocks { + if mock != nil && mock.Kind == models.GRPC_EXPORT && mock.Spec.GRPCReq != nil && mock.Spec.GRPCResp != nil { + res = append(res, mock) + } + } + return res +} + +func FilterMocksBasedOnGrpcRequest(ctx context.Context, logger *zap.Logger, grpcReq models.GrpcReq, mockDb integrations.MockMemDb) (*models.Mock, error) { + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + mocks, err := mockDb.GetFilteredMocks() + if err != nil { + return nil, fmt.Errorf("error while getting tsc mocks %v", err) + } + + var matchedMock *models.Mock + var isMatched bool + + grpcMocks := FilterMocksRelatedToGrpc(mocks) + + if len(grpcMocks) == 0 { + logger.Debug("No grpc mocks found in the db") + return nil, nil + } + + logger.Debug("Here are the grpc mocks in the db", zap.Int("len", len(grpcMocks)), zap.Any("grpcMocks", grpcMocks)) + + schemaMatched, err := schemaMatch(ctx, grpcReq, grpcMocks) + if err != nil { + return nil, err + } + + if len(schemaMatched) == 0 { + logger.Debug("No mock found with schema match") + return nil, nil + } + + logger.Debug("Here are the grpc mocks with schema match", zap.Int("len", len(schemaMatched)), zap.Any("schemaMatched", schemaMatched)) + + // Exact body Match + ok, matchedMock := exactBodyMatch(grpcReq.Body, schemaMatched) + if ok { + logger.Debug("Exact body match found", zap.Any("matchedMock", matchedMock)) + if !mockDb.DeleteFilteredMock(*matchedMock) { + continue + } + return matchedMock, nil + } + + // apply fuzzy match for body with schemaMatched mocks + + logger.Debug("Performing fuzzy match for decoded data in body") + // Perform fuzzy match on the request + isMatched, bestMatch := fuzzyMatch(schemaMatched, grpcReq.Body.DecodedData) + if isMatched { + if !mockDb.DeleteFilteredMock(*bestMatch) { + continue + } + return bestMatch, nil + } + return nil, nil + } + } +} + +func schemaMatch(ctx context.Context, req models.GrpcReq, mocks []*models.Mock) ([]*models.Mock, error) { + var schemaMatched []*models.Mock + + for _, mock := range mocks { + if ctx.Err() != nil { + return nil, ctx.Err() + } + mockReq := mock.Spec.GRPCReq + + // the pseudo headers should defintely match. + if !compareMap(mockReq.Headers.PseudoHeaders, req.Headers.PseudoHeaders) { + continue + } + + // the ordinary headers keys should match. + if !compareMapKeys(mockReq.Headers.OrdinaryHeaders, req.Headers.OrdinaryHeaders) { + continue + } + + // the content type should match. + if mockReq.Headers.OrdinaryHeaders["content-type"] != req.Headers.OrdinaryHeaders["content-type"] { + continue + } + + // additionally check for the compression flag here only + if mockReq.Body.CompressionFlag != req.Body.CompressionFlag { + continue + } + + schemaMatched = append(schemaMatched, mock) + } + + return schemaMatched, nil +} + +// Check if two maps have the same keys +func compareMapKeys(m1, m2 map[string]string) bool { + if len(m1) > len(m2) { + for k := range m2 { + if _, ok := m1[k]; !ok { + return false + } + } + } else { + for k := range m1 { + if _, ok := m2[k]; !ok { + return false + } + } + } + return true +} + +// Check if two maps are identical +func compareMap(m1, m2 map[string]string) bool { + if len(m1) != len(m2) { + return false + } + for k, v := range m1 { + if v2, ok := m2[k]; !ok || v != v2 { + return false + } + } + return true +} + +func exactBodyMatch(body models.GrpcLengthPrefixedMessage, schemaMatched []*models.Mock) (bool, *models.Mock) { + for _, mock := range schemaMatched { + if mock.Spec.GRPCReq.Body.MessageLength == body.MessageLength && mock.Spec.GRPCReq.Body.DecodedData == body.DecodedData { + return true, mock + } + } + return false, nil +} + +// Fuzzy match helper for string matching +func findStringMatch(req string, mockStrings []string) int { + minDist := int(^uint(0) >> 1) + bestMatch := -1 + for idx, mock := range mockStrings { + if !util.IsASCII(mock) { + continue + } + dist := levenshtein.ComputeDistance(req, mock) + if dist == 0 { + return 0 + } + if dist < minDist { + minDist = dist + bestMatch = idx + } + } + return bestMatch +} + +// TODO: generalize the function to work with any type of integration +func findBinaryMatch(mocks []*models.Mock, reqBuff []byte) int { + + mxSim := -1.0 + mxIdx := -1 + // find the fuzzy hash of the mocks + for idx, mock := range mocks { + encoded := []byte(mock.Spec.GRPCReq.Body.DecodedData) + k := util.AdaptiveK(len(reqBuff), 3, 8, 5) + shingles1 := util.CreateShingles(encoded, k) + shingles2 := util.CreateShingles(reqBuff, k) + similarity := util.JaccardSimilarity(shingles1, shingles2) + + // log.Debugf("Jaccard Similarity:%f\n", similarity) + + if mxSim < similarity { + mxSim = similarity + mxIdx = idx + } + } + return mxIdx +} + +// fuzzy match on the request +func fuzzyMatch(tcsMocks []*models.Mock, reqBuff string) (bool, *models.Mock) { + + // String-based fuzzy matching + mockStrings := make([]string, len(tcsMocks)) + for i := range tcsMocks { + mockStrings[i] = tcsMocks[i].Spec.GRPCReq.Body.DecodedData + } + + if util.IsASCII(reqBuff) { + idx := findStringMatch(string(reqBuff), mockStrings) + if idx != -1 { + return true, tcsMocks[idx] + } + } + + idx := findBinaryMatch(tcsMocks, []byte(reqBuff)) + if idx != -1 { + return true, tcsMocks[idx] + } + return false, nil +} diff --git a/keploy/pkg/core/proxy/integrations/grpc/stream.go b/keploy/pkg/core/proxy/integrations/grpc/stream.go new file mode 100644 index 0000000..e98f7b1 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpc/stream.go @@ -0,0 +1,160 @@ +//go:build linux + +package grpc + +import ( + "context" + "encoding/binary" + "sync" + "time" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" +) + +// StreamInfoCollection is a thread-safe data structure to store all communications +// that happen in a stream for grpc. This includes the headers and data frame for the +// request and response. +type StreamInfoCollection struct { + mutex sync.Mutex + StreamInfo map[uint32]models.GrpcStream + ReqTimestampMock time.Time + ResTimestampMock time.Time +} + +func NewStreamInfoCollection() *StreamInfoCollection { + return &StreamInfoCollection{ + StreamInfo: make(map[uint32]models.GrpcStream), + } +} + +func (sic *StreamInfoCollection) InitialiseStream(streamID uint32) { + sic.mutex.Lock() + defer sic.mutex.Unlock() + + _, ok := sic.StreamInfo[streamID] + if !ok { + sic.StreamInfo[streamID] = models.NewGrpcStream(streamID) + } +} + +func (sic *StreamInfoCollection) AddHeadersForRequest(streamID uint32, headers map[string]string, isPseudo bool) { + // Initialise the stream before acquiring the lock for yourself. + sic.InitialiseStream(streamID) + sic.mutex.Lock() + defer sic.mutex.Unlock() + + for key, value := range headers { + if isPseudo { + sic.StreamInfo[streamID].GrpcReq.Headers.PseudoHeaders[key] = value + } else { + sic.StreamInfo[streamID].GrpcReq.Headers.OrdinaryHeaders[key] = value + } + } +} + +func (sic *StreamInfoCollection) AddHeadersForResponse(streamID uint32, headers map[string]string, isPseudo, isTrailer bool) { + // Initialise the stream before acquiring the lock for yourself. + sic.InitialiseStream(streamID) + sic.mutex.Lock() + defer sic.mutex.Unlock() + + for key, value := range headers { + if isTrailer { + if isPseudo { + sic.StreamInfo[streamID].GrpcResp.Trailers.PseudoHeaders[key] = value + } else { + sic.StreamInfo[streamID].GrpcResp.Trailers.OrdinaryHeaders[key] = value + } + } else { + if isPseudo { + sic.StreamInfo[streamID].GrpcResp.Headers.PseudoHeaders[key] = value + } else { + sic.StreamInfo[streamID].GrpcResp.Headers.OrdinaryHeaders[key] = value + } + } + } +} + +// AddPayloadForRequest adds the DATA frame to the stream. +// A data frame always appears after at least one header frame. Hence, we implicitly +// assume that the stream has been initialised. +func (sic *StreamInfoCollection) AddPayloadForRequest(streamID uint32, payload []byte) { + sic.mutex.Lock() + defer sic.mutex.Unlock() + + info := sic.StreamInfo[streamID] + + info.ReqRawData = append(info.ReqRawData, payload...) + + if !info.ReqPrefixParsed && len(info.ReqRawData) >= 5 { + info.ReqExpectedLength = binary.BigEndian.Uint32(info.ReqRawData[1:5]) + info.ReqPrefixParsed = true + } + + totalLen := 5 + int(info.ReqExpectedLength) + if info.ReqPrefixParsed && len(info.ReqRawData) >= totalLen { + info.GrpcReq.Body = pkg.CreateLengthPrefixedMessageFromPayload(info.ReqRawData[:totalLen]) + } + + sic.StreamInfo[streamID] = info +} + +// AddPayloadForResponse adds the DATA frame to the stream. +// A data frame always appears after at least one header frame. Hence, we implicitly +// assume that the stream has been initialised. +func (sic *StreamInfoCollection) AddPayloadForResponse(streamID uint32, payload []byte) { + sic.mutex.Lock() + defer sic.mutex.Unlock() + + info := sic.StreamInfo[streamID] + + info.RespRawData = append(info.RespRawData, payload...) + + if !info.RespPrefixParsed && len(info.RespRawData) >= 5 { + info.RespExpectedLength = binary.BigEndian.Uint32(info.RespRawData[1:5]) + info.RespPrefixParsed = true + } + + totalLen := 5 + int(info.RespExpectedLength) + if info.RespPrefixParsed && len(info.RespRawData) >= totalLen { + info.GrpcResp.Body = pkg.CreateLengthPrefixedMessageFromPayload(info.RespRawData[:totalLen]) + } + + sic.StreamInfo[streamID] = info +} +func (sic *StreamInfoCollection) PersistMockForStream(ctx context.Context, streamID uint32, mocks chan<- *models.Mock) { + sic.mutex.Lock() + defer sic.mutex.Unlock() + grpcReq := sic.StreamInfo[streamID].GrpcReq + grpcResp := sic.StreamInfo[streamID].GrpcResp + metadata := make(map[string]string) + metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) + // save the mock + mocks <- &models.Mock{ + Version: models.GetVersion(), + Name: "mocks", + Kind: models.GRPC_EXPORT, + Spec: models.MockSpec{ + Metadata: metadata, + GRPCReq: &grpcReq, + GRPCResp: &grpcResp, + ReqTimestampMock: sic.ReqTimestampMock, + ResTimestampMock: sic.ResTimestampMock, + }, + } +} + +func (sic *StreamInfoCollection) FetchRequestForStream(streamID uint32) models.GrpcReq { + sic.mutex.Lock() + defer sic.mutex.Unlock() + + return sic.StreamInfo[streamID].GrpcReq +} + +func (sic *StreamInfoCollection) ResetStream(streamID uint32) { + sic.mutex.Lock() + defer sic.mutex.Unlock() + + delete(sic.StreamInfo, streamID) +} diff --git a/keploy/pkg/core/proxy/integrations/grpc/transcoder.go b/keploy/pkg/core/proxy/integrations/grpc/transcoder.go new file mode 100644 index 0000000..300570f --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpc/transcoder.go @@ -0,0 +1,379 @@ +//go:build linux + +package grpc + +import ( + "bytes" + "context" + "fmt" + "io" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/utils" + + "go.uber.org/zap" + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" +) + +type Transcoder struct { + sic *StreamInfoCollection + mockDb integrations.MockMemDb + logger *zap.Logger + framer *http2.Framer + decoder *hpack.Decoder +} + +func NewTranscoder(logger *zap.Logger, framer *http2.Framer, mockDb integrations.MockMemDb) *Transcoder { + return &Transcoder{ + logger: logger, + framer: framer, + mockDb: mockDb, + sic: NewStreamInfoCollection(), + decoder: NewDecoder(), + } +} + +// TODO: (add reason for not using the default value i.e. 16384 // 16KB) +const MAX_FRAME_SIZE = 8192 // 8KB + +func (srv *Transcoder) WriteInitialSettingsFrame() error { + var settings []http2.Setting + // TODO : Get Settings from config file. + settings = append(settings, http2.Setting{ + ID: http2.SettingMaxFrameSize, + Val: MAX_FRAME_SIZE, + }) + return srv.framer.WriteSettings(settings...) +} + +func (srv *Transcoder) ProcessPingFrame(pingFrame *http2.PingFrame) error { + if pingFrame.IsAck() { + // An endpoint MUST NOT respond to PING frames containing this flag. + return nil + } + + if pingFrame.StreamID != 0 { + // "PING frames are not associated with any individual + // stream. If a PING frame is received with a stream + // identifier field value other than 0x0, the recipient MUST + // respond with a conn error (Section 5.4.1) of type + // PROTOCOL_ERROR." + utils.LogError(srv.logger, nil, "As per HTTP/2 spec, stream ID for PING frame should be zero.", zap.Any("stream_id", pingFrame.StreamID)) + return http2.ConnectionError(http2.ErrCodeProtocol) + } + + // Write the ACK for the PING request. + return srv.framer.WritePing(true, pingFrame.Data) + +} + +func (srv *Transcoder) ProcessDataFrame(ctx context.Context, dataFrame *http2.DataFrame) error { + id := dataFrame.Header().StreamID + // DATA frame must be associated with a stream + if id == 0 { + utils.LogError(srv.logger, nil, "As per HTTP/2 spec, DATA frame must be associated with a stream.", zap.Any("stream_id", id)) + return http2.ConnectionError(http2.ErrCodeProtocol) + } + srv.sic.AddPayloadForRequest(id, dataFrame.Data()) + + if dataFrame.StreamEnded() { + defer srv.sic.ResetStream(dataFrame.StreamID) + } + + grpcReq := srv.sic.FetchRequestForStream(id) + + srv.logger.Debug("Getting mock for request from the mock database", zap.Any("request", grpcReq)) + + // Fetch all the mocks. We can't assume that the grpc calls are made in a certain order. + mock, err := FilterMocksBasedOnGrpcRequest(ctx, srv.logger, grpcReq, srv.mockDb) + if err != nil { + return fmt.Errorf("failed match mocks: %v", err) + } + if mock == nil { + return fmt.Errorf("failed to mock the output for unrecorded outgoing grpc call") + } + + srv.logger.Debug("Found a mock for the request", zap.Any("mock", mock)) + + grpcMockResp := mock.Spec.GRPCResp + + // First, send the headers frame. + buf := new(bytes.Buffer) + encoder := hpack.NewEncoder(buf) + + // The pseudo headers should be written before ordinary ones. + for key, value := range grpcMockResp.Headers.PseudoHeaders { + err := encoder.WriteField(hpack.HeaderField{ + Name: key, + Value: value, + }) + if err != nil { + utils.LogError(srv.logger, err, "could not encode pseudo header", zap.Any("key", key), zap.Any("value", value)) + return err + } + } + for key, value := range grpcMockResp.Headers.OrdinaryHeaders { + err := encoder.WriteField(hpack.HeaderField{ + Name: key, + Value: value, + }) + if err != nil { + utils.LogError(srv.logger, err, "could not encode ordinary header", zap.Any("key", key), zap.Any("value", value)) + return err + } + } + + // The headers are prepared. Write the frame. + srv.logger.Debug("Writing the first set of headers in a new HEADER frame.") + err = srv.framer.WriteHeaders(http2.HeadersFrameParam{ + StreamID: id, + BlockFragment: buf.Bytes(), + EndStream: false, + EndHeaders: true, + }) + if err != nil { + utils.LogError(srv.logger, err, "could not write the first set of headers onto client") + return err + } + + payload, err := pkg.CreatePayloadFromLengthPrefixedMessage(grpcMockResp.Body) + if err != nil { + utils.LogError(srv.logger, err, "could not create grpc payload from mocks") + return err + } + + srv.logger.Debug("Writing the payload in a DATA frame", zap.Int("payload length", len(payload))) + // Write the DATA frame with the payload. + err = srv.WriteData(ctx, id, payload) + if err != nil { + utils.LogError(srv.logger, err, "could not write the data frame onto the client") + return err + } + // Reset the buffer and start with a new encoding. + buf = new(bytes.Buffer) + encoder = hpack.NewEncoder(buf) + + srv.logger.Debug("preparing the trailers in a different HEADER frame") + //Prepare the trailers. + //The pseudo headers should be written before ordinary ones. + for key, value := range grpcMockResp.Trailers.PseudoHeaders { + err := encoder.WriteField(hpack.HeaderField{ + Name: key, + Value: value, + }) + if err != nil { + utils.LogError(srv.logger, err, "could not encode pseudo header", zap.Any("key", key), zap.Any("value", value)) + return err + } + } + for key, value := range grpcMockResp.Trailers.OrdinaryHeaders { + err := encoder.WriteField(hpack.HeaderField{ + Name: key, + Value: value, + }) + if err != nil { + utils.LogError(srv.logger, err, "could not encode ordinary header", zap.Any("key", key), zap.Any("value", value)) + return err + } + } + + // The trailer is prepared. Write the frame. + srv.logger.Debug("Writing the trailers in a different HEADER frame") + err = srv.framer.WriteHeaders(http2.HeadersFrameParam{ + StreamID: id, + BlockFragment: buf.Bytes(), + EndStream: true, + EndHeaders: true, + }) + if err != nil { + utils.LogError(srv.logger, err, "could not write the trailers onto client") + return err + } + + return nil +} + +func (srv *Transcoder) WriteData(ctx context.Context, streamID uint32, payload []byte) error { + totalLen := len(payload) + + // Fast path: if payload fits in one frame + if totalLen <= MAX_FRAME_SIZE { + select { + case <-ctx.Done(): + srv.logger.Warn("context cancelled before writing single frame") + return ctx.Err() + default: + err := srv.framer.WriteData(streamID, false, payload) + if err != nil { + utils.LogError(srv.logger, err, "could not write data frame") + return err + } + return nil + } + } + + // Chunked path + offset := 0 + for offset < totalLen { + // Check for context cancellation before each write + select { + case <-ctx.Done(): + srv.logger.Warn("context cancelled during chunked frame write") + return ctx.Err() + default: + } + + remaining := totalLen - offset + chunkSize := min(remaining, MAX_FRAME_SIZE) + + end := offset + chunkSize + data := payload[offset:end] + + srv.logger.Debug("Writing chunked data frame", zap.Int("chunk size", chunkSize), zap.Int("offset", offset), zap.Int("end", end)) + err := srv.framer.WriteData(streamID, false, data) + if err != nil { + utils.LogError(srv.logger, err, "could not write chunked data frame") + return err + } + + offset = end + if offset == totalLen { + srv.logger.Debug("the offset is equal to the total length of the payload", zap.Int("offset", offset)) + } + } + + return nil +} + +func (srv *Transcoder) ProcessWindowUpdateFrame(_ *http2.WindowUpdateFrame) error { + // Silently ignore Window tools frames, as we already know the mock payloads that we would send. + srv.logger.Debug("Received Window Update Frame. Skipping it...") + return nil +} + +func (srv *Transcoder) ProcessResetStreamFrame(resetStreamFrame *http2.RSTStreamFrame) error { + srv.sic.ResetStream(resetStreamFrame.StreamID) + return nil +} + +func (srv *Transcoder) ProcessSettingsFrame(settingsFrame *http2.SettingsFrame) error { + // ACK the settings and silently skip the processing. + // There is no actual server to tune the settings on. We already know the default settings from record mode. + // TODO : Add support for dynamically updating the settings. + if !settingsFrame.IsAck() { + return srv.framer.WriteSettingsAck() + } + return nil +} + +func (srv *Transcoder) ProcessGoAwayFrame(_ *http2.GoAwayFrame) error { + // We do not support a client that requests a server to shut down during test mode. Warn the user. + // TODO : Add support for dynamically shutting down mock server using a channel to send close request. + srv.logger.Warn("Received GoAway Frame. Ideally, clients should not close server during test mode.") + return nil +} + +func (srv *Transcoder) ProcessPriorityFrame(_ *http2.PriorityFrame) error { + // We do not support reordering of frames based on priority, because we flush after each response. + // Silently skip it. + srv.logger.Debug("Received PRIORITY frame, Skipping it...") + return nil +} + +func (srv *Transcoder) ProcessHeadersFrame(headersFrame *http2.HeadersFrame) error { + id := headersFrame.StreamID + // Streams initiated by a client MUST use odd-numbered stream identifiers + if id%2 != 1 { + utils.LogError(srv.logger, nil, "As per HTTP/2 spec, stream_id must be odd for a client if conn init by client.", zap.Any("stream_id", id)) + return http2.ConnectionError(http2.ErrCodeProtocol) + } + + pseudoHeaders, ordinaryHeaders, err := extractHeaders(headersFrame, srv.decoder) + if err != nil { + utils.LogError(srv.logger, err, "could not extract headers from frame") + } + + srv.sic.AddHeadersForRequest(id, pseudoHeaders, true) + srv.sic.AddHeadersForRequest(id, ordinaryHeaders, false) + return nil +} + +func (srv *Transcoder) ProcessPushPromise(_ *http2.PushPromiseFrame) error { + // A client cannot push. Thus, servers MUST treat the receipt of a PUSH_PROMISE + // frame as a conn error (Section 5.4.1) of type PROTOCOL_ERROR. + utils.LogError(srv.logger, nil, "As per HTTP/2 spec, client cannot send PUSH_PROMISE.") + return http2.ConnectionError(http2.ErrCodeProtocol) +} + +func (srv *Transcoder) ProcessContinuationFrame(_ *http2.ContinuationFrame) error { + // Continuation frame support is overkill currently because the headers won't exceed the frame size + // used by our mock server. + // However, if we really need this feature, we can implement it later. + utils.LogError(srv.logger, nil, "Continuation Frame received. This is unsupported currently") + return fmt.Errorf("continuation frame is unsupported in the current implementation") +} + +func (srv *Transcoder) ProcessGenericFrame(ctx context.Context, frame http2.Frame) error { + var err error + switch frame := frame.(type) { + case *http2.PingFrame: + err = srv.ProcessPingFrame(frame) + case *http2.DataFrame: + err = srv.ProcessDataFrame(ctx, frame) + case *http2.WindowUpdateFrame: + err = srv.ProcessWindowUpdateFrame(frame) + case *http2.RSTStreamFrame: + err = srv.ProcessResetStreamFrame(frame) + case *http2.SettingsFrame: + err = srv.ProcessSettingsFrame(frame) + case *http2.GoAwayFrame: + err = srv.ProcessGoAwayFrame(frame) + case *http2.PriorityFrame: + err = srv.ProcessPriorityFrame(frame) + case *http2.HeadersFrame: + err = srv.ProcessHeadersFrame(frame) + case *http2.PushPromiseFrame: + err = srv.ProcessPushPromise(frame) + case *http2.ContinuationFrame: + err = srv.ProcessContinuationFrame(frame) + default: + err = fmt.Errorf("unknown frame received from the client") + } + + return err +} + +// ListenAndServe is a forever blocking call that reads one frame at a time, and responds to them. +func (srv *Transcoder) ListenAndServe(ctx context.Context) error { + err := srv.WriteInitialSettingsFrame() + if err != nil { + utils.LogError(srv.logger, err, "could not write initial settings frame") + return err + } + + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + frame, err := srv.framer.ReadFrame() + if err != nil { + if err == io.EOF { + srv.logger.Debug("EOF reached. Closing the connection.") + return io.EOF + } + utils.LogError(srv.logger, err, "Failed to read frame") + return err + } + if ctx.Err() != nil { + return ctx.Err() + } + err = srv.ProcessGenericFrame(ctx, frame) + if err != nil { + return err + } + } + } +} diff --git a/keploy/pkg/core/proxy/integrations/grpc/util.go b/keploy/pkg/core/proxy/integrations/grpc/util.go new file mode 100644 index 0000000..8a7f892 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpc/util.go @@ -0,0 +1,10 @@ +//go:build linux + +package grpc + +import "golang.org/x/net/http2/hpack" + +// NewDecoder returns a header decoder. +func NewDecoder() *hpack.Decoder { + return hpack.NewDecoder(KmaxDynamicTableSize, nil) +} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/codec.go b/keploy/pkg/core/proxy/integrations/grpcV2/codec.go new file mode 100644 index 0000000..6a6c89f --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpcV2/codec.go @@ -0,0 +1,76 @@ +//go:build linux + +package grpcV2 + +import ( + "fmt" + + "google.golang.org/protobuf/proto" +) + +const rawCodecName = "keploy-raw" + +// rawCodec is a gRPC codec that passes byte slices through without any serialization. +// This is crucial for proxying or mocking requests when we don't have the .proto definitions. +type rawCodec struct{} + +// rawMessage is a wrapper for byte slices to satisfy the proto.Message interface. +type rawMessage struct { + data []byte +} + +func (m *rawMessage) Reset() { *m = rawMessage{} } +func (m *rawMessage) String() string { return string(m.data) } +func (*rawMessage) ProtoMessage() {} + +func (c *rawCodec) Marshal(v interface{}) ([]byte, error) { + // Marshal the rawMessage wrapper to its underlying byte slice. + if rm, ok := v.(*rawMessage); ok { + return rm.data, nil + } + // Fallback for other types, though we primarily use rawMessage. + if p, ok := v.(proto.Message); ok { + return proto.Marshal(p) + } + return nil, fmt.Errorf("failed to marshal, message is %T, want proto.Message", v) +} + +func (c *rawCodec) Unmarshal(data []byte, v interface{}) error { + // Unmarshal the byte slice into the rawMessage wrapper. + if rm, ok := v.(*rawMessage); ok { + rm.data = make([]byte, len(data)) + copy(rm.data, data) + return nil + } + // Fallback for other types. + if p, ok := v.(proto.Message); ok { + return proto.Unmarshal(data, p) + } + return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v) +} + +func (c *rawCodec) Name() string { + return rawCodecName +} + +func (c *rawCodec) String() string { + return c.Name() +} + +// passthroughCodec keeps 'proto' on the wire but avoids re-encoding. +type passthroughCodec struct{} + +func (passthroughCodec) Name() string { return "proto" } // server already knows this one +func (passthroughCodec) Marshal(v interface{}) ([]byte, error) { + if m, ok := v.(*rawMessage); ok { + return m.data, nil // send bytes exactly as we received them + } + return proto.Marshal(v.(proto.Message)) +} +func (passthroughCodec) Unmarshal(data []byte, v interface{}) error { + if m, ok := v.(*rawMessage); ok { + m.data = append([]byte(nil), data...) + return nil + } + return proto.Unmarshal(data, v.(proto.Message)) +} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/grpc.go b/keploy/pkg/core/proxy/integrations/grpcV2/grpc.go new file mode 100644 index 0000000..154968e --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpcV2/grpc.go @@ -0,0 +1,92 @@ +//go:build linux + +package grpcV2 + +import ( + "bytes" + "context" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/encoding" + "google.golang.org/grpc/status" +) + +func init() { + // Register the raw codec for passing raw bytes through the gRPC framework. + encoding.RegisterCodec(new(rawCodec)) + + integrations.Register(integrations.GRPC, &integrations.Parsers{ + Initializer: New, + Priority: 100, + }) +} + +type Grpc struct { + logger *zap.Logger +} + +func New(logger *zap.Logger) integrations.Integrations { + return &Grpc{ + logger: logger, + } +} + +// MatchType determines if the outgoing network call is gRPC by checking for the HTTP/2 preface. +func (g *Grpc) MatchType(_ context.Context, reqBuf []byte) bool { + const preface = "PRI * HTTP/2" + if len(reqBuf) < len(preface) { + return false + } + return bytes.HasPrefix(reqBuf, []byte(preface)) +} + +func (g *Grpc) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + cid, ok := ctx.Value(models.ClientConnectionIDKey).(string) + if !ok { + return status.Errorf(codes.Internal, "missing ClientConnectionID in context") + } + did, ok := ctx.Value(models.DestConnectionIDKey).(string) + if !ok { + return status.Errorf(codes.Internal, "missing DestinationConnectionID in context") + } + logger := g.logger.With( + zap.String("Client ConnectionID", cid), + zap.String("Destination ConnectionID", did), + zap.String("Client IP Address", src.RemoteAddr().String())) + // Peek the preface (needed for type detection) **but replay it** for the gRPC server. + preface, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial grpc message") + return err + } + + return recordOutgoing(ctx, logger, + newReplayConn(preface, src), // <- give server the full preface + dst, mocks) +} + +func (g *Grpc) MockOutgoing(ctx context.Context, src net.Conn, _ *models.ConditionalDstCfg, mockDb integrations.MockMemDb, _ models.OutgoingOptions) error { + cid, ok := ctx.Value(models.ClientConnectionIDKey).(string) + if !ok { + return status.Errorf(codes.Internal, "missing ClientConnectionID in context") + } + logger := g.logger.With( + zap.String("Client ConnectionID", cid), + zap.String("Client IP Address", src.RemoteAddr().String())) + // Consume the initial preface buffer from the connection. + preface, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial grpc message") + return err + } + + return mockOutgoing(ctx, logger, + newReplayConn(preface, src), // <- same in mock path + mockDb) +} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/helper.go b/keploy/pkg/core/proxy/integrations/grpcV2/helper.go new file mode 100644 index 0000000..e4c9749 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpcV2/helper.go @@ -0,0 +1,46 @@ +//go:build linux + +package grpcV2 + +import ( + "github.com/protocolbuffers/protoscope" + "go.keploy.io/server/v2/pkg/models" +) + +// createLengthPrefixedMessage creates a GrpcLengthPrefixedMessage from a raw message payload. +// The gRPC framework handles the actual 5-byte wire protocol prefix. This struct +// is for Keploy's internal representation and matching. +func createLengthPrefixedMessage(data []byte) models.GrpcLengthPrefixedMessage { + // The original implementation stored the raw bytes as a string, which can + // safely hold binary data in Go. We will follow this for consistency with + // the existing fuzzy matching logic. + return models.GrpcLengthPrefixedMessage{ + // Compression flag is 0 for uncompressed. + CompressionFlag: 0, + // MessageLength is the length of the raw data. + MessageLength: uint32(len(data)), + // DecodedData holds the text representation of the wire data. + DecodedData: prettyPrintWire(data, 0), + } +} + +// createPayloadFromLengthPrefixedMessage decodes the pretty-printed data +// from a message back into its raw binary payload. +func createPayloadFromLengthPrefixedMessage(msg models.GrpcLengthPrefixedMessage) ([]byte, error) { + return parsePrettyWire(msg.DecodedData) +} + +// prettyPrintWire renders any protobuf wire payload without needing the .proto file. +// It uses protoscope for a robust, standardized text format. +func prettyPrintWire(b []byte, _ int) string { + // The indent argument is ignored as protoscope handles formatting automatically. + return protoscope.Write(b, protoscope.WriterOptions{}) +} + +// parsePrettyWire decodes the human-readable text format from protoscope +// back into its binary wire format. +func parsePrettyWire(s string) ([]byte, error) { + scanner := protoscope.NewScanner(s) + // The scanner.Exec() method handles the entire parsing process. + return scanner.Exec() +} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/listener.go b/keploy/pkg/core/proxy/integrations/grpcV2/listener.go new file mode 100644 index 0000000..f40d5d6 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpcV2/listener.go @@ -0,0 +1,64 @@ +//go:build linux + +package grpcV2 + +import ( + "net" + "sync" +) + +// singleConnListener adapts an existing net.Conn so it can be passed to +// grpc.Serve, which expects something that looks like a net.Listener but +// only ever needs to serve one connection. +type singleConnListener struct { + conn net.Conn // the single connection we expose + once sync.Once // hands out conn exactly once + closeOnce sync.Once // closes the "done" channel exactly once + done chan struct{} +} + +func newSingleConnListener(conn net.Conn) *singleConnListener { + return &singleConnListener{ + conn: conn, + done: make(chan struct{}), + } +} + +func (l *singleConnListener) Accept() (net.Conn, error) { + var first bool + l.once.Do(func() { first = true }) + + if first { + // Wrap the conn so that closing it notifies the listener. + return &trackedConn{ + Conn: l.conn, + onClose: func() { + l.closeOnce.Do(func() { close(l.done) }) + }, + }, nil + } + + // After the first connection, Serve() may call Accept() again. + <-l.done // block until the trackedConn is closed + return nil, net.ErrClosed +} + +func (l *singleConnListener) Close() error { + l.closeOnce.Do(func() { close(l.done) }) + return l.conn.Close() +} + +func (l *singleConnListener) Addr() net.Addr { return l.conn.LocalAddr() } + +// trackedConn executes onClose exactly once when Close is called. +type trackedConn struct { + net.Conn + once sync.Once + onClose func() +} + +func (c *trackedConn) Close() error { + err := c.Conn.Close() + c.once.Do(c.onClose) + return err +} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/match.go b/keploy/pkg/core/proxy/integrations/grpcV2/match.go new file mode 100644 index 0000000..bc3b9cc --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpcV2/match.go @@ -0,0 +1,291 @@ +//go:build linux + +package grpcV2 + +import ( + "context" + "fmt" + + "github.com/agnivade/levenshtein" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.uber.org/zap" + + "go.keploy.io/server/v2/pkg/matcher/grpc" + "go.keploy.io/server/v2/pkg/models" +) + +func FilterMocksRelatedToGrpc(mocks []*models.Mock) []*models.Mock { + var res []*models.Mock + for _, mock := range mocks { + if mock != nil && mock.Kind == models.GRPC_EXPORT && mock.Spec.GRPCReq != nil && mock.Spec.GRPCResp != nil { + res = append(res, mock) + } + } + return res +} + +func FilterMocksBasedOnGrpcRequest(ctx context.Context, logger *zap.Logger, grpcReq models.GrpcReq, mockDb integrations.MockMemDb) (*models.Mock, error) { + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + mocks, err := mockDb.GetFilteredMocks() + if err != nil { + return nil, fmt.Errorf("error while getting tsc mocks %v", err) + } + + var matchedMock *models.Mock + var isMatched bool + + grpcMocks := FilterMocksRelatedToGrpc(mocks) + + if len(grpcMocks) == 0 { + logger.Debug("No grpc mocks found in the db") + return nil, nil + } + + logger.Debug("grpc mocks in DB", zap.Int("len", len(grpcMocks))) + + for _, mock := range grpcMocks { + logger.Debug("found grpc mock", zap.String("name", mock.Name)) + } + + schemaMatched, err := schemaMatch(ctx, logger, grpcReq, grpcMocks) + if err != nil { + return nil, err + } + + if len(schemaMatched) == 0 { + logger.Debug("No mock found with schema match") + return nil, nil + } + + logger.Debug("grpc mocks with schema match", zap.Int("len", len(schemaMatched))) + + for _, mock := range schemaMatched { + logger.Debug("schema matched grpc mock", zap.String("name", mock.Name)) + } + + // Exact body Match + expBody := grpc.CanonicalizeTopLevelBlocks(grpcReq.Body.DecodedData) + ok, matchedMock := exactBodyMatch(logger, expBody, schemaMatched) + if ok { + logger.Debug("exact body match found", zap.String("name", matchedMock.Name)) + if !mockDb.DeleteFilteredMock(*matchedMock) { + continue + } + return matchedMock, nil + } + + // apply fuzzy match for body with schemaMatched mocks + + // apply fuzzy match for body with schemaMatched mocks + // Guard against quadratic work on very large bodies. + if len(expBody) > 512*1024 { + logger.Debug("skipping fuzzy match for large body", zap.Int("len", len(expBody))) + return nil, nil + } + logger.Debug("performing fuzzy match for decoded data in body") + // Perform fuzzy match on the request + isMatched, bestMatch := fuzzyMatch(schemaMatched, grpcReq.Body.DecodedData) + if isMatched { + if !mockDb.DeleteFilteredMock(*bestMatch) { + continue + } + return bestMatch, nil + } + return nil, nil + } + } +} + +func schemaMatch(ctx context.Context, logger *zap.Logger, req models.GrpcReq, mocks []*models.Mock) ([]*models.Mock, error) { + var schemaMatched []*models.Mock + + for _, mock := range mocks { + if ctx.Err() != nil { + return nil, ctx.Err() + } + mockReq := mock.Spec.GRPCReq + // Require :method and :path to match exactly; tolerate :authority-only differences. + mp := mockReq.Headers.PseudoHeaders + rp := req.Headers.PseudoHeaders + // Require presence AND equality (empty means missing) + if mp[":method"] == "" || rp[":method"] == "" || mp[":method"] != rp[":method"] || + mp[":path"] == "" || rp[":path"] == "" || mp[":path"] != rp[":path"] { + continue + } + if mp[":authority"] != rp[":authority"] { + logger.Debug("ignoring :authority mismatch for gRPC request", + zap.String("mock", mock.Name), + zap.String("mock_authority", mp[":authority"]), + zap.String("req_authority", rp[":authority"])) + } + + // For the rest of pseudo-headers, compare with :authority skipped (if present). + if !compareMapExcept(mp, rp, map[string]struct{}{":authority": {}}) { + continue + } + // the ordinary headers keys should match. + if !compareMapKeys(mockReq.Headers.OrdinaryHeaders, req.Headers.OrdinaryHeaders) { + continue + } + + // the content type should match. + if mockReq.Headers.OrdinaryHeaders["content-type"] != req.Headers.OrdinaryHeaders["content-type"] { + continue + } + + schemaMatched = append(schemaMatched, mock) + } + + return schemaMatched, nil +} + +// compareMapExcept compares two string maps, skipping keys in 'skip'. +func compareMapExcept(m1, m2 map[string]string, skip map[string]struct{}) bool { + if len(m1) != len(m2) { + // Lengths may differ only due to skipped keys; check key-wise. + } + for k, v1 := range m1 { + if _, ok := skip[k]; ok { + continue + } + if v2, ok := m2[k]; !ok || v1 != v2 { + return false + } + } + for k := range m2 { + if _, ok := skip[k]; ok { + continue + } + if _, ok := m1[k]; !ok { + return false + } + } + return true +} + +// Check if two maps have the same keys, ignoring values. +func compareMapKeys(m1, m2 map[string]string) bool { + if len(m1) != len(m2) { + return false + } + for k := range m1 { + if _, ok := m2[k]; !ok { + return false + } + } + return true +} + +// Check if two maps are identical. +func compareMap(m1, m2 map[string]string) bool { + if len(m1) != len(m2) { + return false + } + for k, v := range m1 { + if v2, ok := m2[k]; !ok || v != v2 { + return false + } + } + return true +} + +func exactBodyMatch(logger *zap.Logger, expBody string, schemaMatched []*models.Mock) (bool, *models.Mock) { + for _, mock := range schemaMatched { + got := grpc.CanonicalizeTopLevelBlocks(mock.Spec.GRPCReq.Body.DecodedData) + logger.Debug("Comparing bodies for mock", zap.String("name", mock.Name)) + if got == expBody { + return true, mock + } + } + return false, nil +} + +// fuzzyMatch logic with input trimming for performance. +func findStringMatch(req string, mockStrings []string) int { + // Trim request to 2048 characters for performance + if len(req) > 2048 { + req = req[:2048] + } + + minDist := int(^uint(0) >> 1) + bestMatch := -1 + for idx, mock := range mockStrings { + if !util.IsASCII(mock) { + continue + } + // Trim mock string to 2048 characters for performance + trimmedMock := mock + if len(mock) > 2048 { + trimmedMock = mock[:2048] + } + + dist := levenshtein.ComputeDistance(req, trimmedMock) + if dist == 0 { + return 0 + } + if dist < minDist { + minDist = dist + bestMatch = idx + } + } + return bestMatch +} + +func findBinaryMatch(mocks []*models.Mock, reqBuff []byte) int { + // Trim request buffer to 2048 bytes for performance + if len(reqBuff) > 2048 { + reqBuff = reqBuff[:2048] + } + + mxSim := -1.0 + mxIdx := -1 + for idx, mock := range mocks { + encoded := []byte(mock.Spec.GRPCReq.Body.DecodedData) + // Trim mock data to 2048 bytes for performance + if len(encoded) > 2048 { + encoded = encoded[:2048] + } + + k := util.AdaptiveK(len(reqBuff), 3, 8, 5) + shingles1 := util.CreateShingles(encoded, k) + shingles2 := util.CreateShingles(reqBuff, k) + similarity := util.JaccardSimilarity(shingles1, shingles2) + + if mxSim < similarity { + mxSim = similarity + mxIdx = idx + } + } + return mxIdx +} + +func fuzzyMatch(tcsMocks []*models.Mock, reqBuff string) (bool, *models.Mock) { + // Trim request buffer to 2048 characters for performance + trimmedReqBuff := reqBuff + if len(reqBuff) > 2048 { + trimmedReqBuff = reqBuff[:2048] + } + + mockStrings := make([]string, len(tcsMocks)) + for i := range tcsMocks { + mockStrings[i] = tcsMocks[i].Spec.GRPCReq.Body.DecodedData + } + + if util.IsASCII(trimmedReqBuff) { + idx := findStringMatch(trimmedReqBuff, mockStrings) + if idx != -1 { + return true, tcsMocks[idx] + } + } + + idx := findBinaryMatch(tcsMocks, []byte(trimmedReqBuff)) + if idx != -1 { + return true, tcsMocks[idx] + } + return false, nil +} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/mock.go b/keploy/pkg/core/proxy/integrations/grpcV2/mock.go new file mode 100644 index 0000000..205a69b --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpcV2/mock.go @@ -0,0 +1,213 @@ +//go:build linux + +package grpcV2 + +import ( + "context" + "io" + "math" + "net" + "strconv" + "strings" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// mockOutgoing starts a gRPC server to mock responses for an incoming connection. +func mockOutgoing(ctx context.Context, logger *zap.Logger, clientConn net.Conn, mockDb integrations.MockMemDb) error { + // Always close the socket when we return. + defer func() { + if err := clientConn.Close(); err != nil && + !strings.Contains(err.Error(), "use of closed network connection") { + logger.Error("failed to close client connection in mock mode", zap.Error(err)) + } + }() + + mockServer := &grpcMockServer{ + logger: logger, + mockDb: mockDb, + } + + // Create a gRPC server that uses our raw codec and unknown service handler. + srv := grpc.NewServer( + grpc.UnknownServiceHandler(mockServer.handler), + grpc.ForceServerCodec(new(rawCodec)), + ) + + // Use a single-connection listener to serve the mock on the given connection. + lis := newSingleConnListener(clientConn) + logger.Info("starting mock gRPC server") + // Run Serve in its own goroutine so we can stop it when the context is done. + srvErr := make(chan error, 1) + go func() { srvErr <- srv.Serve(lis) }() + + select { + case <-ctx.Done(): + // Parent context cancelled (Ctrl-C, timeout, etc.). + go srv.GracefulStop() + <-srvErr // wait for Serve to return + return ctx.Err() + + case err := <-srvErr: + // Serve returned on its own (connection closed, reset, etc.). + switch { + case err == nil, + err == io.EOF, + strings.Contains(err.Error(), "use of closed network connection"): + logger.Debug("client connection closed (EOF)") + return nil + case strings.Contains(err.Error(), "connection reset by peer"): + logger.Warn("client connection was reset by peer") + return nil + default: + logger.Error("mock gRPC server failed", zap.Error(err)) + return err + } + } +} + +// grpcMockServer implements the gRPC unknown service handler to mock responses. +type grpcMockServer struct { + logger *zap.Logger + mockDb integrations.MockMemDb +} + +func (s *grpcMockServer) handler(_ interface{}, stream grpc.ServerStream) error { + // 1. Extract request details + fullMethod, ok := grpc.MethodFromServerStream(stream) + if !ok { + s.logger.Error("failed to get method from stream") + return status.Errorf(codes.Internal, "failed to get method from stream") + } + + md, ok := metadata.FromIncomingContext(stream.Context()) + if !ok { + s.logger.Warn("failed to get metadata from context") + } + + s.logger.Debug("received gRPC request to mock", zap.String("method", fullMethod), zap.Any("metadata", md)) + + // Read the request body. + var requestBody []byte + reqMsg := new(rawMessage) + if err := stream.RecvMsg(reqMsg); err != nil && err != io.EOF { + s.logger.Error("failed to receive request message from stream", zap.Error(err)) + return status.Errorf(codes.Internal, "failed to receive request message: %v", err) + } + requestBody = reqMsg.data + s.logger.Debug("fully received request body", zap.Int("size", len(requestBody))) + + grpcReq := &models.GrpcReq{ + Headers: s.grpcMetadataToHeaders(md, fullMethod), + Body: createLengthPrefixedMessage(requestBody), + } + + // 2. Find a matching mock + s.logger.Debug("finding mock for gRPC request", zap.Any("request", grpcReq)) + mock, err := FilterMocksBasedOnGrpcRequest(stream.Context(), s.logger, *grpcReq, s.mockDb) + if err != nil { + s.logger.Error("failed to find mock", zap.Error(err)) + return status.Errorf(codes.Internal, "failed to find mock: %v", err) + } + if mock == nil { + s.logger.Error("no matching gRPC mock found", zap.String("method", fullMethod)) + return status.Errorf(codes.NotFound, "no matching keploy mock found for %s", fullMethod) + } + + s.logger.Debug("found matching mock", zap.String("mock.name", mock.Name), zap.String("mock.kind", string(mock.Kind))) + + // 3. Send the mocked response + grpcResp := mock.Spec.GRPCResp + + // --- Determine final gRPC status -------------------------------------- + scStr := grpcResp.Trailers.OrdinaryHeaders["grpc-status"] + scInt, err := strconv.Atoi(scStr) // bad/missing ⇒ 0 (codes.OK) + var finalCode codes.Code + if err != nil || scInt < 0 || scInt > math.MaxUint32 { + s.logger.Warn("invalid grpc-status value, defaulting to codes.OK", zap.String("grpc-status", scStr)) + finalCode = codes.OK + } else { + finalCode = codes.Code(uint32(scInt)) // 0 ⇒ OK + } + finalMsg := grpcResp.Trailers.OrdinaryHeaders["grpc-message"] + // Send headers + respMd := s.headersToGrpcMetadata(grpcResp.Headers) + if err := stream.SendHeader(respMd); err != nil { + s.logger.Error("failed to send response headers", zap.Error(err)) + return status.Errorf(codes.Internal, "failed to send headers: %v", err) + } + + // Send body **only when grpc-status == OK** + if finalCode == codes.OK { + respBody, err := createPayloadFromLengthPrefixedMessage(grpcResp.Body) + if err != nil { + s.logger.Error("failed to create payload from length-prefixed message", zap.Error(err)) + return status.Errorf(codes.Internal, "failed to create response payload: %v", err) + } + if err := stream.SendMsg(&rawMessage{data: respBody}); err != nil { + s.logger.Error("failed to send response message", zap.Error(err)) + return status.Errorf(codes.Internal, "failed to send response message: %v", err) + } + s.logger.Debug("sent mocked response body", zap.Int("size", len(respBody))) + } + + // For non-OK results return a status.Error – this makes the runtime + // write the required grpc-status / grpc-message trailers for us. + if finalCode != codes.OK { + return status.Error(finalCode, finalMsg) + } + + // Success path – attach any extra trailers and finish. + trailerMd := s.headersToGrpcMetadata(grpcResp.Trailers) + stream.SetTrailer(trailerMd) + s.logger.Debug("sent mocked response trailers", zap.Any("trailers", trailerMd)) + return nil +} + +// grpcMetadataToHeaders converts gRPC metadata to Keploy's header format. +func (s *grpcMockServer) grpcMetadataToHeaders(md metadata.MD, fullMethod string) models.GrpcHeaders { + hdr := models.GrpcHeaders{ + PseudoHeaders: make(map[string]string), + OrdinaryHeaders: make(map[string]string), + } + for k, v := range md { + val := strings.Join(v, ", ") + if strings.HasPrefix(k, ":") { + hdr.PseudoHeaders[k] = val + } else { + hdr.OrdinaryHeaders[k] = val + } + } + // Stabilise the header set so it matches what was recorded + hdr.OrdinaryHeaders["te"] = "trailers" + + // The grpc server framework consumes pseudo-headers, so we must add them back. + if method, ok := hdr.PseudoHeaders[":method"]; !ok || method == "" { + hdr.PseudoHeaders[":method"] = "POST" + } + if scheme, ok := hdr.PseudoHeaders[":scheme"]; !ok || scheme == "" { + hdr.PseudoHeaders[":scheme"] = "http" + } + if path, ok := hdr.PseudoHeaders[":path"]; !ok || path == "" { + hdr.PseudoHeaders[":path"] = fullMethod + } + return hdr +} + +// headersToGrpcMetadata converts Keploy's header format to gRPC metadata. +func (s *grpcMockServer) headersToGrpcMetadata(headers models.GrpcHeaders) metadata.MD { + md := metadata.New(nil) + for k, v := range headers.PseudoHeaders { + md.Set(k, v) + } + for k, v := range headers.OrdinaryHeaders { + md.Set(k, v) + } + return md +} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/record.go b/keploy/pkg/core/proxy/integrations/grpcV2/record.go new file mode 100644 index 0000000..aa6a1ba --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpcV2/record.go @@ -0,0 +1,428 @@ +//go:build linux + +package grpcV2 + +import ( + "bytes" + "context" + "encoding/base64" + "errors" + "fmt" + "io" + "net" + "strings" + "sync" + "time" + + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials/insecure" + _ "google.golang.org/grpc/encoding/gzip" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// recordOutgoing starts a gRPC proxy to record a session. +func recordOutgoing(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, mocks chan<- *models.Mock) error { + // Ensure connections are closed on exit + cid, ok := ctx.Value(models.ClientConnectionIDKey).(string) + if !ok { + return status.Errorf(codes.Internal, "missing ClientConnectionID in context") + } + + proxy := &grpcRecordingProxy{ + logger: logger, + destConn: destConn, + mocks: mocks, + connID: cid, + } + + defer func() { + if err := clientConn.Close(); err != nil && + !strings.Contains(err.Error(), "use of closed network connection") { + logger.Error("failed to close client connection in record mode", zap.Error(err)) + } + if err := destConn.Close(); err != nil && + !strings.Contains(err.Error(), "use of closed network connection") { + logger.Error("failed to close destination connection in record mode", zap.Error(err)) + } + // Close the grpc.ClientConn if it was created. + if proxy.cc != nil { + err := proxy.cc.Close() + if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + logger.Error("failed to close gRPC client connection in record mode", zap.Error(err)) + } + } + }() + + // Create a gRPC server to handle the client's request + srv := grpc.NewServer( + grpc.UnknownServiceHandler(proxy.handler), + grpc.ForceServerCodec(new(rawCodec)), + ) + + lis := newSingleConnListener(clientConn) + logger.Info("starting recording gRPC proxy server") + + srvErr := make(chan error, 1) + go func() { srvErr <- srv.Serve(lis) }() + + select { + case <-ctx.Done(): + // Gracefully shut down once the recorder context is cancelled. + go srv.GracefulStop() + logger.Debug("waiting for gRPC recording proxy server to stop") + err := <-srvErr + logger.Info("gRPC recording proxy server stopped gracefully", zap.Error(err)) + return ctx.Err() + case err := <-srvErr: + switch { + case err == nil, + err == io.EOF, + strings.Contains(err.Error(), "connection reset by peer"), + strings.Contains(err.Error(), "use of closed network connection"): + logger.Info("gRPC recording proxy stopped gracefully") + return nil + default: + logger.Error("gRPC recording proxy server failed", zap.Error(err)) + return err + } + } + +} + +// grpcRecordingProxy proxies gRPC calls, recording the request and response. +type grpcRecordingProxy struct { + logger *zap.Logger + destConn net.Conn + mocks chan<- *models.Mock + connID string + ccMu sync.Mutex // protects cc + cc *grpc.ClientConn // reused for all streams on this TCP conn +} + +// getClientConn returns the (lazily-constructed) grpc.ClientConn that +// multiplexes over p.destConn. +func (p *grpcRecordingProxy) getClientConn(ctx context.Context) (*grpc.ClientConn, error) { + p.ccMu.Lock() + defer p.ccMu.Unlock() + + if p.cc != nil { + s := p.cc.GetState() + p.logger.Debug("checking gRPC client connection state", + zap.String("state", s.String()), + zap.String("connID", p.connID)) + if s != connectivity.Ready && s != connectivity.Connecting { + _ = p.cc.Close() // ignore error + // p.cc = nil // force re-dial + return nil, io.EOF + } + } + + if p.cc != nil { + return p.cc, nil + } + + p.logger.Debug("creating new gRPC client connection because p.cc is nil", zap.Any("p.cc", p.cc)) + + dialer := func(context.Context, string) (net.Conn, error) { return p.destConn, nil } + + target := p.destConn.RemoteAddr().String() // or explicit host:port string + cc, err := grpc.DialContext( + ctx, + target, + grpc.WithContextDialer(dialer), + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultCallOptions(grpc.ForceCodec(passthroughCodec{})), + ) + if err != nil { + return nil, err + } + p.cc = cc + return cc, nil +} + +// handler is the core of the proxy. It receives a call, forwards it, and records the interaction. +func (p *grpcRecordingProxy) handler(_ interface{}, clientStream grpc.ServerStream) error { + p.logger.Debug("received gRPC call") + startTime := time.Now() + clientCtx := clientStream.Context() + fullMethod, _ := grpc.MethodFromServerStream(clientStream) + connID := p.connID + if connID == "" { + connID = "0" // graceful fallback + } + + md, _ := metadata.FromIncomingContext(clientCtx) + + p.logger.Info("proxying gRPC request", zap.String("method", fullMethod), zap.Any("metadata", md)) + + // 1. Obtain (or create once) the grpc.ClientConn that sits on destConn + destClientConn, err := p.getClientConn(clientCtx) + if err != nil { + if errors.Is(err, io.EOF) { + p.logger.Warn("gRPC client connection is closed, cannot forward request", zap.Error(err)) + return io.EOF + } + p.logger.Error("failed to dial destination server", zap.Error(err)) + return status.Errorf(codes.Internal, "failed to connect to destination: %v", err) + } + + if destClientConn == nil { + p.logger.Error("destination client connection is nil") + return status.Errorf(codes.Internal, "destination client connection is nil") + } + + // 2. Forward the call to the destination + downstreamCtx, cancelDownstream := context.WithCancel(clientCtx) + defer cancelDownstream() + + // ── Clean metadata: gRPC forbids user-supplied pseudo headers ("*:"). + cleanMD := metadata.New(nil) + for k, v := range md { + fmt.Printf("[MD] key: %s, value: %s\n", k, v) + if strings.HasPrefix(k, ":") { + continue // strip pseudo-headers + } + cleanMD[k] = v + } + + downstreamCtx = metadata.NewOutgoingContext(downstreamCtx, cleanMD) + destStream, err := destClientConn.NewStream(downstreamCtx, &grpc.StreamDesc{ + StreamName: fullMethod, + ServerStreams: true, + ClientStreams: true, + }, fullMethod) + if err != nil { + if downstreamCtx.Err() != nil { + p.logger.Warn("context cancelled before creating stream to destination", zap.Error(downstreamCtx.Err())) + return status.Errorf(codes.Canceled, "context cancelled before creating stream to destination: %v", downstreamCtx.Err()) + } + p.logger.Error("failed to create new stream to destination", zap.Error(err)) + return status.Errorf(codes.Internal, "failed to create stream to destination: %v", err) + } + + // 3. Goroutines to proxy data in both directions and capture it + var wg sync.WaitGroup + var reqErr, respErr error + var reqBuf, respBuf bytes.Buffer + + wg.Add(1) + go func() { + defer wg.Done() + for { + reqMsg := new(rawMessage) + reqErr = clientStream.RecvMsg(reqMsg) + if reqErr == io.EOF { + err := destStream.CloseSend() + p.logger.Debug("client stream closed", zap.Error(err)) + if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + p.logger.Error("failed to close send stream to destination", zap.Error(err)) + cancelDownstream() + } + return + } + if reqErr != nil { + p.logger.Error("failed to receive message from client", zap.Error(reqErr)) + cancelDownstream() + return + } + p.logger.Debug("received message from client", zap.Int("size", len(reqMsg.data)), + zap.String("Msg", reqMsg.String())) + + // append keploy at the end of message + // reqBuf.Write([]byte("keploy")) + + reqBuf.Write(reqMsg.data) + if err := destStream.SendMsg(reqMsg); err != nil { + p.logger.Error("failed to send message to destination", zap.Error(err)) + reqErr = err + return + } + } + }() + + respHeader := metadata.MD{} + wg.Add(1) + go func() { + defer wg.Done() + header, err := destStream.Header() + if err != nil { + p.logger.Warn("failed to get headers from destination stream", zap.Error(err)) + respErr = err + return + } + + respHeader = header + if err := clientStream.SendHeader(header); err != nil { + p.logger.Error("failed to send headers to client", zap.Error(err)) + respErr = err + return + } + for { + respMsg := new(rawMessage) + p.logger.Debug("received message from server", zap.Int("size", len(respMsg.data)), + zap.String("Msg", respMsg.String())) + + respErr = destStream.RecvMsg(respMsg) + if respErr != nil { + p.logger.Debug("received Error from destination", zap.Error(respErr)) + } + switch respErr { + case nil: + // normal message – relay it + case io.EOF: + p.logger.Debug("destination stream closed due to EOF") + return // clean finish + default: + // gRPC status error (business failure) is *expected*; just stop reading. + if _, ok := status.FromError(respErr); ok { + return + } + // real transport problem – still log it. + p.logger.Error("failed to receive message from destination", + zap.Error(respErr)) + return + } + respBuf.Write(respMsg.data) + if err := clientStream.SendMsg(respMsg); err != nil { + p.logger.Error("failed to send message to client", zap.Error(err)) + respErr = err + return + } + } + }() + + wg.Wait() + + // 4. Finalize and record + endTime := time.Now() + destTrailers := destStream.Trailer() + clientStream.SetTrailer(destTrailers) + // ──────────────────────────────────────────────────────────────── + // Construct & enqueue the mock **before** we possibly return. + // ──────────────────────────────────────────────────────────────── + grpcReq := &models.GrpcReq{ + Body: createLengthPrefixedMessage(reqBuf.Bytes()), + Headers: p.grpcMetadataToHeaders(md, fullMethod, false), + } + + p.logger.Debug("headers and trailer of grpc response", + zap.Any("headers", respHeader), + zap.Any("trailers", destTrailers)) + + body64 := base64.StdEncoding.EncodeToString(respBuf.Bytes()) + + p.logger.Debug("Grpc Response body", zap.Int("body size", len(respBuf.Bytes())), zap.Any("body", respBuf.String()), zap.Any("body64", body64)) + // respHeader, _ := destStream.Header() + grpcResp := &models.GrpcResp{ + Body: createLengthPrefixedMessage(respBuf.Bytes()), + Headers: p.grpcMetadataToHeaders(respHeader, "", true), + Trailers: p.grpcMetadataToHeaders(destTrailers, "", true), + } + + //------------------------------------------------------------------ + // If the server terminated the stream with a status error, make + // sure we record **that** code & message instead of default “0”. + //------------------------------------------------------------------ + if st, ok := status.FromError(respErr); ok && respErr != nil { + grpcResp.Trailers.OrdinaryHeaders["grpc-status"] = fmt.Sprintf("%d", st.Code()) + grpcResp.Trailers.OrdinaryHeaders["grpc-message"] = st.Message() + // Per gRPC spec error responses have no body – keep what we already + // captured (likely empty) but that’s harmless. + } else { + // Ensure mandatory keys are present for the happy-path case. + if _, ok := grpcResp.Trailers.OrdinaryHeaders["grpc-status"]; !ok { + grpcResp.Trailers.OrdinaryHeaders["grpc-status"] = "0" + } + if _, ok := grpcResp.Trailers.OrdinaryHeaders["grpc-message"]; !ok { + grpcResp.Trailers.OrdinaryHeaders["grpc-message"] = "" + } + } + + p.mocks <- &models.Mock{ + Version: models.GetVersion(), + Name: "mocks", + Kind: models.GRPC_EXPORT, + Spec: models.MockSpec{ + Metadata: map[string]string{"connID": connID}, + GRPCReq: grpcReq, + GRPCResp: grpcResp, + ReqTimestampMock: startTime, + ResTimestampMock: endTime, + }, + } + p.logger.Info("successfully recorded gRPC interaction", zap.String("method", fullMethod)) + + // ──────────────────────────────────────────────────────────────── + // Now decide what to return to the client. + // ──────────────────────────────────────────────────────────────── + + // Treat normal end-of-stream (EOF) and context cancellation as success. + benign := func(err error) bool { + return err == nil || err == io.EOF || errors.Is(err, context.Canceled) + } + if !benign(reqErr) { + return status.Errorf(codes.Internal, "error during request forwarding: %v", reqErr) + } + + // If the server ended the call with a (business) gRPC status error, + // propagate that **after** recording. + if s, ok := status.FromError(respErr); ok && respErr != nil { + p.logger.Debug("received gRPC status error from destination", + zap.String("code", s.Code().String()), + zap.String("message", s.Message())) + + return s.Err() + } + + // Any other non-benign transport error? + if !benign(respErr) { + return status.Errorf(codes.Internal, + "error during response forwarding: %v", respErr) + } + + return nil +} + +// grpcMetadataToHeaders converts gRPC metadata to Keploy's header format. +func (p *grpcRecordingProxy) grpcMetadataToHeaders(md metadata.MD, fullMethod string, isResponse bool) models.GrpcHeaders { + hdr := models.GrpcHeaders{ + PseudoHeaders: make(map[string]string), + OrdinaryHeaders: make(map[string]string), + } + + for k, v := range md { + val := strings.Join(v, ", ") + if strings.HasPrefix(k, ":") { + hdr.PseudoHeaders[k] = val + } else { + hdr.OrdinaryHeaders[k] = val + } + } + + if !isResponse { + if _, ok := hdr.PseudoHeaders[":method"]; !ok { + hdr.PseudoHeaders[":method"] = "POST" + } + if _, ok := hdr.PseudoHeaders[":scheme"]; !ok { + hdr.PseudoHeaders[":scheme"] = "http" + } + if _, ok := hdr.PseudoHeaders[":path"]; !ok { + hdr.PseudoHeaders[":path"] = fullMethod + } + hdr.OrdinaryHeaders["te"] = "trailers" // new – stable field + } else { + // if _, ok := hdr.PseudoHeaders[":status"]; !ok { + // hdr.PseudoHeaders[":status"] = "200" + // } + // if ct, ok := hdr.OrdinaryHeaders["content-type"]; ok && + // strings.HasPrefix(ct, "application/grpc") { + // hdr.OrdinaryHeaders["content-type"] = "application/grpc" + // } + } + return hdr +} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/replayconn.go b/keploy/pkg/core/proxy/integrations/grpcV2/replayconn.go new file mode 100644 index 0000000..f0c77f1 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/grpcV2/replayconn.go @@ -0,0 +1,28 @@ +//go:build linux + +package grpcV2 + +import ( + "bytes" + "net" +) + +// replayConn first serves the bytes in buf, then falls through to Conn. +type replayConn struct { + net.Conn + buf *bytes.Reader +} + +func newReplayConn(initial []byte, c net.Conn) net.Conn { + return &replayConn{ + Conn: c, + buf: bytes.NewReader(initial), + } +} + +func (r *replayConn) Read(p []byte) (int, error) { + if r.buf.Len() > 0 { + return r.buf.Read(p) + } + return r.Conn.Read(p) +} diff --git a/keploy/pkg/core/proxy/integrations/http/README.md b/keploy/pkg/core/proxy/integrations/http/README.md new file mode 100755 index 0000000..1115002 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/http/README.md @@ -0,0 +1,6 @@ +# Http Package Documentation + +The `http` package encompasses the parser and mapping logic required +to read HTTP text messages and capture or stub the outputs. Utilized +by the `hooks` package, it aids in redirecting outgoing calls for the +purpose of recording or stubbing the outputs. diff --git a/keploy/pkg/core/proxy/integrations/http/chunk.go b/keploy/pkg/core/proxy/integrations/http/chunk.go new file mode 100644 index 0000000..b670d0c --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/http/chunk.go @@ -0,0 +1,348 @@ +//go:build linux + +// Package http provides functionality for handling HTTP outgoing calls. +package http + +import ( + "context" + "fmt" + "io" + "net" + "strconv" + "strings" + "time" + + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func (h *HTTP) HandleChunkedRequests(ctx context.Context, finalReq *[]byte, clientConn, destConn net.Conn) error { + + if hasCompleteHeaders(*finalReq) { + h.Logger.Debug("this request has complete headers in the first chunk itself.") + } + + for !hasCompleteHeaders(*finalReq) { + h.Logger.Debug("couldn't get complete headers in first chunk so reading more chunks") + reqHeader, err := pUtil.ReadBytes(ctx, h.Logger, clientConn) + if err != nil { + utils.LogError(h.Logger, nil, "failed to read the request message from the client") + return err + } + // destConn is nil in case of test mode + if destConn != nil { + _, err = destConn.Write(reqHeader) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, nil, "failed to write request message to the destination server") + return err + } + } + + *finalReq = append(*finalReq, reqHeader...) + } + + lines := strings.Split(string(*finalReq), "\n") + var contentLengthHeader string + var transferEncodingHeader string + for _, line := range lines { + if strings.HasPrefix(line, "Content-Length:") { + contentLengthHeader = strings.TrimSpace(strings.TrimPrefix(line, "Content-Length:")) + break + } else if strings.HasPrefix(line, "Transfer-Encoding:") { + transferEncodingHeader = strings.TrimSpace(strings.TrimPrefix(line, "Transfer-Encoding:")) + break + } + } + + //Handle chunked requests + if contentLengthHeader != "" { + contentLength, err := strconv.Atoi(contentLengthHeader) + if err != nil { + utils.LogError(h.Logger, err, "failed to get the content-length header") + return fmt.Errorf("failed to handle chunked request") + } + //Get the length of the body in the request. + bodyLength := len(*finalReq) - strings.Index(string(*finalReq), "\r\n\r\n") - 4 + contentLength -= bodyLength + if contentLength > 0 { + err := h.contentLengthRequest(ctx, finalReq, clientConn, destConn, contentLength) + if err != nil { + return err + } + } + } else if transferEncodingHeader != "" { + // check if the initial request is the complete request. + if strings.HasSuffix(string(*finalReq), "0\r\n\r\n") { + return nil + } + if transferEncodingHeader == "chunked" { + err := h.chunkedRequest(ctx, finalReq, clientConn, destConn, transferEncodingHeader) + if err != nil { + return err + } + } + } + return nil +} + +// Handled chunked requests when content-length is given. +func (h *HTTP) contentLengthRequest(ctx context.Context, finalReq *[]byte, clientConn, destConn net.Conn, contentLength int) error { + for contentLength > 0 { + err := clientConn.SetReadDeadline(time.Now().Add(5 * time.Second)) + if err != nil { + utils.LogError(h.Logger, err, "failed to set the read deadline for the client conn") + return err + } + requestChunked, err := pUtil.ReadBytes(ctx, h.Logger, clientConn) + if err != nil { + if err == io.EOF { + utils.LogError(h.Logger, nil, "conn closed by the user client") + return err + } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + h.Logger.Info("Stopped getting data from the conn", zap.Error(err)) + break + } + utils.LogError(h.Logger, nil, "failed to read the response message from the destination server") + return err + } + h.Logger.Debug("This is a chunk of request[content-length]: " + string(requestChunked)) + *finalReq = append(*finalReq, requestChunked...) + contentLength -= len(requestChunked) + + // destConn is nil in case of test mode. + if destConn != nil { + _, err = destConn.Write(requestChunked) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, nil, "failed to write request message to the destination server") + return err + } + } + } + return nil +} + +// Handled chunked requests when transfer-encoding is given. +func (h *HTTP) chunkedRequest(ctx context.Context, finalReq *[]byte, clientConn, destConn net.Conn, _ string) error { + + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + //TODO: we have to implement a way to read the buffer chunk wise according to the chunk size (chunk size comes in hexadecimal) + // because it can happen that some chunks come after 5 seconds. + err := clientConn.SetReadDeadline(time.Now().Add(5 * time.Second)) + if err != nil { + utils.LogError(h.Logger, err, "failed to set the read deadline for the client conn") + return err + } + requestChunked, err := pUtil.ReadBytes(ctx, h.Logger, clientConn) + if err != nil { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + break + } + utils.LogError(h.Logger, nil, "failed to read the response message from the destination server") + return err + } + + *finalReq = append(*finalReq, requestChunked...) + // destConn is nil in case of test mode. + if destConn != nil { + _, err = destConn.Write(requestChunked) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, nil, "failed to write request message to the destination server") + return err + } + } + + //check if the initial request is completed + if strings.HasSuffix(string(requestChunked), "0\r\n\r\n") { + return nil + } + } + } +} + +func (h *HTTP) handleChunkedResponses(ctx context.Context, finalResp *[]byte, clientConn, destConn net.Conn, resp []byte) error { + + if hasCompleteHeaders(*finalResp) { + h.Logger.Debug("this response has complete headers in the first chunk itself.") + } + + for !hasCompleteHeaders(resp) { + h.Logger.Debug("couldn't get complete headers in first chunk so reading more chunks") + respHeader, err := pUtil.ReadBytes(ctx, h.Logger, destConn) + if err != nil { + if err == io.EOF { + h.Logger.Debug("received EOF from the server") + // if there is any buffer left before EOF, we must send it to the client and save this as mock + if len(respHeader) != 0 { + // write the response message to the user client + _, err = clientConn.Write(resp) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, nil, "failed to write response message to the user client") + return err + } + *finalResp = append(*finalResp, respHeader...) + } + return err + } + utils.LogError(h.Logger, nil, "failed to read the response message from the destination server") + return err + } + // write the response message to the user client + _, err = clientConn.Write(respHeader) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, nil, "failed to write response message to the user client") + return err + } + + *finalResp = append(*finalResp, respHeader...) + resp = append(resp, respHeader...) + } + + //Getting the content-length or the transfer-encoding header + var contentLengthHeader, transferEncodingHeader string + lines := strings.Split(string(resp), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "Content-Length:") { + contentLengthHeader = strings.TrimSpace(strings.TrimPrefix(line, "Content-Length:")) + break + } else if strings.HasPrefix(line, "Transfer-Encoding:") { + transferEncodingHeader = strings.TrimSpace(strings.TrimPrefix(line, "Transfer-Encoding:")) + break + } + } + //Handle chunked responses + if contentLengthHeader != "" { + contentLength, err := strconv.Atoi(contentLengthHeader) + if err != nil { + utils.LogError(h.Logger, err, "failed to get the content-length header") + return fmt.Errorf("failed to handle chunked response") + } + bodyLength := len(resp) - strings.Index(string(resp), "\r\n\r\n") - 4 + contentLength -= bodyLength + if contentLength > 0 { + err := h.contentLengthResponse(ctx, finalResp, clientConn, destConn, contentLength) + if err != nil { + return err + } + } + } else if transferEncodingHeader != "" { + //check if the initial response is the complete response. + if strings.HasSuffix(string(*finalResp), "0\r\n\r\n") { + return nil + } + if transferEncodingHeader == "chunked" { + err := h.chunkedResponse(ctx, finalResp, clientConn, destConn) + if err != nil { + return err + } + } + } + return nil +} + +// Handled chunked responses when transfer-encoding is given. +func (h *HTTP) chunkedResponse(ctx context.Context, finalResp *[]byte, clientConn, destConn net.Conn) error { + isEOF := false + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + resp, err := pUtil.ReadBytes(ctx, h.Logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(h.Logger, err, "failed to read the response message from the destination server") + return err + } + isEOF = true + h.Logger.Debug("received EOF", zap.Error(err)) + if len(resp) == 0 { + h.Logger.Debug("exiting loop as response is complete") + break + } + } + + *finalResp = append(*finalResp, resp...) + // write the response message to the user client + _, err = clientConn.Write(resp) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, nil, "failed to write response message to the user client") + return err + } + + //In some cases need to write the response to the client + // where there is some response before getting the true EOF + if isEOF { + break + } + + if string(resp) == "0\r\n\r\n" { + return nil + } + } + } +} + +// Handled chunked responses when content-length is given. +func (h *HTTP) contentLengthResponse(ctx context.Context, finalResp *[]byte, clientConn, destConn net.Conn, contentLength int) error { + isEOF := false + for contentLength > 0 { + resp, err := pUtil.ReadBytes(ctx, h.Logger, destConn) + if err != nil { + if err == io.EOF { + isEOF = true + h.Logger.Debug("received EOF, conn closed by the destination server") + if len(resp) == 0 { + break + } + } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + h.Logger.Info("Stopped getting data from the conn", zap.Error(err)) + break + } else { + utils.LogError(h.Logger, nil, "failed to read the response message from the destination server") + return err + } + } + + h.Logger.Debug("This is a chunk of response[content-length]: " + string(resp)) + *finalResp = append(*finalResp, resp...) + contentLength -= len(resp) + + // write the response message to the user client + _, err = clientConn.Write(resp) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, nil, "failed to write response message to the user client") + return err + } + + if isEOF { + break + } + } + return nil +} diff --git a/keploy/pkg/core/proxy/integrations/http/decode.go b/keploy/pkg/core/proxy/integrations/http/decode.go new file mode 100644 index 0000000..2d91790 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/http/decode.go @@ -0,0 +1,206 @@ +//go:build linux + +// Package http provides functionality for handling HTTP outgoing calls. +package http + +import ( + "bufio" + "bytes" + "context" + "errors" + "fmt" + "io" + "net" + "net/http" + "strconv" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// Decodes the mocks in test mode so that they can be sent to the user application. +func (h *HTTP) decodeHTTP(ctx context.Context, reqBuf []byte, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { + errCh := make(chan error, 1) + go func(errCh chan error, reqBuf []byte, opts models.OutgoingOptions) { + defer pUtil.Recover(h.Logger, clientConn, nil) + defer close(errCh) + for { + //Check if the expected header is present + if bytes.Contains(reqBuf, []byte("Expect: 100-continue")) { + h.Logger.Debug("The expect header is present in the request buffer and writing the 100 continue response to the client") + //Send the 100 continue response + _, err := clientConn.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n")) + if err != nil { + if ctx.Err() != nil { + return + } + utils.LogError(h.Logger, err, "failed to write the 100 continue response to the user application") + errCh <- err + return + } + h.Logger.Debug("The 100 continue response has been sent to the user application") + //Read the request buffer again + newRequest, err := pUtil.ReadBytes(ctx, h.Logger, clientConn) + if err != nil { + utils.LogError(h.Logger, err, "failed to read the request buffer from the user application") + errCh <- err + return + } + //Append the new request buffer to the old request buffer + reqBuf = append(reqBuf, newRequest...) + } + + h.Logger.Debug("handling the chunked requests to read the complete request") + err := h.HandleChunkedRequests(ctx, &reqBuf, clientConn, nil) + if err != nil { + utils.LogError(h.Logger, err, "failed to handle chunked requests") + errCh <- err + return + } + + h.Logger.Debug(fmt.Sprintf("This is the complete request:\n%v", string(reqBuf))) + + //Parse the request buffer + request, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(reqBuf))) + if err != nil { + utils.LogError(h.Logger, err, "failed to parse the http request message") + errCh <- err + return + } + // Set the host header explicitely because the `http.ReadRequest`` trim the host header + // func ReadRequest(b *bufio.Reader) (*Request, error) { + // req, err := readRequest(b) + // if err != nil { + // return nil, err + // } + + // delete(req.Header, "Host") + // return req, err + // } + request.Header.Set("Host", request.Host) + + reqBody, err := io.ReadAll(request.Body) + if err != nil { + utils.LogError(h.Logger, err, "failed to read from request body", zap.Any("metadata", utils.GetReqMeta(request))) + errCh <- err + return + } + + input := &req{ + method: request.Method, + url: request.URL, + header: request.Header, + body: reqBody, + raw: reqBuf, + } + + if input.header.Get("Content-Encoding") != "" { + input.body, err = pkg.Decompress(h.Logger, input.header.Get("Content-Encoding"), input.body) + if err != nil { + utils.LogError(h.Logger, err, "failed to decode the http request body", zap.Any("metadata", utils.GetReqMeta(request))) + errCh <- err + return + } + } + + ok, stub, err := h.match(ctx, input, mockDb) // calling match function to match mocks + if err != nil { + utils.LogError(h.Logger, err, "error while matching http mocks", zap.Any("metadata", utils.GetReqMeta(request))) + errCh <- err + return + } + h.Logger.Debug("after matching the http request", zap.Any("isMatched", ok), zap.Any("stub", stub), zap.Error(err)) + + if !ok { + if !utils.IsPassThrough(h.Logger, request, dstCfg.Port, opts) { + utils.LogError(h.Logger, nil, "Didn't match any preExisting http mock", zap.Any("metadata", utils.GetReqMeta(request))) + } + if opts.FallBackOnMiss { + _, err = pUtil.PassThrough(ctx, h.Logger, clientConn, dstCfg, [][]byte{reqBuf}) + if err != nil { + utils.LogError(h.Logger, err, "failed to passThrough http request", zap.Any("metadata", utils.GetReqMeta(request))) + errCh <- err + return + } + } + errCh <- nil + return + } + + if stub == nil { + utils.LogError(h.Logger, nil, "matched mock is nil", zap.Any("metadata", utils.GetReqMeta(request))) + errCh <- errors.New("matched mock is nil") + return + } + + statusLine := fmt.Sprintf("HTTP/%d.%d %d %s\r\n", stub.Spec.HTTPReq.ProtoMajor, stub.Spec.HTTPReq.ProtoMinor, stub.Spec.HTTPResp.StatusCode, http.StatusText(stub.Spec.HTTPResp.StatusCode)) + + body := stub.Spec.HTTPResp.Body + var respBody string + var responseString string + + // Fetching the response headers + header := pkg.ToHTTPHeader(stub.Spec.HTTPResp.Header) + + //Check if the content encoding is present in the header + if encoding, ok := header["Content-Encoding"]; ok && len(encoding) > 0 { + compressedBody, err := pkg.Compress(h.Logger, encoding[0], []byte(body)) + if err != nil { + utils.LogError(h.Logger, err, "failed to compress the response body", zap.Any("metadata", utils.GetReqMeta(request))) + errCh <- err + return + } + h.Logger.Debug("the length of the response body: " + strconv.Itoa(len(compressedBody))) + respBody = string(compressedBody) + } else { + respBody = body + } + + var headers string + for key, values := range header { + if key == "Content-Length" { + values = []string{strconv.Itoa(len(respBody))} + } + for _, value := range values { + headerLine := fmt.Sprintf("%s: %s\r\n", key, value) + headers += headerLine + } + } + responseString = statusLine + headers + "\r\n" + "" + respBody + + h.Logger.Debug(fmt.Sprintf("Mock Response sending back to client:\n%v", responseString)) + + _, err = clientConn.Write([]byte(responseString)) + if err != nil { + if ctx.Err() != nil { + return + } + utils.LogError(h.Logger, err, "failed to write the mock output to the user application", zap.Any("metadata", utils.GetReqMeta(request))) + errCh <- err + return + } + + reqBuf, err = pUtil.ReadBytes(ctx, h.Logger, clientConn) + if err != nil { + h.Logger.Debug("failed to read the request buffer from the client", zap.Error(err)) + h.Logger.Debug("This was the last response from the server:\n" + string(responseString)) + errCh <- nil + return + } + } + }(errCh, reqBuf, opts) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + return nil + } + return err + } +} diff --git a/keploy/pkg/core/proxy/integrations/http/encode.go b/keploy/pkg/core/proxy/integrations/http/encode.go new file mode 100644 index 0000000..3cbfe32 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/http/encode.go @@ -0,0 +1,262 @@ +//go:build linux + +package http + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "strings" + "time" + + "golang.org/x/sync/errgroup" + + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// encodeHTTP function parses the HTTP request and response text messages to capture outgoing network calls as mocks. +func (h *HTTP) encodeHTTP(ctx context.Context, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + + remoteAddr := destConn.RemoteAddr().(*net.TCPAddr) + destPort := uint(remoteAddr.Port) + + //Writing the request to the server. + _, err := destConn.Write(reqBuf) + if err != nil { + h.Logger.Error("failed to write request message to the destination server", zap.Error(err)) + return err + } + + if ctx.Err() != nil { + return ctx.Err() + } + + h.Logger.Debug("This is the initial request: " + string(reqBuf)) + var finalReq []byte + errCh := make(chan error, 1) + + finalReq = append(finalReq, reqBuf...) + + //get the error group from the context + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context") + } + + //for keeping conn alive + g.Go(func() error { + defer pUtil.Recover(h.Logger, clientConn, destConn) + defer close(errCh) + for { + //check if expect : 100-continue header is present + lines := strings.Split(string(finalReq), "\n") + var expectHeader string + for _, line := range lines { + if strings.HasPrefix(line, "Expect:") { + expectHeader = strings.TrimSpace(strings.TrimPrefix(line, "Expect:")) + break + } + } + if expectHeader == "100-continue" { + //Read if the response from the server is 100-continue + resp, err := pUtil.ReadBytes(ctx, h.Logger, destConn) + if err != nil { + utils.LogError(h.Logger, err, "failed to read the response message from the server after 100-continue request") + errCh <- err + return nil + } + + // write the response message to the client + _, err = clientConn.Write(resp) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, err, "failed to write response message to the user client") + errCh <- err + return nil + } + + h.Logger.Debug("This is the response from the server after the expect header" + string(resp)) + + if string(resp) != "HTTP/1.1 100 Continue\r\n\r\n" { + utils.LogError(h.Logger, nil, "failed to get the 100 continue response from the user client") + errCh <- err + return nil + } + //Reading the request buffer again + reqBuf, err = pUtil.ReadBytes(ctx, h.Logger, clientConn) + if err != nil { + utils.LogError(h.Logger, err, "failed to read the request buffer from the user client") + errCh <- err + return nil + } + // write the request message to the actual destination server + _, err = destConn.Write(reqBuf) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, err, "failed to write request message to the destination server") + errCh <- err + return nil + } + finalReq = append(finalReq, reqBuf...) + } + + // Capture the request timestamp + reqTimestampMock := time.Now() + + err := h.HandleChunkedRequests(ctx, &finalReq, clientConn, destConn) + if err != nil { + utils.LogError(h.Logger, err, "failed to handle chunked requests") + errCh <- err + return nil + } + + h.Logger.Debug(fmt.Sprintf("This is the complete request:\n%v", string(finalReq))) + // read the response from the actual server + resp, err := pUtil.ReadBytes(ctx, h.Logger, destConn) + if err != nil { + if err == io.EOF { + h.Logger.Debug("Response complete, exiting the loop.") + // if there is any buffer left before EOF, we must send it to the client and save this as mock + if len(resp) != 0 { + // Capturing the response timestamp + resTimestampMock := time.Now() + // write the response message to the user client + _, err = clientConn.Write(resp) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, err, "failed to write response message to the user client") + errCh <- err + return nil + } + + // saving last request/response on this conn. + m := &FinalHTTP{ + Req: finalReq, + Resp: resp, + ReqTimestampMock: reqTimestampMock, + ResTimestampMock: resTimestampMock, + } + err := h.parseFinalHTTP(ctx, m, destPort, mocks, opts) + if err != nil { + utils.LogError(h.Logger, err, "failed to parse the final http request and response") + errCh <- err + return nil + } + } + break + } + utils.LogError(h.Logger, err, "failed to read the response message from the destination server") + errCh <- err + return nil + } + + // Capturing the response timestamp + resTimestampMock := time.Now() + + // write the response message to the user client + _, err = clientConn.Write(resp) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, err, "failed to write response message to the user client") + errCh <- err + return nil + } + var finalResp []byte + finalResp = append(finalResp, resp...) + h.Logger.Debug("This is the initial response: " + string(resp)) + + err = h.handleChunkedResponses(ctx, &finalResp, clientConn, destConn, resp) + if err != nil { + if err == io.EOF { + h.Logger.Debug("conn closed by the server", zap.Error(err)) + //check if before EOF complete response came, and try to parse it. + m := &FinalHTTP{ + Req: finalReq, + Resp: finalResp, + ReqTimestampMock: reqTimestampMock, + ResTimestampMock: resTimestampMock, + } + parseErr := h.parseFinalHTTP(ctx, m, destPort, mocks, opts) + if parseErr != nil { + utils.LogError(h.Logger, parseErr, "failed to parse the final http request and response") + errCh <- parseErr + } + errCh <- nil + return nil + } + utils.LogError(h.Logger, err, "failed to handle chunk response") + errCh <- err + return nil + } + + h.Logger.Debug("This is the final response: " + string(finalResp)) + + m := &FinalHTTP{ + Req: finalReq, + Resp: finalResp, + ReqTimestampMock: reqTimestampMock, + ResTimestampMock: resTimestampMock, + } + + err = h.parseFinalHTTP(ctx, m, destPort, mocks, opts) + if err != nil { + utils.LogError(h.Logger, err, "failed to parse the final http request and response") + errCh <- err + return nil + } + + //resetting for the new request and response. + finalReq = []byte("") + finalResp = []byte("") + + // read the request from the same connection + h.Logger.Debug("Reading the request from the user client again from the same connection") + + finalReq, err = pUtil.ReadBytes(ctx, h.Logger, clientConn) + if err != nil { + if err != io.EOF { + h.Logger.Debug("failed to read the request message from the user client", zap.Error(err)) + h.Logger.Debug("This was the last response from the server: " + string(resp)) + errCh <- nil + return nil + } + errCh <- err + return nil + } + // write the request message to the actual destination server + _, err = destConn.Write(finalReq) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(h.Logger, err, "failed to write request message to the destination server") + errCh <- err + return nil + } + } + return nil + }) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + return nil + } + return err + } +} diff --git a/keploy/pkg/core/proxy/integrations/http/http.go b/keploy/pkg/core/proxy/integrations/http/http.go new file mode 100755 index 0000000..b2d3d38 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/http/http.go @@ -0,0 +1,212 @@ +//go:build linux + +package http + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "net" + "net/http" + "strconv" + "time" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/utils" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +func init() { + integrations.Register(integrations.HTTP, &integrations.Parsers{ + Initializer: New, Priority: 100, + }) +} + +type HTTP struct { + Logger *zap.Logger + //opts globalOptions //other global options set by the proxy +} + +func New(logger *zap.Logger) integrations.Integrations { + return &HTTP{ + Logger: logger, + } +} + +type FinalHTTP struct { + Req []byte + Resp []byte + ReqTimestampMock time.Time + ResTimestampMock time.Time +} + +// MatchType function determines if the outgoing network call is HTTP by comparing the +// message format with that of an HTTP text message. +func (h *HTTP) MatchType(_ context.Context, buf []byte) bool { + isHTTP := bytes.HasPrefix(buf[:], []byte("HTTP/")) || + bytes.HasPrefix(buf[:], []byte("GET ")) || + bytes.HasPrefix(buf[:], []byte("POST ")) || + bytes.HasPrefix(buf[:], []byte("PUT ")) || + bytes.HasPrefix(buf[:], []byte("PATCH ")) || + bytes.HasPrefix(buf[:], []byte("DELETE ")) || + bytes.HasPrefix(buf[:], []byte("OPTIONS ")) || + bytes.HasPrefix(buf[:], []byte("HEAD ")) + h.Logger.Debug(fmt.Sprintf("is Http Protocol?: %v ", isHTTP)) + return isHTTP +} + +func (h *HTTP) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + logger := h.Logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + + h.Logger.Debug("Recording the outgoing http call in record mode") + + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial http message") + return err + } + err = h.encodeHTTP(ctx, reqBuf, src, dst, mocks, opts) + if err != nil { + utils.LogError(logger, err, "failed to encode the http message into the yaml") + return err + } + return nil +} + +func (h *HTTP) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { + // h.Logger = h.Logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + h.Logger.Debug("Mocking the outgoing http call in test mode") + + reqBuf, err := util.ReadInitialBuf(ctx, h.Logger, src) + if err != nil { + utils.LogError(h.Logger, err, "failed to read the initial http message") + return err + } + + err = h.decodeHTTP(ctx, reqBuf, src, dstCfg, mockDb, opts) + if err != nil { + utils.LogError(h.Logger, err, "failed to decode the http message from the yaml") + return err + } + return nil +} + +// ParseFinalHTTP is used to parse the final http request and response and save it in a yaml file +func (h *HTTP) parseFinalHTTP(ctx context.Context, mock *FinalHTTP, destPort uint, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + var req *http.Request + // converts the request message buffer to http request + req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(mock.Req))) + if err != nil { + utils.LogError(h.Logger, err, "failed to parse the http request message") + return err + } + + // Set the host header explicitely because the `http.ReadRequest`` trim the host header + // func ReadRequest(b *bufio.Reader) (*Request, error) { + // req, err := readRequest(b) + // if err != nil { + // return nil, err + // } + + // delete(req.Header, "Host") + // return req, err + // } + req.Header.Set("Host", req.Host) + + var reqBody []byte + if req.Body != nil { // Read + var err error + reqBody, err = io.ReadAll(req.Body) + if err != nil { + // TODO right way to log errors + utils.LogError(h.Logger, err, "failed to read the http request body", zap.Any("metadata", utils.GetReqMeta(req))) + return err + } + + if req.Header.Get("Content-Encoding") != "" { + reqBody, err = pkg.Decompress(h.Logger, req.Header.Get("Content-Encoding"), reqBody) + if err != nil { + utils.LogError(h.Logger, err, "failed to decode the http request body", zap.Any("metadata", utils.GetReqMeta(req))) + return err + } + } + } + + // converts the response message buffer to http response + respParsed, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(mock.Resp)), req) + if err != nil { + utils.LogError(h.Logger, err, "failed to parse the http response message", zap.Any("metadata", utils.GetReqMeta(req))) + return err + } + + //Add the content length to the headers. + var respBody []byte + //Checking if the body of the response is empty or does not exist. + if respParsed.Body != nil { // Read + respBody, err = io.ReadAll(respParsed.Body) + if err != nil { + utils.LogError(h.Logger, err, "failed to read the the http response body", zap.Any("metadata", utils.GetReqMeta(req))) + return err + } + + if respParsed.Header.Get("Content-Encoding") != "" { + respBody, err = pkg.Decompress(h.Logger, respParsed.Header.Get("Content-Encoding"), respBody) + if err != nil { + utils.LogError(h.Logger, err, "failed to decode the http response body", zap.Any("metadata", utils.GetReqMeta(req))) + return err + } + } + + h.Logger.Debug("This is the response body: " + string(respBody)) + //Set the content length to the headers. + respParsed.Header.Set("Content-Length", strconv.Itoa(len(respBody))) + } + + // store the request and responses as mocks + meta := map[string]string{ + "name": "Http", + "type": models.HTTPClient, + "operation": req.Method, + "connID": ctx.Value(models.ClientConnectionIDKey).(string), + } + + // Check if the request is a passThrough request + if utils.IsPassThrough(h.Logger, req, destPort, opts) { + h.Logger.Debug("The request is a passThrough request", zap.Any("metadata", utils.GetReqMeta(req))) + return nil + } + + mocks <- &models.Mock{ + Version: models.GetVersion(), + Name: "mocks", + Kind: models.HTTP, + Spec: models.MockSpec{ + Metadata: meta, + HTTPReq: &models.HTTPReq{ + Method: models.Method(req.Method), + ProtoMajor: req.ProtoMajor, + ProtoMinor: req.ProtoMinor, + URL: req.URL.String(), + Header: pkg.ToYamlHTTPHeader(req.Header), + Body: string(reqBody), + URLParams: pkg.URLParams(req), + }, + HTTPResp: &models.HTTPResp{ + StatusCode: respParsed.StatusCode, + Header: pkg.ToYamlHTTPHeader(respParsed.Header), + Body: string(respBody), + }, + Created: time.Now().Unix(), + + ReqTimestampMock: mock.ReqTimestampMock, + ResTimestampMock: mock.ResTimestampMock, + }, + } + return nil +} diff --git a/keploy/pkg/core/proxy/integrations/http/match.go b/keploy/pkg/core/proxy/integrations/http/match.go new file mode 100644 index 0000000..a168d59 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/http/match.go @@ -0,0 +1,349 @@ +//go:build linux + +package http + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/agnivade/levenshtein" + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type req struct { + method string + url *url.URL + header http.Header + body []byte + raw []byte +} + +func (h *HTTP) match(ctx context.Context, input *req, mockDb integrations.MockMemDb) (bool, *models.Mock, error) { + for { + if ctx.Err() != nil { + return false, nil, ctx.Err() + } + + // Fetch and filter HTTP mocks + mocks, err := mockDb.GetUnFilteredMocks() + + if err != nil { + utils.LogError(h.Logger, err, "failed to get unfilteredMocks mocks") + return false, nil, errors.New("error while matching the request with the mocks") + } + unfilteredMocks := FilterHTTPMocks(mocks) + + h.Logger.Debug(fmt.Sprintf("Length of unfilteredMocks:%v", len(unfilteredMocks))) + + // Matching process + schemaMatched, err := h.SchemaMatch(ctx, input, unfilteredMocks) + if err != nil { + return false, nil, err + } + + if len(schemaMatched) == 0 { + return false, nil, nil + } + + // Exact body match + ok, bestMatch := h.ExactBodyMatch(input.body, schemaMatched) + if ok { + if !h.updateMock(ctx, bestMatch, mockDb) { + continue + } + return true, bestMatch, nil + } + + shortListed := schemaMatched + // Schema match for JSON bodies + if pkg.IsJSON(input.body) { + bodyMatched, err := h.PerformBodyMatch(ctx, schemaMatched, input.body) + if err != nil { + return false, nil, err + } + + if len(bodyMatched) == 0 { + h.Logger.Debug("No mock found with body schema match") + return false, nil, nil + } + + if len(bodyMatched) == 1 { + if !h.updateMock(ctx, bodyMatched[0], mockDb) { + continue + } + return true, bodyMatched[0], nil + } + + // More than one match, perform fuzzy match + shortListed = bodyMatched + } + + h.Logger.Debug("Performing fuzzy match for req buffer") + // Perform fuzzy match on the request + isMatched, bestMatch := h.PerformFuzzyMatch(shortListed, input.raw) + if isMatched { + if !h.updateMock(ctx, bestMatch, mockDb) { + continue + } + return true, bestMatch, nil + } + return false, nil, nil + } +} + +// FilterHTTPMocks Filter mocks to only HTTP mocks +func FilterHTTPMocks(mocks []*models.Mock) []*models.Mock { + var httpMocks []*models.Mock + for _, mock := range mocks { + if mock.Kind != models.Kind(models.HTTP) { + continue + } + httpMocks = append(httpMocks, mock) + } + return httpMocks +} + +// MatchBodyType Body type match check (content type matching) +func (h *HTTP) MatchBodyType(mockBody string, reqBody []byte) bool { + if mockBody == "" && string(reqBody) == "" { + return true + } + mockBodyType := pkg.GuessContentType([]byte(mockBody)) + reqBodyType := pkg.GuessContentType(reqBody) + return mockBodyType == reqBodyType +} + +func (h *HTTP) MatchURLPath(mockURL, reqPath string) bool { + parsedURL, err := url.Parse(mockURL) + if err != nil { + return false + } + return parsedURL.Path == reqPath +} + +func (h *HTTP) MapsHaveSameKeys(map1 map[string]string, map2 map[string][]string) bool { + if len(map1) != len(map2) { + return false + } + + for key := range map1 { + lkey := strings.ToLower(key) + if lkey == "keploy-test-id" || lkey == "keploy-test-set-id" { + continue + } + if _, exists := map2[key]; !exists { + return false + } + } + + for key := range map2 { + lkey := strings.ToLower(key) + if lkey == "keploy-test-id" || lkey == "keploy-test-set-id" { + continue + } + if _, exists := map1[key]; !exists { + return false + } + } + + return true +} + +// SchemaMatch match the schema of the request with the mocks +func (h *HTTP) SchemaMatch(ctx context.Context, input *req, unfilteredMocks []*models.Mock) ([]*models.Mock, error) { + var schemaMatched []*models.Mock + + for _, mock := range unfilteredMocks { + if ctx.Err() != nil { + return nil, ctx.Err() + } + + // Content type check + if input.header.Get("Content-Type") != "" { + if input.header.Get("Content-Type") != mock.Spec.HTTPReq.Header["Content-Type"] { + h.Logger.Debug("The content type of mock and request aren't the same") + continue + } + } + // Body type check + if !h.MatchBodyType(mock.Spec.HTTPReq.Body, input.body) { + h.Logger.Debug("The body of mock and request aren't of same type") + continue + } + + // URL path match + if !h.MatchURLPath(mock.Spec.HTTPReq.URL, input.url.Path) { + h.Logger.Debug("The url path of mock and request aren't the same") + continue + } + + // HTTP method match + if mock.Spec.HTTPReq.Method != models.Method(input.method) { + h.Logger.Debug("The method of mock and request aren't the same") + continue + } + + // Header key match + if !h.MapsHaveSameKeys(mock.Spec.HTTPReq.Header, input.header) { + h.Logger.Debug("The header keys of mock and request aren't the same") + continue + } + + // Query parameter match + if !h.MapsHaveSameKeys(mock.Spec.HTTPReq.URLParams, input.url.Query()) { + h.Logger.Debug("The query params of mock and request aren't the same") + continue + } + + schemaMatched = append(schemaMatched, mock) + } + + return schemaMatched, nil +} + +// ExactBodyMatch Exact body match +func (h *HTTP) ExactBodyMatch(body []byte, schemaMatched []*models.Mock) (bool, *models.Mock) { + for _, mock := range schemaMatched { + if mock.Spec.HTTPReq.Body == string(body) { + return true, mock + } + } + return false, nil +} + +func (h *HTTP) bodyMatch(mockBody, reqBody []byte) (bool, error) { + + var mockData map[string]any + var reqData map[string]any + err := json.Unmarshal(mockBody, &mockData) + if err != nil { + utils.LogError(h.Logger, err, "failed to unmarshal the mock request body", zap.String("Req", string(mockBody))) + return false, err + } + err = json.Unmarshal(reqBody, &reqData) + if err != nil { + utils.LogError(h.Logger, err, "failed to unmarshal the request body", zap.String("Req", string(reqBody))) + return false, err + } + + for key := range mockData { + _, exists := reqData[key] + if !exists { + return false, nil + } + } + return true, nil +} + +// PerformBodyMatch Perform body match for JSON data +func (h *HTTP) PerformBodyMatch(ctx context.Context, schemaMatched []*models.Mock, reqBody []byte) ([]*models.Mock, error) { + h.Logger.Debug("Performing schema match for body") + + var bodyMatched []*models.Mock + for _, mock := range schemaMatched { + if ctx.Err() != nil { + return nil, ctx.Err() + } + + ok, err := h.bodyMatch([]byte(mock.Spec.HTTPReq.Body), reqBody) + if err != nil { + h.Logger.Error("failed to do schema matching on request body", zap.Error(err)) + break + } + + if ok { + bodyMatched = append(bodyMatched, mock) + h.Logger.Debug("found a mock with body schema match") + } + } + return bodyMatched, nil +} + +// Fuzzy match helper for string matching +func (h *HTTP) findStringMatch(req string, mockStrings []string) int { + minDist := int(^uint(0) >> 1) + bestMatch := -1 + for idx, mock := range mockStrings { + if !util.IsASCII(mock) { + continue + } + dist := levenshtein.ComputeDistance(req, mock) + if dist == 0 { + return 0 + } + if dist < minDist { + minDist = dist + bestMatch = idx + } + } + return bestMatch +} + +// TODO: generalize the function to work with any type of integration +func (h *HTTP) findBinaryMatch(mocks []*models.Mock, reqBuff []byte) int { + + mxSim := -1.0 + mxIdx := -1 + // find the fuzzy hash of the mocks + for idx, mock := range mocks { + encoded, _ := decode(mock.Spec.HTTPReq.Body) + k := util.AdaptiveK(len(reqBuff), 3, 8, 5) + shingles1 := util.CreateShingles(encoded, k) + shingles2 := util.CreateShingles(reqBuff, k) + similarity := util.JaccardSimilarity(shingles1, shingles2) + + // log.Debugf("Jaccard Similarity:%f\n", similarity) + + if mxSim < similarity { + mxSim = similarity + mxIdx = idx + } + } + return mxIdx +} + +// PerformFuzzyMatch Perform fuzzy match on the request +func (h *HTTP) PerformFuzzyMatch(tcsMocks []*models.Mock, reqBuff []byte) (bool, *models.Mock) { + encodedReq := encode(reqBuff) + for _, mock := range tcsMocks { + encodedMock, _ := decode(mock.Spec.HTTPReq.Body) + if string(encodedMock) == string(reqBuff) || mock.Spec.HTTPReq.Body == encodedReq { + return true, mock + } + } + // String-based fuzzy matching + mockStrings := make([]string, len(tcsMocks)) + for i := range tcsMocks { + mockStrings[i] = tcsMocks[i].Spec.HTTPReq.Body + } + if util.IsASCII(string(reqBuff)) { + idx := h.findStringMatch(string(reqBuff), mockStrings) + if idx != -1 { + return true, tcsMocks[idx] + } + } + idx := h.findBinaryMatch(tcsMocks, reqBuff) + if idx != -1 { + return true, tcsMocks[idx] + } + return false, nil +} + +// Update the matched mock (delete or update) +func (h *HTTP) updateMock(_ context.Context, matchedMock *models.Mock, mockDb integrations.MockMemDb) bool { + originalMatchedMock := *matchedMock + matchedMock.TestModeInfo.IsFiltered = false + matchedMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() + updated := mockDb.UpdateUnFilteredMock(&originalMatchedMock, matchedMock) + return updated +} diff --git a/keploy/pkg/core/proxy/integrations/http/util.go b/keploy/pkg/core/proxy/integrations/http/util.go new file mode 100644 index 0000000..71b37f2 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/http/util.go @@ -0,0 +1,46 @@ +package http + +import ( + "bufio" + "bytes" + "io" + + "go.uber.org/zap" +) + +// Checks if the response is gzipped +func isGZipped(check io.ReadCloser, l *zap.Logger) (bool, *bufio.Reader) { + bufReader := bufio.NewReader(check) + peekedBytes, err := bufReader.Peek(2) + if err != nil && err != io.EOF { + l.Debug("failed to peek the response", zap.Error(err)) + return false, nil + } + if len(peekedBytes) < 2 { + return false, nil + } + if peekedBytes[0] == 0x1f && peekedBytes[1] == 0x8b { + return true, bufReader + } + return false, nil +} + +// hasCompleteHeaders checks if the given byte slice contains the complete HTTP headers +func hasCompleteHeaders(httpChunk []byte) bool { + // Define the sequence for header end: "\r\n\r\n" + headerEndSequence := []byte{'\r', '\n', '\r', '\n'} + + // Check if the byte slice contains the header end sequence + return bytes.Contains(httpChunk, headerEndSequence) +} + +func encode(buffer []byte) string { + //Encode the buffer to string + encoded := string(buffer) + return encoded +} +func decode(encoded string) ([]byte, error) { + // decode the string to a buffer. + data := []byte(encoded) + return data, nil +} diff --git a/keploy/pkg/core/proxy/integrations/integrations.go b/keploy/pkg/core/proxy/integrations/integrations.go new file mode 100644 index 0000000..ffe162e --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/integrations.go @@ -0,0 +1,54 @@ +//go:build linux + +// Package integrations provides functionality for integrating different types of services. +package integrations + +import ( + "context" + "net" + + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +type Initializer func(logger *zap.Logger) Integrations + +type IntegrationType string + +// constants for different types of integrations +const ( + HTTP IntegrationType = "http" + GRPC IntegrationType = "grpc" + GENERIC IntegrationType = "generic" + MYSQL IntegrationType = "mysql" + POSTGRES_V1 IntegrationType = "postgres_v1" + POSTGRES_V2 IntegrationType = "postgres_v2" + MONGO IntegrationType = "mongo" + REDIS IntegrationType = "redis" +) + +type Parsers struct { + Initializer Initializer + Priority int +} + +var Registered = make(map[IntegrationType]*Parsers) + +type Integrations interface { + MatchType(ctx context.Context, reqBuf []byte) bool + RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error + MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb MockMemDb, opts models.OutgoingOptions) error +} + +func Register(name IntegrationType, p *Parsers) { + Registered[name] = p +} + +type MockMemDb interface { + GetFilteredMocks() ([]*models.Mock, error) + GetUnFilteredMocks() ([]*models.Mock, error) + UpdateUnFilteredMock(old *models.Mock, new *models.Mock) bool + DeleteFilteredMock(mock models.Mock) bool + DeleteUnFilteredMock(mock models.Mock) bool + GetMySQLCounts() (total, config, data int) +} diff --git a/keploy/pkg/core/proxy/integrations/mongo/README.md b/keploy/pkg/core/proxy/integrations/mongo/README.md new file mode 100644 index 0000000..c9ea2e5 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mongo/README.md @@ -0,0 +1,6 @@ +# Mongo Package Documentation + +The `mongo` package encompasses the parser and mapping logic required +to read MongoDB wire messages and capture or stub the outputs. +Utilized by the `hooks` package, it assists in redirecting outgoing +calls for the purpose of recording or stubbing the outputs. \ No newline at end of file diff --git a/keploy/pkg/core/proxy/integrations/mongo/command.go b/keploy/pkg/core/proxy/integrations/mongo/command.go new file mode 100644 index 0000000..4d34d1e --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mongo/command.go @@ -0,0 +1,98 @@ +//go:build linux + +// Package mongo provides functionality for working with MongoDB outgoing calls. +package mongo + +// This file contains code from the coinbase mongobetween +// https://github.com/coinbase/mongobetween/blob/1034c5a0c3f10cb1dd84af2981bc55ea1d3b45c0/mongo/command.go#L10 +import ( + "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" +) + +type Command string + +// constants for all the commands that can be proxied +const ( + Unknown Command = "unknown" + AbortTransaction Command = "abortTransaction" + Aggregate Command = "aggregate" + CommitTransaction Command = "commandTransaction" + Count Command = "count" + CreateIndexes Command = "createIndexes" + Delete Command = "delete" + Distinct Command = "distinct" + Drop Command = "drop" + DropDatabase Command = "dropDatabase" + DropIndexes Command = "dropIndexes" + EndSessions Command = "endSessions" + Find Command = "find" + FindAndModify Command = "findAndModify" + GetMore Command = "getMore" + Insert Command = "insert" + IsMaster Command = "isMaster" + Ismaster Command = "ismaster" + ListCollections Command = "listCollections" + ListIndexes Command = "listIndexes" + ListDatabases Command = "listDatabases" + MapReduce Command = "mapReduce" + Update Command = "tools" +) + +var collectionCommands = []Command{Aggregate, Count, CreateIndexes, Delete, Distinct, Drop, DropIndexes, Find, FindAndModify, Insert, ListIndexes, MapReduce, Update} +var int32Commands = []Command{AbortTransaction, Aggregate, CommitTransaction, DropDatabase, IsMaster, Ismaster, ListCollections, ListDatabases} +var int64Commands = []Command{GetMore} +var arrayCommands = []Command{EndSessions} + +func IsWrite(command Command) bool { + switch command { + case CommitTransaction, CreateIndexes, Delete, Drop, DropIndexes, DropDatabase, FindAndModify, Insert, Update: + return true + } + return false +} + +func CommandAndCollection(msg bsoncore.Document) (Command, string) { + for _, s := range collectionCommands { + if coll, ok := msg.Lookup(string(s)).StringValueOK(); ok { + return s, coll + } + } + for _, s := range int32Commands { + value := msg.Lookup(string(s)) + if value.Data != nil { + return s, "" + } + } + for _, s := range int64Commands { + value := msg.Lookup(string(s)) + if value.Data != nil { + if coll, ok := msg.Lookup("collection").StringValueOK(); ok { + return s, coll + } + return s, "" + } + } + for _, s := range arrayCommands { + value := msg.Lookup(string(s)) + if value.Data != nil { + return s, "" + } + } + return Unknown, "" +} + +func IsIsMasterDoc(doc bsoncore.Document) bool { + isMaster := doc.Lookup(string(IsMaster)) + ismaster := doc.Lookup(string(Ismaster)) + return IsIsMasterValueTruthy(isMaster) || IsIsMasterValueTruthy(ismaster) +} + +func IsIsMasterValueTruthy(val bsoncore.Value) bool { + if intValue, isInt := val.Int32OK(); intValue > 0 { + return true + } else if !isInt { + boolValue, isBool := val.BooleanOK() + return boolValue && isBool + } + return false +} diff --git a/keploy/pkg/core/proxy/integrations/mongo/decode.go b/keploy/pkg/core/proxy/integrations/mongo/decode.go new file mode 100644 index 0000000..7973a38 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mongo/decode.go @@ -0,0 +1,345 @@ +//go:build linux + +package mongo + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "strconv" + "time" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" + "go.uber.org/zap" +) + +// decodeMongo decodes the mongo wire message from the client connection +// and sends the response back to the client. +func decodeMongo(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { + startedDecoding := time.Now() + requestBuffers := [][]byte{reqBuf} + + errCh := make(chan error, 1) + + go func(errCh chan error, reqBuf []byte, startedDecoding time.Time, requestBuffers [][]byte) { + defer util.Recover(logger, clientConn, nil) + defer close(errCh) + var readRequestDelay time.Duration + var err error + for { + + var ( + mongoRequests []models.MongoRequest // stores the request packet + ) + // check to read the request buffer from the client connection after the initial packet + if string(reqBuf) == "read form client conn" { + started := time.Now() + // reads the first chunk of the mongo request + reqBuf, err = util.ReadBytes(ctx, logger, clientConn) + if err != nil { + if err == io.EOF { + logger.Debug("received request buffer is empty in test mode for mongo calls") + errCh <- err + return + } + utils.LogError(logger, err, "failed to read request from the mongo client") + errCh <- err + return + } + requestBuffers = append(requestBuffers, reqBuf) + logger.Debug("the request from the mongo client", zap.Any("buffer", reqBuf)) + readRequestDelay = time.Since(started) + } + if len(reqBuf) == 0 { + errCh <- errors.New("the request buffer is empty") + return + } + logger.Debug(fmt.Sprintf("the loop starts with the time delay: %v", time.Since(startedDecoding))) + // convert the request buffer to the mongo wire message in the go struct + opReq, requestHeader, mongoRequest, err := Decode(reqBuf, logger) + if err != nil { + utils.LogError(logger, err, "failed to decode the mongo wire message from the client") + errCh <- err + return + } + mongoRequests = append(mongoRequests, models.MongoRequest{ + Header: &requestHeader, + Message: mongoRequest, + ReadDelay: int64(readRequestDelay), + }) + // check for the more_to_come flag bit in the mongo request + // header to read the next chunks of the request + if val, ok := mongoRequest.(*models.MongoOpMessage); ok && hasSecondSetBit(val.FlagBits) { + for { + started := time.Now() + logger.Debug("into the for loop for request stream") + // reads the next chunk of the mongo request + requestBuffer1, err := util.ReadBytes(ctx, logger, clientConn) + if err != nil { + if err == io.EOF { + logger.Debug("received request buffer is empty for streaming mongo request call") + errCh <- err + return + } + utils.LogError(logger, err, "failed to read request from the mongo client", zap.String("mongo server address", dstCfg.Addr)) + errCh <- err + return + } + requestBuffers = append(requestBuffers, reqBuf) + readRequestDelay = time.Since(started) + + if len(requestBuffer1) == 0 { + logger.Debug("the response from the server is complete") + break + } + // convert the request buffer to the mongo response wire message in the go struct + _, reqHeader, mongoReq, err := Decode(requestBuffer1, logger) + if err != nil { + utils.LogError(logger, err, "failed to decode the mongo wire message from the mongo client") + errCh <- err + return + } + if mongoReqVal, ok := mongoReq.(models.MongoOpMessage); ok && !hasSecondSetBit(mongoReqVal.FlagBits) { + logger.Debug("the request from the client is complete since the more_to_come flagbit is 0") + break + } + mongoRequests = append(mongoRequests, models.MongoRequest{ + Header: &reqHeader, + Message: mongoReq, + ReadDelay: int64(readRequestDelay), + }) + } + } + // check for the heartbeat request from client and use the config mocks to respond + if isHeartBeat(logger, opReq, *mongoRequests[0].Header, mongoRequests[0].Message) { + // isScramAuth(logger, opReq) { + var bestMatchIndex = -1 + var maxMatchScore = 0.0 + var configMocks []*models.Mock + for { + configMocks, err = mockDb.GetUnFilteredMocks() + if err != nil { + utils.LogError(logger, err, "error while getting config mock") + } + logger.Debug(fmt.Sprintf("the config mocks are: %v", configMocks)) + maxMatchScore = 0.0 + bestMatchIndex = -1 + // loop over the recorded config mocks to match with the incoming request + for configIndex, configMock := range configMocks { + logger.Debug("the config mock is: ", zap.Any("config mock", configMock), zap.Any("actual request", mongoRequests)) + // checking the number of chunks for recorded config mocks and the incoming request + if len(configMock.Spec.MongoRequests) != len(mongoRequests) { + continue + } + + for i, req := range configMock.Spec.MongoRequests { + // check the opcode of the incoming request and the recorded config mocks + if len(configMock.Spec.MongoRequests) != len(mongoRequests) || req.Header.Opcode != mongoRequests[i].Header.Opcode { + continue + } + switch req.Header.Opcode { + case wiremessage.OpQuery: + // check the query fields of the incoming request and the recorded config mocks + expectedQuery := req.Message.(*models.MongoOpQuery) + actualQuery := mongoRequests[i].Message.(*models.MongoOpQuery) + if actualQuery.FullCollectionName != expectedQuery.FullCollectionName || + actualQuery.ReturnFieldsSelector != expectedQuery.ReturnFieldsSelector || + actualQuery.Flags != expectedQuery.Flags || + actualQuery.NumberToReturn != expectedQuery.NumberToReturn || + actualQuery.NumberToSkip != expectedQuery.NumberToSkip { + continue + } + + // calculate the matching score for query bson dcouments of the incoming request and the recorded config mocks + expected := map[string]interface{}{} + actual := map[string]interface{}{} + err = bson.UnmarshalExtJSON([]byte(expectedQuery.Query), true, &expected) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the section of recorded request to bson document") + continue + } + err = bson.UnmarshalExtJSON([]byte(actualQuery.Query), true, &actual) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the section of incoming request to bson document") + continue + } + score := calculateMatchingScore(expected, actual) + logger.Debug("the expected and actual msg in the heartbeat OpQuery query.", zap.Any("expected", expected), zap.Any("actual", actual), zap.Any("score", score)) + if score > maxMatchScore { + maxMatchScore = score + bestMatchIndex = configIndex + } + + case wiremessage.OpMsg: + // check the OpMsg sections of the incoming request and the recorded config mocks + if req.Message.(*models.MongoOpMessage).FlagBits != mongoRequests[i].Message.(*models.MongoOpMessage).FlagBits { + continue + } + scoreSum := 0.0 + if len(req.Message.(*models.MongoOpMessage).Sections) != len(mongoRequests[i].Message.(*models.MongoOpMessage).Sections) { + continue + } + // calculate the matching score for each section of the incoming request and the recorded config mocks + for sectionIndx, section := range req.Message.(*models.MongoOpMessage).Sections { + if len(req.Message.(*models.MongoOpMessage).Sections) == len(mongoRequests[i].Message.(*models.MongoOpMessage).Sections) { + score := compareOpMsgSection(logger, section, mongoRequests[i].Message.(*models.MongoOpMessage).Sections[sectionIndx]) + scoreSum += score + } + } + currentScore := scoreSum / float64(len(mongoRequests)) + logger.Debug("the expected and actual msg in the heartbeat OpMsg single section.", zap.Any("expected", req.Message.(*models.MongoOpMessage).Sections), zap.Any("actual", mongoRequests[i].Message.(*models.MongoOpMessage).Sections), zap.Any("score", currentScore)) + if currentScore > maxMatchScore { + maxMatchScore = currentScore + bestMatchIndex = configIndex + } + default: + utils.LogError(logger, err, "the OpCode of the mongo wiremessage is invalid.", zap.Any("opcode", req.Header.Opcode)) + } + } + } + + if bestMatchIndex != -1 && maxMatchScore != 0.0 { + matchedMock := *configMocks[bestMatchIndex] + matchedMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() + isUpdated := mockDb.UpdateUnFilteredMock(configMocks[bestMatchIndex], &matchedMock) + if !isUpdated { + continue + } + } + break + } + + responseTo := mongoRequests[0].Header.RequestID + if bestMatchIndex == -1 || maxMatchScore == 0.0 { + logger.Debug("the mongo request do not matches with any config mocks", zap.Any("request", mongoRequests)) + continue + } + + // write the mongo response to the client connection from the recorded config mocks that most matches the incoming request + for _, mongoResponse := range configMocks[bestMatchIndex].Spec.MongoResponses { + switch mongoResponse.Header.Opcode { + case wiremessage.OpReply: + replySpec := mongoResponse.Message.(*models.MongoOpReply) + replyMessage, err := encodeOpReply(ctx, mongoRequests[0], configMocks[bestMatchIndex].Spec.MongoRequests[0], replySpec, opts.MongoPassword, logger) + if err != nil { + utils.LogError(logger, err, "failed to encode the recorded OpReply yaml", zap.Any("for request with id", responseTo)) + errCh <- err + return + } + requestID := wiremessage.NextRequestID() + heathCheckReplyBuffer := replyMessage.Encode(responseTo, requestID) + responseTo = requestID + logger.Debug(fmt.Sprintf("the bufffer response is: %v", string(heathCheckReplyBuffer))) + // handle scram auth request + _, err = clientConn.Write(heathCheckReplyBuffer) + if err != nil { + if ctx.Err() != nil { + return + } + utils.LogError(logger, err, "failed to write the health check reply to mongo client") + errCh <- err + return + } + case wiremessage.OpMsg: + respMessage := mongoResponse.Message.(*models.MongoOpMessage) + + var expectedRequestSections []string + if len(configMocks[bestMatchIndex].Spec.MongoRequests) > 0 { + expectedRequestSections = configMocks[bestMatchIndex].Spec.MongoRequests[0].Message.(*models.MongoOpMessage).Sections + } + message, err := encodeOpMsg(ctx, respMessage, mongoRequest.(*models.MongoOpMessage).Sections, expectedRequestSections, opts.MongoPassword, logger) + if err != nil { + utils.LogError(logger, err, "failed to encode the recorded OpMsg response", zap.Any("for request with id", responseTo)) + errCh <- err + return + } + _, err = clientConn.Write(message.Encode(responseTo, wiremessage.NextRequestID())) + if err != nil { + if ctx.Err() != nil { + return + } + utils.LogError(logger, err, "failed to write the health check opmsg to mongo client") + errCh <- err + return + } + } + } + } else { + // handle for the non-heartbeat request from the client + + // match the incoming request with the recorded tcsMocks and return a mocked response which matches most with incoming request + matched, matchedMock, err := match(ctx, logger, mongoRequests, mockDb) + if err != nil { + errCh <- err + utils.LogError(logger, err, "error while matching mongo mocks") + return + } + if !matched { + logger.Debug("mongo request not matched with any tcsMocks", zap.Any("request", mongoRequests)) + reqBuf, err = util.PassThrough(ctx, logger, clientConn, dstCfg, requestBuffers) + if err != nil { + utils.LogError(logger, err, "failed to passthrough the mongo request to the actual database server") + errCh <- err + return + } + continue + } + + responseTo := mongoRequests[0].Header.RequestID + logger.Debug("the mock matched with the current request", zap.Any("mock", matchedMock), zap.Any("responseTo", responseTo)) + + // write the mongo response to the client connection from the recorded tcsMocks that most matches the incoming request + for _, resp := range matchedMock.Spec.MongoResponses { + respMessage := resp.Message.(*models.MongoOpMessage) + var expectedRequestSections []string + if len(matchedMock.Spec.MongoRequests) > 0 { + expectedRequestSections = matchedMock.Spec.MongoRequests[0].Message.(*models.MongoOpMessage).Sections + } + message, err := encodeOpMsg(ctx, respMessage, mongoRequest.(*models.MongoOpMessage).Sections, expectedRequestSections, opts.MongoPassword, logger) + if err != nil { + utils.LogError(logger, err, "failed to encode the recorded OpMsg response", zap.Any("for request with id", responseTo)) + errCh <- err + return + } + requestID := wiremessage.NextRequestID() + _, err = clientConn.Write(message.Encode(responseTo, requestID)) + if err != nil { + if ctx.Err() != nil { + return + } + utils.LogError(logger, err, "failed to write the health check opmsg to mongo client", zap.Any("for request with id", responseTo)) + errCh <- err + return + } + responseTo = requestID + } + } + logger.Debug("the length of the requestBuffer after matching: " + strconv.Itoa(len(reqBuf)) + strconv.Itoa(len(requestBuffers[0]))) + if len(requestBuffers) > 0 && len(reqBuf) == len(requestBuffers[0]) { + reqBuf = []byte("read form client conn") + } + + // Clear the buffer for the next dependency call + requestBuffers = [][]byte{} + } + }(errCh, reqBuf, startedDecoding, requestBuffers) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + logger.Debug("connection lost from client") + return nil + } + return err + } +} diff --git a/keploy/pkg/core/proxy/integrations/mongo/encode.go b/keploy/pkg/core/proxy/integrations/mongo/encode.go new file mode 100644 index 0000000..e7b0840 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mongo/encode.go @@ -0,0 +1,288 @@ +//go:build linux + +package mongo + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "time" + + "golang.org/x/sync/errgroup" + + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// encodeMongo records the outgoing mongo messages of the client connection, +// decodes the wiremessage binary and writes readable string +// to the yaml file. +func (m *Mongo) encodeMongo(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, _ models.OutgoingOptions) error { + + errCh := make(chan error, 1) + + //get the error group from the context + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context") + } + + g.Go(func() error { + defer pUtil.Recover(logger, clientConn, destConn) + defer close(errCh) + for { + var err error + var readRequestDelay time.Duration + + // reads the request packets from the client connection after the first request packet. + // Since, that is already read in the RecordOutgoing function. + if string(reqBuf) == "read form client conn" { + started := time.Now() + reqBuf, err = pUtil.ReadBytes(ctx, logger, clientConn) + logger.Debug("reading from the mongo conn", zap.Any("", string(reqBuf))) + if err != nil { + if err == io.EOF { + logger.Debug("received request buffer is empty in record mode for mongo call") + errCh <- err + return nil + } + utils.LogError(logger, err, "failed to read request from the mongo client", zap.String("mongo client address", clientConn.RemoteAddr().String())) + errCh <- err + return nil + } + readRequestDelay = time.Since(started) + // logStr += lstr + logger.Debug(fmt.Sprintf("the request in the mongo parser before passing to dest: %v", len(reqBuf))) + } + + var ( + mongoRequests []models.MongoRequest // stores the decoded binary packets for a request + mongoResponses []models.MongoResponse // stores the decoded binary packets for a response + ) + // decode the binary packet and store the values in the corresponding struct + opReq, requestHeader, mongoRequest, err := Decode(reqBuf, logger) + if err != nil { + utils.LogError(logger, err, "failed to decode the mongo wire message from the client") + errCh <- err + return nil + } + + mongoRequests = append(mongoRequests, models.MongoRequest{ + Header: &requestHeader, + Message: mongoRequest, + ReadDelay: int64(readRequestDelay), + }) + // forwards the request packet to the destination server + _, err = destConn.Write(reqBuf) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(logger, err, "failed to write the request buffer to mongo server", zap.String("mongo server address", destConn.RemoteAddr().String())) + errCh <- err + return nil + } + logger.Debug(fmt.Sprintf("the request in the mongo parser after passing to dest: %v", len(reqBuf))) + + // check for the request packet streaming for the mongo wire message + if val, ok := mongoRequest.(*models.MongoOpMessage); ok && hasSecondSetBit(val.FlagBits) { + for { + // read the streaming request packets + requestBuffer1, err := pUtil.ReadBytes(ctx, logger, clientConn) + if err != nil { + if err == io.EOF { + logger.Debug("received request buffer is empty in record mode for mongo request") + errCh <- err + return nil + } + utils.LogError(logger, err, "failed to read request from the mongo client", zap.String("mongo client address", clientConn.RemoteAddr().String())) + errCh <- err + return nil + } + + // forward the request packet to the destination server + _, err = destConn.Write(requestBuffer1) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(logger, err, "failed to write the reply message to mongo client") + errCh <- err + return nil + } + + if len(requestBuffer1) == 0 { + logger.Debug("the response from the server is complete") + break + } + // decode the binary packet and return the values in the corresponding structs + // for header and message. + _, reqHeader, mongoReq, err := Decode(requestBuffer1, logger) + if err != nil { + utils.LogError(logger, err, "failed to decode the mongo wire message from the destination server") + errCh <- err + return nil + } + if mongoReqVal, ok := mongoReq.(models.MongoOpMessage); ok && !hasSecondSetBit(mongoReqVal.FlagBits) { + logger.Debug("the request from the client is complete since the more_to_come flagbit is 0") + break + } + mongoRequests = append(mongoRequests, models.MongoRequest{ + Header: &reqHeader, + Message: mongoReq, + ReadDelay: int64(readRequestDelay), + }) + } + } + + reqTimestampMock := time.Now() + started := time.Now() + + // read reply message length from the destination mongo server + responsePckLengthBuffer, err := pUtil.ReadRequiredBytes(ctx, logger, destConn, 4) + if err != nil { + if err == io.EOF { + logger.Debug("received response buffer is empty in record mode for mongo call") + errCh <- err + return nil + } + utils.LogError(logger, err, "failed to read reply from the mongo server", zap.String("mongo server address", destConn.RemoteAddr().String())) + errCh <- err + return nil + } + + logger.Debug("received these pck length packets", zap.Any("packets", responsePckLengthBuffer)) + + // convert packet length to LittleEndian integer. + pckLength := getPacketLength(responsePckLengthBuffer) + logger.Debug("received pck length ", zap.Any("packet length", pckLength)) + + // read the entire response packet + responsePckDataBuffer, err := pUtil.ReadRequiredBytes(ctx, logger, destConn, int(pckLength)-4) + + logger.Debug("received these packets", zap.Any("packets", responsePckDataBuffer)) + + responseBuffer := append(responsePckLengthBuffer, responsePckDataBuffer...) + logger.Debug("reading from the destination mongo server", zap.Any("", string(responseBuffer))) + if err != nil { + if err == io.EOF { + logger.Debug("received response buffer is empty in record mode for mongo call") + errCh <- err + return nil + } + utils.LogError(logger, err, "failed to read reply from the mongo server", zap.String("mongo server address", destConn.RemoteAddr().String())) + errCh <- err + return nil + } + readResponseDelay := time.Since(started) + + // write the response packet to mongo client + _, err = clientConn.Write(responseBuffer) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(logger, err, "failed to write the reply message to mongo client") + errCh <- err + return nil + } + + // decode the binary packet of response and return the values in the corresponding structs + _, responseHeader, mongoResponse, err := Decode(responseBuffer, logger) + if err != nil { + utils.LogError(logger, err, "failed to decode the mongo wire message from the destination server") + errCh <- err + return nil + } + mongoResponses = append(mongoResponses, models.MongoResponse{ + Header: &responseHeader, + Message: mongoResponse, + ReadDelay: int64(readResponseDelay), + }) + // check for the response packet streaming for the mongo wire message + if val, ok := mongoResponse.(*models.MongoOpMessage); ok && hasSecondSetBit(val.FlagBits) { + for i := 0; ; i++ { + // handle the streaming response packets for heartbeat calls + if i == 0 && isHeartBeat(logger, opReq, *mongoRequests[0].Header, mongoRequests[0].Message) { + m.recordMessage(ctx, logger, mongoRequests, mongoResponses, opReq, reqTimestampMock, mocks) + } + started = time.Now() + + // read the response packets from the destination server + responseBuffer, err = pUtil.ReadBytes(ctx, logger, destConn) + if err != nil { + if err == io.EOF { + logger.Debug("received response buffer is empty in record mode for mongo call") + errCh <- err + return nil + } + utils.LogError(logger, err, "failed to read reply from the mongo server", zap.String("mongo server address", destConn.RemoteAddr().String())) + errCh <- err + return nil + } + logger.Debug(fmt.Sprintf("the response in the mongo parser before passing to client: %v", len(responseBuffer))) + + readResponseDelay := time.Since(started) + + // write the reply to mongo client + _, err = clientConn.Write(responseBuffer) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(logger, err, "failed to write the reply message to mongo client") + errCh <- err + return nil + } + logger.Debug(fmt.Sprintf("the response in the mongo parser after passing to client: %v", len(responseBuffer))) + + if len(responseBuffer) == 0 { + logger.Debug("the response from the server is complete") + break + } + // decode the binary packet for response and return the values in the corresponding structs + _, respHeader, mongoResp, err := Decode(responseBuffer, logger) + if err != nil { + utils.LogError(logger, err, "failed to decode the mongo wire message from the destination server") + errCh <- err + return nil + } + if mongoRespVal, ok := mongoResp.(models.MongoOpMessage); ok && !hasSecondSetBit(mongoRespVal.FlagBits) { + logger.Debug("the response from the server is complete since the more_to_come flagbit is 0") + break + } + mongoResponses = append(mongoResponses, models.MongoResponse{ + Header: &respHeader, + Message: mongoResp, + ReadDelay: int64(readResponseDelay), + }) + } + } + + // write the response packet to the yaml file + m.recordMessage(ctx, logger, mongoRequests, mongoResponses, opReq, reqTimestampMock, mocks) + // assigns "read form client conn" to the reqBuf to read the next request packet from the client connection + reqBuf = []byte("read form client conn") + } + }) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + return nil + } + return err + } +} + +// getPacketLength returns the length of the packet from the first 4 bytes of the packet. +func getPacketLength(src []byte) (length int32) { + length = int32(src[0]) | int32(src[1])<<8 | int32(src[2])<<16 | int32(src[3])<<24 + return length +} diff --git a/keploy/pkg/core/proxy/integrations/mongo/match.go b/keploy/pkg/core/proxy/integrations/mongo/match.go new file mode 100644 index 0000000..3a2ccce --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mongo/match.go @@ -0,0 +1,244 @@ +//go:build linux + +package mongo + +import ( + "context" + "fmt" + "reflect" + "strings" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/utils" + "go.mongodb.org/mongo-driver/bson" + + "go.keploy.io/server/v2/pkg/models" + "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" + "go.uber.org/zap" +) + +// match mathces and returns the best matching mock for the incoming mongo requests. +func match(ctx context.Context, logger *zap.Logger, mongoRequests []models.MongoRequest, mockDb integrations.MockMemDb) (bool, *models.Mock, error) { + for { + select { + case <-ctx.Done(): + return false, nil, ctx.Err() + default: + mocks, err := mockDb.GetFilteredMocks() + if err != nil { + return false, nil, fmt.Errorf("error while getting tcs mock: %v", err) + } + var tcsMocks []*models.Mock + for _, mock := range mocks { + if mock.Kind != "Mongo" { + continue + } + tcsMocks = append(tcsMocks, mock) + } + maxMatchScore := 0.0 + bestMatchIndex := -1 + // iterate over the tcsMocks and compare the incoming mongo requests with the recorded mongo requests. + for tcsIndx, tcsMock := range tcsMocks { + if ctx.Err() != nil { + return false, nil, ctx.Err() + } + // check for the number of chunks in the incoming mongo requests and the recorded mongo requests. + if len(tcsMock.Spec.MongoRequests) == len(mongoRequests) { + for i, req := range tcsMock.Spec.MongoRequests { + if ctx.Err() != nil { + return false, nil, ctx.Err() + } + // check for the opcode of the incoming mongo requests and the recorded mongo requests. + if len(tcsMock.Spec.MongoRequests) != len(mongoRequests) || req.Header.Opcode != mongoRequests[i].Header.Opcode { + logger.Debug("the received request is not of same type with the tcmocks", zap.Any("at index", tcsIndx)) + continue + } + switch req.Header.Opcode { + case wiremessage.OpMsg: + if req.Message.(*models.MongoOpMessage).FlagBits != mongoRequests[i].Message.(*models.MongoOpMessage).FlagBits { + logger.Debug("the received request is not of same flagbit with the tcmocks", zap.Any("at index", tcsIndx)) + continue + } + scoreSum := 0.0 + for sectionIndx, section := range req.Message.(*models.MongoOpMessage).Sections { + if len(req.Message.(*models.MongoOpMessage).Sections) == len(mongoRequests[i].Message.(*models.MongoOpMessage).Sections) { + score := compareOpMsgSection(logger, section, mongoRequests[i].Message.(*models.MongoOpMessage).Sections[sectionIndx]) + scoreSum += score + } + } + currentScore := scoreSum / float64(len(mongoRequests)) + if currentScore > maxMatchScore { + maxMatchScore = currentScore + bestMatchIndex = tcsIndx + } + default: + utils.LogError(logger, nil, "the OpCode of the mongo wiremessage is invalid.", zap.Any("OpCode", req.Header.Opcode)) + } + } + } + } + if bestMatchIndex == -1 { + return false, nil, nil + } + mock := tcsMocks[bestMatchIndex] + isDeleted := mockDb.DeleteFilteredMock(*mock) + if !isDeleted { + continue + } + return true, mock, nil + } + } +} + +func compareOpMsgSection(logger *zap.Logger, expectedSection, actualSection string) float64 { + // check that the sections are of same type. SectionSingle (section[16] is "m") or SectionSequence (section[16] is "i"). + if (len(expectedSection) < 16 || len(actualSection) < 16) && expectedSection[16] != actualSection[16] { + return 0 + } + logger.Debug(fmt.Sprintf("the sections are. Expected: %v\n and actual: %v", expectedSection, actualSection)) + switch { + case strings.HasPrefix(expectedSection, "{ SectionSingle identifier:"): + var expectedIdentifier string + var expectedMsgsStr string + // // Define the regular expression pattern + // // Compile the regular expression + // // Find submatches using the regular expression + + expectedIdentifier, expectedMsgsStr, err := decodeOpMsgSectionSequence(expectedSection) + if err != nil { + logger.Debug(fmt.Sprintf("the section in mongo OpMsg wiremessage: %v", expectedSection)) + utils.LogError(logger, err, "failed to fetch the identifier/msgs from the section single of recorded OpMsg", zap.Any("identifier", expectedIdentifier)) + return 0 + } + + var actualIdentifier string + var actualMsgsStr string + // _, err = fmt.Sscanf(actualSection, "{ SectionSingle identifier: %s , msgs: [ %s ] }", &actualIdentifier, &actualMsgsStr) + actualIdentifier, actualMsgsStr, err = decodeOpMsgSectionSequence(actualSection) + if err != nil { + utils.LogError(logger, err, "failed to fetch the identifier/msgs from the section single of incoming OpMsg", zap.Any("identifier", actualIdentifier)) + return 0 + } + + // // Compile the regular expression + // // Find submatches using the regular expression + + logger.Debug("the expected section", zap.Any("identifier", expectedIdentifier), zap.Any("docs", expectedMsgsStr)) + logger.Debug("the actual section", zap.Any("identifier", actualIdentifier), zap.Any("docs", actualMsgsStr)) + + expectedMsgs := strings.Split(expectedMsgsStr, ", ") + actualMsgs := strings.Split(actualMsgsStr, ", ") + if len(expectedMsgs) != len(actualMsgs) || expectedIdentifier != actualIdentifier { + return 0 + } + score := 0.0 + for i := range expectedMsgs { + expected := map[string]interface{}{} + actual := map[string]interface{}{} + err := bson.UnmarshalExtJSON([]byte(expectedMsgs[i]), true, &expected) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the section of recorded request to bson document") + return 0 + } + err = bson.UnmarshalExtJSON([]byte(actualMsgs[i]), true, &actual) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the section of incoming request to bson document") + return 0 + } + score += calculateMatchingScore(expected, actual) + } + logger.Debug("the matching score for sectionSequence", zap.Any("", score)) + return score + case strings.HasPrefix(expectedSection, "{ SectionSingle msg:"): + var expectedMsgsStr string + expectedMsgsStr, err := extractSectionSingle(expectedSection) + if err != nil { + utils.LogError(logger, err, "failed to fetch the msgs from the single section of recorded OpMsg") + return 0 + } + // // Define the regular expression pattern + // // Compile the regular expression + // // Find submatches using the regular expression + + var actualMsgsStr string + actualMsgsStr, err = extractSectionSingle(actualSection) + if err != nil { + utils.LogError(logger, err, "failed to fetch the msgs from the single section of incoming OpMsg") + return 0 + } + // // Compile the regular expression + // // Find submatches using the regular expression + + expected := map[string]interface{}{} + actual := map[string]interface{}{} + + err = bson.UnmarshalExtJSON([]byte(expectedMsgsStr), true, &expected) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the section of recorded request to bson document") + return 0 + } + err = bson.UnmarshalExtJSON([]byte(actualMsgsStr), true, &actual) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the section of incoming request to bson document") + return 0 + } + logger.Debug("the expected and actual msg in the single section.", zap.Any("expected", expected), zap.Any("actual", actual), zap.Any("score", calculateMatchingScore(expected, actual))) + return calculateMatchingScore(expected, actual) + + default: + utils.LogError(logger, nil, "failed to detect the OpMsg section into mongo request wiremessage due to invalid format") + return 0 + } +} + +func calculateMatchingScore(obj1, obj2 map[string]interface{}) float64 { + totalFields := len(obj2) + if len(obj1) > totalFields { + totalFields = len(obj1) + } + matchingFields := 0.0 + + for key, value := range obj2 { + if obj1Value, ok := obj1[key]; ok { + if reflect.DeepEqual(value, obj1Value) { + matchingFields++ + } else if reflect.TypeOf(value) == reflect.TypeOf(obj1Value) { + if isNestedMap(value) { + if isNestedMap(obj1Value) { + matchingFields += calculateMatchingScore(obj1Value.(map[string]interface{}), value.(map[string]interface{})) + } + } else if isSlice(value) { + if isSlice(obj1Value) { + matchingFields += calculateMatchingScoreForSlices(obj1Value.([]interface{}), value.([]interface{})) + } + } + } + } + } + + return float64(matchingFields) / float64(totalFields) +} + +func calculateMatchingScoreForSlices(slice1, slice2 []interface{}) float64 { + matchingCount := 0 + + if len(slice1) == len(slice2) { + for indx2, item2 := range slice2 { + if len(slice1) > indx2 && reflect.DeepEqual(item2, slice1[indx2]) { + matchingCount++ + } + } + } + + return float64(matchingCount) / float64(len(slice2)) +} + +func isNestedMap(value interface{}) bool { + _, ok := value.(map[string]interface{}) + return ok +} + +func isSlice(value interface{}) bool { + _, ok := value.([]interface{}) + return ok +} diff --git a/keploy/pkg/core/proxy/integrations/mongo/mongo.go b/keploy/pkg/core/proxy/integrations/mongo/mongo.go new file mode 100644 index 0000000..0f6799a --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mongo/mongo.go @@ -0,0 +1,128 @@ +//go:build linux + +package mongo + +import ( + "context" + "encoding/binary" + "net" + "sync" + "time" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/utils" + + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +func init() { + integrations.Register(integrations.MONGO, &integrations.Parsers{ + Initializer: New, + Priority: 100, + }) +} + +type Mongo struct { + logger *zap.Logger + recordedConfigRequests sync.Map +} + +func New(logger *zap.Logger) integrations.Integrations { + return &Mongo{ + logger: logger, + recordedConfigRequests: sync.Map{}, + } +} + +// MatchType determines if the outgoing network call is Mongo by comparing the +// message format with that of a mongo wire message. +func (m *Mongo) MatchType(_ context.Context, buffer []byte) bool { + if len(buffer) < 4 { + return false + } + // identifies by the starting 4 bytes of the message, since that + // are the length of the message. + messageLength := binary.LittleEndian.Uint32(buffer[0:4]) + return int(messageLength) == len(buffer) +} + +// RecordOutgoing records the outgoing mongo messages of the client connection into the yaml file. +// The database connection is keep-alive so, this function will be called during the connection establishment. +func (m *Mongo) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + logger := m.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial mongo message") + return err + } + + // the mongo messages are converted to the yaml format. + // + // initially the reqBuf contains the first network packet + // from the client connection which is used to determine + // the packet type in MatchType. + err = m.encodeMongo(ctx, logger, reqBuf, src, dst, mocks, opts) + if err != nil { + utils.LogError(logger, err, "failed to encode the mongo message into the yaml") + return err + } + return nil +} + +// MockOutgoing reads the outgoing mongo requests of the client connection and +// mocks the responses from the yaml file. The database connection is keep-alive +func (m *Mongo) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { + // read the initial buffer from the client connection. Initially the + // reqBuf contains the first network packet from the client connection + // which is used to determine the packet type in MatchType. + logger := m.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial mongo message") + return err + } + + // converts the yaml string into the binary packet + err = decodeMongo(ctx, logger, reqBuf, src, dstCfg, mockDb, opts) + if err != nil { + utils.LogError(logger, err, "failed to decode the mongo message") + return err + } + return nil +} + +// recordMessage records the mongo messages into the yaml file. +func (m *Mongo) recordMessage(ctx context.Context, logger *zap.Logger, mongoRequests []models.MongoRequest, mongoResponses []models.MongoResponse, opReq Operation, reqTimestampMock time.Time, mocks chan<- *models.Mock) { + shouldRecordCalls := true // boolean to check for already saved config mocks + name := "mocks" + meta1 := map[string]string{ + "operation": opReq.String(), + "connID": ctx.Value(models.ClientConnectionIDKey).(string), + } + + // check that the packet is heartbeat or not. + // See: https://github.com/mongodb/mongo-go-driver/blob/8489898c64a2d8c2e2160006eb851a11a9db9e9d/x/mongo/driver/operation/hello.go#L503 + if isHeartBeat(logger, opReq, *mongoRequests[0].Header, mongoRequests[0].Message) { + meta1["type"] = "config" + } + // record the mongo messages + if shouldRecordCalls { + mongoMock := &models.Mock{ + Version: models.GetVersion(), + Kind: models.Mongo, + Name: name, + Spec: models.MockSpec{ + Metadata: meta1, + MongoRequests: mongoRequests, + MongoResponses: mongoResponses, + Created: time.Now().Unix(), + ReqTimestampMock: reqTimestampMock, + ResTimestampMock: time.Now(), + }, + } + // Save the mock + mocks <- mongoMock + } +} diff --git a/keploy/pkg/core/proxy/integrations/mongo/operation.go b/keploy/pkg/core/proxy/integrations/mongo/operation.go new file mode 100644 index 0000000..fe0522c --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mongo/operation.go @@ -0,0 +1,1575 @@ +//go:build linux + +package mongo + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "regexp" + "strconv" + "strings" + "time" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/scram" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" + "go.mongodb.org/mongo-driver/x/mongo/driver" + "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" + "go.uber.org/zap" +) + +type Message struct { + Wm []byte + Op Operation +} + +type TransactionDetails struct { + LsID []byte + TxnNumber int64 + IsStartTransaction bool +} + +type Operation interface { + fmt.Stringer + OpCode() wiremessage.OpCode + Encode(responseTo, requestID int32) []byte + IsIsMaster() bool + IsIsAdminDB() bool + CursorID() (cursorID int64, ok bool) + RequestID() int32 + Error() error + Unacknowledged() bool + CommandAndCollection() (Command, string) + TransactionDetails() *TransactionDetails +} + +// Decode decodes the wire message binary into the operation and the mongo message. +// +// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation.go#L1361-L1426 +func Decode(wm []byte, logger *zap.Logger) (Operation, models.MongoHeader, interface{}, error) { + wmLength := len(wm) + // the wiremessage is at least 16 bytes long + length, reqID, responseTo, opCode, wmBody, ok := wiremessage.ReadHeader(wm) + messageHeader := models.MongoHeader{ + Length: length, + RequestID: reqID, + ResponseTo: responseTo, + Opcode: wiremessage.OpCode(opCode), + } + logger.Debug(fmt.Sprintf("the mongo msg header: %v", messageHeader)) + if !ok || int(length) > wmLength { + return nil, messageHeader, &models.MongoOpMessage{}, errors.New("malformed wire message: insufficient bytes") + } + + var ( + op Operation + err error + mongoMsg interface{} + ) + + switch opCode { + case wiremessage.OpQuery: + // decodeQuery is a helper function to decode the OpQuery operation + op, err = decodeQuery(reqID, wmBody) + if err != nil { + return nil, messageHeader, mongoMsg, err + } + // marshal the query document to json + jsonBytes, err := bson.MarshalExtJSON(op.(*opQuery).query, true, false) + if err != nil { + return nil, messageHeader, &models.MongoOpMessage{}, fmt.Errorf("malformed bson document: %v", err.Error()) + } + jsonString := string(jsonBytes) + + mongoMsg = &models.MongoOpQuery{ + Flags: int32(op.(*opQuery).flags), + FullCollectionName: op.(*opQuery).fullCollectionName, + NumberToSkip: op.(*opQuery).numberToSkip, + NumberToReturn: op.(*opQuery).numberToReturn, + Query: jsonString, + ReturnFieldsSelector: op.(*opQuery).returnFieldsSelector.String(), + } + case wiremessage.OpMsg: + // decodeMsg is a helper function to decode the OpMsg operation + op, err = decodeMsg(reqID, wmBody, logger) + if err != nil { + return nil, messageHeader, mongoMsg, err + } + var sections []string + for _, section := range op.(*opMsg).sections { + sections = append(sections, section.String()) + } + mongoMsg = &models.MongoOpMessage{ + FlagBits: int(op.(*opMsg).flags), + Sections: sections, + Checksum: int(op.(*opMsg).checksum), + } + case wiremessage.OpReply: + // decodeReply is a helper function to decode the OpReply operation + op, err = decodeReply(reqID, wmBody) + if err != nil { + return nil, messageHeader, mongoMsg, err + } + var replyDocs []string + for _, v := range op.(*opReply).documents { + // marshal the reply document to json + jsonBytes, err := bson.MarshalExtJSON(v, true, false) + if err != nil { + return nil, messageHeader, &models.MongoOpMessage{}, fmt.Errorf("malformed bson document: %v", err.Error()) + } + jsonString := string(jsonBytes) + replyDocs = append(replyDocs, jsonString) + } + mongoMsg = &models.MongoOpReply{ + ResponseFlags: int32(op.(*opReply).flags), + CursorID: op.(*opReply).cursorID, + StartingFrom: op.(*opReply).startingFrom, + NumberReturned: op.(*opReply).numReturned, + Documents: replyDocs, + } + default: + op = &opUnknown{ + opCode: opCode, + reqID: reqID, + wm: wm, + } + } + if err != nil { + return nil, messageHeader, mongoMsg, err + } + logger.Debug(fmt.Sprintf("the decoded string for the wiremessage: %v", op.String())) + return op, messageHeader, mongoMsg, nil +} + +type opUnknown struct { + opCode wiremessage.OpCode + reqID int32 + wm []byte +} + +func (o *opUnknown) IsIsAdminDB() bool { + return false +} + +func (o *opUnknown) TransactionDetails() *TransactionDetails { + return nil +} + +func (o *opUnknown) OpCode() wiremessage.OpCode { + return o.opCode +} + +func (o *opUnknown) Encode(_, _ int32) []byte { + return o.wm +} + +func (o *opUnknown) IsIsMaster() bool { + return false +} + +func (o *opUnknown) CursorID() (cursorID int64, ok bool) { + return 0, false +} + +func (o *opUnknown) RequestID() int32 { + return o.reqID +} + +func (o *opUnknown) Error() error { + return nil +} + +func (o *opUnknown) Unacknowledged() bool { + return false +} + +func (o *opUnknown) CommandAndCollection() (Command, string) { + return Unknown, "" +} + +func (o *opUnknown) String() string { + return fmt.Sprintf("{ OpUnknown opCode: %d, wm: %s }", o.opCode, o.wm) +} + +// https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#wire-op-query +type opQuery struct { + reqID int32 + flags wiremessage.QueryFlag + fullCollectionName string + numberToSkip int32 + numberToReturn int32 + query bsoncore.Document + returnFieldsSelector bsoncore.Document +} + +func (q *opQuery) IsIsAdminDB() bool { + return false +} + +func (q *opQuery) TransactionDetails() *TransactionDetails { + return nil +} + +// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/topology/server_test.go#L968-L1003 +func decodeQuery(reqID int32, wm []byte) (*opQuery, error) { + var ok bool + q := opQuery{ + reqID: reqID, + } + + q.flags, wm, ok = wiremessage.ReadQueryFlags(wm) + if !ok { + return nil, errors.New("malformed query message: missing OP_QUERY flags") + } + + q.fullCollectionName, wm, ok = wiremessage.ReadQueryFullCollectionName(wm) + if !ok { + return nil, errors.New("malformed query message: full collection name") + } + + q.numberToSkip, wm, ok = wiremessage.ReadQueryNumberToSkip(wm) + if !ok { + return nil, errors.New("malformed query message: number to skip") + } + + q.numberToReturn, wm, ok = wiremessage.ReadQueryNumberToReturn(wm) + if !ok { + return nil, errors.New("malformed query message: number to return") + } + + q.query, wm, ok = wiremessage.ReadQueryQuery(wm) + if !ok { + return nil, errors.New("malformed query message: query document") + } + + if len(wm) > 0 { + q.returnFieldsSelector, _, ok = wiremessage.ReadQueryReturnFieldsSelector(wm) + if !ok { + return nil, errors.New("malformed query message: return fields selector") + } + } + + return &q, nil +} + +func (q *opQuery) OpCode() wiremessage.OpCode { + return wiremessage.OpQuery +} + +// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation_legacy.go#L179-L189 +func (q *opQuery) Encode(responseTo, _ int32) []byte { + var buffer []byte + idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpQuery) + buffer = wiremessage.AppendQueryFlags(buffer, q.flags) + buffer = wiremessage.AppendQueryFullCollectionName(buffer, q.fullCollectionName) + buffer = wiremessage.AppendQueryNumberToSkip(buffer, q.numberToSkip) + buffer = wiremessage.AppendQueryNumberToReturn(buffer, q.numberToReturn) + buffer = append(buffer, q.query...) + if len(q.returnFieldsSelector) != 0 { + // returnFieldsSelector is optional + buffer = append(buffer, q.returnFieldsSelector...) + } + buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) + return buffer +} + +func (q *opQuery) CursorID() (cursorID int64, ok bool) { + return q.query.Lookup("getMore").Int64OK() +} + +func (q *opQuery) RequestID() int32 { + return q.reqID +} + +func (q *opQuery) IsIsMaster() bool { + if q.fullCollectionName != "admin.$cmd" { + return false + } + return IsIsMasterDoc(q.query) +} + +func (q *opQuery) Error() error { + return nil +} + +func (q *opQuery) Unacknowledged() bool { + return false +} + +func (q *opQuery) CommandAndCollection() (Command, string) { + return Find, q.fullCollectionName +} + +func (q *opQuery) String() string { + return fmt.Sprintf("{ OpQuery flags: %s, fullCollectionName: %s, numberToSkip: %d, numberToReturn: %d, query: %s, returnFieldsSelector: %s }", q.flags.String(), q.fullCollectionName, q.numberToSkip, q.numberToReturn, q.query.String(), q.returnFieldsSelector.String()) +} + +// https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op-msg +type opMsg struct { + reqID int32 + flags wiremessage.MsgFlag + sections []opMsgSection + checksum uint32 + logger *zap.Logger +} + +type opMsgSection interface { + fmt.Stringer + cursorID() (cursorID int64, ok bool) + isIsMaster() bool + isDbAdmin() bool + append(buffer []byte) []byte + commandAndCollection() (Command, string) +} + +type opMsgSectionSingle struct { + msg bsoncore.Document +} + +func (o *opMsgSectionSingle) cursorID() (cursorID int64, ok bool) { + if getMore, ok := o.msg.Lookup("getMore").Int64OK(); ok { + return getMore, ok + } + return o.msg.Lookup("cursor", "id").Int64OK() +} + +func (o *opMsgSectionSingle) isIsMaster() bool { + if db, ok := o.msg.Lookup("$db").StringValueOK(); ok && db == "admin" { + return IsIsMasterDoc(o.msg) + } + return false +} + +func (o *opMsgSectionSingle) isDbAdmin() bool { + if db, ok := o.msg.Lookup("$db").StringValueOK(); ok && db == "admin" { + return true + } + return false +} + +func (o *opMsgSectionSingle) append(buffer []byte) []byte { + buffer = wiremessage.AppendMsgSectionType(buffer, wiremessage.SingleDocument) + return append(buffer, o.msg...) +} + +func (o *opMsgSectionSingle) commandAndCollection() (Command, string) { + return CommandAndCollection(o.msg) +} + +func (o *opMsgSectionSingle) String() string { + jsonBytes, err := bson.MarshalExtJSON(o.msg, true, false) + if err != nil { + return "" + } + jsonString := string(jsonBytes) + + return fmt.Sprintf("{ SectionSingle msg: %s }", jsonString) +} + +type opMsgSectionSequence struct { + identifier string + msgs []bsoncore.Document +} + +func (o *opMsgSectionSequence) cursorID() (cursorID int64, ok bool) { + // assume no cursor IDs are returned in OP_MSG document sequences + return 0, false +} + +func (o *opMsgSectionSequence) isIsMaster() bool { + return false +} +func (o *opMsgSectionSequence) isDbAdmin() bool { + res := true + for _, v := range o.msgs { + if db, ok := v.Lookup("$db").StringValueOK(); !ok || db != "admin" { + res = false + break + } + } + return res +} + +func (o *opMsgSectionSequence) append(buffer []byte) []byte { + buffer = wiremessage.AppendMsgSectionType(buffer, wiremessage.DocumentSequence) + + length := int32(len(o.identifier) + 5) + for _, msg := range o.msgs { + length += int32(len(msg)) + } + + buffer = appendi32(buffer, length) + buffer = appendCString(buffer, o.identifier) + for _, msg := range o.msgs { + buffer = append(buffer, msg...) + } + + return buffer +} + +func (o *opMsgSectionSequence) commandAndCollection() (Command, string) { + return Unknown, "" +} + +func (o *opMsgSectionSequence) String() string { + var msgs []string + for _, msg := range o.msgs { + jsonBytes, err := bson.MarshalExtJSON(msg, true, false) + if err != nil { + return "" + } + jsonString := string(jsonBytes) + msgs = append(msgs, jsonString) + } + return fmt.Sprintf("{ SectionSingle identifier: %s , msgs: [ %s ] }", o.identifier, strings.Join(msgs, ", ")) +} + +func decodeOpMsgSectionSequence(section string) (string, string, error) { + var identifier, message = "", "" + + // Define the regular expression pattern + pattern := `\{ SectionSingle identifier: (.+?) , msgs: \[ (.+?) \] \}` + + // Compile the regular expression + regex := regexp.MustCompile(pattern) + + // Find submatches using the regular expression + submatches := regex.FindStringSubmatch(section) + if submatches == nil || len(submatches) != 3 { + return identifier, message, errors.New("invalid format of message section sequence") + } + identifier = submatches[1] + message = submatches[2] + return identifier, message, nil + +} + +func extractSectionSingle(data string) (string, error) { + // Look for the prefix before the actual content + prefix := "{ SectionSingle msg: " + startIndex := strings.Index(data, prefix) + if startIndex == -1 { + return "", fmt.Errorf("start not found") + } + + // Adjust the start index to skip the prefix + startIndex += len(prefix) + + // We'll assume the content ends with " }" that closes the sectionSingle + endIndex := strings.LastIndex(data[startIndex:], " }") + if endIndex == -1 { + return "", fmt.Errorf("end not found") + } + + // Adjust the end index relative to the entire string + endIndex += startIndex + + // Extract the content between the start and end indexes + content := data[startIndex:endIndex] + + // Clean up the extracted content + content = strings.Trim(content, " ") + + return content, nil +} + +func processOpReply(ctx context.Context, expectedRequest, actualRequest models.MongoRequest, expectedResponse *models.MongoOpReply, mongoPassword string, logger *zap.Logger) (string, bool) { + if len(expectedResponse.Documents) == 0 { + return "", false + } + + for _, document := range expectedResponse.Documents { + var responseMsg map[string]interface{} + err := json.Unmarshal([]byte(document), &responseMsg) + if err != nil { + logger.Error("Failed to unmarshal JSON document of OpReply", zap.Error(err)) + return "", false + } + + // Extract and decode the payload from the actual MongoDB request + responseMsgData, ok := responseMsg["speculativeAuthenticate"].(map[string]interface{}) + if !ok { + _, ok = responseMsg["payload"].(map[string]interface{}) + if !ok { + logger.Debug("failed to extract payload from response data", zap.Any("responseMsg", responseMsg)) + continue + } + responseMsgData = responseMsg + } + + // Extract and decode the expected MongoDB request payload + expectedrequestPayload := expectedRequest.Message.(*models.MongoOpQuery).Query // Assuming the query is the payload + var expectedrequestPayloadMap map[string]interface{} + err = json.Unmarshal([]byte(expectedrequestPayload), &expectedrequestPayloadMap) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal request payload into map") + return "", false + } + expectedRequest, ok := expectedrequestPayloadMap["speculativeAuthenticate"].(map[string]interface{}) + if !ok { + _, ok = expectedrequestPayloadMap["payload"].(map[string]interface{}) + if !ok { + logger.Debug("failed to extract payload from response data", zap.Any("responseMsg", responseMsg)) + continue + } + expectedRequest = expectedrequestPayloadMap + } + + actualRequestPayload := actualRequest.Message.(*models.MongoOpQuery).Query // Assuming the query is the payload + var actualRequestPayloadMap map[string]interface{} + err = json.Unmarshal([]byte(actualRequestPayload), &actualRequestPayloadMap) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal request payload into map") + return "", false + } + + actualRequest, ok := actualRequestPayloadMap["speculativeAuthenticate"].(map[string]interface{}) + if !ok { + _, ok = actualRequestPayloadMap["payload"].(map[string]interface{}) + if !ok { + logger.Debug("failed to extract payload from response data", zap.Any("responseMsg", responseMsg)) + continue + } + actualRequest = actualRequestPayloadMap + } + + // TODO should move this to scram related file + + _, ok = actualRequest["saslStart"] + if !ok { + logger.Debug("failed to extract saslStart from response data", zap.Any("responseMsgData", responseMsgData)) + } else { + resPayload, err := extractAuthPayload(responseMsgData) + if err != nil { + logger.Debug("Failed to fetch the payload from the received MongoDB response", zap.Error(err)) + continue + } + logger.Debug(fmt.Sprintf("Payload of the received response: %s", resPayload)) + + decodedResPayload, err := decodeBase64Str(resPayload) + if err != nil { + logger.Error("Error decoding the received payload base64 string", zap.Error(err)) + return "", false + } + logger.Debug(fmt.Sprintf("Decoded payload of the actual for the SASLStart: %s", string(decodedResPayload))) + + expectedPayload, err := extractAuthPayload(expectedRequest) + if err != nil { + logger.Debug("Failed to fetch the payload from the expected MongoDB request", zap.Error(err)) + continue + } + logger.Debug(fmt.Sprintf("Payload of the expected request: %s", expectedPayload)) + + decodedExpPayload, err := decodeBase64Str(expectedPayload) + if err != nil { + logger.Error("Error decoding the expected request payload base64 string", zap.Error(err)) + return "", false + } + logger.Debug(fmt.Sprintf("Decoded payload of the expected for the SASLStart: %s", string(decodedExpPayload))) + + actualReqPayload, err := extractAuthPayload(actualRequest) + if err != nil { + logger.Debug("Failed to extract the payload from the actual MongoDB request", zap.Error(err)) + continue + } + // Extract and decode the payload from the actual MongoDB request + decodedReqPayload, err := decodeBase64Str(actualReqPayload) + if err != nil { + logger.Error("Failed to fetch the payload from the actual MongoDB request", zap.Error(err)) + return "", false + } + logger.Debug(fmt.Sprintf("Payload of the actual request: %s", decodedReqPayload)) + + newFirstAuthResponse, err := scram.GenerateServerFirstMessage(decodedExpPayload, decodedReqPayload, decodedResPayload, logger) + if err != nil { + return "", false + } + logger.Debug("After replacing the new client nonce in auth response", zap.String("first response", newFirstAuthResponse)) + + conversationID, err := extractConversationID(responseMsgData) + if err != nil { + logger.Error("Failed to fetch the conversationId for the SCRAM auth from the recorded first response", zap.Error(err)) + return "", false + } + // Generate the auth message from the received first request and recorded first response + authMessage := scram.GenerateAuthMessage(string(decodedReqPayload), newFirstAuthResponse, logger) + if authMessage == "" { + err := errors.New("failed to generate auth message") + logger.Error("Auth message generation failed", zap.Error(err)) + return "", false + } + authMechanism, ok := actualRequest["mechanism"].(string) + if !ok { + logger.Debug("failed to auth mechanism from expected request data", zap.Any("expectedRequest", actualRequest)) + continue + } + if authMechanism != util.SCRAM_SHA_1 && authMechanism != util.SCRAM_SHA_256 { + logger.Error("Invalid authentication mechanism", zap.String("authMechanism", authMechanism)) + return "", false + } + authMessage = authMessage + ",auth=" + authMechanism + logger.Debug("the auth message for the SCRAM saslstart authentication", zap.String("conversation-id", conversationID), zap.String("authMessage", authMessage)) + + connID := ctx.Value(models.ClientConnectionIDKey).(string) + authMessageMap.Store(connID+"+"+conversationID, authMessage) + // Marshal the new first response for the SCRAM authentication + authResponse := base64.StdEncoding.EncodeToString([]byte(newFirstAuthResponse)) + if authResponse != "" { + return authResponse, true + } + + } + + _, ok = actualRequest["saslContinue"] + if !ok { + logger.Debug("failed to extract saslContinue from response data", zap.Any("responseMsgData", responseMsgData)) + } else { + responsePayload, err := extractAuthPayload(responseMsgData) + if err != nil { + utils.LogError(logger, err, "failed to fetch the payload from the recorded mongo response") + return "", false + } + logger.Debug(fmt.Sprint("the payload of the recorded second response of SCRAM: ", responsePayload)) + + decodedResponsePayload, err := decodeBase64Str(responsePayload) + if err != nil { + utils.LogError(logger, err, "Error decoding the recorded saslContinue response payload base64 string") + return "", false + } + logger.Debug(fmt.Sprint("the decoded payload of the repsonse for the saslContinue: ", (string)(decodedResponsePayload))) + + fields := strings.Split(string(decodedResponsePayload), ",") + verifier, err := parseFieldBase64(fields[0], "v") + if err != nil { + logger.Debug("failed to parse the verifier of final response message", zap.Any("parsing error", err.Error())) + return "", false + } + logger.Debug("the recorded verifier of the auth request", zap.Any("verifier/server-signature", string(verifier))) + + // fetch the conversation id + conversationID, err := extractConversationID(actualRequest) + if err != nil { + utils.LogError(logger, err, "failed to fetch the conversationId for the SCRAM auth from the received final response") + return "", false + } + logger.Debug("fetched conversationId for the SCRAM authentication", zap.String("cid", conversationID)) + + salt := "" + itr := 0 + authType := "" + // get the authMessage from the saslStart conversation. Since, saslContinue have the same conversationId + // authMsg := authMessageMap[conversationID] + authMessage, ok := authMessageMap.Load(conversationID) + authMessageStr := "" + if ok { + authMessageStr = authMessage.(string) + } + + // get the salt and iteration from the authMessage to generate salted password + fields = strings.Split(authMessageStr, ",") + filteredFields := []string{} + for _, part := range fields { + if strings.HasPrefix(part, "s=") { + // Split based on "=" and get the value of "s" + saltByt, err := decodeBase64Str(strings.TrimPrefix(part, "s=")) + if err != nil { + utils.LogError(logger, err, "failed to decode the base64 string of salt") + return "", false + } + salt = string(saltByt) + } + if strings.HasPrefix(part, "i=") { + // Split based on "=" and get the value of "i" + itr, err = strconv.Atoi(strings.Split(part, "=")[1]) + if err != nil { + utils.LogError(logger, err, "failed to convert the string into integer") + return "", false + } + } + if strings.HasPrefix(part, "auth=") { + // Only add fields that are not prefixed with "auth=" + authType = strings.Split(part, "=")[1] + if err != nil { + utils.LogError(logger, err, "failed to convert the string into integer") + return "", false + } + } else { + filteredFields = append(filteredFields, part) + } + } + authMessageStr = strings.Join(filteredFields, ",") + // Since, the server proof is the signature generated by the authMessage and salted password. + // So, need to return the new server proof according to the new authMessage which is different from the recorded. + newVerifier, err := scram.GenerateServerFinalMessage(authMessageStr, authType, mongoPassword, salt, itr, logger) + if err != nil { + utils.LogError(logger, err, "failed to get the new server proof") + return "", false + } + return base64.StdEncoding.EncodeToString([]byte("v=" + newVerifier)), true + } + + } + + return "", false +} + +// encodeOpMsg encodes the OpMsg value into a mongo wire message. +func encodeOpMsg(ctx context.Context, responseOpMsg *models.MongoOpMessage, actualRequestMsgSections []string, expectedRequestMsgSections []string, mongoPassword string, logger *zap.Logger) (*opMsg, error) { + message := &opMsg{ + flags: wiremessage.MsgFlag(responseOpMsg.FlagBits), + checksum: uint32(responseOpMsg.Checksum), + sections: []opMsgSection{}, + logger: logger, + } + for messageIndex, messageValue := range responseOpMsg.Sections { + switch { + case strings.HasPrefix(messageValue, "{ SectionSingle identifier:"): + identifier, msgsStr, err := decodeOpMsgSectionSequence(responseOpMsg.Sections[messageIndex]) + if err != nil { + utils.LogError(logger, err, "failed to extract the msg section from recorded message") + return nil, err + } + msgs := strings.Split(msgsStr, ", ") + docs := []bsoncore.Document{} + for _, msg := range msgs { + var unmarshaledDoc bsoncore.Document + err = bson.UnmarshalExtJSON([]byte(msg), true, &unmarshaledDoc) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the recorded document string of OpMsg") + return nil, err + } + docs = append(docs, unmarshaledDoc) + } + message.sections = append(message.sections, &opMsgSectionSequence{ + identifier: identifier, + msgs: docs, + }) + case strings.HasPrefix(messageValue, "{ SectionSingle msg:"): + sectionStr, err := extractSectionSingle(responseOpMsg.Sections[messageIndex]) + if err != nil { + utils.LogError(logger, err, "failed to extract the msg section from recorded message single section") + return nil, err + } + resultStr, ok, err := handleScramAuth(ctx, actualRequestMsgSections, expectedRequestMsgSections, sectionStr, mongoPassword, logger) + if err != nil { + return nil, err + } + if ok { + logger.Debug("new responses have been generated for the scram authentication", zap.Any("response", resultStr)) + sectionStr = resultStr + } + + var unmarshaledDoc bsoncore.Document + + err = bson.UnmarshalExtJSON([]byte(sectionStr), true, &unmarshaledDoc) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the recorded document string of OpMsg") + return nil, err + } + message.sections = append(message.sections, &opMsgSectionSingle{ + msg: unmarshaledDoc, + }) + default: + utils.LogError(logger, nil, "failed to encode the OpMsg section into mongo wiremessage because of invalid format", zap.Any("section", messageValue)) + } + } + return message, nil +} + +// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation.go#L1387-L1423 +func decodeMsg(reqID int32, wm []byte, logger *zap.Logger) (*opMsg, error) { + var ok bool + m := opMsg{ + reqID: reqID, + logger: logger, + } + + m.flags, wm, ok = wiremessage.ReadMsgFlags(wm) + if !ok { + return nil, errors.New("malformed wire message: missing OP_MSG flags") + } + + checksumPresent := m.flags&wiremessage.ChecksumPresent == wiremessage.ChecksumPresent + for len(wm) > 0 { + // If the checksumPresent flag is set, the last four bytes of the message contain the checksum. + if checksumPresent && len(wm) == 4 { + m.checksum, wm, ok = wiremessage.ReadMsgChecksum(wm) + if !ok { + return nil, errors.New("malformed wire message: insufficient bytes to read checksum") + } + continue + } + + var stype wiremessage.SectionType + stype, wm, ok = wiremessage.ReadMsgSectionType(wm) + if !ok { + return nil, errors.New("malformed wire message: insufficient bytes to read section type") + } + + switch stype { + case wiremessage.SingleDocument: + s := opMsgSectionSingle{} + s.msg, wm, ok = wiremessage.ReadMsgSectionSingleDocument(wm) + if !ok { + return nil, errors.New("malformed wire message: insufficient bytes to read single document") + } + m.sections = append(m.sections, &s) + case wiremessage.DocumentSequence: + s := opMsgSectionSequence{} + s.identifier, s.msgs, wm, ok = wiremessage.ReadMsgSectionDocumentSequence(wm) + if !ok { + return nil, errors.New("malformed wire message: insufficient bytes to read document sequence") + } + m.sections = append(m.sections, &s) + default: + return nil, fmt.Errorf("malformed wire message: unknown section type %v", stype) + } + } + + return &m, nil +} + +func (m *opMsg) OpCode() wiremessage.OpCode { + return wiremessage.OpMsg +} + +// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation.go#L898-L904 +func (m *opMsg) Encode(responseTo, requestID int32) []byte { + var buffer []byte + m.logger.Debug(fmt.Sprintf("the responseTo for the OpMsg: %v, for requestId: %v", responseTo, wiremessage.NextRequestID())) + + idx, buffer := wiremessage.AppendHeaderStart(buffer, requestID, responseTo, wiremessage.OpMsg) + buffer = wiremessage.AppendMsgFlags(buffer, m.flags) + for _, section := range m.sections { + buffer = section.append(buffer) + } + if m.flags&wiremessage.ChecksumPresent == wiremessage.ChecksumPresent { + // The checksum is a uint32, but we can use appendi32 to encode it. Overflow/underflow when casting to int32 is + // not a concern here because the bytes in the number do not change after casting. + buffer = appendi32(buffer, int32(m.checksum)) + } + buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) + m.logger.Debug(fmt.Sprintf("opmsg string: %v", m.String())) + return buffer +} + +func (m *opMsg) IsIsMaster() bool { + for _, section := range m.sections { + if section.isIsMaster() { + return true + } + } + return false +} + +func (m *opMsg) IsIsAdminDB() bool { + for _, section := range m.sections { + if section.isDbAdmin() { + return true + } + } + return false +} + +func (m *opMsg) CursorID() (cursorID int64, ok bool) { + for _, section := range m.sections { + if cursorID, ok := section.cursorID(); ok { + return cursorID, ok + } + } + return 0, false +} + +func (m *opMsg) RequestID() int32 { + return m.reqID +} + +func (m *opMsg) Error() error { + if len(m.sections) == 0 { + return nil + } + single, ok := m.sections[0].(*opMsgSectionSingle) + if !ok { + return nil + } + return driver.ExtractErrorFromServerResponse(single.msg) +} + +func (m *opMsg) Unacknowledged() bool { + return m.flags&wiremessage.MoreToCome == wiremessage.MoreToCome +} + +func (m *opMsg) CommandAndCollection() (Command, string) { + for _, section := range m.sections { + command, collection := section.commandAndCollection() + if command != Unknown { + return command, collection + } + } + return Unknown, "" +} + +// TransactionDetails See https://github.com/mongodb/specifications/blob/master/source/transactions/transactions.rst +// Version 4.0 of the server introduces multi-statement transactions. +// opMsg is available from wire protocol 3.6 +// deprecated operations such OP_UPDATE OP_INSERT are not supposed to support transaction statements. +// When constructing any other command within a transaction, drivers MUST add the lsid, txnNumber, and autocommit fields. +func (m *opMsg) TransactionDetails() *TransactionDetails { + + for _, section := range m.sections { + + if single, ok := section.(*opMsgSectionSingle); ok { + _, lsID, ok := single.msg.Lookup("lsid", "id").BinaryOK() + if !ok { + continue + } + + txnNumber, ok := single.msg.Lookup("txnNumber").Int64OK() + if !ok { + continue + } + + _, ok = single.msg.Lookup("autocommit").BooleanOK() + if !ok { + continue + } + + startTransaction, ok := single.msg.Lookup("startTransaction").BooleanOK() + return &TransactionDetails{ + LsID: lsID, + TxnNumber: txnNumber, + IsStartTransaction: ok && startTransaction, + } + } + } + + return nil +} + +func (m *opMsg) GetFlagBits() int32 { + return int32(m.flags) +} + +func (m *opMsg) String() string { + var sections []string + for _, section := range m.sections { + sections = append(sections, section.String()) + } + return fmt.Sprintf("{ OpMsg flags: %d, sections: [%s], checksum: %d }", m.flags, strings.Join(sections, ", "), m.checksum) +} + +// https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op-reply +type opReply struct { + reqID int32 + flags wiremessage.ReplyFlag + cursorID int64 + startingFrom int32 + numReturned int32 + documents []bsoncore.Document +} + +func (r *opReply) TransactionDetails() *TransactionDetails { + return nil +} + +func encodeOpReply(ctx context.Context, actualRequest models.MongoRequest, expectedRequest models.MongoRequest, expectedResponse *models.MongoOpReply, mongoPassword string, logger *zap.Logger) (*opReply, error) { + + replyDocs := []bsoncore.Document{} + updatedFirstResponse, isResponseUpdated := processOpReply(ctx, expectedRequest, actualRequest, expectedResponse, mongoPassword, logger) + for _, v := range expectedResponse.Documents { + var unmarshaledDoc bsoncore.Document + logger.Debug(fmt.Sprintf("the document string is: %v", string(v))) + var result map[string]interface{} + + err := json.Unmarshal([]byte(v), &result) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal string document of OpReply") + return nil, err + } + // set the fields for handshake calls at test mode + localTime, ok := result["localTime"].(map[string]interface{}) + if ok { + localTime["$date"].(map[string]interface{})["$numberLong"] = strconv.FormatInt(time.Now().Unix(), 10) + logger.Debug(fmt.Sprintf("the updated document string is: %v", result["localTime"].(map[string]interface{})["$date"].(map[string]interface{})["$numberLong"])) + } + if isResponseUpdated { + // add checks + query, ok := result["speculativeAuthenticate"].(map[string]interface{}) + if !ok { + logger.Debug("failed to extract payload from response data", zap.Any("responseMsg", result)) + payload, ok := result["payload"].(map[string]interface{}) + if !ok { + logger.Debug("failed to extract payload from response data", zap.Any("responseMsg", result)) + continue + } + payload["$binary"].(map[string]interface{})["base64"] = updatedFirstResponse + } else { + query["payload"].(map[string]interface{})["$binary"].(map[string]interface{})["base64"] = updatedFirstResponse + } + } + v, err := json.Marshal(result) + if err != nil { + utils.LogError(logger, err, "failed to marshal the updated string document of OpReply") + return nil, err + } + err = bson.UnmarshalExtJSON([]byte(v), false, &unmarshaledDoc) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the updated document string of OpReply") + return nil, err + } + elements, _ := unmarshaledDoc.Elements() + logger.Debug(fmt.Sprintf("the elements of the reply docs: %v", elements)) + replyDocs = append(replyDocs, unmarshaledDoc) + + } + + return &opReply{ + flags: wiremessage.ReplyFlag(expectedResponse.ResponseFlags), + cursorID: expectedResponse.CursorID, + startingFrom: expectedResponse.StartingFrom, + numReturned: expectedResponse.NumberReturned, + documents: replyDocs, + }, nil +} + +// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation.go#L1297-L1358 +func decodeReply(reqID int32, wm []byte) (*opReply, error) { + var ok bool + r := opReply{ + reqID: reqID, + } + + r.flags, wm, ok = wiremessage.ReadReplyFlags(wm) + if !ok { + return nil, errors.New("malformed reply message: missing OP_REPLY flags") + } + + r.cursorID, wm, ok = wiremessage.ReadReplyCursorID(wm) + if !ok { + return nil, errors.New("malformed reply message: cursor id") + } + + r.startingFrom, wm, ok = wiremessage.ReadReplyStartingFrom(wm) + if !ok { + return nil, errors.New("malformed reply message: starting from") + } + + r.numReturned, wm, ok = wiremessage.ReadReplyNumberReturned(wm) + if !ok { + return nil, errors.New("malformed reply message: number returned") + } + + r.documents, _, ok = wiremessage.ReadReplyDocuments(wm) + if !ok { + return nil, errors.New("malformed reply message: could not read documents from reply") + } + + return &r, nil +} + +func (r *opReply) OpCode() wiremessage.OpCode { + return wiremessage.OpReply +} + +// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/drivertest/channel_conn.go#L73-L82 +func (r *opReply) Encode(responseTo, requestID int32) []byte { + var buffer []byte + idx, buffer := wiremessage.AppendHeaderStart(buffer, requestID, responseTo, wiremessage.OpReply) + buffer = wiremessage.AppendReplyFlags(buffer, r.flags) + buffer = wiremessage.AppendReplyCursorID(buffer, r.cursorID) + buffer = wiremessage.AppendReplyStartingFrom(buffer, r.startingFrom) + buffer = wiremessage.AppendReplyNumberReturned(buffer, r.numReturned) + for _, doc := range r.documents { + buffer = append(buffer, doc...) + } + buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) + return buffer +} + +func (r *opReply) IsIsMaster() bool { + return false +} + +func (r *opReply) IsIsAdminDB() bool { + return false +} + +func (r *opReply) CursorID() (cursorID int64, ok bool) { + return r.cursorID, true +} + +func (r *opReply) RequestID() int32 { + return r.reqID +} + +func (r *opReply) Error() error { + if len(r.documents) == 0 { + return nil + } + return driver.ExtractErrorFromServerResponse(r.documents[0]) +} + +func (r *opReply) Unacknowledged() bool { + return false +} + +func (r *opReply) CommandAndCollection() (Command, string) { + return Find, "" +} + +func (r *opReply) String() string { + var documents []string + for _, document := range r.documents { + documents = append(documents, document.String()) + } + return fmt.Sprintf("{ OpReply flags: %d, cursorID: %d, startingFrom: %d, numReturned: %d, documents: [%s] }", r.flags, r.cursorID, r.startingFrom, r.numReturned, strings.Join(documents, ", ")) +} + +// https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op-get-more +// type opGetMore struct { +// reqID int32 +// fullCollectionName string +// numberToReturn int32 +// cursorID int64 +// } + +// func (g *opGetMore) TransactionDetails() *TransactionDetails { +// return nil +// } + +// // see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation.go#L1297-L1358 +// func decodeGetMore(reqID int32, wm []byte) (*opGetMore, error) { +// var ok bool +// g := opGetMore{ +// reqID: reqID, +// } + +// // the driver doesn't support any ReadGetMore* methods, so reuse methods from other operations + +// _, wm, ok = wiremessage.ReadKillCursorsZero(wm) +// if !ok { +// return nil, errors.New("malformed get_more message: missing zero") +// } + +// g.fullCollectionName, wm, ok = wiremessage.ReadQueryFullCollectionName(wm) +// if !ok { +// return nil, errors.New("malformed get_more message: missing full collection name") +// } + +// g.numberToReturn, wm, ok = wiremessage.ReadQueryNumberToReturn(wm) +// if !ok { +// return nil, errors.New("malformed get_more message: missing number to return") +// } + +// g.cursorID, _, ok = wiremessage.ReadReplyCursorID(wm) +// if !ok { +// return nil, errors.New("malformed get_more message: missing cursorID") +// } + +// return &g, nil +// } + +// func (g *opGetMore) OpCode() wiremessage.OpCode { +// return wiremessage.OpGetMore +// } + +// // see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation_legacy.go#L284-L291 +// func (g *opGetMore) Encode(responseTo, requestId int32) []byte { +// var buffer []byte +// idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpGetMore) +// buffer = wiremessage.AppendGetMoreZero(buffer) +// buffer = wiremessage.AppendGetMoreFullCollectionName(buffer, g.fullCollectionName) +// buffer = wiremessage.AppendGetMoreNumberToReturn(buffer, g.numberToReturn) +// buffer = wiremessage.AppendGetMoreCursorID(buffer, g.cursorID) +// buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) +// return buffer +// } + +// func (g *opGetMore) IsIsMaster() bool { +// return false +// } + +// func (g *opGetMore) CursorID() (cursorID int64, ok bool) { +// return g.cursorID, true +// } + +// func (g *opGetMore) RequestID() int32 { +// return g.reqID +// } + +// func (g *opGetMore) Error() error { +// return nil +// } + +// func (g *opGetMore) Unacknowledged() bool { +// return false +// } + +// func (g *opGetMore) CommandAndCollection() (Command, string) { +// return GetMore, g.fullCollectionName +// } + +// func (g *opGetMore) String() string { +// return fmt.Sprintf("{ OpGetMore fullCollectionName: %s, numberToReturn: %d, cursorID: %d }", g.fullCollectionName, g.numberToReturn, g.cursorID) +// } + +// // https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op_update +// type opUpdate struct { +// reqID int32 +// fullCollectionName string +// flags int32 +// selector bsoncore.Document +// tools bsoncore.Document +// } + +// func (u *opUpdate) TransactionDetails() *TransactionDetails { +// return nil +// } + +// func decodeUpdate(reqID int32, wm []byte) (*opUpdate, error) { +// var ok bool +// u := opUpdate{ +// reqID: reqID, +// } + +// u.fullCollectionName, wm, ok = readCString(wm) +// if !ok { +// return nil, errors.New("malformed tools message: full collection name") +// } + +// u.flags, wm, ok = readi32(wm) +// if !ok { +// return nil, errors.New("malformed tools message: missing OP_UPDATE flags") +// } + +// u.selector, wm, ok = bsoncore.ReadDocument(wm) +// if !ok { +// return nil, errors.New("malformed tools message: selector document") +// } + +// u.tools, _, ok = bsoncore.ReadDocument(wm) +// if !ok { +// return nil, errors.New("malformed tools message: tools document") +// } + +// return &u, nil +// } + +// func (u *opUpdate) OpCode() wiremessage.OpCode { +// return wiremessage.OpUpdate +// } + +// func (u *opUpdate) Encode(responseTo, requestId int32) []byte { +// var buffer []byte +// idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpUpdate) +// buffer = appendCString(buffer, u.fullCollectionName) +// buffer = appendi32(buffer, u.flags) +// buffer = append(buffer, u.selector...) +// buffer = append(buffer, u.tools...) +// buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) +// return buffer +// } + +// func (u *opUpdate) IsIsMaster() bool { +// return false +// } + +// func (u *opUpdate) CursorID() (cursorID int64, ok bool) { +// return 0, false +// } + +// func (u *opUpdate) RequestID() int32 { +// return u.reqID +// } + +// func (u *opUpdate) Error() error { +// return nil +// } + +// func (u *opUpdate) Unacknowledged() bool { +// return false +// } + +// func (u *opUpdate) CommandAndCollection() (Command, string) { +// return Update, u.fullCollectionName +// } + +// func (u *opUpdate) String() string { +// return fmt.Sprintf("{ OpQuery fullCollectionName: %s, flags: %d, selector: %s, tools: %s }", u.fullCollectionName, u.flags, u.selector.String(), u.tools.String()) +// } + +// // https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op_insert +// type opInsert struct { +// reqID int32 +// flags int32 +// fullCollectionName string +// documents []bsoncore.Document +// } + +// func (i *opInsert) TransactionDetails() *TransactionDetails { +// return nil +// } + +// func decodeInsert(reqID int32, wm []byte) (*opInsert, error) { +// var ok bool +// i := opInsert{ +// reqID: reqID, +// } + +// i.flags, wm, ok = readi32(wm) +// if !ok { +// return nil, errors.New("malformed insert message: missing OP_INSERT flags") +// } + +// i.fullCollectionName, wm, ok = readCString(wm) +// if !ok { +// return nil, errors.New("malformed insert message: full collection name") +// } + +// i.documents, _, ok = wiremessage.ReadReplyDocuments(wm) +// if !ok { +// return nil, errors.New("malformed insert message: could not read documents") +// } + +// return &i, nil +// } + +// func (i *opInsert) OpCode() wiremessage.OpCode { +// return wiremessage.OpInsert +// } + +// func (i *opInsert) Encode(responseTo, requestId int32) []byte { +// var buffer []byte +// idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpInsert) +// buffer = appendi32(buffer, i.flags) +// buffer = appendCString(buffer, i.fullCollectionName) +// for _, doc := range i.documents { +// buffer = append(buffer, doc...) +// } +// buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) +// return buffer +// } + +// func (i *opInsert) IsIsMaster() bool { +// return false +// } + +// func (i *opInsert) CursorID() (cursorID int64, ok bool) { +// return 0, false +// } + +// func (i *opInsert) RequestID() int32 { +// return i.reqID +// } + +// func (i *opInsert) Error() error { +// return nil +// } + +// func (i *opInsert) Unacknowledged() bool { +// return false +// } + +// func (i *opInsert) CommandAndCollection() (Command, string) { +// return Insert, i.fullCollectionName +// } + +// func (i *opInsert) String() string { +// var documents []string +// for _, document := range i.documents { +// documents = append(documents, document.String()) +// } +// return fmt.Sprintf("{ OpInsert flags: %d, fullCollectionName: %s, documents: %s }", i.flags, i.fullCollectionName, strings.Join(documents, ", ")) +// } + +// // https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op_insert +// type opDelete struct { +// reqID int32 +// fullCollectionName string +// flags int32 +// selector bsoncore.Document +// } + +// func (d *opDelete) TransactionDetails() *TransactionDetails { +// return nil +// } + +// func decodeDelete(reqID int32, wm []byte) (*opDelete, error) { +// var ok bool +// d := opDelete{ +// reqID: reqID, +// } + +// _, wm, ok = readi32(wm) +// if !ok { +// return nil, errors.New("malformed delete message: missing zero") +// } + +// d.fullCollectionName, wm, ok = readCString(wm) +// if !ok { +// return nil, errors.New("malformed delete message: full collection name") +// } + +// d.flags, wm, ok = readi32(wm) +// if !ok { +// return nil, errors.New("malformed delete message: missing OP_DELETE flags") +// } + +// d.selector, _, ok = bsoncore.ReadDocument(wm) +// if !ok { +// return nil, errors.New("malformed delete message: selector document") +// } + +// return &d, nil +// } + +// func (d *opDelete) OpCode() wiremessage.OpCode { +// return wiremessage.OpDelete +// } + +// func (d *opDelete) Encode(responseTo, requestId int32) []byte { +// var buffer []byte +// idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpDelete) +// buffer = appendCString(buffer, d.fullCollectionName) +// buffer = appendi32(buffer, d.flags) +// buffer = append(buffer, d.selector...) +// buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) +// return buffer +// } + +// func (d *opDelete) IsIsMaster() bool { +// return false +// } + +// func (d *opDelete) CursorID() (cursorID int64, ok bool) { +// return 0, false +// } + +// func (d *opDelete) RequestID() int32 { +// return d.reqID +// } + +// func (d *opDelete) Error() error { +// return nil +// } + +// func (d *opDelete) Unacknowledged() bool { +// return false +// } + +// func (d *opDelete) CommandAndCollection() (Command, string) { +// return Delete, d.fullCollectionName +// } + +// func (d *opDelete) String() string { +// return fmt.Sprintf("{ OpDelete fullCollectionName: %s, flags: %d, selector: %s }", d.fullCollectionName, d.flags, d.selector.String()) +// } + +// // https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op_kill_cursors +// type opKillCursors struct { +// reqID int32 +// cursorIDs []int64 +// } + +// func (k *opKillCursors) TransactionDetails() *TransactionDetails { +// return nil +// } + +// func decodeKillCursors(reqID int32, wm []byte) (*opKillCursors, error) { +// var ok bool +// k := opKillCursors{ +// reqID: reqID, +// } + +// _, wm, ok = wiremessage.ReadKillCursorsZero(wm) +// if !ok { +// return nil, errors.New("malformed kill_cursors message: missing zero") +// } + +// var numIDs int32 +// numIDs, wm, ok = wiremessage.ReadKillCursorsNumberIDs(wm) +// if !ok { +// return nil, errors.New("malformed kill_cursors message: missing number of cursor IDs") +// } + +// k.cursorIDs, _, ok = wiremessage.ReadKillCursorsCursorIDs(wm, numIDs) +// if !ok { +// return nil, errors.New("malformed kill_cursors message: missing cursor IDs") +// } + +// return &k, nil +// } + +// func (k *opKillCursors) OpCode() wiremessage.OpCode { +// return wiremessage.OpKillCursors +// } + +// // see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation_legacy.go#L378-L384 +// func (k *opKillCursors) Encode(responseTo, requestId int32) []byte { +// var buffer []byte +// idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpKillCursors) +// buffer = wiremessage.AppendKillCursorsZero(buffer) +// buffer = wiremessage.AppendKillCursorsNumberIDs(buffer, int32(len(k.cursorIDs))) +// buffer = wiremessage.AppendKillCursorsCursorIDs(buffer, k.cursorIDs) +// buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) +// return buffer +// } + +// func (k *opKillCursors) IsIsMaster() bool { +// return false +// } + +// func (k *opKillCursors) CursorID() (cursorID int64, ok bool) { +// return 0, false +// } + +// func (k *opKillCursors) RequestID() int32 { +// return k.reqID +// } + +// func (k *opKillCursors) Error() error { +// return nil +// } + +// func (k *opKillCursors) Unacknowledged() bool { +// return false +// } + +// func (k *opKillCursors) CommandAndCollection() (Command, string) { +// return Unknown, "" +// } + +// func (k *opKillCursors) String() string { +// return fmt.Sprintf("{ OpKillCursors cursorIDs: %v }", k.cursorIDs) +// } + +func appendi32(dst []byte, i32 int32) []byte { + return append(dst, byte(i32), byte(i32>>8), byte(i32>>16), byte(i32>>24)) +} + +func appendCString(b []byte, str string) []byte { + b = append(b, str...) + return append(b, 0x00) +} + +// func readi32(src []byte) (int32, []byte, bool) { +// if len(src) < 4 { +// return 0, src, false +// } + +// return int32(src[0]) | int32(src[1])<<8 | int32(src[2])<<16 | int32(src[3])<<24, src[4:], true +// } + +// func readCString(src []byte) (string, []byte, bool) { +// idx := bytes.IndexByte(src, 0x00) +// if idx < 0 { +// return "", src, false +// } +// return string(src[:idx]), src[idx+1:], true +// } diff --git a/keploy/pkg/core/proxy/integrations/mongo/scramAuth.go b/keploy/pkg/core/proxy/integrations/mongo/scramAuth.go new file mode 100644 index 0000000..160df01 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mongo/scramAuth.go @@ -0,0 +1,512 @@ +//go:build linux + +package mongo + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "sync" + + scramUtil "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.keploy.io/server/v2/pkg/models" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/scram" + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// // scramauth.go (or util.go) +// func isScramAuth(logger *zap.Logger, mongoReq interface{}) bool { +// switch r := mongoReq.(type) { + +// case *models.MongoOpMessage: +// // Reuse the old isScramAuthRequest that checks if the sections +// // have "saslStart"/"saslContinue". +// return isScramAuthRequest(r.Sections, logger) + +// case *models.MongoOpQuery: +// // If your driver is sending legacy OpQuery handshake, +// // you can parse r.Query (the JSON) to see if there's a "saslStart" or "saslContinue". +// if strings.Contains(r.Query, "saslStart") || strings.Contains(r.Query, "saslContinue") { +// logger.Info("the received request is saslStart or saslContinue", +// zap.Any("OpQuery", r.Query)) +// return true +// } + +// } +// return false +// } + +func isScramAuthRequest(actualRequestSections []string, logger *zap.Logger) bool { + // Iterate over each section in the actual request sections + for _, v := range actualRequestSections { + // Extract the message from the section + actualMsg, err := extractMsgFromSection(v) + if err != nil { + utils.LogError(logger, err, "failed to extract the section of the received mongo request message", zap.Any("the section", v)) + return false + } + + conversationID, _ := extractConversationID(actualMsg) + // Check if the message is for starting the SASL (authentication) process + if _, exists := actualMsg["saslStart"]; exists { + logger.Debug("the received request is saslStart", + zap.Any("OpMsg", actualMsg), + zap.Any("conversationId", conversationID)) + return true + // Check if the message is for final request of the SASL (authentication) process + } else if _, exists := actualMsg["saslContinue"]; exists { + logger.Debug("the received request is saslContinue", + zap.Any("OpMsg", actualMsg), + zap.Any("conversationId", conversationID), + ) + return true + } + + } + return false +} + +// authMessageMap stores the auth message from the saslStart request for the converstionIds. So, that +// it can be used in the saslContinue request to generate the new server proof +var authMessageMap = sync.Map{} + +// handleScramAuth handles the SCRAM authentication requests by generating the +// appropriate response string. +// +// Parameters: +// - actualRequestSections: The sections from the received request received. +// - expectedRequestSections: The sections that are recorded in the auth request. +// - responseSection: The section to be used for the response. +// - log: The logging instance for recording activities and errors. +// +// Returns: +// - The generated response string. +// - A boolean indicating if the processing was successful. +// - An error, if any, that occurred during processing. +func handleScramAuth(ctx context.Context, actualRequestSections, expectedRequestSections []string, responseSection, mongoPassword string, logger *zap.Logger) (string, bool, error) { + // Iterate over each section in the actual request sections + for i, v := range actualRequestSections { + // single document do not uses section sequence for SCRAM auth + if !strings.HasPrefix(v, "{ SectionSingle msg:") { + continue + } + + // Extract the message from the section + actualMsg, err := extractMsgFromSection(v) + if err != nil { + utils.LogError(logger, err, "failed to extract the section of the received mongo request message") + return "", false, err + } + + // Check if the message is for starting the SASL (authentication) process + if _, exists := actualMsg["saslStart"]; exists { + mechanism, exists := actualMsg["mechanism"] + // Check the authentication mechanism used and ensure it contains "SCRAM" + if mechanism, ok := mechanism.(string); exists && ok && strings.Contains(mechanism, "SCRAM") { + if _, exists := actualMsg["payload"]; exists { + return handleSaslStart(ctx, i, actualMsg, expectedRequestSections, responseSection, logger) + } + } + // Check if the message is for final request of the SASL (authentication) process + } else if _, exists := actualMsg["saslContinue"]; exists { + if _, exists := actualMsg["payload"]; exists { + return handleSaslContinue(ctx, actualMsg, responseSection, mongoPassword, logger) + } + } + } + return "", false, nil +} + +// extractAuthPayload extracts the base64 authentication payload from a given data structure. +// +// Parameters: +// - data: The interface{} that should represent a nested map with expected keys. +// +// Returns: +// - The extracted base64 string from the nested map structure. +// - An error if the data doesn't have the expected nested structure or if the expected keys are missing. +func extractAuthPayload(data interface{}) (string, error) { + // Top-level map + topMap, ok := data.(map[string]interface{}) + if !ok { + return "", errors.New("expected top-level data to be a map") + } + + // Payload map + payload, ok := topMap["payload"].(map[string]interface{}) + if !ok { + return "", errors.New("expected 'payload' to be a map") + } + + // $binary map + binaryMap, ok := payload["$binary"].(map[string]interface{}) + if !ok { + return "", errors.New("expected '$binary' to be a map") + } + + // Base64 string + base64Str, ok := binaryMap["base64"].(string) + if !ok { + return "", errors.New("expected 'base64' to be a string") + } + + return base64Str, nil +} + +// extractConversationID extracts the 'conversationId' from a given data structure. Example: {"conversationId":{"$numberInt":"113"}} +// +// Parameters: +// - data: The interface{} that should represent a map containing the key 'conversationId'. +// +// Returns: +// - The extracted conversationId as a string. +// - An error if the expected 'conversationId' structure isn't present or if other expected keys are missing. +func extractConversationID(data interface{}) (string, error) { + // Top-level map + topMap, ok := data.(map[string]interface{}) + if !ok { + return "", errors.New("expected top-level data to be a map") + } + + conversationID, exists := topMap["conversationId"] + if !exists { + return "", errors.New("'conversationId' not found") + } + + // conversationId map + conversationIDMap, ok := conversationID.(map[string]interface{}) + if !ok { + return "", errors.New("expected 'conversationId' to be a map") + } + + // Check presence of "$numberInt" + num, exists := conversationIDMap["$numberInt"] + if !exists { + return "", errors.New("'$numberInt' not found") + } + numberIntStr, present := num.(string) + if !present { + return "", errors.New("expected '$numberInt' to be a string") + } + + return numberIntStr, nil +} + +// updateConversationID updates the 'conversationId' in a given data structure. Example: {"conversationId":{"$numberInt":"113"}} +func updateConversationID(actualMsg map[string]interface{}, newConversationID int) (map[string]interface{}, error) { + // Check if conversationID exists and is a map + conversationID, exists := actualMsg["conversationId"] + if !exists { + return actualMsg, errors.New("'conversationId' not found") + } + + conversationIDMap, ok := conversationID.(map[string]interface{}) + if !ok { + return actualMsg, errors.New("expected 'conversationId' to be a map") + } + + // Update the "$numberInt" field with the new value + conversationIDMap["$numberInt"] = fmt.Sprintf("%d", newConversationID) + actualMsg["conversationId"] = conversationIDMap + return actualMsg, nil +} + +// decodeBase64Str is a function variable that wraps the standard Base64 decoding method, +// taking a Base64 encoded string and returning its decoded byte array and any error. +var decodeBase64Str = base64.StdEncoding.DecodeString + +// extractMsgFromSection decodes an OP_MSG section string, and then +// unmarshals the resulting string into a map. +// +// Parameters: +// - section: The OP_MSG section string to decode and unmarshal. +// +// Returns: +// - A map containing the key-value pairs from the unmarshaled section. +// - An error if there's an issue during decoding or unmarshaling. +func extractMsgFromSection(section string) (map[string]interface{}, error) { + var err error + var sectionStr string + var result map[string]interface{} + + if strings.HasPrefix(section, "{ SectionSingle msg:") { + sectionStr, err = extractSectionSingle(section) + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(sectionStr), &result) + if err != nil { + return nil, err + } + } + + return result, nil +} + +func handleSaslStart(ctx context.Context, i int, actualMsg map[string]interface{}, expectedRequestSections []string, responseSection string, logger *zap.Logger) (string, bool, error) { + actualReqPayload, err := extractAuthPayload(actualMsg) + if err != nil { + utils.LogError(logger, err, "failed to fetch the payload from the received mongo request") + return "", false, err + } + logger.Debug(fmt.Sprint("the payload of the received request: ", actualReqPayload)) + + // Decode the base64 encoded payload of the received mongo request + decodedActualReqPayload, err := decodeBase64Str(actualReqPayload) + if err != nil { + utils.LogError(logger, err, "Error decoding the received payload base64 string") + return "", false, err + } + logger.Debug(fmt.Sprint("the decoded payload of the actual for the saslstart: ", (string)(decodedActualReqPayload))) + + // check to ensure that the matched recorded mongo request contains the auth payload for SCRAM + if len(expectedRequestSections) < i+1 { + err = errors.New("unrecorded message sections for the received auth request") + utils.LogError(logger, err, "failed to match the message section payload") + return "", false, err + } + + expectedMsg, err := extractMsgFromSection(expectedRequestSections[i]) + if err != nil { + utils.LogError(logger, err, "failed to extract the section of the recorded mongo request message") + return "", false, err + } + + expectedReqPayload, err := extractAuthPayload(expectedMsg) + if err != nil { + utils.LogError(logger, err, "failed to fetch the payload from the recorded mongo request") + return "", false, err + } + logger.Debug(fmt.Sprint("the payload of the recorded request: ", expectedReqPayload)) + + // Decode the base64 encoded payload of the recorded mongo request + decodedExpectedReqPayload, err := decodeBase64Str(expectedReqPayload) + if err != nil { + utils.LogError(logger, err, "Error decoding the recorded request payload base64 string") + return "", false, err + } + logger.Debug(fmt.Sprint("the decoded payload of the expected for the saslstart: ", (string)(decodedExpectedReqPayload))) + + // the payload of the recorded first response of SCRAM authentication + var responseMsg map[string]interface{} + + err = json.Unmarshal([]byte(responseSection), &responseMsg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal string document of OpReply") + return "", false, err + } + responsePayload, err := extractAuthPayload(responseMsg) + if err != nil { + utils.LogError(logger, err, "failed to fetch the payload from the recorded mongo response") + return "", false, err + } + logger.Debug(fmt.Sprint("the payload of the recorded response: ", responsePayload)) + + // Decode the base64 encoded payload of the recorded mongo response + decodedResponsePayload, err := decodeBase64Str(responsePayload) + if err != nil { + utils.LogError(logger, err, "Error decoding the recorded response payload base64 string") + return "", false, err + } + logger.Debug(fmt.Sprint("the decoded payload of the repsonse for the saslstart: ", (string)(decodedResponsePayload))) + + // Generate the first response for the saslStart request by + // replacing the old client nonce with new client nonce + newFirstAuthResponse, err := scram.GenerateServerFirstMessage(decodedExpectedReqPayload, decodedActualReqPayload, decodedResponsePayload, logger) + if err != nil { + utils.LogError(logger, err, "failed to generate the new first response for the SCRAM authentication") + return "", false, err + } + logger.Debug("after replacing the new client nonce in auth response", zap.String("first response", newFirstAuthResponse)) + // replace the payload with new first response auth + responseMsg["payload"].(map[string]interface{})["$binary"].(map[string]interface{})["base64"] = base64.StdEncoding.EncodeToString([]byte(newFirstAuthResponse)) + responseMsg, err = updateConversationID(responseMsg, int(util.GetNextID())) + if err != nil { + utils.LogError(logger, err, "failed to update the conversationId in the sasl start auth message") + return "", false, err + } + + // fetch the conversation id + conversationID, err := extractConversationID(responseMsg) + if err != nil { + utils.LogError(logger, err, "failed to fetch the conversationId for the SCRAM auth from the recorded first response") + return "", false, err + } + logger.Debug("fetch the conversationId for the SCRAM authentication", zap.String("cid", conversationID)) + // generate the auth message from the received first request and recorded first response + authMessage := scram.GenerateAuthMessage(string(decodedActualReqPayload), newFirstAuthResponse, logger) + authMechanism, ok := actualMsg["mechanism"].(string) + if !ok { + logger.Debug("failed to auth mechanism from expected request data", zap.Any("expectedRequest", actualMsg)) + } else { + if authMechanism != scramUtil.SCRAM_SHA_1 && authMechanism != scramUtil.SCRAM_SHA_256 { + logger.Error("Invalid authentication mechanism", zap.String("authMechanism", authMechanism)) + return "", false, errors.New("invalid authentication mechanism") + } + + authMessage = authMessage + ",auth=" + authMechanism + // store the auth message in the global map for the conversationId + } + connID := ctx.Value(models.ClientConnectionIDKey).(string) + authMessageMap.Store(connID+"+"+conversationID, authMessage) + + logger.Debug("generate the new auth message for the received auth request", zap.String("msg", authMessage)) + + // marshal the new first response for the SCRAM authentication + newAuthResponse, err := json.Marshal(responseMsg) + if err != nil { + utils.LogError(logger, err, "failed to marshal the first auth response for SCRAM") + return "", false, err + } + return string(newAuthResponse), true, nil +} + +// handleSaslContinue processes a SASL continuation message, updates the payload with +// the new verifier, which is prepared by the new auth message. +// +// Parameters: +// - actualMsg: The actual message map from the client. +// - responseSection: The section string to be used for the response. +// - log: The logging instance for recording activities and errors. +// +// Returns: +// - The updated response section string. +// - A boolean indicating if the processing was successful. +// - An error, if any, that occurred during processing. +func handleSaslContinue(ctx context.Context, actualMsg map[string]interface{}, responseSection, mongoPassword string, logger *zap.Logger) (string, bool, error) { + var responseMsg map[string]interface{} + + err := json.Unmarshal([]byte(responseSection), &responseMsg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal string document of OpReply") + return "", false, err + } + logger.Debug(fmt.Sprintf("the recorded OpMsg section: %v", responseMsg)) + + responsePayload, err := extractAuthPayload(responseMsg) + if err != nil { + utils.LogError(logger, err, "failed to fetch the payload from the recorded mongo response") + return "", false, err + } + logger.Debug(fmt.Sprint("the payload of the recorded second response of SCRAM: ", responsePayload)) + + decodedResponsePayload, err := decodeBase64Str(responsePayload) + if err != nil { + utils.LogError(logger, err, "Error decoding the recorded saslContinue response payload base64 string") + return "", false, err + } + logger.Debug(fmt.Sprint("the decoded payload of the repsonse for the saslContinue: ", (string)(decodedResponsePayload))) + + fields := strings.Split(string(decodedResponsePayload), ",") + verifier, err := parseFieldBase64(fields[0], "v") + if err != nil { + logger.Debug("failed to parse the verifier of final response message", zap.Any("parsing error", err.Error())) + return "", false, nil + } + logger.Debug("the recorded verifier of the auth request", zap.Any("verifier/server-signature", string(verifier))) + + // fetch the conversation id + conversationID, err := extractConversationID(actualMsg) + if err != nil { + utils.LogError(logger, err, "failed to fetch the conversationId for the SCRAM auth from the received final response") + return "", false, err + } + logger.Debug("fetched conversationId for the SCRAM authentication", zap.String("cid", conversationID), zap.String("verifier", string(verifier))) + + salt := "" + itr := 0 + authType := "" + // get the authMessage from the saslStart conversation. Since, saslContinue have the same conversationId + // authMsg := authMessageMap[conversationID] + connID := ctx.Value(models.ClientConnectionIDKey).(string) + authMessage, ok := authMessageMap.Load(connID + "+" + conversationID) + authMessageStr := "" + if ok { + authMessageStr = authMessage.(string) + } + + logger.Debug("fetch the auth message for the SCRAM authentication", zap.String("cid", conversationID), zap.String("authMessage", authMessageStr)) + + // get the salt and iteration from the authMessage to generate salted password + fields = strings.Split(authMessageStr, ",") + filteredFields := []string{} + for _, part := range fields { + if strings.HasPrefix(part, "s=") { + // Split based on "=" and get the value of "s" + saltByt, err := decodeBase64Str(strings.TrimPrefix(part, "s=")) + if err != nil { + utils.LogError(logger, err, "failed to decode the base64 string of salt") + return "", false, err + } + salt = string(saltByt) + } + if strings.HasPrefix(part, "i=") { + // Split based on "=" and get the value of "i" + itr, err = strconv.Atoi(strings.Split(part, "=")[1]) + if err != nil { + utils.LogError(logger, err, "failed to convert the string into integer") + return "", false, err + } + } + if strings.HasPrefix(part, "auth=") { + // Only add fields that are not prefixed with "auth=" + authType = strings.Split(part, "=")[1] + if err != nil { + utils.LogError(logger, err, "failed to convert the string into integer") + return "", false, err + } + } else { + filteredFields = append(filteredFields, part) + } + } + authMessageStr = strings.Join(filteredFields, ",") + // Since, the server proof is the signature generated by the authMessage and salted password. + // So, need to return the new server proof according to the new authMessage which is different from the recorded. + newVerifier, err := scram.GenerateServerFinalMessage(authMessageStr, authType, mongoPassword, salt, itr, logger) + if err != nil { + utils.LogError(logger, err, "failed to get the new server proof") + return "", false, err + } + + // tools the payload of the mongo response for the authentication + responseMsg["payload"].(map[string]interface{})["$binary"].(map[string]interface{})["base64"] = base64.StdEncoding.EncodeToString([]byte("v=" + newVerifier)) + byt, err := json.Marshal(responseMsg) + if err != nil { + utils.LogError(logger, err, "failed to marshal the updated string document of OpReply") + return "", false, err + } + responseSection = string(byt) + return responseSection, true, nil +} + +func parseField(s, k string) (string, error) { + t := strings.TrimPrefix(s, k+"=") + if t == s { + return "", fmt.Errorf("error parsing '%s' for field '%s'", s, k) + } + return t, nil +} + +func parseFieldBase64(s, k string) ([]byte, error) { + if !strings.Contains(s, k+"=") { + return nil, fmt.Errorf("verifier doesn't exist in string '%s'", s) + } + raw, err := parseField(s, k) + if err != nil { + return nil, err + } + + dec, err := decodeBase64Str(raw) + if err != nil { + return nil, err + } + + return dec, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mongo/util.go b/keploy/pkg/core/proxy/integrations/mongo/util.go new file mode 100644 index 0000000..31ca69a --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mongo/util.go @@ -0,0 +1,36 @@ +//go:build linux + +package mongo + +import ( + "strings" + + "go.keploy.io/server/v2/pkg/models" + "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" + "go.uber.org/zap" +) + +func hasSecondSetBit(num int) bool { + // Shift the number right by 1 bit and check if the least significant bit is set + return (num>>1)&1 == 1 +} + +// Skip heartbeat from capturing in the global set of mocks. Since, the heartbeat packet always contain the "hello" boolean. +// See: https://github.com/mongodb/mongo-go-driver/blob/8489898c64a2d8c2e2160006eb851a11a9db9e9d/x/mongo/driver/operation/hello.go#L503 +func isHeartBeat(logger *zap.Logger, opReq Operation, requestHeader models.MongoHeader, mongoRequest interface{}) bool { + + switch requestHeader.Opcode { + case wiremessage.OpQuery: + return true + case wiremessage.OpMsg: + _, ok := mongoRequest.(*models.MongoOpMessage) + if ok { + return (opReq.IsIsAdminDB() && strings.Contains(opReq.String(), "hello")) || + opReq.IsIsMaster() || + isScramAuthRequest(mongoRequest.(*models.MongoOpMessage).Sections, logger) + } + default: + return false + } + return false +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/README.md b/keploy/pkg/core/proxy/integrations/mysql/README.md new file mode 100644 index 0000000..7dcd4c8 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/README.md @@ -0,0 +1,52 @@ +# MySQL Package Documentation + +The `mysqlparser` package encompasses the parser and mapping logic required +to read MySql binary messages and capture and test the outputs. +Utilized by the `hooks` package, it assists in redirecting outgoing +calls for the purpose of recording or testing the outputs. + +## SSL Support + +Please note that SSL is currently not supported in the MySQL package. To use the package without SSL, you can include the following parameters in your database URL like the following example: + +`jdbc:mysql://localhost:3306/db_name?useSSL=false&allowPublicKeyRetrieval=true` + +## The following MySQL packet types are handled in the parser: + +**COM_PING**: A ping command sent to the server to check if it's alive and responsive. + +**COM_STMT_EXECUTE**: Executes a prepared statement that was prepared using the COM_STMT_PREPARE command. + +**COM_STMT_FETCH**: Fetches rows from a statement which produced a result set. Used with cursors in server-side prepared statements. + +**COM_STMT_PREPARE**: Prepares a SQL statement for execution. + +**COM_STMT_CLOSE**: Closes a prepared statement, freeing up server resources associated with it. + +**COM_CHANGE_USER**: Changes the user of the current connection and resets the connection state. + +**MySQLOK**: A packet indicating a successful operation. It is usually received after commands like INSERT, UPDATE, DELETE, etc. + +**MySQLErr**: An error packet sent from the server to the client, indicating an error occurred with the last command sent. + +**RESULT_SET_PACKET**: Contains the actual result set data returned by a query. It's a series of packets containing rows and columns of data. + +**MySQLHandshakeV10**: The initial handshake packet sent from the server to the client when a connection is established, containing authentication and connection details. + +**HANDSHAKE_RESPONSE**: The response packet sent by the client in reply to MySQLHandshakeV10, containing client authentication data. + +**MySQLQuery**: Contains a SQL query that is to be executed on the server. + +**AUTH_SWITCH_REQUEST**: Sent by the server to request an authentication method switch during the connection process. + +**AUTH_SWITCH_RESPONSE**: Sent by the client to respond to the AUTH_SWITCH_REQUEST, containing authentication data. + +**MySQLEOF**: An EOF (End Of File) packet that marks the end of a result set or the end of the fields list. + +**AUTH_MORE_DATA**: Sent by the server if it needs more data for authentication (used in plugins). + +**COM_STMT_SEND_LONG_DATA**: Sends data for a column in a row to be inserted/updated in a table using a prepared statement. + +**COM_STMT_RESET**: Resets the data of a prepared statement which was accumulated with COM_STMT_SEND_LONG_DATA commands. + +**COM_QUIT**: Sent by the client to close the connection to the server gracefully. diff --git a/keploy/pkg/core/proxy/integrations/mysql/mysql.go b/keploy/pkg/core/proxy/integrations/mysql/mysql.go new file mode 100644 index 0000000..3e8bba5 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/mysql.go @@ -0,0 +1,62 @@ +//go:build linux + +// Package mysql provides the MySQL integration. +package mysql + +import ( + "context" + "io" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/recorder" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/replayer" + + "go.keploy.io/server/v2/utils" + + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +func init() { + integrations.Register(integrations.MYSQL, &integrations.Parsers{ + Initializer: New, + Priority: 100, + }) +} + +type MySQL struct { + logger *zap.Logger +} + +func New(logger *zap.Logger) integrations.Integrations { + return &MySQL{ + logger: logger, + } +} + +func (m *MySQL) MatchType(_ context.Context, _ []byte) bool { + //Returning false here because sql parser is using the ports to check if the packet is mysql or not. + return false +} + +func (m *MySQL) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + logger := m.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + + err := recorder.Record(ctx, logger, src, dst, mocks, opts) + if err != nil { + utils.LogError(logger, err, "failed to encode the mysql message into the yaml") + return err + } + return nil +} + +func (m *MySQL) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { + logger := m.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + err := replayer.Replay(ctx, logger, src, dstCfg, mockDb, opts) + if err != nil && err != io.EOF { + utils.LogError(logger, err, "failed to decode the mysql message from the yaml") + return err + } + return nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/panic_fix_test.go b/keploy/pkg/core/proxy/integrations/mysql/panic_fix_test.go new file mode 100644 index 0000000..fb85ccf --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/panic_fix_test.go @@ -0,0 +1,192 @@ +//go:build linux + +package mysql + +import ( + "context" + "fmt" + "testing" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/conn" + "go.uber.org/zap" +) + +// TestPanicFixes demonstrates that the previous panic-causing conditions now return errors instead +func TestPanicFixes(t *testing.T) { + logger := zap.NewNop() + ctx := context.Background() + + // Test cases that would have caused panics before the fix + panicCausingInputs := []struct { + name string + test func(t *testing.T) + }{ + { + name: "ReadLengthEncodedInteger with insufficient bytes for 2-byte value", + test: func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("ReadLengthEncodedInteger panicked: %v", r) + } + }() + + // This would have caused a panic before the fix + _, isNull, n := utils.ReadLengthEncodedInteger([]byte{0xFC, 0x01}) // Missing one byte + + // Should return error state (isNull=true, n=0) + if !isNull || n != 0 { + t.Errorf("Expected error state for insufficient bytes, got isNull=%t, n=%d", isNull, n) + } + }, + }, + { + name: "ReadLengthEncodedInteger with insufficient bytes for 3-byte value", + test: func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("ReadLengthEncodedInteger panicked: %v", r) + } + }() + + // This would have caused a panic before the fix + _, isNull, n := utils.ReadLengthEncodedInteger([]byte{0xFD, 0x01, 0x02}) // Missing one byte + + // Should return error state + if !isNull || n != 0 { + t.Errorf("Expected error state for insufficient bytes, got isNull=%t, n=%d", isNull, n) + } + }, + }, + { + name: "ReadLengthEncodedInteger with insufficient bytes for 8-byte value", + test: func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("ReadLengthEncodedInteger panicked: %v", r) + } + }() + + // This would have caused a panic before the fix + _, isNull, n := utils.ReadLengthEncodedInteger([]byte{0xFE, 0x01, 0x02, 0x03, 0x04}) // Missing 4 bytes + + // Should return error state + if !isNull || n != 0 { + t.Errorf("Expected error state for insufficient bytes, got isNull=%t, n=%d", isNull, n) + } + }, + }, + { + name: "DecodeHandshakeResponse with malformed connection attributes", + test: func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("DecodeHandshakeResponse panicked: %v", r) + } + }() + + // Create a packet that would have caused a panic in connection attributes parsing + data := make([]byte, 32) + // Set CLIENT_PROTOCOL_41 (0x200) + CLIENT_CONNECT_ATTRS (0x80000) + data[0] = 0x00 + data[1] = 0x02 + data[2] = 0x08 // CLIENT_CONNECT_ATTRS + data[3] = 0x00 + + // Add null terminator for username + data = append(data, 0x00) + // Add auth response + data = append(data, 0x00, 0x00) // length + filler + // Add connection attributes with insufficient data - this would have caused panic + data = append(data, 0x0A) // total length = 10 + data = append(data, 0x05) // key length = 5 + data = append(data, []byte{0x01, 0x02}...) // Only 2 bytes for key (need 5) + + // This should return an error, not panic + result, err := conn.DecodeHandshakeResponse(ctx, logger, data) + + if err == nil { + t.Errorf("Expected error but got none") + } + if result != nil { + t.Errorf("Expected nil result on error") + } + }, + }, + { + name: "DecodeHandshakeResponse with auth response overflow", + test: func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("DecodeHandshakeResponse panicked: %v", r) + } + }() + + // Create a packet that would have caused panic in auth response parsing + data := make([]byte, 32) + // CLIENT_PROTOCOL_41 only (non-plugin auth) + data[0] = 0x00 + data[1] = 0x02 + data[2] = 0x00 + data[3] = 0x00 + + // Add username + data = append(data, []byte("user")...) + data = append(data, 0x00) // null terminator + // Add auth response with overflow - this would have caused panic + data = append(data, 0x10, 0x00) // auth length = 16 + filler + data = append(data, []byte{0x01, 0x02, 0x03}...) // Only 3 bytes (need 16) + + // This should return an error, not panic + result, err := conn.DecodeHandshakeResponse(ctx, logger, data) + + if err == nil { + t.Errorf("Expected error but got none") + } + if result != nil { + t.Errorf("Expected nil result on error") + } + }, + }, + } + + for _, tc := range panicCausingInputs { + t.Run(tc.name, tc.test) + } +} + +// TestFuzzingResilience tests that random malformed data doesn't cause panics +func TestFuzzingResilience(t *testing.T) { + logger := zap.NewNop() + ctx := context.Background() + + // Test with various sizes of random/malformed data + testCases := [][]byte{ + {}, // Empty + {0xFF}, // Single byte + {0xFC}, // 2-byte marker only + {0xFD}, // 3-byte marker only + {0xFE}, // 8-byte marker only + {0xFC, 0xFF}, // 2-byte marker with 1 byte + {0xFD, 0xFF, 0xFF}, // 3-byte marker with 2 bytes + {0xFE, 0xFF, 0xFF, 0xFF, 0xFF}, // 8-byte marker with 4 bytes + make([]byte, 31), // Just under minimum handshake size + make([]byte, 100), // Large buffer with zeros + } + + for i, data := range testCases { + t.Run(fmt.Sprintf("fuzz_case_%d", i), func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Unexpected panic with fuzz data %d: %v", i, r) + } + }() + + // Test ReadLengthEncodedInteger + utils.ReadLengthEncodedInteger(data) + + // Test DecodeHandshakeResponse + conn.DecodeHandshakeResponse(ctx, logger, data) + }) + } +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/recorder/conn.go b/keploy/pkg/core/proxy/integrations/mysql/recorder/conn.go new file mode 100644 index 0000000..eb475ff --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/recorder/conn.go @@ -0,0 +1,730 @@ +//go:build linux + +package recorder + +import ( + "bufio" + "context" + "crypto/tls" + "fmt" + "io" + "net" + "time" + + mysqlUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" + intgUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + pTls "go.keploy.io/server/v2/pkg/core/proxy/tls" + pUtils "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// Record mode +type handshakeRes struct { + req []mysql.Request + resp []mysql.Response + requestOperation string + responseOperation string + reqTimestamp time.Time + tlsClientConn net.Conn + tlsDestConn net.Conn +} + +func handleInitialHandshake(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext, opts models.OutgoingOptions) (handshakeRes, error) { + + res := handshakeRes{ + req: make([]mysql.Request, 0), + resp: make([]mysql.Response, 0), + } + + // Read the initial handshake from the server (server-greetings) + handshake, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + utils.LogError(logger, err, "failed to read initial handshake from server") + return res, err + } + + // Write the initial handshake to the client + _, err = clientConn.Write(handshake) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write server greetings to the client") + + return res, err + } + + // Set the timestamp of the initial request + res.reqTimestamp = time.Now() + + // Decode server handshake packet + handshakePkt, err := wire.DecodePayload(ctx, logger, handshake, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode handshake packet") + return res, err + } + + // Set the intial request operation + res.requestOperation = handshakePkt.Header.Type + + // Get the initial Plugin Name + pluginName, err := wire.GetPluginName(handshakePkt.Message) + if err != nil { + utils.LogError(logger, err, "failed to get initial plugin name") + return res, err + } + + // Set the initial plugin name + decodeCtx.PluginName = pluginName + + res.resp = append(res.resp, mysql.Response{ + PacketBundle: *handshakePkt, + }) + + // Handshake response from client (or SSL request) + handshakeResponse, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + if err == io.EOF { + logger.Debug("received request buffer is empty in record mode for mysql call") + return res, err + } + utils.LogError(logger, err, "failed to read handshake response from client") + + return res, err + } + + _, err = destConn.Write(handshakeResponse) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write handshake response to server") + + return res, err + } + + // Decode client handshake response (or SSL) packet + handshakeResponsePkt, err := wire.DecodePayload(ctx, logger, handshakeResponse, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode handshake response packet") + return res, err + } + + res.req = append(res.req, mysql.Request{ + PacketBundle: *handshakeResponsePkt, + }) + + // handle the SSL request + if decodeCtx.UseSSL { + + reader := bufio.NewReader(clientConn) + initialData := make([]byte, 5) + // reading the initial data from the client connection to determine if the connection is a TLS handshake + testBuffer, err := reader.Peek(len(initialData)) + if err != nil { + if err == io.EOF && len(testBuffer) == 0 { + logger.Debug("received EOF, closing conn", zap.Error(err)) + return res, nil + } + utils.LogError(logger, err, "failed to peek the mysql request message in proxy") + return res, err + } + + multiReader := io.MultiReader(reader, clientConn) + clientConn = &pUtils.Conn{ + Conn: clientConn, + Reader: multiReader, + Logger: logger, + } + + // handle the TLS connection and get the upgraded client connection + isTLS := pTls.IsTLSHandshake(testBuffer) + if isTLS { + clientConn, err = pTls.HandleTLSConnection(ctx, logger, clientConn, opts.Backdate) + if err != nil { + utils.LogError(logger, err, "failed to handle TLS conn") + return res, err + } + } + + // upgrade the destConn to TLS if the client connection is upgraded to TLS + var tlsDestConn *tls.Conn + if isTLS { + + remoteAddr := clientConn.RemoteAddr().(*net.TCPAddr) + sourcePort := remoteAddr.Port + + url, ok := pTls.SrcPortToDstURL.Load(sourcePort) + if !ok { + utils.LogError(logger, err, "failed to fetch the destination url") + return res, err + } + + //type case the dstUrl to string + dstURL, ok := url.(string) + if !ok { + utils.LogError(logger, err, "failed to type cast the destination url") + return res, err + } + + addr := fmt.Sprintf("%v:%v", dstURL, opts.DstCfg.Port) + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + ServerName: dstURL, + } + + logger.Debug("Upgrading the destination connection to TLS", zap.String("Destination Addr", addr), zap.String("ServerName", tlsConfig.ServerName)) + + tlsDestConn = tls.Client(destConn, tlsConfig) + err = tlsDestConn.Handshake() + if err != nil { + utils.LogError(logger, err, "failed to upgrade the destination connection to TLS for mysql") + return res, err + } + logger.Debug("TLS connection established with the destination server", zap.Any("Destination Addr", destConn.RemoteAddr().String())) + + // Update the destination connection to TLS connection + destConn = tlsDestConn + } + + // Update this tls connection information in the handshake result + res.tlsClientConn = clientConn + res.tlsDestConn = destConn + + // Store (Reset) the last operation for the upgraded client connection, because after ssl request the client will send the handshake response packet again. + decodeCtx.LastOp.Store(clientConn, mysql.HandshakeV10) + + // Store the server greeting packet for the upgraded client connection + sg, ok := handshakePkt.Message.(*mysql.HandshakeV10Packet) + if !ok { + return res, fmt.Errorf("failed to type assert handshake packet") + } + decodeCtx.ServerGreetings.Store(clientConn, sg) + + // Read the handshake response packet from the client + handshakeResponse, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + if err == io.EOF { + logger.Debug("received request buffer is empty in record mode for mysql call") + return res, err + } + utils.LogError(logger, err, "failed to read handshake response from client") + + return res, err + } + + _, err = destConn.Write(handshakeResponse) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write handshake response to server") + + return res, err + } + + // Decode client handshake response packet + handshakeResponsePkt, err := wire.DecodePayload(ctx, logger, handshakeResponse, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode handshake response packet") + return res, err + } + + res.req = append(res.req, mysql.Request{ + PacketBundle: *handshakeResponsePkt, + }) + } + + // Read the next auth packet, + // It can be either auth more data if authentication from both server and client are agreed.(caching_sha2_password) + // or auth switch request if the server wants to switch the auth mechanism + // or it can be OK packet in case of native password + authData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err == io.EOF { + logger.Debug("received request buffer is empty in record mode for mysql call") + + return res, err + } + utils.LogError(logger, err, "failed to read auth or final response packet from server during handshake") + return res, err + } + + // AuthSwitchRequest: If the server sends an AuthSwitchRequest, then there must be a diff auth type with its data + // AuthMoreData: If the server sends an AuthMoreData, then it tells the auth mechanism type for the initial plugin name or for the auth switch request. + // OK/ERR: If the server sends an OK/ERR packet, in case of native password. + _, err = clientConn.Write(authData) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write auth packet to client during handshake") + return res, err + } + + // Decode auth or final response packet + authDecider, err := wire.DecodePayload(ctx, logger, authData, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode auth packet during handshake") + return res, err + } + + // check if the authDecider is of type AuthSwitchRequestPacket. + // AuthSwitchRequestPacket is sent by the server to the client to switch the auth mechanism + if _, ok := authDecider.Message.(*mysql.AuthSwitchRequestPacket); ok { + + logger.Debug("Server is changing the auth mechanism by sending AuthSwitchRequestPacket") + + //save the auth switch request packet + res.resp = append(res.resp, mysql.Response{ + PacketBundle: *authDecider, + }) + + pkt := authDecider.Message.(*mysql.AuthSwitchRequestPacket) + + // Change the plugin name due to auth switch request + decodeCtx.PluginName = pkt.PluginName + + // read the auth switch response from the client + authSwitchResponse, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + if err == io.EOF { + logger.Debug("received request buffer is empty in record mode for mysql call") + return res, err + } + utils.LogError(logger, err, "failed to read auth switch response from client") + return res, err + } + + _, err = destConn.Write(authSwitchResponse) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write auth switch response to server") + return res, err + } + + // Decode the auth switch response packet + authSwithResp, err := mysqlUtils.BytesToMySQLPacket(authSwitchResponse) + if err != nil { + utils.LogError(logger, err, "failed to parse MySQL packet") + return res, err + } + + authSwithRespPkt := &mysql.PacketBundle{ + Header: &mysql.PacketInfo{ + Header: &authSwithResp.Header, + Type: mysql.AuthSwithResponse, // there is no specific identifier for AuthSwitchResponse + }, + Message: intgUtils.EncodeBase64(authSwithResp.Payload), + } + + // save the auth switch response packet + res.req = append(res.req, mysql.Request{ + PacketBundle: *authSwithRespPkt, + }) + + logger.Debug("Auth mechanism is switched successfully") + + // read the further auth packet, now it can be either auth more data or OK packet + authData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err == io.EOF { + logger.Debug("received request buffer is empty in record mode for mysql call") + return res, err + } + utils.LogError(logger, err, "failed to read auth data from the server after handling auth switch response") + + return res, err + } + + _, err = clientConn.Write(authData) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write auth data to client after handling auth switch response") + return res, err + } + + // It can be either auth more data or OK packet + authDecider, err = wire.DecodePayload(ctx, logger, authData, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode auth data packet after handling auth switch response") + return res, err + } + } + + var authRes handshakeRes + switch authDecider.Message.(type) { + case *mysql.AuthMoreDataPacket: + authRes, err = handleAuth(ctx, logger, authDecider, clientConn, destConn, decodeCtx) + if err != nil { + return res, fmt.Errorf("failed to handle auth more data: %w", err) + } + case *mysql.OKPacket: + authRes, err = handleAuth(ctx, logger, authDecider, clientConn, destConn, decodeCtx) + if err != nil { + return res, fmt.Errorf("failed to handle ok packet: %w", err) + } + } + + setHandshakeResult(&res, authRes) + + return res, nil +} + +func setHandshakeResult(res *handshakeRes, authRes handshakeRes) { + res.req = append(res.req, authRes.req...) + res.resp = append(res.resp, authRes.resp...) + res.responseOperation = authRes.responseOperation +} + +func handleAuth(ctx context.Context, logger *zap.Logger, authPkt *mysql.PacketBundle, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (handshakeRes, error) { + res := handshakeRes{ + req: make([]mysql.Request, 0), + resp: make([]mysql.Response, 0), + } + + switch mysql.AuthPluginName(decodeCtx.PluginName) { + case mysql.Native: + res.resp = append(res.resp, mysql.Response{ + PacketBundle: *authPkt, + }) + + res.responseOperation = authPkt.Header.Type + logger.Debug("native password authentication is handled successfully") + case mysql.CachingSha2: + result, err := handleCachingSha2Password(ctx, logger, authPkt, clientConn, destConn, decodeCtx) + if err != nil { + return res, fmt.Errorf("failed to handle caching sha2 password: %w", err) + } + logger.Debug("caching sha2 password authentication is handled successfully") + setHandshakeResult(&res, result) + case mysql.Sha256: + return res, fmt.Errorf("Sha256 Password authentication is not supported") + default: + return res, fmt.Errorf("unsupported authentication plugin: %s", decodeCtx.PluginName) + } + + return res, nil +} + +func handleCachingSha2Password(ctx context.Context, logger *zap.Logger, authPkt *mysql.PacketBundle, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (handshakeRes, error) { + res := handshakeRes{ + req: make([]mysql.Request, 0), + resp: make([]mysql.Response, 0), + } + + var authMechanism string + var err error + var ok bool + var authMorePkt *mysql.AuthMoreDataPacket + + // check if the authPkt is of type AuthMoreDataPacket + if authMorePkt, ok = authPkt.Message.(*mysql.AuthMoreDataPacket); !ok { + return res, fmt.Errorf("invalid packet type for caching sha2 password mechanism, expected: AuthMoreDataPacket, found: %T", authPkt.Message) + } + + // Getting the string value of the caching_sha2_password mechanism + authMechanism, err = wire.GetCachingSha2PasswordMechanism(authMorePkt.Data[0]) + if err != nil { + return res, fmt.Errorf("failed to get caching sha2 password mechanism: %w", err) + } + authMorePkt.Data = authMechanism + + // save the auth more data packet + res.resp = append(res.resp, mysql.Response{ + PacketBundle: *authPkt, + }) + + auth, err := wire.StringToCachingSha2PasswordMechanism(authMechanism) + if err != nil { + return res, fmt.Errorf("failed to convert string to caching sha2 password mechanism: %w", err) + } + + var result handshakeRes + switch auth { + case mysql.PerformFullAuthentication: + result, err = handleFullAuth(ctx, logger, clientConn, destConn, decodeCtx) + if err != nil { + return res, fmt.Errorf("failed to handle caching sha2 password full auth: %w", err) + } + case mysql.FastAuthSuccess: + result, err = handleFastAuthSuccess(ctx, logger, clientConn, destConn, decodeCtx) + if err != nil { + return res, fmt.Errorf("failed to handle caching sha2 password fast auth success: %w", err) + } + } + + setHandshakeResult(&res, result) + + return res, nil +} + +func handleFastAuthSuccess(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (handshakeRes, error) { + res := handshakeRes{ + req: make([]mysql.Request, 0), + resp: make([]mysql.Response, 0), + } + + //As per wire shark capture, during fast auth success, server sends OK packet just after auth more data + + // read the ok/err packet from the server after auth more data + finalResp, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err == io.EOF { + logger.Debug("received request buffer is empty in record mode for mysql call") + return res, err + } + utils.LogError(logger, err, "failed to read final response packet from server") + return res, err + } + + // write the ok/err packet to the client + _, err = clientConn.Write(finalResp) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write ok/err packet to client during fast auth mechanism") + return res, err + } + + finalPkt, err := wire.DecodePayload(ctx, logger, finalResp, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode final response packet after auth data packet") + return res, err + } + + res.resp = append(res.resp, mysql.Response{ + PacketBundle: *finalPkt, + }) + + // Set the final response operation of the handshake + res.responseOperation = finalPkt.Header.Type + logger.Debug("fast auth success is handled successfully") + + return res, nil +} + +func handleFullAuth(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (handshakeRes, error) { + res := handshakeRes{ + req: make([]mysql.Request, 0), + resp: make([]mysql.Response, 0), + } + + // If the connection is using SSL, we don't need to exchange the public key and encrypted password, + // we can directly handle the plain password. + // This is because the SSL connection already provides a secure channel for the password exchange. + if decodeCtx.UseSSL { + logger.Debug("Handling caching_sha2_password full auth in SSL request, using plain password") + res2, err := handlePlainPassword(ctx, logger, clientConn, destConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to handle plain password in caching_sha2_password(full auth) in ssl request") + return res, fmt.Errorf("failed to handle plain password in caching_sha2_password full auth: %w", err) + } + // Set the final response operation of the handshake + setHandshakeResult(&res, res2) + return res, nil + } + + // read the public key request from the client + publicKeyRequest, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + utils.LogError(logger, err, "failed to read public key request from client") + return res, err + } + _, err = destConn.Write(publicKeyRequest) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write public key request to server") + return res, err + } + + publicKeyReqPkt, err := wire.DecodePayload(ctx, logger, publicKeyRequest, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode public key request packet") + return res, err + } + + res.req = append(res.req, mysql.Request{ + PacketBundle: *publicKeyReqPkt, + }) + + // read the "public key" as response from the server + pubKey, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + utils.LogError(logger, err, "failed to read public key from server") + return res, err + } + _, err = clientConn.Write(pubKey) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write public key response to client") + return res, err + } + + pubKeyPkt, err := wire.DecodePayload(ctx, logger, pubKey, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode public key packet") + return res, err + } + + pubKeyPkt.Meta = map[string]string{ + "auth operation": "public key response", + } + + res.resp = append(res.resp, mysql.Response{ + PacketBundle: *pubKeyPkt, + }) + + // read the encrypted password from the client + encryptPass, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + utils.LogError(logger, err, "failed to read encrypted password from client") + + return res, err + } + _, err = destConn.Write(encryptPass) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write encrypted password to server") + return res, err + } + + encPass, err := mysqlUtils.BytesToMySQLPacket(encryptPass) + if err != nil { + utils.LogError(logger, err, "failed to parse MySQL packet") + return res, err + } + + encryptPassPkt := &mysql.PacketBundle{ + Header: &mysql.PacketInfo{ + Header: &encPass.Header, + Type: mysql.EncryptedPassword, + }, + Message: intgUtils.EncodeBase64(encPass.Payload), + } + + res.req = append(res.req, mysql.Request{ + PacketBundle: *encryptPassPkt, + }) + + // read the final response from the server (ok or error) + finalServerResponse, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + utils.LogError(logger, err, "failed to read final response from server") + return res, err + } + _, err = clientConn.Write(finalServerResponse) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write final response to client") + + return res, err + } + + finalResPkt, err := wire.DecodePayload(ctx, logger, finalServerResponse, clientConn, decodeCtx) + + if err != nil { + utils.LogError(logger, err, "failed to decode final response packet during caching sha2 password full auth") + return res, err + } + + res.resp = append(res.resp, mysql.Response{ + PacketBundle: *finalResPkt, + }) + + // Set the final response operation of the handshake + res.responseOperation = finalResPkt.Header.Type + + logger.Debug("full auth is handled successfully") + return res, nil +} + +func handlePlainPassword(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (handshakeRes, error) { + res := handshakeRes{ + req: make([]mysql.Request, 0), + resp: make([]mysql.Response, 0), + } + + // read the plain password from the client + plainPassBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + utils.LogError(logger, err, "failed to read plain password from the client") + return res, err + } + _, err = destConn.Write(plainPassBuf) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write plain password to the server") + return res, err + } + + plainPass, err := mysqlUtils.BytesToMySQLPacket(plainPassBuf) + if err != nil { + utils.LogError(logger, err, "failed to parse MySQL packet") + return res, err + } + + plainPassPkt := &mysql.PacketBundle{ + Header: &mysql.PacketInfo{ + Header: &plainPass.Header, + Type: mysql.PlainPassword, + }, + Message: intgUtils.EncodeBase64(plainPass.Payload), + } + + res.req = append(res.req, mysql.Request{ + PacketBundle: *plainPassPkt, + }) + + // read the final response from the server (ok or error) + finalServerResponse, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + utils.LogError(logger, err, "failed to read final response from server") + return res, err + } + _, err = clientConn.Write(finalServerResponse) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write final response to client") + + return res, err + } + + finalResPkt, err := wire.DecodePayload(ctx, logger, finalServerResponse, clientConn, decodeCtx) + + if err != nil { + utils.LogError(logger, err, "failed to decode final response packet during caching sha2 password full auth (plain password)") + return res, err + } + + res.resp = append(res.resp, mysql.Response{ + PacketBundle: *finalResPkt, + }) + + // Set the final response operation of the handshake + res.responseOperation = finalResPkt.Header.Type + + logger.Debug("full auth (plain password) is handled successfully") + return res, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/recorder/query.go b/keploy/pkg/core/proxy/integrations/mysql/recorder/query.go new file mode 100644 index 0000000..122b57e --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/recorder/query.go @@ -0,0 +1,559 @@ +//go:build linux + +package recorder + +import ( + "context" + "fmt" + "io" + "net" + "time" + + mysqlUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func handleClientQueries(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, mocks chan<- *models.Mock, decodeCtx *wire.DecodeContext) error { + var ( + requests []mysql.Request + responses []mysql.Response + ) + + //for keeping conn alive + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + + // read the command from the client + command, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read command packet from client") + } + return err + } + + // write the command to the destination server + _, err = destConn.Write(command) + if err != nil { + utils.LogError(logger, err, "failed to write command to the server") + return err + } + + // Getting timestamp for the request + reqTimestamp := time.Now() + + commandPkt, err := wire.DecodePayload(ctx, logger, command, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode the MySQL packet from the client") + return err + } + + requests = append(requests, mysql.Request{ + PacketBundle: *commandPkt, + }) + + // handle no response commands like COM_STMT_CLOSE, COM_STMT_SEND_LONG_DATA, etc + if wire.IsNoResponseCommand(commandPkt.Header.Type) { + recordMock(ctx, requests, responses, "mocks", commandPkt.Header.Type, "NO Response Packet", mocks, reqTimestamp) + // reset the requests and responses + requests = []mysql.Request{} + responses = []mysql.Response{} + logger.Debug("No response command", zap.Any("packet", commandPkt.Header.Type)) + continue + } + + commandRespPkt, err := handleQueryResponse(ctx, logger, clientConn, destConn, decodeCtx) + if err != nil { + if err == io.EOF && commandPkt.Header.Type == mysql.CommandStatusToString(mysql.COM_QUIT) { + logger.Debug("server closed the connection without any response") + return err + } + utils.LogError(logger, err, "failed to handle the query response") + return err + } + + responses = append(responses, mysql.Response{ + PacketBundle: *commandRespPkt, + }) + + // record the mock + recordMock(ctx, requests, responses, "mocks", commandPkt.Header.Type, commandRespPkt.Header.Type, mocks, reqTimestamp) + + // reset the requests and responses + requests = []mysql.Request{} + responses = []mysql.Response{} + } + } +} + +func handleQueryResponse(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (*mysql.PacketBundle, error) { + // read the command response from the destination server + commandResp, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read command response from the server") + } + return nil, err + } + + // write the command response to the client + _, err = clientConn.Write(commandResp) + if err != nil { + utils.LogError(logger, err, "failed to write command response to the client") + return nil, err + } + + //decode the command response packet + commandRespPkt, err := wire.DecodePayload(ctx, logger, commandResp, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode the command response packet") + return nil, err + } + + // check if the command response is an error or ok packet + if commandRespPkt.Header.Type == mysql.StatusToString(mysql.ERR) || commandRespPkt.Header.Type == mysql.StatusToString(mysql.OK) { + logger.Debug("command response packet", zap.Any("packet", commandRespPkt.Header.Type)) + return commandRespPkt, nil + } + + // Get the last operation in order to handle current packet if it is not an error or ok packet + lastOp, ok := decodeCtx.LastOp.Load(clientConn) + if !ok { + return nil, fmt.Errorf("failed to get the last operation from the context while handling the query response") + } + + var queryResponsePkt *mysql.PacketBundle + + switch lastOp { + case mysql.COM_QUERY: + logger.Debug("Handling text result set", zap.Any("lastOp", lastOp)) + // handle the query response (TextResultSet) + queryResponsePkt, err = handleTextResultSet(ctx, logger, clientConn, destConn, commandRespPkt, decodeCtx) + if err != nil { + return nil, fmt.Errorf("failed to handle the query response packet: %w", err) + } + + case mysql.COM_STMT_PREPARE: + logger.Debug("Handling prepare Statement Response OK", zap.Any("lastOp", lastOp)) + // handle the prepared statement response (COM_STMT_PREPARE_OK) + queryResponsePkt, err = handlePreparedStmtResponse(ctx, logger, clientConn, destConn, commandRespPkt, decodeCtx) + if err != nil { + return nil, fmt.Errorf("failed to handle the prepared statement response: %w", err) + } + case mysql.COM_STMT_EXECUTE: + logger.Debug("Handling binary protocol result set", zap.Any("lastOp", lastOp)) + // handle the statment execute response (BinaryProtocolResultSet) + queryResponsePkt, err = handleBinaryResultSet(ctx, logger, clientConn, destConn, commandRespPkt, decodeCtx) + if err != nil { + return nil, fmt.Errorf("failed to handle the statement execute response: %w", err) + } + + default: + return nil, fmt.Errorf("unsupported operation: %x", lastOp) + } + + return queryResponsePkt, nil +} + +func handlePreparedStmtResponse(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, commandRespPkt *mysql.PacketBundle, decodeCtx *wire.DecodeContext) (*mysql.PacketBundle, error) { + + //commandRespPkt is the response to prepare, there are parameters, intermediate EOF, columns, and EOF packets to be handled + //ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_prepare.html#sect_protocol_com_stmt_prepare_response_ok + + responseOk, ok := commandRespPkt.Message.(*mysql.StmtPrepareOkPacket) + if !ok { + return nil, fmt.Errorf("expected StmtPrepareOkPacket, got %T", commandRespPkt.Message) + } + + logger.Debug("Parsing the params and columns in the prepared statement response", zap.Any("responseOk", responseOk)) + + //See if there are any parameters + if responseOk.NumParams > 0 { + for i := uint16(0); i < responseOk.NumParams; i++ { + + // Read the column definition packet + colData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read column data for parameter definition") + } + return nil, err + } + + // Write the column definition packet to the client + _, err = clientConn.Write(colData) + if err != nil { + utils.LogError(logger, err, "failed to write column data for parameter definition") + return nil, err + } + + // Decode the column definition packet + column, _, err := rowscols.DecodeColumn(ctx, logger, colData) + if err != nil { + return nil, fmt.Errorf("failed to decode column definition packet: %w", err) + } + + responseOk.ParamDefs = append(responseOk.ParamDefs, column) + } + + logger.Debug("ParamsDefs after parsing", zap.Any("ParamDefs", responseOk.ParamDefs)) + + // Read the EOF packet for parameter definition + eofData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read EOF packet for parameter definition") + } + return nil, err + } + + // Write the EOF packet for parameter definition to the client + _, err = clientConn.Write(eofData) + if err != nil { + utils.LogError(logger, err, "failed to write EOF packet for parameter definition to the client") + return nil, err + } + + // Validate the EOF packet for parameter definition + if !mysqlUtils.IsEOFPacket(eofData) { + return nil, fmt.Errorf("expected EOF packet for parameter definition, got %v", eofData) + } + + responseOk.EOFAfterParamDefs = eofData + + logger.Debug("Eof after param defs", zap.Any("eofData", eofData)) + } + + //See if there are any columns + if responseOk.NumColumns > 0 { + for i := uint16(0); i < responseOk.NumColumns; i++ { + + // Read the column definition packet + colData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read column data for column definition") + } + return nil, err + } + + // Write the column definition packet to the client + _, err = clientConn.Write(colData) + if err != nil { + utils.LogError(logger, err, "failed to write column data for column definition") + return nil, err + } + + // Decode the column definition packet + column, _, err := rowscols.DecodeColumn(ctx, logger, colData) + if err != nil { + return nil, fmt.Errorf("failed to decode column definition packet: %w", err) + } + + responseOk.ColumnDefs = append(responseOk.ColumnDefs, column) + } + + logger.Debug("ColumnDefs after parsing", zap.Any("ColumnDefs", responseOk.ColumnDefs)) + + // Read the EOF packet for column definition + eofData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read EOF packet for column definition") + } + return nil, err + } + + // Write the EOF packet for column definition to the client + _, err = clientConn.Write(eofData) + if err != nil { + utils.LogError(logger, err, "failed to write EOF packet for column definition to the client") + return nil, err + } + + // Validate the EOF packet for column definition + if !mysqlUtils.IsEOFPacket(eofData) { + return nil, fmt.Errorf("expected EOF packet for column definition, got %v, while handling prepared statement response", eofData) + } + + responseOk.EOFAfterColumnDefs = eofData + + logger.Debug("Eof after column defs", zap.Any("eofData", eofData)) + } + + //set the lastOp to COM_STMT_PREPARE_OK + decodeCtx.LastOp.Store(clientConn, mysql.OK) + + // commandRespPkt.Message = responseOk // need to check whether this is necessary + + return commandRespPkt, nil +} + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_query_response_text_resultset.html + +func handleTextResultSet(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, textResultSetPkt *mysql.PacketBundle, decodeCtx *wire.DecodeContext) (*mysql.PacketBundle, error) { + + // colCountPkt is the first packet of the text result set, it is followed by column definition packets, intermediate eof, row data packets and final eof + + textResultSet, ok := textResultSetPkt.Message.(*mysql.TextResultSet) + if !ok { + return nil, fmt.Errorf("expected TextResultSet, got %T", textResultSetPkt.Message) + } + + // Read the column count packet + colCount := textResultSet.ColumnCount + + // Read the column definition packets + for i := uint64(0); i < colCount; i++ { + // Read the column definition packet + colData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read column definition packet") + } + return nil, err + } + + // Write the column definition packet to the client + _, err = clientConn.Write(colData) + if err != nil { + utils.LogError(logger, err, "failed to write column definition packet") + return nil, err + } + + // Decode the column definition packet + column, _, err := rowscols.DecodeColumn(ctx, logger, colData) + if err != nil { + return nil, fmt.Errorf("failed to decode column definition packet: %w", err) + } + + textResultSet.Columns = append(textResultSet.Columns, column) + } + + if decodeCtx.ClientCapabilities&mysql.CLIENT_DEPRECATE_EOF == 0 { + logger.Debug("EOF packet is not deprecated while handling textResultSet") + + // Read the EOF packet for column definition + eofData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read EOF packet for column definition") + } + return nil, err + } + + // Write the EOF packet for column definition to the client + _, err = clientConn.Write(eofData) + if err != nil { + utils.LogError(logger, err, "failed to write EOF packet for column definition to the client") + return nil, err + } + + // Validate the EOF packet for column definition + if !mysqlUtils.IsEOFPacket(eofData) { + return nil, fmt.Errorf("expected EOF packet for column definition, got %v, while handling textResultSet", eofData) + } + + textResultSet.EOFAfterColumns = eofData + + } + // Read the row data packets +rowLoop: + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + + // Read the packet + data, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read data packet while reading row data") + } + return nil, err + } + + // Write the packet to the client + _, err = clientConn.Write(data) + if err != nil { + utils.LogError(logger, err, "failed to write data packet while reading row data") + return nil, err + } + + // // Break if the data packet is a generic response + // resp, ok := mysqlUtils.IsGenericResponse(data) + // if ok { + // textResultSet.FinalResponse = &mysql.GenericResponse{ + // Data: data, + // Type: resp, + // } + // break rowLoop + // } + + // Break if the data packet is an EOF packet, But we need to check for generic response + // Right now we are just checking for EOF packet as we couldn't differentiate between the generic response and row data packet + if mysqlUtils.IsEOFPacket(data) { + logger.Debug("Found EOF packet after row data in text resultset") + textResultSet.FinalResponse = &mysql.GenericResponse{ + Data: data, + Type: mysql.StatusToString(mysql.EOF), + } + break rowLoop + } + + // It must be a row data packet + row, _, err := rowscols.DecodeTextRow(ctx, logger, data, textResultSet.Columns) + if err != nil { + return nil, fmt.Errorf("failed to decode row data packet: %w", err) + } + textResultSet.Rows = append(textResultSet.Rows, row) + } + } + + // reset the last OP + decodeCtx.LastOp.Store(clientConn, wire.RESET) + + return textResultSetPkt, nil +} + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html +// (BinaryProtocolResultset) + +func handleBinaryResultSet(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, binaryResultSetPkt *mysql.PacketBundle, decodeCtx *wire.DecodeContext) (*mysql.PacketBundle, error) { + + // colCountPkt is the first packet of the binary result set, it is followed by column definition packets,intermediate eof, row data packets and final eof + + binaryResultSet, ok := binaryResultSetPkt.Message.(*mysql.BinaryProtocolResultSet) + if !ok { + return nil, fmt.Errorf("expected TextResultSet, got %T", binaryResultSetPkt.Message) + } + + // Read the column count packet + colCount := binaryResultSet.ColumnCount + + logger.Debug("ColCount in handleBinaryResultSet: ", zap.Any("ColCount", colCount)) + // Read the column definition packets + for i := uint64(0); i < colCount; i++ { + // Read the column definition packet + colData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read column definition packet") + } + return nil, err + } + + // Write the column definition packet to the client + _, err = clientConn.Write(colData) + if err != nil { + utils.LogError(logger, err, "failed to write column definition packet") + return nil, err + } + + // Decode the column definition packet + column, _, err := rowscols.DecodeColumn(ctx, logger, colData) + if err != nil { + return nil, fmt.Errorf("failed to decode column definition packet: %w", err) + } + + binaryResultSet.Columns = append(binaryResultSet.Columns, column) + } + + logger.Debug("Columns: ", zap.Any("Columns", binaryResultSet.Columns)) + + // Read the EOF packet for column definition + eofData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read EOF packet for column definition") + } + return nil, err + } + + // Write the EOF packet for column definition to the client + _, err = clientConn.Write(eofData) + if err != nil { + utils.LogError(logger, err, "failed to write EOF packet for column definition to the client") + return nil, err + } + + // Validate the EOF packet for column definition + if !mysqlUtils.IsEOFPacket(eofData) { + return nil, fmt.Errorf("expected EOF packet for column definition, got %v, while handling BinaryProtocolResultSet", eofData) + } + + binaryResultSet.EOFAfterColumns = eofData + + // Read the row data packets +rowLoop: + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + + // Read the packet + data, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read data packet while reading row data") + } + return nil, err + } + + // Write the packet to the client + _, err = clientConn.Write(data) + if err != nil { + utils.LogError(logger, err, "failed to write data packet while reading row data") + return nil, err + } + + // Break if the data packet is a generic response + // resp, ok := mysqlUtils.IsGenericResponse(data) + // if ok { + // binaryResultSet.FinalResponse = &mysql.GenericResponse{ + // Data: data, + // Type: resp, + // } + // //debug log + // fmt.Println("Found generic response after row data") + // break rowLoop + // } + + // Break if the data packet is an EOF packet, But we need to check for generic response + // Right now we are just checking for EOF packet as we couldn't differentiate between the generic response and row data packet + if mysqlUtils.IsEOFPacket(data) { + logger.Debug("Found EOF packet after row data in binary resultset") + binaryResultSet.FinalResponse = &mysql.GenericResponse{ + Data: data, + Type: mysql.StatusToString(mysql.EOF), + } + break rowLoop + } + + // It must be a row data packet + row, _, err := rowscols.DecodeBinaryRow(ctx, logger, data, binaryResultSet.Columns) + if err != nil { + return nil, fmt.Errorf("failed to decode row data packet: %w", err) + } + binaryResultSet.Rows = append(binaryResultSet.Rows, row) + } + } + + logger.Debug("Rows: ", zap.Any("Rows", binaryResultSet.Rows)) + + // reset the last OP + decodeCtx.LastOp.Store(clientConn, wire.RESET) + + return binaryResultSetPkt, nil + +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/recorder/record.go b/keploy/pkg/core/proxy/integrations/mysql/recorder/record.go new file mode 100644 index 0000000..867f273 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/recorder/record.go @@ -0,0 +1,131 @@ +//go:build linux + +// Package recorder is used to record the MySQL traffic between the client and the server. +package recorder + +import ( + "context" + "errors" + "io" + "net" + "time" + + "golang.org/x/sync/errgroup" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// Binary to Mock Yaml + +func Record(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + + var ( + requests []mysql.Request + responses []mysql.Response + ) + + errCh := make(chan error, 1) + + //get the error group from the context + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context") + } + + g.Go(func() error { + defer pUtil.Recover(logger, clientConn, destConn) + defer close(errCh) + + // Helper struct for decoding packets + decodeCtx := &wire.DecodeContext{ + Mode: models.MODE_RECORD, + // Map for storing last operation per connection + LastOp: wire.NewLastOpMap(), + // Map for storing server greetings (inc capabilities, auth plugin, etc) per initial handshake (per connection) + ServerGreetings: wire.NewGreetings(), + // Map for storing prepared statements per connection + PreparedStatements: make(map[uint32]*mysql.StmtPrepareOkPacket), + } + decodeCtx.LastOp.Store(clientConn, wire.RESET) //resetting last command for new loop + + // handle the initial client-server handshake (connection phase) + result, err := handleInitialHandshake(ctx, logger, clientConn, destConn, decodeCtx, opts) + if err != nil { + utils.LogError(logger, err, "failed to handle initial handshake") + errCh <- err + return nil + } + requests = append(requests, result.req...) + responses = append(responses, result.resp...) + + reqTimestamp := result.reqTimestamp + + recordMock(ctx, requests, responses, "config", result.requestOperation, result.responseOperation, mocks, reqTimestamp) + + // reset the requests and responses + requests = []mysql.Request{} + responses = []mysql.Response{} + + if decodeCtx.UseSSL { + if result.tlsClientConn == nil || result.tlsDestConn == nil { + utils.LogError(logger, err, "Expected Tls connections are nil", zap.Any("tlsClientConn", result.tlsClientConn), zap.Any("tlsDestConn", result.tlsDestConn)) + errCh <- errors.New("tls connection is not established") + return nil + } + clientConn = result.tlsClientConn + destConn = result.tlsDestConn + } + + lstOp, _ := decodeCtx.LastOp.Load(clientConn) + logger.Debug("last operation after initial handshake", zap.Any("last operation", lstOp)) + + // handle the client-server interaction (command phase) + err = handleClientQueries(ctx, logger, clientConn, destConn, mocks, decodeCtx) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to handle client queries") + } + errCh <- err + return nil + } + return nil + }) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + return nil + } + return err + } +} + +func recordMock(ctx context.Context, requests []mysql.Request, responses []mysql.Response, mockType, requestOperation, responseOperation string, mocks chan<- *models.Mock, reqTimestampMock time.Time) { + meta := map[string]string{ + "type": mockType, + "requestOperation": requestOperation, + "responseOperation": responseOperation, + "connID": ctx.Value(models.ClientConnectionIDKey).(string), + } + mysqlMock := &models.Mock{ + Version: models.GetVersion(), + Kind: models.MySQL, + Name: mockType, + Spec: models.MockSpec{ + Metadata: meta, + MySQLRequests: requests, + MySQLResponses: responses, + Created: time.Now().Unix(), + ReqTimestampMock: reqTimestampMock, + ResTimestampMock: time.Now(), + }, + } + mocks <- mysqlMock +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/replayer/conn.go b/keploy/pkg/core/proxy/integrations/mysql/replayer/conn.go new file mode 100644 index 0000000..b6edfb0 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/replayer/conn.go @@ -0,0 +1,719 @@ +//go:build linux + +package replayer + +import ( + "bufio" + "context" + "fmt" + "io" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + mysqlUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" + intgUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + pTls "go.keploy.io/server/v2/pkg/core/proxy/tls" + pUtils "go.keploy.io/server/v2/pkg/core/proxy/util" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type reqResp struct { + req []mysql.Request + resp []mysql.Response +} + +type handshakeRes struct { + tlsClientConn net.Conn +} + +// Replay mode +func simulateInitialHandshake(ctx context.Context, logger *zap.Logger, clientConn net.Conn, mocks []*models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext, opts models.OutgoingOptions) (handshakeRes, error) { + // Get the mock for initial handshake + initialHandshakeMock := mocks[0] + + // Read the intial request and response for the handshake from the mocks + resp := initialHandshakeMock.Spec.MySQLResponses + req := initialHandshakeMock.Spec.MySQLRequests + + res := handshakeRes{} + reqIdx, respIdx := 0, 0 + + if len(resp) == 0 || len(req) == 0 { + utils.LogError(logger, nil, "no mysql mocks found for initial handshake") + return res, nil + } + + handshake, ok := resp[respIdx].Message.(*mysql.HandshakeV10Packet) + if !ok { + utils.LogError(logger, nil, "failed to assert handshake packet") + return res, nil + } + + // Store the server greetings + decodeCtx.ServerGreetings.Store(clientConn, handshake) + + // Set the intial auth plugin + decodeCtx.PluginName = handshake.AuthPluginName + decodeCtx.ServerCaps = handshake.CapabilityFlags + var err error + + // encode the response + buf, err := wire.EncodeToBinary(ctx, logger, &resp[respIdx].PacketBundle, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to encode handshake packet") + return res, err + } + + // Write the initial handshake to the client + _, err = clientConn.Write(buf) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write server greetings to the client") + + return res, err + } + + respIdx++ + + // Read the client request, (handshake response or ssl request) + handshakeResponseBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + utils.LogError(logger, err, "failed to read handshake response from client") + return res, err + } + + // Decode the handshakeResponse or sslRequest + pkt, err := wire.DecodePayload(ctx, logger, handshakeResponseBuf, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode handshake response from client") + return res, err + } + + // handle the SSL request + if decodeCtx.UseSSL { + _, ok := pkt.Message.(*mysql.SSLRequestPacket) + if !ok { + utils.LogError(logger, nil, "failed to assert SSL request packet") + return res, nil + } + + // Get the SSL request from the mock + _, ok = req[reqIdx].Message.(*mysql.SSLRequestPacket) + if !ok { + utils.LogError(logger, nil, "failed to assert mock SSL request packet", zap.Any("expected", req[reqIdx].Header.Type)) + return res, nil + } + + // Match the SSL request from the client with the mock + err = matchSSLRequest(ctx, logger, req[reqIdx].PacketBundle, *pkt) + if err != nil { + utils.LogError(logger, err, "error while matching SSL request") + return res, err + } + reqIdx++ // matched with the mock so increment the index + + // Upgrade the client connection to TLS + reader := bufio.NewReader(clientConn) + initialData := make([]byte, 5) + // reading the initial data from the client connection to determine if the connection is a TLS handshake + testBuffer, err := reader.Peek(len(initialData)) + if err != nil { + if err == io.EOF && len(testBuffer) == 0 { + logger.Debug("received EOF, closing conn", zap.Error(err)) + return res, nil + } + utils.LogError(logger, err, "failed to peek the mysql request message in proxy") + return res, err + } + + multiReader := io.MultiReader(reader, clientConn) + clientConn = &pUtils.Conn{ + Conn: clientConn, + Reader: multiReader, + Logger: logger, + } + + // handle the TLS connection and get the upgraded client connection + isTLS := pTls.IsTLSHandshake(testBuffer) + if isTLS { + clientConn, err = pTls.HandleTLSConnection(ctx, logger, clientConn, opts.Backdate) + if err != nil { + utils.LogError(logger, err, "failed to handle TLS conn") + return res, err + } + } + + // Update this tls connection information in the handshake result + res.tlsClientConn = clientConn + + // Store (Reset) the last operation for the upgraded client connection, because after ssl request the client will send the handshake response packet again. + decodeCtx.LastOp.Store(clientConn, mysql.HandshakeV10) + + // Store the server greeting packet for the upgraded client connection + decodeCtx.ServerGreetings.Store(clientConn, handshake) + + // read the actual handshake response packet + handshakeResponseBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + utils.LogError(logger, err, "failed to read handshake response from client") + return res, err + } + + // Decode the handshakeResponse + pkt, err = wire.DecodePayload(ctx, logger, handshakeResponseBuf, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode handshake response from client") + return res, err + } + } + + hr41, ok := pkt.Message.(*mysql.HandshakeResponse41Packet) + if !ok { + utils.LogError(logger, nil, "failed to assert actual handshake response packet") + return res, nil + } + decodeCtx.ClientCaps = hr41.CapabilityFlags // live client caps + + // Get the handshake response from the mock + hrec, ok := req[reqIdx].Message.(*mysql.HandshakeResponse41Packet) + if !ok { + utils.LogError(logger, nil, "failed to assert mock handshake response packet") + return res, nil + } + decodeCtx.RecordedClientCaps = hrec.CapabilityFlags + + // Match the handshake response from the client with the mock + logger.Debug("matching handshake response", zap.Any("actual", pkt), zap.Any("mock", req[reqIdx].PacketBundle)) + err = matchHanshakeResponse41(ctx, logger, req[reqIdx].PacketBundle, *pkt) + if err != nil { + utils.LogError(logger, err, "error while matching handshakeResponse41") + return res, err + } + reqIdx++ // matched with the mock so increment the index + + // Get the next response in order to find the auth mechanism + if len(resp) < respIdx+1 { + utils.LogError(logger, nil, "no mysql mocks found for auth mechanism") + return res, nil + } + + // Get the next packet to decide the auth mechanism or auth switching + // For Native password: next packet is Ok/Err + // For CachingSha2 password: next packet is AuthMoreData + + authDecider := resp[respIdx].Header.Type + + // Check if the next packet is AuthSwitchRequest + // Server sends AuthSwitchRequest when it wants to switch the auth mechanism + if authDecider == mysql.AuthStatusToString(mysql.AuthSwitchRequest) { + logger.Debug("Auth switch request found, switching the auth mechanism") + + // Get the AuthSwitchRequest packet + authSwithReqPkt, ok := resp[respIdx].Message.(*mysql.AuthSwitchRequestPacket) + if !ok { + utils.LogError(logger, nil, "failed to assert auth switch request packet") + return res, nil + } + + // Change the auth plugin name + decodeCtx.PluginName = authSwithReqPkt.PluginName + + // Encode the AuthSwitchRequest packet + buf, err = wire.EncodeToBinary(ctx, logger, &resp[respIdx].PacketBundle, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to encode auth switch request packet") + return res, err + } + + // Write the AuthSwitchRequest packet to the client + _, err = clientConn.Write(buf) + if err != nil { + if ctx.Err() != nil { + return res, ctx.Err() + } + utils.LogError(logger, err, "failed to write auth switch request to the client") + return res, err + } + + respIdx++ + + // Read the auth switch response from the client + authSwitchRespBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + utils.LogError(logger, err, "failed to read auth switch response from the client") + return res, err + } + + // Get the packet from the buffer + authSwitchRespPkt, err := mysqlUtils.BytesToMySQLPacket(authSwitchRespBuf) + if err != nil { + utils.LogError(logger, err, "failed to convert auth switch response to packet") + return res, err + } + + if len(req) < reqIdx+1 { + utils.LogError(logger, nil, "no mysql mocks found for auth switch response") + return res, fmt.Errorf("no mysql mocks found for auth switch response") + } + + // Get the auth switch response from the mock + authSwitchRespMock := req[reqIdx].PacketBundle + + if authSwitchRespMock.Header.Type != mysql.AuthSwithResponse { + utils.LogError(logger, nil, "expected auth switch response mock not found", zap.Any("found", authSwitchRespMock.Header.Type)) + return res, fmt.Errorf("expected %s but found %s", mysql.AuthSwithResponse, authSwitchRespMock.Header.Type) + } + + // Since auth switch response data can be different, we should just check the sequence number + if authSwitchRespMock.Header.Header.SequenceID != authSwitchRespPkt.Header.SequenceID { + utils.LogError(logger, nil, "sequence number mismatch for auth switch response", zap.Any("expected", authSwitchRespMock.Header.Header.SequenceID), zap.Any("actual", authSwitchRespPkt.Header.SequenceID)) + return res, fmt.Errorf("sequence number mismatch for auth switch response") + } + + logger.Debug("auth mechanism switched successfully") + + reqIdx++ + + // Get the next packet to decide the auth mechanism + if len(resp) < respIdx+1 { + utils.LogError(logger, nil, "no mysql mocks found for auth mechanism after auth switch request") + return res, nil + } + + authDecider = resp[respIdx].Header.Type + } + + switch authDecider { + case mysql.StatusToString(mysql.OK): + var nativePassMocks reqResp + nativePassMocks.resp = resp[respIdx:] + + // It means we need to simulate the native password + err := simulateNativePassword(ctx, logger, clientConn, nativePassMocks, initialHandshakeMock, mockDb, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to simulate native password") + return res, err + } + + case mysql.AuthStatusToString(mysql.AuthMoreData): + + var cacheSha2PassMock reqResp + cacheSha2PassMock.req = req[reqIdx:] + cacheSha2PassMock.resp = resp[respIdx:] + + // It means we need to simulate the caching_sha2_password + err := simulateCacheSha2Password(ctx, logger, clientConn, cacheSha2PassMock, initialHandshakeMock, mockDb, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to simulate caching_sha2_password") + return res, err + } + } + + return res, nil +} + +func simulateNativePassword(ctx context.Context, logger *zap.Logger, clientConn net.Conn, nativePassMocks reqResp, initialHandshakeMock *models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) error { + + logger.Debug("final response for native password", zap.Any("response", nativePassMocks.resp[0].Header.Type)) + + // Send the final response (OK/Err) to the client + buf, err := wire.EncodeToBinary(ctx, logger, &nativePassMocks.resp[0].PacketBundle, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to encode final response packet for native password") + return err + } + + _, err = clientConn.Write(buf) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(logger, err, "failed to write final response for native password to the client") + return err + } + + //update the config mock (since it can be reused in case of more connections compared to record mode) + ok := updateMock(ctx, logger, initialHandshakeMock, mockDb) + if !ok { + utils.LogError(logger, nil, "failed to update the mock unfiltered mock during native password") + } + + logger.Debug("native password completed successfully") + + return nil +} + +func simulateCacheSha2Password(ctx context.Context, logger *zap.Logger, clientConn net.Conn, cacheSha2PassMock reqResp, initialHandshakeMock *models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) error { + resp := cacheSha2PassMock.resp + + // Get the AuthMoreData + if len(resp) < 1 { + utils.LogError(logger, nil, "no mysql mocks found for auth more data") + } + + //check if the response is of type AuthMoreData + if _, ok := resp[0].Message.(*mysql.AuthMoreDataPacket); !ok { + utils.LogError(logger, nil, "failed to assert auth more data packet") + return fmt.Errorf("failed to get auth more data packet, expected %T but got %T", mysql.AuthMoreDataPacket{}, resp[0].Message) + } + + // Get the auth more data packet + pkt, ok := resp[0].Message.(*mysql.AuthMoreDataPacket) + if !ok { + utils.LogError(logger, nil, "failed to assert auth more data packet") + return nil + } + + var mechanismString string + CachingSha2PasswordMechanism := pkt.Data + + logger.Debug("[DEBUG] CachingSha2PasswordMechanism CachingSha2PasswordMechanism", zap.String("mechanism", CachingSha2PasswordMechanism), zap.Binary("mechanismBytes", []byte(CachingSha2PasswordMechanism))) + + if len(CachingSha2PasswordMechanism) == 1 { + // CachingSha2PasswordMechanism single byte -> map to symbolic string + b := CachingSha2PasswordMechanism[0] + logger.Debug("[DEBUG] CachingSha2PasswordMechanism byte value", zap.Uint8("mechanismByte", b)) + mechanismString = mysql.CachingSha2PasswordToString(mysql.CachingSha2Password(b)) + } else if len(CachingSha2PasswordMechanism) > 1 { + // already symbolic + mechanismString = CachingSha2PasswordMechanism + } else { + mechanismString = "UNKNOWN" + } + logger.Debug("[DEBUG] CachingSha2PasswordMechanism normalized", zap.String("mechanismString", mechanismString)) + + authBuf, err := wire.EncodeToBinary(ctx, logger, &resp[0].PacketBundle, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to encode auth more data packet") + return err + } + + // Write the AuthMoreData packet to the client + _, err = clientConn.Write(authBuf) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(logger, err, "failed to write auth more data or auth switch request to the client") + return err + } + + if len(cacheSha2PassMock.resp) < 2 { + utils.LogError(logger, nil, "response mock not found for caching_sha2_password after auth more data") + return fmt.Errorf("response mock not found for caching_sha2_password after auth more data") + } + + //update the cacheSha2PassMock resp + cacheSha2PassMock.resp = cacheSha2PassMock.resp[1:] + + //simulate the caching_sha2_password auth mechanism + switch mechanismString { + case mysql.CachingSha2PasswordToString(mysql.PerformFullAuthentication): + err := simulateFullAuth(ctx, logger, clientConn, cacheSha2PassMock, initialHandshakeMock, mockDb, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to simulate full auth") + return err + } + case mysql.CachingSha2PasswordToString(mysql.FastAuthSuccess): + err := simulateFastAuthSuccess(ctx, logger, clientConn, cacheSha2PassMock, initialHandshakeMock, mockDb, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to simulate fast auth success") + return err + } + default: + // return an error + utils.LogError(logger, nil, "unknown caching_sha2_password mechanism", zap.String("mechanism", mechanismString)) + return fmt.Errorf("unknown caching_sha2_password mechanism: %s", mechanismString) + } + return nil +} + +func simulateFastAuthSuccess(ctx context.Context, logger *zap.Logger, clientConn net.Conn, fastAuthMocks reqResp, initialHandshakeMock *models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) error { + resp := fastAuthMocks.resp + + if len(resp) < 1 { + utils.LogError(logger, nil, "final response mock not found for fast auth success") + return fmt.Errorf("final response mock not found for fast auth success") + } + + logger.Debug("final response for fast auth success", zap.Any("response", resp[0].Header.Type)) + + // Send the final response (OK/Err) to the client + buf, err := wire.EncodeToBinary(ctx, logger, &resp[0].PacketBundle, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to encode final response packet for fast auth success") + return err + } + + _, err = clientConn.Write(buf) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(logger, err, "failed to write final response for fast auth success to the client") + return err + } + + //update the config mock (since it can be reused in case of more connections compared to record mode) + //TODO: need to check when updateMock is unsuccessful + ok := updateMock(ctx, logger, initialHandshakeMock, mockDb) + if !ok { + utils.LogError(logger, nil, "failed to update the mock unfiltered mock during fast auth success") + } + + logger.Debug("fast auth success completed successfully") + + return nil +} + +func simulateFullAuth(ctx context.Context, logger *zap.Logger, clientConn net.Conn, fullAuthMocks reqResp, initialHandshakeMock *models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) error { + + resp := fullAuthMocks.resp + req := fullAuthMocks.req + + if decodeCtx.UseSSL { + logger.Debug("This is an ssl request, simulating plain password in caching_sha2_password full auth") + err := simulatePlainPassword(ctx, logger, clientConn, fullAuthMocks, initialHandshakeMock, mockDb, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to simulate plain password in caching_sha2_password full auth") + return err + } + return nil + } + + // read the public key request from the client + publicKeyRequestBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + utils.LogError(logger, err, "failed to read public key request from client") + return err + } + + // decode the public key request + pkt, err := wire.DecodePayload(ctx, logger, publicKeyRequestBuf, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode public key request from client") + return err + } + + publicKey, ok := pkt.Message.(string) + if !ok { + utils.LogError(logger, nil, "failed to assert public key request packet") + return nil + } + + // Get the public key response from the mock + if len(req) < 1 { + utils.LogError(logger, nil, "no mysql mocks found for public key response") + return fmt.Errorf("no mysql mocks found for public key response") + } + + publicKeyMock, ok := req[0].Message.(string) + if !ok { + utils.LogError(logger, nil, "failed to assert public key response packet") + return nil + } + + // Match the header of the public key request + ok = matchHeader(*req[0].Header.Header, *pkt.Header.Header) + if !ok { + utils.LogError(logger, nil, "header mismatch for public key request", zap.Any("expected", req[0].Header.Header), zap.Any("actual", pkt.Header.Header)) + return nil + } + + // Match the public key response from the client with the mock + if publicKey != publicKeyMock { + utils.LogError(logger, nil, "public key mismatch", zap.Any("actual", publicKey), zap.Any("expected", publicKeyMock)) + return fmt.Errorf("public key mismatch") + } + + // Get the AuthMoreData for sending the public key + if len(resp) < 1 { + utils.LogError(logger, nil, "no mysql mocks found for auth more data (public key)") + return fmt.Errorf("no mysql mocks found for auth more data (public key)") + } + + // Get the AuthMoreData packet + _, ok = resp[0].Message.(*mysql.AuthMoreDataPacket) + if !ok { + utils.LogError(logger, nil, "failed to assert auth more data packet (public key)") + return nil + } + + // encode the public key response + buf, err := wire.EncodeToBinary(ctx, logger, &resp[0].PacketBundle, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to encode public key response packet") + return err + } + + // Write the public key response to the client + _, err = clientConn.Write(buf) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(logger, err, "failed to write public key response to the client") + return err + } + + // Read the encrypted password from the client + + encryptedPasswordBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + utils.LogError(logger, err, "failed to read encrypted password from client") + return err + } + + // Get the packet from the buffer + encryptedPassPkt, err := mysqlUtils.BytesToMySQLPacket(encryptedPasswordBuf) + if err != nil { + utils.LogError(logger, err, "failed to convert encrypted password to packet") + return err + } + + if len(req) < 2 { + utils.LogError(logger, nil, "no mysql mocks found for encrypted password during full auth") + return fmt.Errorf("no mysql mocks found for encrypted password during full auth") + } + + // Get the encrypted password from the mock + encryptedPassMock := req[1].PacketBundle + + if encryptedPassMock.Header.Type != mysql.EncryptedPassword { + utils.LogError(logger, nil, "expected encrypted password mock not found", zap.Any("found", encryptedPassMock.Header.Type)) + return fmt.Errorf("expected %s but found %s", mysql.EncryptedPassword, encryptedPassMock.Header.Type) + } + + // Since encrypted password can be different, we should just check the sequence number + if encryptedPassMock.Header.Header.SequenceID != encryptedPassPkt.Header.SequenceID { + utils.LogError(logger, nil, "sequence number mismatch for encrypted password", zap.Any("expected", encryptedPassMock.Header.Header.SequenceID), zap.Any("actual", encryptedPassPkt.Header.SequenceID)) + return fmt.Errorf("sequence number mismatch for encrypted password") + } + + //Now send the final response (OK/Err) to the client + if len(resp) < 2 { + utils.LogError(logger, nil, "final response mock not found for full auth") + return fmt.Errorf("final response mock not found for full auth") + } + + logger.Debug("final response for full auth", zap.Any("response", resp[1].Header.Type)) + + // Get the final response (OK/Err) from the mock + // Send the final response (OK/Err) to the client + buf, err = wire.EncodeToBinary(ctx, logger, &resp[1].PacketBundle, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to encode final response packet for full auth") + return err + } + + _, err = clientConn.Write(buf) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(logger, err, "failed to write final response for full auth to the client") + return err + } + + // FullAuth mechanism only comes for the first time unless COM_CHANGE_USER is called (that is not supported for now). + // Afterwards only fast auth success is expected. So, we can delete this. + ok = mockDb.DeleteUnFilteredMock(*initialHandshakeMock) + // TODO: need to check what to do in this case + if !ok { + utils.LogError(logger, nil, "failed to delete unfiltered mock during full auth") + } + + logger.Debug("full auth completed successfully") + + return nil +} + +func simulatePlainPassword(ctx context.Context, logger *zap.Logger, clientConn net.Conn, fullAuthMocks reqResp, initialHandshakeMock *models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) error { + + req := fullAuthMocks.req + resp := fullAuthMocks.resp + + // read the plain password from the client + plainPassBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + utils.LogError(logger, err, "failed to read plain password from client") + return err + } + + // Get the packet from the buffer + plainPassPkt, err := mysqlUtils.BytesToMySQLPacket(plainPassBuf) + if err != nil { + utils.LogError(logger, err, "failed to convert plain password to packet") + return err + } + + plainPass := string(intgUtils.EncodeBase64(plainPassPkt.Payload)) + + // Get the plain password from the mock + if len(req) < 1 { + utils.LogError(logger, nil, "no mysql mocks found for plain password") + return fmt.Errorf("no mysql mocks found for plain password") + } + + plainPassMock, ok := req[0].Message.(string) + if !ok { + utils.LogError(logger, nil, "failed to assert plain password packet") + return fmt.Errorf("failed to assert plain password packet") + } + + // Match the header of the plain password + ok = matchHeader(*req[0].Header.Header, plainPassPkt.Header) + if !ok { + utils.LogError(logger, nil, "header mismatch for plain password", zap.Any("expected", req[0].Header.Header), zap.Any("actual", plainPassPkt.Header)) + return fmt.Errorf("header mismatch for plain password") + } + + // Match the plain password from the client with the mock + if plainPass != plainPassMock { + utils.LogError(logger, nil, "plain password mismatch", zap.Any("actual", plainPass), zap.Any("expected", plainPassMock)) + return fmt.Errorf("plain password mismatch") + } + + //Now send the final response (OK/Err) to the client + if len(resp) < 1 { + utils.LogError(logger, nil, "final response mock not found for full auth (plain password)") + return fmt.Errorf("final response mock not found for full auth (plain password)") + } + + logger.Debug("final response for full auth(plain password)", zap.Any("response", resp[0].Header.Type)) + + // Get the final response (OK/Err) from the mock + // Send the final response (OK/Err) to the client + buf, err := wire.EncodeToBinary(ctx, logger, &resp[0].PacketBundle, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to encode final response packet for full auth (plain password)") + return err + } + + _, err = clientConn.Write(buf) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + utils.LogError(logger, err, "failed to write final response for full auth (plain password) to the client") + return err + } + + // FullAuth mechanism only comes for the first time unless COM_CHANGE_USER is called (that is not supported for now). + // Afterwards only fast auth success is expected. So, we can delete this. + ok = mockDb.DeleteUnFilteredMock(*initialHandshakeMock) + // TODO: need to check what to do in this case + if !ok { + utils.LogError(logger, nil, "failed to delete unfiltered mock during full auth (plain password) in ssl request") + } + + logger.Debug("full auth (plain-password) in ssl request completed successfully") + return nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/replayer/match.go b/keploy/pkg/core/proxy/integrations/mysql/replayer/match.go new file mode 100644 index 0000000..298a575 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/replayer/match.go @@ -0,0 +1,749 @@ +//go:build linux + +package replayer + +import ( + "bytes" + "context" + "fmt" + "io" + "reflect" + "strings" + "sync" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "vitess.io/vitess/go/vt/sqlparser" +) + +var querySigCache sync.Map // map[string]string + +// case-insensitive prefix check without allocation +func hasPrefixFold(s, p string) bool { + if len(s) < len(p) { + return false + } + return strings.EqualFold(s[:len(p)], p) +} + +func getQueryStructureCached(sql string) (string, error) { + if v, ok := querySigCache.Load(sql); ok { + return v.(string), nil + } + sig, err := getQueryStructure(sql) + if err == nil { + querySigCache.Store(sql, sig) + } + return sig, err +} + +func matchHeader(expected, actual mysql.Header) bool { + + // Match the payloadlength + if actual.PayloadLength != expected.PayloadLength { + return false + } + + // Match the sequence number + if actual.SequenceID != expected.SequenceID { + return false + } + + return true +} + +func matchSSLRequest(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) error { + // Match the type + if expected.Header.Type != actual.Header.Type { + return fmt.Errorf("type mismatch for ssl request") + } + + //Don't match the header, because the payload length can be different. + + // Match the payload + expectedMessage, _ := expected.Message.(*mysql.SSLRequestPacket) + actualMessage, _ := actual.Message.(*mysql.SSLRequestPacket) + + // Match the MaxPacketSize + if expectedMessage.MaxPacketSize != actualMessage.MaxPacketSize { + return fmt.Errorf("max packet size mismatch for ssl request, expected: %d, actual: %d", expectedMessage.MaxPacketSize, actualMessage.MaxPacketSize) + } + + // Match the CharacterSet + if expectedMessage.CharacterSet != actualMessage.CharacterSet { + return fmt.Errorf("character set mismatch for ssl request, expected: %d, actual: %d", expectedMessage.CharacterSet, actualMessage.CharacterSet) + } + + // Match the Filler + if expectedMessage.Filler != actualMessage.Filler { + return fmt.Errorf("filler mismatch for ssl request, expected: %v, actual: %v", expectedMessage.Filler, actualMessage.Filler) + } + + return nil +} + +func matchHanshakeResponse41(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) error { + // Match the type + if expected.Header.Type != actual.Header.Type { + return fmt.Errorf("type mismatch for handshake response") + } + + //Don't match the header, because the payload length can be different. + + // Match the payload + + //Get the packet type from both the packet bundles + // we don't need to do type assertion because its already done in the caller function + + exp := expected.Message.(*mysql.HandshakeResponse41Packet) + act := actual.Message.(*mysql.HandshakeResponse41Packet) + + // // Match the CapabilityFlags + // if exp.CapabilityFlags != act.CapabilityFlags { + // return fmt.Errorf("capability flags mismatch for handshake response, expected: %d, actual: %d", exp.CapabilityFlags, act.CapabilityFlags) + // } + + // Match the MaxPacketSize + if exp.MaxPacketSize != act.MaxPacketSize { + return fmt.Errorf("max packet size mismatch for handshake response, expected: %d, actual: %d", exp.MaxPacketSize, act.MaxPacketSize) + } + + // Match the CharacterSet + if exp.CharacterSet != act.CharacterSet { + return fmt.Errorf("character set mismatch for handshake response, expected: %d, actual: %d", exp.CharacterSet, act.CharacterSet) + } + + // Match the Filler + if exp.Filler != act.Filler { + return fmt.Errorf("filler mismatch for handshake response, expected: %v, actual: %v", exp.Filler, act.Filler) + } + + // Match the Username + if exp.Username != act.Username { + return fmt.Errorf("username mismatch for handshake response, expected: %s, actual: %s", exp.Username, act.Username) + } + + // Match the AuthResponse + if !bytes.Equal(exp.AuthResponse, act.AuthResponse) { + return fmt.Errorf("auth response mismatch for handshake response, expected: %v, actual: %v", exp.AuthResponse, act.AuthResponse) + } + + // Match the Database (backward-compatible: ignore old mocks with junk bytes / off-by-one) + if !dbEqualCompat(exp.Database, act.Database) { + return fmt.Errorf("database mismatch for handshake response, expected: %s, actual: %s", printable(exp.Database), printable(act.Database)) + } + + // Match the AuthPluginName (tolerate unknown/garbled plugin names in old mocks) + if !pluginEqualCompat(exp.AuthPluginName, act.AuthPluginName) { + return fmt.Errorf("auth plugin name mismatch for handshake response, expected: %s, actual: %s", printable(exp.AuthPluginName), printable(act.AuthPluginName)) + } + + // // Match the ConnectionAttributes + // if len(exp.ConnectionAttributes) != len(act.ConnectionAttributes) { + // return fmt.Errorf("connection attributes length mismatch for handshake response, expected: %d, actual: %d", len(exp.ConnectionAttributes), len(act.ConnectionAttributes)) + // } + + // for key, value := range exp.ConnectionAttributes { + // if act.ConnectionAttributes[key] != value && key != "_pid" { + // return fmt.Errorf("connection attributes mismatch for handshake response, expected: %s, actual: %s", value, act.ConnectionAttributes[key]) + // } + // } + + // Match the ZstdCompressionLevel + if exp.ZstdCompressionLevel != act.ZstdCompressionLevel { + return fmt.Errorf("zstd compression level mismatch for handshake response") + } + + return nil +} + +func matchCommand(ctx context.Context, logger *zap.Logger, req mysql.Request, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) (*mysql.Response, bool, error) { + // Precompute string constants once (avoid frequent map lookups) + var ( + sCOM_QUIT = mysql.CommandStatusToString(mysql.COM_QUIT) + sCOM_QUERY = mysql.CommandStatusToString(mysql.COM_QUERY) + sCOM_STMT_PREP = mysql.CommandStatusToString(mysql.COM_STMT_PREPARE) + sCOM_STMT_EXEC = mysql.CommandStatusToString(mysql.COM_STMT_EXECUTE) + sCOM_STMT_CLOSE = mysql.CommandStatusToString(mysql.COM_STMT_CLOSE) + sCOM_INIT_DB = mysql.CommandStatusToString(mysql.COM_INIT_DB) + sCOM_STATS = mysql.CommandStatusToString(mysql.COM_STATISTICS) + sCOM_DEBUG = mysql.CommandStatusToString(mysql.COM_DEBUG) + sCOM_PING = mysql.CommandStatusToString(mysql.COM_PING) + sCOM_RESET_CONN = mysql.CommandStatusToString(mysql.COM_RESET_CONNECTION) + ) + + // Fast path: QUIT may have no mock + if req.Header.Type == sCOM_QUIT { + return nil, false, io.EOF + } + + // Single fetch; no struct copies (see MockManager changes) + unfiltered, err := mockDb.GetUnFilteredMocks() + if err != nil { + if ctx.Err() != nil { + return nil, false, ctx.Err() + } + utils.LogError(logger, err, "failed to get unfiltered mocks") + return nil, false, err + } + if len(unfiltered) == 0 { + utils.LogError(logger, nil, "no mysql mocks found") + return nil, false, fmt.Errorf("no mysql mocks found") + } + + var ( + maxMatchedCount int + matchedResp *mysql.Response + matchedMock *models.Mock + queryMatched bool + ) + + // Single pass: filter & match on the fly. + for _, mock := range unfiltered { + if mock.Kind != models.MySQL { + continue + } + if mock.Spec.Metadata["type"] == "config" { + continue // command-phase only wants data mocks + } + for _, mockReq := range mock.Spec.MySQLRequests { + select { + case <-ctx.Done(): + return nil, false, ctx.Err() + default: + } + switch req.Header.Type { + case sCOM_STMT_CLOSE: + if c := matchClosePacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { + maxMatchedCount, matchedResp, matchedMock = c, &mysql.Response{}, mock + } + case sCOM_QUERY: + if ok, c := matchQueryPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); ok { + matchedResp, matchedMock, queryMatched = &mock.Spec.MySQLResponses[0], mock, true + } else if c > maxMatchedCount { + maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock + } + case sCOM_STMT_PREP: + if ok, c := matchPreparePacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); ok { + matchedResp, matchedMock, queryMatched = &mock.Spec.MySQLResponses[0], mock, true + } else if c > maxMatchedCount { + maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock + } + case sCOM_STMT_EXEC: + if c := matchStmtExecutePacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { + maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock + } + case sCOM_INIT_DB: + if c := matchInitDbPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { + maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock + } + case sCOM_STATS: + if c := matchStatisticsPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { + maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock + } + case sCOM_DEBUG: + if c := matchDebugPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { + maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock + } + case sCOM_PING: + if c := matchPingPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { + maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock + } + case sCOM_RESET_CONN: + if c := matchResetConnectionPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { + maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock + } + } + } + if queryMatched { + break + } + } + + if matchedResp == nil { + // Graceful generic OK for common control statements (no mocks) + if req.Header.Type == sCOM_QUERY { + if qp, ok := req.Message.(*mysql.QueryPacket); ok { + q := strings.TrimSpace(qp.Query) + switch { + case strings.EqualFold(q, "BEGIN"), + strings.EqualFold(q, "START TRANSACTION"), + strings.EqualFold(q, "COMMIT"), + strings.EqualFold(q, "ROLLBACK"), + hasPrefixFold(q, "SET "), + // NEW: DDL/control that only expects an OK from server + hasPrefixFold(q, "ALTER "), + hasPrefixFold(q, "CREATE "), + hasPrefixFold(q, "DROP "), + hasPrefixFold(q, "TRUNCATE "), + hasPrefixFold(q, "RENAME "), + hasPrefixFold(q, "LOCK TABLES"), + hasPrefixFold(q, "UNLOCK TABLES"), + hasPrefixFold(q, "SAVEPOINT "), + hasPrefixFold(q, "RELEASE SAVEPOINT "), + hasPrefixFold(q, "USE "): + // Build a minimal OK; encoder will set length from payload. + seq := byte(1) + if req.PacketBundle.Header != nil && req.PacketBundle.Header.Header != nil { + seq = req.PacketBundle.Header.Header.SequenceID + 1 + } + generic := &mysql.Response{ + PacketBundle: mysql.PacketBundle{ + Header: &mysql.PacketInfo{ + Header: &mysql.Header{PayloadLength: 7, SequenceID: seq}, + Type: mysql.StatusToString(mysql.OK), + }, + Message: &mysql.OKPacket{ + Header: mysql.OK, + AffectedRows: 0, + LastInsertID: 0, + StatusFlags: 0x0002, + Warnings: 0, + Info: "", + }, + }, + } + logger.Debug("Returning synthetic OK for unmocked control/DDL", zap.String("query", q)) + + return generic, true, nil + } + } + } + return nil, false, nil + } + + // Persist prepared-statement metadata + if req.Header.Type == sCOM_STMT_PREP { + if prepareOkResp, ok := matchedResp.Message.(*mysql.StmtPrepareOkPacket); ok && prepareOkResp != nil { + decodeCtx.PreparedStatements[prepareOkResp.StatementID] = prepareOkResp + } + } + + if okk := updateMock(ctx, logger, matchedMock, mockDb); !okk { + logger.Debug("failed to update the matched mock") + // Re-fetch once to avoid spin + return nil, false, fmt.Errorf("failed to update matched mock") + } + return matchedResp, true, nil +} + +func matchClosePacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { + matchCount := 0 + // Match the type and return zero if the types are not equal + if expected.Header.Type != actual.Header.Type { + return 0 + } + // Match the header + ok := matchHeader(*expected.Header.Header, *actual.Header.Header) + if ok { + matchCount += 2 + } + expectedMessage, _ := expected.Message.(*mysql.StmtClosePacket) + actualMessage, _ := actual.Message.(*mysql.StmtClosePacket) + // Match the statementID + if expectedMessage.StatementID == actualMessage.StatementID { + matchCount++ + } + return matchCount +} + +func getQueryStructure(sql string) (string, error) { + + opts := sqlparser.Options{} + parser, err := sqlparser.New(opts) + if err != nil { + return "", fmt.Errorf("failed to create MYSQL query parser: %w", err) + } + + stmt, err := parser.Parse(sql) + if err != nil { + return "", fmt.Errorf("failed to parse SQL: %w", err) + } + + var structureParts []string + // Walk the AST and collect the Go type of each grammatical node. + err = sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) { + structureParts = append(structureParts, reflect.TypeOf(node).String()) + return true, nil + }, stmt) + + if err != nil { + return "", fmt.Errorf("failed to walk the AST: %w", err) + } + + return strings.Join(structureParts, "->"), nil +} + +func matchQuery(_ context.Context, log *zap.Logger, expected, actual mysql.PacketBundle, getQuery func(packet mysql.PacketBundle) string) (bool, int) { + matchCount := 0 + + // Match the type and return zero if the types are not equal + if expected.Header.Type != actual.Header.Type { + return false, 0 + } + + expectedQuery := getQuery(expected) + actualQuery := getQuery(actual) + + if actual.Header.Header.PayloadLength == expected.Header.Header.PayloadLength { + matchCount++ + if expectedQuery == actualQuery { + matchCount++ + log.Debug("Query Exact matched", + zap.String("expected query", expectedQuery), + zap.String("actual query", actualQuery)) + return true, matchCount + } + } + + // check if any of them the query is dml and other is not, then there is no match. + if sqlparser.IsDML(expectedQuery) && !sqlparser.IsDML(actualQuery) { + log.Debug("expected query is dml but actual is not", + zap.String("expected query", expectedQuery), + zap.String("actual query", actualQuery)) + return false, 0 + } else if !sqlparser.IsDML(expectedQuery) && sqlparser.IsDML(actualQuery) { + log.Debug("actual query is dml but expected is not", + zap.String("expected query", expectedQuery), + zap.String("actual query", actualQuery)) + return false, 0 + } + + if !(sqlparser.IsDML(expectedQuery) && sqlparser.IsDML(actualQuery)) { + log.Debug("No Query is dml", + zap.String("expected query", expectedQuery), + zap.String("actual query", actualQuery)) + return false, matchCount + } + + // Here we can compare the structure of the queries, as both are DML queries. + log.Debug("Both queries are DML", + zap.String("expected query", expectedQuery), + zap.String("actual query", actualQuery)) + + actualSignature, err := getQueryStructureCached(actualQuery) + if err != nil { + log.Warn("failed to get actual query structure", + zap.String("actual Query", actualQuery), + zap.Error(err)) + return false, matchCount + } + + expectedSignature, err := getQueryStructureCached(expectedQuery) + if err != nil { + log.Warn("failed to get expected query structure", + zap.String("expected Query", expectedQuery), + zap.Error(err)) + return false, matchCount + } + + if expectedSignature == actualSignature { + log.Debug("query structure matched", + zap.String("expected signature", expectedSignature), + zap.String("actual signature", actualSignature)) + return true, matchCount + } + + return false, matchCount +} + +func matchQueryPacket(ctx context.Context, log *zap.Logger, expected, actual mysql.PacketBundle) (bool, int) { + getQuery := func(packet mysql.PacketBundle) string { + msg, _ := packet.Message.(*mysql.QueryPacket) + return msg.Query + } + return matchQuery(ctx, log, expected, actual, getQuery) +} + +func matchPreparePacket(ctx context.Context, log *zap.Logger, expected, actual mysql.PacketBundle) (bool, int) { + getQuery := func(packet mysql.PacketBundle) string { + msg, _ := packet.Message.(*mysql.StmtPreparePacket) + return msg.Query + } + return matchQuery(ctx, log, expected, actual, getQuery) +} + +func matchStmtExecutePacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { + matchCount := 0 + + // Match the type and return zero if the types are not equal + if expected.Header.Type != actual.Header.Type { + return 0 + } + // Match the header + if matchHeader(*expected.Header.Header, *actual.Header.Header) { + matchCount += 2 + } + expectedMessage, _ := expected.Message.(*mysql.StmtExecutePacket) + actualMessage, _ := actual.Message.(*mysql.StmtExecutePacket) + // Match the status + if expectedMessage.Status == actualMessage.Status { + matchCount++ + } + // Match the statementID + if expectedMessage.StatementID == actualMessage.StatementID { + matchCount++ + } + // Match the flags + if expectedMessage.Flags == actualMessage.Flags { + matchCount++ + } + // Match the iteration count + if expectedMessage.IterationCount == actualMessage.IterationCount { + matchCount++ + } + // Match the parameter count + if expectedMessage.ParameterCount == actualMessage.ParameterCount { + matchCount++ + } + + // Match the newParamsBindFlag + if expectedMessage.NewParamsBindFlag == actualMessage.NewParamsBindFlag { + matchCount++ + } + + // Match the parameters + if len(expectedMessage.Parameters) == len(actualMessage.Parameters) { + for i := range expectedMessage.Parameters { + ep := expectedMessage.Parameters[i] + ap := actualMessage.Parameters[i] + if ep.Type == ap.Type && + ep.Name == ap.Name && + ep.Unsigned == ap.Unsigned && + paramValueEqual(ep.Value, ap.Value) { + matchCount++ + } + } + } + return matchCount +} + +func paramValueEqual(a, b interface{}) bool { + switch av := a.(type) { + case []byte: + bv, ok := b.([]byte) + return ok && bytes.Equal(av, bv) + case string: + bv, ok := b.(string) + return ok && av == bv + case int: + bv, ok := b.(int) + return ok && av == bv + case int32: + bv, ok := b.(int32) + return ok && av == bv + case int64: + switch bv := b.(type) { + case int64: + return av == bv + case int: + return av == int64(bv) + } + case uint32: + bv, ok := b.(uint32) + return ok && av == bv + case uint64: + bv, ok := b.(uint64) + return ok && av == bv + case float32: + bv, ok := b.(float32) + return ok && av == bv + case float64: + bv, ok := b.(float64) + return ok && av == bv + case bool: + bv, ok := b.(bool) + return ok && av == bv + } + // Fallback (rare) + return reflect.DeepEqual(a, b) +} + +// matching for utility commands +func matchQuitPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { + matchCount := 0 + // Match the type and return zero if the types are not equal + if expected.Header.Type != actual.Header.Type { + return 0 + } + // Match the header + if matchHeader(*expected.Header.Header, *actual.Header.Header) { + matchCount += 2 + } + expectedMessage, _ := expected.Message.(*mysql.QuitPacket) + actualMessage, _ := actual.Message.(*mysql.QuitPacket) + // Match the command for quit packet + if expectedMessage.Command == actualMessage.Command { + matchCount++ + } + return matchCount +} + +func matchInitDbPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { + matchCount := 0 + // Match the type and return zero if the types are not equal + if expected.Header.Type != actual.Header.Type { + return 0 + } + // Match the header + if matchHeader(*expected.Header.Header, *actual.Header.Header) { + matchCount += 2 + } + expectedMessage, _ := expected.Message.(*mysql.InitDBPacket) + actualMessage, _ := actual.Message.(*mysql.InitDBPacket) + // Match the command for init db packet + if expectedMessage.Command == actualMessage.Command { + matchCount++ + } + // Match the schema for init db packet + if expectedMessage.Schema == actualMessage.Schema { + matchCount++ + } + return matchCount +} + +func matchStatisticsPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { + matchCount := 0 + // Match the type and return zero if the types are not equal + if expected.Header.Type != actual.Header.Type { + return 0 + } + // Match the header + if matchHeader(*expected.Header.Header, *actual.Header.Header) { + matchCount += 2 + } + expectedMessage, _ := expected.Message.(*mysql.StatisticsPacket) + actualMessage, _ := actual.Message.(*mysql.StatisticsPacket) + // Match the command for statistics packet + if expectedMessage.Command == actualMessage.Command { + matchCount++ + } + return matchCount +} + +func matchDebugPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { + matchCount := 0 + // Match the type and return zero if the types are not equal + if expected.Header.Type != actual.Header.Type { + return 0 + } + // Match the header + if matchHeader(*expected.Header.Header, *actual.Header.Header) { + matchCount += 2 + } + expectedMessage, _ := expected.Message.(*mysql.DebugPacket) + actualMessage, _ := actual.Message.(*mysql.DebugPacket) + // Match the command for debug packet + if expectedMessage.Command == actualMessage.Command { + matchCount++ + } + return matchCount +} + +func matchPingPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { + matchCount := 0 + // Match the type and return zero if the types are not equal + if expected.Header.Type != actual.Header.Type { + return 0 + } + // Match the header + if matchHeader(*expected.Header.Header, *actual.Header.Header) { + matchCount += 2 + } + expectedMessage, _ := expected.Message.(*mysql.PingPacket) + actualMessage, _ := actual.Message.(*mysql.PingPacket) + // Match the command for ping packet + if expectedMessage.Command == actualMessage.Command { + matchCount++ + } + return matchCount +} + +func matchResetConnectionPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { + matchCount := 0 + // Match the type and return zero if the types are not equal + if expected.Header.Type != actual.Header.Type { + return 0 + } + // Match the header + if matchHeader(*expected.Header.Header, *actual.Header.Header) { + matchCount += 2 + } + expectedMessage, _ := expected.Message.(*mysql.ResetConnectionPacket) + actualMessage, _ := actual.Message.(*mysql.ResetConnectionPacket) + // Match the command for reset connection packet + if expectedMessage.Command == actualMessage.Command { + matchCount++ + } + return matchCount +} + +// The same function is used in http parser as well, If you find this useful you can extract it to a common package +// and delete the duplicate code. +// updateMock processes the matched mock based on its filtered status. +func updateMock(_ context.Context, logger *zap.Logger, matchedMock *models.Mock, mockDb integrations.MockMemDb) bool { + originalMatchedMock := *matchedMock + matchedMock.TestModeInfo.IsFiltered = false + matchedMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() + updated := mockDb.UpdateUnFilteredMock(&originalMatchedMock, matchedMock) + if !updated { + logger.Debug("failed to update matched mock") + } + return updated +} + +// printable strips non-printable bytes (common in legacy mocks) +func printable(s string) string { + var b strings.Builder + for _, r := range s { + if r >= 32 && r <= 126 { + b.WriteRune(r) + } + } + return b.String() +} + +// Back-compat database compare: +// - Strip junk bytes +// - Treat empty on either side as OK +// - Allow suffix match (e.g., "...uss" vs "ss") to accommodate off-by-one legacy encodes +func dbEqualCompat(exp, act string) bool { + ex := printable(strings.TrimSpace(exp)) + ac := printable(strings.TrimSpace(act)) + if ex == ac { + return true + } + if ex == "" || ac == "" { + return true + } + return strings.HasSuffix(ex, ac) || strings.HasSuffix(ac, ex) +} + +var knownPlugins = map[string]struct{}{ + "caching_sha2_password": {}, + "mysql_native_password": {}, + "mysql_clear_password": {}, +} + +// Back-compat plugin compare: +// - Strip junk +// - If both are known plugin names and differ -> mismatch +// - Otherwise (unknown/garbled on either side) -> tolerate +func pluginEqualCompat(exp, act string) bool { + ex := printable(strings.TrimSpace(exp)) + ac := printable(strings.TrimSpace(act)) + if ex == ac { + return true + } + _, exKnown := knownPlugins[ex] + _, acKnown := knownPlugins[ac] + if exKnown && acKnown { + return false + } + return true +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/replayer/query.go b/keploy/pkg/core/proxy/integrations/mysql/replayer/query.go new file mode 100644 index 0000000..e290ef9 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/replayer/query.go @@ -0,0 +1,184 @@ +//go:build linux + +package replayer + +import ( + "context" + "fmt" + "io" + "net" + "time" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + mysqlUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func simulateCommandPhase(ctx context.Context, logger *zap.Logger, clientConn net.Conn, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext, opts models.OutgoingOptions) error { + + // Log initial mock state at the start of command phase + total, cfg, data := mockDb.GetMySQLCounts() + logger.Info("Command phase starting", + zap.Int("total_mysql_mocks", total), + zap.Int("config_mocks", cfg), + zap.Int("data_mocks_available", data)) + + commandCount := 0 + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + commandCount++ + + logger.Debug("Starting new command iteration", + zap.Int("command_count", commandCount)) + + // Set a read deadline on the client connection + readTimeout := 2 * time.Second * time.Duration(opts.SQLDelay) + err := clientConn.SetReadDeadline(time.Now().Add(readTimeout)) + if err != nil { + utils.LogError(logger, err, "failed to set read deadline on client conn") + return err + } + + logger.Debug("About to read next command from client", + zap.Int("command_count", commandCount), + zap.Duration("read_timeout", readTimeout)) + + // read the command from the client + command, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) + if err != nil { + if ne, ok := err.(net.Error); ok && ne.Timeout() { + // Idle wait: keep the connection open and continue polling + logger.Debug("read timeout waiting for next client command; keeping connection open") + // Optional: back off a bit to avoid hot loop + time.Sleep(50 * time.Millisecond) + // Clear deadline or set another future deadline, then keep looping + _ = clientConn.SetReadDeadline(time.Now().Add(readTimeout)) + continue + } + if err == io.EOF { + logger.Debug("client closed the connection (EOF)") + } else { + utils.LogError(logger, err, "failed to read command packet from client") + } + return err + } + + logger.Debug("Successfully read command from client", + zap.Int("command_count", commandCount), + zap.Int("command_size_bytes", len(command))) + + // reset the read deadline + err = clientConn.SetReadDeadline(time.Time{}) + if err != nil { + utils.LogError(logger, err, "failed to reset read deadline on client conn") + return err + } + + // Decode the command + commandPkt, err := wire.DecodePayload(ctx, logger, command, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to decode the MySQL packet from the client") + } + + req := mysql.Request{ + PacketBundle: *commandPkt, + } + + // Match the request with the mock + resp, ok, err := matchCommand(ctx, logger, req, mockDb, decodeCtx) + if err != nil { + if err == io.EOF { + logger.Info("Connection closing due to EOF from matchCommand", + zap.Int("commands_processed", commandCount), + zap.String("request_type", req.Header.Type)) + return io.EOF + } + logger.Error("Connection closing due to match command error", + zap.Error(err), + zap.Int("commands_processed", commandCount), + zap.String("request_type", req.Header.Type)) + utils.LogError(logger, err, "failed to match the command") + return err + } + + if !ok { + logger.Error("Connection closing due to no matching mock found", + zap.Int("commands_processed", commandCount), + zap.String("request_type", req.Header.Type)) + utils.LogError(logger, nil, "No matching mock found for the command", zap.Any("command", command)) + return fmt.Errorf("error while simulating the command phase due to no matching mock found") + } + + logger.Debug("Matched the command with the mock", zap.Any("mock", resp)) + + // Handle prepared statement cleanup for COM_STMT_CLOSE + if commandPkt.Header.Type == mysql.CommandStatusToString(mysql.COM_STMT_CLOSE) { + if closePacket, ok := commandPkt.Message.(*mysql.StmtClosePacket); ok { + delete(decodeCtx.PreparedStatements, closePacket.StatementID) + logger.Debug("Cleaned up prepared statement", zap.Uint32("StatementID", closePacket.StatementID)) + } + } + + // We could have just returned before matching the command for no response commands. + // But we need to remove the corresponding mock from the mockDb for no response commands. + if wire.IsNoResponseCommand(commandPkt.Header.Type) { + // No response for COM_STMT_CLOSE and COM_STMT_SEND_LONG_DATA + logger.Debug("No response for the command", zap.Any("command", command)) + continue + } + + //Encode the matched resp + buf, err := wire.EncodeToBinary(ctx, logger, &resp.PacketBundle, clientConn, decodeCtx) + if err != nil { + utils.LogError(logger, err, "failed to encode the response", zap.Any("response", resp)) + return err + } + + logger.Debug("About to write response to client", + zap.String("request_type", req.Header.Type), + zap.String("response_type", resp.Header.Type), + zap.Int("response_size_bytes", len(buf)), + zap.Int("commands_processed", commandCount)) + + // Write the response to the client + _, err = clientConn.Write(buf) + if err != nil { + if ctx.Err() != nil { + logger.Debug("context done while writing the response to the client", zap.Error(ctx.Err())) + return ctx.Err() + } + logger.Error("Failed to write response to client", + zap.Error(err), + zap.String("request_type", req.Header.Type), + zap.Int("commands_processed", commandCount)) + utils.LogError(logger, err, "failed to write the response to the client") + return err + } + + logger.Debug("successfully wrote the response to the client", + zap.Any("request", req.Header.Type), + zap.String("response_type", resp.Header.Type), + zap.Int("response_size_bytes", len(buf)), + zap.Int("commands_processed", commandCount)) + + // Check connection state after writing large response + if len(buf) > 1000 { + logger.Debug("Large response written, checking connection state", + zap.Int("response_size_bytes", len(buf)), + zap.String("request_type", req.Header.Type)) + } + + // Add a small delay and log to see if connection is still alive + logger.Debug("Response write completed, continuing to next iteration", + zap.Int("commands_processed", commandCount), + zap.String("last_request", req.Header.Type)) + } + } +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/replayer/replay.go b/keploy/pkg/core/proxy/integrations/mysql/replayer/replay.go new file mode 100644 index 0000000..807051d --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/replayer/replay.go @@ -0,0 +1,124 @@ +//go:build linux + +// Package replayer is used to mock the MySQL traffic between the client and the server. +package replayer + +import ( + "context" + "io" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// Mock Yaml to Binary + +func Replay(ctx context.Context, logger *zap.Logger, clientConn net.Conn, _ *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { + errCh := make(chan error, 1) + + unfiltered, err := mockDb.GetUnFilteredMocks() + if err != nil { + utils.LogError(logger, err, "failed to get unfiltered mocks") + return err + } + + var configMocks []*models.Mock + var hasMySQLMocks bool + var totalMySQLMocks int + var dataMocks int + // Get the mocks having "config" metadata and check for any MySQL mocks in a single pass. + for _, mock := range unfiltered { + if mock.Kind == models.MySQL { + hasMySQLMocks = true + totalMySQLMocks++ + if mock.Spec.Metadata["type"] == "config" { + configMocks = append(configMocks, mock) + } else { + dataMocks++ + } + } + } + + logger.Info("MySQL replay session starting", + zap.Int("total_unfiltered_mocks", len(unfiltered)), + zap.Int("total_mysql_mocks", totalMySQLMocks), + zap.Int("config_mocks", len(configMocks)), + zap.Int("data_mocks", dataMocks), + zap.Bool("has_mysql_mocks", hasMySQLMocks)) + + if !hasMySQLMocks { + utils.LogError(logger, nil, "no mysql mocks found") + return nil + } + + if len(configMocks) == 0 { + utils.LogError(logger, nil, "no mysql config mocks found for handshake") + return nil + } + + go func(errCh chan error, configMocks []*models.Mock, mockDb integrations.MockMemDb, opts models.OutgoingOptions) { + defer pUtil.Recover(logger, clientConn, nil) + defer close(errCh) + + // Helper struct for decoding packets + decodeCtx := &wire.DecodeContext{ + Mode: models.MODE_TEST, + // Map for storing last operation per connection + LastOp: wire.NewLastOpMap(), + // Map for storing server greetings (inc capabilities, auth plugin, etc) per initial handshake (per connection) + ServerGreetings: wire.NewGreetings(), + // Map for storing prepared statements per connection + PreparedStatements: make(map[uint32]*mysql.StmtPrepareOkPacket), + PluginName: string(mysql.CachingSha2), // usually a default plugin in newer versions of MySQL + PreferRecordedCaps: true, + } + decodeCtx.LastOp.Store(clientConn, wire.RESET) //resetting last command for new loop + + // Simulate the initial client-server handshake (connection phase) + + res, err := simulateInitialHandshake(ctx, logger, clientConn, configMocks, mockDb, decodeCtx, opts) + if err != nil { + utils.LogError(logger, err, "failed to simulate initial handshake") + errCh <- err + return + } + + if decodeCtx.UseSSL { + if res.tlsClientConn == nil { + logger.Error("SSL is enabled but could not get the tls client connection") + errCh <- nil + return + } + clientConn = res.tlsClientConn + } + + logger.Debug("Initial handshake completed successfully") + + // Simulate the client-server interaction (command phase) + err = simulateCommandPhase(ctx, logger, clientConn, mockDb, decodeCtx, opts) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to simulate command phase") + } + errCh <- err + return + } + + }(errCh, configMocks, mockDb, opts) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + return nil + } + return err + } +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/utils/util.go b/keploy/pkg/core/proxy/integrations/mysql/utils/util.go new file mode 100644 index 0000000..2c3a31e --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/utils/util.go @@ -0,0 +1,360 @@ +//go:build linux + +// Package utils provides utility functions for MySQL packets +package utils + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// ReadFirstBuffer reads the first buffer from either clientConn or destConn +func ReadFirstBuffer(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn) ([]byte, string, error) { + // Attempt to read from destConn first + buf, err := util.ReadBytes(ctx, logger, destConn) + // If there is data from destConn, return it + if err == nil { + return buf, "destination", nil + } + // If the error is a timeout, try to read from clientConn + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + buf, err = util.ReadBytes(ctx, logger, clientConn) + // If there is data from clientConn, return it + if err == nil { + return buf, "client", nil + } + // Return any error from reading clientConn + return nil, "", err + } + // Return any other error from reading destConn + return nil, "", err +} + +// ReadPacketStream reads packets from the connection and sends them to the bufferChannel +func ReadPacketStream(ctx context.Context, logger *zap.Logger, conn net.Conn, bufferChannel chan []byte, errChannel chan error) { + for { + select { + case <-ctx.Done(): + // errChannel <- ctx.Err() + return + default: + if conn == nil { + logger.Debug("the conn is nil") + } + buffer, err := ReadPacketBuffer(ctx, logger, conn) + if err != nil { + if ctx.Err() != nil { // to avoid sending buffer to closed channel if the context is cancelled + return + } + if err != io.EOF { + utils.LogError(logger, err, "failed to read mysql packet buffer") + } + errChannel <- err + return + } + if ctx.Err() != nil { // to avoid sending buffer to closed channel if the context is cancelled + return + } + bufferChannel <- buffer + } + } +} + +// ReadPacketBuffer reads a MySQL packet from the connection +func ReadPacketBuffer(ctx context.Context, logger *zap.Logger, conn net.Conn) ([]byte, error) { + var packetBuffer []byte + // first read the header length + header, err := util.ReadRequiredBytes(ctx, logger, conn, 4) + if err != nil { + if err == io.EOF { + return nil, err + } + // return packetBuffer, fmt.Errorf("failed to read mysql packet header: %w", err) + return packetBuffer, err + } + + packetBuffer = append(packetBuffer, header...) + + // read the payload length + payloadLength := GetPayloadLength(header[:3]) + if payloadLength > 0 { + payload, err := util.ReadRequiredBytes(ctx, logger, conn, int(payloadLength)) + if err != nil { + if err == io.EOF { + return nil, err + } + // return packetBuffer, fmt.Errorf("failed to read mysql packet payload: %w", err) + return packetBuffer, err + } + packetBuffer = append(packetBuffer, payload...) + } + + return packetBuffer, nil +} + +// BytesToMySQLPacket converts a byte slice to a MySQL packet +func BytesToMySQLPacket(buffer []byte) (mysql.Packet, error) { + if len(buffer) < 4 { + return mysql.Packet{}, errors.New("buffer is nil or too short to be a valid MySQL packet") + } + + tempBuffer := make([]byte, 4) + copy(tempBuffer, buffer[:3]) + length := binary.LittleEndian.Uint32(tempBuffer) + sequenceID := buffer[3] + + payload := buffer[4:] + + return mysql.Packet{ + Header: mysql.Header{ + PayloadLength: length, + SequenceID: sequenceID, + }, + Payload: payload, + }, nil +} + +// GetPayloadLength returns the length of the payload from the first 3 bytes of the packet. +func GetPayloadLength(src []byte) (length uint32) { + length = uint32(src[0]) | uint32(src[1])<<8 | uint32(src[2])<<16 + return length +} + +func ReadLengthEncodedInteger(b []byte) (num uint64, isNull bool, n int) { + if len(b) == 0 { + return 0, true, 0 + } + + switch b[0] { + // 251: NULL + case 0xfb: + return 0, true, 1 + + // 252: value of following 2 + case 0xfc: + if len(b) < 3 { + return 0, true, 0 + } + return uint64(b[1]) | uint64(b[2])<<8, false, 3 + + // 253: value of following 3 + case 0xfd: + if len(b) < 4 { + return 0, true, 0 + } + return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4 + + // 254: value of following 8 + case 0xfe: + if len(b) < 9 { + return 0, true, 0 + } + return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 | + uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 | + uint64(b[7])<<48 | uint64(b[8])<<56, + false, 9 + } + + // 0-250: value of first byte + return uint64(b[0]), false, 1 +} + +func IsEOFPacket(data []byte) bool { + if len(data) < 5 { + return false // Packet is too short to be valid + } + + // Check if the first byte is 5 or 7 + if data[0] != 5 && data[0] != 7 { + return false + } + + // Check if the packet contains the EOF marker 0xFE + return len(data) > 0 && data[4] == 0xFE +} + +func IsERRPacket(data []byte) bool { + return len(data) > 9 && data[4] == mysql.ERR +} + +func IsOKPacket(data []byte) bool { + return len(data) > 7 && data[4] == mysql.OK +} + +func IsGenericResponse(data []byte) (string, bool) { + if IsOKPacket(data) { + return "OK", true + } + if IsERRPacket(data) { + return "ERR", true + } + if IsEOFPacket(data) { + return "EOF", true + } + return "", false +} + +func ReadUint24(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 +} + +func ReadLengthEncodedString(b []byte) ([]byte, bool, int, error) { + // Get length + num, isNull, n := ReadLengthEncodedInteger(b) + if num < 1 { + return b[n:n], isNull, n, nil + } + + n += int(num) + + // Check data length + if len(b) >= n { + return b[n-int(num) : n : n], false, n, nil + } + return nil, false, n, io.EOF +} + +// ReadNullTerminatedString reads a null-terminated string from a byte slice +func ReadNullTerminatedString(b []byte) ([]byte, int, error) { + i := bytes.IndexByte(b, 0x00) + if i == -1 { + return nil, 0, io.EOF + } + return b[:i], i + 1, nil +} + +func WriteStream(ctx context.Context, logger *zap.Logger, conn net.Conn, buff [][]byte) error { + for _, b := range buff { + if ctx.Err() != nil { + return ctx.Err() + } + _, err := conn.Write(b) + if err != nil { + utils.LogError(logger, err, "failed to write to connection") + return err + } + } + return nil +} + +func WriteLengthEncodedString(buf *bytes.Buffer, s string) error { + length := len(s) + if err := WriteLengthEncodedInteger(buf, uint64(length)); err != nil { + return err + } + if _, err := buf.WriteString(s); err != nil { + return err + } + return nil +} + +func WriteLengthEncodedInteger(buf *bytes.Buffer, num uint64) error { + switch { + case num <= 250: + if err := buf.WriteByte(byte(num)); err != nil { + return err + } + case num <= 0xFFFF: + if err := buf.WriteByte(0xFC); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, uint16(num)); err != nil { + return err + } + case num <= 0xFFFFFF: + if err := buf.WriteByte(0xFD); err != nil { + return err + } + num24 := []byte{byte(num), byte(num >> 8), byte(num >> 16)} + if _, err := buf.Write(num24); err != nil { + return err + } + default: + if err := buf.WriteByte(0xFE); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, num); err != nil { + return err + } + } + return nil +} + +func WriteUint24(buf *bytes.Buffer, value uint32) error { + if value > 0xFFFFFF { + return errors.New("value exceeds 24 bits") + } + buf.WriteByte(byte(value)) + buf.WriteByte(byte(value >> 8)) + buf.WriteByte(byte(value >> 16)) + return nil +} + +func ParseBinaryDate(b []byte) (interface{}, int, error) { + if len(b) == 0 { + return nil, 0, nil + } + length := b[0] + if length == 0 { + return nil, 1, nil + } + year := binary.LittleEndian.Uint16(b[1:3]) + month := b[3] + day := b[4] + return fmt.Sprintf("%04d-%02d-%02d", year, month, day), int(length) + 1, nil +} + +func ParseBinaryDateTime(b []byte) (interface{}, int, error) { + if len(b) == 0 { + return nil, 0, nil + } + length := b[0] + if length == 0 { + return nil, 1, nil + } + year := binary.LittleEndian.Uint16(b[1:3]) + month := b[3] + day := b[4] + hour := b[5] + minute := b[6] + second := b[7] + if length > 7 { + microsecond := binary.LittleEndian.Uint32(b[8:12]) + return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", year, month, day, hour, minute, second, microsecond), int(length) + 1, nil + } + return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second), int(length) + 1, nil +} + +func ParseBinaryTime(b []byte) (interface{}, int, error) { + if len(b) == 0 { + return nil, 0, nil + } + length := b[0] + if length == 0 { + return nil, 1, nil + } + isNegative := b[1] == 1 + days := binary.LittleEndian.Uint32(b[2:6]) + hours := b[6] + minutes := b[7] + seconds := b[8] + var microseconds uint32 + if length > 8 { + microseconds = binary.LittleEndian.Uint32(b[9:13]) + } + timeString := fmt.Sprintf("%d %02d:%02d:%02d.%06d", days, hours, minutes, seconds, microseconds) + if isNegative { + timeString = "-" + timeString + } + return timeString, int(length) + 1, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/utils/util_test.go b/keploy/pkg/core/proxy/integrations/mysql/utils/util_test.go new file mode 100644 index 0000000..fe5a6a1 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/utils/util_test.go @@ -0,0 +1,211 @@ +//go:build linux + +package utils + +import ( + "fmt" + "testing" +) + +func TestReadLengthEncodedInteger(t *testing.T) { + tests := []struct { + name string + input []byte + expectedNum uint64 + expectedNull bool + expectedN int + expectValid bool + }{ + // Test empty input + { + name: "empty input", + input: []byte{}, + expectedNum: 0, + expectedNull: true, + expectedN: 0, + expectValid: true, + }, + + // Test single byte values (0-250) + { + name: "single byte value 0", + input: []byte{0x00}, + expectedNum: 0, + expectedNull: false, + expectedN: 1, + expectValid: true, + }, + { + name: "single byte value 250", + input: []byte{0xFA}, + expectedNum: 250, + expectedNull: false, + expectedN: 1, + expectValid: true, + }, + + // Test NULL value (251) + { + name: "null value", + input: []byte{0xFB}, + expectedNum: 0, + expectedNull: true, + expectedN: 1, + expectValid: true, + }, + + // Test 2-byte values (252) + { + name: "2-byte value with sufficient data", + input: []byte{0xFC, 0x01, 0x02}, + expectedNum: 0x0201, // 513 in little endian + expectedNull: false, + expectedN: 3, + expectValid: true, + }, + { + name: "2-byte value with insufficient data (panic fix test)", + input: []byte{0xFC, 0x01}, // Missing one byte + expectedNum: 0, + expectedNull: true, + expectedN: 0, + expectValid: true, // Should not panic, returns error state + }, + { + name: "2-byte value with only marker (panic fix test)", + input: []byte{0xFC}, // Missing both bytes + expectedNum: 0, + expectedNull: true, + expectedN: 0, + expectValid: true, // Should not panic, returns error state + }, + + // Test 3-byte values (253) + { + name: "3-byte value with sufficient data", + input: []byte{0xFD, 0x01, 0x02, 0x03}, + expectedNum: 0x030201, // little endian + expectedNull: false, + expectedN: 4, + expectValid: true, + }, + { + name: "3-byte value with insufficient data (panic fix test)", + input: []byte{0xFD, 0x01, 0x02}, // Missing one byte + expectedNum: 0, + expectedNull: true, + expectedN: 0, + expectValid: true, + }, + { + name: "3-byte value with only marker (panic fix test)", + input: []byte{0xFD}, // Missing all bytes + expectedNum: 0, + expectedNull: true, + expectedN: 0, + expectValid: true, + }, + + // Test 8-byte values (254) + { + name: "8-byte value with sufficient data", + input: []byte{0xFE, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + expectedNum: 0x0807060504030201, // little endian + expectedNull: false, + expectedN: 9, + expectValid: true, + }, + { + name: "8-byte value with insufficient data (panic fix test)", + input: []byte{0xFE, 0x01, 0x02, 0x03, 0x04}, // Missing 4 bytes + expectedNum: 0, + expectedNull: true, + expectedN: 0, + expectValid: true, + }, + { + name: "8-byte value with only marker (panic fix test)", + input: []byte{0xFE}, // Missing all bytes + expectedNum: 0, + expectedNull: true, + expectedN: 0, + expectValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test that it doesn't panic + defer func() { + if r := recover(); r != nil { + t.Errorf("ReadLengthEncodedInteger panicked with input %v: %v", tt.input, r) + } + }() + + num, isNull, n := ReadLengthEncodedInteger(tt.input) + + if num != tt.expectedNum { + t.Errorf("Expected num %d, got %d", tt.expectedNum, num) + } + if isNull != tt.expectedNull { + t.Errorf("Expected isNull %t, got %t", tt.expectedNull, isNull) + } + if n != tt.expectedN { + t.Errorf("Expected n %d, got %d", tt.expectedN, n) + } + }) + } +} + +func TestReadLengthEncodedIntegerEdgeCases(t *testing.T) { + // Test with malformed data that previously caused panics + edgeCases := [][]byte{ + {0xFC}, // 2-byte marker with no data + {0xFC, 0x01}, // 2-byte marker with only 1 byte + {0xFD}, // 3-byte marker with no data + {0xFD, 0x01}, // 3-byte marker with only 1 byte + {0xFD, 0x01, 0x02}, // 3-byte marker with only 2 bytes + {0xFE}, // 8-byte marker with no data + {0xFE, 0x01}, // 8-byte marker with only 1 byte + {0xFE, 0x01, 0x02, 0x03}, // 8-byte marker with only 4 bytes + } + + for i, data := range edgeCases { + t.Run(fmt.Sprintf("edge_case_%d", i), func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("ReadLengthEncodedInteger panicked with edge case %d (data: %v): %v", i, data, r) + } + }() + + _, isNull, n := ReadLengthEncodedInteger(data) + + // Should return error state (isNull=true, n=0) for insufficient data + if len(data) > 0 && data[0] >= 0xFC { + if !isNull || n != 0 { + t.Errorf("Expected error state (isNull=true, n=0) for insufficient data, got isNull=%t, n=%d", isNull, n) + } + } + }) + } +} + +func BenchmarkReadLengthEncodedInteger(b *testing.B) { + testCases := []struct { + name string + data []byte + }{ + {"single_byte", []byte{0x42}}, + {"two_bytes", []byte{0xFC, 0x01, 0x02}}, + {"three_bytes", []byte{0xFD, 0x01, 0x02, 0x03}}, + {"eight_bytes", []byte{0xFE, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + ReadLengthEncodedInteger(tc.data) + } + }) + } +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/decode.go b/keploy/pkg/core/proxy/integrations/mysql/wire/decode.go new file mode 100644 index 0000000..f443a3f --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/decode.go @@ -0,0 +1,433 @@ +//go:build linux + +// Package wire provides encoding and decoding operation of MySQL packets. +package wire + +import ( + "context" + "fmt" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase" + connection "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/conn" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/utility" + + itgUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +// DecodePayload is used to decode mysql packets that don't consist of multiple packets within them, because we are reading per packet. +func DecodePayload(ctx context.Context, logger *zap.Logger, data []byte, clientConn net.Conn, decodeCtx *DecodeContext) (*mysql.PacketBundle, error) { + //Parse the data into mysql header and payload + packet, err := utils.BytesToMySQLPacket(data) + if err != nil { + return &mysql.PacketBundle{}, fmt.Errorf("failed to parse MySQL packet: %w", err) + } + + if len(packet.Payload) < 1 { + return &mysql.PacketBundle{}, fmt.Errorf("invalid packet, payload is empty") + } + + lastOp, ok := decodeCtx.LastOp.Load(clientConn) + if !ok { + logger.Debug("Last operation not found in DecodePayload") + lastOp = 0x00 + } + + logger.Debug("Last operation in DecodePayload", zap.String("operation", fmt.Sprintf("%#x", lastOp)), zap.Any("header", packet.Header)) + logger.Debug("Mode", zap.Any("Mode", decodeCtx.Mode)) + + if (lastOp == mysql.COM_QUERY || lastOp == mysql.COM_STMT_EXECUTE) && decodeCtx.Mode == models.MODE_RECORD { + return handleQueryStmtResponse(ctx, logger, packet, clientConn, lastOp, decodeCtx) + } + + parsedPacket, err := decodePacket(ctx, logger, packet, clientConn, lastOp, decodeCtx) + if err != nil { + return &mysql.PacketBundle{}, fmt.Errorf("failed to decode packet: %w", err) + } + + return parsedPacket, nil +} + +func handleQueryStmtResponse(ctx context.Context, logger *zap.Logger, packet mysql.Packet, clientConn net.Conn, lastOp byte, decodeCtx *DecodeContext) (*mysql.PacketBundle, error) { + //Get the Header & payload of the packet + header := packet.Header + payload := packet.Payload + + parsedPacket := &mysql.PacketBundle{ + Header: &mysql.PacketInfo{ + Header: &header, + }, + } + + payloadType := payload[0] + + sg, ok := decodeCtx.ServerGreetings.Load(clientConn) + if !ok { + return parsedPacket, fmt.Errorf("server Greetings not found") + } + + logger.Debug("Last operation when handling client query", zap.Any("last operation", mysql.CommandStatusToString(lastOp))) + + switch payloadType { + case mysql.OK: + pkt, err := phase.DecodeOk(ctx, payload, sg.CapabilityFlags) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode OK packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.OK), clientConn, RESET, decodeCtx) + + case mysql.ERR: + + pkt, err := phase.DecodeERR(ctx, payload, sg.CapabilityFlags) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode ERR packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.ERR), clientConn, RESET, decodeCtx) + + case mysql.EOF: + pkt, err := phase.DecodeEOF(ctx, payload, sg.CapabilityFlags) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode EOF packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.EOF), clientConn, RESET, decodeCtx) + + case mysql.LocalInFile: + parsedPacket.Header.Type = "LocalInFile" + decodeCtx.LastOp.Store(clientConn, RESET) //reset the last operation + return parsedPacket, fmt.Errorf("LocalInFile not supported") + default: + //If the packet is not OK, ERR, EOF or LocalInFile, then it is a result set + var pktType string + var rowType query.RowType + if lastOp == mysql.COM_STMT_EXECUTE { + rowType = query.Binary + pktType = string(mysql.Binary) + } else { + rowType = query.Text + pktType = string(mysql.Text) + } + + pkt, err := query.DecodeResultSetMetadata(ctx, logger, payload, rowType) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode result set: %w", err) + } + + // Do not change the last operation if the packet is a result set, it will be changed when the result set is fully received + setPacketInfo(ctx, parsedPacket, pkt, pktType, clientConn, lastOp, decodeCtx) + } + + return parsedPacket, nil +} + +func decodePacket(ctx context.Context, logger *zap.Logger, packet mysql.Packet, clientConn net.Conn, lastOp byte, decodeCtx *DecodeContext) (*mysql.PacketBundle, error) { + //Get the Header & payload of the packet + header := packet.Header + payload := packet.Payload + + parsedPacket := &mysql.PacketBundle{ + Header: &mysql.PacketInfo{ + Header: &header, + }, + } + + payloadType := payload[0] + + var sg *mysql.HandshakeV10Packet + var ok bool + // No need to find the server greetings in the map if the payload is HandshakeV10 because it is the first packet and going to be stored in the map + if payloadType != mysql.HandshakeV10 { + sg, ok = decodeCtx.ServerGreetings.Load(clientConn) + if !ok { + return parsedPacket, fmt.Errorf("server Greetings not found") + } + } + + logger.Debug("payload info", zap.Any("last operation", lastOp), zap.Any("payload type", payloadType)) + + // Handle handshakeResponse41 separately, because its status is not defined and can be changed with the client capabilities. + if lastOp == mysql.HandshakeV10 { + logger.Debug("HandshakeResponse41/SSL Request packet", zap.Any("Type", payloadType)) + pkt, err := connection.DecodeHandshakeResponse(ctx, logger, payload) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode HandshakeResponse41 packet: %w", err) + } + + var pktType string + switch pkt := pkt.(type) { + case *mysql.HandshakeResponse41Packet: + // Store the client capabilities to use it later + decodeCtx.ClientCapabilities = pkt.CapabilityFlags + + pktType = mysql.HandshakeResponse41 + lastOp = payloadType + case *mysql.SSLRequestPacket: + // Store the client capabilities to use it later + decodeCtx.ClientCapabilities = pkt.CapabilityFlags + + pktType = mysql.SSLRequest + decodeCtx.UseSSL = true + logger.Info("SSL Request packet detected") + // Don't change the last operation if the packet is an SSL Request + } + + setPacketInfo(ctx, parsedPacket, pkt, pktType, clientConn, lastOp, decodeCtx) + + logger.Debug("HandshakeResponse41/SSL Request decoded", zap.Any("parsed packet", parsedPacket)) + + return parsedPacket, nil + } + + switch { + // generic response packets + case payloadType == mysql.EOF && len(payload) == 5: //assuming that the payload is always 5 bytes + logger.Debug("EOF packet", zap.Any("Type", payloadType)) + pkt, err := phase.DecodeEOF(ctx, payload, sg.CapabilityFlags) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode EOF packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.EOF), clientConn, mysql.EOF, decodeCtx) + logger.Debug("EOF decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == mysql.ERR: + logger.Debug("ERR packet", zap.Any("Type", payloadType)) + pkt, err := phase.DecodeERR(ctx, payload, sg.CapabilityFlags) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode ERR packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.ERR), clientConn, mysql.ERR, decodeCtx) + logger.Debug("ERR decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == mysql.OK: + if lastOp == mysql.COM_STMT_PREPARE { + logger.Debug("COM_STMT_PREPARE_OK packet", zap.Any("Type", payloadType)) + pkt, err := preparedstmt.DecodePrepareOk(ctx, logger, payload, sg.CapabilityFlags) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode COM_STMT_PREPARE_OK packet: %w", err) + } + + // Do not change the last operation if the packet is a prepared statement, it will be changed when the prepared statement is fully received + setPacketInfo(ctx, parsedPacket, pkt, "COM_STMT_PREPARE_OK", clientConn, lastOp, decodeCtx) + // Store the prepared statement to use it later + decodeCtx.PreparedStatements[pkt.StatementID] = pkt + logger.Debug("Prepared statement stored", zap.Any("statementId", pkt.StatementID), zap.Any("prepared statement", pkt)) + logger.Debug("COM_STMT_PREPARE_OK decoded", zap.Any("parsed packet", parsedPacket)) + + } else { + logger.Debug("OK packet", zap.Any("Type", payloadType)) + pkt, err := phase.DecodeOk(ctx, payload, sg.CapabilityFlags) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode OK packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.OK), clientConn, mysql.OK, decodeCtx) + logger.Debug("OK decoded", zap.Any("parsed packet", parsedPacket)) + } + + // auth packets + case payloadType == 0x01: + if len(payload) == 1 { + logger.Debug("COM_QUIT packet", zap.Any("Type", payloadType)) + pkt := &mysql.QuitPacket{ + Command: payloadType, + } + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_QUIT), clientConn, mysql.COM_QUIT, decodeCtx) + logger.Debug("COM_QUIT decoded", zap.Any("parsed packet", parsedPacket)) + } else { + //otherwise it is a AuthMoreData packet + logger.Debug("AuthMoreData packet", zap.Any("Type", payloadType)) + pkt, err := connection.DecodeAuthMoreData(ctx, payload) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode AuthMoreData packet: %w", err) + } + setPacketInfo(ctx, parsedPacket, pkt, mysql.AuthStatusToString(mysql.AuthMoreData), clientConn, mysql.AuthMoreData, decodeCtx) + logger.Debug("AuthMoreData decoded", zap.Any("parsed packet", parsedPacket)) + } + case payloadType == mysql.AuthSwitchRequest && len(payload) > 5: //conflicting with EOF packet, assuming that the payload is always greater than 5 bytes + logger.Debug("AuthSwitchRequest packet", zap.Any("Type", payloadType)) + pkt, err := connection.DecodeAuthSwitchRequest(ctx, payload) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode AuthSwitchRequest packet: %w", err) + } + setPacketInfo(ctx, parsedPacket, pkt, mysql.AuthStatusToString(mysql.AuthSwitchRequest), clientConn, mysql.AuthSwitchRequest, decodeCtx) + logger.Debug("AuthSwitchRequest decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == 0x02: + if len(payload) == 1 { + logger.Debug(("Request public key detected")) + setPacketInfo(ctx, parsedPacket, "request_public_key", mysql.CachingSha2PasswordToString(mysql.RequestPublicKey), clientConn, byte(mysql.RequestPublicKey), decodeCtx) + logger.Debug("Request public key decoded", zap.Any("parsed packet", parsedPacket)) + } else { + logger.Debug("AuthNextFactor packet", zap.Any("Type", payloadType)) + pkt, err := connection.DecodeAuthNextFactor(ctx, payload) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode AuthNextFactor packet: %w", err) + } + logger.Warn("AuthNextFactor packet not supported, further flow can be affected") + setPacketInfo(ctx, parsedPacket, pkt, mysql.AuthStatusToString(mysql.AuthNextFactor), clientConn, mysql.AuthNextFactor, decodeCtx) + logger.Debug("AuthNextFactor decoded", zap.Any("parsed packet", parsedPacket)) + } + case payloadType == mysql.HandshakeV10: + logger.Debug("HandshakeV10 packet", zap.Any("Type", payloadType)) + pkt, err := connection.DecodeHandshakeV10(ctx, logger, payload) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode HandshakeV10 packet: %w", err) + } + // Store the server greetings to use it later + decodeCtx.ServerGreetings.Store(clientConn, pkt) + setPacketInfo(ctx, parsedPacket, pkt, mysql.AuthStatusToString(mysql.HandshakeV10), clientConn, mysql.HandshakeV10, decodeCtx) + + logger.Debug("HandshakeV10 decoded", zap.Any("parsed packet", parsedPacket)) + + // utility packets + case payloadType == mysql.COM_QUIT: + logger.Debug("COM_QUIT packet", zap.Any("Type", payloadType)) + pkt := &mysql.QuitPacket{ + Command: payloadType, + } + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_QUIT), clientConn, mysql.COM_QUIT, decodeCtx) + logger.Debug("COM_QUIT decoded", zap.Any("parsed packet", parsedPacket)) + case payloadType == mysql.COM_INIT_DB: + logger.Debug("COM_INIT_DB packet", zap.Any("Type", payloadType)) + pkt, err := utility.DecodeInitDb(ctx, payload) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode COM_INIT_DB packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_INIT_DB), clientConn, mysql.COM_INIT_DB, decodeCtx) + logger.Debug("COM_INIT_DB decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == mysql.COM_STATISTICS: + logger.Debug("COM_STATISTICS packet", zap.Any("Type", payloadType)) + pkt := &mysql.StatisticsPacket{ + Command: payloadType, + } + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STATISTICS), clientConn, mysql.COM_STATISTICS, decodeCtx) + logger.Debug("COM_STATISTICS decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == mysql.COM_DEBUG: + logger.Debug("COM_DEBUG packet", zap.Any("Type", payloadType)) + pkt := &mysql.DebugPacket{ + Command: payloadType, + } + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_DEBUG), clientConn, mysql.COM_DEBUG, decodeCtx) + logger.Debug("COM_DEBUG decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == mysql.COM_PING: + logger.Debug("COM_PING packet", zap.Any("Type", payloadType)) + pkt := &mysql.PingPacket{ + Command: payloadType, + } + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_PING), clientConn, mysql.COM_PING, decodeCtx) + logger.Debug("COM_PING decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == mysql.COM_CHANGE_USER: + logger.Debug("COM_CHANGE_USER packet", zap.Any("Type", payloadType)) + pkt := &mysql.ChangeUserPacket{ + Command: payloadType, + } + logger.Warn("COM_CHANGE_USER packet not supported, further flow can be affected") + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_CHANGE_USER), clientConn, mysql.COM_CHANGE_USER, decodeCtx) + logger.Debug("COM_CHANGE_USER decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == mysql.COM_RESET_CONNECTION: + logger.Debug("COM_RESET_CONNECTION packet", zap.Any("Type", payloadType)) + pkt := &mysql.ResetConnectionPacket{ + Command: payloadType, + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_RESET_CONNECTION), clientConn, mysql.COM_RESET_CONNECTION, decodeCtx) + logger.Debug("COM_RESET_CONNECTION decoded", zap.Any("parsed packet", parsedPacket)) + + // case payloadType == mysql.COM_SET_OPTION: + // logger.Debug("COM_SET_OPTION packet", zap.Any("Type", payloadType)) + // pkt, err := utility.DecodeSetOption(ctx, payload) + // if err != nil { + // return parsedPacket, fmt.Errorf("failed to decode COM_SET_OPTION packet: %w", err) + // } + + // setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_SET_OPTION), mysql.COM_SET_OPTION, decodeCtx) + + // command packets + case payloadType == mysql.COM_QUERY: + logger.Debug("COM_QUERY packet", zap.Any("Type", payloadType)) + + pkt, err := query.DecodeQuery(ctx, logger, payload, decodeCtx.ClientCapabilities) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode COM_QUERY packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_QUERY), clientConn, mysql.COM_QUERY, decodeCtx) + + lstOp, _ := decodeCtx.LastOp.Load(clientConn) + logger.Debug("COM_QUERY decoded", zap.Any("parsed packet", parsedPacket), zap.Any("last operation", lstOp)) + + case payloadType == mysql.COM_STMT_PREPARE: + logger.Debug("COM_STMT_PREPARE packet", zap.Any("Type", payloadType)) + + pkt, err := preparedstmt.DecodeStmtPrepare(ctx, payload) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode COM_STMT_PREPARE packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STMT_PREPARE), clientConn, mysql.COM_STMT_PREPARE, decodeCtx) + logger.Debug("COM_STMT_PREPARE decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == mysql.COM_STMT_EXECUTE: + logger.Debug("COM_STMT_EXECUTE packet", zap.Any("Type", payloadType)) + pkt, err := preparedstmt.DecodeStmtExecute(ctx, logger, payload, decodeCtx.PreparedStatements, decodeCtx.ClientCapabilities) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode COM_STMT_EXECUTE packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STMT_EXECUTE), clientConn, mysql.COM_STMT_EXECUTE, decodeCtx) + logger.Debug("COM_STMT_EXECUTE decoded", zap.Any("parsed packet", parsedPacket)) + + // case payloadType == mysql.COM_STMT_FETCH: + case payloadType == mysql.COM_STMT_CLOSE: + logger.Debug("COM_STMT_CLOSE packet", zap.Any("Type", payloadType)) + pkt, err := preparedstmt.DecoderStmtClose(ctx, payload) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode COM_STMT_CLOSE packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STMT_CLOSE), clientConn, mysql.COM_STMT_CLOSE, decodeCtx) + logger.Debug("COM_STMT_CLOSE decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == mysql.COM_STMT_RESET: + logger.Debug("COM_STMT_RESET packet", zap.Any("Type", payloadType)) + pkt, err := preparedstmt.DecodeStmtReset(ctx, payload) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode COM_STMT_RESET packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STMT_RESET), clientConn, mysql.COM_STMT_RESET, decodeCtx) + + logger.Debug("COM_STMT_RESET decoded", zap.Any("parsed packet", parsedPacket)) + + case payloadType == mysql.COM_STMT_SEND_LONG_DATA: + logger.Debug("COM_STMT_SEND_LONG_DATA packet", zap.Any("Type", payloadType)) + pkt, err := preparedstmt.DecodeStmtSendLongData(ctx, payload) + if err != nil { + return parsedPacket, fmt.Errorf("failed to decode COM_STMT_SEND_LONG_DATA packet: %w", err) + } + + setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STMT_SEND_LONG_DATA), clientConn, mysql.COM_STMT_SEND_LONG_DATA, decodeCtx) + logger.Debug("COM_STMT_SEND_LONG_DATA decoded", zap.Any("parsed packet", parsedPacket)) + default: + logger.Warn("Unknown packet type", zap.String("PacketType", fmt.Sprintf("%#x", payloadType)), zap.Any("payload", payload), zap.Any("last operation", lastOp)) + setPacketInfo(ctx, parsedPacket, itgUtils.EncodeBase64(payload), fmt.Sprintf("%#x", payloadType), clientConn, RESET, decodeCtx) + } + + return parsedPacket, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/encode.go b/keploy/pkg/core/proxy/integrations/mysql/wire/encode.go new file mode 100644 index 0000000..71d1f78 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/encode.go @@ -0,0 +1,179 @@ +//go:build linux + +package wire + +import ( + "context" + "encoding/binary" + "fmt" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/conn" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +func EncodeToBinary(ctx context.Context, logger *zap.Logger, packet *mysql.PacketBundle, clientConn net.Conn, decodeCtx *DecodeContext) ([]byte, error) { + + var data []byte + var err error + + //Get the server greeting from the decode context + serverGreeting, ok := decodeCtx.ServerGreetings.Load(clientConn) + if !ok { + return nil, fmt.Errorf("server greeting not found for connection %s", clientConn.RemoteAddr().String()) + } + + switch packet.Message.(type) { + // generic response packets + case *mysql.EOFPacket: + pkt, ok := packet.Message.(*mysql.EOFPacket) + if !ok { + return nil, fmt.Errorf("expected EOFPacket, got %T", packet.Message) + } + + data, err = phase.EncodeEOF(ctx, pkt, serverGreeting.CapabilityFlags) + if err != nil { + return nil, fmt.Errorf("error encoding EOF packet: %v", err) + } + + decodeCtx.LastOp.Store(clientConn, mysql.EOF) + + case *mysql.ERRPacket: + pkt, ok := packet.Message.(*mysql.ERRPacket) + if !ok { + return nil, fmt.Errorf("expected ERRPacket, got %T", packet.Message) + } + + data, err = phase.EncodeErr(ctx, pkt, serverGreeting.CapabilityFlags) + if err != nil { + return nil, fmt.Errorf("error encoding ERR packet: %v", err) + } + + decodeCtx.LastOp.Store(clientConn, mysql.ERR) + + case *mysql.OKPacket: + pkt, ok := packet.Message.(*mysql.OKPacket) + if !ok { + return nil, fmt.Errorf("expected OKPacket, got %T", packet.Message) + } + + data, err = phase.EncodeOk(ctx, pkt, serverGreeting.CapabilityFlags) + if err != nil { + return nil, fmt.Errorf("error encoding OK packet: %v", err) + } + + decodeCtx.LastOp.Store(clientConn, mysql.OK) + + // connection phase packets + case *mysql.AuthMoreDataPacket: + pkt, ok := packet.Message.(*mysql.AuthMoreDataPacket) + if !ok { + return nil, fmt.Errorf("expected AuthMoreDataPacket, got %T", packet.Message) + } + + data, err = conn.EncodeAuthMoreData(ctx, pkt) + if err != nil { + return nil, fmt.Errorf("error encoding AuthMoreData packet: %v", err) + } + + decodeCtx.LastOp.Store(clientConn, mysql.AuthMoreData) + + case *mysql.AuthSwitchRequestPacket: + pkt, ok := packet.Message.(*mysql.AuthSwitchRequestPacket) + if !ok { + return nil, fmt.Errorf("expected AuthSwitchRequestPacket, got %T", packet.Message) + } + + data, err = conn.EncodeAuthSwitchRequest(ctx, pkt) + if err != nil { + return nil, fmt.Errorf("error encoding AuthSwitchRequest packet: %v", err) + } + + decodeCtx.LastOp.Store(clientConn, mysql.AuthSwitchRequest) + + case *mysql.HandshakeV10Packet: + pkt, ok := packet.Message.(*mysql.HandshakeV10Packet) + if !ok { + return nil, fmt.Errorf("expected HandshakeV10Packet, got %T", packet.Message) + } + + data, err = conn.EncodeHandshakeV10(ctx, logger, pkt) + if err != nil { + return nil, fmt.Errorf("error encoding HandshakeV10 packet: %v", err) + } + + decodeCtx.LastOp.Store(clientConn, mysql.HandshakeV10) + + // command phase packets + case *mysql.StmtPrepareOkPacket: + pkt, ok := packet.Message.(*mysql.StmtPrepareOkPacket) + if !ok { + return nil, fmt.Errorf("expected StmtPrepareOkPacket, got %T", packet.Message) + } + + data, err = preparedstmt.EncodePrepareOk(ctx, logger, pkt, serverGreeting.CapabilityFlags) + if err != nil { + return nil, fmt.Errorf("error encoding StmtPrepareOkPacket: %v", err) + } + + decodeCtx.LastOp.Store(clientConn, mysql.COM_STMT_PREPARE) + + case *mysql.TextResultSet: + pkt, ok := packet.Message.(*mysql.TextResultSet) + if !ok { + return nil, fmt.Errorf("expected TextResultSet, got %T", packet.Message) + } + + data, err = query.EncodeTextResultSet(ctx, logger, pkt) + if err != nil { + return nil, fmt.Errorf("error encoding TextResultSet: %v", err) + } + + case *mysql.BinaryProtocolResultSet: + pkt, ok := packet.Message.(*mysql.BinaryProtocolResultSet) + if !ok { + return nil, fmt.Errorf("expected BinaryProtocolResultSet, got %T", packet.Message) + } + + data, err = query.EncodeBinaryResultSet(ctx, logger, pkt) + if err != nil { + return nil, fmt.Errorf("error encoding BinaryProtocolResultSet: %v", err) + } + } + + // Encode the header for the packet + header := make([]byte, 4) + // IMPORTANT: + // For composite (multi-packet) messages, `data` contains multiple packets where + // only the FIRST sub-packet lacks its 4-byte header. The recorded header's + // PayloadLength corresponds to that first payload only (e.g., column-count = 1). + // For single-packet messages, we can safely use len(data). + payloadLen := len(data) + if isCompositeMessage(packet.Message) && packet.Header != nil && packet.Header.Header != nil { + payloadLen = int(packet.Header.Header.PayloadLength) + } + binary.LittleEndian.PutUint32(header, uint32(payloadLen)) + header[3] = packet.Header.Header.SequenceID + data = append(header, data...) + + logger.Debug("Encoded Packet", zap.String("packet", packet.Header.Type), zap.ByteString("data", data)) + + return data, nil +} + +// isCompositeMessage tells whether the encoded `data` contains multiple packets, +// where only the first packet should use the header we write here. +func isCompositeMessage(msg interface{}) bool { + switch msg.(type) { + case *mysql.TextResultSet, + *mysql.BinaryProtocolResultSet, + *mysql.StmtPrepareOkPacket: + return true + default: + return false + } +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authMoreDataPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authMoreDataPacket.go new file mode 100644 index 0000000..c19ee19 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authMoreDataPacket.go @@ -0,0 +1,43 @@ +//go:build linux + +package conn + +import ( + "bytes" + "context" + "fmt" + + "go.keploy.io/server/v2/pkg/models/mysql" +) + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_more_data.html + +func DecodeAuthMoreData(_ context.Context, data []byte) (*mysql.AuthMoreDataPacket, error) { + return &mysql.AuthMoreDataPacket{ + StatusTag: data[0], + Data: string(data[1:]), + }, nil +} + +func EncodeAuthMoreData(_ context.Context, packet *mysql.AuthMoreDataPacket) ([]byte, error) { + buf := new(bytes.Buffer) + + // Write StatusTag + if err := buf.WriteByte(packet.StatusTag); err != nil { + return nil, fmt.Errorf("failed to write StatusTag for AuthMoreData packet: %w", err) + } + + switch packet.Data { + case mysql.CachingSha2PasswordToString(mysql.PerformFullAuthentication): + packet.Data = string(mysql.PerformFullAuthentication) + case mysql.CachingSha2PasswordToString(mysql.FastAuthSuccess): + packet.Data = string(mysql.FastAuthSuccess) + } + + // Write Data + if _, err := buf.WriteString(packet.Data); err != nil { + return nil, fmt.Errorf("failed to write Data for authMoreData packet: %w", err) + } + + return buf.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authNextFactorPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authNextFactorPacket.go new file mode 100644 index 0000000..ddeb065 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authNextFactorPacket.go @@ -0,0 +1,56 @@ +//go:build linux + +package conn + +import ( + "bytes" + "context" + "errors" + "fmt" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/models/mysql" +) + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_next_factor_request.html + +func DecodeAuthNextFactor(_ context.Context, data []byte) (*mysql.AuthNextFactorPacket, error) { + + packet := &mysql.AuthNextFactorPacket{ + PacketType: data[0], + } + + data, idx, err := utils.ReadNullTerminatedString(data[1:]) + if err != nil { + return nil, fmt.Errorf("malformed handshake response packet: missing null terminator for PluginName") + } + packet.PluginName = string(data) + packet.PluginData = string(data[idx:]) + + return packet, nil +} + +func EncodeAuthNextFactor(_ context.Context, packet *mysql.AuthNextFactorPacket) ([]byte, error) { + buf := new(bytes.Buffer) + + // Write PacketType + if err := buf.WriteByte(packet.PacketType); err != nil { + return nil, errors.New("failed to write PacketType") + } + + // Write PluginName followed by a null terminator + if _, err := buf.WriteString(packet.PluginName); err != nil { + return nil, errors.New("failed to write PluginName for AuthNextFactor packet") + } + + if err := buf.WriteByte(0x00); err != nil { + return nil, errors.New("failed to write null terminator for PluginName for AuthNextFactor packet") + } + + // Write PluginData + if _, err := buf.WriteString(packet.PluginData); err != nil { + return nil, errors.New("failed to write PluginData for AuthNextFactor packet") + } + + return buf.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchRequestPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchRequestPacket.go new file mode 100644 index 0000000..e41f88c --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchRequestPacket.go @@ -0,0 +1,59 @@ +//go:build linux + +package conn + +import ( + "bytes" + "context" + "encoding/base64" + "errors" + + "go.keploy.io/server/v2/pkg/models/mysql" +) + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_request.html + +func DecodeAuthSwitchRequest(_ context.Context, data []byte) (*mysql.AuthSwitchRequestPacket, error) { + + packet := &mysql.AuthSwitchRequestPacket{ + StatusTag: data[0], + } + + // Splitting data by null byte to get plugin name and auth data + parts := bytes.SplitN(data[1:], []byte{0x00}, 2) + packet.PluginName = string(parts[0]) + if len(parts) > 1 { + packet.PluginData = base64.RawStdEncoding.EncodeToString(parts[1]) + } + + return packet, nil +} + +func EncodeAuthSwitchRequest(_ context.Context, packet *mysql.AuthSwitchRequestPacket) ([]byte, error) { + buf := new(bytes.Buffer) + + // Write StatusTag + if err := buf.WriteByte(packet.StatusTag); err != nil { + return nil, errors.New("failed to write StatusTag") + } + + // Write PluginName followed by a null terminator + if _, err := buf.WriteString(packet.PluginName); err != nil { + return nil, errors.New("failed to write PluginName for AuthSwitchRequest packet") + } + if err := buf.WriteByte(0x00); err != nil { + return nil, errors.New("failed to write null terminator for PluginName for AuthSwitchRequest packet") + } + + pluginData, err := base64.RawStdEncoding.DecodeString(packet.PluginData) + if err != nil { + return nil, errors.New("failed to decode PluginData for AuthSwitchRequest packet") + } + + // Write PluginData + if _, err := buf.WriteString(string(pluginData)); err != nil { + return nil, errors.New("failed to write PluginData for AuthSwitchRequest packet") + } + + return buf.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchResponsePacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchResponsePacket.go new file mode 100644 index 0000000..4f2307d --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchResponsePacket.go @@ -0,0 +1,31 @@ +//go:build linux + +package conn + +import ( + "bytes" + "context" + "encoding/base64" + "errors" + + "go.keploy.io/server/v2/pkg/models/mysql" +) + +//ref:https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_response.html + +func DecodeAuthSwitchResponse(_ context.Context, data []byte) (*mysql.AuthSwitchResponsePacket, error) { + return &mysql.AuthSwitchResponsePacket{ + Data: base64.StdEncoding.EncodeToString(data), + }, nil +} + +func EncodeAuthSwitchResponse(_ context.Context, packet *mysql.AuthSwitchResponsePacket) ([]byte, error) { + buf := new(bytes.Buffer) + + // Write Data + if _, err := buf.WriteString(packet.Data); err != nil { + return nil, errors.New("failed to write Data for AuthSwitchResponse packet") + } + + return buf.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go new file mode 100644 index 0000000..a2893d3 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go @@ -0,0 +1,272 @@ +//go:build linux + +package conn + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_response.html +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_ssl_request.html + +func DecodeHandshakeResponse(_ context.Context, logger *zap.Logger, data []byte) (interface{}, error) { + + if len(data) < 32 { + return nil, errors.New("handshake response packet too short") + } + + origData := data + + packet := &mysql.HandshakeResponse41Packet{} + + packet.CapabilityFlags = binary.LittleEndian.Uint32(data[:4]) + data = data[4:] + + if packet.CapabilityFlags&mysql.CLIENT_PROTOCOL_41 == 0 { + return nil, errors.New("CLIENT_PROTOCOL_41 compatible client is required") + } + + packet.MaxPacketSize = binary.LittleEndian.Uint32(data[:4]) + data = data[4:] + + packet.CharacterSet = data[0] + data = data[1:] + + copy(packet.Filler[:], data[:23]) + data = data[23:] + + // Check if it is a SSL Request Packet + if len(origData) == (4 + 4 + 1 + 23) { + if packet.CapabilityFlags&mysql.CLIENT_SSL != 0 { + logger.Debug("Client requested SSL connection") + return &mysql.SSLRequestPacket{ + CapabilityFlags: packet.CapabilityFlags, + MaxPacketSize: packet.MaxPacketSize, + CharacterSet: packet.CharacterSet, + Filler: packet.Filler, + }, nil + } + } + + idx := bytes.IndexByte(data, 0x00) + if idx == -1 { + return nil, errors.New("malformed handshake response packet: missing null terminator for Username") + } + + packet.Username = string(data[:idx]) + data = data[idx+1:] + + if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA != 0 { + if len(data) < 1 { + return nil, errors.New("handshake response packet too short for auth data length") + } + length := int(data[0]) + data = data[1:] + + if length > 0 { + if len(data) < length { + return nil, errors.New("handshake response packet too short for auth data") + } + packet.AuthResponse = data[:length] + data = data[length:] + } + } else { + if len(data) < 2 { + return nil, errors.New("handshake response packet too short for auth data length") + } + authLen := int(data[0]) + data = data[2:] + + if len(data) < authLen { + return nil, errors.New("handshake response packet too short for auth data") + } + packet.AuthResponse = data[:authLen] + data = data[authLen:] + } + + if packet.CapabilityFlags&mysql.CLIENT_CONNECT_WITH_DB != 0 { + idx = bytes.IndexByte(data, 0x00) + if idx != -1 { + packet.Database = string(data[:idx]) + data = data[idx+1:] + } + } + + if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 { + idx = bytes.IndexByte(data, 0x00) + if idx == -1 { + return nil, errors.New("malformed handshake response packet: missing null terminator for AuthPluginName") + } + packet.AuthPluginName = string(data[:idx]) + data = data[idx+1:] + } + + if packet.CapabilityFlags&mysql.CLIENT_CONNECT_ATTRS != 0 { + if len(data) < 4 { + return nil, errors.New("handshake response packet too short for connection attributes") + } + + totalLength, isNull, n := utils.ReadLengthEncodedInteger(data) + if isNull || n == 0 { + return nil, errors.New("error decoding total length of connection attributes") + } + data = data[n:] + + // Check if we have enough data for the total attributes length + if len(data) < int(totalLength) { + return nil, errors.New("handshake response packet too short for connection attributes data") + } + + attributesData := data[:totalLength] + data = data[totalLength:] + + packet.ConnectionAttributes = make(map[string]string) + for len(attributesData) > 0 { + keyLength, isNull, n := utils.ReadLengthEncodedInteger(attributesData) + if isNull || n == 0 { + return nil, errors.New("malformed handshake response packet: null length encoded integer for connection attribute key") + } + attributesData = attributesData[n:] + + // Check if we have enough data for the key + if len(attributesData) < int(keyLength) { + return nil, errors.New("handshake response packet too short for connection attribute key") + } + + key := string(attributesData[:keyLength]) + attributesData = attributesData[keyLength:] + + valueLength, isNull, n := utils.ReadLengthEncodedInteger(attributesData) + if isNull || n == 0 { + return nil, errors.New("malformed handshake response packet: null length encoded integer for connection attribute value") + } + attributesData = attributesData[n:] + + // Check if we have enough data for the value + if len(attributesData) < int(valueLength) { + return nil, errors.New("handshake response packet too short for connection attribute value") + } + + value := string(attributesData[:valueLength]) + attributesData = attributesData[valueLength:] + + packet.ConnectionAttributes[key] = value + } + } + if len(data) > 0 { + if packet.CapabilityFlags&mysql.CLIENT_ZSTD_COMPRESSION_ALGORITHM != 0 { + if len(data) < 1 { + return nil, errors.New("handshake response packet too short for ZSTD compression level") + } + packet.ZstdCompressionLevel = data[0] + } + } + + return packet, nil +} + +func EncodeHandshakeResponse41(_ context.Context, _ *zap.Logger, packet *mysql.HandshakeResponse41Packet) ([]byte, error) { + buf := new(bytes.Buffer) + + // Write Capability Flags + if err := binary.Write(buf, binary.LittleEndian, packet.CapabilityFlags); err != nil { + return nil, fmt.Errorf("failed to write CapabilityFlags for HandshakeResponse41Packet: %w", err) + } + + // Write Max Packet Size + if err := binary.Write(buf, binary.LittleEndian, packet.MaxPacketSize); err != nil { + return nil, fmt.Errorf("failed to write MaxPacketSize for HandshakeResponse41Packet: %w", err) + } + + // Write Character Set + if err := buf.WriteByte(packet.CharacterSet); err != nil { + return nil, fmt.Errorf("failed to write CharacterSet for HandshakeResponse41Packet: %w", err) + } + + // Write Filler + if _, err := buf.Write(packet.Filler[:]); err != nil { + return nil, fmt.Errorf("failed to write Filler for HandshakeResponse41Packet: %w", err) + } + + // Write Username + if _, err := buf.WriteString(packet.Username); err != nil { + return nil, fmt.Errorf("failed to write Username for HandshakeResponse41Packet: %w", err) + } + if err := buf.WriteByte(0x00); err != nil { + return nil, fmt.Errorf("failed to write null terminator for Username for HandshakeResponse41Packet: %w", err) + } + + // Write Auth Response + if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA != 0 { + if err := buf.WriteByte(byte(len(packet.AuthResponse))); err != nil { + return nil, fmt.Errorf("failed to write length of AuthResponse for HandshakeResponse41Packet: %w", err) + } + if _, err := buf.Write(packet.AuthResponse); err != nil { + return nil, fmt.Errorf("failed to write AuthResponse for HandshakeResponse41Packet: %w", err) + } + } else { + if err := buf.WriteByte(byte(len(packet.AuthResponse))); err != nil { + return nil, fmt.Errorf("failed to write length of AuthResponse for HandshakeResponse41Packet: %w", err) + } + if _, err := buf.Write(packet.AuthResponse); err != nil { + return nil, fmt.Errorf("failed to write AuthResponse for HandshakeResponse41Packet: %w", err) + } + } + + // Write Database + if packet.CapabilityFlags&mysql.CLIENT_CONNECT_WITH_DB != 0 { + if _, err := buf.WriteString(packet.Database); err != nil { + return nil, fmt.Errorf("failed to write Database for HandshakeResponse41Packet: %w", err) + } + if err := buf.WriteByte(0x00); err != nil { + return nil, fmt.Errorf("failed to write null terminator for Database for HandshakeResponse41Packet: %w", err) + } + } + + // Write Auth Plugin Name + if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 { + if _, err := buf.WriteString(packet.AuthPluginName); err != nil { + return nil, fmt.Errorf("failed to write AuthPluginName for HandshakeResponse41Packet: %w", err) + } + if err := buf.WriteByte(0x00); err != nil { + return nil, fmt.Errorf("failed to write null terminator for AuthPluginName for HandshakeResponse41Packet: %w", err) + } + } + + // Write Connection Attributes + if packet.CapabilityFlags&mysql.CLIENT_CONNECT_ATTRS != 0 { + totalLength := 0 + for key, value := range packet.ConnectionAttributes { + totalLength += len(key) + len(value) + 2 // 2 bytes for length-encoded integer prefixes + } + + if err := utils.WriteLengthEncodedInteger(buf, uint64(totalLength)); err != nil { + return nil, fmt.Errorf("failed to write total length of ConnectionAttributes for HandshakeResponse41Packet: %w", err) + } + + for key, value := range packet.ConnectionAttributes { + if err := utils.WriteLengthEncodedString(buf, key); err != nil { + return nil, fmt.Errorf("failed to write ConnectionAttribute key for HandshakeResponse41Packet: %w", err) + } + if err := utils.WriteLengthEncodedString(buf, value); err != nil { + return nil, fmt.Errorf("failed to write ConnectionAttribute value for HandshakeResponse41Packet: %w", err) + } + } + } + // Write Zstd Compression Level + if packet.CapabilityFlags&mysql.CLIENT_ZSTD_COMPRESSION_ALGORITHM != 0 { + if err := buf.WriteByte(packet.ZstdCompressionLevel); err != nil { + return nil, fmt.Errorf("failed to write ZstdCompressionLevel for HandshakeResponse41Packet: %w", err) + } + } + + return buf.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet_test.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet_test.go new file mode 100644 index 0000000..b6710c6 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet_test.go @@ -0,0 +1,234 @@ +//go:build linux + +package conn + +import ( + "context" + "testing" + + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +func TestDecodeHandshakeResponse_BoundsChecking(t *testing.T) { + logger := zap.NewNop() + ctx := context.Background() + + tests := []struct { + name string + data []byte + expectError bool + errorContains string + }{ + { + name: "too short packet", + data: make([]byte, 10), // Less than minimum 32 bytes + expectError: true, + errorContains: "handshake response packet too short", + }, + { + name: "minimum valid packet", + data: func() []byte { + // Create minimal valid handshake response + data := make([]byte, 32) + // Set CLIENT_PROTOCOL_41 capability (0x200) + data[0] = 0x00 + data[1] = 0x02 // 0x0200 = 512 = CLIENT_PROTOCOL_41 + data[2] = 0x00 + data[3] = 0x00 + // Add null terminator for username at position 32 + data = append(data, 0x00) + // Add auth response length = 0 for non-plugin auth + data = append(data, 0x00, 0x00) // length + filler + return data + }(), + expectError: false, + }, + { + name: "missing username null terminator", + data: func() []byte { + data := make([]byte, 32) + data[0] = 0x00 + data[1] = 0x02 // CLIENT_PROTOCOL_41 + data[2] = 0x00 + data[3] = 0x00 + // No null terminator for username + return append(data, []byte("username")...) + }(), + expectError: true, + errorContains: "missing null terminator for Username", + }, + { + name: "auth response - insufficient data for length (plugin auth)", + data: func() []byte { + data := make([]byte, 32) + // CLIENT_PROTOCOL_41 (0x200) + CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA (0x100000) + data[0] = 0x00 + data[1] = 0x02 // CLIENT_PROTOCOL_41 + data[2] = 0x10 // CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA + data[3] = 0x00 + // Add null terminator for username + data = append(data, 0x00) + // Don't add auth length byte + return data + }(), + expectError: true, + errorContains: "handshake response packet too short for auth data length", + }, + { + name: "auth response - insufficient data for auth data (plugin auth)", + data: func() []byte { + data := make([]byte, 32) + // CLIENT_PROTOCOL_41 + CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA + data[0] = 0x00 + data[1] = 0x02 // CLIENT_PROTOCOL_41 + data[2] = 0x10 // CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA + data[3] = 0x00 + // Add null terminator for username + data = append(data, 0x00) + // Add auth length = 5 but only provide 3 bytes + data = append(data, 0x05) + data = append(data, []byte{0x01, 0x02, 0x03}...) + return data + }(), + expectError: true, + errorContains: "handshake response packet too short for auth data", + }, + { + name: "auth response - insufficient data for length (non-plugin auth)", + data: func() []byte { + data := make([]byte, 32) + // CLIENT_PROTOCOL_41 only (no CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) + data[0] = 0x00 + data[1] = 0x02 + data[2] = 0x00 + data[3] = 0x00 + // Add null terminator for username + data = append(data, 0x00) + // Add only 1 byte (need 2 for non-plugin auth) + data = append(data, 0x05) + return data + }(), + expectError: true, + errorContains: "handshake response packet too short for auth data length", + }, + { + name: "auth response - insufficient data for auth data (non-plugin auth)", + data: func() []byte { + data := make([]byte, 32) + // CLIENT_PROTOCOL_41 only + data[0] = 0x00 + data[1] = 0x02 + data[2] = 0x00 + data[3] = 0x00 + // Add null terminator for username + data = append(data, 0x00) + // Add auth length = 5 but only provide 3 bytes (after skipping 2 bytes) + data = append(data, 0x05, 0x00) // length + filler + data = append(data, []byte{0x01, 0x02, 0x03}...) // Only 3 bytes (need 5) + return data + }(), + expectError: true, + errorContains: "handshake response packet too short for auth data", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("DecodeHandshakeResponse panicked: %v", r) + } + }() + + result, err := DecodeHandshakeResponse(ctx, logger, tt.data) + + if tt.expectError { + if err == nil { + t.Errorf("Expected error but got none") + } else if tt.errorContains != "" && !contains(err.Error(), tt.errorContains) { + t.Errorf("Expected error to contain '%s', but got: %s", tt.errorContains, err.Error()) + } + if result != nil { + t.Errorf("Expected nil result on error, but got: %v", result) + } + } else { + if err != nil { + t.Errorf("Expected no error but got: %s", err.Error()) + } + if result == nil { + t.Errorf("Expected non-nil result but got nil") + } + } + }) + } +} + +func TestDecodeHandshakeResponse_ValidCases(t *testing.T) { + logger := zap.NewNop() + ctx := context.Background() + + tests := []struct { + name string + data []byte + validate func(t *testing.T, result interface{}) + }{ + { + name: "valid basic handshake", + data: func() []byte { + data := make([]byte, 32) + // CLIENT_PROTOCOL_41 (0x200) + data[0] = 0x00 + data[1] = 0x02 + data[2] = 0x00 + data[3] = 0x00 + // Add username + data = append(data, []byte("testuser")...) + data = append(data, 0x00) // null terminator + // Add auth response (non-plugin style) + data = append(data, 0x00, 0x00) // auth length = 0 + filler + return data + }(), + validate: func(t *testing.T, result interface{}) { + packet, ok := result.(*mysql.HandshakeResponse41Packet) + if !ok { + t.Errorf("Expected HandshakeResponse41Packet, got %T", result) + return + } + if packet.Username != "testuser" { + t.Errorf("Expected username 'testuser', got '%s'", packet.Username) + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("DecodeHandshakeResponse panicked: %v", r) + } + }() + + result, err := DecodeHandshakeResponse(ctx, logger, tt.data) + if err != nil { + t.Errorf("Unexpected error: %s", err.Error()) + return + } + + if tt.validate != nil { + tt.validate(t, result) + } + }) + } +} + +// Helper function to check if string contains substring +func contains(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeV10Packet.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeV10Packet.go new file mode 100644 index 0000000..40de4cf --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeV10Packet.go @@ -0,0 +1,167 @@ +//go:build linux + +// Package conn provides decoding and encoding of connection phase mysql packets +package conn + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html + +func DecodeHandshakeV10(_ context.Context, _ *zap.Logger, data []byte) (*mysql.HandshakeV10Packet, error) { + + if len(data) < 4 { + return nil, fmt.Errorf("handshake packet too short") + } + + packet := &mysql.HandshakeV10Packet{} + packet.ProtocolVersion = data[0] + + idx := bytes.IndexByte(data[1:], 0x00) + if idx == -1 { + return nil, fmt.Errorf("malformed handshake packet: missing null terminator for ServerVersion") + } + + packet.ServerVersion = string(data[1 : 1+idx]) + data = data[1+idx+1:] + + if len(data) < 4 { + return nil, fmt.Errorf("handshake packet too short for ConnectionID") + } + + packet.ConnectionID = binary.LittleEndian.Uint32(data[:4]) + data = data[4:] + + if len(data) < 9 { // 8 bytes of AuthPluginData + 1 byte filler + return nil, fmt.Errorf("handshake packet too short for AuthPluginData") + } + packet.AuthPluginData = append([]byte{}, data[:8]...) + + packet.Filler = data[8] + + data = data[9:] // Skip 8 bytes of AuthPluginData and 1 byte filler + + if len(data) < 5 { // Capability flags (2 bytes), character set (1 byte), status flags (2 bytes) + return nil, fmt.Errorf("handshake packet too short for flags") + } + + capabilityFlagsLower := binary.LittleEndian.Uint16(data[:2]) + data = data[2:] + + packet.CharacterSet = data[0] + data = data[1:] + + packet.StatusFlags = binary.LittleEndian.Uint16(data[:2]) + data = data[2:] + + capabilityFlagsUpper := binary.LittleEndian.Uint16(data[:2]) + data = data[2:] + + packet.CapabilityFlags = uint32(capabilityFlagsLower) | uint32(capabilityFlagsUpper)<<16 + var authPluginDataLen int + + packet.CapabilityFlags = uint32(capabilityFlagsLower) | uint32(capabilityFlagsUpper)<<16 + + if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 { + authPluginDataLen = int(data[0]) + data = data[1:] // Skip 1 byte AuthPluginDataLen + } else { + data = data[1:] // constant 0x00 + } + + data = data[10:] // Skip 10 bytes reserved (all 0s) + + if authPluginDataLen > 8 { + lenToRead := min(authPluginDataLen-8, len(data)) + packet.AuthPluginData = append(packet.AuthPluginData, data[:lenToRead]...) + data = data[lenToRead:] + } + + if len(data) == 0 { + return nil, fmt.Errorf("handshake packet too short for AuthPluginName") + } + + if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 { + idx = bytes.IndexByte(data, 0x00) + if idx == -1 { + return nil, fmt.Errorf("malformed handshake packet: missing null terminator for AuthPluginName") + } + packet.AuthPluginName = string(data[:idx]) + } + + return packet, nil +} + +func EncodeHandshakeV10(_ context.Context, _ *zap.Logger, packet *mysql.HandshakeV10Packet) ([]byte, error) { + buf := new(bytes.Buffer) + + // Protocol version + buf.WriteByte(packet.ProtocolVersion) + + // Server version + buf.WriteString(packet.ServerVersion) + buf.WriteByte(0x00) // Null terminator + + // Connection ID + if err := binary.Write(buf, binary.LittleEndian, packet.ConnectionID); err != nil { + return nil, err + } + + // Auth-plugin-data-part-1 (first 8 bytes) + if len(packet.AuthPluginData) < 8 { + return nil, errors.New("auth plugin data too short") + } + buf.Write(packet.AuthPluginData[:8]) + + // Filler + buf.WriteByte(packet.Filler) + + // Capability flags (lower 2 bytes) + if err := binary.Write(buf, binary.LittleEndian, uint16(packet.CapabilityFlags&0xFFFF)); err != nil { + return nil, err + } + + // Character set + buf.WriteByte(packet.CharacterSet) + + // Status flags + if err := binary.Write(buf, binary.LittleEndian, packet.StatusFlags); err != nil { + return nil, err + } + + // Capability flags (upper 2 bytes) + if err := binary.Write(buf, binary.LittleEndian, uint16((packet.CapabilityFlags>>16)&0xFFFF)); err != nil { + return nil, err + } + + // Length of auth-plugin-data + if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 && len(packet.AuthPluginData) >= 21 { + buf.WriteByte(byte(len(packet.AuthPluginData))) // Length of entire auth plugin data + } else { + buf.WriteByte(0x00) + } + + // Reserved (10 zero bytes) + buf.Write(make([]byte, 10)) + + // Auth-plugin-data-part-2 (remaining auth data) + if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 && len(packet.AuthPluginData) > 8 { + buf.Write(packet.AuthPluginData[8:]) // Write all remaining bytes of auth plugin data + } + + // Auth-plugin name + if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 { + buf.WriteString(packet.AuthPluginName) + buf.WriteByte(0x00) // Null terminator + } + + return buf.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/generic.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/generic.go new file mode 100644 index 0000000..d0663b7 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/generic.go @@ -0,0 +1,205 @@ +//go:build linux + +// Package phase contains the encoding and decoding functions for the different phases of the MySQL protocol. And also contains the same for EOF, ERR, and OK packets. +package phase + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/models/mysql" +) + +// EOF packets (encoding & decoding) + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_eof_packet.html + +func DecodeEOF(_ context.Context, data []byte, capabilities uint32) (*mysql.EOFPacket, error) { + if len(data) > 5 { + return nil, fmt.Errorf("EOF packet too long for EOF") + } + + packet := &mysql.EOFPacket{ + Header: data[0], + } + + if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { + packet.Warnings = binary.LittleEndian.Uint16(data[1:3]) + packet.StatusFlags = binary.LittleEndian.Uint16(data[3:5]) + } + + return packet, nil +} + +func EncodeEOF(_ context.Context, packet *mysql.EOFPacket, capabilities uint32) ([]byte, error) { + buf := new(bytes.Buffer) + + // Write the header + if err := buf.WriteByte(packet.Header); err != nil { + return nil, fmt.Errorf("failed to write header: %w", err) + } + + // Write the warnings and status flags if CLIENT_PROTOCOL_41 is set + if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { + if err := binary.Write(buf, binary.LittleEndian, packet.Warnings); err != nil { + return nil, fmt.Errorf("failed to write warnings for eof packet: %w", err) + } + + if err := binary.Write(buf, binary.LittleEndian, packet.StatusFlags); err != nil { + return nil, fmt.Errorf("failed to write status flags for eof packet: %w", err) + } + } + + return buf.Bytes(), nil +} + +// ERR packets (encoding & decoding) + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_err_packet.html + +func DecodeERR(_ context.Context, data []byte, capabilities uint32) (*mysql.ERRPacket, error) { + if len(data) < 9 { + return nil, fmt.Errorf("ERR packet too short") + } + + packet := &mysql.ERRPacket{ + Header: data[0], + } + + pos := 1 + packet.ErrorCode = binary.LittleEndian.Uint16(data[pos : pos+2]) + pos += 2 + + if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { + if data[pos] != '#' { + return nil, fmt.Errorf("invalid SQL state marker: %c", data[pos]) + } + packet.SQLStateMarker = string(data[pos]) + + pos++ + packet.SQLState = string(data[pos : pos+5]) + pos += 5 + } + + packet.ErrorMessage = string(data[pos:]) + return packet, nil +} + +func EncodeErr(_ context.Context, packet *mysql.ERRPacket, capabilities uint32) ([]byte, error) { + buf := new(bytes.Buffer) + + // Write the header + if err := buf.WriteByte(packet.Header); err != nil { + return nil, fmt.Errorf("failed to write header: %w", err) + } + + // Write the error code + if err := binary.Write(buf, binary.LittleEndian, packet.ErrorCode); err != nil { + return nil, fmt.Errorf("failed to write error code: %w", err) + } + + // Write the SQL state marker and SQL state if CLIENT_PROTOCOL_41 is set + if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { + if len(packet.SQLStateMarker) != 1 || len(packet.SQLState) != 5 { + return nil, fmt.Errorf("invalid SQL state marker or SQL state length") + } + + if err := buf.WriteByte(packet.SQLStateMarker[0]); err != nil { + return nil, fmt.Errorf("failed to write SQL state marker: %w", err) + } + + if _, err := buf.WriteString(packet.SQLState); err != nil { + return nil, fmt.Errorf("failed to write SQL state: %w", err) + } + } + + // Write the error message + if _, err := buf.WriteString(packet.ErrorMessage); err != nil { + return nil, fmt.Errorf("failed to write error message: %w", err) + } + + return buf.Bytes(), nil +} + +// ref:https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html + +func DecodeOk(_ context.Context, data []byte, capabilities uint32) (*mysql.OKPacket, error) { + if len(data) < 7 { + return nil, fmt.Errorf("OK packet too short") + } + + packet := &mysql.OKPacket{ + Header: data[0], + } + + var n int + var pos = 1 + + packet.AffectedRows, _, n = utils.ReadLengthEncodedInteger(data[pos:]) + pos += n + packet.LastInsertID, _, n = utils.ReadLengthEncodedInteger(data[pos:]) + pos += n + + if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { + packet.StatusFlags = binary.LittleEndian.Uint16(data[pos:]) + pos += 2 + packet.Warnings = binary.LittleEndian.Uint16(data[pos:]) + pos += 2 + } else if capabilities&uint32(mysql.CLIENT_TRANSACTIONS) > 0 { + packet.StatusFlags = binary.LittleEndian.Uint16(data[pos:]) + pos += 2 + } + + // new ok package will check CLIENT_SESSION_TRACK too, but it is not supported in this version + + if pos < len(data) { + packet.Info = string(data[pos:]) + } + + return packet, nil +} + +func EncodeOk(_ context.Context, packet *mysql.OKPacket, capabilities uint32) ([]byte, error) { + buf := new(bytes.Buffer) + + // Write Header + if err := buf.WriteByte(packet.Header); err != nil { + return nil, fmt.Errorf("failed to write Header: %w", err) + } + + // Write Affected Rows + if err := utils.WriteLengthEncodedInteger(buf, packet.AffectedRows); err != nil { + return nil, fmt.Errorf("failed to write AffectedRows for OK packet: %w", err) + } + + // Write Last Insert ID + if err := utils.WriteLengthEncodedInteger(buf, packet.LastInsertID); err != nil { + return nil, fmt.Errorf("failed to write LastInsertID for OK packet: %w", err) + } + + // Write Status Flags and Warnings + if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { + if err := binary.Write(buf, binary.LittleEndian, packet.StatusFlags); err != nil { + return nil, fmt.Errorf("failed to write StatusFlags for OK packet: %w", err) + } + if err := binary.Write(buf, binary.LittleEndian, packet.Warnings); err != nil { + return nil, fmt.Errorf("failed to write Warnings for OK packet: %w", err) + } + } else if capabilities&uint32(mysql.CLIENT_TRANSACTIONS) > 0 { + if err := binary.Write(buf, binary.LittleEndian, packet.StatusFlags); err != nil { + return nil, fmt.Errorf("failed to write StatusFlags for OK packet: %w", err) + } + } + + // Write Info + if packet.Info != "" { + if _, err := buf.WriteString(packet.Info); err != nil { + return nil, fmt.Errorf("failed to write Info for OK packet: %w", err) + } + } + + return buf.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/ResultSetPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/ResultSetPacket.go new file mode 100644 index 0000000..02d4993 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/ResultSetPacket.go @@ -0,0 +1,142 @@ +//go:build linux + +package query + +import ( + "bytes" + "context" + "fmt" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +type RowType int + +// Constants for RowType +const ( + Binary RowType = iota + Text +) + +func DecodeResultSetMetadata(ctx context.Context, logger *zap.Logger, data []byte, rowType RowType) (interface{}, error) { + + // Decode the column count (No need to get the header as well as it is already decoded by the caller function) + colCount, err := rowscols.DecodeColumnCount(ctx, logger, data) + if err != nil { + return nil, fmt.Errorf("failed to decode column count packet: %w", err) + } + + switch rowType { + case Binary: + return &mysql.BinaryProtocolResultSet{ + ColumnCount: colCount, + }, nil + case Text: + return &mysql.TextResultSet{ + ColumnCount: colCount, + }, nil + } + return nil, nil +} + +func EncodeTextResultSet(ctx context.Context, logger *zap.Logger, resultSet *mysql.TextResultSet) ([]byte, error) { + buf := new(bytes.Buffer) + + // Encode the column count + if err := utils.WriteLengthEncodedInteger(buf, resultSet.ColumnCount); err != nil { + return nil, fmt.Errorf("failed to write column count for text resultset: %w", err) + } + + // Encode the column definition packets + for _, column := range resultSet.Columns { + columnBytes, err := rowscols.EncodeColumn(ctx, logger, column) + if err != nil { + return nil, fmt.Errorf("failed to encode column for text resultset: %w", err) + } + if _, err := buf.Write(columnBytes); err != nil { + return nil, fmt.Errorf("failed to write column for text resultset: %w", err) + } + } + + // Write the EOF packet after columns if present + if len(resultSet.EOFAfterColumns) != 0 { + if _, err := buf.Write(resultSet.EOFAfterColumns); err != nil { + return nil, fmt.Errorf("failed to write EOF packet after columns for text resultset: %w", err) + } + } + + // Encode each row data packet + for _, row := range resultSet.Rows { + rowBytes, err := rowscols.EncodeTextRow(ctx, logger, row, resultSet.Columns) + if err != nil { + return nil, fmt.Errorf("failed to encode row for text resultset: %w", err) + } + if _, err := buf.Write(rowBytes); err != nil { + return nil, fmt.Errorf("failed to write row for text resultset: %w", err) + } + } + // Write the final EOF packet if present + if resultSet.FinalResponse != nil && utils.IsEOFPacket(resultSet.FinalResponse.Data) { + if _, err := buf.Write(resultSet.FinalResponse.Data); err != nil { + return nil, fmt.Errorf("failed to write final EOF packet: %w", err) + } + } + + return buf.Bytes(), nil +} + +func EncodeBinaryResultSet(ctx context.Context, logger *zap.Logger, resultSet *mysql.BinaryProtocolResultSet) ([]byte, error) { + buf := new(bytes.Buffer) + + // Encode the column count + if err := utils.WriteLengthEncodedInteger(buf, resultSet.ColumnCount); err != nil { + return nil, fmt.Errorf("failed to write column count: %w", err) + } + + // Encode the column definition packets + for _, column := range resultSet.Columns { + columnBytes, err := rowscols.EncodeColumn(ctx, logger, column) + if err != nil { + return nil, fmt.Errorf("failed to encode column: %w", err) + } + if _, err := buf.Write(columnBytes); err != nil { + return nil, fmt.Errorf("failed to write column: %w", err) + } + } + + // Write the EOF packet after columns + if _, err := buf.Write(resultSet.EOFAfterColumns); err != nil { + return nil, fmt.Errorf("failed to write EOF packet after columns: %w", err) + } + + // Encode each row data packet + for _, row := range resultSet.Rows { + rowBytes, err := rowscols.EncodeBinaryRow(ctx, logger, row, resultSet.Columns) + if err != nil { + return nil, fmt.Errorf("failed to encode row: %w", err) + } + if _, err := buf.Write(rowBytes); err != nil { + return nil, fmt.Errorf("failed to write row: %w", err) + } + } + + // Write the final EOF packet if present + if resultSet.FinalResponse != nil && utils.IsEOFPacket(resultSet.FinalResponse.Data) { + logger.Debug("Writing final EOF packet for BinaryProtocolResultSet", + zap.Int("final_response_length", len(resultSet.FinalResponse.Data)), + zap.String("final_response_hex", fmt.Sprintf("%x", resultSet.FinalResponse.Data))) + if _, err := buf.Write(resultSet.FinalResponse.Data); err != nil { + return nil, fmt.Errorf("failed to write final EOF packet: %w", err) + } + logger.Debug("Successfully wrote final EOF packet for BinaryProtocolResultSet") + } else { + logger.Debug("No final EOF packet to write for BinaryProtocolResultSet", + zap.Bool("has_final_response", resultSet.FinalResponse != nil), + zap.Bool("is_eof_packet", resultSet.FinalResponse != nil && utils.IsEOFPacket(resultSet.FinalResponse.Data))) + } + + return buf.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/StmtPrepareOkPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/StmtPrepareOkPacket.go new file mode 100644 index 0000000..ce51969 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/StmtPrepareOkPacket.go @@ -0,0 +1,153 @@ +//go:build linux + +package preparedstmt + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +// COM_STMT_PREPARE_OK: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_prepare.html#sect_protocol_com_stmt_prepare_response_ok + +func DecodePrepareOk(_ context.Context, _ *zap.Logger, data []byte, capabilities uint32) (*mysql.StmtPrepareOkPacket, error) { + if len(data) < 10 { + return nil, errors.New("data length is not enough for COM_STMT_PREPARE_OK") + } + + offset := 0 + + response := &mysql.StmtPrepareOkPacket{} + + response.Status = data[offset] + offset++ + + response.StatementID = binary.LittleEndian.Uint32(data[offset : offset+4]) + offset += 4 + + response.NumColumns = binary.LittleEndian.Uint16(data[offset : offset+2]) + offset += 2 + + response.NumParams = binary.LittleEndian.Uint16(data[offset : offset+2]) + offset += 2 + + //data[10] is reserved byte ([00] filler) + response.Filler = data[offset] + offset++ + + if len(data) >= 12 { + response.WarningCount = binary.LittleEndian.Uint16(data[offset : offset+2]) + response.WarningAvailable = true + offset += 2 + + if capabilities&uint32(mysql.CLIENT_OPTIONAL_RESULTSET_METADATA) > 0 && len(data) > offset { + response.MetaFollows = data[offset] + response.MetaFollowsAvailable = true + // offset++ + } + } + + return response, nil +} + +func EncodePrepareOk(ctx context.Context, logger *zap.Logger, packet *mysql.StmtPrepareOkPacket, capabilities uint32) ([]byte, error) { + buf := new(bytes.Buffer) + + // Encode the Prepare OK packet + prepareOkBytes, err := EncodePreparedStmtResponse(ctx, logger, packet, capabilities) + if err != nil { + return nil, fmt.Errorf("failed to encode StmtPrepareOkPacket: %w", err) + } + if _, err := buf.Write(prepareOkBytes); err != nil { + return nil, fmt.Errorf("failed to write StmtPrepareOkPacket: %w", err) + } + + // Encode parameter definitions if present + for _, paramDef := range packet.ParamDefs { + paramBytes, err := rowscols.EncodeColumn(ctx, logger, paramDef) + if err != nil { + return nil, fmt.Errorf("failed to encode parameter definition: %w", err) + } + if _, err := buf.Write(paramBytes); err != nil { + return nil, fmt.Errorf("failed to write parameter definition: %w", err) + } + } + + // Write EOF packet after parameter definitions + if packet.NumParams > 0 { + if _, err := buf.Write(packet.EOFAfterParamDefs); err != nil { + return nil, fmt.Errorf("failed to write EOF packet after parameter definitions: %w", err) + } + } + + // Encode column definitions if present + for _, columnDef := range packet.ColumnDefs { + columnBytes, err := rowscols.EncodeColumn(ctx, logger, columnDef) + if err != nil { + return nil, fmt.Errorf("failed to encode column definition: %w", err) + } + if _, err := buf.Write(columnBytes); err != nil { + return nil, fmt.Errorf("failed to write column definition: %w", err) + } + } + + // Write EOF packet after column definitions + if packet.NumColumns > 0 { + if _, err := buf.Write(packet.EOFAfterColumnDefs); err != nil { + return nil, fmt.Errorf("failed to write EOF packet after column definitions: %w", err) + } + } + + return buf.Bytes(), nil +} + +func EncodePreparedStmtResponse(_ context.Context, _ *zap.Logger, packet *mysql.StmtPrepareOkPacket, capabilities uint32) ([]byte, error) { + buf := new(bytes.Buffer) + + // Write Status + if err := buf.WriteByte(packet.Status); err != nil { + return nil, fmt.Errorf("failed to write Status: %w", err) + } + + // Write Statement ID + if err := binary.Write(buf, binary.LittleEndian, packet.StatementID); err != nil { + return nil, fmt.Errorf("failed to write StatementID: %w", err) + } + + // Write Number of Columns + if err := binary.Write(buf, binary.LittleEndian, packet.NumColumns); err != nil { + return nil, fmt.Errorf("failed to write NumColumns: %w", err) + } + + // Write Number of Parameters + if err := binary.Write(buf, binary.LittleEndian, packet.NumParams); err != nil { + return nil, fmt.Errorf("failed to write NumParams: %w", err) + } + + // Write Filler + if err := buf.WriteByte(packet.Filler); err != nil { + return nil, fmt.Errorf("failed to write Filler: %w", err) + } + + if packet.WarningAvailable { + // Write Warning Count + if err := binary.Write(buf, binary.LittleEndian, packet.WarningCount); err != nil { + return nil, fmt.Errorf("failed to write WarningCount: %w", err) + } + } + + if capabilities&uint32(mysql.CLIENT_OPTIONAL_RESULTSET_METADATA) > 0 && packet.MetaFollowsAvailable { + // Write Meta Follows + if err := buf.WriteByte(packet.MetaFollows); err != nil { + return nil, fmt.Errorf("failed to write MetaFollows: %w", err) + } + } + + return buf.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtClosePacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtClosePacket.go new file mode 100644 index 0000000..32a6b64 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtClosePacket.go @@ -0,0 +1,26 @@ +//go:build linux + +// Package preparedstmt provides functionality for decoding prepared statement packets. +package preparedstmt + +import ( + "context" + "encoding/binary" + "errors" + + "go.keploy.io/server/v2/pkg/models/mysql" +) + +//COM_STMT_CLOSE: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_close.html + +func DecoderStmtClose(_ context.Context, data []byte) (*mysql.StmtClosePacket, error) { + if len(data) != 5 { + return nil, errors.New("invalid packet for COM_STMT_CLOSE") + } + + packet := &mysql.StmtClosePacket{ + Status: data[0], + StatementID: binary.LittleEndian.Uint32(data[1:5]), + } + return packet, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket.go new file mode 100644 index 0000000..c494897 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket.go @@ -0,0 +1,237 @@ +//go:build linux + +package preparedstmt + +import ( + "context" + "encoding/binary" + "fmt" + "io" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + intUtil "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +// COM_STMT_EXECUTE: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_execute.html + +func DecodeStmtExecute(_ context.Context, _ *zap.Logger, data []byte, preparedStmts map[uint32]*mysql.StmtPrepareOkPacket, clientCapabilities uint32) (*mysql.StmtExecutePacket, error) { + if len(data) < 10 { + return &mysql.StmtExecutePacket{}, fmt.Errorf("packet length too short for COM_STMT_EXECUTE") + } + + pos := 0 + + packet := &mysql.StmtExecutePacket{} + + // Read Status + if pos+1 > len(data) { + return nil, io.ErrUnexpectedEOF + } + //data[0] is COM_STMT_EXECUTE (0x17) + packet.Status = data[pos] + pos++ + + // Read StatementID + if pos+4 > len(data) { + return nil, io.ErrUnexpectedEOF + } + packet.StatementID = binary.LittleEndian.Uint32(data[pos : pos+4]) + pos += 4 + + stmtPrepOk, ok := preparedStmts[packet.StatementID] + if !ok && stmtPrepOk == nil { + return nil, fmt.Errorf("prepared statement with ID %d not found", packet.StatementID) + } + + // Read Flags + if pos+1 > len(data) { + return nil, io.ErrUnexpectedEOF + } + packet.Flags = data[pos] + pos++ + + // Read IterationCount + if pos+4 > len(data) { + return nil, io.ErrUnexpectedEOF + } + packet.IterationCount = binary.LittleEndian.Uint32(data[pos : pos+4]) + pos += 4 + + if stmtPrepOk.NumParams > 0 || (clientCapabilities&mysql.CLIENT_QUERY_ATTRIBUTES > 0 && (packet.Flags&mysql.PARAMETER_COUNT_AVAILABLE > 0)) { + // Set the parameter count from the prepared statement + packet.ParameterCount = int(stmtPrepOk.NumParams) + + if clientCapabilities&mysql.CLIENT_QUERY_ATTRIBUTES > 0 && (packet.Flags&mysql.PARAMETER_COUNT_AVAILABLE > 0) { + // If query attributes are supported and parameter count is available in the packet, + // we could potentially override it here, but for now we use the prepared statement count + packet.ParameterCount = int(stmtPrepOk.NumParams) + } + + if packet.ParameterCount <= 0 { + return packet, nil + } + + // Read Parameters only if there are any + + // Read NULL bitmap + nullBitmapLength := (packet.ParameterCount + 7) / 8 + if pos+nullBitmapLength > len(data) { + return nil, io.ErrUnexpectedEOF + } + packet.NullBitmap = data[pos : pos+nullBitmapLength] + pos += int(nullBitmapLength) + + // Read NewParamsBindFlag + if pos+1 > len(data) { + return nil, io.ErrUnexpectedEOF + } + packet.NewParamsBindFlag = data[pos] + pos++ + + // Initialize Parameters slice regardless of NewParamsBindFlag + packet.Parameters = make([]mysql.Parameter, packet.ParameterCount) + + // Read Parameters if NewParamsBindFlag is set + if packet.NewParamsBindFlag == 1 { + for i := 0; i < packet.ParameterCount; i++ { + if pos+2 > len(data) { + return nil, io.ErrUnexpectedEOF + } + packet.Parameters[i].Type = binary.LittleEndian.Uint16(data[pos : pos+2]) + packet.Parameters[i].Unsigned = (data[pos+1] & 0x80) != 0 // Check if the highest bit is set + pos += 2 + } + } else { + // When NewParamsBindFlag is 0, we reuse the previous parameter types + // For now, we'll set a default type for all parameters + for i := 0; i < packet.ParameterCount; i++ { + packet.Parameters[i].Type = uint16(mysql.FieldTypeString) // Default type + packet.Parameters[i].Unsigned = false + } + } + + // Read Parameter Values + for i := 0; i < packet.ParameterCount; i++ { + // Check if this parameter is NULL according to the NULL bitmap + byteIndex := i / 8 + bitIndex := i % 8 + if byteIndex < len(packet.NullBitmap) && (packet.NullBitmap[byteIndex]&(1<= len(data) { + return nil, io.ErrUnexpectedEOF + } + + // Process Parameter based on its type + param := &packet.Parameters[i] + + // Handle length-encoded values (only for types that require variable-length data) + switch mysql.FieldType(param.Type) { + case mysql.FieldTypeString, mysql.FieldTypeVarString, mysql.FieldTypeVarChar, mysql.FieldTypeBLOB, mysql.FieldTypeTinyBLOB, mysql.FieldTypeMediumBLOB, mysql.FieldTypeLongBLOB, mysql.FieldTypeJSON: + // Read the length of the parameter value + length, _, n := utils.ReadLengthEncodedInteger(data[pos:]) + pos += n + if pos+int(length) > len(data) { + return nil, io.ErrUnexpectedEOF + } + + if intUtil.IsASCII(string(data[pos : pos+int(length)])) { + param.Value = string(data[pos : pos+int(length)]) + } else { + param.Value = intUtil.EncodeBase64(data[pos : pos+int(length)]) + } + pos += int(length) + case mysql.FieldTypeLong: + if len(data[pos:]) < 4 { + return nil, fmt.Errorf("malformed FieldTypeLong value") + } + if param.Unsigned { + param.Value = uint32(binary.LittleEndian.Uint32(data[pos : pos+4])) + } else { + param.Value = int32(binary.LittleEndian.Uint32(data[pos : pos+4])) + } + pos += 4 + + case mysql.FieldTypeTiny: + if len(data[pos:]) < 1 { + return nil, fmt.Errorf("malformed FieldTypeTiny value") + } + if param.Unsigned { + param.Value = uint8(data[pos]) + } else { + param.Value = int8(data[pos]) + } + pos += 1 + + case mysql.FieldTypeShort, mysql.FieldTypeYear: + if len(data[pos:]) < 2 { + return nil, fmt.Errorf("malformed FieldTypeShort value") + } + if param.Unsigned { + param.Value = uint16(binary.LittleEndian.Uint16(data[pos : pos+2])) + } else { + param.Value = int16(binary.LittleEndian.Uint16(data[pos : pos+2])) + } + pos += 2 + + case mysql.FieldTypeLongLong: + if len(data[pos:]) < 8 { + return nil, fmt.Errorf("malformed FieldTypeLongLong value") + } + if param.Unsigned { + param.Value = uint64(binary.LittleEndian.Uint64(data[pos : pos+8])) + } else { + param.Value = int64(binary.LittleEndian.Uint64(data[pos : pos+8])) + } + pos += 8 + + case mysql.FieldTypeFloat: + if len(data[pos:]) < 4 { + return nil, fmt.Errorf("malformed FieldTypeFloat value") + } + param.Value = float32(binary.LittleEndian.Uint32(data[pos : pos+4])) + pos += 4 + + case mysql.FieldTypeDouble: + if len(data[pos:]) < 8 { + return nil, fmt.Errorf("malformed FieldTypeDouble value") + } + param.Value = float64(binary.LittleEndian.Uint64(data[pos : pos+8])) + pos += 8 + + case mysql.FieldTypeDate, mysql.FieldTypeNewDate: + value, _, err := utils.ParseBinaryDate(data[pos:]) + if err != nil { + return nil, err + } + param.Value = value + pos += len(param.Value.(string)) // Assuming date parsing returns a string + + case mysql.FieldTypeTimestamp, mysql.FieldTypeDateTime: + value, _, err := utils.ParseBinaryDateTime(data[pos:]) + if err != nil { + return nil, err + } + param.Value = value + pos += len(param.Value.(string)) // Assuming datetime parsing returns a string + + case mysql.FieldTypeTime: + value, _, err := utils.ParseBinaryTime(data[pos:]) + if err != nil { + return nil, err + } + param.Value = value + pos += len(param.Value.(string)) // Assuming time parsing returns a string + default: + return nil, fmt.Errorf("unsupported parameter type: %d", param.Type) + } + } + } + return packet, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket_test.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket_test.go new file mode 100644 index 0000000..fbbf59d --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket_test.go @@ -0,0 +1,110 @@ +//go:build linux + +package preparedstmt + +import ( + "context" + "testing" + + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +func TestDecodeStmtExecute_ParameterCountFix(t *testing.T) { + // Create a mock prepared statement with 3 parameters + stmtPrepOk := &mysql.StmtPrepareOkPacket{ + StatementID: 945, + NumParams: 3, + NumColumns: 10, + } + + preparedStmts := map[uint32]*mysql.StmtPrepareOkPacket{ + 945: stmtPrepOk, + } + + // Mock COM_STMT_EXECUTE packet data (39 bytes as seen in the logs) + // This represents a packet with statement ID 945, but with 3 NULL parameters + data := []byte{ + 0x17, // COM_STMT_EXECUTE command + 0xb1, 0x03, 0x00, 0x00, // Statement ID (945) + 0x00, // Flags + 0x01, 0x00, 0x00, 0x00, // Iteration count (1) + 0x01, // NULL bitmap (1 byte for 3 params, all NULL) + 0x00, // New params bind flag (0 = reuse previous types) + // No parameter type data since new_params_bind_flag = 0 + // No parameter value data since all parameters are NULL + } + + // Pad to 39 bytes total to match the logs + for len(data) < 39 { + data = append(data, 0x00) + } + + logger := zap.NewNop() + ctx := context.Background() + + // Test with CLIENT_QUERY_ATTRIBUTES disabled (common case) + clientCapabilities := uint32(0) + + packet, err := DecodeStmtExecute(ctx, logger, data, preparedStmts, clientCapabilities) + + if err != nil { + t.Fatalf("DecodeStmtExecute failed: %v", err) + } + + // Verify that ParameterCount is set correctly from the prepared statement + if packet.ParameterCount != 3 { + t.Errorf("Expected ParameterCount to be 3, got %d", packet.ParameterCount) + } + + // Verify other fields + if packet.StatementID != 945 { + t.Errorf("Expected StatementID to be 945, got %d", packet.StatementID) + } + + if packet.Status != mysql.COM_STMT_EXECUTE { + t.Errorf("Expected Status to be COM_STMT_EXECUTE (%d), got %d", mysql.COM_STMT_EXECUTE, packet.Status) + } +} + +func TestDecodeStmtExecute_NoParameters(t *testing.T) { + // Create a mock prepared statement with 0 parameters + stmtPrepOk := &mysql.StmtPrepareOkPacket{ + StatementID: 946, + NumParams: 0, + NumColumns: 10, + } + + preparedStmts := map[uint32]*mysql.StmtPrepareOkPacket{ + 946: stmtPrepOk, + } + + // Mock COM_STMT_EXECUTE packet data with no parameters + data := []byte{ + 0x17, // COM_STMT_EXECUTE command + 0xb2, 0x03, 0x00, 0x00, // Statement ID (946) + 0x00, // Flags + 0x01, 0x00, 0x00, 0x00, // Iteration count (1) + // No NULL bitmap since no parameters + // No new params bind flag since no parameters + } + + logger := zap.NewNop() + ctx := context.Background() + clientCapabilities := uint32(0) + + packet, err := DecodeStmtExecute(ctx, logger, data, preparedStmts, clientCapabilities) + + if err != nil { + t.Fatalf("DecodeStmtExecute failed: %v", err) + } + + // Verify that ParameterCount is 0 and the function returns early + if packet.ParameterCount != 0 { + t.Errorf("Expected ParameterCount to be 0, got %d", packet.ParameterCount) + } + + if packet.StatementID != 946 { + t.Errorf("Expected StatementID to be 946, got %d", packet.StatementID) + } +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtFetchPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtFetchPacket.go new file mode 100644 index 0000000..9b2761b --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtFetchPacket.go @@ -0,0 +1,29 @@ +//go:build linux + +package preparedstmt + +import ( + "context" + "encoding/binary" + "errors" + + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +// COM_STMT_FETCH: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_fetch.html + +func DecodeStmtFetch(_ context.Context, _ *zap.Logger, data []byte) (*mysql.StmtFetchPacket, error) { + if len(data) < 9 { + return &mysql.StmtFetchPacket{}, errors.New("data too short for COM_STMT_FETCH") + } + + packet := &mysql.StmtFetchPacket{ + Status: data[0], + } + + packet.StatementID = binary.LittleEndian.Uint32(data[1:5]) + packet.NumRows = binary.LittleEndian.Uint32(data[5:9]) + + return packet, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtPreparePacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtPreparePacket.go new file mode 100644 index 0000000..7e02c87 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtPreparePacket.go @@ -0,0 +1,22 @@ +//go:build linux + +package preparedstmt + +import ( + "context" + "strings" + + "go.keploy.io/server/v2/pkg/models/mysql" +) + +//COM_STMT_PREPARE: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_prepare.html + +func DecodeStmtPrepare(_ context.Context, data []byte) (*mysql.StmtPreparePacket, error) { + packet := &mysql.StmtPreparePacket{ + Command: data[0], + } + + query := string(data[1:]) + packet.Query = strings.ReplaceAll(query, "\t", "") + return packet, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtResetPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtResetPacket.go new file mode 100644 index 0000000..40ae959 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtResetPacket.go @@ -0,0 +1,25 @@ +//go:build linux + +package preparedstmt + +import ( + "context" + "encoding/binary" + "fmt" + + "go.keploy.io/server/v2/pkg/models/mysql" +) + +//COM_STMT_RESET: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_reset.html + +func DecodeStmtReset(_ context.Context, data []byte) (*mysql.StmtResetPacket, error) { + if len(data) != 5 { + return nil, fmt.Errorf("invalid COM_STMT_RESET packet") + } + + packet := &mysql.StmtResetPacket{ + Status: data[0], + StatementID: binary.LittleEndian.Uint32(data[1:5]), + } + return packet, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtSendLongDataPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtSendLongDataPacket.go new file mode 100644 index 0000000..4dc02c4 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtSendLongDataPacket.go @@ -0,0 +1,28 @@ +//go:build linux + +package preparedstmt + +import ( + "context" + "encoding/binary" + "fmt" + + "go.keploy.io/server/v2/pkg/models/mysql" +) + +// COM_STMT_SEND_LONG_DATA: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_send_long_data.html + +func DecodeStmtSendLongData(_ context.Context, data []byte) (*mysql.StmtSendLongDataPacket, error) { + if len(data) < 7 || data[0] != 0x18 { + return &mysql.StmtSendLongDataPacket{}, fmt.Errorf("invalid COM_STMT_SEND_LONG_DATA packet") + } + + packet := &mysql.StmtSendLongDataPacket{ + Status: data[0], + StatementID: binary.LittleEndian.Uint32(data[1:5]), + ParameterID: binary.LittleEndian.Uint16(data[5:7]), + Data: data[7:], + } + + return packet, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/queryPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/queryPacket.go new file mode 100644 index 0000000..a6b3854 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/queryPacket.go @@ -0,0 +1,225 @@ +//go:build linux + +// Package query provides functions to decode MySQL command phase packets. +package query + +import ( + "context" + "encoding/binary" + "fmt" + "io" + "strings" + + intUtil "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +// COM_QUERY: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_query.html + +func DecodeQuery(_ context.Context, logger *zap.Logger, data []byte, clientCapabilities uint32) (*mysql.QueryPacket, error) { + + logger.Debug("Decoding query packet", zap.Int("data_length", len(data)), zap.Any("query buffer", string(data))) + + if len(data) < 2 { + return nil, fmt.Errorf("query packet is empty") + } + packet := &mysql.QueryPacket{ + Command: data[0], + } + + pos := 1 // Start reading after the command byte + + // Early return if no query attributes to process + if clientCapabilities&mysql.CLIENT_QUERY_ATTRIBUTES == 0 { + packet.Query = string(data[pos:]) + packet.Query = replaceTabsWithSpaces(packet.Query) + logger.Debug("Decoded query packet without attributes", zap.String("query", packet.Query)) + return packet, nil + } + + if pos >= len(data) { + return nil, fmt.Errorf("malformed query packet: no data for parameter_count when CLIENT_QUERY_ATTRIBUTES is set") + } + + // 1. Read parameter_count (lenenc-int). + paramCount, isNull, n := utils.ReadLengthEncodedInteger(data[pos:]) + if isNull { + return nil, fmt.Errorf("malformed query packet: got NULL for parameter_count") + } + pos += n + + packet.ParameterCount = int(paramCount) + + if pos >= len(data) { + return nil, fmt.Errorf("malformed query packet: missing parameter_set_count") + } + + // 2. Read parameter_set_count (lenenc-int). + paramSetCount, isNull, n := utils.ReadLengthEncodedInteger(data[pos:]) + if isNull || (paramSetCount != 1) { + return nil, fmt.Errorf("malformed query packet: expected parameter_set_count of 1, got %d", paramSetCount) + } + pos += n + + if paramCount > 0 { + + nullBitmapLength := (packet.ParameterCount + 7) / 8 + if pos+nullBitmapLength > len(data) { + return nil, fmt.Errorf("malformed query packet: data too short for query attribute null_bitmap") + } + packet.NullBitmap = data[pos : pos+nullBitmapLength] + pos += int(nullBitmapLength) + + if pos+1 > len(data) { + return nil, fmt.Errorf("malformed query packet: data too short for new_params_bind_flag") + } + packet.NewParamsBindFlag = data[pos] + pos++ + + if packet.NewParamsBindFlag != 1 { + return nil, fmt.Errorf("malformed query packet: new_params_bind_flag should be always 1 if parameter_count > 0") + } + + packet.Parameters = make([]mysql.Parameter, packet.ParameterCount) + + for i := 0; i < packet.ParameterCount; i++ { + if pos+2 > len(data) { + return nil, fmt.Errorf("malformed query packet: data too short for parameter types") + } + packet.Parameters[i].Type = binary.LittleEndian.Uint16(data[pos : pos+2]) + packet.Parameters[i].Unsigned = (data[pos+1] & 0x80) != 0 // Check if the highest bit is set + pos += 2 + } + + // Read Parameter Values + for i := 0; i < packet.ParameterCount; i++ { + if pos >= len(data) { + return nil, io.ErrUnexpectedEOF + } + + // Process Parameter based on its type + param := &packet.Parameters[i] + + // Handle length-encoded values (only for types that require variable-length data) + switch mysql.FieldType(param.Type) { + case mysql.FieldTypeString, mysql.FieldTypeVarString, mysql.FieldTypeVarChar, mysql.FieldTypeBLOB, mysql.FieldTypeTinyBLOB, mysql.FieldTypeMediumBLOB, mysql.FieldTypeLongBLOB, mysql.FieldTypeJSON: + // Read the length of the parameter value + length, _, n := utils.ReadLengthEncodedInteger(data[pos:]) + pos += n + if pos+int(length) > len(data) { + return nil, io.ErrUnexpectedEOF + } + + if intUtil.IsASCII(string(data[pos : pos+int(length)])) { + param.Value = string(data[pos : pos+int(length)]) + } else { + param.Value = intUtil.EncodeBase64(data[pos : pos+int(length)]) + } + pos += int(length) + case mysql.FieldTypeLong: + if len(data[pos:]) < 4 { + return nil, fmt.Errorf("malformed FieldTypeLong value") + } + if param.Unsigned { + param.Value = uint32(binary.LittleEndian.Uint32(data[pos : pos+4])) + } else { + param.Value = int32(binary.LittleEndian.Uint32(data[pos : pos+4])) + } + pos += 4 + + case mysql.FieldTypeTiny: + if len(data[pos:]) < 1 { + return nil, fmt.Errorf("malformed FieldTypeTiny value") + } + if param.Unsigned { + param.Value = uint8(data[pos]) + } else { + param.Value = int8(data[pos]) + } + pos += 1 + + case mysql.FieldTypeShort, mysql.FieldTypeYear: + if len(data[pos:]) < 2 { + return nil, fmt.Errorf("malformed FieldTypeShort value") + } + if param.Unsigned { + param.Value = uint16(binary.LittleEndian.Uint16(data[pos : pos+2])) + } else { + param.Value = int16(binary.LittleEndian.Uint16(data[pos : pos+2])) + } + pos += 2 + + case mysql.FieldTypeLongLong: + if len(data[pos:]) < 8 { + return nil, fmt.Errorf("malformed FieldTypeLongLong value") + } + if param.Unsigned { + param.Value = uint64(binary.LittleEndian.Uint64(data[pos : pos+8])) + } else { + param.Value = int64(binary.LittleEndian.Uint64(data[pos : pos+8])) + } + pos += 8 + + case mysql.FieldTypeFloat: + if len(data[pos:]) < 4 { + return nil, fmt.Errorf("malformed FieldTypeFloat value") + } + param.Value = float32(binary.LittleEndian.Uint32(data[pos : pos+4])) + pos += 4 + + case mysql.FieldTypeDouble: + if len(data[pos:]) < 8 { + return nil, fmt.Errorf("malformed FieldTypeDouble value") + } + param.Value = float64(binary.LittleEndian.Uint64(data[pos : pos+8])) + pos += 8 + + case mysql.FieldTypeDate, mysql.FieldTypeNewDate: + value, _, err := utils.ParseBinaryDate(data[pos:]) + if err != nil { + return nil, err + } + param.Value = value + pos += len(param.Value.(string)) // Assuming date parsing returns a string + + case mysql.FieldTypeTimestamp, mysql.FieldTypeDateTime: + value, _, err := utils.ParseBinaryDateTime(data[pos:]) + if err != nil { + return nil, err + } + param.Value = value + pos += len(param.Value.(string)) // Assuming datetime parsing returns a string + + case mysql.FieldTypeTime: + value, _, err := utils.ParseBinaryTime(data[pos:]) + if err != nil { + return nil, err + } + param.Value = value + pos += len(param.Value.(string)) // Assuming time parsing returns a string + default: + return nil, fmt.Errorf("unsupported parameter type: %d", param.Type) + } + } + } + + if pos >= len(data) { + return nil, fmt.Errorf("malformed query packet: no data for query") + } + + // Trim any trailing null bytes which can sometimes be appended by clients. + packet.Query = string(data[pos:]) + packet.Query = replaceTabsWithSpaces(packet.Query) + + logger.Debug("Decoded query packet with attributes", zap.String("query", packet.Query)) + + return packet, nil +} + +// This is required to replace tabs with spaces in the query string, as yaml does not support tabs. +func replaceTabsWithSpaces(query string) string { + return strings.ReplaceAll(query, "\t", " ") +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go new file mode 100644 index 0000000..ff360da --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go @@ -0,0 +1,497 @@ +//go:build linux + +// Package rowscols provides encoding and decoding of MySQL row & column packets. +package rowscols + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "strings" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html#sect_protocol_binary_resultset_row + +func DecodeBinaryRow(_ context.Context, _ *zap.Logger, data []byte, columns []*mysql.ColumnDefinition41) (*mysql.BinaryRow, int, error) { + + offset := 0 + row := &mysql.BinaryRow{ + Header: mysql.Header{ + PayloadLength: utils.ReadUint24(data[offset : offset+3]), + SequenceID: data[offset+3], + }, + } + offset += 4 + + if data[offset] != 0x00 { + return nil, offset, errors.New("malformed binary row packet") + } + row.OkAfterRow = true + offset++ + + nullBitmapLen := (len(columns) + 7 + 2) / 8 + nullBitmap := data[offset : offset+nullBitmapLen] + row.RowNullBuffer = nullBitmap + + offset += nullBitmapLen + + for i, col := range columns { + if isNull(nullBitmap, i) { // This Null doesn't progress the offset + row.Values = append(row.Values, mysql.ColumnEntry{ + Type: mysql.FieldType(col.Type), + Name: col.Name, + Value: nil, + }) + continue + } + + res, n, err := readBinaryValue(data[offset:], col) + if err != nil { + return nil, offset, err + } + + row.Values = append(row.Values, mysql.ColumnEntry{ + Type: mysql.FieldType(col.Type), + Name: col.Name, + Value: res.value, + Unsigned: res.isUnsigned, + }) + offset += n + } + return row, offset, nil +} + +func isNull(nullBitmap []byte, index int) bool { + bytePos := (index + 2) / 8 + bitPos := (index + 2) % 8 + return nullBitmap[bytePos]&(1< uint32(pos) { + //length of default value lenenc-int + defaultValueLength, _, n := utils.ReadLengthEncodedInteger(b[pos:]) + pos += n + + if pos+int(defaultValueLength) > len(b) { + + return nil, pos, fmt.Errorf("malformed packet: %v", err) + } + + //default value string[$len] + packet.DefaultValue = string(b[pos:(pos + int(defaultValueLength))]) + pos-- + } + + return packet, pos, nil +} + +func EncodeColumn(_ context.Context, _ *zap.Logger, packet *mysql.ColumnDefinition41) ([]byte, error) { + // Build the body first + body := new(bytes.Buffer) + + // Catalog, Schema, Table, OrgTable, Name, OrgName + if err := utils.WriteLengthEncodedString(body, packet.Catalog); err != nil { + return nil, fmt.Errorf("failed to write Catalog: %w", err) + } + if err := utils.WriteLengthEncodedString(body, packet.Schema); err != nil { + return nil, fmt.Errorf("failed to write Schema: %w", err) + } + if err := utils.WriteLengthEncodedString(body, packet.Table); err != nil { + return nil, fmt.Errorf("failed to write Table: %w", err) + } + if err := utils.WriteLengthEncodedString(body, packet.OrgTable); err != nil { + return nil, fmt.Errorf("failed to write OrgTable: %w", err) + } + if err := utils.WriteLengthEncodedString(body, packet.Name); err != nil { + return nil, fmt.Errorf("failed to write Name: %w", err) + } + if err := utils.WriteLengthEncodedString(body, packet.OrgName); err != nil { + return nil, fmt.Errorf("failed to write OrgName: %w", err) + } + + // Fixed-length fields length (always 0x0c) + if err := body.WriteByte(packet.FixedLength); err != nil { + return nil, fmt.Errorf("failed to write FixedLength: %w", err) + } + + // CharacterSet, ColumnLength, Type, Flags, Decimals + if err := binary.Write(body, binary.LittleEndian, packet.CharacterSet); err != nil { + return nil, fmt.Errorf("failed to write CharacterSet: %w", err) + } + if err := binary.Write(body, binary.LittleEndian, packet.ColumnLength); err != nil { + return nil, fmt.Errorf("failed to write ColumnLength: %w", err) + } + if err := body.WriteByte(packet.Type); err != nil { + return nil, fmt.Errorf("failed to write Type: %w", err) + } + if err := binary.Write(body, binary.LittleEndian, packet.Flags); err != nil { + return nil, fmt.Errorf("failed to write Flags: %w", err) + } + if err := body.WriteByte(packet.Decimals); err != nil { + return nil, fmt.Errorf("failed to write Decimals: %w", err) + } + + // Filler (2 bytes) + filler := packet.Filler + if len(filler) != 2 { + filler = []byte{0x00, 0x00} + } + if _, err := body.Write(filler); err != nil { + return nil, fmt.Errorf("failed to write Filler: %w", err) + } + + // Optional DefaultValue (COM_FIELD_LIST case) + if packet.DefaultValue != "" { + if err := utils.WriteLengthEncodedString(body, packet.DefaultValue); err != nil { + return nil, fmt.Errorf("failed to write DefaultValue: %w", err) + } + } + + // Prepend header with computed payload length + out := new(bytes.Buffer) + if err := utils.WriteUint24(out, uint32(body.Len())); err != nil { + return nil, fmt.Errorf("failed to write PayloadLength: %w", err) + } + if err := out.WriteByte(packet.Header.SequenceID); err != nil { + return nil, fmt.Errorf("failed to write SequenceID: %w", err) + } + if _, err := out.Write(body.Bytes()); err != nil { + return nil, err + } + return out.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/textRowPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/textRowPacket.go new file mode 100644 index 0000000..04e2d67 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/textRowPacket.go @@ -0,0 +1,105 @@ +//go:build linux + +package rowscols + +import ( + "bytes" + "context" + "fmt" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.uber.org/zap" +) + +//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_query_response_text_resultset_row.html + +func DecodeTextRow(_ context.Context, _ *zap.Logger, data []byte, columns []*mysql.ColumnDefinition41) (*mysql.TextRow, int, error) { + offset := 0 + row := &mysql.TextRow{ + Header: mysql.Header{ + PayloadLength: utils.ReadUint24(data[offset : offset+3]), + SequenceID: data[offset+3], + }, + } + + offset += 4 + + for _, col := range columns { + dataLength := data[offset] + if dataLength == 0xfb { // NULL + row.Values = append(row.Values, mysql.ColumnEntry{ + Type: mysql.FieldType(col.Type), + Name: col.Name, + Value: nil, + }) + offset++ + continue + } + + value, _, n, err := utils.ReadLengthEncodedString(data[offset:]) + if err != nil { + return nil, offset, fmt.Errorf("failed to read length-encoded string: %w", err) + } + row.Values = append(row.Values, mysql.ColumnEntry{ + Type: mysql.FieldType(col.Type), + Name: col.Name, + Value: string(value), + }) + offset += n + } + + return row, offset, nil +} + +// rowscols/textRowPacket.go + +func EncodeTextRow(_ context.Context, _ *zap.Logger, row *mysql.TextRow, columns []*mysql.ColumnDefinition41) ([]byte, error) { + body := new(bytes.Buffer) + + // Write each column's value into the body + for i := range columns { + v := row.Values[i].Value + switch x := v.(type) { + case nil: + // NULL is 0xfb + if err := body.WriteByte(0xfb); err != nil { + return nil, fmt.Errorf("failed to write NULL: %w", err) + } + + case string: + if err := utils.WriteLengthEncodedString(body, x); err != nil { + return nil, fmt.Errorf("failed to write lenenc string: %w", err) + } + + case []byte: + // allow blobs as raw bytes in text rows + if err := utils.WriteLengthEncodedInteger(body, uint64(len(x))); err != nil { + return nil, fmt.Errorf("failed to write len for bytes: %w", err) + } + if _, err := body.Write(x); err != nil { + return nil, fmt.Errorf("failed to write bytes: %w", err) + } + + default: + // fall back to the textual form (keeps old recordings working) + s := fmt.Sprint(x) + if err := utils.WriteLengthEncodedString(body, s); err != nil { + return nil, fmt.Errorf("failed to write fallback string: %w", err) + } + } + } + + // Now prepend the header using the computed length + out := new(bytes.Buffer) + if err := utils.WriteUint24(out, uint32(body.Len())); err != nil { + return nil, fmt.Errorf("failed to write PayloadLength: %w", err) + } + if err := out.WriteByte(row.Header.SequenceID); err != nil { + return nil, fmt.Errorf("failed to write SequenceID: %w", err) + } + if _, err := out.Write(body.Bytes()); err != nil { + return nil, err + } + return out.Bytes(), nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/initDbPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/initDbPacket.go new file mode 100644 index 0000000..a498e69 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/initDbPacket.go @@ -0,0 +1,20 @@ +//go:build linux + +// Package utility provides encoding and decoding of utility command packets. +package utility + +import ( + "context" + + "go.keploy.io/server/v2/pkg/models/mysql" +) + +//COM_INIT_DB: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_init_db.html + +func DecodeInitDb(_ context.Context, data []byte) (*mysql.InitDBPacket, error) { + packet := &mysql.InitDBPacket{ + Command: data[0], + Schema: string(data[1:]), + } + return packet, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/setOptionPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/setOptionPacket.go new file mode 100644 index 0000000..91599d0 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/setOptionPacket.go @@ -0,0 +1,26 @@ +//go:build linux + +package utility + +import ( + "context" + "encoding/binary" + "fmt" + + "go.keploy.io/server/v2/pkg/models/mysql" +) + +//COM_SET_OPTION: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_set_option.html + +func DecodeSetOption(_ context.Context, data []byte) (*mysql.SetOptionPacket, error) { + if len(data) < 3 { + return nil, fmt.Errorf("set option packet too short") + } + + packet := &mysql.SetOptionPacket{ + Status: data[0], + Option: binary.LittleEndian.Uint16(data[1:3]), + } + + return packet, nil +} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/util.go b/keploy/pkg/core/proxy/integrations/mysql/wire/util.go new file mode 100644 index 0000000..1203af7 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/mysql/wire/util.go @@ -0,0 +1,175 @@ +//go:build linux + +package wire + +import ( + "context" + "fmt" + "net" + "sync" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/models/mysql" +) + +const RESET = 0x00 + +type DecodeContext struct { + Mode models.Mode + LastOp *LastOperation + PreparedStatements map[uint32]*mysql.StmtPrepareOkPacket + ServerGreetings *ServerGreetings + ClientCapabilities uint32 + PluginName string + UseSSL bool + // Capability flags + ServerCaps uint32 // negotiated server caps (from HandshakeV10) + ClientCaps uint32 // live client's caps (from HandshakeResponse41) + RecordedClientCaps uint32 // caps from the recorded config mock + PreferRecordedCaps bool // if true, prefer RecordedClientCaps over ClientCaps +} + +const CLIENT_DEPRECATE_EOF = 0x01000000 + +func (d *DecodeContext) effectiveClientCaps() uint32 { + if d.PreferRecordedCaps && d.RecordedClientCaps != 0 { + return d.RecordedClientCaps + } + return d.ClientCaps +} + +func (d *DecodeContext) DeprecateEOF() bool { + return (d.ServerCaps&CLIENT_DEPRECATE_EOF) != 0 && + (d.effectiveClientCaps()&CLIENT_DEPRECATE_EOF) != 0 +} + +// This map is used to store the last operation that was performed on a connection. +// It helps us to determine the last mysql packet. + +type LastOperation struct { + sync.RWMutex + operations map[net.Conn]byte +} + +func NewLastOpMap() *LastOperation { + return &LastOperation{ + operations: make(map[net.Conn]byte), + } +} + +func (lo *LastOperation) Load(key net.Conn) (value byte, ok bool) { + lo.RLock() + result, ok := lo.operations[key] + lo.RUnlock() + return result, ok +} + +func (lo *LastOperation) Store(key net.Conn, value byte) { + lo.Lock() + lo.operations[key] = value + lo.Unlock() +} + +// This map is used to store the server greetings for each connection. +// It helps us to determine the server version and capabilities. +// Capabilities are helpful in decoding some packets. + +type ServerGreetings struct { + sync.RWMutex + handshakes map[net.Conn]*mysql.HandshakeV10Packet +} + +func NewGreetings() *ServerGreetings { + return &ServerGreetings{ + handshakes: make(map[net.Conn]*mysql.HandshakeV10Packet), + } +} + +func (sg *ServerGreetings) Load(key net.Conn) (*mysql.HandshakeV10Packet, bool) { + sg.RLock() + result, ok := sg.handshakes[key] + sg.RUnlock() + return result, ok +} + +func (sg *ServerGreetings) Store(key net.Conn, value *mysql.HandshakeV10Packet) { + sg.Lock() + sg.handshakes[key] = value + sg.Unlock() +} + +func setPacketInfo(_ context.Context, parsedPacket *mysql.PacketBundle, pkt interface{}, pktType string, clientConn net.Conn, lastOp byte, decodeCtx *DecodeContext) { + parsedPacket.Header.Type = pktType + parsedPacket.Message = pkt + decodeCtx.LastOp.Store(clientConn, lastOp) +} + +func GetPluginName(buf interface{}) (string, error) { + switch v := buf.(type) { + case *mysql.HandshakeV10Packet: + return v.AuthPluginName, nil + case *mysql.AuthSwitchRequestPacket: + return v.PluginName, nil + default: + return "", fmt.Errorf("invalid packet type to get plugin name") + } +} + +func GetCachingSha2PasswordMechanism(data byte) (string, error) { + switch data { + case byte(mysql.PerformFullAuthentication): + return mysql.CachingSha2PasswordToString(mysql.PerformFullAuthentication), nil + case byte(mysql.FastAuthSuccess): + return mysql.CachingSha2PasswordToString(mysql.FastAuthSuccess), nil + default: + einval := fmt.Sprintf("invalid caching_sha2_password mechanism, found:%02x ", data) + return "", fmt.Errorf("%s", einval) + } +} + +func StringToCachingSha2PasswordMechanism(data string) (mysql.CachingSha2Password, error) { + switch data { + case "PerformFullAuthentication": + return mysql.PerformFullAuthentication, nil + case "FastAuthSuccess": + return mysql.FastAuthSuccess, nil + default: + einval := fmt.Sprintf("invalid caching_sha2_password mechanism, found:%s ", data) + return 0, fmt.Errorf("%s", einval) + } +} + +func IsGenericResponsePkt(packet *mysql.PacketBundle) bool { + if packet == nil { + return false + } + switch packet.Message.(type) { + case *mysql.OKPacket, *mysql.ERRPacket, *mysql.EOFPacket: + return true + default: + return false + } +} + +func IsNoResponseCommand(command string) bool { + switch command { + case mysql.CommandStatusToString(mysql.COM_STMT_CLOSE), mysql.CommandStatusToString(mysql.COM_STMT_SEND_LONG_DATA): + return true + default: + return false + } +} + +// PrintByteArray is only for debugging purpose +func PrintByteArray(name string, b []byte) { + fmt.Printf("%s:\n", name) + var i = 1 + for _, byte := range b { + fmt.Printf(" %02x", byte) + i++ + if i%16 == 0 { + fmt.Println() + } + } + fmt.Println() +} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/decode.go b/keploy/pkg/core/proxy/integrations/postgres/v1/decode.go new file mode 100644 index 0000000..25b8f5e --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/postgres/v1/decode.go @@ -0,0 +1,152 @@ +//go:build linux + +// Package v1 provides functionality for decoding Postgres requests and responses. +package v1 + +import ( + "context" + "fmt" + "io" + "net" + "strings" + "sync" + "time" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func decodePostgres(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, _ models.OutgoingOptions) error { + pgRequests := [][]byte{reqBuf} + errCh := make(chan error, 1) + + go func(errCh chan error, pgRequests [][]byte) { + defer pUtil.Recover(logger, clientConn, nil) + // close should be called from the producer of the channel + defer close(errCh) + for { + // Since protocol packets have to be parsed for checking stream end, + // clientConnection have deadline for read to determine the end of stream. + err := clientConn.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) + if err != nil && err != io.EOF && strings.Contains(err.Error(), "use of closed network connection") { + utils.LogError(logger, err, "failed to set the read deadline for the pg client conn") + errCh <- err + } + + // To read the stream of request packets from the client + for { + buffer, err := pUtil.ReadBytes(ctx, logger, clientConn) + if err != nil { + // Applied this nolint to ignore the staticcheck error here because of readability + // nolint:staticcheck + if netErr, ok := err.(net.Error); !(ok && netErr.Timeout()) { + if err == io.EOF { + logger.Debug("EOF error received from client. Closing conn in postgres !!") + errCh <- err + } + logger.Debug("failed to read the request message in proxy for postgres dependency") + errCh <- err + } + } + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + break + } + pgRequests = append(pgRequests, buffer) + } + + if len(pgRequests) == 0 { + continue + } + var mutex sync.Mutex + matched, pgResponses, err := matchingReadablePG(ctx, logger, &mutex, pgRequests, mockDb) + if err != nil { + errCh <- fmt.Errorf("error while matching tcs mocks %v", err) + return + } + + if !matched { + logger.Debug("MISMATCHED REQ is" + string(pgRequests[0])) + _, err = pUtil.PassThrough(ctx, logger, clientConn, dstCfg, pgRequests) + if err != nil { + utils.LogError(logger, err, "failed to pass the request", zap.Any("request packets", len(pgRequests))) + errCh <- err + } + continue + } + for _, pgResponse := range pgResponses { + encoded, err := util.DecodeBase64(pgResponse.Payload) + if len(pgResponse.PacketTypes) > 0 && len(pgResponse.Payload) == 0 { + encoded, err = postgresDecoderFrontend(pgResponse) + } + if err != nil { + utils.LogError(logger, err, "failed to decode the response message in proxy for postgres dependency") + errCh <- err + } + _, err = clientConn.Write(encoded) + if err != nil && err != io.EOF && strings.Contains(err.Error(), "use of closed network connection") { + utils.LogError(logger, err, "failed to write the response message to the client application") + errCh <- err + } + } + // Clear the buffer for the next dependency call + pgRequests = [][]byte{} + } + }(errCh, pgRequests) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + return nil + } + return err + } +} + +type QueryData struct { + PrepIdentifier string `json:"PrepIdentifier" yaml:"PrepIdentifier"` + Query string `json:"Query" yaml:"Query"` +} + +type PrepMap map[string][]QueryData + +type TestPrepMap map[string][]QueryData + +func getRecordPrepStatement(allMocks []*models.Mock) PrepMap { + preparedstatement := make(PrepMap) + for _, v := range allMocks { + if v.Kind != "Postgres" { + continue + } + for _, req := range v.Spec.PostgresRequests { + var querydata []QueryData + psMap := make(map[string]string) + if len(req.PacketTypes) > 0 && req.PacketTypes[0] != "p" && req.Identfier != "StartupRequest" { + p := 0 + for _, header := range req.PacketTypes { + if header == "P" { + if strings.Contains(req.Parses[p].Name, "S_") || strings.Contains(req.Parses[p].Name, "s") { + psMap[req.Parses[p].Query] = req.Parses[p].Name + querydata = append(querydata, QueryData{PrepIdentifier: req.Parses[p].Name, + Query: req.Parses[p].Query, + }) + + } + p++ + } + } + } + // also append the query data for the prepared statement + if len(querydata) > 0 { + preparedstatement[v.ConnectionID] = append(preparedstatement[v.ConnectionID], querydata...) + } + } + + } + return preparedstatement +} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/encode.go b/keploy/pkg/core/proxy/integrations/postgres/v1/encode.go new file mode 100755 index 0000000..670a4d7 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/postgres/v1/encode.go @@ -0,0 +1,401 @@ +//go:build linux + +package v1 + +import ( + "context" + "encoding/binary" + "fmt" + "io" + "net" + "strconv" + "time" + + "github.com/jackc/pgproto3/v2" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" +) + +func encodePostgres(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, _ models.OutgoingOptions) error { + + logger.Debug("Inside the encodePostgresOutgoing function") + var pgRequests []models.Backend + + bufStr := util.EncodeBase64(reqBuf) + pg := NewBackend() + _, err := pg.decodeStartupMessage(reqBuf) + if err != nil { + utils.LogError(logger, err, "failed to decode startup message server") + } + + if bufStr != "" { + pgRequests = append(pgRequests, models.Backend{ + PacketTypes: pg.BackendWrapper.PacketTypes, + Identfier: "StartupRequest", + Length: uint32(len(reqBuf)), + Payload: bufStr, + Bind: pg.BackendWrapper.Bind, + PasswordMessage: pg.BackendWrapper.PasswordMessage, + CancelRequest: pg.BackendWrapper.CancelRequest, + Close: pg.BackendWrapper.Close, + CopyData: pg.BackendWrapper.CopyData, + CopyDone: pg.BackendWrapper.CopyDone, + CopyFail: pg.BackendWrapper.CopyFail, + Describe: pg.BackendWrapper.Describe, + Execute: pg.BackendWrapper.Execute, + Flush: pg.BackendWrapper.Flush, + FunctionCall: pg.BackendWrapper.FunctionCall, + GssEncRequest: pg.BackendWrapper.GssEncRequest, + Parse: pg.BackendWrapper.Parse, + Query: pg.BackendWrapper.Query, + SSlRequest: pg.BackendWrapper.SSlRequest, + StartupMessage: pg.BackendWrapper.StartupMessage, + SASLInitialResponse: pg.BackendWrapper.SASLInitialResponse, + SASLResponse: pg.BackendWrapper.SASLResponse, + Sync: pg.BackendWrapper.Sync, + Terminate: pg.BackendWrapper.Terminate, + MsgType: pg.BackendWrapper.MsgType, + AuthType: pg.BackendWrapper.AuthType, + }) + + logger.Debug("Before for loop pg request starts", zap.Any("pgReqs", len(pgRequests))) + } + + _, err = destConn.Write(reqBuf) + if err != nil { + utils.LogError(logger, err, "failed to write request message to the destination server") + return err + } + var pgResponses []models.Frontend + + clientBuffChan := make(chan []byte) + destBuffChan := make(chan []byte) + errChan := make(chan error, 1) + + //get the error group from the context + g := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + + // read requests from client + g.Go(func() error { + defer pUtil.Recover(logger, clientConn, destConn) + defer close(clientBuffChan) + pUtil.ReadBuffConn(ctx, logger, clientConn, clientBuffChan, errChan) + return nil + }) + + // read responses from destination + g.Go(func() error { + defer pUtil.Recover(logger, clientConn, destConn) + defer close(destBuffChan) + pUtil.ReadBuffConn(ctx, logger, destConn, destBuffChan, errChan) + return nil + }) + + go func() { + defer pUtil.Recover(logger, clientConn, destConn) + err := g.Wait() + if err != nil { + logger.Info("error group is returning an error", zap.Error(err)) + } + close(errChan) + }() + + prevChunkWasReq := false + logger.Debug("the iteration for the pg request starts", zap.Any("pgReqs", len(pgRequests)), zap.Any("pgResps", len(pgResponses))) + + reqTimestampMock := time.Now() + var resTimestampMock time.Time + + for { + select { + case <-ctx.Done(): + if !prevChunkWasReq && len(pgRequests) > 0 && len(pgResponses) > 0 { + metadata := make(map[string]string) + metadata["type"] = "config" + metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) + // Save the mock + mocks <- &models.Mock{ + Version: models.GetVersion(), + Name: "mocks", + Kind: models.Postgres, + Spec: models.MockSpec{ + PostgresRequests: pgRequests, + PostgresResponses: pgResponses, + ReqTimestampMock: reqTimestampMock, + ResTimestampMock: resTimestampMock, + Metadata: metadata, + }, + ConnectionID: ctx.Value(models.ClientConnectionIDKey).(string), + } + return ctx.Err() + } + case buffer := <-clientBuffChan: + + // Write the request message to the destination + _, err := destConn.Write(buffer) + if err != nil { + utils.LogError(logger, err, "failed to write request message to the destination server") + return err + } + + logger.Debug("the iteration for the pg request ends with no of pgReqs:" + strconv.Itoa(len(pgRequests)) + " and pgResps: " + strconv.Itoa(len(pgResponses))) + if !prevChunkWasReq && len(pgRequests) > 0 && len(pgResponses) > 0 { + metadata := make(map[string]string) + metadata["type"] = "config" + metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) + // Save the mock + mocks <- &models.Mock{ + Version: models.GetVersion(), + Name: "mocks", + Kind: models.Postgres, + Spec: models.MockSpec{ + PostgresRequests: pgRequests, + PostgresResponses: pgResponses, + ReqTimestampMock: reqTimestampMock, + ResTimestampMock: resTimestampMock, + Metadata: metadata, + }, + ConnectionID: ctx.Value(models.ClientConnectionIDKey).(string), + } + pgRequests = []models.Backend{} + pgResponses = []models.Frontend{} + } + + bufStr := util.EncodeBase64(buffer) + if bufStr != "" { + pg := NewBackend() + var msg pgproto3.FrontendMessage + + if !isStartupPacket(buffer) && len(buffer) > 5 { + bufferCopy := buffer + for i := 0; i < len(bufferCopy)-5; { + pg.BackendWrapper.BodyLen = int(binary.BigEndian.Uint32(buffer[i+1:])) - 4 + if len(buffer) < (i + pg.BackendWrapper.BodyLen + 5) { + utils.LogError(logger, nil, "failed to translate the postgres request message due to shorter network packet buffer. Length of buffer is "+fmt.Sprint(len(buffer))+" buffer value :"+string(buffer)+" and pg.BackendWrapper.BodyLen is "+fmt.Sprint(pg.BackendWrapper.BodyLen)) + break + } + pg.BackendWrapper.MsgType = buffer[i] + + msg, err = pg.translateToReadableBackend(buffer[i:(i + pg.BackendWrapper.BodyLen + 5)]) + if err != nil && buffer[i] != 112 { + utils.LogError(logger, err, "failed to translate the request message to readable") + } + if pg.BackendWrapper.MsgType == 'p' { + pg.BackendWrapper.PasswordMessage = *msg.(*pgproto3.PasswordMessage) + } + + if pg.BackendWrapper.MsgType == 'P' { + pg.BackendWrapper.Parse = *msg.(*pgproto3.Parse) + pg.BackendWrapper.Parses = append(pg.BackendWrapper.Parses, pg.BackendWrapper.Parse) + } + + if pg.BackendWrapper.MsgType == 'B' { + pg.BackendWrapper.Bind = *msg.(*pgproto3.Bind) + pg.BackendWrapper.Binds = append(pg.BackendWrapper.Binds, pg.BackendWrapper.Bind) + } + + if pg.BackendWrapper.MsgType == 'E' { + pg.BackendWrapper.Execute = *msg.(*pgproto3.Execute) + pg.BackendWrapper.Executes = append(pg.BackendWrapper.Executes, pg.BackendWrapper.Execute) + } + + pg.BackendWrapper.PacketTypes = append(pg.BackendWrapper.PacketTypes, string(pg.BackendWrapper.MsgType)) + + i += 5 + pg.BackendWrapper.BodyLen + } + + pgMock := &models.Backend{ + PacketTypes: pg.BackendWrapper.PacketTypes, + Identfier: "ClientRequest", + Length: uint32(len(reqBuf)), + // Payload: bufStr, + Bind: pg.BackendWrapper.Bind, + Binds: pg.BackendWrapper.Binds, + PasswordMessage: pg.BackendWrapper.PasswordMessage, + CancelRequest: pg.BackendWrapper.CancelRequest, + Close: pg.BackendWrapper.Close, + CopyData: pg.BackendWrapper.CopyData, + CopyDone: pg.BackendWrapper.CopyDone, + CopyFail: pg.BackendWrapper.CopyFail, + Describe: pg.BackendWrapper.Describe, + Execute: pg.BackendWrapper.Execute, + Executes: pg.BackendWrapper.Executes, + Flush: pg.BackendWrapper.Flush, + FunctionCall: pg.BackendWrapper.FunctionCall, + GssEncRequest: pg.BackendWrapper.GssEncRequest, + Parse: pg.BackendWrapper.Parse, + Parses: pg.BackendWrapper.Parses, + Query: pg.BackendWrapper.Query, + SSlRequest: pg.BackendWrapper.SSlRequest, + StartupMessage: pg.BackendWrapper.StartupMessage, + SASLInitialResponse: pg.BackendWrapper.SASLInitialResponse, + SASLResponse: pg.BackendWrapper.SASLResponse, + Sync: pg.BackendWrapper.Sync, + Terminate: pg.BackendWrapper.Terminate, + MsgType: pg.BackendWrapper.MsgType, + AuthType: pg.BackendWrapper.AuthType, + } + afterEncoded, err := postgresDecoderBackend(*pgMock) + if err != nil { + logger.Debug("failed to decode the response message in proxy for postgres dependency", zap.Error(err)) + } + + if len(afterEncoded) != len(buffer) && len(pgMock.PacketTypes) > 0 && pgMock.PacketTypes[0] != "p" { + logger.Debug("the length of the encoded buffer is not equal to the length of the original buffer", zap.Any("after_encoded", len(afterEncoded)), zap.Any("buffer", len(buffer))) + pgMock.Payload = bufStr + } + pgRequests = append(pgRequests, *pgMock) + + } + + if isStartupPacket(buffer) { + pgMock := &models.Backend{ + Identfier: "StartupRequest", + Payload: bufStr, + } + pgRequests = append(pgRequests, *pgMock) + } + } + prevChunkWasReq = true + case buffer := <-destBuffChan: + if prevChunkWasReq { + // store the request timestamp + reqTimestampMock = time.Now() + } + + // Write the response message to the client + _, err := clientConn.Write(buffer) + if err != nil { + utils.LogError(logger, err, "failed to write response message to the client") + return err + } + + bufStr := util.EncodeBase64(buffer) + + if bufStr != "" { + pg := NewFrontend() + if !isStartupPacket(buffer) && len(buffer) > 5 && bufStr != "Tg==" { + bufferCopy := buffer + + //Saving list of packets in case of multiple packets in a single buffer steam + ps := make([]pgproto3.ParameterStatus, 0) + var dataRows []pgproto3.DataRow + + for i := 0; i < len(bufferCopy)-5; { + pg.FrontendWrapper.MsgType = buffer[i] + pg.FrontendWrapper.BodyLen = int(binary.BigEndian.Uint32(buffer[i+1:])) - 4 + msg, err := pg.translateToReadableResponse(logger, buffer[i:(i+pg.FrontendWrapper.BodyLen+5)]) + if err != nil { + utils.LogError(logger, err, "failed to translate the response message to readable") + break + } + + pg.FrontendWrapper.PacketTypes = append(pg.FrontendWrapper.PacketTypes, string(pg.FrontendWrapper.MsgType)) + i += 5 + pg.FrontendWrapper.BodyLen + + if pg.FrontendWrapper.ParameterStatus.Name != "" { + ps = append(ps, pg.FrontendWrapper.ParameterStatus) + } + if pg.FrontendWrapper.MsgType == 'C' { + pg.FrontendWrapper.CommandComplete = *msg.(*pgproto3.CommandComplete) + // empty the command tag + pg.FrontendWrapper.CommandComplete.CommandTag = []byte{} + pg.FrontendWrapper.CommandCompletes = append(pg.FrontendWrapper.CommandCompletes, pg.FrontendWrapper.CommandComplete) + } + if pg.FrontendWrapper.MsgType == 'D' && pg.FrontendWrapper.DataRow.RowValues != nil { + // Create a new slice for each DataRow + valuesCopy := make([]string, len(pg.FrontendWrapper.DataRow.RowValues)) + copy(valuesCopy, pg.FrontendWrapper.DataRow.RowValues) + + row := pgproto3.DataRow{ + RowValues: valuesCopy, // Use the copy of the values + Values: pg.FrontendWrapper.DataRow.Values, + } + dataRows = append(dataRows, row) + } + } + + if len(ps) > 0 { + pg.FrontendWrapper.ParameterStatusCombined = ps + } + if len(dataRows) > 0 { + pg.FrontendWrapper.DataRows = dataRows + } + + // from here take the msg and append its readable form to the pgResponses + pgMock := &models.Frontend{ + PacketTypes: pg.FrontendWrapper.PacketTypes, + Identfier: "ServerResponse", + Length: uint32(len(reqBuf)), + // Payload: bufStr, + AuthenticationOk: pg.FrontendWrapper.AuthenticationOk, + AuthenticationCleartextPassword: pg.FrontendWrapper.AuthenticationCleartextPassword, + AuthenticationMD5Password: pg.FrontendWrapper.AuthenticationMD5Password, + AuthenticationGSS: pg.FrontendWrapper.AuthenticationGSS, + AuthenticationGSSContinue: pg.FrontendWrapper.AuthenticationGSSContinue, + AuthenticationSASL: pg.FrontendWrapper.AuthenticationSASL, + AuthenticationSASLContinue: pg.FrontendWrapper.AuthenticationSASLContinue, + AuthenticationSASLFinal: pg.FrontendWrapper.AuthenticationSASLFinal, + BackendKeyData: pg.FrontendWrapper.BackendKeyData, + BindComplete: pg.FrontendWrapper.BindComplete, + CloseComplete: pg.FrontendWrapper.CloseComplete, + CommandComplete: pg.FrontendWrapper.CommandComplete, + CommandCompletes: pg.FrontendWrapper.CommandCompletes, + CopyData: pg.FrontendWrapper.CopyData, + CopyDone: pg.FrontendWrapper.CopyDone, + CopyInResponse: pg.FrontendWrapper.CopyInResponse, + CopyOutResponse: pg.FrontendWrapper.CopyOutResponse, + DataRow: pg.FrontendWrapper.DataRow, + DataRows: pg.FrontendWrapper.DataRows, + EmptyQueryResponse: pg.FrontendWrapper.EmptyQueryResponse, + ErrorResponse: pg.FrontendWrapper.ErrorResponse, + FunctionCallResponse: pg.FrontendWrapper.FunctionCallResponse, + NoData: pg.FrontendWrapper.NoData, + NoticeResponse: pg.FrontendWrapper.NoticeResponse, + NotificationResponse: pg.FrontendWrapper.NotificationResponse, + ParameterDescription: pg.FrontendWrapper.ParameterDescription, + ParameterStatusCombined: pg.FrontendWrapper.ParameterStatusCombined, + ParseComplete: pg.FrontendWrapper.ParseComplete, + PortalSuspended: pg.FrontendWrapper.PortalSuspended, + ReadyForQuery: pg.FrontendWrapper.ReadyForQuery, + RowDescription: pg.FrontendWrapper.RowDescription, + MsgType: pg.FrontendWrapper.MsgType, + AuthType: pg.FrontendWrapper.AuthType, + } + + afterEncoded, err := postgresDecoderFrontend(*pgMock) + if err != nil { + logger.Debug("failed to decode the response message in proxy for postgres dependency", zap.Error(err)) + } + if len(afterEncoded) != len(buffer) && len(pgMock.PacketTypes) > 0 && pgMock.PacketTypes[0] != "R" { + logger.Debug("the length of the encoded buffer is not equal to the length of the original buffer", zap.Any("after_encoded", len(afterEncoded)), zap.Any("buffer", len(buffer))) + pgMock.Payload = bufStr + } + pgResponses = append(pgResponses, *pgMock) + } + + if bufStr == "Tg==" || len(buffer) <= 5 { + + pgMock := &models.Frontend{ + Payload: bufStr, + } + pgResponses = append(pgResponses, *pgMock) + } + } + + resTimestampMock = time.Now() + + logger.Debug("the iteration for the postgres response ends with no of postgresReqs:" + strconv.Itoa(len(pgRequests)) + " and pgResps: " + strconv.Itoa(len(pgResponses))) + prevChunkWasReq = false + case err := <-errChan: + if err == io.EOF { + return nil + } + return err + } + } +} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/match.go b/keploy/pkg/core/proxy/integrations/postgres/v1/match.go new file mode 100644 index 0000000..ae86297 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/postgres/v1/match.go @@ -0,0 +1,867 @@ +//go:build linux + +package v1 + +import ( + "context" + "encoding/base64" + "fmt" + "math" + "reflect" + "regexp" + "strings" + "sync" + + "github.com/agnivade/levenshtein" + "github.com/jackc/pgproto3/v2" + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +var testmap TestPrepMap + +func getTestPS(reqBuff [][]byte, logger *zap.Logger, ConnectionID string) { + // maintain a map of current prepared statements and their corresponding connection id + // if it's the prepared statement match the query with the recorded prepared statement and return the response of that matched prepared statement at that connection + // so if parse is coming save to a same map + actualPgReq := decodePgRequest(reqBuff[0], logger) + if actualPgReq == nil { + return + } + testmap2 := make(TestPrepMap) + if testmap != nil { + testmap2 = testmap + } + querydata := make([]QueryData, 0) + if len(actualPgReq.PacketTypes) > 0 && actualPgReq.PacketTypes[0] != "p" && actualPgReq.Identfier != "StartupRequest" { + p := 0 + for _, header := range actualPgReq.PacketTypes { + if header == "P" { + if (strings.Contains(actualPgReq.Parses[p].Name, "S_") || strings.Contains(actualPgReq.Parses[p].Name, "s")) && !IsValuePresent(ConnectionID, actualPgReq.Parses[p].Name) { + querydata = append(querydata, QueryData{PrepIdentifier: actualPgReq.Parses[p].Name, Query: actualPgReq.Parses[p].Query}) + } + p++ + } + } + } + + // also append the query data for the prepared statement + if len(querydata) > 0 { + testmap2[ConnectionID] = append(testmap2[ConnectionID], querydata...) + // logger.Debug("Test Prepared statement Map", testmap2) + testmap = testmap2 + } + +} + +func IsValuePresent(connectionid string, value string) bool { + if testmap != nil { + for _, v := range testmap[connectionid] { + if v.PrepIdentifier == value { + return true + } + } + } + return false +} + +func matchingReadablePG(ctx context.Context, logger *zap.Logger, mutex *sync.Mutex, requestBuffers [][]byte, mockDb integrations.MockMemDb) (bool, []models.Frontend, error) { +OuterLoop: + for { + select { + case <-ctx.Done(): + return false, nil, ctx.Err() + default: + + mocks, err := mockDb.GetUnFilteredMocks() + var tcsMocks []*models.Mock + + for _, mock := range mocks { + if mock.Kind != "Postgres" { + continue + } + tcsMocks = append(tcsMocks, mock) + } + if err != nil { + return false, nil, fmt.Errorf("error while getting tcs mocks %v", err) + } + + ConnectionID := ctx.Value(models.ClientConnectionIDKey).(string) + + recordedPrep := getRecordPrepStatement(tcsMocks) + reqGoingOn := decodePgRequest(requestBuffers[0], logger) + if reqGoingOn != nil { + logger.Debug("PacketTypes", zap.Any("PacketTypes", reqGoingOn.PacketTypes)) + // fmt.Println("REQUEST GOING ON - ", reqGoingOn) + logger.Debug("ConnectionId-", zap.String("ConnectionId", ConnectionID)) + logger.Debug("TestMap*****", zap.Any("TestMap", testmap)) + } + + // merge all the streaming requests into 1 for matching + newRq := mergePgRequests(requestBuffers, logger) + if len(newRq) > 0 { + requestBuffers = newRq + } + + var sortFlag = true + var sortedTcsMocks []*models.Mock + var matchedMock *models.Mock + + for _, mock := range tcsMocks { + if ctx.Err() != nil { + return false, nil, ctx.Err() + } + if mock == nil { + continue + } + + mutex.Lock() + if sortFlag { + if !mock.TestModeInfo.IsFiltered { + sortFlag = false + } else { + sortedTcsMocks = append(sortedTcsMocks, mock) + } + } + mutex.Unlock() + + initMock := *mock + if len(initMock.Spec.PostgresRequests) == len(requestBuffers) { + for requestIndex, reqBuff := range requestBuffers { + bufStr := base64.StdEncoding.EncodeToString(reqBuff) + encodedMock, err := postgresDecoderBackend(initMock.Spec.PostgresRequests[requestIndex]) + if err != nil { + logger.Debug("Error while decoding postgres request", zap.Error(err)) + } + + switch { + case bufStr == "AAAACATSFi8=": + ssl := models.Frontend{ + Payload: "Tg==", + } + return true, []models.Frontend{ssl}, nil + case initMock.Spec.PostgresRequests[requestIndex].Identfier == "StartupRequest" && isStartupPacket(reqBuff) && initMock.Spec.PostgresRequests[requestIndex].Payload != "AAAACATSFi8=" && initMock.Spec.PostgresResponses[requestIndex].AuthType == 10: + logger.Debug("CHANGING TO MD5 for Response", zap.String("mock", initMock.Name), zap.String("Req", bufStr)) + res := make([]models.Frontend, len(initMock.Spec.PostgresResponses)) + copy(res, initMock.Spec.PostgresResponses) + res[requestIndex].AuthType = 5 + newInitMock := initMock + newInitMock.TestModeInfo.IsFiltered = false + newInitMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() + isUpdated := mockDb.UpdateUnFilteredMock(&initMock, &newInitMock) + if !isUpdated { + logger.Debug("failed to update matched mock", zap.Error(err)) + continue OuterLoop + } + return true, res, nil + case len(encodedMock) > 0 && encodedMock[0] == 'p' && initMock.Spec.PostgresRequests[requestIndex].PacketTypes[0] == "p" && reqBuff[0] == 'p': + logger.Debug("CHANGING TO MD5 for Request and Response", zap.String("mock", initMock.Name), zap.String("Req", bufStr)) + + res := make([]models.Frontend, len(initMock.Spec.PostgresResponses)) + copy(res, initMock.Spec.PostgresResponses) + res[requestIndex].PacketTypes = []string{"R", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "K", "Z"} + res[requestIndex].AuthType = 0 + res[requestIndex].BackendKeyData = pgproto3.BackendKeyData{ + ProcessID: 2613, + SecretKey: 824670820, + } + res[requestIndex].ReadyForQuery.TxStatus = 73 + res[requestIndex].ParameterStatusCombined = []pgproto3.ParameterStatus{ + { + Name: "application_name", + Value: "", + }, + { + Name: "client_encoding", + Value: "UTF8", + }, + { + Name: "DateStyle", + Value: "ISO, MDY", + }, + { + Name: "integer_datetimes", + Value: "on", + }, + { + Name: "IntervalStyle", + Value: "postgres", + }, + { + Name: "is_superuser", + Value: "UTF8", + }, + { + Name: "server_version", + Value: "13.12 (Debian 13.12-1.pgdg120+1)", + }, + { + Name: "session_authorization", + Value: "keploy-user", + }, + { + Name: "standard_conforming_strings", + Value: "on", + }, + { + Name: "TimeZone", + Value: "Etc/UTC", + }, + { + Name: "TimeZone", + Value: "Etc/UTC", + }, + } + newInitMock := initMock + newInitMock.TestModeInfo.IsFiltered = false + newInitMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() + isUpdated := mockDb.UpdateUnFilteredMock(&initMock, &newInitMock) + if !isUpdated { + logger.Debug("failed to update matched mock", zap.Error(err)) + continue OuterLoop + } + return true, res, nil + } + + } + } + + // maintain test prepare statement map for each connection id + getTestPS(requestBuffers, logger, ConnectionID) + } + + logger.Debug("Sorted Mocks inside pg parser: ", zap.Any("Len of sortedTcsMocks", len(sortedTcsMocks))) + + var matched, sorted bool + var idx int + //use findBinaryMatch twice one for sorted and another for unsorted + // give more priority to sorted like if you find more than 0.5 in sorted then return that + if len(sortedTcsMocks) > 0 { + sorted = true + idx1, newMock := findPGStreamMatch(sortedTcsMocks, requestBuffers, logger, sorted, ConnectionID, recordedPrep) + if idx1 != -1 { + matched = true + matchedMock = tcsMocks[idx1] + if newMock != nil { + matchedMock = newMock + } + logger.Debug("Matched In Sorted PG Matching Stream", zap.String("mock", matchedMock.Name)) + } + + if !matched { + idx = findBinaryStreamMatch(logger, sortedTcsMocks, requestBuffers, sorted) + if idx != -1 { + matched = true + matchedMock = tcsMocks[idx] + } + } + } + + if !matched { + sorted = false + idx1, newMock := findPGStreamMatch(tcsMocks, requestBuffers, logger, sorted, ConnectionID, recordedPrep) + if idx1 != -1 { + matched = true + matchedMock = tcsMocks[idx1] + if newMock != nil { + matchedMock = newMock + } + logger.Debug("Matched In Unsorted PG Matching Stream", zap.String("mock", matchedMock.Name)) + } + if !matched { + idx = findBinaryStreamMatch(logger, tcsMocks, requestBuffers, sorted) + // check if the validate the query with the matched mock + // if the query is same then return the response of that mock + var isValid = true + if idx != -1 && len(sortedTcsMocks) != 0 { + isValid, newMock = validateMock(tcsMocks, idx, requestBuffers, logger) + logger.Debug("Is Valid", zap.Bool("Is Valid", isValid)) + } + if idx != -1 { + matched = true + matchedMock = tcsMocks[idx] + if newMock != nil && !isValid { + matchedMock = newMock + } + logger.Debug("Matched In Binary Matching for Unsorted", zap.String("mock", matchedMock.Name)) + } + + } + } + + if matched { + logger.Debug("Matched mock", zap.String("mock", matchedMock.Name)) + originalMatchedMock := *matchedMock + matchedMock.TestModeInfo.IsFiltered = false + matchedMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() + updated := mockDb.UpdateUnFilteredMock(&originalMatchedMock, matchedMock) + if !updated { + logger.Debug("failed to update matched mock", zap.Error(err)) + } + return true, matchedMock.Spec.PostgresResponses, nil + } + return false, nil, nil + } + } +} + +func findBinaryStreamMatch(logger *zap.Logger, tcsMocks []*models.Mock, requestBuffers [][]byte, sorted bool) int { + mxSim := -1.0 + mxIdx := -1 + + for idx, mock := range tcsMocks { + // merging the mocks as well before comparing + mock.Spec.PostgresRequests = mergeMocks(mock.Spec.PostgresRequests, logger) + + if len(mock.Spec.PostgresRequests) == len(requestBuffers) { + for requestIndex, reqBuf := range requestBuffers { + + expectedPgReq := mock.Spec.PostgresRequests[requestIndex] + encoded, err := postgresDecoderBackend(expectedPgReq) + if err != nil { + logger.Debug("Error while decoding postgres request", zap.Error(err)) + } + var encoded64 []byte + if expectedPgReq.Payload != "" { + encoded64, err = util.DecodeBase64(mock.Spec.PostgresRequests[requestIndex].Payload) + if err != nil { + logger.Debug("Error while decoding postgres request", zap.Error(err)) + return -1 + } + } + var similarity1, similarity2 float64 + if len(encoded) > 0 { + similarity1 = fuzzyCheck(encoded, reqBuf) + } + if len(encoded64) > 0 { + similarity2 = fuzzyCheck(encoded64, reqBuf) + } + + // calculate the jaccard similarity between the two buffers one with base64 encoding and another via that + //find the max similarity between the two + similarity := math.Max(similarity1, similarity2) + if mxSim < similarity { + mxSim = similarity + mxIdx = idx + continue + } + } + } + } + + if sorted { + if mxIdx != -1 && mxSim >= 0.78 { + logger.Debug("Matched with Sorted Stream", zap.Float64("similarity", mxSim)) + } else { + mxIdx = -1 + } + } else { + if mxIdx != -1 { + logger.Debug("Matched with Unsorted Stream", zap.Float64("similarity", mxSim)) + } + } + return mxIdx +} + +func fuzzyCheck(encoded, reqBuf []byte) float64 { + k := util.AdaptiveK(len(reqBuf), 3, 8, 5) + shingles1 := util.CreateShingles(encoded, k) + shingles2 := util.CreateShingles(reqBuf, k) + similarity := util.JaccardSimilarity(shingles1, shingles2) + return similarity +} + +func findPGStreamMatch(tcsMocks []*models.Mock, requestBuffers [][]byte, logger *zap.Logger, isSorted bool, connectionID string, recordedPrep PrepMap) (int, *models.Mock) { + + mxIdx := -1 + + match := false + // loop for the exact match of the request + for idx, mock := range tcsMocks { + // merging the mocks as well before comparing + mock.Spec.PostgresRequests = mergeMocks(mock.Spec.PostgresRequests, logger) + + if len(mock.Spec.PostgresRequests) == len(requestBuffers) { + for _, reqBuff := range requestBuffers { + actualPgReq := decodePgRequest(reqBuff, logger) + if actualPgReq == nil { + return -1, nil + } + // here handle cases of prepared statement very carefully + match, err := compareExactMatch(mock, actualPgReq, logger) + if err != nil { + logger.Error("Error while matching exact match", zap.Error(err)) + continue + } + if match { + return idx, nil + } + } + } + } + if !isSorted { + return mxIdx, nil + } + // loop for the ps match of the request + if !match { + for idx, mock := range tcsMocks { + // merging the mocks as well before comparing + mock.Spec.PostgresRequests = mergeMocks(mock.Spec.PostgresRequests, logger) + + if len(mock.Spec.PostgresRequests) == len(requestBuffers) { + for _, reqBuff := range requestBuffers { + actualPgReq := decodePgRequest(reqBuff, logger) + if actualPgReq == nil { + return -1, nil + } + // just matching the corresponding PS in this case there is no need to edit the mock + match, newBindPs, err := PreparedStatementMatch(mock, actualPgReq, logger, connectionID, recordedPrep) + if err != nil { + logger.Error("Error while matching prepared statements", zap.Error(err)) + } + + if match { + logger.Debug("New Bind Prepared Statement", zap.Any("New Bind Prepared Statement", newBindPs), zap.String("ConnectionId", connectionID), zap.String("Mock Name", mock.Name)) + return idx, nil + } + // just check the query + if reflect.DeepEqual(actualPgReq.PacketTypes, []string{"P", "B", "D", "E"}) && reflect.DeepEqual(mock.Spec.PostgresRequests[0].PacketTypes, []string{"P", "B", "D", "E"}) { + if mock.Spec.PostgresRequests[0].Parses[0].Query == actualPgReq.Parses[0].Query { + return idx, nil + } + } + } + } + } + } + + if !match { + + for idx, mock := range tcsMocks { + // merging the mocks as well before comparing + mock.Spec.PostgresRequests = mergeMocks(mock.Spec.PostgresRequests, logger) + + if len(mock.Spec.PostgresRequests) == len(requestBuffers) { + for _, reqBuff := range requestBuffers { + actualPgReq := decodePgRequest(reqBuff, logger) + if actualPgReq == nil { + return -1, nil + } + + // have to ignore first parse message of begin read only + // should compare only query in the parse message + if len(actualPgReq.PacketTypes) != len(mock.Spec.PostgresRequests[0].PacketTypes) { + //check for begin read only + if len(actualPgReq.PacketTypes) > 0 && len(mock.Spec.PostgresRequests[0].PacketTypes) > 0 { + + ischanged, newMock := changeResToPS(mock, actualPgReq, logger, connectionID) + + if ischanged { + return idx, newMock + } + continue + + } + + } + } + } + } + } + + return mxIdx, nil +} + +// check what are the queries for the given ps of actualPgReq +// check if the execute query is present for that or not +// mark that mock true and return the response by changing the res format like +// postgres data types acc to result set format +func changeResToPS(mock *models.Mock, actualPgReq *models.Backend, logger *zap.Logger, connectionID string) (bool, *models.Mock) { + actualpackets := actualPgReq.PacketTypes + mockPackets := mock.Spec.PostgresRequests[0].PacketTypes + + // [P, B, E, P, B, D, E] => [B, E, B, E] + // write code that of packet is ["B", "E"] and mockPackets ["P", "B", "D", "E"] handle it in case1 + // and if packet is [B, E, B, E] and mockPackets [P, B, E, P, B, D, E] handle it in case2 + + ischanged := false + var newMock *models.Mock + // [B E P D B E] + // [P, B, E, P, B, D, E] -> [B, E, P, B, D, E] + if (reflect.DeepEqual(actualpackets, []string{"B", "E", "P", "D", "B", "E"}) || reflect.DeepEqual(actualpackets, []string{"B", "E", "P", "B", "D", "E"})) && reflect.DeepEqual(mockPackets, []string{"P", "B", "E", "P", "B", "D", "E"}) { + // logger.Debug("Handling Case 1 for mock", mock.Name) + // handleCase1(packets, mockPackets) + // also check if the second query is same or not + // logger.Debug("ActualPgReq", actualPgReq.Parses[0].Query, "MOCK REQ 1", mock.Spec.PostgresRequests[0].Parses[0].Query, "MOCK REQ 2", mock.Spec.PostgresRequests[0].Parses[1].Query) + if actualPgReq.Parses[0].Query != mock.Spec.PostgresRequests[0].Parses[1].Query { + return false, nil + } + newMock = sliceCommandTag(mock, logger, testmap[connectionID], actualPgReq, 1) + return true, newMock + } + + // case 2 + var ps string + if reflect.DeepEqual(actualpackets, []string{"B", "E"}) && reflect.DeepEqual(mockPackets, []string{"P", "B", "D", "E"}) { + // logger.Debug("Handling Case 2 for mock", mock.Name) + ps = actualPgReq.Binds[0].PreparedStatement + for _, v := range testmap[connectionID] { + if v.Query == mock.Spec.PostgresRequests[0].Parses[0].Query && v.PrepIdentifier == ps { + ischanged = true + break + } + } + } + + if ischanged { + // if strings.Contains(ps, "S_") { + // logger.Debug("Inside Prepared Statement") + newMock = sliceCommandTag(mock, logger, testmap[connectionID], actualPgReq, 2) + // } + return true, newMock + } + + // packets = []string{"B", "E", "B", "E"} + // mockPackets = []string{"P", "B", "E", "P", "B", "D", "E"} + + // Case 3 + if reflect.DeepEqual(actualpackets, []string{"B", "E", "B", "E"}) && reflect.DeepEqual(mockPackets, []string{"P", "B", "E", "P", "B", "D", "E"}) { + // logger.Debug("Handling Case 3 for mock", mock.Name) + ischanged1 := false + ps1 := actualPgReq.Binds[0].PreparedStatement + for _, v := range testmap[connectionID] { + if v.Query == mock.Spec.PostgresRequests[0].Parses[0].Query && v.PrepIdentifier == ps1 { + ischanged1 = true + break + } + } + //Matched In Binary Matching for Unsorted mock-222 + ischanged2 := false + ps2 := actualPgReq.Binds[1].PreparedStatement + for _, v := range testmap[connectionID] { + if v.Query == mock.Spec.PostgresRequests[0].Parses[1].Query && v.PrepIdentifier == ps2 { + ischanged2 = true + break + } + } + if ischanged1 && ischanged2 { + newMock = sliceCommandTag(mock, logger, testmap[connectionID], actualPgReq, 2) + return true, newMock + } + } + + // Case 4 + if reflect.DeepEqual(actualpackets, []string{"B", "E", "B", "E"}) && reflect.DeepEqual(mockPackets, []string{"B", "E", "P", "B", "D", "E"}) { + // logger.Debug("Handling Case 4 for mock", mock.Name) + // get the query for the prepared statement of test mode + ischanged := false + ps := actualPgReq.Binds[1].PreparedStatement + for _, v := range testmap[connectionID] { + if v.Query == mock.Spec.PostgresRequests[0].Parses[0].Query && v.PrepIdentifier == ps { + ischanged = true + break + } + } + if ischanged { + newMock = sliceCommandTag(mock, logger, testmap[connectionID], actualPgReq, 2) + return true, newMock + } + + } + + return false, nil + +} + +func PreparedStatementMatch(mock *models.Mock, actualPgReq *models.Backend, logger *zap.Logger, ConnectionID string, recordedPrep PrepMap) (bool, []string, error) { + // logger.Debug("Inside PreparedStatementMatch") + + if !reflect.DeepEqual(mock.Spec.PostgresRequests[0].PacketTypes, actualPgReq.PacketTypes) { + logger.Debug("mock and actual packet types are unequal", zap.Any("mock name", mock.Name)) + return false, nil, nil + } + + // get all the binds from the actualPgReq + binds := actualPgReq.Binds + newBinPreparedStatement := make([]string, 0) + mockBinds := mock.Spec.PostgresRequests[0].Binds + // If the client sent a different number of Bind messages than the mock + // recorded, the two batches can’t possibly align, so we can return early + // instead of walking the loop and risking panic due to out‑of‑bounds. + if len(binds) != len(mockBinds) { + logger.Debug("len of binds in actual request is not equal to len of binds in mock", zap.String("mock name", mock.Name)) + return false, nil, nil + } + mockConn := mock.ConnectionID + var foo = false + for idx, bind := range binds { + currentPs := bind.PreparedStatement + currentQuerydata := testmap[ConnectionID] + currentQuery := "" + // check in the map that what's the current query for this preparedstatement + // then will check what is the recorded prepared statement for this query + for _, v := range currentQuerydata { + if v.PrepIdentifier == currentPs { + // logger.Debug("Current query for this identifier is ", v.Query) + currentQuery = v.Query + break + } + } + + // this means that the bind is preceeded by a parse with name field empty + // we can say that the name field (identifier) was empty that's why it didn't get inserted in testMap. + // skip it, as it doesn't use already cached query, instead parsing followed by binding is done in the same query. + if currentQuery == "" { + continue + } + + logger.Debug("Current Query for this prepared statement", zap.String("Query", currentQuery), zap.String("Identifier", currentPs)) + foo = false + + // check if the query for mock ps (v.PreparedStatement) is same as the current query + for _, querydata := range recordedPrep[mockConn] { + if querydata.Query == currentQuery && mockBinds[idx].PreparedStatement == querydata.PrepIdentifier { + logger.Debug("Matched with the recorded prepared statement with Identifier and connectionID is", zap.String("Identifier", querydata.PrepIdentifier), zap.String("ConnectionId", mockConn), zap.String("Current Identifier", currentPs), zap.String("Query", currentQuery)) + foo = true + break + } + + } + // this means we are unable to find the query in recordedPrep or the prepared statement is not same + if !foo { + break + } + } + if !foo { + return false, nil, nil + } + + parses := actualPgReq.Parses + mockParses := mock.Spec.PostgresRequests[0].Parses + // If the client sent a different number of Parse messages than the mock + // recorded, the two batches can’t possibly align, so we can return early + // instead of walking the loop and risking panic due to out‑of‑bounds. + if len(parses) != len(mockParses) { + logger.Debug("len of parse in actual request is not equal to len of parse in mock", zap.String("mock name", mock.Name)) + return false, nil, nil + } + + foo = true + // check if all parse queries in pg request is same for the corresponding query in mock + for idx, parse := range parses { + if parse.Query != mockParses[idx].Query { + logger.Debug(fmt.Sprintf("parse query for actual request is not equal to parse query for mock name: %s, at index: %d", mock.Name, idx)) + foo = false // if any parse query is not same then break, mock didn't match + break + } + } + if foo { + return true, newBinPreparedStatement, nil + } + + return false, nil, nil +} + +func compareExactMatch(mock *models.Mock, actualPgReq *models.Backend, logger *zap.Logger) (bool, error) { + logger.Debug("Inside CompareExactMatch") + // have to ignore first parse message of begin read only + // should compare only query in the parse message + if len(actualPgReq.PacketTypes) != len(mock.Spec.PostgresRequests[0].PacketTypes) { + return false, nil + } + + // call a separate function for matching prepared statements + for idx, v := range actualPgReq.PacketTypes { + if v != mock.Spec.PostgresRequests[0].PacketTypes[idx] { + return false, nil + } + } + // IsPreparedStatement(mock, actualPgReq, logger, ConnectionId) + + // this will give me the + var ( + p, b, e = 0, 0, 0 + ) + for i := 0; i < len(actualPgReq.PacketTypes); i++ { + switch actualPgReq.PacketTypes[i] { + case "P": + // logger.Debug("Inside P") + p++ + if actualPgReq.Parses[p-1].Query != mock.Spec.PostgresRequests[0].Parses[p-1].Query { + return false, nil + } + + if actualPgReq.Parses[p-1].Name != mock.Spec.PostgresRequests[0].Parses[p-1].Name { + return false, nil + } + + if len(actualPgReq.Parses[p-1].ParameterOIDs) != len(mock.Spec.PostgresRequests[0].Parses[p-1].ParameterOIDs) { + return false, nil + } + for j := 0; j < len(actualPgReq.Parses[p-1].ParameterOIDs); j++ { + if actualPgReq.Parses[p-1].ParameterOIDs[j] != mock.Spec.PostgresRequests[0].Parses[p-1].ParameterOIDs[j] { + return false, nil + } + } + + case "B": + // logger.Debug("Inside B") + b++ + if actualPgReq.Binds[b-1].DestinationPortal != mock.Spec.PostgresRequests[0].Binds[b-1].DestinationPortal { + return false, nil + } + + if actualPgReq.Binds[b-1].PreparedStatement != mock.Spec.PostgresRequests[0].Binds[b-1].PreparedStatement { + return false, nil + } + + if len(actualPgReq.Binds[b-1].ParameterFormatCodes) != len(mock.Spec.PostgresRequests[0].Binds[b-1].ParameterFormatCodes) { + return false, nil + } + for j := 0; j < len(actualPgReq.Binds[b-1].ParameterFormatCodes); j++ { + if actualPgReq.Binds[b-1].ParameterFormatCodes[j] != mock.Spec.PostgresRequests[0].Binds[b-1].ParameterFormatCodes[j] { + return false, nil + } + } + if len(actualPgReq.Binds[b-1].Parameters) != len(mock.Spec.PostgresRequests[0].Binds[b-1].Parameters) { + return false, nil + } + for j := 0; j < len(actualPgReq.Binds[b-1].Parameters); j++ { + // parameter represents a timestamp value do not compare it just continue + if isTimestamp(actualPgReq.Binds[b-1].Parameters[j]) { + logger.Debug("found a timestamp value") + continue + } + if isBcryptHash(actualPgReq.Binds[b-1].Parameters[j]) { + logger.Debug("found a bcrypt hash") + continue + } + for i, v := range actualPgReq.Binds[b-1].Parameters[j] { + if v != mock.Spec.PostgresRequests[0].Binds[b-1].Parameters[j][i] { + return false, nil + } + } + } + if len(actualPgReq.Binds[b-1].ResultFormatCodes) != len(mock.Spec.PostgresRequests[0].Binds[b-1].ResultFormatCodes) { + return false, nil + } + for j := 0; j < len(actualPgReq.Binds[b-1].ResultFormatCodes); j++ { + if actualPgReq.Binds[b-1].ResultFormatCodes[j] != mock.Spec.PostgresRequests[0].Binds[b-1].ResultFormatCodes[j] { + return false, nil + } + } + + case "E": + // logger.Debug("Inside E") + e++ + if actualPgReq.Executes[e-1].Portal != mock.Spec.PostgresRequests[0].Executes[e-1].Portal { + return false, nil + } + if actualPgReq.Executes[e-1].MaxRows != mock.Spec.PostgresRequests[0].Executes[e-1].MaxRows { + return false, nil + } + + case "c": + if actualPgReq.CopyDone != mock.Spec.PostgresRequests[0].CopyDone { + return false, nil + } + case "H": + if actualPgReq.CopyFail.Message != mock.Spec.PostgresRequests[0].CopyFail.Message { + return false, nil + } + case "Q": + if actualPgReq.Query.String != mock.Spec.PostgresRequests[0].Query.String { + if LaevensteinDistance(actualPgReq.Query.String, mock.Spec.PostgresRequests[0].Query.String) { + logger.Debug("The strings are more than 90%% similar.") + } + + return false, nil + } + default: + return false, nil + } + } + return true, nil +} + +func LaevensteinDistance(str1, str2 string) bool { + // Compute the Levenshtein distance + distance := levenshtein.ComputeDistance(str1, str2) + maxLength := max(len(str1), len(str2)) + similarity := (1 - float64(distance)/float64(maxLength)) * 100 + + // Check if similarity is greater than 90% + return similarity > 90 + +} + +// make this in such a way if it returns -1 then we will continue with the original mock +func validateMock(tcsMocks []*models.Mock, idx int, requestBuffers [][]byte, logger *zap.Logger) (bool, *models.Mock) { + + actualPgReq := decodePgRequest(requestBuffers[0], logger) + if actualPgReq == nil { + return true, nil + } + mock := tcsMocks[idx].Spec.PostgresRequests[0] + if len(mock.PacketTypes) == len(actualPgReq.PacketTypes) { + if reflect.DeepEqual(tcsMocks[idx].Spec.PostgresRequests[0].PacketTypes, []string{"B", "E", "P", "B", "D", "E"}) { + if mock.Parses[0].Query == actualPgReq.Parses[0].Query { + return true, nil + } + } + if reflect.DeepEqual(mock.PacketTypes, []string{"B", "E", "B", "E"}) { + // logger.Debug("Inside Validate Mock for B, E, B, E") + return true, nil + } + if reflect.DeepEqual(mock.PacketTypes, []string{"B", "E"}) { + // logger.Debug("Inside Validate Mock for B, E") + copyMock := *tcsMocks[idx] + copyMock.Spec.PostgresResponses[0].PacketTypes = []string{"2", "C", "Z"} + copyMock.Spec.PostgresResponses[0].Payload = "" + return false, ©Mock + } + if reflect.DeepEqual(mock.PacketTypes, []string{"P", "B", "D", "E"}) { + // logger.Debug("Inside Validate Mock for P, B, D, E") + copyMock := *tcsMocks[idx] + copyMock.Spec.PostgresResponses[0].PacketTypes = []string{"1", "2", "T", "C", "Z"} + copyMock.Spec.PostgresResponses[0].Payload = "" + return false, ©Mock + } + } else { + // [B, E, P, B, D, E] => [ P, B, D, E] + if reflect.DeepEqual(mock.PacketTypes, []string{"B", "E", "P", "B", "D", "E"}) && reflect.DeepEqual(actualPgReq.PacketTypes, []string{"P", "B", "D", "E"}) { + // logger.Debug("Inside Validate Mock for B, E, B, E") + if mock.Parses[0].Query == actualPgReq.Parses[0].Query { + // no need to do anything + + copyMock := *tcsMocks[idx] + copyMock.Spec.PostgresResponses[0].PacketTypes = []string{"1", "2", "T", "C", "Z"} + copyMock.Spec.PostgresResponses[0].Payload = "" + copyMock.Spec.PostgresResponses[0].CommandCompletes = copyMock.Spec.PostgresResponses[0].CommandCompletes[1:] + return false, ©Mock + } + } + } + return true, nil +} + +func isTimestamp(byteArray []byte) bool { + // Convert byte array to string + s := string(byteArray) + + // Define a regex for ISO 8601 timestamps + timestampRegex := regexp.MustCompile(`\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(.\d+)?(Z)?`) + return timestampRegex.MatchString(s) +} + +func isBcryptHash(byteArray []byte) bool { + // Convert byte array to string + s := string(byteArray) + + // Define a regex for bcrypt hashes + bcryptRegex := regexp.MustCompile(`^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$`) + return bcryptRegex.MatchString(s) +} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/postgres.go b/keploy/pkg/core/proxy/integrations/postgres/v1/postgres.go new file mode 100755 index 0000000..20a2a54 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/postgres/v1/postgres.go @@ -0,0 +1,87 @@ +//go:build linux + +package v1 + +import ( + "context" + "encoding/binary" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/utils" + + "go.keploy.io/server/v2/pkg/models" + + "go.uber.org/zap" +) + +func init() { + integrations.Register(integrations.POSTGRES_V1, &integrations.Parsers{ + Initializer: New, + Priority: 100, + }) +} + +type PostgresV1 struct { + logger *zap.Logger +} + +func New(logger *zap.Logger) integrations.Integrations { + return &PostgresV1{ + logger: logger, + } +} + +// MatchType determines if the outgoing network call is Postgres by comparing the +// message format with that of a Postgres text message. +func (p *PostgresV1) MatchType(_ context.Context, reqBuf []byte) bool { + const ProtocolVersion = 0x00030000 // Protocol version 3.0 + + if len(reqBuf) < 8 { + // Not enough data for a complete header + return false + } + + // The first four bytes are the message length, but we don't need to check those + // The next four bytes are the protocol version + version := binary.BigEndian.Uint32(reqBuf[4:8]) + if version == 80877103 { + return true + } + return version == ProtocolVersion +} + +func (p *PostgresV1) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + logger := p.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial postgres message") + return err + } + err = encodePostgres(ctx, logger, reqBuf, src, dst, mocks, opts) + if err != nil { + // TODO: why debug log? + logger.Debug("failed to encode the postgres message into the yaml") + return err + } + return nil + +} + +func (p *PostgresV1) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { + logger := p.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial postgres message") + return err + } + + err = decodePostgres(ctx, logger, reqBuf, src, dstCfg, mockDb, opts) + if err != nil { + logger.Debug("failed to decode the postgres message from the yaml") + return err + } + return nil +} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/transcoder.go b/keploy/pkg/core/proxy/integrations/postgres/v1/transcoder.go new file mode 100644 index 0000000..9e1ce08 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/postgres/v1/transcoder.go @@ -0,0 +1,313 @@ +package v1 + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + + "github.com/jackc/pgproto3/v2" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type BackendWrapper struct { + BackendWrapper models.Backend +} + +type FrontendWrapper struct { + FrontendWrapper models.Frontend +} + +func NewBackend() *BackendWrapper { + return &BackendWrapper{} +} + +func NewFrontend() *FrontendWrapper { + return &FrontendWrapper{} +} + +// PG Response Packet Transcoder +func (b *BackendWrapper) translateToReadableBackend(msgBody []byte) (pgproto3.FrontendMessage, error) { + // fmt.Println("msgType", b.BackendWrapper.MsgType) + var msg pgproto3.FrontendMessage + switch b.BackendWrapper.MsgType { + case 'B': + msg = &b.BackendWrapper.Bind + case 'C': + msg = &b.BackendWrapper.Close + case 'D': + msg = &b.BackendWrapper.Describe + case 'E': + msg = &b.BackendWrapper.Execute + case 'F': + msg = &b.BackendWrapper.FunctionCall + case 'f': + msg = &b.BackendWrapper.CopyFail + case 'd': + msg = &b.BackendWrapper.CopyData + case 'c': + msg = &b.BackendWrapper.CopyDone + case 'H': + msg = &b.BackendWrapper.Flush + case 'P': + msg = &b.BackendWrapper.Parse + case 'p': + switch b.BackendWrapper.AuthType { + case pgproto3.AuthTypeSASL: + msg = &pgproto3.SASLInitialResponse{} + case pgproto3.AuthTypeSASLContinue: + msg = &pgproto3.SASLResponse{} + case pgproto3.AuthTypeSASLFinal: + msg = &pgproto3.SASLResponse{} + case pgproto3.AuthTypeGSS, pgproto3.AuthTypeGSSCont: + msg = &pgproto3.GSSResponse{} + case pgproto3.AuthTypeCleartextPassword, pgproto3.AuthTypeMD5Password: + fallthrough + default: + // to maintain backwards compatability + msg = &pgproto3.PasswordMessage{} + } + case 'Q': + msg = &b.BackendWrapper.Query + case 'S': + msg = &b.BackendWrapper.Sync + case 'X': + msg = &b.BackendWrapper.Terminate + default: + return nil, fmt.Errorf("unknown message type: %c", b.BackendWrapper.MsgType) + } + err := msg.Decode(msgBody[5:]) + if b.BackendWrapper.MsgType == 'P' { + *msg.(*pgproto3.Parse) = b.BackendWrapper.Parse + } + + return msg, err +} + +func (f *FrontendWrapper) translateToReadableResponse(logger *zap.Logger, msgBody []byte) (pgproto3.BackendMessage, error) { + f.FrontendWrapper.BodyLen = int(binary.BigEndian.Uint32(msgBody[1:])) - 4 + f.FrontendWrapper.MsgType = msgBody[0] + var msg pgproto3.BackendMessage + switch f.FrontendWrapper.MsgType { + case '1': + msg = &f.FrontendWrapper.ParseComplete + case '2': + msg = &f.FrontendWrapper.BindComplete + case '3': + msg = &f.FrontendWrapper.CloseComplete + case 'A': + msg = &f.FrontendWrapper.NotificationResponse + case 'c': + msg = &f.FrontendWrapper.CopyDone + case 'C': + msg = &f.FrontendWrapper.CommandComplete + case 'd': + msg = &f.FrontendWrapper.CopyData + case 'D': + msg = &f.FrontendWrapper.DataRow + logger.Debug("Data Row", zap.String("data", string(msgBody))) + case 'E': + msg = &f.FrontendWrapper.ErrorResponse + case 'G': + msg = &f.FrontendWrapper.CopyInResponse + case 'H': + msg = &f.FrontendWrapper.CopyOutResponse + case 'I': + msg = &f.FrontendWrapper.EmptyQueryResponse + case 'K': + msg = &f.FrontendWrapper.BackendKeyData + case 'n': + msg = &f.FrontendWrapper.NoData + case 'N': + msg = &f.FrontendWrapper.NoticeResponse + case 'R': + var err error + msg, err = f.findAuthMsgType(msgBody) + if err != nil { + return nil, err + } + case 's': + msg = &f.FrontendWrapper.PortalSuspended + case 'S': + msg = &f.FrontendWrapper.ParameterStatus + case 't': + msg = &f.FrontendWrapper.ParameterDescription + case 'T': + msg = &f.FrontendWrapper.RowDescription + case 'V': + msg = &f.FrontendWrapper.FunctionCallResponse + case 'W': + msg = &f.FrontendWrapper.CopyBothResponse + case 'Z': + msg = &f.FrontendWrapper.ReadyForQuery + default: + return nil, fmt.Errorf("unknown message type: %c", f.FrontendWrapper.MsgType) + } + + logger.Debug("msgFrontend", zap.String("msgFrontend", string(msgBody))) + + err := msg.Decode(msgBody[5:]) + if err != nil { + utils.LogError(logger, err, "Error from decoding request message") + } + + bits := msg.Encode([]byte{}) + // println("Length of bits", len(bits), "Length of msgBody", len(msgBody)) + if len(bits) != len(msgBody) { + logger.Debug("Encoded Data doesn't match the original data ..") + } + + return msg, err +} + +const ( + minStartupPacketLen = 4 // minStartupPacketLen is a single 32-bit int version or code. + maxStartupPacketLen = 10000 // maxStartupPacketLen is MAX_STARTUP_PACKET_LENGTH from PG source. + sslRequestNumber = 80877103 + cancelRequestCode = 80877102 + gssEncReqNumber = 80877104 +) + +// ProtocolVersionNumber Replace with actual version number if different +const ProtocolVersionNumber uint32 = 196608 + +func (b *BackendWrapper) decodeStartupMessage(buf []byte) (pgproto3.FrontendMessage, error) { + + reader := pgproto3.NewByteReader(buf) + buf, err := reader.Next(4) + + if err != nil { + return nil, err + } + msgSize := int(binary.BigEndian.Uint32(buf) - 4) + + if msgSize < minStartupPacketLen || msgSize > maxStartupPacketLen { + return nil, fmt.Errorf("invalid length of startup packet: %d", msgSize) + } + + buf, err = reader.Next(msgSize) + if err != nil { + return nil, fmt.Errorf("invalid length of startup packet: %d", msgSize) + } + + code := binary.BigEndian.Uint32(buf) + + switch code { + case ProtocolVersionNumber: + err := b.BackendWrapper.StartupMessage.Decode(buf) + if err != nil { + return nil, err + } + return &b.BackendWrapper.StartupMessage, nil + case sslRequestNumber: + err := b.BackendWrapper.SSlRequest.Decode(buf) + if err != nil { + return nil, err + } + return &b.BackendWrapper.SSlRequest, nil + case cancelRequestCode: + err := b.BackendWrapper.CancelRequest.Decode(buf) + if err != nil { + return nil, err + } + return &b.BackendWrapper.CancelRequest, nil + case gssEncReqNumber: + err := b.BackendWrapper.GssEncRequest.Decode(buf) + if err != nil { + return nil, err + } + return &b.BackendWrapper.GssEncRequest, nil + default: + return nil, fmt.Errorf("unknown startup message code: %d", code) + } +} + +// constants for the authentication message types +const ( + AuthTypeOk = 0 + AuthTypeCleartextPassword = 3 + AuthTypeMD5Password = 5 + AuthTypeSCMCreds = 6 + AuthTypeGSS = 7 + AuthTypeGSSCont = 8 + AuthTypeSSPI = 9 + AuthTypeSASL = 10 + AuthTypeSASLContinue = 11 + AuthTypeSASLFinal = 12 +) + +func (f *FrontendWrapper) findAuthMsgType(src []byte) (pgproto3.BackendMessage, error) { + if len(src) < 4 { + return nil, errors.New("authentication message too short") + } + + authType, err := parseAuthType(src) + if err != nil { + return nil, err + + } + + f.FrontendWrapper.AuthType = authType + switch f.FrontendWrapper.AuthType { + case pgproto3.AuthTypeOk: + return &f.FrontendWrapper.AuthenticationOk, nil + case pgproto3.AuthTypeCleartextPassword: + return &f.FrontendWrapper.AuthenticationCleartextPassword, nil + case pgproto3.AuthTypeMD5Password: + return &f.FrontendWrapper.AuthenticationMD5Password, nil + case pgproto3.AuthTypeSCMCreds: + return nil, errors.New("AuthTypeSCMCreds is unimplemented") + case pgproto3.AuthTypeGSS: + return &f.FrontendWrapper.AuthenticationGSS, nil + case pgproto3.AuthTypeGSSCont: + return &f.FrontendWrapper.AuthenticationGSSContinue, nil + case pgproto3.AuthTypeSSPI: + return nil, errors.New("AuthTypeSSPI is unimplemented") + case pgproto3.AuthTypeSASL: + return &f.FrontendWrapper.AuthenticationSASL, nil + case pgproto3.AuthTypeSASLContinue: + return &f.FrontendWrapper.AuthenticationSASLContinue, nil + case pgproto3.AuthTypeSASLFinal: + return &f.FrontendWrapper.AuthenticationSASLFinal, nil + default: + return nil, fmt.Errorf("unknown authentication type: %d", f.FrontendWrapper.AuthType) + } + +} + +// GetAuthType returns the authType used in the current state of the frontend. +// See SetAuthType for more information. +func parseAuthType(buffer []byte) (int32, error) { + // Create a bytes reader from the buffer + reader := bytes.NewReader(buffer) + + // Skip the message type (1 byte) as you know it's 'R' + _, err := reader.Seek(1, 0) + if err != nil { + return 0, err + } + + // Read the length of the message (4 bytes) + var length int32 + err = binary.Read(reader, binary.BigEndian, &length) + if err != nil { + return 0, err + } + + // Read the auth type code (4 bytes) + var authType int32 + err = binary.Read(reader, binary.BigEndian, &authType) + if err != nil { + return 0, err + } + + return authType, nil +} + +func isStartupPacket(packet []byte) bool { + protocolVersion := binary.BigEndian.Uint32(packet[4:8]) + // printStartupPacketDetails(packet) + return protocolVersion == 196608 // 3.0 in PostgreSQL +} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/util.go b/keploy/pkg/core/proxy/integrations/postgres/v1/util.go new file mode 100755 index 0000000..09d812d --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/postgres/v1/util.go @@ -0,0 +1,500 @@ +//go:build linux + +package v1 + +import ( + "encoding/binary" + "errors" + "fmt" + "strconv" + "time" + + "github.com/jackc/pgproto3/v2" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +func postgresDecoderFrontend(response models.Frontend) ([]byte, error) { + var resbuffer []byte + // list of packets available in the buffer + packets := response.PacketTypes + var cc, dtr, ps = 0, 0, 0 + for _, packet := range packets { + var msg pgproto3.BackendMessage + + switch packet { + case string('1'): + msg = &pgproto3.ParseComplete{} + case string('2'): + msg = &pgproto3.BindComplete{} + case string('3'): + msg = &pgproto3.CloseComplete{} + case string('A'): + msg = &pgproto3.NotificationResponse{ + PID: response.NotificationResponse.PID, + Channel: response.NotificationResponse.Channel, + Payload: response.NotificationResponse.Payload, + } + case string('c'): + msg = &pgproto3.CopyDone{} + case string('C'): + if len(response.CommandCompletes) == 0 { + cc++ + continue + } + msg = &pgproto3.CommandComplete{ + CommandTag: response.CommandCompletes[cc].CommandTag, + CommandTagType: response.CommandCompletes[cc].CommandTagType, + } + cc++ + case string('d'): + msg = &pgproto3.CopyData{ + Data: response.CopyData.Data, + } + case string('D'): + msg = &pgproto3.DataRow{ + RowValues: response.DataRows[dtr].RowValues, + Values: response.DataRows[dtr].Values, + } + dtr++ + case string('E'): + msg = &pgproto3.ErrorResponse{ + Severity: response.ErrorResponse.Severity, + Code: response.ErrorResponse.Code, + Message: response.ErrorResponse.Message, + Detail: response.ErrorResponse.Detail, + Hint: response.ErrorResponse.Hint, + Position: response.ErrorResponse.Position, + InternalPosition: response.ErrorResponse.InternalPosition, + InternalQuery: response.ErrorResponse.InternalQuery, + Where: response.ErrorResponse.Where, + SchemaName: response.ErrorResponse.SchemaName, + TableName: response.ErrorResponse.TableName, + ColumnName: response.ErrorResponse.ColumnName, + DataTypeName: response.ErrorResponse.DataTypeName, + ConstraintName: response.ErrorResponse.ConstraintName, + File: response.ErrorResponse.File, + Line: response.ErrorResponse.Line, + Routine: response.ErrorResponse.Routine, + } + case string('G'): + msg = &pgproto3.CopyInResponse{ + OverallFormat: response.CopyInResponse.OverallFormat, + ColumnFormatCodes: response.CopyInResponse.ColumnFormatCodes, + } + case string('H'): + msg = &pgproto3.CopyOutResponse{ + OverallFormat: response.CopyOutResponse.OverallFormat, + ColumnFormatCodes: response.CopyOutResponse.ColumnFormatCodes, + } + case string('I'): + msg = &pgproto3.EmptyQueryResponse{} + case string('K'): + msg = &pgproto3.BackendKeyData{ + ProcessID: response.BackendKeyData.ProcessID, + SecretKey: response.BackendKeyData.SecretKey, + } + case string('n'): + msg = &pgproto3.NoData{} + case string('N'): + msg = &pgproto3.NoticeResponse{ + Severity: response.NoticeResponse.Severity, + Code: response.NoticeResponse.Code, + Message: response.NoticeResponse.Message, + Detail: response.NoticeResponse.Detail, + Hint: response.NoticeResponse.Hint, + Position: response.NoticeResponse.Position, + InternalPosition: response.NoticeResponse.InternalPosition, + InternalQuery: response.NoticeResponse.InternalQuery, + Where: response.NoticeResponse.Where, + SchemaName: response.NoticeResponse.SchemaName, + TableName: response.NoticeResponse.TableName, + ColumnName: response.NoticeResponse.ColumnName, + DataTypeName: response.NoticeResponse.DataTypeName, + ConstraintName: response.NoticeResponse.ConstraintName, + File: response.NoticeResponse.File, + Line: response.NoticeResponse.Line, + Routine: response.NoticeResponse.Routine, + } + + case string('R'): + switch response.AuthType { + case AuthTypeOk: + msg = &pgproto3.AuthenticationOk{} + case AuthTypeCleartextPassword: + msg = &pgproto3.AuthenticationCleartextPassword{} + case AuthTypeMD5Password: + msg = &pgproto3.AuthenticationMD5Password{} + case AuthTypeSCMCreds: + return nil, errors.New("AuthTypeSCMCreds is unimplemented") + case AuthTypeGSS: + return nil, errors.New("AuthTypeGSS is unimplemented") + case AuthTypeGSSCont: + msg = &pgproto3.AuthenticationGSSContinue{} + case AuthTypeSSPI: + return nil, errors.New("AuthTypeSSPI is unimplemented") + case AuthTypeSASL: + msg = &pgproto3.AuthenticationSASL{} + case AuthTypeSASLContinue: + msg = &pgproto3.AuthenticationSASLContinue{} + case AuthTypeSASLFinal: + msg = &pgproto3.AuthenticationSASLFinal{} + default: + return nil, fmt.Errorf("unknown authentication type: %d", response.AuthType) + } + + case string('s'): + msg = &pgproto3.PortalSuspended{} + case string('S'): + msg = &pgproto3.ParameterStatus{ + Name: response.ParameterStatusCombined[ps].Name, + Value: response.ParameterStatusCombined[ps].Value, + } + ps++ + + case string('t'): + msg = &pgproto3.ParameterDescription{ + ParameterOIDs: response.ParameterDescription.ParameterOIDs, + } + case string('T'): + msg = &pgproto3.RowDescription{ + Fields: response.RowDescription.Fields, + } + case string('V'): + msg = &pgproto3.FunctionCallResponse{ + Result: response.FunctionCallResponse.Result, + } + case string('W'): + msg = &pgproto3.CopyBothResponse{ + OverallFormat: response.CopyBothResponse.OverallFormat, + ColumnFormatCodes: response.CopyBothResponse.ColumnFormatCodes, + } + case string('Z'): + msg = &pgproto3.ReadyForQuery{ + TxStatus: response.ReadyForQuery.TxStatus, + } + default: + return nil, fmt.Errorf("unknown message type: %q", packet) + } + + encoded := msg.Encode([]byte{}) + resbuffer = append(resbuffer, encoded...) + } + return resbuffer, nil +} + +func postgresDecoderBackend(request models.Backend) ([]byte, error) { + // take each object , try to make it frontend or backend message so that it can call it's corresponding encode function + // and then append it to the buffer, for a particular mock .. + + var reqbuffer []byte + // list of packets available in the buffer + var b, e, p = 0, 0, 0 + packets := request.PacketTypes + for _, packet := range packets { + var msg pgproto3.FrontendMessage + switch packet { + case string('B'): + msg = &pgproto3.Bind{ + DestinationPortal: request.Binds[b].DestinationPortal, + PreparedStatement: request.Binds[b].PreparedStatement, + ParameterFormatCodes: request.Binds[b].ParameterFormatCodes, + Parameters: request.Binds[b].Parameters, + ResultFormatCodes: request.Binds[b].ResultFormatCodes, + } + b++ + case string('C'): + msg = &pgproto3.Close{ + Object_Type: request.Close.Object_Type, + Name: request.Close.Name, + } + case string('D'): + msg = &pgproto3.Describe{ + ObjectType: request.Describe.ObjectType, + Name: request.Describe.Name, + } + case string('E'): + msg = &pgproto3.Execute{ + Portal: request.Executes[e].Portal, + MaxRows: request.Executes[e].MaxRows, + } + e++ + case string('F'): + // *msg.(*pgproto3.Flush) = request.Flush + msg = &pgproto3.Flush{} + case string('f'): + // *msg.(*pgproto3.FunctionCall) = request.FunctionCall + msg = &pgproto3.FunctionCall{ + Function: request.FunctionCall.Function, + Arguments: request.FunctionCall.Arguments, + ArgFormatCodes: request.FunctionCall.ArgFormatCodes, + ResultFormatCode: request.FunctionCall.ResultFormatCode, + } + case string('d'): + msg = &pgproto3.CopyData{ + Data: request.CopyData.Data, + } + case string('c'): + msg = &pgproto3.CopyDone{} + case string('H'): + msg = &pgproto3.CopyFail{ + Message: request.CopyFail.Message, + } + case string('P'): + msg = &pgproto3.Parse{ + Name: request.Parses[p].Name, + Query: request.Parses[p].Query, + ParameterOIDs: request.Parses[p].ParameterOIDs, + } + p++ + case string('p'): + switch request.AuthType { + case pgproto3.AuthTypeSASL: + msg = &pgproto3.SASLInitialResponse{ + AuthMechanism: request.SASLInitialResponse.AuthMechanism, + Data: request.SASLInitialResponse.Data, + } + case pgproto3.AuthTypeSASLContinue: + msg = &pgproto3.SASLResponse{ + Data: request.SASLResponse.Data, + } + case pgproto3.AuthTypeSASLFinal: + msg = &pgproto3.SASLResponse{ + Data: request.SASLResponse.Data, + } + case pgproto3.AuthTypeGSS, pgproto3.AuthTypeGSSCont: + msg = &pgproto3.GSSResponse{ + Data: []byte{}, // TODO: implement + } + case pgproto3.AuthTypeCleartextPassword, pgproto3.AuthTypeMD5Password: + fallthrough + default: + // to maintain backwards compatability + msg = &pgproto3.PasswordMessage{Password: request.PasswordMessage.Password} + } + case string('Q'): + msg = &pgproto3.Query{ + String: request.Query.String, + } + case string('S'): + msg = &pgproto3.Sync{} + case string('X'): + // *msg.(*pgproto3.Terminate) = request.Terminate + msg = &pgproto3.Terminate{} + default: + return nil, fmt.Errorf("unknown message type: %q", packet) + } + if msg == nil { + return nil, errors.New("msg is nil") + } + encoded := msg.Encode([]byte{}) + + reqbuffer = append(reqbuffer, encoded...) + } + return reqbuffer, nil +} + +func checkIfps(array []string) bool { + n := len(array) + if n%2 != 0 { + // If the array length is odd, it cannot match the pattern + return false + } + + for i := 0; i < n; i += 2 { + // Check if consecutive elements are "B" and "E" + if array[i] != "B" || array[i+1] != "E" { + return false + } + } + + return true +} + +func sliceCommandTag(mock *models.Mock, logger *zap.Logger, prep []QueryData, actualPgReq *models.Backend, psCase int) *models.Mock { + + logger.Debug("Inside Slice Command Tag for ", zap.Int("psCase", psCase)) + logger.Debug("Prep Query Data", zap.Any("prep", prep)) + switch psCase { + case 1: + + copyMock := *mock + // fmt.Println("Inside Slice Command Tag for ", psCase) + mockPackets := copyMock.Spec.PostgresResponses[0].PacketTypes + for idx, v := range mockPackets { + if v == "1" { + mockPackets = append(mockPackets[:idx], mockPackets[idx+1:]...) + } + } + copyMock.Spec.PostgresResponses[0].Payload = "" + copyMock.Spec.PostgresResponses[0].PacketTypes = mockPackets + + return ©Mock + case 2: + // ["2", D, C, Z] + copyMock := *mock + // fmt.Println("Inside Slice Command Tag for ", psCase) + mockPackets := copyMock.Spec.PostgresResponses[0].PacketTypes + for idx, v := range mockPackets { + if v == "1" || v == "T" { + mockPackets = append(mockPackets[:idx], mockPackets[idx+1:]...) + } + } + copyMock.Spec.PostgresResponses[0].Payload = "" + copyMock.Spec.PostgresResponses[0].PacketTypes = mockPackets + rsFormat := actualPgReq.Bind.ResultFormatCodes + + for idx, datarow := range copyMock.Spec.PostgresResponses[0].DataRows { + for column, rowVal := range datarow.RowValues { + // fmt.Println("datarow.RowValues", len(datarow.RowValues)) + if rsFormat[column] == 1 { + // datarows := make([]byte, 4) + newRow, _ := getChandedDataRow(rowVal) + // logger.Info("New Row Value", zap.String("newRow", newRow)) + copyMock.Spec.PostgresResponses[0].DataRows[idx].RowValues[column] = newRow + } + } + } + return ©Mock + default: + } + return nil +} + +func getChandedDataRow(input string) (string, error) { + // Convert input1 (integer input as string) to integer + buffer := make([]byte, 4) + if uintValue, err := strconv.ParseUint(input, 10, 32); err == nil { + + binary.BigEndian.PutUint32(buffer, uint32(uintValue)) + return "b64:" + util.EncodeBase64(buffer), nil + } else if dateValue, err := time.Parse("2006-01-02", input); err == nil { + // Perform additional operations on the date + epoch := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) + difference := dateValue.Sub(epoch).Hours() / 24 + // fmt.Printf("Difference in days from epoch: %.2f days\n", difference) + binary.BigEndian.PutUint32(buffer, uint32(difference)) + return "b64:" + util.EncodeBase64(buffer), nil + } + return "b64:AAAAAA==", errors.New("invalid input") + +} + +func decodePgRequest(buffer []byte, logger *zap.Logger) *models.Backend { + + pg := NewBackend() + + if !isStartupPacket(buffer) && len(buffer) > 5 { + bufferCopy := buffer + for i := 0; i < len(bufferCopy)-5; { + pg.BackendWrapper.MsgType = buffer[i] + pg.BackendWrapper.BodyLen = int(binary.BigEndian.Uint32(buffer[i+1:])) - 4 + if len(buffer) < (i + pg.BackendWrapper.BodyLen + 5) { + logger.Debug("failed to translate the postgres request message due to shorter network packet buffer") + break + } + msg, err := pg.translateToReadableBackend(buffer[i:(i + pg.BackendWrapper.BodyLen + 5)]) + if err != nil && buffer[i] != 112 { + logger.Debug("failed to translate the request message to readable", zap.Error(err)) + } + if pg.BackendWrapper.MsgType == 'p' { + pg.BackendWrapper.PasswordMessage = *msg.(*pgproto3.PasswordMessage) + } + + if pg.BackendWrapper.MsgType == 'P' { + pg.BackendWrapper.Parse = *msg.(*pgproto3.Parse) + pg.BackendWrapper.Parses = append(pg.BackendWrapper.Parses, pg.BackendWrapper.Parse) + } + + if pg.BackendWrapper.MsgType == 'B' { + pg.BackendWrapper.Bind = *msg.(*pgproto3.Bind) + pg.BackendWrapper.Binds = append(pg.BackendWrapper.Binds, pg.BackendWrapper.Bind) + } + + if pg.BackendWrapper.MsgType == 'E' { + pg.BackendWrapper.Execute = *msg.(*pgproto3.Execute) + pg.BackendWrapper.Executes = append(pg.BackendWrapper.Executes, pg.BackendWrapper.Execute) + } + + pg.BackendWrapper.PacketTypes = append(pg.BackendWrapper.PacketTypes, string(pg.BackendWrapper.MsgType)) + + i += 5 + pg.BackendWrapper.BodyLen + } + + pgMock := &models.Backend{ + PacketTypes: pg.BackendWrapper.PacketTypes, + Identfier: "ClientRequest", + Length: uint32(len(buffer)), + // Payload: bufStr, + Bind: pg.BackendWrapper.Bind, + Binds: pg.BackendWrapper.Binds, + PasswordMessage: pg.BackendWrapper.PasswordMessage, + CancelRequest: pg.BackendWrapper.CancelRequest, + Close: pg.BackendWrapper.Close, + CopyData: pg.BackendWrapper.CopyData, + CopyDone: pg.BackendWrapper.CopyDone, + CopyFail: pg.BackendWrapper.CopyFail, + Describe: pg.BackendWrapper.Describe, + Execute: pg.BackendWrapper.Execute, + Executes: pg.BackendWrapper.Executes, + Flush: pg.BackendWrapper.Flush, + FunctionCall: pg.BackendWrapper.FunctionCall, + GssEncRequest: pg.BackendWrapper.GssEncRequest, + Parse: pg.BackendWrapper.Parse, + Parses: pg.BackendWrapper.Parses, + Query: pg.BackendWrapper.Query, + SSlRequest: pg.BackendWrapper.SSlRequest, + StartupMessage: pg.BackendWrapper.StartupMessage, + SASLInitialResponse: pg.BackendWrapper.SASLInitialResponse, + SASLResponse: pg.BackendWrapper.SASLResponse, + Sync: pg.BackendWrapper.Sync, + Terminate: pg.BackendWrapper.Terminate, + MsgType: pg.BackendWrapper.MsgType, + AuthType: pg.BackendWrapper.AuthType, + } + return pgMock + } + + return nil +} + +func mergePgRequests(requestBuffers [][]byte, logger *zap.Logger) [][]byte { + // Check for PBDE first + var mergeBuff []byte + for _, v := range requestBuffers { + backend := decodePgRequest(v, logger) + + if backend == nil { + logger.Debug("Rerurning nil while merging ") + break + } + buf, _ := postgresDecoderBackend(*backend) + mergeBuff = append(mergeBuff, buf...) + } + if len(mergeBuff) > 0 { + return [][]byte{mergeBuff} + } + + return requestBuffers +} + +func mergeMocks(pgmocks []models.Backend, logger *zap.Logger) []models.Backend { + if len(pgmocks) == 0 { + return pgmocks + } + // Check for PBDE first + if len(pgmocks[0].PacketTypes) == 0 || pgmocks[0].PacketTypes[0] != "P" { + return pgmocks + } + var mergeBuff []byte + for _, v := range pgmocks { + buf, _ := postgresDecoderBackend(v) + mergeBuff = append(mergeBuff, buf...) + } + if len(mergeBuff) > 0 { + return []models.Backend{*decodePgRequest(mergeBuff, logger)} + } + + return pgmocks +} diff --git a/keploy/pkg/core/proxy/integrations/redis/decode.go b/keploy/pkg/core/proxy/integrations/redis/decode.go new file mode 100644 index 0000000..6f945db --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/redis/decode.go @@ -0,0 +1,125 @@ +//go:build linux + +// Package redis is the decode point for the redis application. +package redis + +import ( + "context" + "io" + "net" + "time" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func decodeRedis(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, _ models.OutgoingOptions) error { + redisRequests := [][]byte{reqBuf} + logger.Debug("Into the redis parser in test mode") + errCh := make(chan error, 1) + + go func(errCh chan error, redisRequests [][]byte) { + defer pUtil.Recover(logger, clientConn, nil) + defer close(errCh) + for { + + // Read the stream of request packets from the client + for len(redisRequests) == 0 { + err := clientConn.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) + if err != nil { + utils.LogError(logger, err, "failed to set the read deadline for the client conn") + return + } + buffer, err := pUtil.ReadBytes(ctx, logger, clientConn) + // Applied this nolint to ignore the staticcheck error here because of readability + // nolint:staticcheck + if netErr, ok := err.(net.Error); !(ok && netErr.Timeout()) && err != nil && err.Error() != "EOF" { + logger.Debug("failed to read the request message in proxy for redis dependency") + return + } + if netErr, ok := err.(net.Error); (ok && netErr.Timeout()) || (err != nil && err.Error() == "EOF") { + logger.Debug("timeout for client read in redis or EOF") + break + } + if len(buffer) > 0 { + redisRequests = append(redisRequests, buffer) + break + } + } + + if len(redisRequests) == 0 { + logger.Debug("redis request buffer is empty") + continue + } + + // Fuzzy match to get the best matched redis mock + matched, redisResponses, err := fuzzyMatch(ctx, redisRequests, mockDb) + if err != nil { + utils.LogError(logger, err, "error while matching redis mocks") + } + + if !matched { + err := clientConn.SetReadDeadline(time.Time{}) + if err != nil { + utils.LogError(logger, err, "failed to set the read deadline for the client conn") + return + } + + logger.Debug("redisRequests before pass through:", zap.Any("length", len(redisRequests))) + for _, redReq := range redisRequests { + logger.Debug("redisRequests:", zap.Any("h", string(redReq))) + } + + reqBuffer, err := pUtil.PassThrough(ctx, logger, clientConn, dstCfg, redisRequests) + if err != nil { + utils.LogError(logger, err, "failed to passthrough the redis request") + return + } + + redisRequests = [][]byte{} + logger.Debug("request buffer after pass through in redis:", zap.Any("buffer", string(reqBuffer))) + if len(reqBuffer) > 0 { + redisRequests = [][]byte{reqBuffer} + } + logger.Debug("length of redisRequests after passThrough:", zap.Any("length", len(redisRequests))) + continue + } + for _, redisResponse := range redisResponses { + encoded := []byte(redisResponse.Message[0].Data) + if redisResponse.Message[0].Type != models.String { + encoded, err = util.DecodeBase64(redisResponse.Message[0].Data) + if err != nil { + utils.LogError(logger, err, "failed to decode the base64 response") + return + } + } + _, err := clientConn.Write(encoded) + if err != nil { + if ctx.Err() != nil { + return + } + utils.LogError(logger, err, "failed to write the response message to the client application") + return + } + } + + // Clear the redisRequests buffer for the next dependency call + redisRequests = [][]byte{} + logger.Debug("redisRequests after the iteration:", zap.Any("length", len(redisRequests))) + } + }(errCh, redisRequests) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + return nil + } + return err + } +} diff --git a/keploy/pkg/core/proxy/integrations/redis/encode.go b/keploy/pkg/core/proxy/integrations/redis/encode.go new file mode 100644 index 0000000..90e25c1 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/redis/encode.go @@ -0,0 +1,188 @@ +//go:build linux + +package redis + +import ( + "context" + "errors" + "io" + "net" + "time" + + "golang.org/x/sync/errgroup" + + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func encodeRedis(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, _ models.OutgoingOptions) error { + + var redisRequests []models.Payload + var redisResponses []models.Payload + + bufStr := string(reqBuf) + dataType := models.String + + if bufStr != "" { + redisRequests = append(redisRequests, models.Payload{ + Origin: models.FromClient, + Message: []models.OutputBinary{ + { + Type: dataType, + Data: bufStr, + }, + }, + }) + } + _, err := destConn.Write(reqBuf) + if err != nil { + utils.LogError(logger, err, "failed to write request message to the destination server") + return err + } + errCh := make(chan error, 1) + + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context") + } + + reqTimestampMock := time.Now() + + // Read and process responses from the destination server + g.Go(func() error { + defer pUtil.Recover(logger, clientConn, destConn) + defer close(errCh) + + for { + // Read the response from the destination server + resp, err := pUtil.ReadBytes(ctx, logger, destConn) + if err != nil { + if err == io.EOF { + logger.Debug("Response complete, exiting the loop.") + // if there is any buffer left before EOF, we must send it to the client and save this as mock + if len(resp) != 0 { + resTimestampMock := time.Now() + _, err = clientConn.Write(resp) + if err != nil { + utils.LogError(logger, err, "failed to write response message to the client") + errCh <- err + return nil + } + processBuffer(resp, models.FromServer, &redisResponses) + saveMock(ctx, redisRequests, redisResponses, reqTimestampMock, resTimestampMock, mocks) + } + break + } + utils.LogError(logger, err, "failed to read the response message from the destination server") + errCh <- err + return nil + } + + // Write the response message to the client + _, err = clientConn.Write(resp) + if err != nil { + utils.LogError(logger, err, "failed to write response message to the client") + errCh <- err + return nil + } + + resTimestampMock := time.Now() + processBuffer(resp, models.FromServer, &redisResponses) + + // Save the mock with both request and response + if len(redisRequests) > 0 && len(redisResponses) > 0 { + saveMock(ctx, redisRequests, redisResponses, reqTimestampMock, resTimestampMock, mocks) + redisRequests = []models.Payload{} + redisResponses = []models.Payload{} + } + + // Read the next request from the client + reqBuf, err = pUtil.ReadBytes(ctx, logger, clientConn) + if err != nil { + if err != io.EOF { + utils.LogError(logger, err, "failed to read the request message from the client") + errCh <- err + return nil + } + errCh <- err + return nil + } + + bufStr := string(reqBuf) + dataType := models.String + + if bufStr != "" { + redisRequests = append(redisRequests, models.Payload{ + Origin: models.FromClient, + Message: []models.OutputBinary{ + { + Type: dataType, + Data: bufStr, + }, + }, + }) + } + _, err = destConn.Write(reqBuf) + if err != nil { + utils.LogError(logger, err, "failed to write request message to the destination server") + errCh <- err + return nil + } + reqTimestampMock = time.Now() + } + return nil + }) + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + if err == io.EOF { + return nil + } + return err + } +} + +func processBuffer(buffer []byte, origin models.OriginType, payloads *[]models.Payload) { + bufStr := string(buffer) + buffDataType := models.String + + if bufStr != "" { + *payloads = append(*payloads, models.Payload{ + Origin: origin, + Message: []models.OutputBinary{ + { + Type: buffDataType, + Data: bufStr, + }, + }, + }) + } +} + +func saveMock(ctx context.Context, requests, responses []models.Payload, reqTimestampMock, resTimestampMock time.Time, mocks chan<- *models.Mock) { + redisRequestsCopy := make([]models.Payload, len(requests)) + redisResponsesCopy := make([]models.Payload, len(responses)) + copy(redisResponsesCopy, responses) + copy(redisRequestsCopy, requests) + + metadata := make(map[string]string) + metadata["type"] = "config" + metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) + + mocks <- &models.Mock{ + Version: models.GetVersion(), + Name: "mocks", + Kind: models.REDIS, + Spec: models.MockSpec{ + RedisRequests: redisRequestsCopy, + RedisResponses: redisResponsesCopy, + ReqTimestampMock: reqTimestampMock, + ResTimestampMock: resTimestampMock, + Metadata: metadata, + }, + } +} diff --git a/keploy/pkg/core/proxy/integrations/redis/match.go b/keploy/pkg/core/proxy/integrations/redis/match.go new file mode 100755 index 0000000..ecc4977 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/redis/match.go @@ -0,0 +1,151 @@ +//go:build linux + +package redis + +import ( + "context" + "fmt" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.keploy.io/server/v2/pkg/models" +) + +// fuzzyMatch performs a fuzzy matching algorithm to find the best matching mock for the given request. +// It takes a context, a request buffer, and a mock database as input parameters. +// The function iterates over the mocks in the database and applies the fuzzy matching algorithm to find the best match. +// If a match is found, it returns the corresponding response mock and a boolean value indicating success. +// If no match is found, it returns false and a nil response. +// If an error occurs during the matching process, it returns an error. +func fuzzyMatch(ctx context.Context, reqBuff [][]byte, mockDb integrations.MockMemDb) (bool, []models.Payload, error) { + for { + select { + case <-ctx.Done(): + return false, nil, ctx.Err() + default: + mocks, err := mockDb.GetUnFilteredMocks() + if err != nil { + return false, nil, fmt.Errorf("error while getting unfiltered mocks %v", err) + } + + var filteredMocks []*models.Mock + var unfilteredMocks []*models.Mock + + for _, mock := range mocks { + if mock.Kind != "Redis" { + continue + } + if mock.TestModeInfo.IsFiltered { + filteredMocks = append(filteredMocks, mock) + } else { + unfilteredMocks = append(unfilteredMocks, mock) + } + } + + index := findExactMatch(filteredMocks, reqBuff) + + if index == -1 { + index = findBinaryMatch(filteredMocks, reqBuff, 0.9) + } + + if index != -1 { + responseMock := make([]models.Payload, len(filteredMocks[index].Spec.RedisResponses)) + copy(responseMock, filteredMocks[index].Spec.RedisResponses) + originalFilteredMock := *filteredMocks[index] + filteredMocks[index].TestModeInfo.IsFiltered = false + filteredMocks[index].TestModeInfo.SortOrder = pkg.GetNextSortNum() + isUpdated := mockDb.UpdateUnFilteredMock(&originalFilteredMock, filteredMocks[index]) + if !isUpdated { + continue + } + return true, responseMock, nil + } + + index = findExactMatch(unfilteredMocks, reqBuff) + + if index != -1 { + responseMock := make([]models.Payload, len(unfilteredMocks[index].Spec.RedisResponses)) + copy(responseMock, unfilteredMocks[index].Spec.RedisResponses) + return true, responseMock, nil + } + + totalMocks := append(filteredMocks, unfilteredMocks...) + index = findBinaryMatch(totalMocks, reqBuff, 0.4) + + if index != -1 { + responseMock := make([]models.Payload, len(totalMocks[index].Spec.RedisResponses)) + copy(responseMock, totalMocks[index].Spec.RedisResponses) + originalFilteredMock := *totalMocks[index] + if totalMocks[index].TestModeInfo.IsFiltered { + totalMocks[index].TestModeInfo.IsFiltered = false + totalMocks[index].TestModeInfo.SortOrder = pkg.GetNextSortNum() + isUpdated := mockDb.UpdateUnFilteredMock(&originalFilteredMock, totalMocks[index]) + if !isUpdated { + continue + } + } + return true, responseMock, nil + } + + return false, nil, nil + } + } +} + +// TODO: need to generalize this function for different types of integrations. +func findBinaryMatch(tcsMocks []*models.Mock, reqBuffs [][]byte, mxSim float64) int { + // TODO: need find a proper similarity index to set a benchmark for matching or need to find another way to do approximate matching + mxIdx := -1 + for idx, mock := range tcsMocks { + if len(mock.Spec.RedisRequests) == len(reqBuffs) { + for requestIndex, reqBuff := range reqBuffs { + mockReq, err := util.DecodeBase64(mock.Spec.RedisRequests[requestIndex].Message[0].Data) + if err != nil { + mockReq = []byte(mock.Spec.RedisRequests[requestIndex].Message[0].Data) + } + + similarity := fuzzyCheck(mockReq, reqBuff) + if mxSim < similarity { + mxSim = similarity + mxIdx = idx + } + } + + } + } + return mxIdx +} + +func fuzzyCheck(encoded, reqBuf []byte) float64 { + k := util.AdaptiveK(len(reqBuf), 3, 8, 5) + shingles1 := util.CreateShingles(encoded, k) + shingles2 := util.CreateShingles(reqBuf, k) + similarity := util.JaccardSimilarity(shingles1, shingles2) + return similarity +} + +func findExactMatch(tcsMocks []*models.Mock, reqBuffs [][]byte) int { + for idx, mock := range tcsMocks { + if len(mock.Spec.RedisRequests) == len(reqBuffs) { + matched := true // Flag to track if all requests match + + for requestIndex, reqBuff := range reqBuffs { + + bufStr := string(reqBuff) + + // Compare the encoded data + if mock.Spec.RedisRequests[requestIndex].Message[0].Data != bufStr { + matched = false + break // Exit the loop if any request doesn't match + } + } + + if matched { + return idx + } + } + } + return -1 +} diff --git a/keploy/pkg/core/proxy/integrations/redis/redis.go b/keploy/pkg/core/proxy/integrations/redis/redis.go new file mode 100755 index 0000000..2356d2b --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/redis/redis.go @@ -0,0 +1,78 @@ +//go:build linux + +package redis + +import ( + "context" + "net" + + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// func init() { +// integrations.Register(integrations.REDIS, &integrations.Parsers{ +// Initializer: New, +// Priority: 100, +// }) +// } + +type Redis struct { + logger *zap.Logger +} + +func New(logger *zap.Logger) integrations.Integrations { + return &Redis{ + logger: logger, + } +} + +func (r *Redis) MatchType(_ context.Context, buf []byte) bool { + if len(buf) == 0 { + return false + } + + // Check the first byte to determine the RESP data type + switch buf[0] { + case '+', '-', ':', '$', '*', '_', '#', ',', '(', '!', '=', '%', '~', '>': + return true + default: + return false + } +} + +func (r *Redis) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + logger := r.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial redis message") + return err + } + + err = encodeRedis(ctx, logger, reqBuf, src, dst, mocks, opts) + if err != nil { + utils.LogError(logger, err, "failed to encode the redis message into the yaml") + return err + } + return nil +} + +func (r *Redis) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { + logger := r.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) + if err != nil { + utils.LogError(logger, err, "failed to read the initial redis message") + return err + } + + err = decodeRedis(ctx, logger, reqBuf, src, dstCfg, mockDb, opts) + if err != nil { + utils.LogError(logger, err, "failed to decode the redis message") + return err + } + return nil +} diff --git a/keploy/pkg/core/proxy/integrations/scram/scram.go b/keploy/pkg/core/proxy/integrations/scram/scram.go new file mode 100644 index 0000000..e5853fe --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/scram/scram.go @@ -0,0 +1,137 @@ +//go:build linux + +// Package scram provides functionality for SCRAM authentication. +package scram + +import ( + "encoding/base64" + "errors" + "fmt" + "strings" + + "github.com/xdg-go/pbkdf2" + "github.com/xdg-go/scram" + "github.com/xdg-go/stringprep" + "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// GenerateServerFinalMessage generates the server's final message (i.e., the server proof) +// for SCRAM authentication, using a default password and given authentication message, mechanism, salt, iteration count. +func GenerateServerFinalMessage(authMessage, mechanism, password, salt string, itr int, logger *zap.Logger) (string, error) { + var ( + // Declare a variable to hold the hash generation function based on the chosen mechanism. + hashGen scram.HashGeneratorFcn + // normalised password is used in the salted password + passwordDigest string + ) + + username, err := extractUsername(authMessage) + if err != nil { + return "", err + } + + // Switch based on the provided mechanism to determine the hash function to be used. + switch mechanism { + case util.SCRAM_SHA_1: + hashGen = scram.SHA1 + passwordDigest = mongoPasswordDigest(username, password) + case util.SCRAM_SHA_256: + hashGen = scram.SHA256 + passwordDigest, err = stringprep.SASLprep.Prepare(password) + if err != nil { + return "", fmt.Errorf("error SASLprepping password for SCRAM-SHA-256 with password: %s. error: %v", password, err.Error()) + } + default: + // If the mechanism isn't supported, return an error. + return "", errors.New("unsupported authentication mechanism by keploy") + } + + // Get the hash function instance based on the determined generator. + h := hashGen() + + // Compute the salted password using the PBKDF2 function with the provided salt and iteration count. + // It uses the given password. This is the key derivation step. + logger.Debug("the input for generating the salted password", zap.Any("normalised password", passwordDigest), zap.Any("salt", salt), zap.Any("iteration", itr), zap.Any("hash size", h.Size()), zap.Any("mechanism", mechanism)) + saltedPassword := pbkdf2.Key([]byte(passwordDigest), []byte(salt), itr, h.Size(), hashGen) + logger.Debug("after generating the salted password", zap.Any("salted password", saltedPassword)) + + // Compute the server key using HMAC with the derived salted password and the string "Server Key". + serverKey := computeHMAC(hashGen, saltedPassword, []byte("Server Key")) + logger.Debug("generating the server using the salted password", zap.Any("server key", serverKey)) + + // Compute the server signature (server proof) using HMAC with the server key and the provided authMessage. + serverSignature := computeHMAC(hashGen, serverKey, []byte(authMessage)) + logger.Debug("the new server proof for the second auth request", zap.Any("server signature", base64.StdEncoding.EncodeToString(serverSignature)), zap.Any("derived from auth message", authMessage)) + + return base64.StdEncoding.EncodeToString(serverSignature), nil +} + +// GenerateServerFirstMessage generates the server's first response message for SCRAM authentication. +// It replaces the expected nonce from the recorded request with the actual nonce from the received request. +// +// Parameters: +// - recordedRequestMsg: The byte slice containing the recorded client's first message. +// - receivedRequestMsg: The byte slice containing the received client's first message. +// - firstResponseMsg: The byte slice containing the server's initial response message. +// - log: An instance of a log from the zap package. +// +// Returns: +// - A modified server's first response message with the nonce replaced. +// - An error if nonce extraction or replacement fails. +func GenerateServerFirstMessage(recordedRequestMsg, receivedRequestMsg, firstResponseMsg []byte, logger *zap.Logger) (string, error) { + expectedNonce, err := extractClientNonce(string(recordedRequestMsg)) + if err != nil { + utils.LogError(logger, err, "failed to extract the client nonce from the recorded first message") + return "", err + } + actualNonce, err := extractClientNonce(string(receivedRequestMsg)) + if err != nil { + utils.LogError(logger, err, "failed to extract the client nonce from the received first message") + return "", err + } + // Since, the nonce are randomlly generated string. so, each session have unique nonce. + // Thus, the mocked server response should be updated according to the current nonce + updatedResponse := strings.ReplaceAll(string(firstResponseMsg), expectedNonce, actualNonce) + + logger.Debug("Updated server first message after nonce substitution", zap.String("updatedResponse", updatedResponse)) + + return updatedResponse, nil +} + +// GenerateAuthMessage creates an authentication message based on the initial +// client request and the server's first response. The function extracts the GS2 +// header and the client's nonce from the provided strings and then concatenates +// them to form the complete authentication message. +// +// Parameters: +// - firstRequest: The initial request string from the client. +// - firstResponse: The server's first response string. +// - log: An instance of a log for logging errors and activities. +// +// Returns: +// - A string representing the complete authentication message. If there's an +// error during extraction or message creation, the function logs the error +// and returns an empty string. +func GenerateAuthMessage(firstRequest, firstResponse string, logger *zap.Logger) string { + gs2, err := extractAuthID(firstRequest) + if err != nil { + utils.LogError(logger, err, "failed to extract the client gs2 header from the received first message") + return "" + } + authMsg := firstRequest[len(gs2):] + "," + firstResponse + "," + nonce, err := extractClientNonce(firstResponse) + if err != nil { + utils.LogError(logger, err, "failed to extract the client nonce from the recorded first message") + return "" + } + + authMsg += fmt.Sprintf( + "c=%s,r=%s", + base64.StdEncoding.EncodeToString([]byte(gs2)), + nonce, + ) + + return authMsg +} diff --git a/keploy/pkg/core/proxy/integrations/scram/util.go b/keploy/pkg/core/proxy/integrations/scram/util.go new file mode 100644 index 0000000..ee960b1 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/scram/util.go @@ -0,0 +1,90 @@ +//go:build linux + +package scram + +import ( + "crypto/hmac" + "crypto/md5" + "errors" + "fmt" + "io" + "regexp" + "strings" + + "github.com/xdg-go/scram" +) + +// extractClientNonce extracts the nonce value from a SCRAM authentication first message. +// +// Parameters: +// - firstMsg: The SCRAM authentication message string, which should contain key-value pairs +// separated by commas, e.g., "n,,n=username,r=nonce". +// +// Returns: +// - The extracted nonce value as a string. +// - An error if the nonce ("r=") cannot be found in the provided message. +func extractClientNonce(firstMsg string) (string, error) { + // Split the string based on "," + parts := strings.Split(firstMsg, ",") + + // Iterate over the parts to find the one starting with "r=" + for _, part := range parts { + if strings.HasPrefix(part, "r=") { + // Split based on "=" and get the value of "r" + // value := strings.Split(part, "=")[1] + value := strings.TrimPrefix(part, "r=") + if value == part { + return "", fmt.Errorf("error parsing '%s' for fetching client nonce", part) + } + return value, nil + } + } + return "", errors.New("nonce not found") +} + +// computeHMAC computes the HMAC (Hash-based Message Authentication Code) of the provided data +// using the specified hash generation function and key. +// +// Parameters: +// - hg: A function to generate the desired hash (like SHA-1 or SHA-256). +// - key: The secret key to use for the HMAC computation. +// - data: The input data for which the HMAC is to be computed. +// +// Returns: +// - A byte slice representing the computed HMAC value. +func computeHMAC(hg scram.HashGeneratorFcn, key, data []byte) []byte { + mac := hmac.New(hg, key) + mac.Write(data) + return mac.Sum(nil) +} + +func mongoPasswordDigest(username, password string) string { + // Ignore gosec warning "Use of weak cryptographic primitive". We need to use MD5 here to + // implement the SCRAM specification. + /* #nosec G401 */ + h := md5.New() + _, _ = io.WriteString(h, username) + _, _ = io.WriteString(h, ":mongo:") + _, _ = io.WriteString(h, password) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func extractUsername(authMessage string) (string, error) { + parts := strings.Split(authMessage, ",") + for _, part := range parts { + if strings.HasPrefix(part, "n=") { + nValue := strings.TrimPrefix(part, "n=") + return nValue, nil + } + } + return "", fmt.Errorf("no username found in the auth message") +} + +func extractAuthID(input string) (string, error) { + re := regexp.MustCompile(`n,([^,]*),`) // Regular expression to match "n,," or "n,SOMETHING," + matches := re.FindStringSubmatch(input) + if len(matches) >= 2 { + return "n," + matches[1] + ",", nil + } + return "", fmt.Errorf("no match found") +} diff --git a/keploy/pkg/core/proxy/integrations/util/util.go b/keploy/pkg/core/proxy/integrations/util/util.go new file mode 100644 index 0000000..dd9aec4 --- /dev/null +++ b/keploy/pkg/core/proxy/integrations/util/util.go @@ -0,0 +1,88 @@ +//go:build linux + +// Package util provides utility functions for the integration package. +package util + +import ( + "encoding/base64" + "unicode" + + "go.keploy.io/server/v2/pkg/models" +) + +func IsASCII(s string) bool { + for _, r := range s { + if r > unicode.MaxASCII { + return false + } + } + return true +} + +func DecodeBase64(encoded string) ([]byte, error) { + // Decode the base64 encoded string to buffer + data, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return nil, err + } + return data, nil +} + +func EncodeBase64(decoded []byte) string { + // Encode the []byte string to encoded string + return base64.StdEncoding.EncodeToString(decoded) +} + +// Functions related to fuzzy matching + +func AdaptiveK(length, min, max, N int) int { + k := length / N + if k < min { + return min + } else if k > max { + return max + } + return k +} + +func CreateShingles(data []byte, k int) map[string]struct{} { + shingles := make(map[string]struct{}) + for i := 0; i < len(data)-k+1; i++ { + shingle := string(data[i : i+k]) + shingles[shingle] = struct{}{} + } + return shingles +} + +// JaccardSimilarity computes the Jaccard similarity between two sets of shingles. +func JaccardSimilarity(setA, setB map[string]struct{}) float64 { + intersectionSize := 0 + for k := range setA { + if _, exists := setB[k]; exists { + intersectionSize++ + } + } + + unionSize := len(setA) + len(setB) - intersectionSize + + if unionSize == 0 { + return 0.0 + } + return float64(intersectionSize) / float64(unionSize) +} + +func GetMockByKind(mocks []*models.Mock, kind string) []*models.Mock { + var filteredMocks []*models.Mock + for _, mock := range mocks { + if mock.Kind == models.Kind(kind) { + filteredMocks = append(filteredMocks, mock) + } + } + return filteredMocks +} + +// scram enum values +const ( + SCRAM_SHA_1 = "SCRAM-SHA-1" + SCRAM_SHA_256 = "SCRAM-SHA-256" +) diff --git a/keploy/pkg/core/proxy/mockmanager.go b/keploy/pkg/core/proxy/mockmanager.go new file mode 100644 index 0000000..ff721fd --- /dev/null +++ b/keploy/pkg/core/proxy/mockmanager.go @@ -0,0 +1,514 @@ +//go:build linux + +package proxy + +import ( + "fmt" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +// ---------------- MockManager (kind-aware) ---------------- + +type MockManager struct { + // legacy "all" trees (kept for compatibility with existing callers) + filtered *TreeDb + unfiltered *TreeDb + + // global revision (legacy) + rev uint64 + + // NEW: per-kind revisions + revMu sync.RWMutex + revByKind map[models.Kind]*uint64 + + // NEW: per-kind trees (guarded by treesMu) + treesMu sync.RWMutex + filteredByKind map[models.Kind]*TreeDb + unfilteredByKind map[models.Kind]*TreeDb + + logger *zap.Logger + consumedMocks sync.Map // zero value is ready-to-use; no explicit init required +} + +func NewMockManager(filtered, unfiltered *TreeDb, logger *zap.Logger) *MockManager { + if filtered == nil { + filtered = NewTreeDb(customComparator) + } + if unfiltered == nil { + unfiltered = NewTreeDb(customComparator) + } + return &MockManager{ + filtered: filtered, + unfiltered: unfiltered, + filteredByKind: make(map[models.Kind]*TreeDb), + unfilteredByKind: make(map[models.Kind]*TreeDb), + revByKind: make(map[models.Kind]*uint64), + logger: logger, + } +} + +// ---------- revision helpers ---------- + +func (m *MockManager) Revision() uint64 { + return atomic.LoadUint64(&m.rev) +} + +func (m *MockManager) bumpRevisionAll() { + atomic.AddUint64(&m.rev, 1) +} + +func (m *MockManager) RevisionByKind(kind models.Kind) uint64 { + m.revMu.RLock() + ptr := m.revByKind[kind] + m.revMu.RUnlock() + if ptr == nil { + return 0 + } + return atomic.LoadUint64(ptr) +} + +func (m *MockManager) bumpRevisionKind(kind models.Kind) { + m.revMu.Lock() + ptr := m.revByKind[kind] + if ptr == nil { + var v uint64 + // Store pointer in map; safe to use after unlocking as we mutate via atomics. + ptr = &v + m.revByKind[kind] = ptr + } + m.revMu.Unlock() + atomic.AddUint64(ptr, 1) +} + +// ensureKindTrees returns per-kind trees, creating them if missing. +// It is safe for concurrent use. +func (m *MockManager) ensureKindTrees(kind models.Kind) (f *TreeDb, u *TreeDb) { + // Fast path: read lock + m.treesMu.RLock() + f = m.filteredByKind[kind] + u = m.unfilteredByKind[kind] + m.treesMu.RUnlock() + if f != nil && u != nil { + return f, u + } + + // Slow path: upgrade to write lock and double-check + m.treesMu.Lock() + if f = m.filteredByKind[kind]; f == nil { + f = NewTreeDb(customComparator) + m.filteredByKind[kind] = f + } + if u = m.unfilteredByKind[kind]; u == nil { + u = NewTreeDb(customComparator) + m.unfilteredByKind[kind] = u + } + m.treesMu.Unlock() + return f, u +} + +// ---------- getters ---------- + +func (m *MockManager) GetFilteredMocks() ([]*models.Mock, error) { + results := make([]*models.Mock, 0, 64) + m.filtered.rangeValues(func(v interface{}) bool { + if mock, ok := v.(*models.Mock); ok && mock != nil { + results = append(results, mock) + } + return true + }) + return results, nil +} + +func (m *MockManager) GetUnFilteredMocks() ([]*models.Mock, error) { + results := make([]*models.Mock, 0, 128) + m.unfiltered.rangeValues(func(v interface{}) bool { + if mock, ok := v.(*models.Mock); ok && mock != nil { + results = append(results, mock) + } + return true + }) + return results, nil +} + +// NEW: kind-scoped getters used by Redis matcher +func (m *MockManager) GetFilteredMocksByKind(kind models.Kind) ([]*models.Mock, error) { + // Fetch pointer safely; the tree itself is responsible for its own safety. + m.treesMu.RLock() + flt := m.filteredByKind[kind] + m.treesMu.RUnlock() + if flt == nil { + flt, _ = m.ensureKindTrees(kind) + } + + results := make([]*models.Mock, 0, 64) + flt.rangeValues(func(v interface{}) bool { + if mock, ok := v.(*models.Mock); ok && mock != nil { + results = append(results, mock) + } + return true + }) + return results, nil +} + +func (m *MockManager) GetUnFilteredMocksByKind(kind models.Kind) ([]*models.Mock, error) { + m.treesMu.RLock() + unf := m.unfilteredByKind[kind] + m.treesMu.RUnlock() + if unf == nil { + _, unf = m.ensureKindTrees(kind) + } + + results := make([]*models.Mock, 0, 128) + unf.rangeValues(func(v interface{}) bool { + if mock, ok := v.(*models.Mock); ok && mock != nil { + results = append(results, mock) + } + return true + }) + return results, nil +} + +// ---------- setters (populate both legacy + per-kind) ---------- + +func (m *MockManager) SetFilteredMocks(mocks []*models.Mock) { + // legacy rebuild + m.filtered.deleteAll() + + // rebuild per-kind filtered maps from scratch to avoid stale entries + newFilteredByKind := make(map[models.Kind]*TreeDb, len(m.filteredByKind)) + touched := map[models.Kind]struct{}{} + + for index, mock := range mocks { + if mock.TestModeInfo.SortOrder == 0 { + mock.TestModeInfo.SortOrder = int64(index) + 1 + } + mock.TestModeInfo.ID = index + m.filtered.insert(mock.TestModeInfo, mock) + + k := mock.Kind + td := newFilteredByKind[k] + if td == nil { + td = NewTreeDb(customComparator) + newFilteredByKind[k] = td + } + td.insert(mock.TestModeInfo, mock) + touched[k] = struct{}{} + } + + // atomically swap the per-kind map + m.treesMu.Lock() + m.filteredByKind = newFilteredByKind + m.treesMu.Unlock() + + for k := range touched { + m.bumpRevisionKind(k) + } + m.bumpRevisionAll() +} + +func (m *MockManager) SetUnFilteredMocks(mocks []*models.Mock) { + // legacy rebuild + m.unfiltered.deleteAll() + + // rebuild per-kind unfiltered maps from scratch to avoid stale entries + newUnfilteredByKind := make(map[models.Kind]*TreeDb, len(m.unfilteredByKind)) + touched := map[models.Kind]struct{}{} + + for index, mock := range mocks { + if mock.TestModeInfo.SortOrder == 0 { + mock.TestModeInfo.SortOrder = int64(index) + 1 + } + mock.TestModeInfo.ID = index + m.unfiltered.insert(mock.TestModeInfo, mock) + + k := mock.Kind + td := newUnfilteredByKind[k] + if td == nil { + td = NewTreeDb(customComparator) + newUnfilteredByKind[k] = td + } + td.insert(mock.TestModeInfo, mock) + touched[k] = struct{}{} + } + + // atomically swap the per-kind map + m.treesMu.Lock() + m.unfilteredByKind = newUnfilteredByKind + m.treesMu.Unlock() + + for k := range touched { + m.bumpRevisionKind(k) + } + m.bumpRevisionAll() +} + +// ---------- point updates / deletes (keep per-kind in sync) ---------- + +func (m *MockManager) UpdateUnFilteredMock(old *models.Mock, new *models.Mock) bool { + // Update legacy/global tree first + updatedGlobal := m.unfiltered.update(old.TestModeInfo, new.TestModeInfo, new) + + oldK, newK := old.Kind, new.Kind + var updatedOldKind, updatedNewKind bool + + if oldK == newK { + // Same kind: update the per-kind tree under lock + _, unf := m.ensureKindTrees(newK) + m.treesMu.Lock() + updatedNewKind = unf.update(old.TestModeInfo, new.TestModeInfo, new) + + // Self-heal if global updated but per-kind missed (e.g., not present yet) + if updatedGlobal && !updatedNewKind { + if m.logger != nil { + m.logger.Warn("self-healing per-kind tree: global update succeeded but per-kind missed", + zap.String("kind", string(newK)), + zap.String("mockName", new.Name), + zap.Any("testModeInfo", new.TestModeInfo), + ) + } + unf.insert(new.TestModeInfo, new) + updatedNewKind = true + } + m.treesMu.Unlock() + } else { + // Kind changed: remove from old kind tree, insert/update in new kind tree under one lock + _, oldUnf := m.ensureKindTrees(oldK) + _, newUnf := m.ensureKindTrees(newK) + m.treesMu.Lock() + updatedOldKind = oldUnf.delete(old.TestModeInfo) + updatedNewKind = newUnf.update(old.TestModeInfo, new.TestModeInfo, new) + if !updatedNewKind { + newUnf.insert(new.TestModeInfo, new) + updatedNewKind = true + } + m.treesMu.Unlock() + if m.logger != nil { + m.logger.Info("moved mock across kinds", + zap.String("mockName", new.Name), + zap.String("fromKind", string(oldK)), + zap.String("toKind", string(newK)), + ) + } + } + + // Mark usage if global changed (legacy behavior) + if updatedGlobal { + if err := m.flagMockAsUsed(models.MockState{ + Name: new.Name, + Usage: models.Updated, + IsFiltered: new.TestModeInfo.IsFiltered, + SortOrder: new.TestModeInfo.SortOrder, + }); err != nil { + m.logger.Error("failed to flag mock as used", zap.Error(err)) + } + } + + // Bump revisions accurately: + // - global only if the global tree changed + // - per-kind only for kinds whose per-kind tree changed + if oldK != newK { + if updatedOldKind { + m.bumpRevisionKind(oldK) + } + if updatedNewKind { + m.bumpRevisionKind(newK) + } + } else if updatedNewKind { + m.bumpRevisionKind(newK) + } + if updatedGlobal { + m.bumpRevisionAll() + } + return updatedGlobal +} + +func (m *MockManager) DeleteFilteredMock(mock models.Mock) bool { + deletedGlobal := m.filtered.delete(mock.TestModeInfo) + + // per-kind + k := mock.Kind + flt, _ := m.ensureKindTrees(k) + m.treesMu.Lock() + deletedKind := flt.delete(mock.TestModeInfo) + m.treesMu.Unlock() + + if deletedGlobal { + if err := m.flagMockAsUsed(models.MockState{ + Name: mock.Name, + Usage: models.Deleted, + IsFiltered: mock.TestModeInfo.IsFiltered, + SortOrder: mock.TestModeInfo.SortOrder, + }); err != nil { + m.logger.Error("failed to flag mock as used", zap.Error(err)) + } + } + + // Bump per-kind only if that tree changed; global only if global changed + if deletedKind { + m.bumpRevisionKind(k) + } + if deletedGlobal { + m.bumpRevisionAll() + } + return deletedGlobal +} + +func (m *MockManager) DeleteUnFilteredMock(mock models.Mock) bool { + deletedGlobal := m.unfiltered.delete(mock.TestModeInfo) + + // per-kind + k := mock.Kind + _, unf := m.ensureKindTrees(k) + m.treesMu.Lock() + deletedKind := unf.delete(mock.TestModeInfo) + m.treesMu.Unlock() + + if deletedGlobal { + if err := m.flagMockAsUsed(models.MockState{ + Name: mock.Name, + Usage: models.Deleted, + IsFiltered: mock.TestModeInfo.IsFiltered, + SortOrder: mock.TestModeInfo.SortOrder, + }); err != nil { + m.logger.Error("failed to flag mock as used", zap.Error(err)) + } + } + + // Bump per-kind only if that tree changed; global only if global changed + if deletedKind { + m.bumpRevisionKind(k) + } + if deletedGlobal { + m.bumpRevisionAll() + } + return deletedGlobal +} + +// ---------- bookkeeping ---------- + +func (m *MockManager) flagMockAsUsed(mock models.MockState) error { + if mock.Name == "" { + return fmt.Errorf("mock is empty") + } + m.consumedMocks.Store(mock.Name, mock) + return nil +} + +func (m *MockManager) GetConsumedMocks() []models.MockState { + var out []models.MockState + + // Snapshot & collect first (no deletes during Range). We intentionally drain only what existed at snapshot time. + m.consumedMocks.Range(func(key, val interface{}) bool { + k, ok := key.(string) + if !ok { + if m.logger != nil { + m.logger.Warn("unexpected key type in consumedMocks; skipping", + zap.Any("keyType", fmt.Sprintf("%T", key))) + } + return true // skip this entry + } + if st, ok := val.(models.MockState); ok { + out = append(out, st) + } else if m.logger != nil { + m.logger.Warn("unexpected value type in consumedMocks; skipping", + zap.String("key", k), + zap.Any("valueType", fmt.Sprintf("%T", val))) + } + return true + }) + + // Sort: prefer numeric suffix after the last '-' (e.g., name-123); else lexicographic + type withSuffix struct { + st models.MockState + name string + num int + has bool + } + numericSuffix := func(name string) (int, bool) { + i := strings.LastIndexByte(name, '-') + if i < 0 || i+1 >= len(name) { + return 0, false + } + n, err := strconv.Atoi(name[i+1:]) + if err != nil { + return 0, false + } + return n, true + } + + ws := make([]withSuffix, len(out)) + for i, st := range out { + n, ok := numericSuffix(st.Name) + ws[i] = withSuffix{st: st, name: st.Name, num: n, has: ok} + } + sort.SliceStable(ws, func(i, j int) bool { + a, b := ws[i], ws[j] + if a.has && b.has { + if a.num != b.num { + return a.num < b.num + } + // tie-break numerics by name for determinism + return a.name < b.name + } + return a.name < b.name + }) + for i := range out { + out[i] = ws[i].st + } + + // Now clear those entries from the map we just drained + for _, st := range out { + m.consumedMocks.Delete(st.Name) + } + return out +} + +// GetMySQLCounts computes counts of MySQL mocks. +// Uses the per-kind unfiltered tree if available, otherwise falls back +// to scanning the legacy unfiltered tree. +func (m *MockManager) GetMySQLCounts() (total, config, data int) { + // Fast path: snapshot the per-kind tree pointer under lock + m.treesMu.RLock() + tree := m.unfilteredByKind[models.MySQL] + m.treesMu.RUnlock() + + if tree != nil { + tree.rangeValues(func(v interface{}) bool { + mock, ok := v.(*models.Mock) + if !ok || mock == nil { + return true + } + total++ + if mock.Spec.Metadata["type"] == "config" { + config++ + } else { + data++ + } + return true + }) + return + } + + // Fallback: legacy scan of the combined tree + m.unfiltered.rangeValues(func(v interface{}) bool { + mock, ok := v.(*models.Mock) + if !ok || mock == nil || mock.Kind != models.MySQL { + return true + } + total++ + if mock.Spec.Metadata["type"] == "config" { + config++ + } else { + data++ + } + return true + }) + return +} diff --git a/keploy/pkg/core/proxy/options.go b/keploy/pkg/core/proxy/options.go new file mode 100755 index 0000000..3783649 --- /dev/null +++ b/keploy/pkg/core/proxy/options.go @@ -0,0 +1,12 @@ +//go:build linux + +package proxy + +// TODO: what is the need of this? currently it is not being used anywhere. + +// Option provides a means to initiate the proxy based on user input. +type Option struct { + Port uint32 + DNSPort uint32 + MongoPassword string +} diff --git a/keploy/pkg/core/proxy/parsers.go b/keploy/pkg/core/proxy/parsers.go new file mode 100644 index 0000000..b6cfe8b --- /dev/null +++ b/keploy/pkg/core/proxy/parsers.go @@ -0,0 +1,15 @@ +//go:build linux + +package proxy + +import ( + // import all the integrations + _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/generic" + _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/grpc" + _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/grpcV2" + _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/http" + _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/mongo" + _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql" + _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/postgres/v1" + _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/redis" +) diff --git a/keploy/pkg/core/proxy/proxy.go b/keploy/pkg/core/proxy/proxy.go new file mode 100755 index 0000000..b2a3ddc --- /dev/null +++ b/keploy/pkg/core/proxy/proxy.go @@ -0,0 +1,731 @@ +//go:build linux + +// Package proxy handles all the outgoing network calls and captures/forwards the request and response messages. +// It also handles the DNS resolution mechanism. +package proxy + +import ( + "bufio" + "bytes" + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/sync/errgroup" + + "github.com/miekg/dns" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/core" + "go.keploy.io/server/v2/pkg/core/proxy/integrations" + pTls "go.keploy.io/server/v2/pkg/core/proxy/tls" + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type ParserPriority struct { + Priority int + ParserType integrations.IntegrationType +} + +type Proxy struct { + logger *zap.Logger + + IP4 string + IP6 string + Port uint32 + DNSPort uint32 + + DestInfo core.DestInfo + Integrations map[integrations.IntegrationType]integrations.Integrations + + MockManagers sync.Map + integrationsPriority []ParserPriority + + sessions *core.Sessions + + connMutex *sync.Mutex + ipMutex *sync.Mutex + + clientConnections []net.Conn + + Listener net.Listener + + //to store the nsswitch.conf file data + nsswitchData []byte // in test mode we change the configuration of "hosts" in nsswitch.conf file to disable resolution over unix socket + UDPDNSServer *dns.Server + TCPDNSServer *dns.Server +} + +func New(logger *zap.Logger, info core.DestInfo, opts *config.Config) *Proxy { + return &Proxy{ + logger: logger, + Port: opts.ProxyPort, // default: 16789 + DNSPort: opts.DNSPort, // default: 26789 + IP4: "127.0.0.1", // default: "127.0.0.1" <-> (2130706433) + IP6: "::1", //default: "::1" <-> ([4]uint32{0000, 0000, 0000, 0001}) + ipMutex: &sync.Mutex{}, + connMutex: &sync.Mutex{}, + DestInfo: info, + sessions: core.NewSessions(), + MockManagers: sync.Map{}, + Integrations: make(map[integrations.IntegrationType]integrations.Integrations), + } +} + +func (p *Proxy) InitIntegrations(_ context.Context) error { + // initialize the integrations + for parserType, parser := range integrations.Registered { + logger := p.logger.With(zap.Any("Type", parserType)) + prs := parser.Initializer(logger) + p.Integrations[parserType] = prs + p.integrationsPriority = append(p.integrationsPriority, ParserPriority{Priority: parser.Priority, ParserType: parserType}) + } + sort.Slice(p.integrationsPriority, func(i, j int) bool { + return p.integrationsPriority[i].Priority > p.integrationsPriority[j].Priority + }) + return nil +} + +func (p *Proxy) StartProxy(ctx context.Context, opts core.ProxyOptions) error { + + //first initialize the integrations + err := p.InitIntegrations(ctx) + if err != nil { + utils.LogError(p.logger, err, "failed to initialize the integrations") + return err + } + + // set up the CA for tls connections + err = pTls.SetupCA(ctx, p.logger) + if err != nil { + // log the error and continue + p.logger.Warn("failed to setup CA", zap.Error(err)) + } + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context") + } + // Create a channel to signal readiness of each server + readyChan := make(chan error, 1) + + // start the proxy server + g.Go(func() error { + defer utils.Recover(p.logger) + err := p.start(ctx, readyChan) + readyChan <- err + if err != nil { + utils.LogError(p.logger, err, "error while running the proxy server") + return err + } + return nil + }) + + //change the ip4 and ip6 if provided in the opts in case of docker environment + if len(opts.DNSIPv4Addr) != 0 { + p.IP4 = opts.DNSIPv4Addr + } + + if len(opts.DNSIPv6Addr) != 0 { + p.IP6 = opts.DNSIPv6Addr + } + + // start the TCP DNS server + p.logger.Debug("Starting Tcp Dns Server for handling Dns queries over TCP") + g.Go(func() error { + defer utils.Recover(p.logger) + errCh := make(chan error, 1) + go func(errCh chan error) { + defer utils.Recover(p.logger) + err := p.startTCPDNSServer(ctx) + if err != nil { + errCh <- err + } + }(errCh) + + select { + case <-ctx.Done(): + err := p.TCPDNSServer.Shutdown() + if err != nil { + utils.LogError(p.logger, err, "failed to shutdown tcp dns server") + return err + } + return nil + case err := <-errCh: + return err + } + }) + + // start the UDP DNS server + p.logger.Debug("Starting Udp Dns Server for handling Dns queries over UDP") + g.Go(func() error { + defer utils.Recover(p.logger) + errCh := make(chan error, 1) + go func(errCh chan error) { + defer utils.Recover(p.logger) + err := p.startUDPDNSServer(ctx) + if err != nil { + errCh <- err + } + }(errCh) + + select { + case <-ctx.Done(): + err := p.UDPDNSServer.Shutdown() + if err != nil { + utils.LogError(p.logger, err, "failed to shutdown tcp dns server") + return err + } + return nil + case err := <-errCh: + return err + } + }) + // Wait for the proxy server to be ready or fail + err = <-readyChan + if err != nil { + return err + } + p.logger.Info("Keploy has taken control of the DNS resolution mechanism, your application may misbehave if you have provided wrong domain name in your application code.") + + p.logger.Info(fmt.Sprintf("Proxy started at port:%v", p.Port)) + return nil +} + +// start function starts the proxy server on the idle local port +func (p *Proxy) start(ctx context.Context, readyChan chan<- error) error { + + // It will listen on all the interfaces + listener, err := net.Listen("tcp", fmt.Sprintf(":%v", p.Port)) + if err != nil { + utils.LogError(p.logger, err, fmt.Sprintf("failed to start proxy on port:%v", p.Port)) + // Notify failure + readyChan <- err + return err + } + p.Listener = listener + p.logger.Debug(fmt.Sprintf("Proxy server is listening on %s", fmt.Sprintf(":%v", listener.Addr()))) + // Signal that the server is ready + readyChan <- nil + defer func(listener net.Listener) { + err := listener.Close() + + if err != nil { + p.logger.Error("failed to close the listener", zap.Error(err)) + } + p.logger.Info("proxy stopped...") + }(listener) + + clientConnCtx, clientConnCancel := context.WithCancel(ctx) + clientConnErrGrp, _ := errgroup.WithContext(clientConnCtx) + defer func() { + clientConnCancel() + err := clientConnErrGrp.Wait() + if err != nil { + p.logger.Debug("failed to handle the client connection", zap.Error(err)) + } + //closing all the mock channels (if any in record mode) + for _, mc := range p.sessions.GetAllMC() { + if mc != nil { + close(mc) + } + } + + if string(p.nsswitchData) != "" { + // reset the hosts config in nsswitch.conf of the system (in test mode) + err = p.resetNsSwitchConfig() + if err != nil { + utils.LogError(p.logger, err, "failed to reset the nsswitch config") + } + } + }() + + for { + clientConnCh := make(chan net.Conn, 1) + errCh := make(chan error, 1) + go func() { + defer utils.Recover(p.logger) + conn, err := listener.Accept() + if err != nil { + if strings.Contains(err.Error(), "use of closed network connection") { + errCh <- nil + return + } + utils.LogError(p.logger, err, "failed to accept connection to the proxy") + errCh <- err + return + } + clientConnCh <- conn + }() + select { + case <-ctx.Done(): + return nil + case err := <-errCh: + return err + // handle the client connection + case clientConn := <-clientConnCh: + clientConnErrGrp.Go(func() error { + defer util.Recover(p.logger, clientConn, nil) + err := p.handleConnection(clientConnCtx, clientConn) + if err != nil && err != io.EOF { + utils.LogError(p.logger, err, "failed to handle the client connection") + } + return nil + }) + } + } +} + +// handleConnection function executes the actual outgoing network call and captures/forwards the request and response messages. +func (p *Proxy) handleConnection(ctx context.Context, srcConn net.Conn) error { + //checking how much time proxy takes to execute the flow. + start := time.Now() + + // making a new client connection id for each client connection + clientConnID := util.GetNextID() + + defer func(start time.Time) { + duration := time.Since(start) + p.logger.Debug("time taken by proxy to execute the flow", zap.Any("Client ConnectionID", clientConnID), zap.Any("Duration(ms)", duration.Milliseconds())) + }(start) + + // dstConn stores conn with actual destination for the outgoing network call + var dstConn net.Conn + + //Dialing for tls conn + destConnID := util.GetNextID() + + remoteAddr := srcConn.RemoteAddr().(*net.TCPAddr) + sourcePort := remoteAddr.Port + + p.logger.Debug("Inside handleConnection of proxyServer", zap.Any("source port", sourcePort), zap.Any("Time", time.Now().Unix())) + + destInfo, err := p.DestInfo.Get(ctx, uint16(sourcePort)) + if err != nil { + utils.LogError(p.logger, err, "failed to fetch the destination info", zap.Any("Source port", sourcePort)) + return err + } + + // releases the occupied source port when done fetching the destination info + err = p.DestInfo.Delete(ctx, uint16(sourcePort)) + if err != nil { + utils.LogError(p.logger, err, "failed to delete the destination info", zap.Any("Source port", sourcePort)) + return err + } + + //get the session rule + rule, ok := p.sessions.Get(destInfo.AppID) + if !ok { + utils.LogError(p.logger, nil, "failed to fetch the session rule", zap.Any("AppID", destInfo.AppID)) + return err + } + + var dstAddr string + + switch destInfo.Version { + case 4: + p.logger.Debug("the destination is ipv4") + dstAddr = fmt.Sprintf("%v:%v", util.ToIP4AddressStr(destInfo.IPv4Addr), destInfo.Port) + p.logger.Debug("", zap.Any("DestIp4", destInfo.IPv4Addr), zap.Any("DestPort", destInfo.Port)) + case 6: + p.logger.Debug("the destination is ipv6") + dstAddr = fmt.Sprintf("[%v]:%v", util.ToIPv6AddressStr(destInfo.IPv6Addr), destInfo.Port) + p.logger.Debug("", zap.Any("DestIp6", destInfo.IPv6Addr), zap.Any("DestPort", destInfo.Port)) + } + + // This is used to handle the parser errors + parserErrGrp, parserCtx := errgroup.WithContext(ctx) + parserCtx = context.WithValue(parserCtx, models.ErrGroupKey, parserErrGrp) + parserCtx = context.WithValue(parserCtx, models.ClientConnectionIDKey, fmt.Sprint(clientConnID)) + parserCtx = context.WithValue(parserCtx, models.DestConnectionIDKey, fmt.Sprint(destConnID)) + parserCtx, parserCtxCancel := context.WithCancel(parserCtx) + defer func() { + parserCtxCancel() + + if srcConn != nil { + err := srcConn.Close() + if err != nil { + if !strings.Contains(err.Error(), "use of closed network connection") { + utils.LogError(p.logger, err, "failed to close the source connection", zap.Any("clientConnID", clientConnID)) + } + return + } + } + + if dstConn != nil { + err = dstConn.Close() + if err != nil { + // Use string matching as a last resort to check for the specific error + if !strings.Contains(err.Error(), "use of closed network connection") { + // Log other errors + utils.LogError(p.logger, err, "failed to close the destination connection") + } + return + } + } + + err = parserErrGrp.Wait() + if err != nil { + utils.LogError(p.logger, err, "failed to handle the parser cleanUp") + } + }() + + //check for global passthrough in test mode + if !rule.Mocking && rule.Mode == models.MODE_TEST { + + dstConn, err = net.Dial("tcp", dstAddr) + if err != nil { + utils.LogError(p.logger, err, "failed to dial the conn to destination server", zap.Any("proxy port", p.Port), zap.Any("server address", dstAddr)) + return err + } + + err = p.globalPassThrough(parserCtx, srcConn, dstConn) + if err != nil { + utils.LogError(p.logger, err, "failed to handle the global pass through") + return err + } + return nil + } + + //checking for the destination port of "mysql" + if destInfo.Port == 3306 { + if rule.Mode != models.MODE_TEST { + dstConn, err = net.Dial("tcp", dstAddr) + if err != nil { + utils.LogError(p.logger, err, "failed to dial the conn to destination server", zap.Any("proxy port", p.Port), zap.Any("server address", dstAddr)) + return err + } + + dstCfg := &models.ConditionalDstCfg{ + Port: uint(destInfo.Port), + } + rule.DstCfg = dstCfg + + // Record the outgoing message into a mock + err := p.Integrations[integrations.MYSQL].RecordOutgoing(parserCtx, srcConn, dstConn, rule.MC, rule.OutgoingOptions) + if err != nil { + utils.LogError(p.logger, err, "failed to record the outgoing message") + return err + } + return nil + } + + m, ok := p.MockManagers.Load(destInfo.AppID) + if !ok { + utils.LogError(p.logger, nil, "failed to fetch the mock manager", zap.Any("AppID", destInfo.AppID)) + return err + } + + //mock the outgoing message + err := p.Integrations[integrations.MYSQL].MockOutgoing(parserCtx, srcConn, &models.ConditionalDstCfg{Addr: dstAddr}, m.(*MockManager), rule.OutgoingOptions) + if err != nil { + utils.LogError(p.logger, err, "failed to mock the outgoing message") + return err + } + return nil + } + + reader := bufio.NewReader(srcConn) + initialData := make([]byte, 5) + // reading the initial data from the client connection to determine if the connection is a TLS handshake + testBuffer, err := reader.Peek(len(initialData)) + if err != nil { + if err == io.EOF && len(testBuffer) == 0 { + p.logger.Debug("received EOF, closing conn", zap.Any("connectionID", clientConnID), zap.Error(err)) + return nil + } + utils.LogError(p.logger, err, "failed to peek the request message in proxy", zap.Any("proxy port", p.Port)) + return err + } + + multiReader := io.MultiReader(reader, srcConn) + srcConn = &util.Conn{ + Conn: srcConn, + Reader: multiReader, + Logger: p.logger, + } + + isTLS := pTls.IsTLSHandshake(testBuffer) + if isTLS { + srcConn, err = pTls.HandleTLSConnection(ctx, p.logger, srcConn, rule.Backdate) + if err != nil { + utils.LogError(p.logger, err, "failed to handle TLS conn") + return err + } + } + + clientID, ok := parserCtx.Value(models.ClientConnectionIDKey).(string) + if !ok { + utils.LogError(p.logger, err, "failed to fetch the client connection id") + return err + } + destID, ok := parserCtx.Value(models.DestConnectionIDKey).(string) + if !ok { + utils.LogError(p.logger, err, "failed to fetch the destination connection id") + return err + } + + logger := p.logger.With(zap.Any("Client ConnectionID", clientID), zap.Any("Destination ConnectionID", destID), zap.Any("Destination IP Address", dstAddr), zap.Any("Client IP Address", srcConn.RemoteAddr().String())) + + var initialBuf []byte + // attempt to read conn until buffer is either filled or conn is closed + initialBuf, err = util.ReadInitialBuf(parserCtx, p.logger, srcConn) + if err != nil { + utils.LogError(logger, err, "failed to read the initial buffer") + return err + } + + if util.IsHTTPReq(initialBuf) && !util.HasCompleteHTTPHeaders(initialBuf) { + // HTTP headers are never chunked according to the HTTP protocol, + // but at the TCP layer, we cannot be sure if we have received the entire + // header in the first buffer chunk. This is why we check if the headers are complete + // and read more data if needed. + + // Some HTTP requests, like AWS SQS, may require special handling in Keploy Enterprise. + // These cases may send partial headers in multiple chunks, so we need to read until + // we get the complete headers. + + logger.Debug("Partial HTTP headers detected, reading more data to get complete headers") + + // Read more data from the TCP connection to get the complete HTTP headers. + headerBuf, err := util.ReadHTTPHeadersUntilEnd(parserCtx, p.logger, srcConn) + if err != nil { + // Log the error if we fail to read the complete HTTP headers. + utils.LogError(logger, err, "failed to read the complete HTTP headers from client") + return err + } + // Append the additional data to the initial buffer. + initialBuf = append(initialBuf, headerBuf...) + } + + //update the src connection to have the initial buffer + srcConn = &util.Conn{ + Conn: srcConn, + Reader: io.MultiReader(bytes.NewReader(initialBuf), srcConn), + Logger: p.logger, + } + + dstCfg := &models.ConditionalDstCfg{ + Port: uint(destInfo.Port), + } + + //make new connection to the destination server + if isTLS { + + // get the destinationUrl from the map for the tls connection + url, ok := pTls.SrcPortToDstURL.Load(sourcePort) + if !ok { + utils.LogError(logger, err, "failed to fetch the destination url") + return err + } + //type case the dstUrl to string + dstURL, ok := url.(string) + if !ok { + utils.LogError(logger, err, "failed to type cast the destination url") + return err + } + + logger.Debug("the external call is tls-encrypted", zap.Any("isTLS", isTLS)) + cfg := &tls.Config{ + InsecureSkipVerify: true, + ServerName: dstURL, + } + + addr := fmt.Sprintf("%v:%v", dstURL, destInfo.Port) + if rule.Mode != models.MODE_TEST { + dstConn, err = tls.Dial("tcp", addr, cfg) + if err != nil { + utils.LogError(logger, err, "failed to dial the conn to destination server", zap.Any("proxy port", p.Port), zap.Any("server address", dstAddr)) + return err + } + } + + dstCfg.TLSCfg = cfg + dstCfg.Addr = addr + + } else { + if rule.Mode != models.MODE_TEST { + dstConn, err = net.Dial("tcp", dstAddr) + if err != nil { + utils.LogError(logger, err, "failed to dial the conn to destination server", zap.Any("proxy port", p.Port), zap.Any("server address", dstAddr)) + return err + } + } + dstCfg.Addr = dstAddr + } + + // get the mock manager for the current app + m, ok := p.MockManagers.Load(destInfo.AppID) + if !ok { + utils.LogError(logger, err, "failed to fetch the mock manager", zap.Any("AppID", destInfo.AppID)) + return err + } + + generic := true + + var matchedParser integrations.Integrations + var parserType integrations.IntegrationType + + //Checking for all the parsers according to their priority. + for _, parserPair := range p.integrationsPriority { // Iterate over ordered priority list + parser, exists := p.Integrations[parserPair.ParserType] + if !exists { + continue // Skip if parser not found + } + + p.logger.Debug("Checking for the parser", zap.Any("ParserType", parserPair.ParserType)) + if parser.MatchType(parserCtx, initialBuf) { + matchedParser = parser + parserType = parserPair.ParserType + generic = false + break + } + } + + if !generic { + p.logger.Debug("The external dependency is supported. Hence using the parser", zap.Any("ParserType", parserType)) + switch rule.Mode { + case models.MODE_RECORD: + err := matchedParser.RecordOutgoing(parserCtx, srcConn, dstConn, rule.MC, rule.OutgoingOptions) + if err != nil { + utils.LogError(logger, err, "failed to record the outgoing message") + return err + } + case models.MODE_TEST: + err := matchedParser.MockOutgoing(parserCtx, srcConn, dstCfg, m.(*MockManager), rule.OutgoingOptions) + if err != nil && err != io.EOF { + utils.LogError(logger, err, "failed to mock the outgoing message") + return err + } + } + } + + if generic { + logger.Debug("The external dependency is not supported. Hence using generic parser") + if rule.Mode == models.MODE_RECORD { + err := p.Integrations[integrations.GENERIC].RecordOutgoing(parserCtx, srcConn, dstConn, rule.MC, rule.OutgoingOptions) + if err != nil { + utils.LogError(logger, err, "failed to record the outgoing message") + return err + } + } else { + err := p.Integrations[integrations.GENERIC].MockOutgoing(parserCtx, srcConn, dstCfg, m.(*MockManager), rule.OutgoingOptions) + if err != nil { + utils.LogError(logger, err, "failed to mock the outgoing message") + return err + } + } + } + return nil +} + +func (p *Proxy) StopProxyServer(ctx context.Context) { + <-ctx.Done() + + p.logger.Info("stopping proxy server...") + + p.connMutex.Lock() + for _, clientConn := range p.clientConnections { + err := clientConn.Close() + if err != nil { + return + } + } + p.connMutex.Unlock() + + if p.Listener != nil { + err := p.Listener.Close() + if err != nil { + utils.LogError(p.logger, err, "failed to stop proxy server") + } + } + + // stop dns servers + err := p.stopDNSServers(ctx) + if err != nil { + utils.LogError(p.logger, err, "failed to stop the dns servers") + return + } + + p.logger.Info("proxy stopped...") +} + +func (p *Proxy) Record(_ context.Context, id uint64, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { + p.sessions.Set(id, &core.Session{ + ID: id, + Mode: models.MODE_RECORD, + MC: mocks, + OutgoingOptions: opts, + }) + + p.MockManagers.Store(id, NewMockManager(NewTreeDb(customComparator), NewTreeDb(customComparator), p.logger)) + + ////set the new proxy ip:port for a new session + //err := p.setProxyIP(opts.DnsIPv4Addr, opts.DnsIPv6Addr) + //if err != nil { + // return errors.New("failed to record the outgoing message") + //} + + return nil +} + +func (p *Proxy) Mock(_ context.Context, id uint64, opts models.OutgoingOptions) error { + p.sessions.Set(id, &core.Session{ + ID: id, + Mode: models.MODE_TEST, + OutgoingOptions: opts, + }) + p.MockManagers.Store(id, NewMockManager(NewTreeDb(customComparator), NewTreeDb(customComparator), p.logger)) + + if !opts.Mocking { + p.logger.Info("🔀 Mocking is disabled, the response will be fetched from the actual service") + } + + if string(p.nsswitchData) == "" { + // setup the nsswitch config to redirect the DNS queries to the proxy + err := p.setupNsswitchConfig() + if err != nil { + utils.LogError(p.logger, err, "failed to setup nsswitch config") + return errors.New("failed to mock the outgoing message") + } + } + + ////set the new proxy ip:port for a new session + //err := p.setProxyIP(opts.DnsIPv4Addr, opts.DnsIPv6Addr) + //if err != nil { + // return errors.New("failed to mock the outgoing message") + //} + + return nil +} + +func (p *Proxy) SetMocks(_ context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error { + //session, ok := p.sessions.Get(id) + //if !ok { + // return fmt.Errorf("session not found") + //} + m, ok := p.MockManagers.Load(id) + if ok { + m.(*MockManager).SetFilteredMocks(filtered) + m.(*MockManager).SetUnFilteredMocks(unFiltered) + } + + return nil +} + +// GetConsumedMocks returns the consumed filtered mocks for a given app id +func (p *Proxy) GetConsumedMocks(_ context.Context, id uint64) ([]models.MockState, error) { + m, ok := p.MockManagers.Load(id) + if !ok { + return nil, fmt.Errorf("mock manager not found to get consumed filtered mocks") + } + return m.(*MockManager).GetConsumedMocks(), nil +} diff --git a/keploy/pkg/core/proxy/tls/asset/ca.crt b/keploy/pkg/core/proxy/tls/asset/ca.crt new file mode 100755 index 0000000..f42e240 --- /dev/null +++ b/keploy/pkg/core/proxy/tls/asset/ca.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBczCCARigAwIBAgIUe1yXSfGefdyA09reeQpit2S7TXMwCgYIKoZIzj0EAwIw +FzEVMBMGA1UEAxMMTXkgQ3VzdG9tIENBMB4XDTIzMDQyMDExMDYwMFoXDTMzMDQx +NzExMDYwMFowFzEVMBMGA1UEAxMMTXkgQ3VzdG9tIENBMFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAE+QJh3KtWSGRe9NtzZGcJQBcn0ipsjzpo7YlLvbaSFeI62H1b +5EPUjnbDInG4K0nO1Dq/gPtnsVXZcJx06oHj9qNCMEAwDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFDejUAabmE7NFoKSOo5uHAHMumal +MAoGCCqGSM49BAMCA0kAMEYCIQDkGU6Oku9vLo7fLLquHWXR1rB/1YgWIBghnQXJ +fF5GawIhAK/0BaJCVm6rkRuYCdtahkN1N5DtTPr8AQoDV+MT0UmL +-----END CERTIFICATE----- diff --git a/keploy/pkg/core/proxy/tls/asset/ca.key b/keploy/pkg/core/proxy/tls/asset/ca.key new file mode 100755 index 0000000..de51e1a --- /dev/null +++ b/keploy/pkg/core/proxy/tls/asset/ca.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHtVJjlhxefhiK/LyFlFUHgaA51mbtTLYJPdGWBZHeDSoAoGCCqGSM49 +AwEHoUQDQgAE+QJh3KtWSGRe9NtzZGcJQBcn0ipsjzpo7YlLvbaSFeI62H1b5EPU +jnbDInG4K0nO1Dq/gPtnsVXZcJx06oHj9g== +-----END EC PRIVATE KEY----- diff --git a/keploy/pkg/core/proxy/tls/asset/setup_ca.sh b/keploy/pkg/core/proxy/tls/asset/setup_ca.sh new file mode 100755 index 0000000..76042a4 --- /dev/null +++ b/keploy/pkg/core/proxy/tls/asset/setup_ca.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +# Path to the CA certificate +caCertPath="./ca.crt" + +# Paths to check for CA store +caStorePaths=( + "/usr/local/share/ca-certificates/" + "/etc/pki/ca-trust/source/anchors/" + "/etc/ca-certificates/trust-source/anchors/" + "/etc/pki/trust/anchors/" + "/usr/local/share/certs/" + "/etc/ssl/certs/" +) +# Commands to tools the CA store +caStoreUpdateCmds=( + "update-ca-certificates" + "update-ca-trust" + "trust extract-compat" + "update-ca-trust extract" + "certctl rehash" +) + +# Java related variables +storePass="changeit" +alias="keployCA" + +# Check if directory exists +directory_exists() { + [[ -d $1 ]] +} + +# Check if command exists +command_exists() { + command -v $1 &> /dev/null +} + +# Check if Java is installed +is_java_installed() { + command_exists "java" +} + +# Update the CA store +update_ca_store() { + for cmd in "${caStoreUpdateCmds[@]}"; do + if command_exists "$cmd"; then + $cmd + fi + done +} + +# Install Java CA +install_java_ca() { + caPath=$1 + + if is_java_installed; then + javaHome=$(java -XshowSettings:properties -version 2>&1 > /dev/null | grep 'java.home' | awk -F'=' '{print $2}' | xargs) + cacertsPath="${javaHome}/lib/security/cacerts" + + keytool -list -keystore $cacertsPath -storepass $storePass -alias $alias &> /dev/null + if [ $? -eq 0 ]; then + echo "Java detected and CA already exists, cacertsPath:$cacertsPath" + return + fi + + keytool -import -trustcacerts -keystore $cacertsPath -storepass $storePass -noprompt -alias $alias -file $caPath + if [ $? -eq 0 ]; then + echo "Java detected and successfully imported CA, path:$cacertsPath" + else + echo "Java detected but failed to import CA" + fi + else + echo "Java is not installed on the system" + fi +} + +# Handle TLS setup +handle_tls_setup() { + for path in "${caStorePaths[@]}"; do + if directory_exists "$path"; then + caPath="${path}ca.crt" + cp $caCertPath $caPath + install_java_ca $caPath + fi + done + update_ca_store + +# Set the NODE_EXTRA_CA_CERTS environment variable + export NODE_EXTRA_CA_CERTS="/tmp/ca.crt" + cat $caCertPath > $NODE_EXTRA_CA_CERTS + +# Change the file permissions to allow read and write access for all users + chmod 0666 $NODE_EXTRA_CA_CERTS + +# Log the NODE_EXTRA_CA_CERTS environment variable + echo "NODE_EXTRA_CA_CERTS is set to: $NODE_EXTRA_CA_CERTS" + + # Set the REQUESTS_CA_BUNDLE to the same value as NODE_EXTRA_CA_CERTS for python + export REQUESTS_CA_BUNDLE=$NODE_EXTRA_CA_CERTS + + # Log the REQUESTS_CA_BUNDLE environment variable + echo "REQUESTS_CA_BUNDLE is set to: $REQUESTS_CA_BUNDLE" + + echo "Setup successful" +} + +# Execute the main function +handle_tls_setup \ No newline at end of file diff --git a/keploy/pkg/core/proxy/tls/ca.go b/keploy/pkg/core/proxy/tls/ca.go new file mode 100644 index 0000000..0220e0a --- /dev/null +++ b/keploy/pkg/core/proxy/tls/ca.go @@ -0,0 +1,345 @@ +//go:build linux + +// Package tls provides functionality for handling tls connetions. +package tls + +import ( + "context" + "crypto" + "crypto/tls" + "crypto/x509" + "embed" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "sync" + "time" + + "github.com/cloudflare/cfssl/csr" + cfsslLog "github.com/cloudflare/cfssl/log" + "github.com/cloudflare/cfssl/signer" + "github.com/cloudflare/cfssl/signer/local" + "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +//go:embed asset/ca.crt +var caCrt []byte //certificate + +//go:embed asset/ca.key +var caPKey []byte //private key + +//go:embed asset +var _ embed.FS + +var caStorePath = []string{ + "/usr/local/share/ca-certificates/", + "/etc/pki/ca-trust/source/anchors/", + "/etc/ca-certificates/trust-source/anchors/", + "/etc/pki/trust/anchors/", + "/usr/local/share/certs/", + "/etc/ssl/certs/", +} + +var caStoreUpdateCmd = []string{ + "update-ca-certificates", + "update-ca-trust", + "trust extract-compat", + "tools-ca-trust extract", + "certctl rehash", +} + +func commandExists(cmd string) bool { + _, err := exec.LookPath(cmd) + return err == nil +} + +func updateCaStore(ctx context.Context) error { + commandRun := false + for _, cmd := range caStoreUpdateCmd { + if commandExists(cmd) { + commandRun = true + c := exec.CommandContext(ctx, cmd) + _, err := c.CombinedOutput() + if err != nil { + select { + case <-ctx.Done(): + return ctx.Err() + default: + return err + } + } + } + } + if !commandRun { + return fmt.Errorf("no valid CA store tools command found") + } + return nil +} + +func getCaPaths() ([]string, error) { + var caPaths []string + for _, dir := range caStorePath { + if util.IsDirectoryExist(dir) { + caPaths = append(caPaths, dir) + } + } + if len(caPaths) == 0 { + return nil, fmt.Errorf("no valid CA store path found") + } + return caPaths, nil +} + +// to extract ca certificate to temp +func extractCertToTemp() (string, error) { + tempFile, err := os.CreateTemp("", "ca.crt") + + if err != nil { + return "", err + } + defer func(tempFile *os.File) { + err := tempFile.Close() + if err != nil { + return + } + }(tempFile) + + // Change the file permissions to allow read access for all users + err = os.Chmod(tempFile.Name(), 0666) + if err != nil { + return "", err + } + + // Write to the file + _, err = tempFile.Write(caCrt) + if err != nil { + return "", err + } + + // Close the file + err = tempFile.Close() + if err != nil { + return "", err + } + return tempFile.Name(), nil +} + +// isJavaCAExist checks if the CA is already installed in the specified Java keystore +func isJavaCAExist(ctx context.Context, alias, storepass, cacertsPath string) bool { + cmd := exec.CommandContext(ctx, "keytool", "-list", "-keystore", cacertsPath, "-storepass", storepass, "-alias", alias) + + err := cmd.Run() + select { + case <-ctx.Done(): + return false + default: + } + return err == nil +} + +// installJavaCA installs the CA in the Java keystore +func installJavaCA(ctx context.Context, logger *zap.Logger, caPath string) error { + // check if java is installed + if util.IsJavaInstalled() { + logger.Debug("checking java path from default java home") + javaHome, err := util.GetJavaHome(ctx) + + if err != nil { + utils.LogError(logger, err, "Java detected but failed to find JAVA_HOME") + return err + } + + // Assuming modern Java structure (without /jre/) + cacertsPath := fmt.Sprintf("%s/lib/security/cacerts", javaHome) + // You can modify these as per your requirements + storePass := "changeit" + alias := "keployCA" + + logger.Debug("", zap.Any("java_home", javaHome), zap.Any("caCertsPath", cacertsPath), zap.Any("caPath", caPath)) + + if isJavaCAExist(ctx, alias, storePass, cacertsPath) { + logger.Debug("Java detected and CA already exists", zap.String("path", cacertsPath)) + return nil + } + + cmd := exec.CommandContext(ctx, "keytool", "-import", "-trustcacerts", "-keystore", cacertsPath, "-storepass", storePass, "-noprompt", "-alias", alias, "-file", caPath) + cmdOutput, err := cmd.CombinedOutput() + + if err != nil { + select { + case <-ctx.Done(): + return ctx.Err() + default: + utils.LogError(logger, err, "Java detected but failed to import CA", zap.String("output", string(cmdOutput))) + return err + } + } + + logger.Debug("Java detected and successfully imported CA", zap.String("path", cacertsPath), zap.String("output", string(cmdOutput))) + logger.Debug("Successfully imported CA", zap.Any("", cmdOutput)) + } else { + logger.Debug("Java is not installed on the system") + } + return nil +} + +// TODO: This function should be used even before starting the proxy server. It should be called just after the keploy is started. +// because the custom ca in case of NODE is set via env variable NODE_EXTRA_CA_CERTS and env variables can be set only on startup. +// As in case of unit test integration, we are starting the proxy via api. + +// SetupCA setups custom certificate authority to handle TLS connections +func SetupCA(ctx context.Context, logger *zap.Logger) error { + caPaths, err := getCaPaths() + if err != nil { + utils.LogError(logger, err, "Failed to find the CA store path") + return err + } + + for _, path := range caPaths { + caPath := filepath.Join(path, "ca.crt") + + fs, err := os.Create(caPath) + if err != nil { + utils.LogError(logger, err, "Failed to create path for ca certificate", zap.Any("root store path", path)) + return err + } + + _, err = fs.Write(caCrt) + if err != nil { + utils.LogError(logger, err, "Failed to write custom ca certificate", zap.Any("root store path", path)) + return err + } + + // install CA in the java keystore if java is installed + err = installJavaCA(ctx, logger, caPath) + if err != nil { + utils.LogError(logger, err, "Failed to install CA in the java keystore") + return err + } + } + + // Update the trusted CAs store + err = updateCaStore(ctx) + if err != nil { + utils.LogError(logger, err, "Failed to update the CA store") + return err + } + + tempCertPath, err := extractCertToTemp() + if err != nil { + utils.LogError(logger, err, "Failed to extract certificate to tmp folder") + return err + } + + // for node + err = os.Setenv("NODE_EXTRA_CA_CERTS", tempCertPath) + if err != nil { + utils.LogError(logger, err, "Failed to set environment variable NODE_EXTRA_CA_CERTS") + return err + } + + // for python + err = os.Setenv("REQUESTS_CA_BUNDLE", tempCertPath) + if err != nil { + utils.LogError(logger, err, "Failed to set environment variable REQUESTS_CA_BUNDLE") + return err + } + return nil +} + +// SrcPortToDstURL map is used to store the mapping between source port and DstURL for the TLS connection +var SrcPortToDstURL = sync.Map{} + +var setLogLevelOnce sync.Once + +func CertForClient(logger *zap.Logger, clientHello *tls.ClientHelloInfo, caPrivKey any, caCertParsed *x509.Certificate, backdate time.Time) (*tls.Certificate, error) { + + // Ensure log level is set only once + + /* + * Since multiple goroutines can call this function concurrently, we need to ensure that the log level is set only once. + */ + setLogLevelOnce.Do(func() { + + // * Set the log level to error to avoid unnecessary logs. like below... + + // 2025/03/18 20:54:25 [INFO] received CSR + // 2025/03/18 20:54:25 [INFO] generating key: ecdsa-256 + // 2025/03/18 20:54:25 [INFO] received CSR + // 2025/03/18 20:54:25 [INFO] generating key: ecdsa-256 + // 2025/03/18 20:54:25 [INFO] encoded CSR + // 2025/03/18 20:54:25 [INFO] encoded CSR + // 2025/03/18 20:54:25 [INFO] signed certificate with serial number 435398774381835435678674951099961010543769077102 + + cfsslLog.Level = cfsslLog.LevelError + }) + + // Generate a new server certificate and private key for the given hostname + dstURL := clientHello.ServerName + + remoteAddr := clientHello.Conn.RemoteAddr().(*net.TCPAddr) + sourcePort := remoteAddr.Port + + SrcPortToDstURL.Store(sourcePort, dstURL) + + serverReq := &csr.CertificateRequest{ + //Make the name accordng to the ip of the request + CN: clientHello.ServerName, + Hosts: []string{ + clientHello.ServerName, + }, + KeyRequest: csr.NewKeyRequest(), + } + + serverCsr, serverKey, err := csr.ParseRequest(serverReq) + if err != nil { + return nil, fmt.Errorf("failed to create server CSR: %v", err) + } + cryptoSigner, ok := caPrivKey.(crypto.Signer) + if !ok { + return nil, fmt.Errorf("failed to typecast the caPrivKey") + } + signerd, err := local.NewSigner(cryptoSigner, caCertParsed, signer.DefaultSigAlgo(cryptoSigner), nil) + if err != nil { + return nil, fmt.Errorf("failed to create signer: %v", err) + } + + if backdate.IsZero() { + logger.Debug("backdate is zero, using current time") + backdate = time.Now() + } + + // Case: time freezing (an Ent. feature) is enabled, + // If application time is frozen in past, and the certificate is signed today, then the certificate will be invalid. + // This results in a certificate error during tls handshake. + // To avoid this, we set the certificate’s validity period (NotBefore and NotAfter) + // by referencing the testcase request time of the application (backdate) instead of the current real time. + // + // Note: If you have recorded test cases before April 20, 2024 (http://www.sslchecker.com/certdecoder?su=269725513dfeb137f6f29b8488f17ca9) + // and are using time freezing, please reach out to us if you get tls handshake error. + signReq := signer.SignRequest{ + Hosts: serverReq.Hosts, + Request: string(serverCsr), + Profile: "web", + NotBefore: backdate.AddDate(-1, 0, 0), + NotAfter: time.Now().AddDate(1, 0, 0), + } + + serverCert, err := signerd.Sign(signReq) + if err != nil { + return nil, fmt.Errorf("failed to sign server certificate: %v", err) + } + + logger.Debug("signed the certificate for a duration of 2 years", zap.Any("notBefore", signReq.NotBefore.String()), zap.Any("notAfter", signReq.NotAfter.String())) + + // Load the server certificate and private key + serverTLSCert, err := tls.X509KeyPair(serverCert, serverKey) + if err != nil { + return nil, fmt.Errorf("failed to load server certificate and key: %v", err) + } + + return &serverTLSCert, nil +} diff --git a/keploy/pkg/core/proxy/tls/tls.go b/keploy/pkg/core/proxy/tls/tls.go new file mode 100644 index 0000000..8c019f8 --- /dev/null +++ b/keploy/pkg/core/proxy/tls/tls.go @@ -0,0 +1,58 @@ +//go:build linux + +package tls + +import ( + "context" + "crypto/tls" + "net" + "time" + + "github.com/cloudflare/cfssl/helpers" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func IsTLSHandshake(data []byte) bool { + if len(data) < 5 { + return false + } + return data[0] == 0x16 && data[1] == 0x03 && (data[2] == 0x00 || data[2] == 0x01 || data[2] == 0x02 || data[2] == 0x03) +} + +func HandleTLSConnection(_ context.Context, logger *zap.Logger, conn net.Conn, backdate time.Time) (net.Conn, error) { + //Load the CA certificate and private key + + caPrivKey, err := helpers.ParsePrivateKeyPEM(caPKey) + if err != nil { + utils.LogError(logger, err, "Failed to parse CA private key") + return nil, err + } + caCertParsed, err := helpers.ParseCertificatePEM(caCrt) + if err != nil { + utils.LogError(logger, err, "Failed to parse CA certificate") + return nil, err + } + + // Create a TLS configuration + config := &tls.Config{ + GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { + return CertForClient(logger, clientHello, caPrivKey, caCertParsed, backdate) + }, + } + + // Wrap the TCP conn with TLS + tlsConn := tls.Server(conn, config) + // Perform the handshake + err = tlsConn.Handshake() + + if err != nil { + utils.LogError(logger, err, "failed to complete TLS handshake with the client") + return nil, err + } + // Use the tlsConn for further communication + // For example, you can read and write data using tlsConn.Read() and tlsConn.Write() + + // Here, we simply close the conn + return tlsConn, nil +} diff --git a/keploy/pkg/core/proxy/treedb.go b/keploy/pkg/core/proxy/treedb.go new file mode 100644 index 0000000..513d01a --- /dev/null +++ b/keploy/pkg/core/proxy/treedb.go @@ -0,0 +1,88 @@ +//go:build linux + +package proxy + +// treeDb is a simple wrapper around redblacktree to provide thread safety +// Here it is used to handle the mocks. + +import ( + "sync" + + "github.com/emirpasic/gods/trees/redblacktree" + "go.keploy.io/server/v2/pkg/models" +) + +// customComparator is a custom comparator function for the tree db +var customComparator = func(a, b interface{}) int { + aStruct := a.(models.TestModeInfo) + bStruct := b.(models.TestModeInfo) + if aStruct.SortOrder < bStruct.SortOrder { + return -1 + } else if aStruct.SortOrder > bStruct.SortOrder { + return 1 + } + if aStruct.ID < bStruct.ID { + return -1 + } else if aStruct.ID > bStruct.ID { + return 1 + } + return 0 +} + +type TreeDb struct { + rbt *redblacktree.Tree + mu sync.RWMutex // RWMutex: many reads, few writes +} + +func NewTreeDb(comparator func(a, b interface{}) int) *TreeDb { + return &TreeDb{ + rbt: redblacktree.NewWith(comparator), + } +} + +func (db *TreeDb) insert(key interface{}, obj interface{}) { + db.mu.Lock() + db.rbt.Put(key, obj) + db.mu.Unlock() +} + +func (db *TreeDb) delete(key interface{}) bool { + db.mu.Lock() + defer db.mu.Unlock() + _, found := db.rbt.Get(key) + if !found { + return false + } + db.rbt.Remove(key) + return true +} + +func (db *TreeDb) update(oldKey interface{}, newKey interface{}, newObj interface{}) bool { + db.mu.Lock() + defer db.mu.Unlock() + _, found := db.rbt.Get(oldKey) + if !found { + return false + } + db.rbt.Remove(oldKey) + db.rbt.Put(newKey, newObj) + return true +} + +func (db *TreeDb) deleteAll() { + db.mu.Lock() + db.rbt.Clear() + db.mu.Unlock() +} + +// rangeValues iterates without allocating a []interface{} snapshot. +func (db *TreeDb) rangeValues(fn func(v interface{}) bool) { + db.mu.RLock() + it := db.rbt.Iterator() + for it.Next() { + if !fn(it.Value()) { + break + } + } + db.mu.RUnlock() +} diff --git a/keploy/pkg/core/proxy/util.go b/keploy/pkg/core/proxy/util.go new file mode 100644 index 0000000..e6d1359 --- /dev/null +++ b/keploy/pkg/core/proxy/util.go @@ -0,0 +1,75 @@ +//go:build linux + +package proxy + +import ( + "context" + "fmt" + "io" + "net" + "os" + + pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// writeNsswitchConfig writes the content to nsswitch.conf file +func writeNsswitchConfig(logger *zap.Logger, nsSwitchConfig string, data []byte, perm os.FileMode) error { + + err := os.WriteFile(nsSwitchConfig, data, perm) + if err != nil { + logger.Error("failed to write the configuration to the nsswitch.conf file to redirect the DNS queries to proxy", zap.Error(err)) + return err + } + return nil +} + +func (p *Proxy) globalPassThrough(ctx context.Context, client, dest net.Conn) error { + + logger := p.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", client.RemoteAddr().String())) + + clientBuffChan := make(chan []byte) + destBuffChan := make(chan []byte) + errChan := make(chan error, 2) + + // read requests from client + err := pUtil.ReadFromPeer(ctx, logger, client, clientBuffChan, errChan, pUtil.Client) + if err != nil { + return fmt.Errorf("error reading from client:%v", err) + } + + // read responses from destination + err = pUtil.ReadFromPeer(ctx, logger, dest, destBuffChan, errChan, pUtil.Destination) + if err != nil { + return fmt.Errorf("error reading from destination:%v", err) + } + + //write the request or response buffer to the respective destination + for { + select { + case <-ctx.Done(): + return ctx.Err() + case buffer := <-clientBuffChan: + // Write the request message to the destination + _, err := dest.Write(buffer) + if err != nil { + utils.LogError(logger, err, "failed to write request message to the destination server") + return fmt.Errorf("error writing to destination") + } + case buffer := <-destBuffChan: + // Write the response message to the client + _, err := client.Write(buffer) + if err != nil { + utils.LogError(logger, err, "failed to write response message to the client") + return fmt.Errorf("error writing to client") + } + case err := <-errChan: + if err == io.EOF { + return nil + } + return err + } + } +} diff --git a/keploy/pkg/core/proxy/util/util.go b/keploy/pkg/core/proxy/util/util.go new file mode 100755 index 0000000..2bd3d33 --- /dev/null +++ b/keploy/pkg/core/proxy/util/util.go @@ -0,0 +1,648 @@ +// Package util provides utility functions for the proxy package. +package util + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/binary" + "errors" + "fmt" + "io" + "os" + "os/exec" + "sync" + "sync/atomic" + "time" + + "github.com/getsentry/sentry-go" + "go.keploy.io/server/v2/pkg/models" + "golang.org/x/sync/errgroup" + + "go.keploy.io/server/v2/utils" + + "go.uber.org/zap" + + // "math/rand" + "net" + "strconv" + "strings" +) + +var Emoji = "\U0001F430" + " Keploy:" + +// idCounter is used to generate random ID for each request +var idCounter int64 = -1 + +func GetNextID() int64 { + return atomic.AddInt64(&idCounter, 1) +} + +// Conn is helpful for multiple reads from the same connection +type Conn struct { + net.Conn + Reader io.Reader + Logger *zap.Logger + mu sync.Mutex +} + +func (c *Conn) Read(p []byte) (int, error) { + c.mu.Lock() + defer c.mu.Unlock() + if len(p) == 0 { + c.Logger.Debug("the length is 0 for the reading from customConn") + } + return c.Reader.Read(p) +} + +type Peer string + +// Peer constants +const ( + Client Peer = "client" + Destination Peer = "destination" +) + +func HasCompleteHTTPHeaders(buf []byte) bool { + + // Check for the presence of the end of headers sequence "\r\n\r\n" + endOfHeaders := []byte("\r\n\r\n") + if len(buf) < len(endOfHeaders) { + return false + } + + // Check if the buffer contains the end of headers sequence + return bytes.Contains(buf, endOfHeaders) +} + +func IsHTTPReq(buf []byte) bool { + isHTTP := bytes.HasPrefix(buf[:], []byte("HTTP/")) || + bytes.HasPrefix(buf[:], []byte("GET ")) || + bytes.HasPrefix(buf[:], []byte("POST ")) || + bytes.HasPrefix(buf[:], []byte("PUT ")) || + bytes.HasPrefix(buf[:], []byte("PATCH ")) || + bytes.HasPrefix(buf[:], []byte("DELETE ")) || + bytes.HasPrefix(buf[:], []byte("OPTIONS ")) || + bytes.HasPrefix(buf[:], []byte("HEAD ")) || + bytes.HasPrefix(buf[:], []byte("CONNECT ")) + + return isHTTP +} + +// ReadBuffConn is used to read the buffer from the connection +func ReadBuffConn(ctx context.Context, logger *zap.Logger, conn net.Conn, bufferChannel chan []byte, errChannel chan error) { + //TODO: where to close the errChannel + for { + select { + case <-ctx.Done(): + // errChannel <- ctx.Err() + return + default: + if conn == nil { + logger.Debug("the conn is nil") + } + buffer, err := ReadBytes(ctx, logger, conn) + if err != nil { + if ctx.Err() != nil { // to avoid sending buffer to closed channel if the context is cancelled + return + } + if err != io.EOF { + utils.LogError(logger, err, "failed to read the packet message in proxy") + } + errChannel <- err + return + } + if ctx.Err() != nil { // to avoid sending buffer to closed channel if the context is cancelled + return + } + bufferChannel <- buffer + } + } +} + +func ReadHTTPHeadersUntilEnd(ctx context.Context, logger *zap.Logger, conn net.Conn) ([]byte, error) { + readErr := errors.New("failed to read HTTP headers") + + // Read the incoming data (headers) + initialBuf, err := ReadBytes(ctx, logger, conn) + + // Early return if we receive EOF with no data + if err == io.EOF && len(initialBuf) == 0 { + logger.Debug("received EOF, closing conn", zap.Error(err)) + return nil, readErr + } + + // Handle errors other than EOF + if err != nil && err != io.EOF { + utils.LogError(logger, err, "failed to read HTTP headers") + return nil, readErr + } + + // Check if the initial buffer already contains complete headers + if HasCompleteHTTPHeaders(initialBuf) { + logger.Debug("received complete HTTP headers in initial buffer", zap.Any("size", len(initialBuf)), zap.Any("headers", string(initialBuf))) + return initialBuf, nil + } + + // If not, continue reading until we find the end of headers + var buffer []byte + buffer = append(buffer, initialBuf...) + + for { + select { + case <-ctx.Done(): + return buffer, ctx.Err() + default: + // Check if the connection is nil + if conn == nil { + logger.Debug("the conn is nil") + return nil, readErr + } + // Read more data until we find the header end sequence + part, err := ReadBytes(ctx, logger, conn) + if err != nil { + if err == io.EOF && len(part) == 0 { + break // EOF reached, but nothing more to read + } + utils.LogError(logger, err, "error while reading HTTP headers") + return nil, readErr + } + + // Append the new data to the buffer + buffer = append(buffer, part...) + + // Check if we reached the end of headers + if HasCompleteHTTPHeaders(buffer) { + logger.Debug("received complete HTTP headers", zap.Any("size", len(buffer)), zap.Any("headers", string(buffer))) + return buffer, nil + } + } + } +} + +func ReadInitialBuf(ctx context.Context, logger *zap.Logger, conn net.Conn) ([]byte, error) { + readErr := errors.New("failed to read the initial request buffer") + + initialBuf, err := ReadBytes(ctx, logger, conn) + + if err == io.EOF && len(initialBuf) == 0 { + logger.Debug("received EOF, closing conn", zap.Error(err)) + return nil, readErr + } + + if err != nil && err != io.EOF { + utils.LogError(logger, err, "failed to read the request message in proxy") + return nil, readErr + } + + logger.Debug("received initial buffer", zap.Any("size", len(initialBuf)), zap.Any("initial buffer", initialBuf)) + return initialBuf, nil +} + +// ReadBytes function is utilized to read the complete message from the reader until the end of the file (EOF). +// It returns the content as a byte array. +func ReadBytes(ctx context.Context, logger *zap.Logger, reader io.Reader) ([]byte, error) { + var buffer []byte + const maxEmptyReads = 5 + emptyReads := 0 + + // Channel to communicate read results + readResult := make(chan struct { + n int + err error + buf []byte + }) + + g, ctx := errgroup.WithContext(ctx) + + defer func() { + err := g.Wait() + if err != nil { + utils.LogError(logger, err, "failed to read the request message in proxy") + } + close(readResult) + }() + + for { + // Start a goroutine to perform the read operation + g.Go(func() error { + defer Recover(logger, nil, nil) + buf := make([]byte, 1024) + n, err := reader.Read(buf) + if ctx.Err() != nil { + return nil + } + readResult <- struct { + n int + err error + buf []byte + }{n, err, buf} + return nil + }) + + // Use a select statement to wait for either the read result or context cancellation + select { + case <-ctx.Done(): + return buffer, ctx.Err() + case result := <-readResult: + if result.n > 0 { + buffer = append(buffer, result.buf[:result.n]...) + emptyReads = 0 // Reset the counter because we got some data + } + + if result.err != nil { + if result.err == io.EOF { + emptyReads++ + if emptyReads >= maxEmptyReads { + return buffer, result.err // Multiple EOFs in a row, probably a true EOF + } + time.Sleep(time.Millisecond * 100) // Sleep before trying again + continue + } + return buffer, result.err + } + if result.n < len(result.buf) { + return buffer, nil + } + } + } +} + +// ReadRequiredBytes ReadBytes function is utilized to read the required number of bytes from the reader. +// It returns the content as a byte array. +func ReadRequiredBytes(ctx context.Context, logger *zap.Logger, reader io.Reader, numBytes int) ([]byte, error) { + var buffer []byte + const maxEmptyReads = 5 + emptyReads := 0 + + // Channel to communicate read results + readResult := make(chan struct { + n int + err error + buf []byte + }) + + g, ctx := errgroup.WithContext(ctx) + + defer func() { + err := g.Wait() + if err != nil { + utils.LogError(logger, err, "failed to read the request message in proxy") + } + close(readResult) + }() + + for numBytes > 0 { + // Start a goroutine to perform the read operation + g.Go(func() error { + defer Recover(logger, nil, nil) + buf := make([]byte, numBytes) + n, err := reader.Read(buf) + if ctx.Err() != nil { + return nil + } + readResult <- struct { + n int + err error + buf []byte + }{n, err, buf} + return nil + }) + + // Use a select statement to wait for either the read result or context cancellation with timeout + select { + case <-ctx.Done(): + return buffer, ctx.Err() + // case <-time.After(5 * time.Second): + // logger.Error("timeout occurred while reading the packet") + // return buffer, context.DeadlineExceeded + case result := <-readResult: + if result.n > 0 { + buffer = append(buffer, result.buf[:result.n]...) + numBytes -= result.n + emptyReads = 0 // Reset the counter because we got some data + } + + if result.err != nil { + if result.err == io.EOF { + emptyReads++ + if emptyReads >= maxEmptyReads { + return buffer, result.err // Multiple EOFs in a row, probably a true EOF + } + time.Sleep(time.Millisecond * 100) // Sleep before trying again + continue + } + return buffer, result.err + } + + if numBytes == 0 { + return buffer, nil + } + } + } + + return buffer, nil +} + +// ReadFromPeer function is used to read the buffer from the peer connection. The peer can be either the client or the destination. +func ReadFromPeer(ctx context.Context, logger *zap.Logger, conn net.Conn, buffChan chan []byte, errChan chan error, peer Peer) error { + //get the error group from the context + g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) + if !ok { + return errors.New("failed to get the error group from the context while reading from peer") + } + + var client, dest net.Conn + + if peer == Client { + client = conn + } else { + dest = conn + } + + g.Go(func() error { + defer Recover(logger, client, dest) + defer close(buffChan) + ReadBuffConn(ctx, logger, conn, buffChan, errChan) + return nil + }) + + return nil +} + +// PassThrough function is used to pass the network traffic to the destination connection. +// It also closes the destination connection if the function returns an error. +func PassThrough(ctx context.Context, logger *zap.Logger, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, requestBuffer [][]byte) ([]byte, error) { + logger.Debug("passing through the network traffic to the destination server", zap.Any("Destination Addr", dstCfg.Addr)) + // making destConn + var destConn net.Conn + var err error + if dstCfg.TLSCfg != nil { + logger.Debug("trying to establish a TLS connection with the destination server", zap.Any("Destination Addr", dstCfg.Addr)) + + destConn, err = tls.Dial("tcp", dstCfg.Addr, dstCfg.TLSCfg) + if err != nil { + utils.LogError(logger, err, "failed to dial the conn to destination server", zap.Any("server address", dstCfg.Addr)) + return nil, err + } + logger.Debug("TLS connection established with the destination server", zap.Any("Destination Addr", destConn.RemoteAddr().String())) + } else { + logger.Debug("trying to establish a connection with the destination server", zap.Any("Destination Addr", dstCfg.Addr)) + destConn, err = net.Dial("tcp", dstCfg.Addr) + if err != nil { + utils.LogError(logger, err, "failed to dial the destination server") + return nil, err + } + logger.Debug("connection established with the destination server", zap.Any("Destination Addr", destConn.RemoteAddr().String())) + } + + logger.Debug("trying to forward requests to target", zap.Any("Destination Addr", destConn.RemoteAddr().String())) + for _, v := range requestBuffer { + _, err := destConn.Write(v) + if err != nil { + utils.LogError(logger, err, "failed to write request message to the destination server", zap.Any("Destination Addr", destConn.RemoteAddr().String())) + return nil, err + } + } + + // channels for writing messages from proxy to destination or client + destBufferChannel := make(chan []byte) + errChannel := make(chan error, 1) + + go func() { + defer Recover(logger, clientConn, nil) + defer close(destBufferChannel) + defer close(errChannel) + defer func(destConn net.Conn) { + err := destConn.Close() + if err != nil { + utils.LogError(logger, err, "failed to close the destination connection") + } + }(destConn) + + ReadBuffConn(ctx, logger, destConn, destBufferChannel, errChannel) + }() + + select { + case buffer := <-destBufferChannel: + // Write the response message to the client + _, err := clientConn.Write(buffer) + if err != nil { + if ctx.Err() != nil { + return nil, ctx.Err() + } + utils.LogError(logger, err, "failed to write response to the client") + return nil, err + } + + logger.Debug("the iteration for the generic response ends with responses:"+strconv.Itoa(len(buffer)), zap.Any("buffer", buffer)) + case err := <-errChannel: + // Applied this nolint to ignore the staticcheck error here because of readability + // nolint:staticcheck + if netErr, ok := err.(net.Error); !(ok && netErr.Timeout()) && err != nil { + return nil, err + } + return nil, nil + + case <-ctx.Done(): + return nil, ctx.Err() + } + + return nil, nil +} + +// ToIP4AddressStr converts the integer IP4 Address to the octet format +func ToIP4AddressStr(ip uint32) string { + // convert the IP address to a 32-bit binary number + ipBinary := fmt.Sprintf("%032b", ip) + + // divide the binary number into four 8-bit segments + firstByte, _ := strconv.ParseUint(ipBinary[0:8], 2, 64) + secondByte, _ := strconv.ParseUint(ipBinary[8:16], 2, 64) + thirdByte, _ := strconv.ParseUint(ipBinary[16:24], 2, 64) + fourthByte, _ := strconv.ParseUint(ipBinary[24:32], 2, 64) + + // concatenate the four decimal segments with a dot separator to form the dot-decimal string + return fmt.Sprintf("%d.%d.%d.%d", firstByte, secondByte, thirdByte, fourthByte) +} + +func ToIPv6AddressStr(ip [4]uint32) string { + // construct a byte slice + ipBytes := make([]byte, 16) // IPv6 address is 128 bits or 16 bytes long + for i := 0; i < 4; i++ { + // for each uint32, extract its four bytes and put them into the byte slice + ipBytes[i*4] = byte(ip[i] >> 24) + ipBytes[i*4+1] = byte(ip[i] >> 16) + ipBytes[i*4+2] = byte(ip[i] >> 8) + ipBytes[i*4+3] = byte(ip[i]) + } + // net.IP is a byte slice, so it can be directly used to construct an IPv6 address + ipv6Addr := net.IP(ipBytes) + return ipv6Addr.String() +} + +func GetLocalIPv4() (net.IP, error) { + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + + for _, iface := range ifaces { + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + + for _, addr := range addrs { + ipNet, ok := addr.(*net.IPNet) + if ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil { + return ipNet.IP, nil + } + } + } + + return nil, fmt.Errorf("no valid IP address found") +} + +func ToIPV4(ip net.IP) (uint32, bool) { + ipv4 := ip.To4() + if ipv4 == nil { + return 0, false // Return 0 or handle the error accordingly + } + + return uint32(ipv4[0])<<24 | uint32(ipv4[1])<<16 | uint32(ipv4[2])<<8 | uint32(ipv4[3]), true +} + +func GetLocalIPv6() (net.IP, error) { + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + + for _, iface := range ifaces { + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + + for _, addr := range addrs { + ipNet, ok := addr.(*net.IPNet) + if ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() == nil && ipNet.IP.To16() != nil { + return ipNet.IP, nil + } + } + } + + return nil, fmt.Errorf("no valid IPv6 address found") +} + +func IPv6ToUint32Array(ip net.IP) ([4]uint32, error) { + ip = ip.To16() + if ip == nil { + return [4]uint32{}, errors.New("invalid IPv6 address") + } + + return [4]uint32{ + binary.BigEndian.Uint32(ip[0:4]), + binary.BigEndian.Uint32(ip[4:8]), + binary.BigEndian.Uint32(ip[8:12]), + binary.BigEndian.Uint32(ip[12:16]), + }, nil +} + +func IPToDotDecimal(ip net.IP) string { + ipStr := ip.String() + if ip.To4() != nil { + ipStr = ip.To4().String() + } + return ipStr +} + +func IsDirectoryExist(path string) bool { + info, err := os.Stat(path) + if os.IsNotExist(err) { + return false + } + return info.IsDir() +} + +func IsJava(input string) bool { + // Convert the input string and the search term "java" to lowercase for a case-insensitive comparison. + inputLower := strings.ToLower(input) + searchTerm := "java" + searchTermLower := strings.ToLower(searchTerm) + + // Use strings.Contains to check if the lowercase input contains the lowercase search term. + return strings.Contains(inputLower, searchTermLower) +} + +// IsJavaInstalled checks if java is installed on the system +func IsJavaInstalled() bool { + _, err := exec.LookPath("java") + return err == nil +} + +// GetJavaHome returns the JAVA_HOME path +func GetJavaHome(ctx context.Context) (string, error) { + cmd := exec.CommandContext(ctx, "java", "-XshowSettings:properties", "-version") + var out bytes.Buffer + cmd.Stderr = &out // The output we need is printed to STDERR + + err := cmd.Run() + if err != nil { + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + return "", err + } + } + + for _, line := range strings.Split(out.String(), "\n") { + if strings.Contains(line, "java.home") { + parts := strings.Split(line, "=") + if len(parts) > 1 { + return strings.TrimSpace(parts[1]), nil + } + } + } + + return "", fmt.Errorf("java.home not found in command output") +} + +// Recover recovers from a panic in any parser and logs the stack trace to Sentry. +// It also closes the client and destination connection. +func Recover(logger *zap.Logger, client, dest net.Conn) { + if logger == nil { + fmt.Println(Emoji + "Failed to recover from panic. Logger is nil.") + return + } + + sentry.Flush(2 * time.Second) + if r := recover(); r != nil { + logger.Error("Recovered from panic in parser, closing active connections") + if client != nil { + err := client.Close() + if err != nil { + // Use string matching as a last resort to check for the specific error + if !strings.Contains(err.Error(), "use of closed network connection") { + // Log other errors + utils.LogError(logger, err, "failed to close the client connection") + } + } + } + + if dest != nil { + err := dest.Close() + if err != nil { + // Use string matching as a last resort to check for the specific error + if !strings.Contains(err.Error(), "use of closed network connection") { + // Log other errors + utils.LogError(logger, err, "failed to close the destination connection") + } + } + } + utils.HandleRecovery(logger, r, "Recovered from panic") + sentry.Flush(time.Second * 2) + } +} diff --git a/keploy/pkg/core/record.go b/keploy/pkg/core/record.go new file mode 100644 index 0000000..471ebf4 --- /dev/null +++ b/keploy/pkg/core/record.go @@ -0,0 +1,24 @@ +//go:build linux + +package core + +import ( + "context" + + "go.keploy.io/server/v2/pkg/models" +) + +func (c *Core) GetIncoming(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) { + return c.Hooks.Record(ctx, id, opts) +} + +func (c *Core) GetOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) (<-chan *models.Mock, error) { + m := make(chan *models.Mock, 500) + + err := c.Proxy.Record(ctx, id, m, opts) + if err != nil { + return nil, err + } + + return m, nil +} diff --git a/keploy/pkg/core/replay.go b/keploy/pkg/core/replay.go new file mode 100644 index 0000000..e1a4d68 --- /dev/null +++ b/keploy/pkg/core/replay.go @@ -0,0 +1,19 @@ +//go:build linux + +package core + +import ( + "context" + + "go.keploy.io/server/v2/pkg/models" +) + +func (c *Core) MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error { + + err := c.Mock(ctx, id, opts) + if err != nil { + return err + } + + return nil +} diff --git a/keploy/pkg/core/service.go b/keploy/pkg/core/service.go new file mode 100644 index 0000000..31f941d --- /dev/null +++ b/keploy/pkg/core/service.go @@ -0,0 +1,142 @@ +//go:build linux + +package core + +import ( + "context" + "sync" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/core/app" + "go.keploy.io/server/v2/pkg/core/hooks/structs" + "go.keploy.io/server/v2/utils" + + "go.keploy.io/server/v2/pkg/models" +) + +type Hooks interface { + AppInfo + DestInfo + OutgoingInfo + Load(ctx context.Context, id uint64, cfg HookCfg) error + Record(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) + GetUnloadDone() <-chan struct{} +} + +type HookCfg struct { + AppID uint64 + Pid uint32 + IsDocker bool + KeployIPV4 string + Mode models.Mode + Rules []config.BypassRule + E2E bool + Port uint32 +} + +type App interface { + Setup(ctx context.Context, opts app.Options) error + Run(ctx context.Context, inodeChan chan uint64, opts app.Options) error + Kind(ctx context.Context) utils.CmdType + KeployIPv4Addr() string +} + +// Proxy listens on all available interfaces and forwards traffic to the destination +type Proxy interface { + StartProxy(ctx context.Context, opts ProxyOptions) error + Record(ctx context.Context, id uint64, mocks chan<- *models.Mock, opts models.OutgoingOptions) error + Mock(ctx context.Context, id uint64, opts models.OutgoingOptions) error + SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error + GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) +} + +type ProxyOptions struct { + // DNSIPv4Addr is the proxy IP returned by the DNS server. default is loopback address + DNSIPv4Addr string + // DNSIPv6Addr is the proxy IP returned by the DNS server. default is loopback address + DNSIPv6Addr string +} + +type DestInfo interface { + Get(ctx context.Context, srcPort uint16) (*NetworkAddress, error) + Delete(ctx context.Context, srcPort uint16) error +} + +type AppInfo interface { + SendDockerAppInfo(id uint64, dockerAppInfo structs.DockerAppInfo) error +} + +// For keploy test bench + +type Tester interface { + Setup(ctx context.Context, opts models.TestingOptions) error +} +type TestBenchInfo interface { + // SendKeployPids(key models.ModeKey, pid uint32) error + // SendKeployPorts(key models.ModeKey, port uint32) error +} + +// ---------------------- + +type OutgoingInfo interface { +} + +type NetworkAddress struct { + AppID uint64 + Version uint32 + IPv4Addr uint32 + IPv6Addr [4]uint32 + Port uint32 +} + +type Sessions struct { + sessions sync.Map +} + +func NewSessions() *Sessions { + return &Sessions{ + sessions: sync.Map{}, + } +} + +func (s *Sessions) Get(id uint64) (*Session, bool) { + v, ok := s.sessions.Load(id) + if !ok { + return nil, false + } + return v.(*Session), true +} + +func (s *Sessions) Set(id uint64, session *Session) { + s.sessions.Store(id, session) +} + +func (s *Sessions) Delete(id uint64) { + s.sessions.Delete(id) +} + +func (s *Sessions) getAll() map[uint64]*Session { + sessions := map[uint64]*Session{} + s.sessions.Range(func(k, v interface{}) bool { + sessions[k.(uint64)] = v.(*Session) + return true + }) + return sessions +} + +func (s *Sessions) GetAllMC() []chan<- *models.Mock { + sessions := s.getAll() + var mc []chan<- *models.Mock + for _, session := range sessions { + mc = append(mc, session.MC) + } + return mc +} + +type Session struct { + ID uint64 + Mode models.Mode + TC chan<- *models.TestCase + MC chan<- *models.Mock + models.OutgoingOptions +} diff --git a/keploy/pkg/core/tester/tester.go b/keploy/pkg/core/tester/tester.go new file mode 100644 index 0000000..68fbeb0 --- /dev/null +++ b/keploy/pkg/core/tester/tester.go @@ -0,0 +1,134 @@ +//go:build linux + +// Package tester provides functionality for testing keploy with itself +package tester + +import ( + "context" + "errors" + "fmt" + "time" + + "go.keploy.io/server/v2/pkg/core" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type Tester struct { + logger *zap.Logger + testBenchInfo core.TestBenchInfo +} + +func New(logger *zap.Logger, testBenchInfo core.TestBenchInfo) *Tester { + return &Tester{ + logger: logger, + testBenchInfo: testBenchInfo, + } +} + +const ( + testPort = 56789 + recordPort = 36789 +) + +func (t *Tester) Setup(ctx context.Context, opts models.TestingOptions) error { + t.logger.Info("🧪 setting up environment for testing keploy with itself") + + if opts.Mode == models.MODE_TEST { + err := t.setupReplay(ctx) + if err != nil { + return err + } + return nil + } + + err := t.setupRecord(ctx) + if err != nil { + return err + } + return nil +} + +func (t *Tester) setupReplay(ctx context.Context) error { + setUpErr := errors.New("failed to setup the keploy replay testing") + + recordPid, err := utils.GetPIDFromPort(ctx, t.logger, recordPort) + if err != nil { + t.logger.Error("failed to get the keployRecord pid", zap.Error(err)) + utils.LogError(t.logger, err, "failed to get the keployRecord pid from port", zap.Any("port", recordPort)) + return setUpErr + } + + t.logger.Debug(fmt.Sprintf("keployRecord pid:%v", recordPid)) + + // err = t.testBenchInfo.SendKeployPids(models.RecordKey, recordPid) + // if err != nil { + // utils.LogError(t.logger, err, fmt.Sprintf("failed to send keploy %v server pid to the epbf program", models.MODE_RECORD), zap.Any("Keploy Pid", recordPid)) + // return setUpErr + // } + + // err = t.testBenchInfo.SendKeployPorts(models.RecordKey, recordPort) + // if err != nil { + // utils.LogError(t.logger, err, fmt.Sprintf("failed to send keploy %v server port to the epbf program", models.MODE_RECORD), zap.Any("Keploy server port", recordPort)) + // return setUpErr + // } + + // err = t.testBenchInfo.SendKeployPorts(models.TestKey, testPort) + // if err != nil { + // utils.LogError(t.logger, err, fmt.Sprintf("failed to send keploy %v server port to the epbf program", models.MODE_TEST), zap.Any("Keploy server port", testPort)) + // return setUpErr + // } + + // to get the pid of keployTest binary in keployRecord binary, we have to wait for some time till the proxy server is started + // TODO: find other way to filter child process (keployTest) pid in parent process binary (keployRecord) + time.Sleep(10 * time.Second) // just for test bench. + + return nil +} + +func (t *Tester) setupRecord(ctx context.Context) error { + + go func() { + defer utils.Recover(t.logger) + + timeout := 30 * time.Second + startTime := time.Now() + + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + testPid, err := utils.GetPIDFromPort(ctx, t.logger, testPort) + if err != nil { + t.logger.Debug("failed to get the keploytest pid", zap.Error(err)) + continue + } + + if testPid == 0 { + continue + } + + t.logger.Debug("keploytest pid", zap.Uint32("pid", testPid)) + + // // sending keploytest binary pid in keployrecord binary to filter out ingress/egress calls related to keploytest binary. + // err = t.testBenchInfo.SendKeployPids(models.TestKey, testPid) + // if err != nil { + // utils.LogError(t.logger, err, fmt.Sprintf("failed to send keploy %v server pid to the epbf program", models.MODE_TEST), zap.Any("Keploy Pid", testPid)) + // } + return + case <-time.After(timeout - time.Since(startTime)): + t.logger.Info("Timeout reached, exiting loop from setupRecordTesting") + return // Exit the goroutine + + case <-ctx.Done(): + t.logger.Debug("Context cancelled, exiting loop from setupRecordTesting") + return // Exit the goroutine + } + } + }() + + return nil +} diff --git a/keploy/pkg/http2.go b/keploy/pkg/http2.go new file mode 100644 index 0000000..0d415a6 --- /dev/null +++ b/keploy/pkg/http2.go @@ -0,0 +1,741 @@ +// Package pkg provides common utilities for gRPC and HTTP/2 handling +package pkg + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "io" + "net" + "strings" + "sync" + "time" + + "github.com/protocolbuffers/protoscope" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" +) + +// HTTP/2 constants +const ( + // DefaultMaxFrameSize is the default maximum frame size as per HTTP/2 spec (16KB) + DefaultMaxFrameSize = 16384 + // MaxFrameSize is the maximum allowed frame size (16MB) + MaxFrameSize = 16777215 // 2^24 - 1 + // HTTP2Preface is the HTTP/2 connection preface + HTTP2Preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" + MaxDynamicTableSize = 8192 +) + +// ExtractHTTP2Frame attempts to extract an HTTP/2 frame from raw bytes +func ExtractHTTP2Frame(data []byte) (http2.Frame, int, error) { + if len(data) < 9 { // Minimum frame size + return nil, 0, fmt.Errorf("incomplete frame: got %d bytes, need at least 9", len(data)) + } + + // First, read the frame header to determine the full frame length + // Length is a 24-bit unsigned integer + length := (uint32(data[0]) << 16) | (uint32(data[1]) << 8) | uint32(data[2]) + + // Validate length before proceeding + if length > MaxFrameSize { + return nil, 0, fmt.Errorf("frame length %d exceeds maximum allowed size %d", length, MaxFrameSize) + } + + // Check if we have the complete frame + totalSize := int(length) + 9 // frame header (9 bytes) + payload + if len(data) < totalSize { + return nil, 0, fmt.Errorf("incomplete frame: got %d bytes, need %d", len(data), totalSize) + } + + // Create a reader for just this frame + frameReader := bytes.NewReader(data[:totalSize]) + framer := http2.NewFramer(nil, frameReader) + + // Read the frame + frame, err := framer.ReadFrame() + if err != nil { + return nil, 0, fmt.Errorf("failed to read frame: %v", err) + } + + return frame, totalSize, nil +} + +// HTTP2StreamState represents the state of an HTTP/2 stream +type HTTP2StreamState struct { + // Stream identification + ID uint32 + RequestID string + ParentID string + isRequest bool + + // Timing + startTime time.Time + endTime time.Time + + // Message state + headerBlockFragments [][]byte + dataFrames [][]byte + isComplete bool + endStreamReceived bool + requestComplete bool + + // gRPC specific state + grpcReq *models.GrpcReq + grpcResp *models.GrpcResp + + // Header state + headersReceived bool + trailersReceived bool +} + +// HTTP2Stream represents a complete HTTP/2 stream +type HTTP2Stream struct { + ID uint32 + GRPCReq *models.GrpcReq + GRPCResp *models.GrpcResp +} + +// DefaultStreamManager implements stream management +type DefaultStreamManager struct { + mutex sync.RWMutex + streams map[uint32]*HTTP2StreamState + // completed []*HTTP2Stream + buffer []byte + logger *zap.Logger + decoder *hpack.Decoder + // Context-aware header tables + requestHeaders map[string]string // For request headers + responseHeaders map[string]string // For response headers + trailerHeaders map[string]string // For trailer headers +} + +// NewStreamManager creates a new stream manager +func NewStreamManager(logger *zap.Logger) *DefaultStreamManager { + return &DefaultStreamManager{ + streams: make(map[uint32]*HTTP2StreamState), + buffer: make([]byte, 0, DefaultMaxFrameSize), + logger: logger, + decoder: hpack.NewDecoder(MaxDynamicTableSize, nil), + // Initialize separate header tables + requestHeaders: make(map[string]string), + responseHeaders: make(map[string]string), + trailerHeaders: make(map[string]string), + } +} + +// storeHeaders stores headers in the appropriate connection header table based on context +func (sm *DefaultStreamManager) storeHeaders(headers *models.GrpcHeaders, isRequest bool, isTrailer bool) { + if headers == nil { + return + } + + // Select the appropriate header table + var headerTable map[string]string + if isTrailer { + headerTable = sm.trailerHeaders + } else if isRequest { + headerTable = sm.requestHeaders + } else { + headerTable = sm.responseHeaders + } + + // Store pseudo headers + for k, v := range headers.PseudoHeaders { + headerTable[k] = v + } + + // Store ordinary headers + for k, v := range headers.OrdinaryHeaders { + headerTable[k] = v + } +} + +// rehydrateHeaders adds missing headers from the appropriate connection header table +func (sm *DefaultStreamManager) rehydrateHeaders(headers *models.GrpcHeaders, isRequest bool, isTrailer bool) { + if headers == nil { + return + } + + // Select the appropriate header table + var headerTable map[string]string + if isTrailer { + headerTable = sm.trailerHeaders + } else if isRequest { + headerTable = sm.requestHeaders + } else { + headerTable = sm.responseHeaders + } + + // Initialize maps if nil + if headers.PseudoHeaders == nil { + headers.PseudoHeaders = make(map[string]string) + } + if headers.OrdinaryHeaders == nil { + headers.OrdinaryHeaders = make(map[string]string) + } + + // Add missing headers from the connection's header table + for k, v := range headerTable { + if strings.HasPrefix(k, ":") { + // This is a pseudo-header + if _, exists := headers.PseudoHeaders[k]; !exists { + headers.PseudoHeaders[k] = v + } + } else { + // This is an ordinary header + if _, exists := headers.OrdinaryHeaders[k]; !exists { + headers.OrdinaryHeaders[k] = v + } + } + } +} + +// HandleFrame processes an HTTP/2 frame +func (sm *DefaultStreamManager) HandleFrame(frame http2.Frame, isOutgoing bool, frameTime time.Time) error { + sm.mutex.Lock() + defer sm.mutex.Unlock() + + streamID := frame.Header().StreamID + + // Initialize stream if it doesn't exist + if _, exists := sm.streams[streamID]; !exists { + prefix := "Incoming_" + if isOutgoing { + prefix = "Outgoing_" + } + requestID := fmt.Sprintf(prefix+"%d", streamID) + sm.streams[streamID] = &HTTP2StreamState{ + ID: streamID, + RequestID: requestID, + isRequest: streamID != 0, // Stream 0 is for connection control + startTime: frameTime, + } + } + + stream := sm.streams[streamID] + + switch f := frame.(type) { + case *http2.HeadersFrame: + // Store header block fragments for later processing + stream.headerBlockFragments = append(stream.headerBlockFragments, f.HeaderBlockFragment()) + + if f.HeadersEnded() { + // Process complete headers + headerBlock := bytes.Join(stream.headerBlockFragments, nil) + stream.headerBlockFragments = nil + fields, err := sm.decoder.DecodeFull(headerBlock) + if err != nil { + return fmt.Errorf("failed to decode headers: %v", err) + } + + for _, field := range fields { + if field.Name == ":status" { + stream.isRequest = false + } + } + + headers := ProcessHeaders(fields) + + if !stream.headersReceived { + // These are initial headers + if stream.isRequest { + // Rehydrate from request headers + sm.rehydrateHeaders(headers, true, false) + + stream.grpcReq = &models.GrpcReq{ + Headers: *headers, + } + // Store headers for future requests + sm.storeHeaders(headers, true, false) + } else { + // Rehydrate from response headers + sm.rehydrateHeaders(headers, false, false) + + stream.grpcResp = &models.GrpcResp{ + Headers: *headers, + } + // Store headers for future responses + sm.storeHeaders(headers, false, false) + } + stream.headersReceived = true + } else if !stream.trailersReceived && !stream.isRequest { + // These are trailers (only for responses) + // Rehydrate from trailer headers + sm.rehydrateHeaders(headers, false, true) + + if stream.grpcResp != nil { + stream.grpcResp.Trailers = *headers + // Store headers for future trailers + sm.storeHeaders(headers, false, true) + } + stream.trailersReceived = true + } + } + + if f.StreamEnded() { + stream.endStreamReceived = true + // Process the complete message + if err := sm.processCompleteMessage(stream); err != nil { + return err + } + sm.checkStreamCompletion(streamID) + + // Clear header fragments and data frames after processing request part + if stream.isRequest { + stream.headerBlockFragments = nil + stream.endStreamReceived = false + stream.headersReceived = false + stream.trailersReceived = false + } else { + stream.endTime = frameTime + } + } + + case *http2.DataFrame: + // Store data frames for later processing + stream.dataFrames = append(stream.dataFrames, f.Data()) + + if f.StreamEnded() { + stream.endStreamReceived = true + // Process the complete message + if err := sm.processCompleteMessage(stream); err != nil { + return err + } + sm.checkStreamCompletion(streamID) + + // Clear header fragments and data frames after processing request part + if stream.isRequest { + stream.headerBlockFragments = nil + stream.endStreamReceived = false + stream.headersReceived = false + stream.trailersReceived = false + } else { + stream.endTime = frameTime + } + } + } + + return nil +} + +// GetCompleteStreams returns all completed HTTP/2 streams +func (sm *DefaultStreamManager) GetCompleteStreams() []*HTTP2Stream { + sm.mutex.Lock() + defer sm.mutex.Unlock() + + var completed []*HTTP2Stream + + for id, stream := range sm.streams { + if stream.isComplete { + stream.grpcReq.Timestamp = stream.startTime + stream.grpcResp.Timestamp = stream.endTime + http2Stream := &HTTP2Stream{ + ID: id, + GRPCReq: stream.grpcReq, + GRPCResp: stream.grpcResp, + } + completed = append(completed, http2Stream) + } + } + + return completed +} + +// CleanupStream removes a stream from the manager +func (sm *DefaultStreamManager) CleanupStream(streamID uint32) { + sm.mutex.Lock() + defer sm.mutex.Unlock() + delete(sm.streams, streamID) +} + +// processCompleteMessage processes a complete HTTP/2 message +func (sm *DefaultStreamManager) processCompleteMessage(stream *HTTP2StreamState) error { + if stream == nil { + return fmt.Errorf("nil stream") + } + + if len(stream.dataFrames) == 0 { + return nil + } + + // Combine all data frame fragments + data := bytes.Join(stream.dataFrames, nil) + + // Process the complete gRPC message + msg := CreateLengthPrefixedMessageFromPayload(data) + + if stream.isRequest { + if stream.grpcReq == nil { + stream.grpcReq = &models.GrpcReq{} + } + stream.grpcReq.Body = msg + } else { + if stream.grpcResp == nil { + stream.grpcResp = &models.GrpcResp{} + } + stream.grpcResp.Body = msg + } + + // Clear the data frames after processing + stream.dataFrames = nil + return nil +} + +// checkStreamCompletion checks if a stream is complete and processes it accordingly +func (sm *DefaultStreamManager) checkStreamCompletion(streamID uint32) { + stream := sm.streams[streamID] + + // For requests: mark the message part as complete but don't complete the stream + if stream.isRequest { + if stream.endStreamReceived && stream.headersReceived { + // Mark request part as complete but keep stream open for response + stream.requestComplete = true + } + return // Don't mark stream as complete yet + } + + // For responses: check if both request and response are complete + if !stream.isRequest && stream.requestComplete { + if stream.endStreamReceived && stream.headersReceived && stream.trailersReceived { // For gRPC, ensure trailers are received + + sm.logger.Debug("Stream completed", zap.Any("stream", stream)) + + stream.isComplete = true + } + } +} + +// ProcessHeaders converts HPACK header fields to a map +func ProcessHeaders(fields []hpack.HeaderField) *models.GrpcHeaders { + headers := &models.GrpcHeaders{ + PseudoHeaders: make(map[string]string), + OrdinaryHeaders: make(map[string]string), + } + + for _, field := range fields { + if len(field.Name) > 0 && field.Name[0] == ':' { + headers.PseudoHeaders[field.Name] = field.Value + } else { + headers.OrdinaryHeaders[field.Name] = field.Value + } + } + + return headers +} + +// IsGRPCGatewayRequest checks if the stream appears to be from gRPC-gateway that proxies http requests to grpc services +func IsGRPCGatewayRequest(stream *HTTP2Stream) bool { + if stream == nil || stream.GRPCReq == nil { + return false + } + + // Check for HTTP gateway specific headers + headers := stream.GRPCReq.Headers.OrdinaryHeaders + gatewayHeaders := []string{ + "grpc-gateway-user-agent", + } + + for _, header := range gatewayHeaders { + if _, exists := headers[header]; exists { + return true + } + } + + return false +} + +// SimulateGRPC simulates a gRPC call and returns the response +// This is a standalone version of the simulateGRPC method from Hooks +func SimulateGRPC(_ context.Context, tc *models.TestCase, testSetID string, logger *zap.Logger) (*models.GrpcResp, error) { + grpcReq := tc.GrpcReq + + logger.Info("starting test for of", zap.Any("test case", models.HighlightString(tc.Name)), zap.Any("test set", models.HighlightString(testSetID))) + + // Create a TCP connection + conn, err := net.Dial("tcp", grpcReq.Headers.PseudoHeaders[":authority"]) + if err != nil { + return nil, fmt.Errorf("failed to dial: %w", err) + } + defer func() { + if cerr := conn.Close(); cerr != nil { + logger.Error("failed to close connection", zap.Error(cerr)) + } + }() + + // Write HTTP/2 connection preface + if _, err := conn.Write([]byte(http2.ClientPreface)); err != nil { + return nil, fmt.Errorf("failed to write client preface: %w", err) + } + + // Create HTTP/2 client connection + framer := http2.NewFramer(conn, conn) + framer.AllowIllegalWrites = true // Allow HTTP/2 without TLS + + // Initial sequence of frames that gRPC sends: + // 1. Empty SETTINGS frame + // 2. Wait for server SETTINGS and ACK + // 3. Send SETTINGS ACK + // 4. HEADERS frame + // 5. DATA frame + // 6. WINDOW_UPDATE frame (connection-level) + // 7. PING frame + + // Send initial empty SETTINGS frame + err = framer.WriteSettings() + if err != nil { + return nil, fmt.Errorf("failed to write settings: %w", err) + } + + // Wait for server's SETTINGS and SETTINGS ACK + settingsReceived := false + settingsAckReceived := false + + for !settingsReceived || !settingsAckReceived { + frame, err := framer.ReadFrame() + if err != nil { + return nil, fmt.Errorf("failed to read server settings: %w", err) + } + + switch f := frame.(type) { + case *http2.SettingsFrame: + if f.IsAck() { + settingsAckReceived = true + } else { + settingsReceived = true + // Send ACK for server SETTINGS + if err := framer.WriteSettingsAck(); err != nil { + return nil, fmt.Errorf("failed to write settings ack: %w", err) + } + } + } + } + + // Create stream ID (client streams are odd-numbered) + streamID := uint32(1) + + // Prepare HEADERS frame + headerBuf := new(bytes.Buffer) + encoder := hpack.NewEncoder(headerBuf) + + // Write pseudo-headers first (order matters in HTTP/2) + pseudoHeaders := []struct { + name, value string + }{ + {":method", grpcReq.Headers.PseudoHeaders[":method"]}, + {":scheme", grpcReq.Headers.PseudoHeaders[":scheme"]}, + {":authority", grpcReq.Headers.PseudoHeaders[":authority"]}, + {":path", grpcReq.Headers.PseudoHeaders[":path"]}, + } + + for _, ph := range pseudoHeaders { + if err := encoder.WriteField(hpack.HeaderField{Name: ph.name, Value: ph.value}); err != nil { + return nil, fmt.Errorf("failed to encode pseudo-header %s: %w", ph.name, err) + } + } + + // Write regular headers in a specific order + orderedHeaders := []struct { + name, value string + }{} + + // Add any remaining headers from the request + for k, v := range grpcReq.Headers.OrdinaryHeaders { + orderedHeaders = append(orderedHeaders, struct{ name, value string }{k, v}) + } + + for _, h := range orderedHeaders { + if err := encoder.WriteField(hpack.HeaderField{Name: h.name, Value: h.value}); err != nil { + return nil, fmt.Errorf("failed to encode header %s: %w", h.name, err) + } + } + + // Send HEADERS frame + if err := framer.WriteHeaders(http2.HeadersFrameParam{ + StreamID: streamID, + BlockFragment: headerBuf.Bytes(), + EndHeaders: true, + EndStream: false, + }); err != nil { + return nil, fmt.Errorf("failed to write headers: %w", err) + } + + // Create and send DATA frame + payload, err := CreatePayloadFromLengthPrefixedMessage(grpcReq.Body) + if err != nil { + return nil, fmt.Errorf("failed to create payload: %w", err) + } + + if err := framer.WriteData(streamID, true, payload); err != nil { + return nil, fmt.Errorf("failed to write data: %w", err) + } + + // Send WINDOW_UPDATE frame for connection (stream 0) + if err := framer.WriteWindowUpdate(0, 983041); err != nil { + return nil, fmt.Errorf("failed to write window update: %w", err) + } + + // Send PING frame + pingData := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} // Example ping data + if err := framer.WritePing(false, pingData); err != nil { + return nil, fmt.Errorf("failed to write ping: %w", err) + } + + // Read response frames + grpcResp := &models.GrpcResp{ + Headers: models.GrpcHeaders{ + PseudoHeaders: make(map[string]string), + OrdinaryHeaders: make(map[string]string), + }, + Body: models.GrpcLengthPrefixedMessage{}, + Trailers: models.GrpcHeaders{ + PseudoHeaders: make(map[string]string), + OrdinaryHeaders: make(map[string]string), + }, + } + + // Read frames until we get the end of stream + streamEnded := false + for !streamEnded { + frame, err := framer.ReadFrame() + if err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("failed to read frame: %w", err) + } + + switch f := frame.(type) { + case *http2.HeadersFrame: + // If we already have headers, this must be trailers + if len(grpcResp.Headers.OrdinaryHeaders) > 0 || len(grpcResp.Headers.PseudoHeaders) > 0 { + decoder := hpack.NewDecoder(MaxDynamicTableSize, func(f hpack.HeaderField) { + if strings.HasPrefix(f.Name, ":") { + grpcResp.Trailers.PseudoHeaders[f.Name] = f.Value + } else { + grpcResp.Trailers.OrdinaryHeaders[f.Name] = f.Value + } + }) + if _, err := decoder.Write(f.HeaderBlockFragment()); err != nil { + return nil, fmt.Errorf("failed to decode trailers: %w", err) + } + } else { + // These are headers + decoder := hpack.NewDecoder(MaxDynamicTableSize, func(f hpack.HeaderField) { + if strings.HasPrefix(f.Name, ":") { + grpcResp.Headers.PseudoHeaders[f.Name] = f.Value + } else { + grpcResp.Headers.OrdinaryHeaders[f.Name] = f.Value + } + }) + if _, err := decoder.Write(f.HeaderBlockFragment()); err != nil { + return nil, fmt.Errorf("failed to decode headers: %w", err) + } + } + if f.StreamEnded() { + streamEnded = true + } + + case *http2.DataFrame: + frame, err := ReadGRPCFrame(bytes.NewReader(f.Data())) + if err != nil { + return nil, fmt.Errorf("failed to read gRPC frame: %w", err) + } + + grpcResp.Body = CreateLengthPrefixedMessageFromPayload(frame) + if f.StreamEnded() { + streamEnded = true + } + + case *http2.RSTStreamFrame: + // Stream was reset by peer + streamEnded = true + + case *http2.GoAwayFrame: + // Connection is being closed + streamEnded = true + } + } + + return grpcResp, nil +} + +// CreateLengthPrefixedMessageFromPayload creates a GrpcLengthPrefixedMessage from raw payload data +func CreateLengthPrefixedMessageFromPayload(data []byte) models.GrpcLengthPrefixedMessage { + msg := models.GrpcLengthPrefixedMessage{} + + // If the body is not length prefixed, we return the default value. + if len(data) < 5 { + return msg + } + + // The first byte is the compression flag. + msg.CompressionFlag = uint(data[0]) + + // The next 4 bytes are message length. + msg.MessageLength = binary.BigEndian.Uint32(data[1:5]) + + // The payload could be empty. We only parse it if it is present. + if len(data) > 5 { + // Use protoscope to decode the message. + msg.DecodedData = protoscope.Write(data[5:], protoscope.WriterOptions{}) + } + + return msg +} + +// CreatePayloadFromLengthPrefixedMessage converts a GrpcLengthPrefixedMessage to raw payload bytes +func CreatePayloadFromLengthPrefixedMessage(msg models.GrpcLengthPrefixedMessage) ([]byte, error) { + scanner := protoscope.NewScanner(msg.DecodedData) + encodedData, err := scanner.Exec() + if err != nil { + return nil, fmt.Errorf("could not encode grpc msg using protoscope: %v", err) + } + + // Note that the encoded length is present in the msg, but it is also equal to the len of encodedData. + // We should give the preference to the length of encodedData, since the mocks might have been altered. + + // Reserve 1 byte for compression flag, 4 bytes for length capture. + payload := make([]byte, 1+4) + payload[0] = uint8(msg.CompressionFlag) + binary.BigEndian.PutUint32(payload[1:5], uint32(len(encodedData))) + payload = append(payload, encodedData...) + + return payload, nil +} + +// ReadGRPCFrame reads a gRPC frame from the given reader and returns it as a byte array +func ReadGRPCFrame(r io.Reader) ([]byte, error) { + // Read compression flag (1 byte) + compFlag := make([]byte, 1) + if _, err := io.ReadFull(r, compFlag); err != nil { + if err == io.EOF { + return nil, err + } + return nil, fmt.Errorf("failed to read compression flag: %w", err) + } + + // Read message length (4 bytes) + lenBuf := make([]byte, 4) + if _, err := io.ReadFull(r, lenBuf); err != nil { + return nil, fmt.Errorf("failed to read message length: %w", err) + } + msgLen := binary.BigEndian.Uint32(lenBuf) + + // Read message data + msgBuf := make([]byte, msgLen) + if _, err := io.ReadFull(r, msgBuf); err != nil { + return nil, fmt.Errorf("failed to read message: %w", err) + } + + // Combine all parts + frame := make([]byte, 5+msgLen) + frame[0] = compFlag[0] + copy(frame[1:5], lenBuf) + copy(frame[5:], msgBuf) + + return frame, nil +} diff --git a/keploy/pkg/matcher/grpc/canonical.go b/keploy/pkg/matcher/grpc/canonical.go new file mode 100644 index 0000000..26183d6 --- /dev/null +++ b/keploy/pkg/matcher/grpc/canonical.go @@ -0,0 +1,282 @@ +package grpc + +import ( + "sort" + "strings" +) + +// Hard guards to prevent pathological work on giant or adversarial inputs. +const ( + maxCanonDepth = 64 // reasonable for nested protos + maxCanonBytes = 1 << 20 // 1 MiB per side; beyond this we bail out + maxBlocks = 10_000 // safety for degenerate line/block splits +) + +// canonicalizeTopLevelBlocks makes protoscope-like text order-insensitive at *all* levels by: +// - splitting into top-level field blocks +// - recursively canonicalizing the content of each "{...}" block +// - sorting blocks lexicographically +// - joining them back together +func CanonicalizeTopLevelBlocks(s string) string { + if len(s) > maxCanonBytes { + // Too large to safely canonicalize – return normalized but otherwise unchanged. + return normalizeWhitespace(s) + } + return canonicalizeRecursive(s, 0) +} + +func canonicalizeRecursive(s string, depth int) string { + if depth > maxCanonDepth { + return normalizeWhitespace(s) + } + trimmed := strings.TrimSpace(s) + if trimmed == "" { + return "" + } + + blocks := splitTopLevelBlocks(trimmed) + if len(blocks) > maxBlocks { + // Degenerate input: avoid n^2 sorts/work. + return normalizeWhitespace(s) + } + // Recursively canonicalize inner bodies of each block + for i := range blocks { + blocks[i] = canonicalizeBlock(blocks[i], depth) + blocks[i] = normalizeWhitespace(blocks[i]) + } + + // Order-insensitive among siblings + sort.Strings(blocks) + return strings.Join(blocks, "\n") +} + +// splitTopLevelBlocks groups lines into top-level "field blocks". +// A new block starts when depth==0 and the line matches ^\s*\d+: +// Bare tokens like "64", "ai64", etc. stay with the preceding block. +func splitTopLevelBlocks(s string) []string { + lines := strings.Split(s, "\n") + var blocks []string + var cur []string + depth := 0 + + isFieldLine := func(line string) bool { + line = strings.TrimLeft(line, " \t") + if line == "" { + return false + } + i := 0 + for i < len(line) && line[i] >= '0' && line[i] <= '9' { + i++ + } + return i > 0 && i < len(line) && line[i] == ':' + } + + flush := func() { + if len(cur) == 0 { + return + } + blk := strings.TrimRight(strings.Join(cur, "\n"), "\n") + if blk != "" { + blocks = append(blocks, blk) + } + cur = cur[:0] + } + + for i, ln := range lines { + // If we see a new top-level field start, flush previous block + if depth == 0 && isFieldLine(ln) && len(cur) > 0 { + flush() + } + cur = append(cur, ln) + depth += braceDeltaIgnoringStrings(ln) + if i == len(lines)-1 { + flush() + } + if len(blocks) > maxBlocks { + // Stop early; upstream will bail out. + return blocks + } + } + return blocks +} + +// canonicalizeBlock finds the outermost "{...}" of this block (if any), +// recursively canonicalizes the inside, and reassembles the block. +// If there is no brace, returns the block as-is (after whitespace normalize by caller). +func canonicalizeBlock(block string, depth int) string { + // Find first '{' outside strings/backticks. + open := indexFirstOpenBrace(block) + if open < 0 { + return block + } + + // Find its matching '}' (depth-aware, strings-safe). + closeIdx := findMatchingCloseBrace(block, open) + if closeIdx < 0 { + // Unbalanced; fallback to original to avoid mangling. + return block + } + + inner := block[open+1 : closeIdx] + innerCanon := canonicalizeRecursive(inner, depth+1) + + // Reassemble: keep exactly the same outer text, replace inner with canonical form. + return block[:open+1] + innerCanon + block[closeIdx:] +} + +// indexFirstOpenBrace returns the index of the first '{' not inside quotes/backticks. +func indexFirstOpenBrace(s string) int { + inDq := false // " + inBt := false // ` + esc := false + + for i := 0; i < len(s); i++ { + ch := s[i] + + if inBt { + if ch == '`' { + inBt = false + } + continue + } + if inDq { + if esc { + esc = false + continue + } + if ch == '\\' { + esc = true + continue + } + if ch == '"' { + inDq = false + } + continue + } + + // Not in string + if ch == '`' { + inBt = true + continue + } + if ch == '"' { + inDq = true + continue + } + if ch == '{' { + return i + } + } + return -1 +} + +// findMatchingCloseBrace finds the matching '}' for the '{' at openIdx, +// counting only braces that are outside quotes/backticks. +func findMatchingCloseBrace(s string, openIdx int) int { + inDq := false + inBt := false + esc := false + depth := 0 + + for i := openIdx; i < len(s); i++ { + ch := s[i] + + if inBt { + if ch == '`' { + inBt = false + } + continue + } + if inDq { + if esc { + esc = false + continue + } + if ch == '\\' { + esc = true + continue + } + if ch == '"' { + inDq = false + } + continue + } + + switch ch { + case '`': + inBt = true + case '"': + inDq = true + case '{': + depth++ + case '}': + depth-- + if depth == 0 { + return i + } + } + } + return -1 +} + +// braceDeltaIgnoringStrings returns net brace count change for the line, +// ignoring any braces that appear inside quotes/backticks. +func braceDeltaIgnoringStrings(line string) int { + inDq := false + inBt := false + esc := false + delta := 0 + + for i := 0; i < len(line); i++ { + ch := line[i] + + if inBt { + if ch == '`' { + inBt = false + } + continue + } + if inDq { + if esc { + esc = false + continue + } + if ch == '\\' { + esc = true + continue + } + if ch == '"' { + inDq = false + } + continue + } + + switch ch { + case '`': + inBt = true + case '"': + inDq = true + case '{': + delta++ + case '}': + delta-- + } + } + return delta +} + +func normalizeWhitespace(s string) string { + lines := strings.Split(s, "\n") + out := make([]string, 0, len(lines)) + prevBlank := false + for _, ln := range lines { + ln = strings.TrimRight(ln, " \t") + blank := strings.TrimSpace(ln) == "" + if blank && prevBlank { + continue + } + out = append(out, ln) + prevBlank = blank + } + return strings.Join(out, "\n") +} diff --git a/keploy/pkg/matcher/grpc/match.go b/keploy/pkg/matcher/grpc/match.go new file mode 100644 index 0000000..c0f65c0 --- /dev/null +++ b/keploy/pkg/matcher/grpc/match.go @@ -0,0 +1,234 @@ +// Package grpc provides gRPC response matching functionality +package grpc + +import ( + "fmt" + "strings" + + "github.com/k0kubun/pp/v3" + "go.keploy.io/server/v2/pkg/matcher" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// Match compares an expected gRPC response with an actual response and returns whether they match +// along with detailed comparison results +func Match(tc *models.TestCase, actualResp *models.GrpcResp, noiseConfig map[string]map[string][]string, logger *zap.Logger) (bool, *models.Result) { + expectedResp := tc.GrpcResp + result := &models.Result{ + HeadersResult: make([]models.HeaderResult, 0), + BodyResult: make([]models.BodyResult, 0), + TrailerResult: make([]models.HeaderResult, 0), + } + + // Local variables to track overall match status + differences := make(map[string]struct { + Expected string + Actual string + Message string + }) + + // Only compare :status in pseudo headers + if expectedStatus, ok := expectedResp.Headers.PseudoHeaders[":status"]; ok { + actualStatus, exists := actualResp.Headers.PseudoHeaders[":status"] + headerResult := models.HeaderResult{ + Expected: models.Header{ + Key: ":status", + Value: []string{expectedStatus}, + }, + Actual: models.Header{ + Key: ":status", + Value: []string{}, + }, + } + + if !exists { + differences["headers.pseudo_headers.:status"] = struct { + Expected string + Actual string + Message string + }{ + Expected: expectedStatus, + Actual: "", + Message: "missing status header in response", + } + headerResult.Normal = false + } else { + headerResult.Actual.Value = []string{actualStatus} + headerResult.Normal = expectedStatus == actualStatus + + if !headerResult.Normal { + differences["headers.pseudo_headers.:status"] = struct { + Expected string + Actual string + Message string + }{ + Expected: expectedStatus, + Actual: actualStatus, + Message: "status header value mismatch", + } + } + } + + result.HeadersResult = append(result.HeadersResult, headerResult) + } + + // Compare Body - using specialized body types for gRPC + // Compare compression flag + compressionFlagNormal := expectedResp.Body.CompressionFlag == actualResp.Body.CompressionFlag + if !compressionFlagNormal { + differences["body.compression_flag"] = struct { + Expected string + Actual string + Message string + }{ + Expected: fmt.Sprintf("%d", expectedResp.Body.CompressionFlag), + Actual: fmt.Sprintf("%d", actualResp.Body.CompressionFlag), + Message: "compression flag mismatch", + } + } + result.BodyResult = append(result.BodyResult, models.BodyResult{ + Normal: compressionFlagNormal, + Type: models.GrpcCompression, + Expected: fmt.Sprintf("%d", expectedResp.Body.CompressionFlag), + Actual: fmt.Sprintf("%d", actualResp.Body.CompressionFlag), + }) + + // Compare message length + messageLengthNormal := expectedResp.Body.MessageLength == actualResp.Body.MessageLength + if !messageLengthNormal { + differences["body.message_length"] = struct { + Expected string + Actual string + Message string + }{ + Expected: fmt.Sprintf("%d", expectedResp.Body.MessageLength), + Actual: fmt.Sprintf("%d", actualResp.Body.MessageLength), + Message: "message length mismatch", + } + } + result.BodyResult = append(result.BodyResult, models.BodyResult{ + Normal: messageLengthNormal, + Type: models.GrpcLength, + Expected: fmt.Sprintf("%d", expectedResp.Body.MessageLength), + Actual: fmt.Sprintf("%d", actualResp.Body.MessageLength), + }) + + // Compare decoded data + expCanon := CanonicalizeTopLevelBlocks(expectedResp.Body.DecodedData) + actCanon := CanonicalizeTopLevelBlocks(actualResp.Body.DecodedData) + decodedDataNormal := expCanon == actCanon + + if !decodedDataNormal { + differences["body.decoded_data"] = struct { + Expected string + Actual string + Message string + }{ + Expected: expCanon, + Actual: actCanon, + Message: "decoded data mismatch", + } + } + result.BodyResult = append(result.BodyResult, models.BodyResult{ + Normal: decodedDataNormal, + Type: models.GrpcData, + Expected: expCanon, + Actual: actCanon, + }) + + // Handle noise configuration + var ( + bodyNoise = noiseConfig["body"] + headerNoise = noiseConfig["header"] + ) + + if bodyNoise == nil { + bodyNoise = map[string][]string{} + } + if headerNoise == nil { + headerNoise = map[string][]string{} + } + + // Apply noise configuration to ignore specified differences + for path := range differences { + pathParts := strings.Split(path, ".") + if len(pathParts) > 1 { + if pathParts[0] == "body" && len(bodyNoise) > 0 { + if _, found := bodyNoise[strings.Join(pathParts[1:], ".")]; found { + delete(differences, path) + } + } else if pathParts[0] == "headers" && len(headerNoise) > 0 { + if _, found := headerNoise[pathParts[len(pathParts)-1]]; found { + delete(differences, path) + } + } + } + } + + // Calculate final match status based on remaining differences + matched := len(differences) == 0 + + if !matched { + // Display differences to the user, similar to HTTP matcher + logDiffs := matcher.NewDiffsPrinter(tc.Name) + newLogger := pp.New() + newLogger.WithLineInfo = false + newLogger.SetColorScheme(models.GetFailingColorScheme()) + var logs = "" + + logs = logs + newLogger.Sprintf("Testrun failed for testcase with id: %s\n\n--------------------------------------------------------------------\n\n", tc.Name) + + // Display gRPC differences + if len(differences) > 0 { + for path, diff := range differences { + if strings.HasPrefix(path, "headers.") { + // Header differences + header := strings.TrimPrefix(path, "headers.") + logDiffs.PushHeaderDiff(diff.Expected, diff.Actual, header, headerNoise) + } else if strings.HasPrefix(path, "body.") { + bodyPart := strings.TrimPrefix(path, "body.") + switch bodyPart { + case "message_length": + // Message length is a good indicator of difference for gRPC + logDiffs.PushHeaderDiff(diff.Expected, diff.Actual, "message_length (body)", bodyNoise) + case "compression_flag": + // Compression flag + logDiffs.PushHeaderDiff(diff.Expected, diff.Actual, "compression_flag (body)", bodyNoise) + default: + // Any other body differences + logDiffs.PushBodyDiff(diff.Expected, diff.Actual, bodyNoise) + } + } + } + } else { + // If there are no specific differences but match still failed, show a generic message + logDiffs.PushHeaderDiff("See logs for details", "Matching failed", "gRPC", nil) + } + + // Print the differences + _, err := newLogger.Printf(logs) + if err != nil { + utils.LogError(logger, err, "failed to print the logs") + } + + err = logDiffs.Render() + if err != nil { + utils.LogError(logger, err, "failed to render the diffs") + } + } else { + // Display success message + newLogger := pp.New() + newLogger.WithLineInfo = false + newLogger.SetColorScheme(models.GetPassingColorScheme()) + var log2 = "" + log2 += newLogger.Sprintf("Testrun passed for testcase with id: %s\n\n--------------------------------------------------------------------\n\n", tc.Name) + _, err := newLogger.Printf(log2) + if err != nil { + utils.LogError(logger, err, "failed to print the logs") + } + } + + return matched, result +} diff --git a/keploy/pkg/matcher/http/absmatch.go b/keploy/pkg/matcher/http/absmatch.go new file mode 100644 index 0000000..4094f0f --- /dev/null +++ b/keploy/pkg/matcher/http/absmatch.go @@ -0,0 +1,472 @@ +package http + +import ( + "encoding/json" + "strings" + + "go.keploy.io/server/v2/pkg" + matcher "go.keploy.io/server/v2/pkg/matcher" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +// AbsMatch (Absolute Match) compares two test cases and returns a boolean value indicating whether they are equal or not. +// It also returns a AbsResult object which contains the results of the comparison. +// Parameters: tcs1, tcs2, noiseConfig, ignoreOrdering, logger +// Returns: bool, *models.AbsResult +func AbsMatch(tcs1, tcs2 *models.TestCase, noiseConfig map[string]map[string][]string, ignoreOrdering bool, logger *zap.Logger) (bool, bool, bool, *models.AbsResult) { + if tcs1 == nil || tcs2 == nil { + logger.Error("test case is nil", zap.Any("tcs1", tcs1), zap.Any("tcs2", tcs2)) + return false, false, false, nil + } + + pass := true + absResult := &models.AbsResult{} + + kindResult := models.StringResult{ + Normal: true, + Expected: string(tcs1.Kind), + Actual: string(tcs2.Kind), + } + + nameResult := models.StringResult{ + Normal: true, + Expected: tcs1.Name, + Actual: tcs2.Name, + } + + // curlResult := models.StringResult{ + // Normal: true, + // Expected: tcs1.Curl, + // Actual: tcs2.Curl, + // } + + //compare kind + if tcs1.Kind != tcs2.Kind { + kindResult.Normal = false + logger.Info("test case kind is not equal", zap.Any("tcs1Kind", tcs1.Kind), zap.Any("tcs2Kind", tcs2.Kind)) + pass = false + } + + //compare name (just for debugging) + if tcs1.Name != tcs2.Name { + nameResult.Normal = false + logger.Debug("test case name is not equal", zap.Any("tcs1Name", tcs1.Name), zap.Any("tcs2Name", tcs2.Name)) + } + + //compare curl + // ok := CompareCurl(tcs1.Curl, tcs2.Curl, logger) + // if !ok { + // curlResult.Normal = false + // logger.Info("test case curl is not equal", zap.Any("tcs1Curl", tcs1.Curl), zap.Any("tcs2Curl", tcs2.Curl)) + // pass = false + // } + + //compare http req + reqPass, reqCompare := CompareHTTPReq(tcs1, tcs2, noiseConfig, ignoreOrdering, logger) + if !reqPass { + logger.Info("test case http req is not equal", zap.Any("tcs1HttpReq", tcs1.HTTPReq), zap.Any("tcs2HttpReq", tcs2.HTTPReq)) + pass = false + } + + //compare http resp + respPass, respCompare := CompareHTTPResp(tcs1, tcs2, noiseConfig, ignoreOrdering, logger) + if !respPass { + logger.Info("test case http resp is not equal", zap.Any("tcs1HttpResp", tcs1.HTTPResp), zap.Any("tcs2HttpResp", tcs2.HTTPResp)) + pass = false + } + + absResult.Name = nameResult + absResult.Kind = kindResult + absResult.Req = reqCompare + absResult.Resp = respCompare + // absResult.CurlResult = curlResult + + return pass, reqPass, respPass, absResult +} + +// CompareHTTPReq compares two http requests and returns a boolean value indicating whether they are equal or not. +func CompareHTTPReq(tcs1, tcs2 *models.TestCase, _ models.GlobalNoise, ignoreOrdering bool, logger *zap.Logger) (bool, models.ReqCompare) { + pass := true + //compare http req + reqCompare := models.ReqCompare{ + MethodResult: models.StringResult{ + Normal: true, + Expected: string(tcs1.HTTPReq.Method), + Actual: string(tcs2.HTTPReq.Method), + }, + URLResult: models.StringResult{ + Normal: true, + Expected: tcs1.HTTPReq.URL, + Actual: tcs2.HTTPReq.URL, + }, + URLParamsResult: []models.URLParamsResult{}, + ProtoMajor: models.IntResult{ + Normal: true, + Expected: tcs1.HTTPReq.ProtoMajor, + Actual: tcs2.HTTPReq.ProtoMajor, + }, + ProtoMinor: models.IntResult{ + Normal: true, + Expected: tcs1.HTTPReq.ProtoMinor, + Actual: tcs2.HTTPReq.ProtoMinor, + }, + HeaderResult: []models.HeaderResult{}, + BodyResult: models.BodyResult{ + Normal: true, + Expected: tcs1.HTTPReq.Body, + Actual: tcs2.HTTPReq.Body, + }, + } + + if tcs1.HTTPReq.Method != tcs2.HTTPReq.Method { + reqCompare.MethodResult.Normal = false + logger.Debug("test case http req method is not equal", zap.Any("tcs1HttpReqMethod", tcs1.HTTPReq.Method), zap.Any("tcs2HttpReqMethod", tcs2.HTTPReq.Method)) + pass = false + } + + if tcs1.HTTPReq.URL != tcs2.HTTPReq.URL { + reqCompare.URLResult.Normal = false + logger.Debug("test case http req url is not equal", zap.Any("tcs1HttpReqURL", tcs1.HTTPReq.URL), zap.Any("tcs2HttpReqURL", tcs2.HTTPReq.URL)) + pass = false + } + + if tcs1.HTTPReq.ProtoMajor != tcs2.HTTPReq.ProtoMajor { + reqCompare.ProtoMajor.Normal = false + logger.Debug("test case http req proto major is not equal", zap.Any("tcs1HttpReqProtoMajor", tcs1.HTTPReq.ProtoMajor), zap.Any("tcs2HttpReqProtoMajor", tcs2.HTTPReq.ProtoMajor)) + pass = false + } + + if tcs1.HTTPReq.ProtoMinor != tcs2.HTTPReq.ProtoMinor { + reqCompare.ProtoMinor.Normal = false + logger.Debug("test case http req proto minor is not equal", zap.Any("tcs1HttpReqProtoMinor", tcs1.HTTPReq.ProtoMinor), zap.Any("tcs2HttpReqProtoMinor", tcs2.HTTPReq.ProtoMinor)) + pass = false + } + + //compare url params + urlParams1 := tcs1.HTTPReq.URLParams + urlParams2 := tcs2.HTTPReq.URLParams + if len(urlParams1) == len(urlParams2) { + ok := CompareURLParams(urlParams1, urlParams2, &reqCompare.URLParamsResult) + if !ok { + logger.Debug("test case http req url params are not equal", zap.Any("tcs1HttpReqURLParams", tcs1.HTTPReq.URLParams), zap.Any("tcs2HttpReqURLParams", tcs2.HTTPReq.URLParams)) + pass = false + } + } else { + logger.Debug("test case http req url params are not equal", zap.Any("tcs1HttpReqURLParams", tcs1.HTTPReq.URLParams), zap.Any("tcs2HttpReqURLParams", tcs2.HTTPReq.URLParams)) + pass = false + } + + reqHeaderNoise := map[string][]string{} + reqHeaderNoise["keploy-test-id"] = []string{} + reqHeaderNoise["keploy-test-set-id"] = []string{} + tcs1.HTTPReq.Header["Keploy-Test-Id"] = "dummyTest" + tcs1.HTTPReq.Header["Keploy-Test-Set-Id"] = "dummyTestSet" + + // compare http req headers + ok := matcher.CompareHeaders(pkg.ToHTTPHeader(tcs1.HTTPReq.Header), pkg.ToHTTPHeader(tcs2.HTTPReq.Header), &reqCompare.HeaderResult, reqHeaderNoise) + if !ok { + logger.Debug("test case http req headers are not equal", zap.Any("tcs1HttpReqHeaders", tcs1.HTTPReq.Header), zap.Any("tcs2HttpReqHeaders", tcs2.HTTPReq.Header)) + pass = false + } + + reqBodyNoise := map[string][]string{} + + // compare http req body + bodyType1 := models.Plain + if json.Valid([]byte(tcs1.HTTPReq.Body)) { + bodyType1 = models.JSON + } + + bodyType2 := models.Plain + if json.Valid([]byte(tcs2.HTTPReq.Body)) { + bodyType2 = models.JSON + } + + if bodyType1 != bodyType2 { + logger.Debug("test case http req body type is not equal", zap.Any("tcs1HttpReqBodyType", bodyType1), zap.Any("tcs2HttpReqBodyType", bodyType2)) + pass = false + reqCompare.BodyResult.Normal = false + return pass, reqCompare + } + + bodyRes := true + // stores the json body after removing the noise + cleanExp, cleanAct := tcs1.HTTPReq.Body, tcs2.HTTPReq.Body + var jsonComparisonResult matcher.JSONComparisonResult + if !matcher.Contains(matcher.MapToArray(reqBodyNoise), "body") && bodyType1 == models.JSON { + //validate the stored json + validatedJSON, err := matcher.ValidateAndMarshalJSON(logger, &cleanExp, &cleanAct) + if err != nil { + logger.Error("failed to validate and marshal json", zap.Error(err)) + reqCompare.BodyResult.Normal = false + return false, reqCompare + } + if validatedJSON.IsIdentical() { + jsonComparisonResult, err = matcher.JSONDiffWithNoiseControl(validatedJSON, reqBodyNoise, ignoreOrdering) + exact := jsonComparisonResult.IsExact() + if err != nil { + logger.Error("failed to compare json", zap.Error(err)) + reqCompare.BodyResult.Normal = false + return false, reqCompare + } + + if !exact { + pass = false + bodyRes = false + } + } else { + pass = false + bodyRes = false + } + + // debug log for cleanExp and cleanAct + logger.Debug("cleanExp", zap.Any("", cleanExp)) + logger.Debug("cleanAct", zap.Any("", cleanAct)) + } else { + if !matcher.Contains(matcher.MapToArray(reqBodyNoise), "body") && tcs1.HTTPReq.Body != tcs2.HTTPReq.Body { + pass = false + bodyRes = false + } + } + + if !bodyRes { + reqCompare.BodyResult.Normal = false + } + + return pass, reqCompare +} + +// CompareHTTPResp compares two http responses and returns a boolean value indicating whether they are equal or not. +func CompareHTTPResp(tcs1, tcs2 *models.TestCase, noiseConfig models.GlobalNoise, ignoreOrdering bool, logger *zap.Logger) (bool, models.RespCompare) { + pass := true + //compare http resp + respCompare := models.RespCompare{ + StatusCode: models.IntResult{ + Normal: true, + Expected: tcs1.HTTPResp.StatusCode, + Actual: tcs2.HTTPResp.StatusCode, + }, + HeadersResult: []models.HeaderResult{}, + BodyResult: models.BodyResult{ + Normal: true, + Expected: tcs1.HTTPResp.Body, + Actual: tcs2.HTTPResp.Body, + }, + } + + if tcs1.HTTPResp.StatusCode != tcs2.HTTPResp.StatusCode { + respCompare.StatusCode.Normal = false + logger.Debug("test case http resp status code is not equal", zap.Any("tcs1HttpRespStatusCode", tcs1.HTTPResp.StatusCode), zap.Any("tcs2HttpRespStatusCode", tcs2.HTTPResp.StatusCode)) + pass = false + } + + //compare the auto added noise in test case + noise1 := tcs1.Noise + noise2 := tcs2.Noise + ok := CompareNoise(noise1, noise2) + if !ok { + logger.Debug("test case noise is not equal", zap.Any("tcs1Noise", tcs1.Noise), zap.Any("tcs2Noise", tcs2.Noise)) + logger.Debug("response body and headers can not be compared because noise is not equal") + pass = false + respCompare.BodyResult.Normal = false + return pass, respCompare + } + + noise := noise1 + + var ( + bodyNoise = noiseConfig["body"] + headerNoise = noiseConfig["header"] + ) + + if bodyNoise == nil { + bodyNoise = map[string][]string{} + } + if headerNoise == nil { + headerNoise = map[string][]string{} + } + + for field, regexArr := range noise { + a := strings.Split(field, ".") + if len(a) > 1 && a[0] == "body" { + x := strings.Join(a[1:], ".") + bodyNoise[strings.ToLower(x)] = regexArr + } else if a[0] == "header" { + headerNoise[strings.ToLower(a[len(a)-1])] = regexArr + } + } + + // compare http resp headers + ok = matcher.CompareHeaders(pkg.ToHTTPHeader(tcs1.HTTPResp.Header), pkg.ToHTTPHeader(tcs2.HTTPResp.Header), &respCompare.HeadersResult, headerNoise) + if !ok { + logger.Debug("test case http resp headers are not equal", zap.Any("tcs1HttpRespHeaders", tcs1.HTTPResp.Header), zap.Any("tcs2HttpRespHeaders", tcs2.HTTPResp.Header)) + pass = false + } + + // compare http resp body + bodyType1 := models.Plain + if json.Valid([]byte(tcs1.HTTPResp.Body)) { + bodyType1 = models.JSON + } + + bodyType2 := models.Plain + if json.Valid([]byte(tcs2.HTTPResp.Body)) { + bodyType2 = models.JSON + } + + if bodyType1 != bodyType2 { + logger.Debug("test case http resp body type is not equal", zap.Any("tcs1HttpRespBodyType", bodyType1), zap.Any("tcs2HttpRespBodyType", bodyType2)) + pass = false + respCompare.BodyResult.Normal = false + return pass, respCompare + } + + bodyRes := true + + // stores the json body after removing the noise + cleanExp, cleanAct := tcs1.HTTPResp.Body, tcs2.HTTPResp.Body + var jsonComparisonResult matcher.JSONComparisonResult + if !matcher.Contains(matcher.MapToArray(noise), "body") && bodyType1 == models.JSON { + //validate the stored json + validatedJSON, err := matcher.ValidateAndMarshalJSON(logger, &cleanExp, &cleanAct) + if err != nil { + logger.Error("failed to validate and marshal json", zap.Error(err)) + respCompare.BodyResult.Normal = false + return false, respCompare + } + if validatedJSON.IsIdentical() { + jsonComparisonResult, err = matcher.JSONDiffWithNoiseControl(validatedJSON, bodyNoise, ignoreOrdering) + exact := jsonComparisonResult.IsExact() + if err != nil { + logger.Error("failed to compare json", zap.Error(err)) + respCompare.BodyResult.Normal = false + return false, respCompare + } + if !exact { + pass = false + bodyRes = false + } + } else { + pass = false + bodyRes = false + } + + // debug log for cleanExp and cleanAct + logger.Debug("cleanExp", zap.Any("", cleanExp)) + logger.Debug("cleanAct", zap.Any("", cleanAct)) + } else { + if !matcher.Contains(matcher.MapToArray(noise), "body") && tcs1.HTTPResp.Body != tcs2.HTTPResp.Body { + pass = false + bodyRes = false + } + } + + if !bodyRes { + respCompare.BodyResult.Normal = false + } + return pass, respCompare +} + +func CompareURLParams(urlParams1, urlParams2 map[string]string, urlParamsResult *[]models.URLParamsResult) bool { + pass := true + for k, v := range urlParams1 { + if v2, ok := urlParams2[k]; ok { + + if v != v2 { + pass = false + } + + *urlParamsResult = append(*urlParamsResult, models.URLParamsResult{ + Normal: v == v2, + Expected: models.Params{ + Key: k, + Value: v, + }, + Actual: models.Params{ + Key: k, + Value: v2, + }, + }) + } else { + pass = false + } + } + return pass +} + +func CompareNoise(noise1, noise2 map[string][]string) bool { + pass := true + for k, v := range noise1 { + if v2, ok := noise2[k]; ok { + if len(v) != len(v2) { + pass = false + } else { + for i := 0; i < len(v); i++ { + if v[i] != v2[i] { + pass = false + } + } + } + } else { + pass = false + } + } + return pass +} + +func CompareCurl(curl1, curl2 string, logger *zap.Logger) bool { + // Parse the values into method, URL, headers, and data + method1, url1, headers1, data1 := parseCurlString(curl1) + method2, url2, headers2, data2 := parseCurlString(curl2) + + // Compare method, URL, and data + if method1 != method2 || url1 != url2 || data1 != data2 { + return false + } + + // remove any quotes from the header keys (if any due to parsing issues) + for k, v := range headers1 { + delete(headers1, k) + headers1[strings.Trim(k, "'")] = v + } + for k, v := range headers2 { + delete(headers2, k) + headers2[strings.Trim(k, "'")] = v + } + + curlHeaderNoise := map[string][]string{} + curlHeaderNoise["keploy-test-id"] = []string{} + curlHeaderNoise["keploy-test-set-id"] = []string{} + headers1["Keploy-Test-Id"] = "dummyTest" + headers1["Keploy-Test-Set-Id"] = "dummyTestSet" + + hres := []models.HeaderResult{} + ok := matcher.CompareHeaders(pkg.ToHTTPHeader(headers1), pkg.ToHTTPHeader(headers2), &hres, curlHeaderNoise) + if !ok { + logger.Debug("test case curl headers are not equal", zap.Any("curlHeaderResult", hres)) + return false + } + return true +} + +func parseCurlString(curlString string) (method, url string, headers map[string]string, data string) { + lines := strings.Split(curlString, "\\") + headers = make(map[string]string) + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "--request") { + method = strings.TrimSpace(strings.Split(line, " ")[1]) + } else if strings.HasPrefix(line, "--url") { + url = strings.TrimSpace(strings.Split(line, " ")[1]) + } else if strings.HasPrefix(line, "--header") { + headerParts := strings.SplitN(strings.TrimSpace(strings.TrimPrefix(line, "--header")), ":", 2) + if len(headerParts) == 2 { + headers[strings.TrimSpace(headerParts[0])] = strings.TrimSpace(headerParts[1]) + } + } else if strings.HasPrefix(line, "--data") { + data = strings.TrimSpace(strings.TrimPrefix(line, "--data")) + } + } + return +} diff --git a/keploy/pkg/matcher/http/match.go b/keploy/pkg/matcher/http/match.go new file mode 100644 index 0000000..4489c39 --- /dev/null +++ b/keploy/pkg/matcher/http/match.go @@ -0,0 +1,536 @@ +// Package http for http matching +package http + +import ( + "encoding/json" + "fmt" + "net/http" + "regexp" + "strconv" + "strings" + + "go.uber.org/zap" + + "github.com/k0kubun/pp/v3" + "github.com/wI2L/jsondiff" + "go.keploy.io/server/v2/pkg" + matcherUtils "go.keploy.io/server/v2/pkg/matcher" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/service/tools" + "go.keploy.io/server/v2/utils" +) + +// Assignable global variables for system and utility functions +var jsonValid234 = json.Valid +var fmtSprint234 = fmt.Sprint +var ppNew234 = pp.New +var jsonMarshal234 = json.Marshal +var jsonUnmarshal234 = json.Unmarshal + +func Match(tc *models.TestCase, actualResponse *models.HTTPResp, noiseConfig map[string]map[string][]string, ignoreOrdering bool, logger *zap.Logger) (bool, *models.Result) { + bodyType := models.Plain + if jsonValid234([]byte(actualResponse.Body)) { + bodyType = models.JSON + } + + pass := true + hRes := &[]models.HeaderResult{} + res := &models.Result{ + StatusCode: models.IntResult{ + Normal: false, + Expected: tc.HTTPResp.StatusCode, + Actual: actualResponse.StatusCode, + }, + BodyResult: []models.BodyResult{{ + Normal: false, + Type: bodyType, + Expected: tc.HTTPResp.Body, + Actual: actualResponse.Body, + }}, + } + noise := tc.Noise + var ( + bodyNoise = noiseConfig["body"] + headerNoise = noiseConfig["header"] + ) + if bodyNoise != nil { + if ignoreFields, ok := bodyNoise["*"]; ok && len(ignoreFields) > 0 && ignoreFields[0] == "*" { + if noise["body"] == nil { + noise["body"] = make([]string, 0) + } + } + } else { + bodyNoise = map[string][]string{} + } + if headerNoise == nil { + headerNoise = map[string][]string{} + } + + for field, regexArr := range noise { + a := strings.Split(field, ".") + if len(a) > 1 && a[0] == "body" { + x := strings.Join(a[1:], ".") + bodyNoise[strings.ToLower(x)] = regexArr + } else if a[0] == "header" { + headerNoise[strings.ToLower(a[len(a)-1])] = regexArr + } + } + + // stores the json body after removing the noise + cleanExp, cleanAct := tc.HTTPResp.Body, actualResponse.Body + var jsonComparisonResult matcherUtils.JSONComparisonResult + if !matcherUtils.Contains(matcherUtils.MapToArray(noise), "body") && bodyType == models.JSON && jsonValid234([]byte(tc.HTTPResp.Body)) { + //validate the stored json + validatedJSON, err := matcherUtils.ValidateAndMarshalJSON(logger, &cleanExp, &cleanAct) + if err != nil { + return false, res + } + if validatedJSON.IsIdentical() { + jsonComparisonResult, err = matcherUtils.JSONDiffWithNoiseControl(validatedJSON, bodyNoise, ignoreOrdering) + pass = jsonComparisonResult.IsExact() + if err != nil { + return false, res + } + } else { + pass = false + } + + // debug log for cleanExp and cleanAct + logger.Debug("cleanExp", zap.Any("", cleanExp)) + logger.Debug("cleanAct", zap.Any("", cleanAct)) + } else { + if !matcherUtils.Contains(matcherUtils.MapToArray(noise), "body") && tc.HTTPResp.Body != actualResponse.Body { + pass = false + } + } + + res.BodyResult[0].Normal = pass + + if !matcherUtils.CompareHeaders(pkg.ToHTTPHeader(tc.HTTPResp.Header), pkg.ToHTTPHeader(actualResponse.Header), hRes, headerNoise) { + pass = false + } + + res.HeadersResult = *hRes + if tc.HTTPResp.StatusCode == actualResponse.StatusCode { + res.StatusCode.Normal = true + } else { + pass = false + } + + skipSuccessMsg := false + if !pass { + isStatusMismatch := false + isHeaderMismatch := false + isBodyMismatch := false + + logDiffs := matcherUtils.NewDiffsPrinter(tc.Name) + newLogger := ppNew234() + newLogger.WithLineInfo = false + newLogger.SetColorScheme(models.GetFailingColorScheme()) + var logs = "" + + logs = logs + newLogger.Sprintf("Testrun failed for testcase with id: %s\n\n--------------------------------------------------------------------\n\n", tc.Name) + + // ------------ DIFFS RELATED CODE ----------- + if !res.StatusCode.Normal { + logDiffs.PushStatusDiff(fmtSprint234(res.StatusCode.Expected), fmtSprint234(res.StatusCode.Actual)) + isStatusMismatch = true + } else { + isStatusMismatch = false + } + + var ( + actualHeader = map[string][]string{} + expectedHeader = map[string][]string{} + ) + + for _, j := range res.HeadersResult { + var actualValue []string + var expectedValue []string + if !j.Normal { + for _, v := range j.Actual.Value { + _, temp, err := tools.RenderIfTemplatized(v) + if err != nil { + utils.LogError(logger, err, "failed to render the actual header") + return false, nil + } + val, ok := temp.(string) + if !ok { + utils.LogError(logger, fmt.Errorf("failed to convert the actual header value to string while templatizing"), "") + return false, nil + } + actualValue = append(actualValue, val) + } + for _, v := range j.Expected.Value { + _, temp, err := tools.RenderIfTemplatized(v) + if err != nil { + utils.LogError(logger, err, "failed to render the expected header") + return false, nil + } + val, ok := temp.(string) + if !ok { + utils.LogError(logger, fmt.Errorf("failed to convert the expected header value to string while templatizing"), "") + return false, nil + } + expectedValue = append(expectedValue, val) + } + } + if len(actualValue) != len(expectedValue) { + isHeaderMismatch = true + actualHeader[j.Actual.Key] = actualValue + expectedHeader[j.Expected.Key] = expectedValue + } else { + for i, v := range actualValue { + if v != expectedValue[i] { + isHeaderMismatch = true + actualHeader[j.Actual.Key] = actualValue + expectedHeader[j.Expected.Key] = expectedValue + break + } + } + } + } + + if isHeaderMismatch { + for i, j := range expectedHeader { + logDiffs.PushHeaderDiff(fmtSprint234(j), fmtSprint234(actualHeader[i]), i, headerNoise) + } + } + + actRespBodyType := pkg.GuessContentType([]byte(actualResponse.Body)) + expRespBodyType := pkg.GuessContentType([]byte(tc.HTTPResp.Body)) + + if !res.BodyResult[0].Normal { + if actRespBodyType != expRespBodyType { + actRespBodyType = models.UnknownType + } + + switch actRespBodyType { + case models.JSON: + patch, err := jsondiff.Compare(cleanExp, cleanAct) + if err != nil { + logger.Warn("failed to compute json diff", zap.Error(err)) + } + + // Checking for templatized values. + for _, val := range patch { + // Parse the value in map. + expStringVal, ok := val.OldValue.(string) + if !ok { + continue + } + // Parse the body into json. + expResponse, err := matcherUtils.ParseIntoJSON(expStringVal) + if err != nil { + utils.LogError(logger, err, "failed to parse the exp response into json") + break + } + + actStringVal, ok := val.Value.(string) + if !ok { + continue + } + + actResponse, err := matcherUtils.ParseIntoJSON(actStringVal) + if err != nil { + utils.LogError(logger, err, "failed to parse the act response into json") + break + } + matcherUtils.CompareResponses(&expResponse, &actResponse, "") + jsonBytes, err := jsonMarshal234(expResponse) + if err != nil { + return false, nil + } + actJSONBytes, err := jsonMarshal234(actResponse) + if err != nil { + return false, nil + } + cleanExp = string(jsonBytes) + cleanAct = string(actJSONBytes) + } + validatedJSON, err := matcherUtils.ValidateAndMarshalJSON(logger, &cleanExp, &cleanAct) + if err != nil { + return false, res + } + isBodyMismatch = false + if validatedJSON.IsIdentical() { + jsonComparisonResult, err = matcherUtils.JSONDiffWithNoiseControl(validatedJSON, bodyNoise, ignoreOrdering) + if err != nil { + return false, res + } + if !jsonComparisonResult.IsExact() { + isBodyMismatch = true + } + } else { + isBodyMismatch = true + } + // Comparing the body again after updating the expected + patch, err = jsondiff.Compare(cleanExp, cleanAct) + if err != nil { + logger.Warn("failed to compute json diff", zap.Error(err)) + } + for _, op := range patch { + if jsonComparisonResult.Matches() { + logDiffs.SetHasarrayIndexMismatch(true) + logDiffs.PushFooterDiff(strings.Join(jsonComparisonResult.Differences(), ", ")) + } + logDiffs.PushBodyDiff(fmtSprint234(op.OldValue), fmtSprint234(op.Value), bodyNoise) + } + default: // right now for every other type we would do a simple comparison, till we don't have dedicated logic for other types. + if tc.HTTPResp.Body != actualResponse.Body { + isBodyMismatch = true + } + logDiffs.PushBodyDiff(fmtSprint234(tc.HTTPResp.Body), fmtSprint234(actualResponse.Body), bodyNoise) + } + } + + if isStatusMismatch || isHeaderMismatch || isBodyMismatch { + skipSuccessMsg = true + _, err := newLogger.Printf(logs) + if err != nil { + utils.LogError(logger, err, "failed to print the logs") + } + + err = logDiffs.Render() + if err != nil { + utils.LogError(logger, err, "failed to render the diffs") + } + } else { + pass = true + } + } + + if !skipSuccessMsg { + newLogger := ppNew234() + newLogger.WithLineInfo = false + newLogger.SetColorScheme(models.GetPassingColorScheme()) + var log2 = "" + log2 += newLogger.Sprintf("Testrun passed for testcase with id: %s\n\n--------------------------------------------------------------------\n\n", tc.Name) + _, err := newLogger.Printf(log2) + if err != nil { + utils.LogError(logger, err, "failed to print the logs") + } + } + + if len(tc.Assertions) > 1 || (len(tc.Assertions) == 1 && tc.Assertions[models.NoiseAssertion] == nil) { + return AssertionMatch(tc, actualResponse, logger) + } + + return pass, res +} + +// AssertionMatch checks the assertions in the test case against the actual response, if all of the assertions pass, it returns true, it doesn't care about other parameters of the response, +// and make the test case pass. + +// Assignable global variables for system and utility functions +var fmtSprintf234 = fmt.Sprintf +var strconvAtoi234 = strconv.Atoi + +func AssertionMatch(tc *models.TestCase, actualResponse *models.HTTPResp, logger *zap.Logger) (bool, *models.Result) { + pass := true + res := &models.Result{ + StatusCode: models.IntResult{ + Normal: false, + Expected: tc.HTTPResp.StatusCode, + Actual: actualResponse.StatusCode, + }, + BodyResult: []models.BodyResult{{ + Normal: false, + Expected: tc.HTTPResp.Body, + Actual: actualResponse.Body, + }}, + } + + for assertionName, value := range tc.Assertions { + switch assertionName { + + case models.StatusCode: + expected, err := toInt(value) + if err != nil || expected != actualResponse.StatusCode { + pass = false + logger.Error("status_code assertion failed", zap.Int("expected", expected), zap.Int("actual", actualResponse.StatusCode)) + } else { + res.StatusCode.Normal = true + } + + case models.StatusCodeClass: + class := toString(value) + var classStr string + if len(class) == 3 { + // handle if class given is status code without xx, e.g. 200 + if class[1:] != "xx" { + classStr = fmtSprintf234("%cxx", class[0]) + } else { + classStr = class + } + } else { + classStr = class + } + actualClass := fmtSprintf234("%dxx", 200/100) + if classStr != actualClass { + pass = false + logger.Error("status_code_class assertion failed", zap.String("expected", class), zap.String("actual", actualClass)) + } + + case models.StatusCodeIn: + codes := toStringSlice(value) + var ints []int + for _, s := range codes { + if i, err := strconvAtoi234(s); err == nil { + ints = append(ints, i) + } + } + found := false + for _, c := range ints { + if c == actualResponse.StatusCode { + found = true + break + } + } + if !found { + pass = false + logger.Error("status_code_in assertion failed", zap.Any("expectedCodes", ints), zap.Int("actual", actualResponse.StatusCode)) + } + + case models.HeaderEqual: + // value should be a map[string]interface{} → we convert to map[string]string + hm := toStringMap(value) + for header, exp := range hm { + act, ok := actualResponse.Header[header] + if !ok || act != exp { + pass = false + logger.Error("header_equal assertion failed", + zap.String("header", header), + zap.String("expected", exp), + zap.String("actual", act), + ) + } + logger.Info("header_equal assertion failed", + zap.String("header", header), + zap.String("expected", exp), + zap.String("actual", act), + ) + } + + case models.HeaderContains: + hm := toStringMap(value) + for header, exp := range hm { + act, ok := actualResponse.Header[header] + if !ok || !strings.Contains(act, exp) { + pass = false + logger.Error("header_contains assertion failed", + zap.String("header", header), + zap.String("expected_substr", exp), + zap.String("actual", act), + ) + } + } + + case models.HeaderExists: + switch v := value.(type) { + + // a flat slice of header names + case []interface{}: + for _, item := range v { + hdr := fmtSprint234(item) + if _, ok := actualResponse.Header[hdr]; !ok { + pass = false + logger.Error("header_exists assertion failed", zap.String("header", hdr)) + } + } + + // a map[string]… where the keys are header names + case map[string]interface{}: + for hdr := range v { + if _, ok := actualResponse.Header[hdr]; !ok { + pass = false + logger.Error("header_exists assertion failed", zap.String("header", hdr)) + } + } + + case map[models.AssertionType]interface{}: + for kt := range v { + hdr := string(kt) + if _, ok := actualResponse.Header[hdr]; !ok { + pass = false + logger.Error("header_exists assertion failed", zap.String("header", hdr)) + } + } + + default: + pass = false + logger.Error("header_exists: unsupported format, expected slice or map", zap.Any("value", value)) + } + + case models.HeaderMatches: + // value should be a map[string]interface{} → convert to map[string]string + hm := toStringMap(value) + for header, pattern := range hm { + act, ok := actualResponse.Header[header] + if !ok { + pass = false + logger.Error("header_matches: header not found", zap.String("header", header)) + continue + } + if matched, err := regexp.MatchString(pattern, act); err != nil || !matched { + pass = false + logger.Error("header_matches assertion failed", + zap.String("header", header), + zap.String("pattern", pattern), + zap.String("actual", act), + zap.Error(err), + ) + } + } + + case models.JsonEqual: + expJSON := tc.HTTPResp.Body + actJSON := actualResponse.Body + if expJSON != actJSON { + pass = false + logger.Error("json_equal assertion failed", zap.String("expected", expJSON), zap.String("actual", actJSON)) + } + + case models.JsonContains: + var expectedMap map[string]interface{} + switch v := value.(type) { + case map[string]interface{}: + expectedMap = v + case string: + _ = jsonUnmarshal234([]byte(v), &expectedMap) + default: + pass = false + logger.Error("json_contains: unexpected format", zap.Any("value", value)) + continue + } + if ok, _ := matcherUtils.JsonContains(actualResponse.Body, expectedMap); !ok { + pass = false + logger.Error("json_contains assertion failed", zap.Any("expected", expectedMap)) + } + + default: + if assertionName != models.NoiseAssertion { + logger.Warn("unhandled assertion type", zap.String("name", string(assertionName))) + } + } + } + + if pass { + res.StatusCode.Normal = true + res.BodyResult[0].Normal = true + } + + return pass, res +} + +func FlattenHTTPResponse(h http.Header, body string) (map[string][]string, error) { + m := map[string][]string{} + for k, v := range h { + m["header."+k] = []string{strings.Join(v, "")} + } + err := matcherUtils.AddHTTPBodyToMap(body, m) + if err != nil { + return m, err + } + return m, nil +} diff --git a/keploy/pkg/matcher/http/match_test.go b/keploy/pkg/matcher/http/match_test.go new file mode 100644 index 0000000..979448b --- /dev/null +++ b/keploy/pkg/matcher/http/match_test.go @@ -0,0 +1,236 @@ +package http + +import ( + "testing" + + "errors" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +// TestMatch_HeaderNoiseUpdate_123 ensures that the `headerNoise` map is updated correctly when the `noise` map contains a "header" key. +func TestMatch_HeaderNoiseUpdate_123(t *testing.T) { + // Arrange + logger := zap.NewNop() + tc := &models.TestCase{ + HTTPResp: models.HTTPResp{ + StatusCode: 200, + Header: map[string]string{"Content-Type": "application/json"}, + Body: `{"key":"value"}`, + }, + Noise: map[string][]string{ + "header.Content-Type": {"regex"}, + }, + } + actualResponse := &models.HTTPResp{ + StatusCode: 200, + Header: map[string]string{"Content-Type": "application/json"}, + Body: `{"key":"value"}`, + } + noiseConfig := map[string]map[string][]string{ + "header": {}, + } + ignoreOrdering := false + + // Act + pass, result := Match(tc, actualResponse, noiseConfig, ignoreOrdering, logger) + + // Assert + require.NotNil(t, result) + assert.True(t, pass) + assert.Contains(t, noiseConfig["header"], "content-type") + assert.Equal(t, []string{"regex"}, noiseConfig["header"]["content-type"]) +} + +// TestMatch_FailureAndDiffLogging_890 tests the Match function with comprehensive failures +// in status code, headers, and JSON body to ensure that the diff logging mechanism is triggered. +func TestMatch_FailureAndDiffLogging_890(t *testing.T) { + // Arrange + logger := zap.NewNop() + tc := &models.TestCase{ + Name: "test-comprehensive-fail", + HTTPResp: models.HTTPResp{ + StatusCode: 200, + Header: map[string]string{"Expected-Header": "value1"}, + Body: `{"id": 1, "value": "expected"}`, + }, + } + actualResponse := &models.HTTPResp{ + StatusCode: 404, // Mismatch + Header: map[string]string{"Actual-Header": "value2"}, + Body: `{"id": 2, "value": "actual"}`, // Mismatch + } + noiseConfig := map[string]map[string][]string{} + ignoreOrdering := false + + // Act + pass, result := Match(tc, actualResponse, noiseConfig, ignoreOrdering, logger) + + // Assert + assert.False(t, pass, "Should fail due to multiple mismatches") + require.NotNil(t, result) + assert.False(t, result.StatusCode.Normal) + assert.False(t, result.BodyResult[0].Normal) + // We can't easily assert the console output, but by running this + // we exercise the entire diff generation logic in lines 121-301. +} + +// TestMatch_BodyNoiseFromTestCase_124 verifies that the Match function correctly applies +// noise rules defined within the TestCase's Noise field to ignore specific JSON body fields. +func TestMatch_BodyNoiseFromTestCase_124(t *testing.T) { + // Arrange + logger := zap.NewNop() + tc := &models.TestCase{ + Name: "test-body-noise-from-tc", + HTTPResp: models.HTTPResp{ + StatusCode: 200, + Body: `{"id": 123, "name": "expected"}`, + }, + Noise: map[string][]string{ + "body.id": {".*"}, // Ignore the 'id' field + }, + } + actualResponse := &models.HTTPResp{ + StatusCode: 200, + Body: `{"id": 456, "name": "expected"}`, // Only 'id' is different + } + noiseConfig := map[string]map[string][]string{} + ignoreOrdering := false + + // Act + pass, result := Match(tc, actualResponse, noiseConfig, ignoreOrdering, logger) + + // Assert + assert.True(t, pass, "Should pass because the 'id' field difference is covered by noise") + require.NotNil(t, result) + assert.True(t, result.StatusCode.Normal) + assert.True(t, result.BodyResult[0].Normal) +} + +// TestMatch_RedirectToAssertionMatch_567 ensures that if a TestCase contains assertions, +// the Match function correctly calls AssertionMatch and returns its result. +func TestMatch_RedirectToAssertionMatch_567(t *testing.T) { + // Arrange + logger := zap.NewNop() + tc := &models.TestCase{ + Name: "test-redirect-to-assertion", + HTTPResp: models.HTTPResp{ + StatusCode: 201, // Deliberate mismatch to show normal matching would fail + Body: `{"key":"wrong"}`, + }, + Assertions: map[models.AssertionType]interface{}{ + models.StatusCode: 200, + models.JsonContains: map[string]interface{}{ + "key": "value", + }, + }, + } + actualResponse := &models.HTTPResp{ + StatusCode: 200, + Body: `{"key":"value", "other": "stuff"}`, + } + noiseConfig := map[string]map[string][]string{} + ignoreOrdering := false + + // Act + pass, result := Match(tc, actualResponse, noiseConfig, ignoreOrdering, logger) + + // Assert + assert.True(t, pass, "AssertionMatch should be called and return true") + require.NotNil(t, result) + assert.True(t, result.StatusCode.Normal) + assert.True(t, result.BodyResult[0].Normal) +} + +// TestMatch_InvalidJSONBody_321 ensures that when the actual response body is not valid JSON, +// it is treated as plain text and compared directly, leading to a mismatch if different. +func TestMatch_InvalidJSONBody_321(t *testing.T) { + logger := zap.NewNop() + tc := &models.TestCase{ + HTTPResp: models.HTTPResp{ + StatusCode: 200, + Body: `{"id": "123", "name": "keploy"}`, + }, + } + actualResponse := &models.HTTPResp{ + StatusCode: 200, + Body: `{"id": "123", "name": "keploy"`, // Invalid JSON + } + noiseConfig := map[string]map[string][]string{} + + pass, res := Match(tc, actualResponse, noiseConfig, false, logger) + + assert.False(t, pass) + assert.False(t, res.BodyResult[0].Normal) + assert.Equal(t, models.Plain, res.BodyResult[0].Type) +} + +// TestMatch_JsonMarshalErrorInDiff_987 simulates a failure in json.Marshal when generating +// diffs for a failed test case to ensure the error is handled gracefully. +func TestMatch_JsonMarshalErrorInDiff_987(t *testing.T) { + logger := zap.NewNop() + tc := &models.TestCase{ + Name: "test-marshal-error", + HTTPResp: models.HTTPResp{ + StatusCode: 200, + Body: `{"id": 1, "value": "expected"}`, + }, + } + actualResponse := &models.HTTPResp{ + StatusCode: 200, + Body: `{"id": 1, "value": "actual"}`, + } + noiseConfig := map[string]map[string][]string{} + + originalJSONMarshal := jsonMarshal234 + jsonMarshal234 = func(v interface{}) ([]byte, error) { + // This mock will fail the first time json.Marshal is called within the diffing logic. + return nil, errors.New("mock marshal error") + } + defer func() { jsonMarshal234 = originalJSONMarshal }() + + pass, res := Match(tc, actualResponse, noiseConfig, false, logger) + + // The function returns (false, nil) on this specific error path + assert.False(t, pass) + assert.Nil(t, res) +} + +// TestMatch_BodyNoiseWildcard_789 tests the scenario where a global noise configuration +// specifies that the entire body should be ignored ("*": "*"). Even if the actual +// response body is completely different from the expected one, the match should pass. +// It also verifies that the test case's noise map for the body is initialized. +func TestMatch_BodyNoiseWildcard_789(t *testing.T) { + logger := zap.NewNop() + tc := &models.TestCase{ + Name: "test-wildcard-noise", + HTTPResp: models.HTTPResp{ + StatusCode: 200, + Body: `{"id": 1, "name": "keploy"}`, + }, + Noise: map[string][]string{}, // Noise is empty in TC + } + actualResponse := &models.HTTPResp{ + StatusCode: 200, + Body: `{"id": 2, "name": "keploy-test"}`, // Body is completely different + } + // Global noise config says to ignore the entire body + noiseConfig := map[string]map[string][]string{ + "body": {"*": {"*"}}, + } + ignoreOrdering := false + + // Act + pass, result := Match(tc, actualResponse, noiseConfig, ignoreOrdering, logger) + + // Assert + assert.True(t, pass, "Should pass because the entire body is ignored by wildcard noise") + require.NotNil(t, result) + assert.True(t, result.StatusCode.Normal) + assert.True(t, result.BodyResult[0].Normal) + // Check that tc.Noise["body"] was initialized + assert.NotNil(t, tc.Noise["body"]) +} diff --git a/keploy/pkg/matcher/http/utils.go b/keploy/pkg/matcher/http/utils.go new file mode 100644 index 0000000..7b39571 --- /dev/null +++ b/keploy/pkg/matcher/http/utils.go @@ -0,0 +1,103 @@ +package http + +import ( + "encoding/json" + "fmt" + "strconv" + + "strings" + + "go.keploy.io/server/v2/pkg/models" +) + +func toInt(v interface{}) (int, error) { + switch x := v.(type) { + case int: + return x, nil + case float64: + return int(x), nil + case json.Number: + i64, err := x.Int64() + return int(i64), err + case string: + i64, err := strconv.ParseInt(x, 10, 64) + if err != nil { + return 0, err + } + const maxInt = int64(^uint(0) >> 1) // Maximum value for int + const minInt = -maxInt - 1 // Minimum value for int + if i64 > maxInt || i64 < minInt { + return 0, fmt.Errorf("value out of range for int: %d", i64) + } + return int(i64), nil + default: + return 0, fmt.Errorf("cannot convert %T to int", v) + } +} + +func toString(v interface{}) string { + switch x := v.(type) { + case string: + return x + case json.Number: + return x.String() + case float64: + return strconv.FormatFloat(x, 'f', -1, 64) + case int: + return strconv.Itoa(x) + default: + return fmt.Sprintf("%v", v) + } +} + +func toStringSlice(v interface{}) []string { + var out []string + switch x := v.(type) { + case []interface{}: + for _, e := range x { + out = append(out, toString(e)) + } + case string: + for _, part := range strings.Split(x, ",") { + out = append(out, strings.TrimSpace(part)) + } + } + return out +} + +func toStringMap(val interface{}) map[string]string { + out := make(map[string]string) + switch m := val.(type) { + case map[string]interface{}: + for k, v := range m { + out[k] = fmt.Sprint(v) + } + + case map[string]string: + // already the right shape + for k, v := range m { + out[k] = v + } + + case map[models.AssertionType]interface{}: + for kType, v := range m { + out[string(kType)] = fmt.Sprint(v) + } + + case map[models.AssertionType]string: + for kType, v := range m { + out[string(kType)] = v + } + + case map[interface{}]interface{}: + // sometimes YAML v3 gives you this + for ki, vi := range m { + key := fmt.Sprint(ki) + out[key] = fmt.Sprint(vi) + } + + default: + // not a map we know—return empty + } + return out +} diff --git a/keploy/pkg/matcher/schema/match.go b/keploy/pkg/matcher/schema/match.go new file mode 100644 index 0000000..d87662c --- /dev/null +++ b/keploy/pkg/matcher/schema/match.go @@ -0,0 +1,262 @@ +// Package schema for schema matching +package schema + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + + "github.com/k0kubun/pp/v3" + "github.com/wI2L/jsondiff" + matcher "go.keploy.io/server/v2/pkg/matcher" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type ValidatedJSONWrapper struct { + Expected interface{} `json:"expected"` + Actual interface{} `json:"actual"` + IsIdentical bool `json:"isIdentical"` +} +type JSONComparisonResultWrapper struct { + Matches bool `json:"matches"` + IsExact bool `json:"isExact"` + Differences []string `json:"differences"` +} + +const NOTCANDIDATE = -1.0 + +func compareOperationTypes(mockOperationType, testOperationType string) (bool, error) { + pass := true + if mockOperationType != testOperationType { + pass = false + return pass, nil + + } + return pass, nil +} +func compareRequestBodies(mockOperation, testOperation *models.Operation, logDiffs matcher.DiffsPrinter, newLogger *pp.PrettyPrinter, logger *zap.Logger, testName, mockName, testSetID, mockSetID string) (bool, error) { + pass := false + var score float64 + mockRequestBodyStr, testRequestBodyStr, err := matcher.MarshalRequestBodies(mockOperation, testOperation) + if err != nil { + return false, err + } + + validatedJSON, err := matcher.ValidateAndMarshalJSON(logger, &mockRequestBodyStr, &testRequestBodyStr) + if err != nil { + return false, err + } + + if validatedJSON.IsIdentical() { + if score, pass, err = handleJSONDiff(validatedJSON, logDiffs, newLogger, logger, testName, mockName, testSetID, mockSetID, mockRequestBodyStr, testRequestBodyStr, "request", 0); err != nil { + return false, err + } + if score == NOTCANDIDATE { + return false, nil + } + + } else { + pass = false + return pass, nil + + } + return pass, nil +} + +func compareParameters(mockParameters, testParameters []models.Parameter) (bool, error) { + pass := true + + for _, mockParam := range mockParameters { + if mockParam.In == "header" { + continue + } + found := false + for _, testParam := range testParameters { + if mockParam.Name == testParam.Name && mockParam.In == testParam.In { + found = true + if mockParam.Schema.Type != testParam.Schema.Type { + pass = false + return pass, nil + } + } + } + if !found { + pass = false + return pass, nil + } + } + + return pass, nil +} + +func compareResponseBodies(status string, mockOperation, testOperation *models.Operation, logDiffs matcher.DiffsPrinter, newLogger *pp.PrettyPrinter, logger *zap.Logger, testName, mockName, testSetID, mockSetID string, mode models.SchemaMatchMode) (float64, bool, bool, error) { + pass := true + overallScore := 0.0 + matched := false + differencesCount := 0.0 + if _, ok := testOperation.Responses[status]; ok { + mockResponseBodyStr, testResponseBodyStr, err := matcher.MarshalResponseBodies(status, mockOperation, testOperation) + if err != nil { + return differencesCount, false, false, err + } + overallScore = float64(len(mockOperation.Responses[status].Content["application/json"].Schema.Properties)) + validatedJSON, err := matcher.ValidateAndMarshalJSON(logger, &mockResponseBodyStr, &testResponseBodyStr) + if err != nil { + return differencesCount, false, false, err + } + + if validatedJSON.IsIdentical() { + switch mode { + case models.CompareMode: + if _, matched, err = handleJSONDiff(validatedJSON, logDiffs, newLogger, logger, testName, mockName, testSetID, mockSetID, mockResponseBodyStr, testResponseBodyStr, "response", mode); err != nil { + return differencesCount, false, false, err + } + case models.IdentifyMode: + differencesCount, err = calculateSimilarityScore(mockOperation, testOperation, status) + if err != nil { + return differencesCount, false, false, err + } + } + } else { + differencesCount = overallScore + + if mode == models.CompareMode { + logDiffs.PushTypeDiff(fmt.Sprint(reflect.TypeOf(validatedJSON.Expected())), fmt.Sprint(reflect.TypeOf(validatedJSON.Actual()))) + logs := newLogger.Sprintf("Contract Check failed for test: %s (%s) / mock: %s (%s) \n\n--------------------------------------------------------------------\n\n", testName, testSetID, mockName, mockSetID) + + if err := printAndRenderLogs(logs, newLogger, logDiffs, logger); err != nil { + return differencesCount, false, false, err + } + } + } + } else { + pass = false + differencesCount = -1 + + } + return differencesCount / overallScore, pass, matched, nil +} +func Match(mock, test models.OpenAPI, testSetID string, mockSetID string, logger *zap.Logger, mode models.SchemaMatchMode) (float64, bool, error) { + pass := false + + candidateScore := -1.0 + newLogger := pp.New() + newLogger.WithLineInfo = false + newLogger.SetColorScheme(models.GetFailingColorScheme()) + + for path, mockItem := range mock.Paths { + logDiffs := matcher.NewDiffsPrinter(test.Info.Title + "/" + mock.Info.Title) + var err error + if testItem, found := test.Paths[path]; found { + mockOperation, mockOperationType := matcher.FindOperation(mockItem) + testOperation, testOperationType := matcher.FindOperation(testItem) + if mode == models.IdentifyMode { + if pass, err = compareOperationTypes(mockOperationType, testOperationType); err != nil { + return candidateScore, false, err + } + if !pass { + continue + } + if pass, err = compareParameters(mockOperation.Parameters, testOperation.Parameters); err != nil { + return candidateScore, false, err + } + if !pass { + continue + } + if pass, err = compareRequestBodies(mockOperation, testOperation, logDiffs, newLogger, logger, test.Info.Title, mock.Info.Title, testSetID, mockSetID); err != nil { + return candidateScore, false, err + } + if !pass { + continue + } + } + var statusCode string + for status := range mockOperation.Responses { + statusCode = status + break + + } + + if candidateScore, pass, _, err = compareResponseBodies(statusCode, mockOperation, testOperation, logDiffs, newLogger, logger, test.Info.Title, mock.Info.Title, testSetID, mockSetID, mode); err != nil { + return candidateScore, false, err + } + + } else { + pass = false + + } + + } + + return candidateScore, pass, nil +} +func calculateSimilarityScore(mockOperation, testOperation *models.Operation, status string) (float64, error) { + testParameters := testOperation.Responses[status].Content["application/json"].Schema.Properties + mockParameters := mockOperation.Responses[status].Content["application/json"].Schema.Properties + score := 0.0 + for key, testParam := range testParameters { + if _, ok := mockParameters[key]; ok { + if testParam["type"] == mockParameters[key]["type"] { + score++ + } + } + } + return score, nil +} + +func handleJSONDiff(validatedJSON matcher.ValidatedJSON, logDiffs matcher.DiffsPrinter, newLogger *pp.PrettyPrinter, logger *zap.Logger, _ string, _ string, _ string, _ string, mockBodyStr string, testBodyStr string, diffType string, mode models.SchemaMatchMode) (float64, bool, error) { + pass := true + differencesCount := 0.0 + jsonComparisonResult, err := matcher.JSONDiffWithNoiseControl(validatedJSON, nil, false) + if err != nil { + return differencesCount, false, err + } + if !jsonComparisonResult.IsExact() { + pass = false + // logs := newLogger.Sprintf("Contract Check failed for test: %s (%s) / mock: %s (%s) \n\n--------------------------------------------------------------------\n\n", testName, testSetID, mockName, mockSetID) + if json.Valid([]byte(mockBodyStr)) { + patch, err := jsondiff.Compare(testBodyStr, mockBodyStr) + if err != nil { + logger.Warn("failed to compute json diff", zap.Error(err)) + return differencesCount, false, err + } + differencesCount = float64(len(patch)) + if diffType == "request" && differencesCount > 1 { + return -1.0, false, nil + } + if diffType == "response" { + for _, op := range patch { + if jsonComparisonResult.Matches() { + logDiffs.SetHasarrayIndexMismatch(true) + logDiffs.PushFooterDiff(strings.Join(jsonComparisonResult.Differences(), ", ")) + } + + logDiffs.PushBodyDiff(fmt.Sprint(op.OldValue), fmt.Sprint(op.Value), nil) + + } + } + } + if diffType == "response" && mode == models.CompareMode { + if err := printAndRenderLogs("", newLogger, logDiffs, logger); err != nil { + return differencesCount, false, err + } + + } + } + return differencesCount, pass, nil +} + +func printAndRenderLogs(logs string, newLogger *pp.PrettyPrinter, logDiffs matcher.DiffsPrinter, logger *zap.Logger) error { + if _, err := newLogger.Printf(logs); err != nil { + utils.LogError(logger, err, "failed to print the logs") + return err + } + if err := logDiffs.RenderAppender(); err != nil { + utils.LogError(logger, err, "failed to render the diffs") + return err + } + return nil +} diff --git a/keploy/pkg/matcher/utils.go b/keploy/pkg/matcher/utils.go new file mode 100644 index 0000000..13da402 --- /dev/null +++ b/keploy/pkg/matcher/utils.go @@ -0,0 +1,1207 @@ +// Package matcher for matching utilities +package matcher + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "reflect" + "regexp" + "strconv" + "strings" + "sync" + + "github.com/7sDream/geko" + "github.com/fatih/color" + jsonDiff "github.com/keploy/jsonDiff" + "github.com/olekukonko/tablewriter" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +var ( + regexCacheMu sync.RWMutex + regexCache = make(map[string]*regexp.Regexp) +) + +// getCompiled returns a cached compiled regexp for pattern. +// It never panics: on invalid patterns, it returns a "never matches" regex (?!) and caches it. +func getCompiled(pattern string) *regexp.Regexp { + regexCacheMu.RLock() + re := regexCache[pattern] + regexCacheMu.RUnlock() + if re != nil { + return re + } + + // Compile outside the read lock. + compiled, err := regexp.Compile(pattern) + if err != nil { + // Fallback to a regex that never matches to avoid panics / repeated compiles + compiled, _ = regexp.Compile(`(?!)`) + } + + regexCacheMu.Lock() + // Double-check to avoid races. + if old := regexCache[pattern]; old == nil { + regexCache[pattern] = compiled + } else { + compiled = old + } + regexCacheMu.Unlock() + return compiled +} + +func MatchesAnyRegex(str string, regexArray []string) (bool, string) { + for _, pattern := range regexArray { + if getCompiled(pattern).MatchString(str) { + return true, pattern + } + } + return false, "" +} + +type noiseEntry struct { + keyLower string + regexps []*regexp.Regexp // empty => ignore subtree +} +type noiseIndex struct { + entries []noiseEntry +} + +func buildNoiseIndex(mp map[string][]string) noiseIndex { + if mp == nil { + return noiseIndex{} + } + out := noiseIndex{entries: make([]noiseEntry, 0, len(mp))} + for k, arr := range mp { + e := noiseEntry{keyLower: strings.ToLower(k)} + if len(arr) > 0 { + e.regexps = make([]*regexp.Regexp, 0, len(arr)) + for _, p := range arr { + e.regexps = append(e.regexps, getCompiled(p)) + } + } + out.entries = append(out.entries, e) + } + return out +} + +func (ni noiseIndex) match(keyLower string) (regs []*regexp.Regexp, isNoisy bool) { + for _, e := range ni.entries { + if strings.Contains(keyLower, e.keyLower) { + return e.regexps, true + } + } + return nil, false +} + +func JSONDiffWithNoiseControl(validatedJSON ValidatedJSON, noise map[string][]string, ignoreOrdering bool) (JSONComparisonResult, error) { + idx := buildNoiseIndex(noise) + return matchJSONWithNoiseHandlingIndexed("", validatedJSON.expected, validatedJSON.actual, idx, ignoreOrdering) +} + +// Back-compat shim used by a few spots internally (if any). +func matchJSONWithNoiseHandling(key string, expected, actual interface{}, noiseMap map[string][]string, ignoreOrdering bool) (JSONComparisonResult, error) { + return matchJSONWithNoiseHandlingIndexed(key, expected, actual, buildNoiseIndex(noiseMap), ignoreOrdering) +} + +// New optimized implementation. +func matchJSONWithNoiseHandlingIndexed(key string, expected, actual interface{}, ni noiseIndex, ignoreOrdering bool) (JSONComparisonResult, error) { + var out JSONComparisonResult + // Type check fast-path (JSON unmarshal produces these concrete types). + switch e := expected.(type) { + case nil: + if actual == nil { + out.matches, out.isExact = true, true + } + return out, nil + + case string: + a, ok := actual.(string) + if !ok { + return out, errors.New("type not matched") + } + regs, noisy := ni.match(strings.ToLower(key)) + if noisy && len(regs) != 0 { + if anyRegexpMatchStr(a, regs) { + out.matches, out.isExact = true, true + return out, nil + } + } + if e == a || noisy { + out.matches, out.isExact = true, true + } + return out, nil + + case bool: + a, ok := actual.(bool) + if !ok { + return out, errors.New("type not matched") + } + _, noisy := ni.match(strings.ToLower(key)) + if e == a || noisy { + out.matches, out.isExact = true, true + } + return out, nil + + case float64: + a, ok := actual.(float64) + if !ok { + return out, errors.New("type not matched") + } + _, noisy := ni.match(strings.ToLower(key)) + if e == a || noisy { + out.matches, out.isExact = true, true + } + return out, nil + + case map[string]interface{}: + a, ok := actual.(map[string]interface{}) + if !ok { + return out, errors.New("type not matched") + } + + // If whole subtree is noisy (no regex guard), accept. + if regs, noisy := ni.match(strings.ToLower(key)); noisy && len(regs) == 0 { + out.matches, out.isExact = true, true + return out, nil + } + + // Quick length check — allows early exit if extra/missing keys (ignoring noisy children). + // We still need to walk to account for noisy exclusions; so we won't return solely on len. + isExact := true + + // Lowercased prefix once. + prefix := "" + if key != "" { + prefix = key + "." + } + prefixLower := strings.ToLower(prefix) + + // 1) All expected keys must be present & match. + for k, v := range e { + val, ok := a[k] + if !ok { + return out, nil + } + childKeyLower := prefixLower + strings.ToLower(k) + + // If child subtree is entirely noisy, skip deep compare. + if regs, noisy := ni.match(childKeyLower); noisy && len(regs) == 0 { + continue + } + + res, err := matchJSONWithNoiseHandlingIndexed(prefix+k, v, val, ni, ignoreOrdering) + if err != nil || !res.matches { + return out, nil + } + if !res.isExact { + isExact = false + out.differences = append(out.differences, k) + out.differences = append(out.differences, res.differences...) + } + } + + // 2) No unexpected non-noisy keys in actual. + for k := range a { + if _, ok := e[k]; ok { + continue + } + childKeyLower := prefixLower + strings.ToLower(k) + if regs, noisy := ni.match(childKeyLower); noisy && len(regs) == 0 { + continue // ignore unexpected but noisy subtree + } + return out, nil + } + + out.matches, out.isExact = true, isExact + return out, nil + + case []interface{}: + a, ok := actual.([]interface{}) + if !ok { + return out, errors.New("type not matched") + } + if len(e) != len(a) { + return out, nil + } + + // If the whole slice is marked noisy-without-regex, accept. + if regs, noisy := ni.match(strings.ToLower(key)); noisy && len(regs) == 0 { + out.matches, out.isExact = true, true + return out, nil + } + + // Fast path: if ordering matters, avoid O(n²). + if !ignoreOrdering { + isExact := true + for i := 0; i < len(e); i++ { + res, err := matchJSONWithNoiseHandlingIndexed(key, e[i], a[i], ni, ignoreOrdering) + if err != nil || !res.matches { + return out, nil + } + if !res.isExact { + isExact = false + } + } + out.matches, out.isExact = true, isExact + return out, nil + } + + // ignoreOrdering == true: greedy matching with "used" flags to avoid reusing elements. + used := make([]bool, len(a)) + isExact := true + + for i := 0; i < len(e); i++ { + matched := false + // Try primitive fast match first to reduce recursion. + if j, ok := findAndClaimPrimitiveEqual(e[i], a, used); ok { + used[j] = true + matched = true + } else { + // Fallback to structural match. + for j := 0; j < len(a); j++ { + if used[j] { + continue + } + childKey := "" // by design: no index prefix at root mixed objects + res, err := matchJSONWithNoiseHandlingIndexed(childKey, e[i], a[j], ni, ignoreOrdering) + if err == nil && res.matches { + if !res.isExact { + isExact = false + for _, v := range res.differences { + if childKey != "" { + v = childKey + "." + v + } + out.differences = append(out.differences, v) + } + } + used[j] = true + matched = true + break + } + } + } + if !matched { + out.matches, out.isExact = false, false + return out, nil + } + } + + out.matches, out.isExact = true, isExact + return out, nil + } + + return out, errors.New("type not registered for json") +} + +func anyRegexpMatchStr(s string, regs []*regexp.Regexp) bool { + for _, re := range regs { + if re.MatchString(s) { + return true + } + } + return false +} + +func findAndClaimPrimitiveEqual(x interface{}, arr []interface{}, used []bool) (int, bool) { + switch v := x.(type) { + case string: + for i, y := range arr { + if used[i] { + continue + } + if ys, ok := y.(string); ok && ys == v { + return i, true + } + } + case float64: + for i, y := range arr { + if used[i] { + continue + } + if yf, ok := y.(float64); ok && yf == v { + return i, true + } + } + case bool: + for i, y := range arr { + if used[i] { + continue + } + if yb, ok := y.(bool); ok && yb == v { + return i, true + } + } + } + return -1, false +} + +type ValidatedJSON struct { + expected interface{} // The expected JSON + actual interface{} // The actual JSON + isIdentical bool +} + +func (v *ValidatedJSON) IsIdentical() bool { + return v.isIdentical +} +func (v *ValidatedJSON) Expected() interface{} { + return v.expected +} +func (v *ValidatedJSON) Actual() interface{} { + return v.actual +} + +type JSONComparisonResult struct { + matches bool // Indicates if the JSON strings match according to the criteria + isExact bool // Indicates if the match is exact, considering ordering and noise + differences []string // Lists the keys or indices of values that are not the same +} + +func (v *JSONComparisonResult) IsExact() bool { + return v.isExact +} +func (v *JSONComparisonResult) Matches() bool { + return v.matches +} +func (v *JSONComparisonResult) Differences() []string { + return v.differences +} +func MarshalRequestBodies(mockOperation, testOperation *models.Operation) (string, string, error) { + var mockRequestBody []byte + var testRequestBody []byte + var err error + if mockOperation.RequestBody != nil { + mockRequestBody, err = json.Marshal(mockOperation.RequestBody.Content["application/json"].Schema.Properties) + if err != nil { + return "", "", fmt.Errorf("error marshalling mock RequestBody: %v", err) + } + } + if testOperation.RequestBody != nil { + testRequestBody, err = json.Marshal(testOperation.RequestBody.Content["application/json"].Schema.Properties) + if err != nil { + return "", "", fmt.Errorf("error marshalling test RequestBody: %v", err) + } + } + return string(mockRequestBody), string(testRequestBody), nil +} + +func MarshalResponseBodies(status string, mockOperation, testOperation *models.Operation) (string, string, error) { + var mockResponseBody []byte + var testResponseBody []byte + var err error + if mockOperation.Responses[status].Content != nil { + mockResponseBody, err = json.Marshal(mockOperation.Responses[status].Content["application/json"].Schema.Properties) + if err != nil { + return "", "", fmt.Errorf("error marshalling mock ResponseBody: %v", err) + } + } + if testOperation.Responses[status].Content != nil { + testResponseBody, err = json.Marshal(testOperation.Responses[status].Content["application/json"].Schema.Properties) + if err != nil { + return "", "", fmt.Errorf("error marshalling test ResponseBody: %v", err) + } + } + return string(mockResponseBody), string(testResponseBody), nil +} +func FindOperation(item models.PathItem) (*models.Operation, string) { + operations := map[string]*models.Operation{ + "GET": item.Get, + "POST": item.Post, + "PUT": item.Put, + "DELETE": item.Delete, + "PATCH": item.Patch, + } + + for method, operation := range operations { + if operation != nil { + return operation, method + } + } + return nil, "" +} + +// ParseIntoJSON Parse the json string into a geko type variable, it will maintain the order of the keys in the json. +func ParseIntoJSON(response string) (interface{}, error) { + // Parse the response into a json object. + if response == "" { + return nil, nil + } + result, err := geko.JSONUnmarshal([]byte(response)) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal the response: %v", err) + } + return result, nil +} + +// CompareResponses compares the two responses, if there is any difference in the values, +// It checks in the templatized values map if the value is already present, it will update the value in the map. +// It also changes the expected value to the actual value in the response1 (expected body) +func CompareResponses(response1, response2 *interface{}, key string) { + switch v1 := (*response1).(type) { + case geko.Array: + for _, val1 := range v1.List { + CompareResponses(&val1, response2, "") + } + case geko.ObjectItems: + keys := v1.Keys() + vals := v1.Values() + for i := range keys { + CompareResponses(&vals[i], response2, keys[i]) + v1.SetValueByIndex(i, vals[i]) // in order to change the expected value if required + } + case map[string]interface{}: + for key, val := range v1 { + CompareResponses(&val, response2, key) + v1[key] = val // in order to change the expected value if required + } + case string: + compareSecondResponse(&v1, response2, key, "") + *response1 = v1 + case float64, int64, int, float32: + v1String := utils.ToString(v1) + compareSecondResponse(&(v1String), response2, key, "") + // Retain the original type + switch (*response1).(type) { + case float64: + *response1 = utils.ToFloat(v1String) + case int: + *response1 = utils.ToInt(v1String) + case int64: + *response1 = utils.ToInt(v1String) + case float32: + f := utils.ToFloat(v1String) + *response1 = float32(f) + default: + *response1 = v1 // Keep it unchanged if it’s an unexpected type + } + } +} + +// Simplify the second response into type string for comparison. +func compareSecondResponse(val1 *string, response2 *interface{}, key1 string, key2 string) { + switch v2 := (*response2).(type) { + case geko.Array: + for _, val2 := range v2.List { + compareSecondResponse(val1, &val2, key1, "") + } + + case geko.ObjectItems: + keys := v2.Keys() + vals := v2.Values() + for i := range keys { + compareSecondResponse(val1, &vals[i], key1, keys[i]) + } + case map[string]interface{}: + for key, val := range v2 { + compareSecondResponse(val1, &val, key1, key) + } + case string: + if *val1 != v2 { + // Reverse the templatized values map. + revMap := reverseMap(utils.TemplatizedValues) + if _, ok := revMap[*val1]; ok && key1 == key2 { + key := revMap[*val1] + utils.TemplatizedValues[key] = v2 + *val1 = v2 + } + } + case float64, int64, int, float32: + if *val1 != v2 { + + // Reverse the templatized values map. + revMap := reverseMap(utils.TemplatizedValues) + if _, ok := revMap[*val1]; ok && key1 == key2 { + key := revMap[*val1] + utils.TemplatizedValues[key] = v2 + *val1 = utils.ToString(v2) + return + } + // 1) try integer parse + if i, err := strconv.Atoi(*val1); err == nil { + if _, ok := revMap[i]; ok && key1 == key2 { + key := revMap[i] + utils.TemplatizedValues[key] = v2 + *val1 = utils.ToString(v2) + } + } + if f, err := strconv.ParseFloat(*val1, 32); err == nil { + if _, ok := revMap[f]; ok && key1 == key2 { + key := revMap[f] + utils.TemplatizedValues[key] = v2 + *val1 = utils.ToString(v2) + } + } + if f, err := strconv.ParseFloat(*val1, 64); err == nil { + if _, ok := revMap[f]; ok && key1 == key2 { + + key := revMap[*val1] + utils.TemplatizedValues[key] = v2 + *val1 = utils.ToString(v2) + } + } + + } + } +} +func reverseMap(m map[string]interface{}) map[interface{}]string { + var reverseMap = make(map[interface{}]string) + for key, val := range m { + reverseMap[val] = key + } + return reverseMap +} + +func ValidateAndMarshalJSON(log *zap.Logger, exp, act *string) (ValidatedJSON, error) { + var validatedJSON ValidatedJSON + var expected interface{} + var actual interface{} + var err error + if *exp != "" { + expected, err = UnmarshallJSON(*exp, log) + if err != nil { + return validatedJSON, err + } + } + if *act != "" { + actual, err = UnmarshallJSON(*act, log) + if err != nil { + return validatedJSON, err + } + } + validatedJSON.expected = expected + validatedJSON.actual = actual + if reflect.TypeOf(expected) != reflect.TypeOf(actual) { + validatedJSON.isIdentical = false + return validatedJSON, nil + } + cleanExp, err := json.Marshal(expected) + if err != nil { + return validatedJSON, err + } + cleanAct, err := json.Marshal(actual) + if err != nil { + return validatedJSON, err + } + *exp = string(cleanExp) + *act = string(cleanAct) + validatedJSON.isIdentical = true + return validatedJSON, nil +} + +// UnmarshallJSON returns unmarshalled JSON object. +func UnmarshallJSON(s string, log *zap.Logger) (interface{}, error) { + var result interface{} + if s == "" { + return nil, nil + } + if err := json.Unmarshal([]byte(s), &result); err != nil { + utils.LogError(log, err, "cannot convert json string into json object", zap.String("json", s)) + return nil, err + } + return result, nil +} + +// maxLineLength is chars PER expected/actual string. Can be changed no problem +const maxLineLength = 50 + +// ansiRegex is compiled at init-time; if compilation fails, falls back to a never-matches regex. +var ansiRegex *regexp.Regexp + +func init() { + re, err := regexp.Compile(`\x1b\[[0-9;]*[a-zA-Z]`) + if err != nil { + re, _ = regexp.Compile(`(?!)`) + } + ansiRegex = re +} + +var ansiResetCode = "\x1b[0m" + +type DiffsPrinter struct { + testCase string + statusExp string + statusAct string + headerExp map[string]string + headerAct map[string]string + bodyExp string + bodyAct string + bodyNoise map[string][]string + headNoise map[string][]string + hasarrayIndexMismatch bool + text string + typeExp string + typeAct string +} + +func (d *DiffsPrinter) SetHasarrayIndexMismatch(has bool) { + d.hasarrayIndexMismatch = has +} + +func NewDiffsPrinter(testCase string) DiffsPrinter { + return DiffsPrinter{testCase, "", "", map[string]string{}, map[string]string{}, "", "", map[string][]string{}, map[string][]string{}, false, "", "", ""} +} +func (d *DiffsPrinter) PushTypeDiff(exp, act string) { + d.typeExp, d.typeAct = exp, act +} +func (d *DiffsPrinter) PushStatusDiff(exp, act string) { + d.statusExp, d.statusAct = exp, act +} + +func (d *DiffsPrinter) PushFooterDiff(key string) { + d.hasarrayIndexMismatch = true + d.text = key +} + +func (d *DiffsPrinter) PushHeaderDiff(exp, act, key string, noise map[string][]string) { + d.headerExp[key], d.headerAct[key], d.headNoise = exp, act, noise +} + +func (d *DiffsPrinter) PushBodyDiff(exp, act string, noise map[string][]string) { + d.bodyExp, d.bodyAct, d.bodyNoise = exp, act, noise +} + +// Render will display and colorize diffs side-by-side +func (d *DiffsPrinter) Render() error { + diffs := []string{} + + if d.statusExp != d.statusAct { + diffs = append(diffs, sprintDiff(d.statusExp, d.statusAct, "status")) + } + + diffs = append(diffs, sprintDiffHeader(d.headerExp, d.headerAct)) + if len(d.bodyExp) != 0 || len(d.bodyAct) != 0 { + bE, bA := []byte(d.bodyExp), []byte(d.bodyAct) + if json.Valid(bE) && json.Valid(bA) { + difference, err := sprintJSONDiff(bE, bA, "body", d.bodyNoise) + if err != nil { + difference = sprintDiff(d.bodyExp, d.bodyAct, "body") + } + diffs = append(diffs, difference) + } else { + diffs = append(diffs, sprintDiff(d.bodyExp, d.bodyAct, "body")) + } + + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetAutoWrapText(false) + table.SetHeader([]string{fmt.Sprintf("Diffs %v", d.testCase)}) + table.SetHeaderColor(tablewriter.Colors{tablewriter.FgHiRedColor}) + table.SetAlignment(tablewriter.ALIGN_CENTER) + + for _, e := range diffs { + table.Append([]string{e}) + } + if d.hasarrayIndexMismatch { + yellowPaint := color.New(color.FgYellow).SprintFunc() + redPaint := color.New(color.FgRed).SprintFunc() + startPart := " Expected and actual value" + var midPartpaint string + if len(d.text) > 0 { + midPartpaint = redPaint(d.text) + startPart += " of " + } + initalPart := yellowPaint(utils.WarningSign + startPart) + + endPaint := yellowPaint(" are in different order but have the same objects") + table.SetHeader([]string{initalPart + midPartpaint + endPaint}) + table.SetAlignment(tablewriter.ALIGN_CENTER) + table.Append([]string{initalPart + midPartpaint + endPaint}) + } + table.Render() + return nil +} +func (d *DiffsPrinter) TableWriter(diffs []string) error { + + table := tablewriter.NewWriter(os.Stdout) + table.SetAutoWrapText(false) + table.SetHeader([]string{fmt.Sprintf("Diffs %v", d.testCase)}) + table.SetHeaderColor(tablewriter.Colors{tablewriter.FgHiRedColor}) + table.SetAlignment(tablewriter.ALIGN_CENTER) + + for _, e := range diffs { + table.Append([]string{e}) + } + if d.hasarrayIndexMismatch { + yellowPaint := color.New(color.FgYellow).SprintFunc() + redPaint := color.New(color.FgRed).SprintFunc() + startPart := " Expected and actual value" + var midPartpaint string + if len(d.text) > 0 { + midPartpaint = redPaint(d.text) + startPart += " of " + } + initalPart := yellowPaint(utils.WarningSign + startPart) + + endPaint := yellowPaint(" are in different order but have the same objects") + table.SetHeader([]string{initalPart + midPartpaint + endPaint}) + table.SetAlignment(tablewriter.ALIGN_CENTER) + table.Append([]string{initalPart + midPartpaint + endPaint}) + } + table.Render() + return nil +} +func (d *DiffsPrinter) RenderAppender() error { + //Only show difference for the response body + diffs := []string{} + pass := true + + if d.typeExp != d.typeAct { + diffs = append(diffs, sprintDiff(d.typeExp, d.typeAct, "request body type")) + pass = false + } + if !pass { + err := d.TableWriter(diffs) + if err != nil { + return err + } + return nil + } + + if len(d.bodyExp) != 0 || len(d.bodyAct) != 0 { + pass = false + bE, bA := []byte(d.bodyExp), []byte(d.bodyAct) + if json.Valid(bE) && json.Valid(bA) { + difference, err := sprintJSONDiff(bE, bA, "response", d.bodyNoise) + if err != nil { + difference = sprintDiff(d.bodyExp, d.bodyAct, "response") + } + diffs = append(diffs, difference) + } else { + diffs = append(diffs, sprintDiff(d.bodyExp, d.bodyAct, "response")) + } + } + if !pass { + err := d.TableWriter(diffs) + if err != nil { + return err + } + + } + + return nil +} + +/* + * Returns a nice diff table where the left is the expect and the right + * is the actual. each entry in expect and actual will contain the key + * and the corresponding value. + */ +func sprintDiffHeader(expect, actual map[string]string) string { + + diff := jsonDiff.CompareHeaders(expect, actual) + + if len(expect) > maxLineLength || len(actual) > maxLineLength { + return expectActualTable(diff.Expected, diff.Actual, "header", false) // Don't centerize + } + return expectActualTable(diff.Expected, diff.Actual, "header", true) +} + +/* + * Returns a nice diff table where the left is the expect and the right + * is the actual. For JSON-based diffs use SprintJSONDiff + * field: body, status... + */ +func sprintDiff(expect, actual, field string) string { + + diff := jsonDiff.Compare(expect, actual) + + if len(expect) > maxLineLength || len(actual) > maxLineLength { + return expectActualTable(diff.Expected, diff.Actual, field, false) + } + return expectActualTable(diff.Expected, diff.Actual, field, true) +} + +/* This will return the json diffs in a beautifull way. It will in fact + * create a colorized table-based expect-response string and return it. + * on the left-side there'll be the expect and on the right the actual + * response. Its important to mention the inputs must to be a json. If + * the body isnt in the rest-api formats (what means it is not json-based) + * its better to use a generic diff output as the SprintDiff. + */ +func sprintJSONDiff(json1 []byte, json2 []byte, field string, noise map[string][]string) (string, error) { + diff, err := jsonDiff.CompareJSON(json1, json2, noise, false) + if err != nil { + return "", err + } + result := expectActualTable(diff.Expected, diff.Actual, field, false) + return result, nil +} + +func wrapTextWithAnsi(input string) string { + scanner := bufio.NewScanner(strings.NewReader(input)) // Create a scanner to read the input string line by line. + var wrappedBuilder strings.Builder // Builder for the resulting wrapped text. + currentAnsiCode := "" // Variable to hold the current ANSI escape sequence. + lastAnsiCode := "" // Variable to hold the last ANSI escape sequence. + + // Iterate over each line in the input string. + for scanner.Scan() { + line := scanner.Text() // Get the current line. + + // If there is a current ANSI code, append it to the builder. + if currentAnsiCode != "" { + wrappedBuilder.WriteString(currentAnsiCode) + } + + // Find all ANSI escape sequences in the current line. + startAnsiCodes := ansiRegex.FindAllString(line, -1) + if len(startAnsiCodes) > 0 { + // Update the last ANSI escape sequence to the last one found in the line. + lastAnsiCode = startAnsiCodes[len(startAnsiCodes)-1] + } + + // Append the current line to the builder. + wrappedBuilder.WriteString(line) + + // Check if the current ANSI code needs to be reset or updated. + if (currentAnsiCode != "" && !strings.HasSuffix(line, ansiResetCode)) || len(startAnsiCodes) > 0 { + // If the current line does not end with a reset code or if there are ANSI codes, append a reset code. + wrappedBuilder.WriteString(ansiResetCode) + // Update the current ANSI code to the last one found in the line. + currentAnsiCode = lastAnsiCode + } else { + // If no ANSI codes need to be maintained, reset the current ANSI code. + currentAnsiCode = "" + } + + // Append a newline character to the builder. + wrappedBuilder.WriteString("\n") + } + + // Return the processed string with properly wrapped ANSI escape sequences. + return wrappedBuilder.String() +} + +func expectActualTable(exp string, act string, field string, centerize bool) string { + buf := &bytes.Buffer{} + table := tablewriter.NewWriter(buf) + + if centerize { + table.SetAlignment(tablewriter.ALIGN_CENTER) + } else { + table.SetAlignment(tablewriter.ALIGN_LEFT) + } + // jsonDiff.JsonDiff() + exp = wrapTextWithAnsi(exp) + act = wrapTextWithAnsi(act) + table.SetHeader([]string{fmt.Sprintf("Expect %v", field), fmt.Sprintf("Actual %v", field)}) + table.SetAutoWrapText(false) + table.SetBorder(false) + table.SetColMinWidth(0, maxLineLength) + table.SetColMinWidth(1, maxLineLength) + table.Append([]string{exp, act}) + table.Render() + return buf.String() +} + +func Contains(elems []string, v string) bool { + for _, s := range elems { + if v == s { + return true + } + } + return false +} + +func checkKey(res *[]models.HeaderResult, key string) bool { + for _, v := range *res { + if key == v.Expected.Key { + return false + } + } + return true +} + +func CompareHeaders(h1 http.Header, h2 http.Header, res *[]models.HeaderResult, noise map[string][]string) bool { + if res == nil { + return false + } + match := true + _, isHeaderNoisy := noise["header"] + for k, v := range h1 { + regexArr, isNoisy := SubstringKeyMatch(strings.ToLower(k), noise) + if isNoisy && len(regexArr) != 0 { + isNoisy, _ = MatchesAnyRegex(v[0], regexArr) + } + isNoisy = isNoisy || isHeaderNoisy + val, ok := h2[k] + if !isNoisy { + if !ok { + if checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: false, + Expected: models.Header{ + Key: k, + Value: v, + }, + Actual: models.Header{ + Key: k, + Value: nil, + }, + }) + } + + match = false + continue + } + if len(v) != len(val) { + if checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: false, + Expected: models.Header{ + Key: k, + Value: v, + }, + Actual: models.Header{ + Key: k, + Value: val, + }, + }) + } + match = false + continue + } + for i, e := range v { + if val[i] != e { + if checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: false, + Expected: models.Header{ + Key: k, + Value: v, + }, + Actual: models.Header{ + Key: k, + Value: val, + }, + }) + } + match = false + continue + } + } + } + if checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: true, + Expected: models.Header{ + Key: k, + Value: v, + }, + Actual: models.Header{ + Key: k, + Value: val, + }, + }) + } + } + for k, v := range h2 { + regexArr, isNoisy := SubstringKeyMatch(strings.ToLower(k), noise) + if isNoisy && len(regexArr) != 0 { + isNoisy, _ = MatchesAnyRegex(v[0], regexArr) + } + isNoisy = isNoisy || isHeaderNoisy + val, ok := h1[k] + if isNoisy && checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: true, + Expected: models.Header{ + Key: k, + Value: val, + }, + Actual: models.Header{ + Key: k, + Value: v, + }, + }) + continue + } + if !ok { + if checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: false, + Expected: models.Header{ + Key: k, + Value: nil, + }, + Actual: models.Header{ + Key: k, + Value: v, + }, + }) + } + + match = false + } + } + return match +} + +func MapToArray(mp map[string][]string) []string { + var result []string + for k := range mp { + result = append(result, k) + } + return result +} + +func SubstringKeyMatch(s string, mp map[string][]string) ([]string, bool) { + for key, val := range mp { + if strings.Contains(s, key) { + return val, true + } + } + return []string{}, false +} + +// func CheckStringExist(s string, mp map[string][]string) ([]string, bool) { +// if val, ok := mp[s]; ok { +// return val, ok +// } +// return []string{}, false +// } + +func AddHTTPBodyToMap(body string, m map[string][]string) error { + // add body + if json.Valid([]byte(body)) { + var result interface{} + + err := json.Unmarshal([]byte(body), &result) + if err != nil { + return err + } + j := Flatten(result) + for k, v := range j { + nk := "body" + if k != "" { + nk = nk + "." + k + } + m[nk] = v + } + } else { + // add it as raw text + m["body"] = []string{body} + } + return nil +} + +// Flatten takes a map and returns a new one where nested maps are replaced +// by dot-delimited keys. +// examples of valid jsons - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#examples +func Flatten(j interface{}) map[string][]string { + if j == nil { + return map[string][]string{"": {""}} + } + o := make(map[string][]string) + x := reflect.ValueOf(j) + switch x.Kind() { + case reflect.Map: + m, ok := j.(map[string]interface{}) + if !ok { + return map[string][]string{} + } + for k, v := range m { + nm := Flatten(v) + for nk, nv := range nm { + fk := k + if nk != "" { + fk = fk + "." + nk + } + o[fk] = nv + } + } + case reflect.Bool: + o[""] = []string{strconv.FormatBool(x.Bool())} + case reflect.Float64: + o[""] = []string{strconv.FormatFloat(x.Float(), 'E', -1, 64)} + case reflect.String: + o[""] = []string{x.String()} + case reflect.Slice: + child, ok := j.([]interface{}) + if !ok { + return map[string][]string{} + } + for _, av := range child { + nm := Flatten(av) + for nk, nv := range nm { + if ov, exists := o[nk]; exists { + o[nk] = append(ov, nv...) + } else { + o[nk] = nv + } + } + } + } + return o +} + +func ArrayToMap(arr []string) map[string]bool { + res := map[string]bool{} + for i := range arr { + res[arr[i]] = true + } + return res +} + +func InterfaceToString(val interface{}) string { + switch v := val.(type) { + case int: + return fmt.Sprintf("%d", v) + case float64: + return fmt.Sprintf("%f", v) + case bool: + return fmt.Sprintf("%t", v) + case string: + return v + default: + return fmt.Sprintf("%v", v) + } +} + +func JsonContains(actualJSON string, expectedJSON map[string]interface{}) (bool, error) { + var actual interface{} + err := json.Unmarshal([]byte(actualJSON), &actual) + if err != nil { + return false, fmt.Errorf("failed to unmarshal actual JSON: %v", err) + } + + return containsRecursive(actual, expectedJSON), nil +} + +// containsRecursive recursively checks if the expected data is in the actual data. +func containsRecursive(actual interface{}, expected map[string]interface{}) bool { + actualMap, ok := actual.(map[string]interface{}) + if !ok { + return false + } + for key, expectedValue := range expected { + actualValue, exists := actualMap[key] + if !exists { + return false + } + + switch v := expectedValue.(type) { + case map[string]interface{}: + if actualMapVal, ok := actualValue.(map[string]interface{}); ok { + if !containsRecursive(actualMapVal, v) { + return false + } + } else { + return false + } + default: + if !reflect.DeepEqual(actualValue, expectedValue) { + return false + } + } + } + return true +} diff --git a/keploy/pkg/models/README.md b/keploy/pkg/models/README.md new file mode 100755 index 0000000..18c3566 --- /dev/null +++ b/keploy/pkg/models/README.md @@ -0,0 +1,3 @@ +# Models Package Documentation + +This package defines all the Go structs used for storing the captured data. It is designed as an independent module. \ No newline at end of file diff --git a/keploy/pkg/models/assertions.go b/keploy/pkg/models/assertions.go new file mode 100644 index 0000000..5a2f52f --- /dev/null +++ b/keploy/pkg/models/assertions.go @@ -0,0 +1,17 @@ +package models + +// AssertionType defines a custom type for supported assertion keys. +type AssertionType string + +const ( + NoiseAssertion AssertionType = "noise" + StatusCode AssertionType = "status_code" + StatusCodeClass AssertionType = "status_code_class" + StatusCodeIn AssertionType = "status_code_in" + HeaderEqual AssertionType = "header_equal" + HeaderContains AssertionType = "header_contains" + HeaderExists AssertionType = "header_exists" + HeaderMatches AssertionType = "header_matches" + JsonEqual AssertionType = "json_equal" + JsonContains AssertionType = "json_contains" +) diff --git a/keploy/pkg/models/auth.go b/keploy/pkg/models/auth.go new file mode 100644 index 0000000..297988e --- /dev/null +++ b/keploy/pkg/models/auth.go @@ -0,0 +1,14 @@ +package models + +type AuthReq struct { + InstallationID string `json:"installationID"` + GitHubToken string `json:"gitHubtoken"` + Platform string `json:"platform"` +} + +type AuthResp struct { + IsValid bool `json:"isValid"` + EmailID string `json:"email"` + JwtToken string `json:"jwtToken"` + Error string `json:"error"` +} diff --git a/keploy/pkg/models/config.go b/keploy/pkg/models/config.go new file mode 100644 index 0000000..e74d79e --- /dev/null +++ b/keploy/pkg/models/config.go @@ -0,0 +1,33 @@ +// Package models provides data models for the keploy. +package models + +type TestSet struct { + PreScript string `json:"pre_script" bson:"pre_script" yaml:"preScript"` + PostScript string `json:"post_script" bson:"post_script" yaml:"postScript"` + AppCommand string `json:"app_command" bson:"app_command" yaml:"appCommand"` + Template map[string]interface{} `json:"template" bson:"template" yaml:"template"` + Secret map[string]interface{} `json:"secret" bson:"secret" yaml:"secret,omitempty"` + MockRegistry *MockRegistry `yaml:"mockRegistry" bson:"mock_registry" json:"mockRegistry,omitempty"` + Metadata map[string]interface{} `json:"metadata" bson:"metadata" yaml:"metadata"` +} + +// Secret interface for types that support secret configuration. +type Secret interface { + SetSecrets(secrets map[string]interface{}) +} + +func (ts *TestSet) SetSecrets(secrets map[string]interface{}) { + ts.Secret = secrets +} + +type MockRegistry struct { + Mock string `json:"mock" bson:"mock" yaml:"mock,omitempty"` + App string `json:"app" bson:"app" yaml:"app,omitempty"` + User string `json:"user" bson:"user" yaml:"user,omitempty"` +} + +type Plan struct { + Type string `json:"type"` + Status string `json:"status"` + KUnits int `json:"kunits,omitempty"` +} diff --git a/keploy/pkg/models/const.go b/keploy/pkg/models/const.go new file mode 100755 index 0000000..9ad2b97 --- /dev/null +++ b/keploy/pkg/models/const.go @@ -0,0 +1,128 @@ +package models + +import ( + "fmt" + "time" + + "github.com/fatih/color" + "github.com/k0kubun/pp/v3" + "go.keploy.io/server/v2/config" +) + +// Patterns for different usecases in keploy +const ( + NoSQLDB string = "NO_SQL_DB" + SQLDB string = "SQL_DB" + GRPC string = "GRPC" + HTTPClient string = "HTTP_CLIENT" + TestSetPattern string = "test-set-" + String string = "string" + TestRunTemplateName string = "test-run-" +) + +const ( + Unknown config.Language = "Unknown" // Unknown language + Go config.Language = "go" // Go language + Java config.Language = "java" // Java language + Python config.Language = "python" // Python language + Javascript config.Language = "javascript" // Javascript language +) + +var ( + PassThroughHosts = []string{"^dc\\.services\\.visualstudio\\.com$"} +) + +var orangeColorSGR = []color.Attribute{38, 5, 208} + +var BaseTime = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) + +var IsAnsiDisabled = false + +var HighlightString = func(a ...interface{}) string { + if IsAnsiDisabled { + return fmt.Sprint(a) + } + return color.New(orangeColorSGR...).SprintFunc()(a) +} + +var HighlightPassingString = func(a ...interface{}) string { + if IsAnsiDisabled { + return fmt.Sprint(a) + } + return color.New(color.FgGreen).SprintFunc()(a) +} + +var HighlightFailingString = func(a ...interface{}) string { + if IsAnsiDisabled { + return fmt.Sprint(a) + } + return color.New(color.FgRed).SprintFunc()(a) +} + +var HighlightGrayString = func(a ...interface{}) string { + if IsAnsiDisabled { + return fmt.Sprint(a) + } + return color.New(color.FgHiBlack).SprintFunc()(a) +} + +var defaultColorScheme = pp.ColorScheme{ + Bool: pp.NoColor, + Integer: pp.NoColor, + Float: pp.NoColor, + String: pp.NoColor, + StringQuotation: pp.NoColor, + EscapedChar: pp.NoColor, + FieldName: pp.NoColor, + PointerAdress: pp.NoColor, + Nil: pp.NoColor, + Time: pp.NoColor, + StructName: pp.NoColor, + ObjectLength: pp.NoColor, +} + +var GetPassingColorScheme = func() pp.ColorScheme { + if IsAnsiDisabled { + return defaultColorScheme + } + return pp.ColorScheme{ + String: pp.Green, + StringQuotation: pp.Green | pp.Bold, + FieldName: pp.White, + Integer: pp.Blue | pp.Bold, + StructName: pp.NoColor, + Bool: pp.Cyan | pp.Bold, + Float: pp.Magenta | pp.Bold, + EscapedChar: pp.Magenta | pp.Bold, + PointerAdress: pp.Blue | pp.Bold, + Nil: pp.Cyan | pp.Bold, + Time: pp.Blue | pp.Bold, + ObjectLength: pp.Blue, + } +} + +var GetFailingColorScheme = func() pp.ColorScheme { + if IsAnsiDisabled { + return defaultColorScheme + } + return pp.ColorScheme{ + Bool: pp.Cyan | pp.Bold, + Integer: pp.Blue | pp.Bold, + Float: pp.Magenta | pp.Bold, + String: pp.Red, + StringQuotation: pp.Red | pp.Bold, + EscapedChar: pp.Magenta | pp.Bold, + FieldName: pp.Yellow, + PointerAdress: pp.Blue | pp.Bold, + Nil: pp.Cyan | pp.Bold, + Time: pp.Blue | pp.Bold, + StructName: pp.White, + ObjectLength: pp.Blue, + } +} + +type contextKey string + +const ErrGroupKey contextKey = "errGroup" +const ClientConnectionIDKey contextKey = "clientConnectionId" +const DestConnectionIDKey contextKey = "destConnectionId" diff --git a/keploy/pkg/models/errors.go b/keploy/pkg/models/errors.go new file mode 100644 index 0000000..3722a5a --- /dev/null +++ b/keploy/pkg/models/errors.go @@ -0,0 +1,27 @@ +package models + +import "fmt" + +type AppError struct { + AppErrorType AppErrorType + Err error +} + +type AppErrorType string + +func (e AppError) Error() string { + if e.Err != nil { + return fmt.Sprintf("%s: %v", e.AppErrorType, e.Err) + } + return string(e.AppErrorType) +} + +// AppErrorType is a type of error that can be returned by the application +const ( + ErrCommandError AppErrorType = "exited due to command error" + ErrUnExpected AppErrorType = "an unexpected error occurred" + ErrInternal AppErrorType = "an internal error occurred" + ErrAppStopped AppErrorType = "app stopped" + ErrCtxCanceled AppErrorType = "context canceled" + ErrTestBinStopped AppErrorType = "test binary stopped" +) diff --git a/keploy/pkg/models/generic.go b/keploy/pkg/models/generic.go new file mode 100644 index 0000000..0327c0c --- /dev/null +++ b/keploy/pkg/models/generic.go @@ -0,0 +1,13 @@ +package models + +import ( + "time" +) + +type GenericSchema struct { + Metadata map[string]string `json:"metadata" yaml:"metadata"` + GenericRequests []Payload `json:"RequestBin,omitempty"` + GenericResponses []Payload `json:"ResponseBin,omitempty"` + ReqTimestampMock time.Time `json:"reqTimestampMock,omitempty"` + ResTimestampMock time.Time `json:"resTimestampMock,omitempty"` +} diff --git a/keploy/pkg/models/github.go b/keploy/pkg/models/github.go new file mode 100644 index 0000000..47aa509 --- /dev/null +++ b/keploy/pkg/models/github.go @@ -0,0 +1,20 @@ +package models + +type DeviceCodeResponse struct { + DeviceCode string `json:"device_code"` + UserCode string `json:"user_code"` + VerificationURI string `json:"verification_uri"` + Interval int `json:"interval"` +} + +type AccessTokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + Scope string `json:"scope"` +} + +// GitHub URLs +const ( + DeviceCodeURL = "https://github.com/login/device/code" + TokenURL = "https://github.com/login/oauth/access_token" +) diff --git a/keploy/pkg/models/grpc.go b/keploy/pkg/models/grpc.go new file mode 100644 index 0000000..bf0ca62 --- /dev/null +++ b/keploy/pkg/models/grpc.go @@ -0,0 +1,79 @@ +package models + +import ( + "time" +) + +type GrpcSpec struct { + Metadata map[string]string `json:"metadata" yaml:"metadata"` + GrpcReq GrpcReq `json:"grpcReq" yaml:"grpcReq"` + GrpcResp GrpcResp `json:"grpcResp" yaml:"grpcResp"` + Created int64 `json:"created" yaml:"created"` + Assertions map[AssertionType]interface{} `json:"assertions" yaml:"assertions"` + ReqTimestampMock time.Time `json:"reqTimestampMock" yaml:"reqTimestampMock,omitempty"` + ResTimestampMock time.Time `json:"resTimestampMock" yaml:"resTimestampMock,omitempty"` +} + +type GrpcHeaders struct { + PseudoHeaders map[string]string `json:"pseudo_headers" yaml:"pseudo_headers"` + OrdinaryHeaders map[string]string `json:"ordinary_headers" yaml:"ordinary_headers"` +} + +type GrpcLengthPrefixedMessage struct { + CompressionFlag uint `json:"compression_flag" yaml:"compression_flag"` + MessageLength uint32 `json:"message_length" yaml:"message_length"` + DecodedData string `json:"decoded_data" yaml:"decoded_data"` +} + +type GrpcReq struct { + Headers GrpcHeaders `json:"headers" yaml:"headers"` + Body GrpcLengthPrefixedMessage `json:"body" yaml:"body"` + Timestamp time.Time `json:"timestamp" yaml:"timestamp"` +} + +type GrpcResp struct { + Headers GrpcHeaders `json:"headers" yaml:"headers"` + Body GrpcLengthPrefixedMessage `json:"body" yaml:"body"` + Trailers GrpcHeaders `json:"trailers" yaml:"trailers"` + Timestamp time.Time `json:"timestamp" yaml:"timestamp"` +} + +// GrpcStream is a helper function to combine the request-response model in a single struct +type GrpcStream struct { + StreamID uint32 + GrpcReq GrpcReq + GrpcResp GrpcResp + + // to handle request (coming in multiple frames) + ReqRawData []byte + ReqPrefixParsed bool + ReqExpectedLength uint32 + + // to handle response (coming in multiple frames) + RespRawData []byte + RespPrefixParsed bool + RespExpectedLength uint32 +} + +// NewGrpcStream returns a GrpcStream with all the nested maps initialised. +func NewGrpcStream(streamID uint32) GrpcStream { + return GrpcStream{ + StreamID: streamID, + GrpcReq: GrpcReq{ + Headers: GrpcHeaders{ + PseudoHeaders: make(map[string]string), + OrdinaryHeaders: make(map[string]string), + }, + }, + GrpcResp: GrpcResp{ + Headers: GrpcHeaders{ + PseudoHeaders: make(map[string]string), + OrdinaryHeaders: make(map[string]string), + }, + Trailers: GrpcHeaders{ + PseudoHeaders: make(map[string]string), + OrdinaryHeaders: make(map[string]string), + }, + }, + } +} diff --git a/keploy/pkg/models/http.go b/keploy/pkg/models/http.go new file mode 100755 index 0000000..e7403bd --- /dev/null +++ b/keploy/pkg/models/http.go @@ -0,0 +1,48 @@ +package models + +import ( + "time" +) + +type Method string + +type HTTPReq struct { + Method Method `json:"method" yaml:"method"` + ProtoMajor int `json:"proto_major" yaml:"proto_major"` // e.g. 1 + ProtoMinor int `json:"proto_minor" yaml:"proto_minor"` // e.g. 0 + URL string `json:"url" yaml:"url"` + URLParams map[string]string `json:"url_params" yaml:"url_params,omitempty"` + Header map[string]string `json:"header" yaml:"header"` + Body string `json:"body" yaml:"body"` + Binary string `json:"binary" yaml:"binary,omitempty"` + Form []FormData `json:"form" yaml:"form,omitempty"` + Timestamp time.Time `json:"timestamp" yaml:"timestamp"` +} + +type HTTPSchema struct { + Metadata map[string]string `json:"metadata" yaml:"metadata"` + Request HTTPReq `json:"req" yaml:"req"` + Response HTTPResp `json:"resp" yaml:"resp"` + Objects []*OutputBinary `json:"objects" yaml:"objects"` + Assertions map[AssertionType]interface{} `json:"assertions" yaml:"assertions,omitempty"` + Created int64 `json:"created" yaml:"created,omitempty"` + ReqTimestampMock time.Time `json:"reqTimestampMock" yaml:"reqTimestampMock,omitempty"` + ResTimestampMock time.Time `json:"resTimestampMock" yaml:"resTimestampMock,omitempty"` +} + +type FormData struct { + Key string `json:"key" bson:"key" yaml:"key"` + Values []string `json:"values" bson:"values,omitempty" yaml:"values,omitempty"` + Paths []string `json:"paths" bson:"paths,omitempty" yaml:"paths,omitempty"` +} + +type HTTPResp struct { + StatusCode int `json:"status_code" yaml:"status_code"` // e.g. 200 + Header map[string]string `json:"header" yaml:"header"` + Body string `json:"body" yaml:"body"` + StatusMessage string `json:"status_message" yaml:"status_message"` + ProtoMajor int `json:"proto_major" yaml:"proto_major"` + ProtoMinor int `json:"proto_minor" yaml:"proto_minor"` + Binary string `json:"binary" yaml:"binary,omitempty"` + Timestamp time.Time `json:"timestamp" yaml:"timestamp"` +} diff --git a/keploy/pkg/models/instrument.go b/keploy/pkg/models/instrument.go new file mode 100644 index 0000000..1078eba --- /dev/null +++ b/keploy/pkg/models/instrument.go @@ -0,0 +1,63 @@ +package models + +import ( + "crypto/tls" + "time" + + "go.keploy.io/server/v2/config" +) + +type HookOptions struct { + Rules []config.BypassRule + Mode Mode + EnableTesting bool + E2E bool + Port uint32 // used for e2e filtering +} + +type OutgoingOptions struct { + Rules []config.BypassRule + MongoPassword string + // TODO: role of SQLDelay should be mentioned in the comments. + SQLDelay time.Duration // This is the same as Application delay. + FallBackOnMiss bool // this enables to pass the request to the actual server if no mock is found during test mode. + Mocking bool // used to enable/disable mocking + DstCfg *ConditionalDstCfg + Backdate time.Time // used to set backdate in cacert request +} + +type ConditionalDstCfg struct { + Addr string // Destination Addr (ip:port) + Port uint + TLSCfg *tls.Config +} + +type IncomingOptions struct { + Filters []config.Filter + BasePath string +} + +type SetupOptions struct { + Container string + DockerNetwork string + DockerDelay uint64 +} + +type RunOptions struct { + //IgnoreErrors bool + AppCommand string // command to run the application +} + +//For test bench + +type ModeKey uint32 + +// These are the keys used to send the keploy record and test ports and pids to the ebpf program when testbench is enabled +const ( + RecordKey ModeKey = 0 + TestKey ModeKey = 1 +) + +type TestingOptions struct { + Mode Mode +} diff --git a/keploy/pkg/models/mock.go b/keploy/pkg/models/mock.go new file mode 100755 index 0000000..2611084 --- /dev/null +++ b/keploy/pkg/models/mock.go @@ -0,0 +1,92 @@ +package models + +import ( + "time" + + "go.keploy.io/server/v2/pkg/models/mysql" +) + +type Kind string + +const ( + HTTP Kind = "Http" + GENERIC Kind = "Generic" + REDIS Kind = "Redis" + MySQL Kind = "MySQL" + Postgres Kind = "Postgres" + GRPC_EXPORT Kind = "gRPC" + Mongo Kind = "Mongo" +) + +type Mock struct { + Version Version `json:"Version,omitempty" bson:"Version,omitempty"` + Name string `json:"Name,omitempty" bson:"Name,omitempty"` + Kind Kind `json:"Kind,omitempty" bson:"Kind,omitempty"` + Spec MockSpec `json:"Spec,omitempty" bson:"Spec,omitempty"` + TestModeInfo TestModeInfo `json:"TestModeInfo,omitempty" bson:"TestModeInfo,omitempty"` // Map for additional test mode information + ConnectionID string `json:"ConnectionId,omitempty" bson:"ConnectionId,omitempty"` +} + +type TestModeInfo struct { + ID int `json:"Id,omitempty" bson:"Id,omitempty"` + IsFiltered bool `json:"isFiltered,omitempty" bson:"isFiltered,omitempty"` + SortOrder int64 `json:"sortOrder,omitempty" bson:"SortOrder,omitempty"` +} + +func (m *Mock) GetKind() string { + return string(m.Kind) +} + +type MockSpec struct { + Metadata map[string]string `json:"Metadata,omitempty" bson:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + GenericRequests []Payload `json:"RequestBin,omitempty" bson:"generic_requests,omitempty"` + GenericResponses []Payload `json:"ResponseBin,omitempty" bson:"generic_responses,omitempty"` + RedisRequests []Payload `json:"redisRequests,omitempty" bson:"redis_requests,omitempty"` + RedisResponses []Payload `json:"redisResponses,omitempty" bson:"redis_responses,omitempty"` + HTTPReq *HTTPReq `json:"Req,omitempty" bson:"http_req,omitempty"` + HTTPResp *HTTPResp `json:"Res,omitempty" bson:"http_resp,omitempty"` + Created int64 `json:"Created,omitempty" bson:"created,omitempty"` + MongoRequests []MongoRequest `json:"MongoRequests,omitempty" bson:"mongo_requests,omitempty"` + MongoResponses []MongoResponse `json:"MongoResponses,omitempty" bson:"mongo_responses,omitempty"` + PostgresRequests []Backend `json:"postgresRequests,omitempty" bson:"postgres_requests,omitempty"` + PostgresResponses []Frontend `json:"postgresResponses,omitempty" bson:"postgres_responses,omitempty"` + GRPCReq *GrpcReq `json:"gRPCRequest,omitempty" bson:"grpc_req,omitempty"` + GRPCResp *GrpcResp `json:"grpcResponse,omitempty" bson:"grpc_resp,omitempty"` + MySQLRequests []mysql.Request `json:"MySqlRequests,omitempty" bson:"my_sql_requests,omitempty"` + MySQLResponses []mysql.Response `json:"MySqlResponses,omitempty" bson:"my_sql_responses,omitempty"` + ReqTimestampMock time.Time `json:"ReqTimestampMock,omitempty" bson:"req_timestamp_mock,omitempty"` + ResTimestampMock time.Time `json:"ResTimestampMock,omitempty" bson:"res_timestamp_mock,omitempty"` +} + +// OutputBinary store the encoded binary output of the egress calls as base64-encoded strings +type OutputBinary struct { + Type string `json:"type" bson:"type" yaml:"type"` + Data string `json:"data" bson:"data" yaml:"data"` +} + +type OriginType string + +// constant for mock origin +const ( + FromServer OriginType = "server" + FromClient OriginType = "client" +) + +type MockUsage string + +const ( + Updated MockUsage = "updated" + Deleted MockUsage = "deleted" +) + +type Payload struct { + Origin OriginType `json:"Origin,omitempty" yaml:"origin" bson:"origin,omitempty"` + Message []OutputBinary `json:"Message,omitempty" yaml:"message" bson:"message,omitempty"` +} + +type MockState struct { + Name string `json:"name"` + Usage MockUsage `json:"usage"` + IsFiltered bool `json:"isFiltered"` + SortOrder int64 `json:"sortOrder"` +} diff --git a/keploy/pkg/models/mock_Secret.go b/keploy/pkg/models/mock_Secret.go new file mode 100644 index 0000000..ccfaa4c --- /dev/null +++ b/keploy/pkg/models/mock_Secret.go @@ -0,0 +1,29 @@ +// Code generated by mockery v2.53.2. DO NOT EDIT. + +package models + +import mock "github.com/stretchr/testify/mock" + +// MockSecret is an autogenerated mock type for the Secret type +type MockSecret struct { + mock.Mock +} + +// SetSecrets provides a mock function with given fields: secrets +func (_m *MockSecret) SetSecrets(secrets map[string]interface{}) { + _m.Called(secrets) +} + +// NewMockSecret creates a new instance of MockSecret. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockSecret(t interface { + mock.TestingT + Cleanup(func()) +}) *MockSecret { + mock := &MockSecret{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/keploy/pkg/models/mode.go b/keploy/pkg/models/mode.go new file mode 100755 index 0000000..ceb642b --- /dev/null +++ b/keploy/pkg/models/mode.go @@ -0,0 +1,52 @@ +package models + +import "errors" + +// Mode represents the mode at which the SDK is operating +// MODE_RECORD is for recording API calls to generate testcases +// MODE_TEST is for testing the application on previous recorded testcases +// MODE_OFF disables keploy SDK automatically from the application +type Mode string + +type KctxType string + +// constants for keploy mode +const ( + MODE_RECORD Mode = "record" + MODE_TEST Mode = "test" + MODE_OFF Mode = "off" + KCTX KctxType = "KeployContext" + KTime KctxType = "KeployTime" +) + +var ( + mode = MODE_OFF +) + +// Valid checks if the provided mode is valid +func (m Mode) Valid() bool { + if m == MODE_RECORD || m == MODE_TEST || m == MODE_OFF { + return true + } + return false +} + +// GetMode returns the mode of the keploy SDK +func GetMode() Mode { + return mode +} + +// SetTestMode sets the keploy SDK mode to MODE_TEST +func SetTestMode() { + _ = SetMode(MODE_TEST) +} + +// SetMode sets the keploy SDK mode +// error is returned if the mode is invalid +func SetMode(m Mode) error { + if !m.Valid() { + return errors.New("invalid mode: " + string(m)) + } + mode = m + return nil +} diff --git a/keploy/pkg/models/mongo.go b/keploy/pkg/models/mongo.go new file mode 100755 index 0000000..ce2db77 --- /dev/null +++ b/keploy/pkg/models/mongo.go @@ -0,0 +1,285 @@ +package models + +import ( + "encoding/json" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" + "gopkg.in/yaml.v3" +) + +type MongoSpec struct { + Metadata map[string]string `json:"metadata" yaml:"metadata"` + Requests []RequestYaml `json:"requests" yaml:"requests"` + Response []ResponseYaml `json:"responses" yaml:"responses"` + CreatedAt int64 `json:"created" yaml:"created,omitempty"` + ReqTimestampMock time.Time `json:"reqTimestampMock" yaml:"reqTimestampMock,omitempty"` + ResTimestampMock time.Time `json:"resTimestampMock" yaml:"resTimestampMock,omitempty"` +} + +type RequestYaml struct { + Header *MongoHeader `json:"header,omitempty" yaml:"header"` + Message yaml.Node `json:"message,omitempty" yaml:"message"` + ReadDelay int64 `json:"read_delay,omitempty" yaml:"read_delay,omitempty"` +} + +type ResponseYaml struct { + Header *MongoHeader `json:"header,omitempty" yaml:"header"` + Message yaml.Node `json:"message,omitempty" yaml:"message"` + ReadDelay int64 `json:"read_delay,omitempty" yaml:"read_delay,omitempty"` +} + +type MongoOpMessage struct { + FlagBits int `json:"flagBits" yaml:"flagBits" bson:"flagBits"` + Sections []string `json:"sections" yaml:"sections" bson:"sections"` + Checksum int `json:"checksum" yaml:"checksum" bson:"checksum"` +} + +type MongoOpQuery struct { + Flags int32 `json:"flags" yaml:"flags" bson:"flags"` + FullCollectionName string `json:"collection_name" yaml:"collection_name" bson:"collection_name"` + NumberToSkip int32 `json:"number_to_skip" yaml:"number_to_skip" bson:"number_to_skip"` + NumberToReturn int32 `json:"number_to_return" yaml:"number_to_return" bson:"number_to_return"` + Query string `json:"query" yaml:"query" bson:"query"` + ReturnFieldsSelector string `json:"return_fields_selector" yaml:"return_fields_selector" bson:"return_fields_selector"` +} + +type MongoOpReply struct { + ResponseFlags int32 `json:"response_flags" yaml:"response_flags" bson:"response_flags"` + CursorID int64 `json:"cursor_id" yaml:"cursor_id" bson:"cursor_id"` + StartingFrom int32 `json:"starting_from" yaml:"starting_from" bson:"starting_from"` + NumberReturned int32 `json:"number_returned" yaml:"number_returned" bson:"number_returned"` + Documents []string `json:"documents" yaml:"documents" bson:"documents"` +} + +type MongoHeader struct { + Length int32 `json:"length" yaml:"length" bson:"length"` + RequestID int32 `json:"requestId" yaml:"requestId" bson:"request_id"` + ResponseTo int32 `json:"responseTo" yaml:"responseTo" bson:"response_to"` + Opcode wiremessage.OpCode `json:"Opcode" yaml:"Opcode" bson:"opcode"` +} + +type MongoRequest struct { + Header *MongoHeader `json:"header,omitempty" yaml:"header,omitempty" bson:"header,omitempty"` + Message interface{} `json:"message,omitempty" yaml:"message,omitempty" bson:"message,omitempty"` + ReadDelay int64 `json:"read_delay,omitempty" yaml:"read_delay,omitempty" bson:"read_delay,omitempty"` +} + +// UnmarshalBSON implements bson.Unmarshaler for mongoRequests because of interface typeof field +func (mr *MongoRequest) UnmarshalBSON(data []byte) error { + + // duplicate struct to avoid infinite recursion + type MongoRequestAlias struct { + Header *MongoHeader `bson:"header,omitempty"` + Message bson.Raw `bson:"message,omitempty"` + ReadDelay int64 `bson:"read_delay,omitempty"` + } + var aux MongoRequestAlias + + if err := bson.Unmarshal(data, &aux); err != nil { + return err + } + + // assign the unmarshalled data to the original data + mr.Header = aux.Header + mr.ReadDelay = aux.ReadDelay + + // unmarshal the message into the correct type + switch mr.Header.Opcode { + case wiremessage.OpMsg: + var msg MongoOpMessage + if err := bson.Unmarshal(aux.Message, &msg); err != nil { + return err + } + mr.Message = &msg + case wiremessage.OpQuery: + var msg MongoOpQuery + if err := bson.Unmarshal(aux.Message, &msg); err != nil { + return err + } + mr.Message = &msg + default: + return errors.New("failed to unmarshal unknown opcode") + } + return nil +} + +// UnmarshalJSON implements json.Unmarshaler for mongoRequests because of interface typeof field +func (mr *MongoRequest) UnmarshalJSON(data []byte) error { + // duplicate struct to avoid infinite recursion + type MongoRequestAlias struct { + Header *MongoHeader `json:"header"` + Message json.RawMessage `json:"message"` + ReadDelay int64 `json:"read_delay"` + } + var aux MongoRequestAlias + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // assign the unmarshalled data to the original data + mr.Header = aux.Header + mr.ReadDelay = aux.ReadDelay + + // unmarshal the message into the correct type + switch mr.Header.Opcode { + case wiremessage.OpMsg: + var msg MongoOpMessage + if err := json.Unmarshal(aux.Message, &msg); err != nil { + return err + } + mr.Message = &msg + case wiremessage.OpQuery: + var msg MongoOpQuery + if err := json.Unmarshal(aux.Message, &msg); err != nil { + return err + } + mr.Message = &msg + default: + return errors.New("failed to unmarshal unknown opcode") + } + + return nil +} + +// MarshalJSON implements json.Marshaler for mongoRequests because of interface typeof field +func (mr *MongoRequest) MarshalJSON() ([]byte, error) { + // duplicate struct to avoid infinite recursion + type MongoRequestAlias struct { + Header *MongoHeader `json:"header"` + Message json.RawMessage `json:"message"` + ReadDelay int64 `json:"read_delay"` + } + + aux := MongoRequestAlias{ + Header: mr.Header, + Message: json.RawMessage(nil), + ReadDelay: mr.ReadDelay, + } + + if mr.Message != nil { + // Marshal the message interface{} into JSON + msgJSON, err := json.Marshal(mr.Message) + if err != nil { + return nil, err + } + aux.Message = msgJSON + } + + return json.Marshal(aux) +} + +type MongoResponse struct { + Header *MongoHeader `json:"header,omitempty" yaml:"header,omitempty" bson:"header,omitempty"` + Message interface{} `json:"message,omitempty" yaml:"message,omitempty" bson:"message,omitempty"` + ReadDelay int64 `json:"read_delay,omitempty" yaml:"read_delay,omitempty" bson:"read_delay,omitempty"` +} + +// UnmarshalBSON implements bson.Unmarshaler for mongoResponses because of interface typeof field +func (mr *MongoResponse) UnmarshalBSON(data []byte) error { + // duplicate struct to avoid infinite recursion + type MongoResponseAlias struct { + Header *MongoHeader `bson:"header,omitempty"` + Message bson.Raw `bson:"message,omitempty"` + ReadDelay int64 `bson:"read_delay,omitempty"` + } + var aux MongoResponseAlias + + if err := bson.Unmarshal(data, &aux); err != nil { + return err + } + + // assign the unmarshalled data to the original data + mr.Header = aux.Header + mr.ReadDelay = aux.ReadDelay + + // unmarshal the message into the correct type + switch mr.Header.Opcode { + case wiremessage.OpMsg: + var msg MongoOpMessage + if err := bson.Unmarshal(aux.Message, &msg); err != nil { + return err + } + mr.Message = &msg + case wiremessage.OpReply: + var msg MongoOpReply + if err := bson.Unmarshal(aux.Message, &msg); err != nil { + return err + } + mr.Message = &msg + default: + return errors.New("failed to unmarshal unknown opcode") + } + + return nil +} + +// UnmarshalJSON implements json.Unmarshaler for mongoResponses because of interface typeof field +func (mr *MongoResponse) UnmarshalJSON(data []byte) error { + // duplicate struct to avoid infinite recursion + type MongoResponseAlias struct { + Header *MongoHeader `json:"header"` + Message json.RawMessage `json:"message"` + ReadDelay int64 `json:"read_delay"` + } + var aux MongoResponseAlias + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // assign the unmarshalled data to the original data + mr.Header = aux.Header + mr.ReadDelay = aux.ReadDelay + + // unmarshal the message into the correct type + switch mr.Header.Opcode { + case wiremessage.OpMsg: + var msg MongoOpMessage + if err := json.Unmarshal(aux.Message, &msg); err != nil { + return err + } + mr.Message = &msg + case wiremessage.OpReply: + var msg MongoOpReply + if err := json.Unmarshal(aux.Message, &msg); err != nil { + return err + } + mr.Message = &msg + default: + return errors.New("failed to unmarshal unknown opcode") + } + + return nil + +} + +// MarshalJSON implements json.Marshaler for mongoResponses because of interface typeof field +func (mr *MongoResponse) MarshalJSON() ([]byte, error) { + // duplicate struct to avoid infinite recursion + type MongoResponseAlias struct { + Header *MongoHeader `json:"header"` + Message json.RawMessage `json:"message"` + ReadDelay int64 `json:"read_delay"` + } + + aux := MongoResponseAlias{ + Header: mr.Header, + Message: json.RawMessage(nil), + ReadDelay: mr.ReadDelay, + } + + if mr.Message != nil { + // Marshal the message interface{} into JSON + msgJSON, err := json.Marshal(mr.Message) + if err != nil { + return nil, err + } + aux.Message = msgJSON + } + + return json.Marshal(aux) +} diff --git a/keploy/pkg/models/mysql/comm.go b/keploy/pkg/models/mysql/comm.go new file mode 100644 index 0000000..14d9b5e --- /dev/null +++ b/keploy/pkg/models/mysql/comm.go @@ -0,0 +1,228 @@ +// Package mysql in models provides realted structs for mysql protocol +package mysql + +// This file contains struct for command phase packets +/* refer: + +(i) https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_command_phase_text.html +(ii) https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_command_phase_utility.html +(iii) https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_command_phase_ps.html + +*/ + +// COM_QUERY packet + +type QueryPacket struct { + Command byte `yaml:"command"` + ParameterCount int `yaml:"parameter_count"` + NullBitmap []byte `yaml:"null_bitmap"` + NewParamsBindFlag byte `yaml:"new_params_bind_flag"` + Parameters []Parameter `yaml:"parameters"` + Query string `yaml:"query"` +} + +// LocalInFileRequestPacket is used to send local file request to server, currently not supported +type LocalInFileRequestPacket struct { + PacketType byte `yaml:"command"` + Filename string +} + +// TextResultSet is used as a response packet for COM_QUERY +type TextResultSet struct { + ColumnCount uint64 `yaml:"columnCount"` + Columns []*ColumnDefinition41 `yaml:"columns"` + EOFAfterColumns []byte `yaml:"eofAfterColumns"` + Rows []*TextRow `yaml:"rows"` + FinalResponse *GenericResponse `yaml:"FinalResponse"` +} + +// BinaryProtocolResultSet is used as a response packet for COM_STMT_EXECUTE +type BinaryProtocolResultSet struct { + ColumnCount uint64 `yaml:"columnCount"` + Columns []*ColumnDefinition41 `yaml:"columns"` + EOFAfterColumns []byte `yaml:"eofAfterColumns"` + Rows []*BinaryRow `yaml:"rows"` + FinalResponse *GenericResponse `yaml:"FinalResponse"` +} + +type GenericResponse struct { + Data []byte `yaml:"data"` + Type string `yaml:"type"` +} + +// Columns + +type ColumnCount struct { + // Header Header `yaml:"header"` + Count uint64 `yaml:"count"` +} + +type ColumnDefinition41 struct { + Header Header `yaml:"header"` + Catalog string `yaml:"catalog"` + Schema string `yaml:"schema"` + Table string `yaml:"table"` + OrgTable string `yaml:"org_table"` + Name string `yaml:"name"` + OrgName string `yaml:"org_name"` + FixedLength byte `yaml:"fixed_length"` + CharacterSet uint16 `yaml:"character_set"` + ColumnLength uint32 `yaml:"column_length"` + Type byte `yaml:"type"` + Flags uint16 `yaml:"flags"` + Decimals byte `yaml:"decimals"` + Filler []byte `yaml:"filler"` + DefaultValue string `yaml:"defaultValue"` +} + +//Rows + +type TextRow struct { + Header Header `yaml:"header"` + Values []ColumnEntry `yaml:"values"` +} + +type BinaryRow struct { + Header Header `yaml:"header"` + Values []ColumnEntry `yaml:"values"` + OkAfterRow bool `yaml:"okAfterRow"` + RowNullBuffer []byte `yaml:"rowNullBuffer"` +} + +type ColumnEntry struct { + Type FieldType `yaml:"type"` + Name string `yaml:"name"` + Value interface{} `yaml:"value"` + Unsigned bool `yaml:"unsigned"` +} + +// COM_STMT_PREPARE packet + +type StmtPreparePacket struct { + Command byte `yaml:"command"` + Query string `yaml:"query"` +} + +// COM_STMT_PREPARE_OK packet + +type StmtPrepareOkPacket struct { + Status byte `yaml:"status"` + StatementID uint32 `yaml:"statement_id"` + NumColumns uint16 `yaml:"num_columns"` + NumParams uint16 `yaml:"num_params"` + Filler byte `yaml:"filler"` + WarningAvailable bool `yaml:"warning_available"` + WarningCount uint16 `yaml:"warning_count"` + MetaFollowsAvailable bool `yaml:"meta_follows_available"` + MetaFollows byte `yaml:"meta_follows"` + + ParamDefs []*ColumnDefinition41 `yaml:"param_definitions"` + EOFAfterParamDefs []byte `yaml:"eofAfterParamDefs"` + ColumnDefs []*ColumnDefinition41 `yaml:"column_definitions"` + EOFAfterColumnDefs []byte `yaml:"eofAfterColumnDefs"` +} + +// COM_STMT_EXECUTE packet + +type StmtExecutePacket struct { + Status byte `yaml:"status"` + StatementID uint32 `yaml:"statement_id"` + Flags byte `yaml:"flags"` + IterationCount uint32 `yaml:"iteration_count"` + ParameterCount int `yaml:"parameter_count"` + NullBitmap []byte `yaml:"null_bitmap"` + NewParamsBindFlag byte `yaml:"new_params_bind_flag"` + Parameters []Parameter `yaml:"parameters"` +} + +type Parameter struct { + Type uint16 `yaml:"type"` + Unsigned bool `yaml:"unsigned"` + Name string `yaml:"name,omitempty"` + Value any `yaml:"value"` +} + +// COM_STMT_FETCH packet is not currently supported because its response involves multi-resultset + +type StmtFetchPacket struct { + Status byte `yaml:"status"` + StatementID uint32 `yaml:"statement_id"` + NumRows uint32 `yaml:"num_rows"` +} + +// COM_STMT_CLOSE packet + +type StmtClosePacket struct { + Status byte `yaml:"status"` + StatementID uint32 `yaml:"statement_id"` +} + +// COM_STMT_RESET packet + +type StmtResetPacket struct { + Status byte `yaml:"status"` + StatementID uint32 `yaml:"statement_id"` +} + +// COM_STMT_SEND_LONG_DATA packet + +type StmtSendLongDataPacket struct { + Status byte `yaml:"status"` + StatementID uint32 `yaml:"statement_id"` + ParameterID uint16 `yaml:"parameter_id"` + Data []byte `yaml:"data"` +} + +// Utility commands + +// COM_QUIT packet + +type QuitPacket struct { + Command byte `yaml:"command"` +} + +// COM_INIT_DB packet + +type InitDBPacket struct { + Command byte `yaml:"command"` + Schema string `yaml:"schema"` +} + +// COM_STATISTICS packet + +type StatisticsPacket struct { + Command byte `yaml:"command"` +} + +// COM_DEBUG packet + +type DebugPacket struct { + Command byte `yaml:"command"` +} + +// COM_PING packet + +type PingPacket struct { + Command byte `yaml:"command"` +} + +// COM_RESET_CONNECTION packet + +type ResetConnectionPacket struct { + Command byte `yaml:"command"` +} + +// COM_SET_OPTION packet + +type SetOptionPacket struct { + Status byte `yaml:"status"` + Option uint16 `yaml:"option"` +} + +// COM_CHANGE_USER packet (Not completed/supported as of now) +//refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_change_user.html + +type ChangeUserPacket struct { + Command byte `yaml:"command"` + // rest of the fields are not present as the packet is not supported +} diff --git a/keploy/pkg/models/mysql/conn.go b/keploy/pkg/models/mysql/conn.go new file mode 100644 index 0000000..51af3a6 --- /dev/null +++ b/keploy/pkg/models/mysql/conn.go @@ -0,0 +1,68 @@ +package mysql + +// This file contains struct for connection phase packets +// refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets.html + +// Initial Handshake Packets + +// HandshakeV10Packet represents the initial handshake packet sent by the server to the client +type HandshakeV10Packet struct { + ProtocolVersion uint8 `yaml:"protocol_version"` + ServerVersion string `yaml:"server_version"` + ConnectionID uint32 `yaml:"connection_id"` + AuthPluginData []byte `yaml:"auth_plugin_data,omitempty,flow"` + Filler byte `yaml:"filler"` + CapabilityFlags uint32 `yaml:"capability_flags"` + CharacterSet uint8 `yaml:"character_set"` + StatusFlags uint16 `yaml:"status_flags"` + AuthPluginName string `yaml:"auth_plugin_name"` +} + +// HandshakeResponse41Packet represents the response packet sent by the client to the server after receiving the HandshakeV10Packet +type HandshakeResponse41Packet struct { + CapabilityFlags uint32 `yaml:"capability_flags"` + MaxPacketSize uint32 `yaml:"max_packet_size"` + CharacterSet uint8 `yaml:"character_set"` + Filler [23]byte `yaml:"filler,omitempty,flow"` + Username string `yaml:"username"` + AuthResponse []byte `yaml:"auth_response,omitempty,flow"` + Database string `yaml:"database"` + AuthPluginName string `yaml:"auth_plugin_name"` + ConnectionAttributes map[string]string `yaml:"connection_attributes,omitempty"` + ZstdCompressionLevel byte `yaml:"zstdcompressionlevel"` +} + +type SSLRequestPacket struct { + CapabilityFlags uint32 `yaml:"capability_flags"` + MaxPacketSize uint32 `yaml:"max_packet_size"` + CharacterSet uint8 `yaml:"character_set"` + Filler [23]byte `yaml:"filler,omitempty,flow"` +} + +// Authentication Packets + +// AuthSwitchRequestPacket represents the packet sent by the server to the client to switch to a different authentication method +type AuthSwitchRequestPacket struct { + StatusTag byte `yaml:"status_tag"` + PluginName string `yaml:"plugin_name"` + PluginData string `yaml:"plugin_data"` +} + +// AuthSwitchResponsePacket represents the packet sent by the client to the server in response to an AuthSwitchRequestPacket. +// Note: If the server sends an AuthMoreDataPacket, the client will continue sending AuthSwitchResponsePackets until the server sends an OK packet or an ERR packet. +type AuthSwitchResponsePacket struct { + Data string `yaml:"data"` +} + +// AuthMoreDataPacket represents the packet sent by the server to the client to request additional data for authentication +type AuthMoreDataPacket struct { + StatusTag byte `yaml:"status_tag"` + Data string `yaml:"data"` +} + +// AuthNextFactorPacket represents the packet sent by the server to the client to request the next factor for multi-factor authentication +type AuthNextFactorPacket struct { + PacketType byte `yaml:"packet_type"` + PluginName string `yaml:"plugin_name"` + PluginData string `yaml:"plugin_data"` +} diff --git a/keploy/pkg/models/mysql/const.go b/keploy/pkg/models/mysql/const.go new file mode 100644 index 0000000..8b05206 --- /dev/null +++ b/keploy/pkg/models/mysql/const.go @@ -0,0 +1,267 @@ +package mysql + +// Basic Response Packet Status +const ( + OK byte = 0x00 + ERR byte = 0xff + EOF byte = 0xfe +) + +// LocalInFile Request packet type is not supported +const LocalInFile = 0xfb + +// Auth Packet Status +const ( + AuthSwitchRequest byte = 0xfe + AuthMoreData byte = 0x01 + AuthNextFactor byte = 0x02 + HandshakeV10 byte = 0x0a +) + +// Some constants for MySQL +const ( + HandshakeResponse41 = "HandshakeResponse41" + SSLRequest = "SSLRequest" + COM_STMT_PREPARE_OK = "COM_STMT_PREPARE_OK" +) + +type CachingSha2Password byte + +// MySQL authentication methods + +type AuthPluginName string + +// AuthPluginName constants +const ( + Native AuthPluginName = "mysql_native_password" + CachingSha2 AuthPluginName = "caching_sha2_password" + Sha256 AuthPluginName = "sha256_password" +) + +// Some constants for MySQL +const ( + EncryptedPassword = "encrypted_password" + PlainPassword = "plain_password" + AuthSwithResponse = "AuthSwitchResponse" +) + +// CachingSha2Password constants +const ( + RequestPublicKey CachingSha2Password = 2 + FastAuthSuccess CachingSha2Password = 3 + PerformFullAuthentication CachingSha2Password = 4 +) + +// Client Capability Flags +const ( + // https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__capabilities__flags.html + + CLIENT_LONG_PASSWORD uint32 = 1 << iota + CLIENT_FOUND_ROWS + CLIENT_LONG_FLAG + CLIENT_CONNECT_WITH_DB + CLIENT_NO_SCHEMA + CLIENT_COMPRESS + CLIENT_ODBC + CLIENT_LOCAL_FILES + CLIENT_IGNORE_SPACE + CLIENT_PROTOCOL_41 + CLIENT_INTERACTIVE + CLIENT_SSL + CLIENT_IGNORE_SIGPIPE + CLIENT_TRANSACTIONS + CLIENT_RESERVED + CLIENT_SECURE_CONNECTION + CLIENT_MULTI_STATEMENTS + CLIENT_MULTI_RESULTS + CLIENT_PS_MULTI_RESULTS + CLIENT_PLUGIN_AUTH + CLIENT_CONNECT_ATTRS + CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA + CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS + CLIENT_SESSION_TRACK + CLIENT_DEPRECATE_EOF + CLIENT_OPTIONAL_RESULTSET_METADATA + CLIENT_ZSTD_COMPRESSION_ALGORITHM + CLIENT_QUERY_ATTRIBUTES + MULTI_FACTOR_AUTHENTICATION + CLIENT_CAPABILITY_EXTENSION + CLIENT_SSL_VERIFY_SERVER_CERT + CLIENT_REMEMBER_OPTIONS +) + +type FieldType byte + +// Field Types +const ( + FieldTypeDecimal FieldType = iota + FieldTypeTiny + FieldTypeShort + FieldTypeLong + FieldTypeFloat + FieldTypeDouble + FieldTypeNULL + FieldTypeTimestamp + FieldTypeLongLong + FieldTypeInt24 + FieldTypeDate + FieldTypeTime + FieldTypeDateTime + FieldTypeYear + FieldTypeNewDate + FieldTypeVarChar + FieldTypeBit +) + +// Additional Field Types +const ( + FieldTypeJSON FieldType = iota + 0xf5 + FieldTypeNewDecimal + FieldTypeEnum + FieldTypeSet + FieldTypeTinyBLOB + FieldTypeMediumBLOB + FieldTypeLongBLOB + FieldTypeBLOB + FieldTypeVarString + FieldTypeString + FieldTypeGeometry +) + +// Field Flags +const ( + NOT_NULL_FLAG = 1 + PRI_KEY_FLAG = 2 + UNIQUE_KEY_FLAG = 4 + BLOB_FLAG = 16 + UNSIGNED_FLAG = 32 + ZEROFILL_FLAG = 64 + BINARY_FLAG = 128 + ENUM_FLAG = 256 + AUTO_INCREMENT_FLAG = 512 + TIMESTAMP_FLAG = 1024 + SET_FLAG = 2048 + NUM_FLAG = 32768 + PART_KEY_FLAG = 16384 + GROUP_FLAG = 32768 + UNIQUE_FLAG = 65536 +) + +// Cursor types for prepared statements +const ( + CURSOR_TYPE_NO_CURSOR byte = 0x0 + CURSOR_TYPE_READ_ONLY byte = 0x1 + CURSOR_TYPE_FOR_UPDATE byte = 0x2 + CURSOR_TYPE_SCROLLABLE byte = 0x4 + PARAMETER_COUNT_AVAILABLE byte = 0x8 +) + +// Utility command Packet Status +const ( + COM_QUIT byte = 0x01 + COM_INIT_DB byte = 0x02 + COM_FIELD_LIST byte = 0x04 + COM_STATISTICS byte = 0x08 + COM_DEBUG byte = 0x0d + COM_PING byte = 0x0e + COM_CHANGE_USER byte = 0x11 + COM_RESET_CONNECTION byte = 0x1f + // COM_SET_OPTION byte = 0x1a +) + +// Command Packet Status +const ( + COM_QUERY byte = 0x03 + COM_STMT_PREPARE byte = 0x16 + COM_STMT_EXECUTE byte = 0x17 + // COM_STMT_FETCH byte = 0x19 + COM_STMT_CLOSE byte = 0x19 + COM_STMT_RESET byte = 0x1a + COM_STMT_SEND_LONG_DATA byte = 0x18 + COM_TIME byte = 0x0f +) + +// ResultSet packets +type ResultSet string + +// ResultSet types +const ( + Binary ResultSet = "BinaryProtocolResultSet" + Text ResultSet = "TextResultSet" +) + +// Define the maps for basic response packets +var statusToString = map[byte]string{ + OK: "OK", + ERR: "ERR", + EOF: "EOF", + LocalInFile: "LocalInFile", +} + +// Define the maps for auth packet status +var authStatusToString = map[byte]string{ + AuthSwitchRequest: "AuthSwitchRequest", + AuthMoreData: "AuthMoreData", + AuthNextFactor: "AuthNextFactor", + HandshakeV10: "HandshakeV10", +} + +// Define the map for command packet status +var commandStatusToString = map[byte]string{ + //utility command + COM_QUIT: "COM_QUIT", + COM_INIT_DB: "COM_INIT_DB", + COM_FIELD_LIST: "COM_FIELD_LIST", + COM_STATISTICS: "COM_STATISTICS", + COM_DEBUG: "COM_DEBUG", + COM_PING: "COM_PING", + COM_CHANGE_USER: "COM_CHANGE_USER", + COM_RESET_CONNECTION: "COM_RESET_CONNECTION", + // COM_SET_OPTION: "COM_SET_OPTION", + // command + COM_QUERY: "COM_QUERY", + COM_STMT_PREPARE: "COM_STMT_PREPARE", + COM_STMT_EXECUTE: "COM_STMT_EXECUTE", + // COM_STMT_FETCH: "COM_STMT_FETCH", + COM_STMT_CLOSE: "COM_STMT_CLOSE", + COM_STMT_RESET: "COM_STMT_RESET", + COM_STMT_SEND_LONG_DATA: "COM_STMT_SEND_LONG_DATA", + + //Some extra commands + COM_TIME: "COM_TIME", +} + +// Define the map for cachingSha2Password +var cachingSha2PasswordToString = map[CachingSha2Password]string{ + RequestPublicKey: "RequestPublicKey", + FastAuthSuccess: "FastAuthSuccess", + PerformFullAuthentication: "PerformFullAuthentication", +} + +func StatusToString(status byte) string { + if str, ok := statusToString[status]; ok { + return str + } + return "UNKNOWN" +} + +func AuthStatusToString(status byte) string { + if str, ok := authStatusToString[status]; ok { + return str + } + return "UNKNOWN" +} + +func CommandStatusToString(status byte) string { + if str, ok := commandStatusToString[status]; ok { + return str + } + return "UNKNOWN" +} + +func CachingSha2PasswordToString(status CachingSha2Password) string { + if str, ok := cachingSha2PasswordToString[status]; ok { + return str + } + return "UNKNOWN" +} diff --git a/keploy/pkg/models/mysql/generic.go b/keploy/pkg/models/mysql/generic.go new file mode 100644 index 0000000..07be522 --- /dev/null +++ b/keploy/pkg/models/mysql/generic.go @@ -0,0 +1,30 @@ +package mysql + +// This file contains structs for mysql generic response packets +//refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_response_packets.html + +// OKPacket represents the OK packet sent by the server to the client, it represents a successful completion of a command +type OKPacket struct { + Header byte `json:"header" yaml:"header"` + AffectedRows uint64 `json:"affected_rows,omitempty" yaml:"affected_rows"` + LastInsertID uint64 `json:"last_insert_id,omitempty" yaml:"last_insert_id"` + StatusFlags uint16 `json:"status_flags,omitempty" yaml:"status_flags"` + Warnings uint16 `json:"warnings,omitempty" yaml:"warnings"` + Info string `json:"info,omitempty" yaml:"info"` +} + +// ERRPacket represents the ERR packet sent by the server to the client, it represents an error occurred during the execution of a command +type ERRPacket struct { + Header byte `yaml:"header"` + ErrorCode uint16 `yaml:"error_code"` + SQLStateMarker string `yaml:"sql_state_marker"` + SQLState string `yaml:"sql_state"` + ErrorMessage string `yaml:"error_message"` +} + +// EOFPacket represents the EOF packet sent by the server to the client, it represents the end of a query execution result +type EOFPacket struct { + Header byte `yaml:"header"` + Warnings uint16 `yaml:"warnings"` + StatusFlags uint16 `yaml:"status_flags"` +} diff --git a/keploy/pkg/models/mysql/mysql.go b/keploy/pkg/models/mysql/mysql.go new file mode 100644 index 0000000..c1f6389 --- /dev/null +++ b/keploy/pkg/models/mysql/mysql.go @@ -0,0 +1,61 @@ +package mysql + +import ( + "time" + + "gopkg.in/yaml.v3" +) + +type Spec struct { + Metadata map[string]string `json:"metadata" yaml:"metadata"` + Requests []RequestYaml `json:"requests" yaml:"requests"` + Response []ResponseYaml `json:"responses" yaml:"responses"` + CreatedAt int64 `json:"created" yaml:"created,omitempty"` + ReqTimestampMock time.Time `json:"ReqTimestampMock,omitempty"` + ResTimestampMock time.Time `json:"ResTimestampMock,omitempty"` +} + +type RequestYaml struct { + Header *PacketInfo `json:"header,omitempty" yaml:"header"` + Meta map[string]string `json:"meta,omitempty" yaml:"meta,omitempty"` + Message yaml.Node `json:"message,omitempty" yaml:"message"` +} + +type ResponseYaml struct { + Header *PacketInfo `json:"header,omitempty" yaml:"header"` + Meta map[string]string `json:"meta,omitempty" yaml:"meta,omitempty"` + Message yaml.Node `json:"message,omitempty" yaml:"message"` +} + +type PacketInfo struct { + Header *Header `json:"header" yaml:"header"` + Type string `json:"packet_type" yaml:"packet_type"` +} + +type Request struct { + PacketBundle `json:"packet_bundle" yaml:"packet_bundle"` +} + +type Response struct { + PacketBundle `json:"packet_bundle" yaml:"packet_bundle"` + Payload string `json:"payload,omitempty" yaml:"payload,omitempty"` +} + +type PacketBundle struct { + Header *PacketInfo `json:"header" yaml:"header"` + Message interface{} `json:"message" yaml:"message"` + Meta map[string]string `json:"meta,omitempty" yaml:"meta,omitempty"` +} + +// MySql Packet +//refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_packets.html + +type Packet struct { + Header Header `json:"header" yaml:"header"` + Payload []byte `json:"payload,omitempty" yaml:"payload,omitempty"` +} + +type Header struct { + PayloadLength uint32 `json:"payload_length" yaml:"payload_length"` + SequenceID uint8 `json:"sequence_id" yaml:"sequence_id"` +} diff --git a/keploy/pkg/models/openapi.go b/keploy/pkg/models/openapi.go new file mode 100644 index 0000000..f6945e4 --- /dev/null +++ b/keploy/pkg/models/openapi.go @@ -0,0 +1,142 @@ +package models + +// SchemaMatchMode defines the possible modes for schema matching. +type SchemaMatchMode int + +const ( + // IdentifyMode is used for identifying schema. + IdentifyMode SchemaMatchMode = iota + + // CompareMode is used for comparing schemas. + CompareMode +) + +// String returns the string representation of the SchemaMatchMode. +func (s SchemaMatchMode) String() string { + schemaModes := []string{"IDENTIFYMODE", "COMPAREMODE"} + + if int(s) < 0 || int(s) >= len(schemaModes) { + return "UNKNOWN" + } + + return schemaModes[s] +} + +// DrivenMode defines the possible modes for driven contexts. +type DrivenMode int + +const ( + // ConsumerMode is used for a consumer context. + ConsumerMode DrivenMode = iota + + // ProviderMode is used for a provider context. + ProviderMode +) + +// String returns the string representation of the DrivenMode. +func (d DrivenMode) String() string { + drivenModes := []string{"consumer", "provider"} + + if int(d) < 0 || int(d) >= len(drivenModes) { + return "UNKNOWN" + } + + return drivenModes[d] +} + +type HTTPDoc struct { + Version string `json:"version" yaml:"version"` + Kind string `json:"kind" yaml:"kind"` + Name string `json:"name" yaml:"name"` + Spec HTTPSchema `json:"spec" yaml:"spec"` +} + +type OpenAPI struct { + OpenAPI string `json:"openapi" yaml:"openapi"` + Info Info `json:"info" yaml:"info"` + Servers []map[string]string `json:"servers" yaml:"servers"` + Paths map[string]PathItem `json:"paths" yaml:"paths"` + Components map[string]interface{} `json:"components" yaml:"components"` +} + +type Info struct { + Title string `json:"title" yaml:"title"` + Version string `json:"version" yaml:"version"` + Description string `json:"description" yaml:"description"` +} + +type PathItem struct { + Get *Operation `json:"get,omitempty" yaml:"get,omitempty"` + Post *Operation `json:"post,omitempty" yaml:"post,omitempty"` + Put *Operation `json:"put,omitempty" yaml:"put,omitempty"` + Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"` + Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"` +} + +type Operation struct { + Summary string `json:"summary" yaml:"summary"` + Description string `json:"description" yaml:"description"` + Parameters []Parameter `json:"parameters" yaml:"parameters"` + OperationID string `json:"operationId" yaml:"operationId"` + RequestBody *RequestBody `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` + Responses map[string]ResponseItem `json:"responses" yaml:"responses"` +} + +type Parameter struct { + Name string `json:"name" yaml:"name"` + In string `json:"in" yaml:"in"` + Required bool `json:"required" yaml:"required"` + Schema ParamSchema `json:"schema" yaml:"schema"` + Example string `json:"example" yaml:"example"` +} + +type RequestBody struct { + Content map[string]MediaType `json:"content" yaml:"content"` +} + +type MediaType struct { + Schema Schema `json:"schema" yaml:"schema"` + Example map[string]interface{} `json:"example" yaml:"example"` +} + +type ResponseItem struct { + Description string `json:"description" yaml:"description"` + Content map[string]MediaType `json:"content" yaml:"content"` +} + +type Schema struct { + Type string `json:"type" yaml:"type"` + Properties map[string]map[string]interface{} `json:"properties" yaml:"properties"` +} + +type ParamSchema struct { + Type string `json:"type" yaml:"type"` +} +type SchemaInfo struct { + Service string `json:"service" yaml:"service"` + TestSetID string `json:"testSetId" yaml:"testSetId"` + Name string `json:"name" yaml:"name"` + Score float64 `json:"score" yaml:"score"` + Data OpenAPI `json:"data" yaml:"data"` +} +type Summary struct { + ServicesSummary []ServiceSummary `json:"servicesSummary" yaml:"servicesSummary"` +} +type ServiceSummary struct { + Service string `json:"service" yaml:"service"` + TestSets map[string]Status `json:"testSets" yaml:"testSets"` + PassedCount int `json:"passedCount" yaml:"passedCount"` + FailedCount int `json:"failedCount" yaml:"failedCount"` + MissedCount int `json:"missedCount" yaml:"missedCount"` +} +type Status struct { + Failed []string `json:"failed" yaml:"failed"` + Passed []string `json:"passed" yaml:"passed"` + Missed []string `json:"missed" yaml:"missed"` +} + +type MockMapping struct { + Service string `json:"service" yaml:"service"` + TestSetID string `json:"testSetId" yaml:"testSetId"` + Mocks []*OpenAPI `json:"mocks" yaml:"mocks"` +} diff --git a/keploy/pkg/models/postgres.go b/keploy/pkg/models/postgres.go new file mode 100755 index 0000000..bb8306b --- /dev/null +++ b/keploy/pkg/models/postgres.go @@ -0,0 +1,114 @@ +package models + +import ( + "time" + + "github.com/jackc/pgproto3/v2" +) + +// ProtocolVersionNumber should be replaced with actual version number if different +const ProtocolVersionNumber uint32 = 196608 + +type PostgresSpec struct { + Metadata map[string]string `json:"metadata" yaml:"metadata"` + + // Objects []*models.OutputBinary `json:"objects" yaml:"objects"` + PostgresRequests []Backend `json:"RequestBin,omitempty"` + PostgresResponses []Frontend `json:"ResponseBin,omitempty"` + + ReqTimestampMock time.Time `json:"ReqTimestampMock,omitempty"` + ResTimestampMock time.Time `json:"ResTimestampMock,omitempty"` +} + +// Backend is PG Request Packet Transcoder +type Backend struct { + PacketTypes []string `json:"header,omitempty" yaml:"header,omitempty,flow"` + Identfier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` + Length uint32 `json:"length,omitempty" yaml:"length,omitempty"` + Payload string `json:"payload,omitempty" yaml:"payload,omitempty"` + Bind pgproto3.Bind `yaml:"-"` + Binds []pgproto3.Bind `json:"bind,omitempty" yaml:"bind,omitempty"` + CancelRequest pgproto3.CancelRequest `json:"cancel_request,omitempty" yaml:"cancel_request,omitempty"` + Close pgproto3.Close `json:"close,omitempty" yaml:"close,omitempty"` + CopyFail pgproto3.CopyFail `json:"copy_fail,omitempty" yaml:"copy_fail,omitempty"` + CopyData pgproto3.CopyData `json:"copy_data,omitempty" yaml:"copy_data,omitempty"` + CopyDone pgproto3.CopyDone `json:"copy_done,omitempty" yaml:"copy_done,omitempty"` + Describe pgproto3.Describe `json:"describe,omitempty" yaml:"describe,omitempty"` + Execute pgproto3.Execute `yaml:"-"` + Executes []pgproto3.Execute `json:"execute,omitempty" yaml:"execute,omitempty"` + Flush pgproto3.Flush `json:"flush,omitempty" yaml:"flush,omitempty"` + FunctionCall pgproto3.FunctionCall `json:"function_call,omitempty" yaml:"function_call,omitempty"` + GssEncRequest pgproto3.GSSEncRequest `json:"gss_enc_request,omitempty" yaml:"gss_enc_request,omitempty"` + Parse pgproto3.Parse `yaml:"-"` + Parses []pgproto3.Parse `json:"parse,omitempty" yaml:"parse,omitempty"` + Query pgproto3.Query `json:"query,omitempty" yaml:"query,omitempty"` + SSlRequest pgproto3.SSLRequest `json:"ssl_request,omitempty" yaml:"ssl_request,omitempty"` + StartupMessage pgproto3.StartupMessage `json:"startup_message,omitempty" yaml:"startup_message,omitempty"` + Sync pgproto3.Sync `json:"sync,omitempty" yaml:"sync,omitempty"` + Terminate pgproto3.Terminate `json:"terminate,omitempty" yaml:"terminate,omitempty"` + SASLInitialResponse pgproto3.SASLInitialResponse `json:"sasl_initial_response,omitempty" yaml:"sasl_initial_response,omitempty"` + SASLResponse pgproto3.SASLResponse `json:"sasl_response,omitempty" yaml:"sasl_response,omitempty"` + PasswordMessage pgproto3.PasswordMessage `json:"password_message,omitempty" yaml:"password_message,omitempty"` + MsgType byte `json:"msg_type,omitempty" yaml:"msg_type,omitempty"` + PartialMsg bool `json:"partial_msg,omitempty" yaml:"partial_msg,omitempty"` + AuthType int32 `json:"auth_type" yaml:"auth_type"` + BodyLen int `json:"body_len,omitempty" yaml:"body_len,omitempty"` + // AuthMechanism string `json:"auth_mechanism,omitempty" yaml:"auth_mechanism,omitempty"` +} + +type Frontend struct { + PacketTypes []string `json:"header,omitempty" yaml:"header,omitempty,flow"` + Identfier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` + Length uint32 `json:"length,omitempty" yaml:"length,omitempty"` + Payload string `json:"payload,omitempty" yaml:"payload,omitempty"` + AuthenticationOk pgproto3.AuthenticationOk `json:"authentication_ok,omitempty" yaml:"authentication_ok,omitempty"` + AuthenticationCleartextPassword pgproto3.AuthenticationCleartextPassword `json:"authentication_cleartext_password,omitempty" yaml:"authentication_cleartext_password,omitempty"` + AuthenticationMD5Password pgproto3.AuthenticationMD5Password `json:"authentication_md5_password,omitempty" yaml:"authentication_md5_password,omitempty"` + AuthenticationGSS pgproto3.AuthenticationGSS `json:"authentication_gss,omitempty" yaml:"authentication_gss,omitempty"` + AuthenticationGSSContinue pgproto3.AuthenticationGSSContinue `json:"authentication_gss_continue,omitempty" yaml:"authentication_gss_continue,omitempty"` + AuthenticationSASL pgproto3.AuthenticationSASL `json:"authentication_sasl,omitempty" yaml:"authentication_sasl,omitempty"` + AuthenticationSASLContinue pgproto3.AuthenticationSASLContinue `json:"authentication_sasl_continue,omitempty" yaml:"authentication_sasl_continue,omitempty,flow"` + AuthenticationSASLFinal pgproto3.AuthenticationSASLFinal `json:"authentication_sasl_final,omitempty" yaml:"authentication_sasl_final,omitempty,flow"` + BackendKeyData pgproto3.BackendKeyData `json:"backend_key_data,omitempty" yaml:"backend_key_data,omitempty"` + BindComplete pgproto3.BindComplete `yaml:"-"` + BindCompletes []pgproto3.BindComplete `json:"bind_complete,omitempty" yaml:"bind_complete,omitempty"` + CloseComplete pgproto3.CloseComplete `json:"close_complete,omitempty" yaml:"close_complete,omitempty"` + CommandComplete pgproto3.CommandComplete `yaml:"-"` + CommandCompletes []pgproto3.CommandComplete `json:"command_complete,omitempty" yaml:"command_complete,omitempty"` + CopyBothResponse pgproto3.CopyBothResponse `json:"copy_both_response,omitempty" yaml:"copy_both_response,omitempty"` + CopyData pgproto3.CopyData `json:"copy_data,omitempty" yaml:"copy_data,omitempty"` + CopyInResponse pgproto3.CopyInResponse `json:"copy_in_response,omitempty" yaml:"copy_in_response,omitempty"` + CopyOutResponse pgproto3.CopyOutResponse `json:"copy_out_response,omitempty" yaml:"copy_out_response,omitempty"` + CopyDone pgproto3.CopyDone `json:"copy_done,omitempty" yaml:"copy_done,omitempty"` + DataRow pgproto3.DataRow `yaml:"-"` + DataRows []pgproto3.DataRow `json:"data_row,omitempty" yaml:"data_row,omitempty,flow"` + EmptyQueryResponse pgproto3.EmptyQueryResponse `json:"empty_query_response,omitempty" yaml:"empty_query_response,omitempty"` + ErrorResponse pgproto3.ErrorResponse `json:"error_response,omitempty" yaml:"error_response,omitempty"` + FunctionCallResponse pgproto3.FunctionCallResponse `json:"function_call_response,omitempty" yaml:"function_call_response,omitempty"` + NoData pgproto3.NoData `json:"no_data,omitempty" yaml:"no_data,omitempty"` + NoticeResponse pgproto3.NoticeResponse `json:"notice_response,omitempty" yaml:"notice_response,omitempty"` + NotificationResponse pgproto3.NotificationResponse `json:"notification_response,omitempty" yaml:"notification_response,omitempty"` + ParameterDescription pgproto3.ParameterDescription `json:"parameter_description,omitempty" yaml:"parameter_description,omitempty"` + ParameterStatus pgproto3.ParameterStatus `yaml:"-"` + ParameterStatusCombined []pgproto3.ParameterStatus `json:"parameter_status,omitempty" yaml:"parameter_status,omitempty"` + ParseComplete pgproto3.ParseComplete `yaml:"-"` + ParseCompletes []pgproto3.ParseComplete `json:"parse_complete,omitempty" yaml:"parse_complete,omitempty"` + ReadyForQuery pgproto3.ReadyForQuery `json:"ready_for_query,omitempty" yaml:"ready_for_query,omitempty"` + RowDescription pgproto3.RowDescription `json:"row_description,omitempty" yaml:"row_description,omitempty,flow"` + PortalSuspended pgproto3.PortalSuspended `json:"portal_suspended,omitempty" yaml:"portal_suspended,omitempty"` + MsgType byte `json:"msg_type,omitempty" yaml:"msg_type,omitempty"` + AuthType int32 `json:"auth_type" yaml:"auth_type"` + // AuthMechanism string `json:"auth_mechanism,omitempty" yaml:"auth_mechanism,omitempty"` + BodyLen int `json:"body_len,omitempty" yaml:"body_len,omitempty"` +} + +type StartupPacket struct { + Length uint32 + ProtocolVersion uint32 +} + +type RegularPacket struct { + Identifier byte + Length uint32 + Payload []byte +} diff --git a/keploy/pkg/models/redis.go b/keploy/pkg/models/redis.go new file mode 100644 index 0000000..98aa94e --- /dev/null +++ b/keploy/pkg/models/redis.go @@ -0,0 +1,13 @@ +package models + +import ( + "time" +) + +type RedisSchema struct { + Metadata map[string]string `json:"metadata" yaml:"metadata"` + RedisRequests []Payload `json:"RequestBin,omitempty"` + RedisResponses []Payload `json:"ResponseBin,omitempty"` + ReqTimestampMock time.Time `json:"reqTimestampMock,omitempty"` + ResTimestampMock time.Time `json:"resTimestampMock,omitempty"` +} diff --git a/keploy/pkg/models/tele.go b/keploy/pkg/models/tele.go new file mode 100755 index 0000000..b30f250 --- /dev/null +++ b/keploy/pkg/models/tele.go @@ -0,0 +1,14 @@ +package models + +import "sync" + +type TeleEvent struct { + InstallationID string `json:"installationId"` + EventType string `json:"eventType"` + Meta *sync.Map `json:"meta"` + CreatedAt int64 `json:"createdAt"` + TeleCheck bool `json:"tele_check"` + OS string `json:"os"` + KeployVersion string `json:"keploy_version"` + Arch string `json:"arch"` +} diff --git a/keploy/pkg/models/testcase.go b/keploy/pkg/models/testcase.go new file mode 100755 index 0000000..94778af --- /dev/null +++ b/keploy/pkg/models/testcase.go @@ -0,0 +1,73 @@ +package models + +type BodyType string +type Version string + +const V1Beta1 = Version("api.keploy.io/v1beta1") + +// BodyType constants for HTTP and gRPC +const ( + JSON BodyType = "JSON" + XML BodyType = "XML" + HTML BodyType = "HTML" + CSV BodyType = "CSV" + Plain BodyType = "PLAIN" + Utf8 BodyType = "utf-8" + Binary BodyType = "BINARY" + GrpcCompression BodyType = "GRPC_COMPRESSION" + GrpcLength BodyType = "GRPC_LENGTH" + GrpcData BodyType = "GRPC_DATA" + UnknownType BodyType = "UNKNOWN" +) + +var ( + currentVersion = V1Beta1 +) + +func SetVersion(V1 string) { + currentVersion = Version(V1) +} + +func GetVersion() (V1 Version) { + return currentVersion +} + +type TestCase struct { + Version Version `json:"version" bson:"version"` + Kind Kind `json:"kind" bson:"kind"` + Name string `json:"name" bson:"name"` + Description string `json:"description" bson:"description"` + Created int64 `json:"created" bson:"created"` + Updated int64 `json:"updated" bson:"updated"` + Captured int64 `json:"captured" bson:"captured"` + HTTPReq HTTPReq `json:"http_req" bson:"http_req"` + HTTPResp HTTPResp `json:"http_resp" bson:"http_resp"` + AllKeys map[string][]string `json:"all_keys" bson:"all_keys"` + GrpcResp GrpcResp `json:"grpcResp" bson:"grpcResp"` + GrpcReq GrpcReq `json:"grpcReq" bson:"grpcReq"` + Anchors map[string][]string `json:"anchors" bson:"anchors"` + Noise map[string][]string `json:"noise" bson:"noise"` + Mocks []*Mock `json:"mocks" bson:"mocks"` + Type string `json:"type" bson:"type"` + Curl string `json:"curl" bson:"curl"` + IsLast bool `json:"is_last" bson:"is_last"` + Assertions map[AssertionType]interface{} `json:"assertion" bson:"assertion"` +} + +func (tc *TestCase) GetKind() string { + return string(tc.Kind) +} + +type NoiseParams struct { + TestCaseID string `json:"testCaseID"` + EditedBy string `json:"editedBy"` + Assertion map[string][]string `json:"assertion"` + Ops string `json:"ops"` + AfterNoise map[string][]string `json:"afterNoise"` +} + +// enum for ops +const ( + OpsAdd = "ADD" + OpsRemove = "REMOVE" +) diff --git a/keploy/pkg/models/testcompare.go b/keploy/pkg/models/testcompare.go new file mode 100644 index 0000000..3c8ec0b --- /dev/null +++ b/keploy/pkg/models/testcompare.go @@ -0,0 +1,42 @@ +package models + +type AbsResult struct { + Kind StringResult `json:"kind" bson:"kind" yaml:"kind"` + Name StringResult `json:"name" bson:"name" yaml:"name"` + Req ReqCompare `json:"req" bson:"req" yaml:"req"` + Resp RespCompare `json:"resp" bson:"resp" yaml:"resp"` + // CurlResult StringResult `json:"curl_result" bson:"curl_result" yaml:"curl_result"` +} + +type ReqCompare struct { + MethodResult StringResult `json:"method_result" bson:"method_result" yaml:"method_result"` + URLResult StringResult `json:"url_result" bson:"url_result" yaml:"url_result"` + URLParamsResult []URLParamsResult `json:"url_params_result" bson:"url_params_result" yaml:"url_params_result"` + ProtoMajor IntResult `json:"proto_major" bson:"proto_major" yaml:"proto_major"` + ProtoMinor IntResult `json:"proto_minor" bson:"proto_minor" yaml:"proto_minor"` + HeaderResult []HeaderResult `json:"headers_result" bson:"headers_result" yaml:"headers_result"` + BodyResult BodyResult `json:"body_result" bson:"body_result" yaml:"body_result"` +} + +type RespCompare struct { + StatusCode IntResult `json:"status_code" bson:"status_code" yaml:"status_code"` + HeadersResult []HeaderResult `json:"headers_result" bson:"headers_result" yaml:"headers_result"` + BodyResult BodyResult `json:"body_result" bson:"body_result" yaml:"body_result"` +} + +type StringResult struct { + Normal bool `json:"normal" bson:"normal" yaml:"normal"` + Expected string `json:"expected" bson:"expected" yaml:"expected"` + Actual string `json:"actual" bson:"actual" yaml:"actual"` +} + +type URLParamsResult struct { + Normal bool `json:"normal" bson:"normal" yaml:"normal"` + Expected Params `json:"expected" bson:"expected" yaml:"expected"` + Actual Params `json:"actual" bson:"actual" yaml:"actual"` +} + +type Params struct { + Key string `json:"key" bson:"key" yaml:"key"` + Value string `json:"value" bson:"value" yaml:"value"` +} diff --git a/keploy/pkg/models/testrun.go b/keploy/pkg/models/testrun.go new file mode 100755 index 0000000..06241f7 --- /dev/null +++ b/keploy/pkg/models/testrun.go @@ -0,0 +1,155 @@ +package models + +import ( + "errors" +) + +type TestReport struct { + Version Version `json:"version" yaml:"version"` + Name string `json:"name" yaml:"name"` + Status string `json:"status" yaml:"status"` + Success int `json:"success" yaml:"success"` + Failure int `json:"failure" yaml:"failure"` + Ignored int `json:"ignored" yaml:"ignored"` + Total int `json:"total" yaml:"total"` + Tests []TestResult `json:"tests" yaml:"tests,omitempty"` + TestSet string `json:"testSet" yaml:"test_set"` + CreatedAt int64 `json:"created_at" yaml:"created_at"` +} + +type TestCoverage struct { + FileCov map[string]string `json:"fileCoverage" yaml:"file_coverage"` + TotalCov string `json:"totalCoverage" yaml:"total_coverage"` + Loc Loc `json:"loc" yaml:"loc"` +} + +type Loc struct { + Total int `json:"total" yaml:"total"` + Covered int `json:"covered" yaml:"covered"` +} + +func (tr *TestReport) GetKind() string { + return "TestReport" +} + +type TestResult struct { + Kind Kind `json:"kind" yaml:"kind"` + Name string `json:"name" yaml:"name"` + Status TestStatus `json:"status" yaml:"status"` + Started int64 `json:"started" yaml:"started"` + Completed int64 `json:"completed" yaml:"completed"` + TestCasePath string `json:"testCasePath" yaml:"test_case_path"` + MockPath string `json:"mockPath" yaml:"mock_path"` + TestCaseID string `json:"testCaseID" yaml:"test_case_id"` + Req HTTPReq `json:"req" yaml:"req,omitempty"` + Res HTTPResp `json:"resp" yaml:"resp,omitempty"` + GrpcReq GrpcReq `json:"grpcReq,omitempty" yaml:"grpcReq,omitempty"` + GrpcRes GrpcResp `json:"grpcRes,omitempty" yaml:"grpcRes,omitempty"` + Noise Noise `json:"noise" yaml:"noise,omitempty"` + Result Result `json:"result" yaml:"result"` +} + +func (tr *TestResult) GetKind() string { + return string(tr.Kind) +} + +type TestSetStatus string + +// constants for testSet status +const ( + TestSetStatusRunning TestSetStatus = "RUNNING" + TestSetStatusFailed TestSetStatus = "FAILED" + TestSetStatusPassed TestSetStatus = "PASSED" + TestSetStatusAppHalted TestSetStatus = "APP_HALTED" + TestSetStatusUserAbort TestSetStatus = "USER_ABORT" + TestSetStatusFaultUserApp TestSetStatus = "APP_FAULT" + TestSetStatusInternalErr TestSetStatus = "INTERNAL_ERR" + TestSetStatusFaultScript TestSetStatus = "SCRIPT_FAULT" + TestSetStatusIgnored TestSetStatus = "IGNORED" + TestSetStatusNoTestsToRun TestSetStatus = "NO_TESTS_TO_RUN" +) + +func StringToTestSetStatus(s string) (TestSetStatus, error) { + switch s { + case "RUNNING": + return TestSetStatusRunning, nil + case "FAILED": + return TestSetStatusFailed, nil + case "PASSED": + return TestSetStatusPassed, nil + case "APP_HALTED": + return TestSetStatusAppHalted, nil + case "USER_ABORT": + return TestSetStatusUserAbort, nil + case "APP_FAULT": + return TestSetStatusFaultUserApp, nil + case "INTERNAL_ERR": + return TestSetStatusInternalErr, nil + case "NO_TESTS_TO_RUN": + return TestSetStatusNoTestsToRun, nil + default: + return "", errors.New("invalid TestSetStatus value") + } +} + +type Result struct { + StatusCode IntResult `json:"status_code" bson:"status_code" yaml:"status_code"` + HeadersResult []HeaderResult `json:"headers_result" bson:"headers_result" yaml:"headers_result"` + BodyResult []BodyResult `json:"body_result" bson:"body_result" yaml:"body_result"` + DepResult []DepResult `json:"dep_result" bson:"dep_result" yaml:"dep_result"` + TrailerResult []HeaderResult `json:"trailer_result,omitempty" bson:"trailer_result,omitempty" yaml:"trailer_result,omitempty"` +} + +type DepResult struct { + Name string `json:"name" bson:"name" yaml:"name"` + Type string `json:"type" bson:"type" yaml:"type"` + Meta []DepMetaResult `json:"meta" bson:"meta" yaml:"meta"` +} + +type DepMetaResult struct { + Normal bool `json:"normal" bson:"normal" yaml:"normal"` + Key string `json:"key" bson:"key" yaml:"key"` + Expected string `json:"expected" bson:"expected" yaml:"expected"` + Actual string `json:"actual" bson:"actual" yaml:"actual"` +} + +type IntResult struct { + Normal bool `json:"normal" bson:"normal" yaml:"normal"` + Expected int `json:"expected" bson:"expected" yaml:"expected"` + Actual int `json:"actual" bson:"actual" yaml:"actual"` +} + +type HeaderResult struct { + Normal bool `json:"normal" bson:"normal" yaml:"normal"` + Expected Header `json:"expected" bson:"expected" yaml:"expected"` + Actual Header `json:"actual" bson:"actual" yaml:"actual"` +} + +type Header struct { + Key string `json:"key" bson:"key" yaml:"key"` + Value []string `json:"value" bson:"value" yaml:"value"` +} + +type BodyResult struct { + Normal bool `json:"normal" bson:"normal" yaml:"normal"` + Type BodyType `json:"type" bson:"type" yaml:"type"` + Expected string `json:"expected" bson:"expected" yaml:"expected"` + Actual string `json:"actual" bson:"actual" yaml:"actual"` +} + +type TestStatus string + +// constants for test status +const ( + TestStatusPending TestStatus = "PENDING" + TestStatusRunning TestStatus = "RUNNING" + TestStatusFailed TestStatus = "FAILED" + TestStatusPassed TestStatus = "PASSED" + TestStatusIgnored TestStatus = "IGNORED" +) + +type ( + Noise map[string][]string + GlobalNoise map[string]map[string][]string + TestsetNoise map[string]map[string]map[string][]string +) diff --git a/keploy/pkg/models/ut.go b/keploy/pkg/models/ut.go new file mode 100644 index 0000000..4495830 --- /dev/null +++ b/keploy/pkg/models/ut.go @@ -0,0 +1,133 @@ +package models + +import "encoding/xml" + +type UTResult struct { + Status string `yaml:"status"` + Reason string `yaml:"reason"` + ExitCode int `yaml:"exit_code"` + Stderr string `yaml:"stderr"` + Stdout string `yaml:"stdout"` + Test string `yaml:"test"` +} + +type UTDetails struct { + Language string `yaml:"language"` + TestSignature string `yaml:"existing_test_function_signature"` + NewTests []UT `yaml:"new_tests"` + RefactoredSourceCode string `yaml:"refactored_source_code,omitempty"` +} + +type UT struct { + TestBehavior string `yaml:"test_behavior"` + TestName string `yaml:"test_name"` + TestCode string `yaml:"test_code"` + NewImportsCode string `yaml:"new_imports_code"` + LibraryInstallationCode string `yaml:"library_installation_code"` + TestsTags string `yaml:"tests_tags"` +} + +type FailedUT struct { + TestCode string `yaml:"test_code"` + ErrorMsg string `yaml:"error_msg"` + NewImportsCode string `yaml:"imports_code"` + LibraryInstallationCode string `yaml:"library_installation_code"` +} + +type UTIndentationInfo struct { + Language string `yaml:"language"` + TestingFramework string `yaml:"testing_framework"` + NumberOfTests int `yaml:"number_of_tests"` + Indentation int `yaml:"test_headers_indentation"` +} + +type UTInsertionInfo struct { + Language string `yaml:"language"` + TestingFramework string `yaml:"testing_framework"` + NumberOfTests int `yaml:"number_of_tests"` + Line int `yaml:"relevant_line_number_to_insert_after"` +} + +type CoverageResult struct { + LinesCovered []int + LinesMissed []int + Coverage float64 + Files []string + ReportContent string +} + +type Cobertura struct { + XMLName xml.Name `xml:"coverage"` + Sources []string `xml:"sources>source"` + Packages []Package `xml:"packages>package"` +} + +type Package struct { + Name string `xml:"name,attr"` + Classes []Class `xml:"classes>class"` +} + +type Class struct { + Name string `xml:"name,attr"` + FileName string `xml:"filename,attr"` + Lines []Line `xml:"lines>line"` +} + +type Line struct { + Number int `xml:"number,attr"` + Hits int `xml:"hits,attr"` +} + +type Jacoco struct { + Name string `xml:"name,attr"` + XMLName xml.Name `xml:"report"` + Packages []JacocoPackage `xml:"package"` + SessionInfo []SessionInfo `xml:"sessioninfo"` +} + +type SessionInfo struct { + ID string `xml:"id,attr"` + Start string `xml:"start,attr"` + Dump string `xml:"dump,attr"` +} + +type JacocoPackage struct { + Name string `xml:"name,attr"` + Classes []JacocoClass `xml:"class"` + Counters []Counter `xml:"counter"` + SourceFiles []JacocoSourceFile `xml:"sourcefile"` // Adding this field to capture source files +} + +type JacocoSourceFile struct { + Name string `xml:"name,attr"` + Lines []JacocoLine `xml:"line"` + Counters []Counter `xml:"counter"` +} + +type JacocoClass struct { + Name string `xml:"name,attr"` + SourceFile string `xml:"sourcefilename,attr"` + Methods []JacocoMethod `xml:"method"` + Lines []JacocoLine `xml:"line"` // This is where JacocoLine is used +} + +type JacocoMethod struct { + Name string `xml:"name,attr"` + Descriptor string `xml:"desc,attr"` + Line string `xml:"line,attr"` + Counters []Counter `xml:"counter"` +} + +type JacocoLine struct { + Number string `xml:"nr,attr"` + MissedInstructions string `xml:"mi,attr"` + CoveredInstructions string `xml:"ci,attr"` + MissedBranches string `xml:"mb,attr"` + CoveredBranches string `xml:"cb,attr"` +} + +type Counter struct { + Type string `xml:"type,attr"` + Missed string `xml:"missed,attr"` + Covered string `xml:"covered,attr"` +} diff --git a/keploy/pkg/platform/README.md b/keploy/pkg/platform/README.md new file mode 100755 index 0000000..ab693cc --- /dev/null +++ b/keploy/pkg/platform/README.md @@ -0,0 +1,12 @@ +# Platform Package Documentation + +This package exposes a set of interfaces and methods for common data +store operations such as creating, reading, updating, and deleting +(CRUD) records. + +Implementations for different types of data stores can be added +here, all in one place. These implementations are abstracted from +other logic using interface methods. + +This package depends on the `models` package, using its structs to +perform the CRUD operations. \ No newline at end of file diff --git a/keploy/pkg/platform/auth/auth.go b/keploy/pkg/platform/auth/auth.go new file mode 100644 index 0000000..dc98202 --- /dev/null +++ b/keploy/pkg/platform/auth/auth.go @@ -0,0 +1,237 @@ +// Package auth defines methods for authenticating with GitHub. +package auth + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/service" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type Auth struct { + serverURL string + jwtToken string + installationID string + logger *zap.Logger + GitHubClientID string +} + +func New(serverURL string, installationID string, logger *zap.Logger, gitHubClientID string) service.Auth { + return &Auth{ + serverURL: serverURL, + installationID: installationID, + logger: logger, + GitHubClientID: gitHubClientID, + } +} + +func (a *Auth) Login(ctx context.Context) bool { + + deviceCodeResp, err := requestDeviceCode(a.logger, a.GitHubClientID) + if err != nil { + a.logger.Error("Error requesting device code", zap.Error(err)) + return false + } + + promptUser(deviceCodeResp) + + tokenResp, err := pollForAccessToken(ctx, a.logger, deviceCodeResp.DeviceCode, a.GitHubClientID, deviceCodeResp.Interval) + if err != nil { + if ctx.Err() == context.Canceled { + return false + } + utils.LogError(a.logger, err, "failed to poll for access token") + return false + } + + userEmail, isValid, authErr, err := a.Validate(ctx, tokenResp.AccessToken) + if err != nil { + if ctx.Err() == context.Canceled { + return false + } + a.logger.Error("Error checking auth", zap.Error(err)) + return false + } + + if !isValid { + a.logger.Error("Invalid token", zap.Any("error", authErr)) + return false + } + a.logger.Info("Successfully logged in to Keploy using GitHub as " + userEmail) + return true +} + +func (a *Auth) Validate(ctx context.Context, token string) (string, bool, string, error) { + url := fmt.Sprintf("%s/auth/github", a.serverURL) + // this i can directly call the vs code extension + requestBody := &models.AuthReq{ + GitHubToken: token, + InstallationID: a.installationID, + } + requestJSON, err := json.Marshal(requestBody) + if err != nil { + utils.LogError(a.logger, err, "failed to marshal request body for authentication") + return "", false, "", fmt.Errorf("error marshaling request body for authentication: %s", err.Error()) + } + + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(requestJSON)) + if err != nil { + utils.LogError(a.logger, err, "failed to create request for authentication") + return "", false, "", fmt.Errorf("error creating request for authentication: %s", err.Error()) + } + req.Header.Set("Content-Type", "application/json") + client := &http.Client{} + res, err := client.Do(req) + if err != nil { + return "", false, "", fmt.Errorf("failed to authenticate: %s", err.Error()) + } + + defer func() { + err := res.Body.Close() + if err != nil { + utils.LogError(a.logger, err, "failed to close response body for authentication") + } + }() + + var respBody models.AuthResp + err = json.NewDecoder(res.Body).Decode(&respBody) + if err != nil { + return "", false, "", fmt.Errorf("error unmarshalling the authentication response: %s", err.Error()) + } + + if res.StatusCode != 200 || res.StatusCode >= 300 { + return "", false, "", fmt.Errorf("failed to authenticate: %s", respBody.Error) + } + + a.jwtToken = respBody.JwtToken + return respBody.EmailID, respBody.IsValid, respBody.Error, nil +} + +func (a *Auth) GetToken(ctx context.Context) (string, error) { + if a.jwtToken == "" { + _, _, _, err := a.Validate(ctx, "") + if err != nil { + return "", err + } + } + + if a.jwtToken == "" { + a.logger.Warn("Looks like you are not logged in.") + a.logger.Warn("Please follow the instructions to login.") + isSuccessful := a.Login(ctx) + if !isSuccessful { + return "", fmt.Errorf("failed to login") + } + } + + return a.jwtToken, nil +} + +// requestDeviceCode sends a request to GitHub to get a device code for the users machine to authenticate +func requestDeviceCode(logger *zap.Logger, gitHubClientID string) (*models.DeviceCodeResponse, error) { + data := url.Values{} + data.Set("client_id", gitHubClientID) + data.Set("scope", "read:user") + + resp, err := http.PostForm(models.DeviceCodeURL, data) + if err != nil { + return nil, err + } + defer func() { + err := resp.Body.Close() + if err != nil { + utils.LogError(logger, err, "failed to close response body") + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + // Parse the URL-encoded response + parsed, err := url.ParseQuery(string(body)) + if err != nil { + return nil, err + } + + // Populate the DeviceCodeResponse struct + deviceCodeResp := &models.DeviceCodeResponse{ + DeviceCode: parsed.Get("device_code"), + UserCode: parsed.Get("user_code"), + VerificationURI: parsed.Get("verification_uri"), + Interval: 5, // Default value; you can parse this from the response if needed + } + + return deviceCodeResp, nil +} + +func promptUser(deviceCodeResp *models.DeviceCodeResponse) { + fmt.Printf("Please go to %s and enter the code: %s\n", deviceCodeResp.VerificationURI, deviceCodeResp.UserCode) +} + +// pollForAccessToken polls GitHub for an access token using the device code provided by requestDeviceCode +// It will poll every interval seconds until the user has approved the device code +func pollForAccessToken(ctx context.Context, logger *zap.Logger, deviceCode, gitHubClientID string, interval int) (*models.AccessTokenResponse, error) { + data := url.Values{} + data.Set("client_id", gitHubClientID) + data.Set("device_code", deviceCode) + data.Set("grant_type", "urn:ietf:params:oauth:grant-type:device_code") + + fmt.Println("waiting for approval (this might take 4-5 sec for approval)...") + + for { + resp, err := http.PostForm(models.TokenURL, data) + if err != nil { + return nil, err + } + defer func() { + err := resp.Body.Close() + if err != nil { + utils.LogError(logger, err, "failed to close response body") + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var tokenResp models.AccessTokenResponse + parsed, err := url.ParseQuery(string(body)) + if err != nil { + return nil, err + } + if parsed.Get("error") == "authorization_pending" { + select { + case <-time.After(time.Duration(interval) * time.Second): + case <-ctx.Done(): + return nil, ctx.Err() + } + continue + } else if parsed.Get("error") != "" { + return nil, fmt.Errorf("error: %s", parsed.Get("error_description")) + } + if accessToken := parsed.Get("access_token"); accessToken != "" { + return &models.AccessTokenResponse{ + AccessToken: accessToken, + TokenType: parsed.Get("token_type"), + Scope: parsed.Get("scope"), + }, nil + } + return &tokenResp, nil + } +} diff --git a/keploy/pkg/platform/coverage/csharp/csharp.go b/keploy/pkg/platform/coverage/csharp/csharp.go new file mode 100644 index 0000000..5de1cd9 --- /dev/null +++ b/keploy/pkg/platform/coverage/csharp/csharp.go @@ -0,0 +1,130 @@ +// Package csharp impliments methods for Csharp coverage services. +package csharp + +import ( + "context" + "encoding/xml" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/coverage" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type Csharp struct { + ctx context.Context + logger *zap.Logger + reportdb coverage.ReportDB + cmd string + executable string + dotnetCoveragePath string +} + +type CoverageStructure struct { + LineRate float64 `xml:"line-rate,attr"` + + Packages []struct { + Classes []struct { + FileName string `xml:"filename,attr"` + ClassLineRate float64 `xml:"line-rate,attr"` + ClassBranchRate float64 `xml:"branch-rate,attr"` + } `xml:"classes>class"` + } `xml:"packages>package"` +} + +func New(ctx context.Context, logger *zap.Logger, reportDB coverage.ReportDB, cmd, dotnetCoveragePath, executable string) *Csharp { + return &Csharp{ + ctx: ctx, + logger: logger, + reportdb: reportDB, + cmd: cmd, + dotnetCoveragePath: dotnetCoveragePath, + executable: executable, + } +} + +func (cs *Csharp) PreProcess(_ bool) (string, error) { + // default location for dotnet-coverage + dotnetCoveragePath := "~/.dotnet/tools/dotnet-coverage" + if cs.dotnetCoveragePath != "" { + dotnetCoveragePath = cs.dotnetCoveragePath + } + + isFileExists, err := utils.FileExists(dotnetCoveragePath) + if err != nil { + cs.logger.Warn("error checking dotnet-coverage tool existance: %s", zap.Error(err)) + return cs.cmd, err + } + + if !isFileExists { + return cs.cmd, fmt.Errorf("dotnet coverage tool not found at: %s", dotnetCoveragePath) + } + + cs.cmd = strings.Replace(cs.cmd, cs.executable, fmt.Sprintf("%s collect --output target/${TESTSETID}.cobertura --output-format cobertura", cs.executable), 1) + + // download dotnet coverage + dotnetPath := filepath.Join(os.TempDir(), "dotnet") + err = os.MkdirAll(dotnetPath, 0777) + + if err != nil { + cs.logger.Debug("failed to create dotnet directory: %s", zap.Error(err)) + return cs.cmd, err + } + + err = downloadDotnetCoverage(cs.ctx) + if err != nil { + cs.logger.Debug("failed to download and extract dotnet binaries: %s", zap.Error(err)) + return cs.cmd, err + } + + return cs.cmd, nil +} + +func (cs *Csharp) GetCoverage() (models.TestCoverage, error) { + testCov := models.TestCoverage{ + FileCov: make(map[string]string), + TotalCov: "", + } + + // Define the path to cobertura file + coberturaPath := filepath.Join("target", "site", "KeployE2E", "e2e.cobertura") + + file, err := os.Open(coberturaPath) + if err != nil { + return testCov, fmt.Errorf("failed to open cobertura file: %w", err) + } + defer func() { + if err := file.Close(); err != nil { + utils.LogError(cs.logger, err, "Error closing coverage cobertura file") + } + }() + + // complete line-by-line coverage + coverageStruct := CoverageStructure{} + if err := xml.Unmarshal([]byte(coberturaPath), &coverageStruct); err != nil { + return testCov, fmt.Errorf("failed to unmarshal cobertura file: %w", err) + } + testCov.TotalCov = strconv.FormatFloat(coverageStruct.LineRate*100, 'E', -1, 64) + + // class coverage + totalClasses, classCovered := 0.0, 0.0 + + for _, pkg := range coverageStruct.Packages { + for _, class := range pkg.Classes { + totalClasses++ + if class.ClassLineRate > 0 || class.ClassBranchRate > 0 { + classCovered++ + } + + classCoverage := (classCovered / totalClasses) * 100 + testCov.FileCov[class.FileName] = strconv.FormatFloat(classCoverage, 'E', -1, 64) + "%" + } + } + + return testCov, nil +} diff --git a/keploy/pkg/platform/coverage/csharp/utils.go b/keploy/pkg/platform/coverage/csharp/utils.go new file mode 100644 index 0000000..38739a1 --- /dev/null +++ b/keploy/pkg/platform/coverage/csharp/utils.go @@ -0,0 +1,140 @@ +package csharp + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "go.uber.org/zap" +) + +func downloadDotnetCoverage(ctx context.Context) error { + // Consruct arguments for command to check if dotnet-coverage is already installed or not + checkArgs := []string{ + "dotnet", + "tool", + "list", + "-g", + } + + checkCmd := exec.CommandContext(ctx, checkArgs[0], checkArgs[1:]...) + checkCmd.Stdout = os.Stdout + checkCmd.Stderr = os.Stderr + + if err := checkCmd.Run(); err != nil { + return fmt.Errorf("failed to check for existing dotnet-coverage: %w", err) + } + if strings.Contains(checkCmd.String(), "dotnet-coverage") { + fmt.Println("dotnet-coverage is already installed") + return nil + } + + fmt.Println("dotnet-coverage not found. Installing...") + + // Construct the command arguments to install dotnet-coverage + installArgs := []string{ + "dotnet", + "tool", + "install", + "--global", + "dotnet-coverage", + } + + installCmd := exec.CommandContext(ctx, installArgs[0], installArgs[1:]...) + installCmd.Stdout = os.Stdout + installCmd.Stderr = os.Stderr + + if err := installCmd.Run(); err != nil { + return fmt.Errorf("failed to install dotnet-coverage. Ensure .NET SDK is installed and try again: %w", err) + } + + return nil +} + +func MergeAndGenerateDotnetReport(ctx context.Context, logger *zap.Logger) error { + err := MergeCoverageFiles(ctx) + if err == nil { + err = GenerateCoverageReport(ctx) + + if err != nil { + logger.Debug("failed to generate dotnet-coverage report: %w", zap.Error(err)) + } + } else { + logger.Debug("failed to merge dotnet-coverage data: %w", zap.Error(err)) + } + + if err != nil { + return err + } + + return nil +} + +func MergeCoverageFiles(ctx context.Context) error { + // Find all .cobertura files in the target directory + sourceFiles, err := filepath.Glob("target/*.cobertura") + if err != nil { + return fmt.Errorf("error finding coverage files: %w", err) + } + + if len(sourceFiles) == 0 { + return errors.New("no coverage files found") + } + + // Construct command arguments to merge coverage files + args := []string{ + "dotnet-coverage", + "merge", + "--output", + "target/*.cobertura", + "--output-format", + "cobertura", + } + + cmd := exec.CommandContext(ctx, args[0], args[1:]...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to merge coverage files: %w", err) + } + + return nil +} + +func GenerateCoverageReport(ctx context.Context) error { + reportDir := "target/site/keployE2E" + + // Ensure that the report dir exists + if err := os.MkdirAll(reportDir, 0777); err != nil { + return fmt.Errorf("failed to create report directory: %w", err) + } + + // Consturct command arguments to generate the coverage report + args := []string{ + "dotnet-coverage", + "collect", + "--output", + "target/keploy-e2e.cobertura", + "output.coverage", + "--output-format", + "cobertura", + "--", + "dotnet", + "test", + } + + cmd := exec.CommandContext(ctx, args[0], args[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to generate report: %w", err) + } + + return nil +} diff --git a/keploy/pkg/platform/coverage/golang/golang.go b/keploy/pkg/platform/coverage/golang/golang.go new file mode 100644 index 0000000..9de8042 --- /dev/null +++ b/keploy/pkg/platform/coverage/golang/golang.go @@ -0,0 +1,151 @@ +// Package golang implements the methods for golang coverage services. +package golang + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "os/exec" + "strconv" + "strings" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/coverage" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type Golang struct { + ctx context.Context + logger *zap.Logger + reportDB coverage.ReportDB + cmd string + coverageReportPath string + commandType string +} + +func New(ctx context.Context, logger *zap.Logger, reportDB coverage.ReportDB, cmd, coverageReportPath, commandType string) *Golang { + return &Golang{ + ctx: ctx, + logger: logger, + reportDB: reportDB, + cmd: cmd, + coverageReportPath: coverageReportPath, + commandType: commandType, + } +} + +func (g *Golang) PreProcess(_ bool) (string, error) { + if !checkForCoverFlag(g.logger, g.cmd) { + return g.cmd, errors.New("binary not coverable") + } + if utils.CmdType(g.commandType) == utils.Native { + goCovPath, err := utils.SetCoveragePath(g.logger, g.coverageReportPath) + if err != nil { + g.logger.Warn("failed to set go coverage path", zap.Error(err)) + return g.cmd, err + } + err = os.Setenv("GOCOVERDIR", goCovPath) + if err != nil { + g.logger.Warn("failed to set GOCOVERDIR", zap.Error(err)) + return g.cmd, err + } + } + return g.cmd, nil +} + +func (g *Golang) GetCoverage() (models.TestCoverage, error) { + testCov := models.TestCoverage{ + FileCov: make(map[string]string), + TotalCov: "", + } + + coverageDir := os.Getenv("GOCOVERDIR") + + f, err := os.Open(coverageDir) + if err != nil { + utils.LogError(g.logger, err, "failed to open coverage directory, skipping coverage calculation") + return testCov, err + } + defer func() { + if err := f.Close(); err != nil { + utils.LogError(g.logger, err, "Error closing coverage directory, skipping coverage calculation") + } + }() + + _, err = f.Readdirnames(1) // Or f.Readdir(1) + if err == io.EOF { + utils.LogError(g.logger, err, fmt.Sprintf("no coverage files found in %s, skipping coverage calculation", coverageDir)) + return testCov, err + } + + generateCovTxtCmd := exec.CommandContext(g.ctx, "go", "tool", "covdata", "textfmt", "-i="+os.Getenv("GOCOVERDIR"), "-o="+os.Getenv("GOCOVERDIR")+"/total-coverage.txt") + _, err = generateCovTxtCmd.Output() + if err != nil { + return testCov, err + } + + coveragePerFileTmp := make(map[string][]int) // filename -> [noOfLines, coveredLines] + covdata, err := os.ReadFile(os.Getenv("GOCOVERDIR") + "/total-coverage.txt") + if err != nil { + return testCov, err + } + // a line is of the form: :.,. + for idx, line := range strings.Split(string(covdata), "\n") { + line = strings.TrimSpace(line) + if strings.Split(line, ":")[0] == "mode" || line == "" { + continue + } + lineFields := strings.Fields(line) + malformedErrMsg := "go coverage file is malformed" + if len(lineFields) == 3 { + noOfLines, err := strconv.Atoi(lineFields[1]) + if err != nil { + return testCov, err + } + coveredOrNot, err := strconv.Atoi(lineFields[2]) + if err != nil { + return testCov, err + } + i := strings.Index(line, ":") + var filename string + if i > 0 { + filename = line[:i] + } else { + return testCov, fmt.Errorf("%s at line %d", malformedErrMsg, idx) + } + + if _, ok := coveragePerFileTmp[filename]; !ok { + coveragePerFileTmp[filename] = make([]int, 2) + } + + coveragePerFileTmp[filename][0] += noOfLines + if coveredOrNot != 0 { + coveragePerFileTmp[filename][1] += noOfLines + } + } else { + return testCov, fmt.Errorf("%s at %d", malformedErrMsg, idx) + } + } + + totalLines := 0 + totalCoveredLines := 0 + for filename, lines := range coveragePerFileTmp { + totalLines += lines[0] + totalCoveredLines += lines[1] + covPercentage := float64(lines[1]*100) / float64(lines[0]) + testCov.FileCov[filename] = strconv.FormatFloat(float64(covPercentage), 'f', 2, 64) + "%" + } + testCov.TotalCov = strconv.FormatFloat(float64(totalCoveredLines*100)/float64(totalLines), 'f', 2, 64) + "%" + testCov.Loc = models.Loc{ + Total: totalLines, + Covered: totalCoveredLines, + } + return testCov, nil +} + +func (g *Golang) AppendCoverage(coverage *models.TestCoverage, testRunID string) error { + return g.reportDB.UpdateReport(g.ctx, testRunID, coverage) +} diff --git a/keploy/pkg/platform/coverage/golang/utils.go b/keploy/pkg/platform/coverage/golang/utils.go new file mode 100644 index 0000000..a54fc6a --- /dev/null +++ b/keploy/pkg/platform/coverage/golang/utils.go @@ -0,0 +1,56 @@ +package golang + +import ( + "debug/elf" + "slices" + "strings" + + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// checkForCoverFlag checks if the given Go binary has the coverage flag enabled if one argument +// else check if -cover flag is passed or not +// TODO: use native approach till https://github.com/golang/go/issues/67366 gets resolved +func checkForCoverFlag(logger *zap.Logger, cmd string) bool { + cmdFields := strings.Fields(cmd) + i := 0 + var part string + for i, part = range cmdFields { + if !strings.Contains(part, "=") { + break + } + } + if cmdFields[i] == "go" && len(cmdFields) > 1 { + if slices.Contains(cmdFields, "-cover") { + return true + } + logger.Warn("cover flag not found in command, skipping coverage calculation") + return false + } + file, err := elf.Open(cmdFields[i]) + if err != nil { + utils.LogError(logger, err, "failed to open file, skipping coverage calculation") + return false + } + defer func() { + if err := file.Close(); err != nil { + utils.LogError(logger, err, "failed to close binary file", zap.String("file", cmd)) + } + }() + + symbols, err := file.Symbols() + if err != nil { + utils.LogError(logger, err, "failed to read symbols, skipping coverage calculation") + return false + } + + for _, symbol := range symbols { + // Check for symbols that related to Go coverage instrumentation + if strings.Contains(symbol.Name, "internal/coverage") { + return true + } + } + logger.Warn("go binary was not build with -cover flag", zap.String("file", cmd)) + return false +} diff --git a/keploy/pkg/platform/coverage/java/java.go b/keploy/pkg/platform/coverage/java/java.go new file mode 100644 index 0000000..7fff377 --- /dev/null +++ b/keploy/pkg/platform/coverage/java/java.go @@ -0,0 +1,145 @@ +// Package java implements the methods for java coverage services. +package java + +import ( + "context" + "encoding/csv" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/coverage" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type Java struct { + ctx context.Context + logger *zap.Logger + reportDB coverage.ReportDB + cmd string + jacocoAgentPath string + executable string +} + +func New(ctx context.Context, logger *zap.Logger, reportDB coverage.ReportDB, cmd, jacocoAgentPath, executable string) *Java { + return &Java{ + ctx: ctx, + logger: logger, + reportDB: reportDB, + cmd: cmd, + jacocoAgentPath: jacocoAgentPath, + executable: executable, + } +} + +func (j *Java) PreProcess(_ bool) (string, error) { + // default location for jar of jacoco agent + jacocoAgentPath := "~/.m2/repository/org/jacoco/org.jacoco.agent/0.8.8/org.jacoco.agent-0.8.8-runtime.jar" + if j.jacocoAgentPath != "" { + jacocoAgentPath = j.jacocoAgentPath + } + var err error + jacocoAgentPath, err = utils.ExpandPath(jacocoAgentPath) + if err == nil { + isFileExist, err := utils.FileExists(jacocoAgentPath) + if err == nil && isFileExist { + j.cmd = strings.Replace( + j.cmd, + j.executable, + fmt.Sprintf("%s -javaagent:%s=destfile=target/${TESTSETID}.exec", j.executable, jacocoAgentPath), 1, + ) + } + } + if err != nil { + j.logger.Warn("failed to find jacoco agent. If jacoco agent is present in a different path, please set it using --jacocoAgentPath") + return j.cmd, err + } + // downlaod jacoco cli + jacocoPath := filepath.Join(os.TempDir(), "jacoco") + err = os.MkdirAll(jacocoPath, 0777) + if err != nil { + j.logger.Debug("failed to create jacoco directory", zap.Error(err)) + return j.cmd, err + } + err = downloadAndExtractJaCoCoCli(j.logger, "0.8.12", jacocoPath) + if err != nil { + j.logger.Debug("failed to download and extract jacoco binaries", zap.Error(err)) + return j.cmd, err + } + return j.cmd, nil +} + +func (j *Java) GetCoverage() (models.TestCoverage, error) { + testCov := models.TestCoverage{ + FileCov: make(map[string]string), + TotalCov: "", + } + + // Define the path to the CSV file + csvPath := filepath.Join("target", "site", "keployE2E", "e2e.csv") + + file, err := os.Open(csvPath) + if err != nil { + return testCov, fmt.Errorf("failed to open CSV file: %w", err) + } + defer func() { + if err := file.Close(); err != nil { + utils.LogError(j.logger, err, "Error closing coverage csv file") + } + }() + + reader := csv.NewReader(file) + records, err := reader.ReadAll() + if err != nil { + return testCov, fmt.Errorf("failed to read CSV file: %w", err) + } + + var totalInstructions, coveredInstructions int + + // Skip header row and process each record + for i, record := range records { + if i == 0 { + continue // Skip header + } + + // Parse instructions coverage data + instructionsMissed, err := strconv.Atoi(record[3]) + if err != nil { + return testCov, err + } + instructionsCovered, err := strconv.Atoi(record[4]) + if err != nil { + return testCov, err + } + + // Calculate total instructions and covered instructions + totalInstructions += instructionsMissed + instructionsCovered + coveredInstructions += instructionsCovered + + // Calculate coverage percentage for each class + if instructionsCovered > 0 { + coverage := float64(instructionsCovered) / float64(instructionsCovered+instructionsMissed) * 100 + classPath := strings.ReplaceAll(record[1], ".", string(os.PathSeparator)) // Replace dots with path separator + testCov.FileCov[filepath.Join(classPath, record[2])] = fmt.Sprintf("%.2f%%", coverage) // Use class path as key + } + } + if totalInstructions > 0 { + totalCoverage := float64(coveredInstructions) / float64(totalInstructions) * 100 + testCov.TotalCov = fmt.Sprintf("%.2f%%", totalCoverage) + } + + testCov.Loc = models.Loc{ + Total: totalInstructions, + Covered: coveredInstructions, + } + + return testCov, nil +} + +func (j *Java) AppendCoverage(coverage *models.TestCoverage, testRunID string) error { + return j.reportDB.UpdateReport(j.ctx, testRunID, coverage) +} diff --git a/keploy/pkg/platform/coverage/java/utils.go b/keploy/pkg/platform/coverage/java/utils.go new file mode 100644 index 0000000..36ba64c --- /dev/null +++ b/keploy/pkg/platform/coverage/java/utils.go @@ -0,0 +1,171 @@ +package java + +import ( + "archive/zip" + "bytes" + "context" + "errors" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func downloadAndExtractJaCoCoCli(logger *zap.Logger, version, dir string) error { + cliPath := filepath.Join(dir, "jacococli.jar") + + downloadURL := fmt.Sprintf("https://github.com/jacoco/jacoco/releases/download/v%s/jacoco-%s.zip", version, version) + + _, err := os.Stat(cliPath) + if err == nil { + return nil + } + + resp, err := http.Get(downloadURL) + if err != nil { + return err + } + defer func() { + if err := resp.Body.Close(); err != nil { + utils.LogError(logger, err, "failed to close response body") + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body))) + if err != nil { + return err + } + + for _, file := range zipReader.File { + if strings.HasSuffix(file.Name, "jacococli.jar") { + cliFile, err := file.Open() + if err != nil { + return err + } + defer func() { + if err := cliFile.Close(); err != nil { + utils.LogError(logger, err, "failed to close jacoco cli jar file") + } + }() + + outFile, err := os.Create(cliPath) + if err != nil { + return err + } + defer func() { + if err := outFile.Close(); err != nil { + utils.LogError(logger, err, "failed to close the output file for jacoco cli jar") + } + }() + + _, err = io.Copy(outFile, cliFile) + if err != nil { + return err + } + } + } + + cliStat, err := os.Stat(cliPath) + + if os.IsNotExist(err) || cliStat != nil { + return fmt.Errorf("failed to find JaCoCo binaries in the distribution") + } + + return nil +} + +func MergeAndGenerateJacocoReport(ctx context.Context, logger *zap.Logger) error { + jacocoPath := filepath.Join(os.TempDir(), "jacoco") + jacocoCliPath := filepath.Join(jacocoPath, "jacococli.jar") + err := MergeJacocoCoverageFiles(ctx, jacocoCliPath) + if err == nil { + err = generateJacocoReport(ctx, jacocoCliPath) + if err != nil { + logger.Debug("failed to generate jacoco report", zap.Error(err)) + } + } else { + logger.Debug("failed to merge jacoco coverage data", zap.Error(err)) + } + if err != nil { + return err + } + return nil +} + +func MergeJacocoCoverageFiles(ctx context.Context, jacocoCliPath string) error { + // Find all .exec files in the target directory + sourceFiles, err := filepath.Glob("target/*.exec") + if err != nil { + return fmt.Errorf("error finding coverage files: %w", err) + } + if len(sourceFiles) == 0 { + return errors.New("no coverage files found") + } + + // Construct the command arguments + args := []string{ + "java", + "-jar", + jacocoCliPath, + "merge", + } + + args = append(args, sourceFiles...) + + // Specify the output file + args = append(args, "--destfile", "target/keploy-e2e.exec") + + cmd := exec.CommandContext(ctx, args[0], args[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to merge coverage files: %w", err) + } + + return nil +} + +func generateJacocoReport(ctx context.Context, jacocoCliPath string) error { + reportDir := "target/site/keployE2E" + + // Ensure the report directory exists + if err := os.MkdirAll(reportDir, 0777); err != nil { + return fmt.Errorf("failed to create report directory: %w", err) + } + + command := []string{ + "java", + "-jar", + jacocoCliPath, + "report", + "target/keploy-e2e.exec", + "--classfiles", + "target/classes", + "--csv", + reportDir + "/e2e.csv", + "--html", + reportDir, + } + + cmd := exec.CommandContext(ctx, command[0], command[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to generate report: %w", err) + } + + return nil +} diff --git a/keploy/pkg/platform/coverage/javascript/javascript.go b/keploy/pkg/platform/coverage/javascript/javascript.go new file mode 100644 index 0000000..eb5676e --- /dev/null +++ b/keploy/pkg/platform/coverage/javascript/javascript.go @@ -0,0 +1,174 @@ +// Package javascript implements the methods for javascript coverage services. +package javascript + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/coverage" + "go.uber.org/zap" +) + +type Javascript struct { + ctx context.Context + logger *zap.Logger + reportDB coverage.ReportDB + cmd string +} + +func New(ctx context.Context, logger *zap.Logger, reportDB coverage.ReportDB, cmd string) *Javascript { + return &Javascript{ + ctx: ctx, + logger: logger, + reportDB: reportDB, + cmd: cmd, + } +} + +func (j *Javascript) PreProcess(disableLineCoverage bool) (string, error) { + cmd := exec.Command("nyc", "--version") + err := cmd.Run() + if err != nil { + j.logger.Warn("coverage tool not found, skipping coverage caluclation. please install coverage tool using 'npm install -g nyc'") + return j.cmd, err + } + nycCmd := "nyc --clean=$CLEAN " + if disableLineCoverage { + nycCmd += "--reporter=none " + } + return nycCmd + j.cmd, nil +} + +type StartTy struct { + Line int `json:"line"` + Column int `json:"column"` +} + +type EndTy struct { + Line int `json:"line"` + Column int `json:"column"` +} + +type Loc struct { + StartTy `json:"start"` + EndTy `json:"end"` +} + +type Coverage map[string]struct { + Path string `json:"path"` + StatementMap map[string]struct { + StartTy `json:"start"` + EndTy `json:"end"` + } `json:"statementMap"` + FnMap map[string]struct { + Name string `json:"name"` + Decl struct { + StartTy `json:"start"` + EndTy `json:"end"` + } `json:"decl"` + Loc `json:"loc"` + Line int `json:"line"` + } `json:"fnMap"` + BranchMap map[string]struct { + Loc `json:"loc"` + Type string `json:"type"` + Locations []struct { + StartTy `json:"start"` + EndTy `json:"end"` + } `json:"locations"` + Line int `json:"line"` + } `json:"branchMap"` + S map[string]interface{} `json:"s"` + F map[string]interface{} `json:"f"` + B map[string]interface{} `json:"b"` + CoverageSchema string `json:"_coverageSchema"` + Hash string `json:"hash"` + ContentHash string `json:"contentHash"` +} + +func (j *Javascript) GetCoverage() (models.TestCoverage, error) { + testCov := models.TestCoverage{ + FileCov: make(map[string]string), + TotalCov: "", + } + + coverageFilePaths, err := getCoverageFilePathsJavascript(filepath.Join(".", ".nyc_output", "processinfo")) + if err != nil { + return testCov, err + } + if len(coverageFilePaths) == 0 { + return testCov, fmt.Errorf("no coverage files found") + } + + // coverage is calculated as: (no of statements covered / total no of statements) * 100 + // no of statements covered is the no of entries in S which has a value greater than 0 + // Total no of statements is len of S + + linesCoveredPerFile := make(map[string]map[string]bool) // filename -> line -> covered/not covered + + for _, coverageFilePath := range coverageFilePaths { + + coverageData, err := os.ReadFile(coverageFilePath) + if err != nil { + return testCov, err + } + var cov Coverage + err = json.Unmarshal(coverageData, &cov) + if err != nil { + return testCov, err + } + + for filename, file := range cov { + if _, ok := linesCoveredPerFile[filename]; !ok { + linesCoveredPerFile[filename] = make(map[string]bool) + } + for line, isStatementCovered := range file.S { + if _, ok := linesCoveredPerFile[filename][line]; !ok { + linesCoveredPerFile[filename][line] = false + } + + switch isExecSegmentCov := isStatementCovered.(type) { + case float64: + if (isExecSegmentCov) > 0 { + linesCoveredPerFile[filename][line] = true + } + default: + linesCoveredPerFile[filename][line] = false + } + } + } + } + + totalLines := 0 + totalCoveredLines := 0 + coveredLinesPerFile := make(map[string]int) // filename -> no of covered lines + for filename, lines := range linesCoveredPerFile { + for _, isCovered := range lines { + totalLines++ + if isCovered { + totalCoveredLines++ + coveredLinesPerFile[filename]++ + } + } + } + + for filename, lines := range linesCoveredPerFile { + testCov.FileCov[filename] = strconv.FormatFloat(float64(coveredLinesPerFile[filename]*100)/float64(len(lines)), 'f', 2, 64) + "%" + } + testCov.TotalCov = strconv.FormatFloat(float64(totalCoveredLines*100)/float64(totalLines), 'f', 2, 64) + "%" + testCov.Loc = models.Loc{ + Total: totalLines, + Covered: totalCoveredLines, + } + return testCov, nil +} + +func (j *Javascript) AppendCoverage(coverage *models.TestCoverage, testRunID string) error { + return j.reportDB.UpdateReport(j.ctx, testRunID, coverage) +} diff --git a/keploy/pkg/platform/coverage/javascript/utils.go b/keploy/pkg/platform/coverage/javascript/utils.go new file mode 100644 index 0000000..fb0a6be --- /dev/null +++ b/keploy/pkg/platform/coverage/javascript/utils.go @@ -0,0 +1,48 @@ +package javascript + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" +) + +type ProcessInfo struct { + Parent string `json:"parent"` + Pid int `json:"pid"` + Argv []string `json:"argv"` + ExecArgv []string `json:"execArgv"` + Cwd string `json:"cwd"` + Time int `json:"time"` + Ppid int `json:"ppid"` + CoverageFilename string `json:"coverageFilename"` + ExternalID string `json:"externalId"` + UUID string `json:"uuid"` + Files []string `json:"files"` +} + +func getCoverageFilePathsJavascript(path string) ([]string, error) { + filePaths := []string{} + walkfn := func(path string, info os.FileInfo, _ error) error { + if !info.IsDir() && !strings.HasSuffix(path, "index.json") { + fileData, err := os.ReadFile(path) + if err != nil { + return err + } + var processInfo ProcessInfo + err = json.Unmarshal(fileData, &processInfo) + if err != nil { + return err + } + if len(processInfo.Files) > 0 { + filePaths = append(filePaths, processInfo.CoverageFilename) + } + } + return nil + } + err := filepath.Walk(path, walkfn) + if err != nil { + return nil, err + } + return filePaths, nil +} diff --git a/keploy/pkg/platform/coverage/python/python.go b/keploy/pkg/platform/coverage/python/python.go new file mode 100644 index 0000000..1a087c7 --- /dev/null +++ b/keploy/pkg/platform/coverage/python/python.go @@ -0,0 +1,173 @@ +// Package python implements the methods for python coverage services. +package python + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/coverage" + "go.uber.org/zap" +) + +type Python struct { + ctx context.Context + logger *zap.Logger + reportDB coverage.ReportDB + cmd string + executable string +} + +func New(ctx context.Context, logger *zap.Logger, reportDB coverage.ReportDB, cmd, executable string) *Python { + return &Python{ + ctx: ctx, + logger: logger, + reportDB: reportDB, + cmd: cmd, + executable: executable, + } +} + +func (p *Python) PreProcess(_ bool) (string, error) { + cmd := exec.Command("coverage") + err := cmd.Run() + if err != nil { + p.logger.Warn("coverage tool not found, skipping coverage caluclation. Please install coverage tool using 'pip install coverage'") + return p.cmd, err + } + createPyCoverageConfig(p.logger) + + // Split the command into parts to handle environment variables and other prefixes + parts := strings.Fields(p.cmd) + + // Find the index of the executable + executableIndex := -1 + for i, part := range parts { + if part == p.executable { + executableIndex = i + break + } + } + + if executableIndex == -1 { + // Fallback to original behavior if executable not found as separate part + covCmd := fmt.Sprintf("%s -m coverage run", p.executable) + str := strings.Replace(p.cmd, p.executable, covCmd, 1) + p.logger.Debug("PreProcess command for Python coverage (fallback)", zap.String("command", str)) + return str, nil + } + + // Insert coverage flags right after the executable + newParts := make([]string, 0, len(parts)+3) // +3 for "-m", "coverage", "run" + newParts = append(newParts, parts[:executableIndex+1]...) // Include executable + newParts = append(newParts, "-m", "coverage", "run") // Add coverage flags + newParts = append(newParts, parts[executableIndex+1:]...) // Add remaining parts + + str := strings.Join(newParts, " ") + p.logger.Debug("PreProcess command for Python coverage", zap.String("command", str), zap.String("executable", p.executable)) + return str, nil +} + +type pyCoverageFile struct { + Meta struct { + Version string `json:"version"` + Timestamp string `json:"timestamp"` + BranchCoverage bool `json:"branch_coverage"` + ShowContexts bool `json:"show_contexts"` + } `json:"meta"` + Files map[string]struct { + ExecutedLines []int `json:"executed_lines"` + Summary struct { + CoveredLines int `json:"covered_lines"` + NumStatements int `json:"num_statements"` + PercentCovered float64 `json:"percent_covered"` + PercentCoveredDisplay string `json:"percent_covered_display"` + MissingLines int `json:"missing_lines"` + ExcludedLines int `json:"excluded_lines"` + } `json:"summary"` + MissingLines []int `json:"missing_lines"` + ExcludedLines []int `json:"excluded_lines"` + } `json:"files"` + Totals struct { + CoveredLines int `json:"covered_lines"` + NumStatements int `json:"num_statements"` + PercentCovered float64 `json:"percent_covered"` + PercentCoveredDisplay string `json:"percent_covered_display"` + MissingLines int `json:"missing_lines"` + ExcludedLines int `json:"excluded_lines"` + } `json:"totals"` +} + +func (p *Python) GetCoverage() (models.TestCoverage, error) { + testCov := models.TestCoverage{ + FileCov: make(map[string]string), + TotalCov: "", + } + + covFileName := os.Getenv("COVERAGE_FILE") + if covFileName == "" { + covFileName = ".coverage.keploy" + } + + p.logger.Info("Combining coverage from child processes when present; no impact if none exist") + + matches, err := filepath.Glob(".coverage.keploy.*") + if err != nil { + return testCov, fmt.Errorf("glob failed for .coverage.keploy.*: %w", err) + } + if len(matches) == 0 { + p.logger.Warn("no per-process .coverage files found – nothing to combine") + return testCov, nil + } + + args := append([]string{ + "-m", + "coverage", + "combine", + "--data-file=" + covFileName, // final merged file + }, matches...) + + combineCmd := exec.CommandContext(p.ctx, p.executable, args...) + combineCmd.Stdout = os.Stdout + combineCmd.Stderr = os.Stderr + + if err := combineCmd.Run(); err != nil { + p.logger.Error("failed to combine coverage files", zap.Error(err)) + return testCov, err + } + generateCovJSONCmd := exec.CommandContext(p.ctx, p.executable, "-m", "coverage", "json", "--data-file="+covFileName) + generateCovJSONCmd.Stdout = os.Stdout + generateCovJSONCmd.Stderr = os.Stderr + err = generateCovJSONCmd.Run() + if err != nil { + return testCov, err + } + coverageData, err := os.ReadFile("coverage.json") + if err != nil { + return testCov, err + } + var cov pyCoverageFile + err = json.Unmarshal(coverageData, &cov) + if err != nil { + return testCov, err + } + for filename, file := range cov.Files { + testCov.FileCov[filename] = file.Summary.PercentCoveredDisplay + "%" + } + testCov.TotalCov = cov.Totals.PercentCoveredDisplay + "%" + testCov.Loc = models.Loc{ + Total: cov.Totals.NumStatements, + Covered: cov.Totals.CoveredLines, + } + + return testCov, nil +} + +func (p *Python) AppendCoverage(coverage *models.TestCoverage, testRunID string) error { + return p.reportDB.UpdateReport(p.ctx, testRunID, coverage) +} diff --git a/keploy/pkg/platform/coverage/python/utils.go b/keploy/pkg/platform/coverage/python/utils.go new file mode 100644 index 0000000..0070701 --- /dev/null +++ b/keploy/pkg/platform/coverage/python/utils.go @@ -0,0 +1,166 @@ +package python + +import ( + "fmt" + "os" + "strings" + + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func createPyCoverageConfig(logger *zap.Logger) { + // Check if .coveragerc already exists + existingConfig, err := readExistingCoverageConfig() + if err != nil && !os.IsNotExist(err) { + utils.LogError(logger, err, "failed to read existing .coveragerc file") + } + + // Merge existing config with Keploy's required settings + mergedConfig := mergeCoverageConfig(existingConfig, logger) + + // Write the merged configuration + file, err := os.Create(".coveragerc") + if err != nil { + utils.LogError(logger, err, "failed to create .coveragerc file") + return + } + defer func() { + if err := file.Close(); err != nil { + utils.LogError(logger, err, "failed to close coveragerc file", zap.String("file", file.Name())) + } + }() + + _, err = file.WriteString(mergedConfig) + if err != nil { + utils.LogError(logger, err, "failed to write to .coveragerc file") + return + } + + logger.Debug("Configuration written to .coveragerc with preserved user settings") +} + +// readExistingCoverageConfig reads and returns the content of existing .coveragerc file +func readExistingCoverageConfig() (string, error) { + content, err := os.ReadFile(".coveragerc") + if err != nil { + return "", err + } + return string(content), nil +} + +// mergeCoverageConfig merges existing configuration with Keploy's required settings +func mergeCoverageConfig(existingConfig string, logger *zap.Logger) string { + // In the below config, in the concurrency section, we are setting the concurrency to multiprocessing and thread. + // Where multiprocessing is for collecting coverage for processes spawned by the Python application, + // and thread is for collecting coverage for the main thread. + keploySettings := map[string]string{ + "sigterm": "true", + "concurrency": "multiprocessing, thread", + "parallel": "true", + "data_file": ".coverage.keploy", + } + + // If no existing config, create with Keploy defaults + if existingConfig == "" { + logger.Info("No existing .coveragerc found, creating with Keploy defaults") + return createDefaultKeployConfig(keploySettings) + } + + logger.Info("Existing .coveragerc found, merging with Keploy settings") + + // Parse existing config and merge with Keploy settings + return parseAndMergeConfig(existingConfig, keploySettings, logger) +} + +// createDefaultKeployConfig creates a basic configuration with only Keploy requirements +func createDefaultKeployConfig(keploySettings map[string]string) string { + config := "[run]\n" + config += "omit =\n /usr/*\n" + // Add all Keploy settings since there are no existing settings to process + emptyProcessedSettings := make(map[string]bool) + settingLines := addMissingKeploySettings(keploySettings, emptyProcessedSettings) + for _, line := range settingLines { + config += line + "\n" + } + return config +} + +// parseAndMergeConfig parses the existing config and merges it with Keploy settings +func parseAndMergeConfig(existingConfig string, keploySettings map[string]string, logger *zap.Logger) string { + lines := strings.Split(existingConfig, "\n") + var result []string + var currentSection string + runSectionFound := false + runSectionProcessed := false + processedSettings := make(map[string]bool) // Track which Keploy settings have been processed + + for _, line := range lines { + trimmedLine := strings.TrimSpace(line) + + // Detect section headers + if strings.HasPrefix(trimmedLine, "[") && strings.HasSuffix(trimmedLine, "]") { + currentSection = trimmedLine + if currentSection == "[run]" { + runSectionFound = true + } else if runSectionFound && !runSectionProcessed { + // We're leaving the [run] section, add any missing Keploy settings + result = append(result, addMissingKeploySettings(keploySettings, processedSettings)...) + runSectionProcessed = true + } + result = append(result, line) + continue + } + + // Process settings within [run] section + if currentSection == "[run]" && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { + settingName := extractSettingName(trimmedLine) + + // Check if this is a Keploy required setting + if keployValue, isKeploySetting := keploySettings[settingName]; isKeploySetting { + if settingName != "omit" { + // Override with Keploy value + result = append(result, fmt.Sprintf("%s = %s", settingName, keployValue)) + logger.Debug("Overriding setting with Keploy requirement", zap.String("setting", settingName), zap.String("value", keployValue)) + processedSettings[settingName] = true // Mark as processed + continue + } + } + } + + // Add the original line if not processed above + result = append(result, line) + } + + // If [run] section was found but not all Keploy settings were processed + if runSectionFound && !runSectionProcessed { + result = append(result, addMissingKeploySettings(keploySettings, processedSettings)...) + } + + // If no [run] section was found, add it with Keploy settings + if !runSectionFound { + result = append([]string{"[run]"}, append(addMissingKeploySettings(keploySettings, processedSettings), result...)...) + } + + return strings.Join(result, "\n") +} + +// extractSettingName extracts the setting name from a configuration line +func extractSettingName(line string) string { + parts := strings.SplitN(line, "=", 2) + if len(parts) > 0 { + return strings.TrimSpace(parts[0]) + } + return "" +} + +// addMissingKeploySettings adds any Keploy settings that weren't found in the existing config +func addMissingKeploySettings(keploySettings map[string]string, processedSettings map[string]bool) []string { + var result []string + for key, value := range keploySettings { + if !processedSettings[key] { + result = append(result, fmt.Sprintf("%s = %s", key, value)) + } + } + return result +} diff --git a/keploy/pkg/platform/coverage/service.go b/keploy/pkg/platform/coverage/service.go new file mode 100644 index 0000000..f256aec --- /dev/null +++ b/keploy/pkg/platform/coverage/service.go @@ -0,0 +1,18 @@ +// Package coverage defines the interface for coverage services. +package coverage + +import ( + "context" + + "go.keploy.io/server/v2/pkg/models" +) + +type Service interface { + PreProcess(disableLineCoverage bool) (string, error) + GetCoverage() (models.TestCoverage, error) + AppendCoverage(coverage *models.TestCoverage, testRunID string) error +} + +type ReportDB interface { + UpdateReport(ctx context.Context, testRunID string, coverageReport any) error +} diff --git a/keploy/pkg/platform/docker/docker.go b/keploy/pkg/platform/docker/docker.go new file mode 100644 index 0000000..4b4a0a1 --- /dev/null +++ b/keploy/pkg/platform/docker/docker.go @@ -0,0 +1,585 @@ +// Package docker provides functionality for working with Docker containers. +package docker + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + nativeDockerClient "github.com/docker/docker/client" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + + "github.com/docker/docker/api/types/network" + + "github.com/docker/docker/api/types" + dockerContainerPkg "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/volume" +) + +const ( + defaultTimeoutForDockerQuery = 1 * time.Minute +) + +type Impl struct { + nativeDockerClient.APIClient + timeoutForDockerQuery time.Duration + logger *zap.Logger + containerID string +} + +func New(logger *zap.Logger) (Client, error) { + dockerClient, err := nativeDockerClient.NewClientWithOpts(nativeDockerClient.FromEnv, + nativeDockerClient.WithAPIVersionNegotiation()) + if err != nil { + return nil, err + } + return &Impl{ + APIClient: dockerClient, + timeoutForDockerQuery: defaultTimeoutForDockerQuery, + logger: logger, + }, nil +} + +// GetContainerID is a Getter function for containerID +func (idc *Impl) GetContainerID() string { + return idc.containerID +} + +// SetContainerID is a Setter function for containerID +func (idc *Impl) SetContainerID(containerID string) { + idc.containerID = containerID +} + +// ExtractNetworksForContainer returns the list of all the networks that the container is a part of. +// Note that if a user did not explicitly attach the container to a network, the Docker daemon attaches it +// to a network called "bridge". +func (idc *Impl) ExtractNetworksForContainer(containerName string) (map[string]*network.EndpointSettings, error) { + ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) + defer cancel() + + containerJSON, err := idc.ContainerInspect(ctx, containerName) + if err != nil { + utils.LogError(idc.logger, err, "couldn't inspect container via the Docker API", zap.String("containerName", containerName)) + return nil, err + } + + if settings := containerJSON.NetworkSettings; settings != nil { + return settings.Networks, nil + } + // Docker attaches the container to "bridge" network by default. + // If the network list is empty, the docker daemon is possibly misbehaving, + // or the container is in a bad state. + utils.LogError(idc.logger, nil, "The network list for the given container is empty. This is unexpected.", zap.String("containerName", containerName)) + return nil, fmt.Errorf("the container is not attached to any network") +} + +func (idc *Impl) ConnectContainerToNetworks(containerName string, settings map[string]*network.EndpointSettings) error { + if settings == nil { + return fmt.Errorf("provided network settings is empty") + } + + existingNetworks, err := idc.ExtractNetworksForContainer(containerName) + if err != nil { + return fmt.Errorf("could not get existing networks for container %s", containerName) + } + + ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) + defer cancel() + + for networkName, setting := range settings { + // If the container is already part of this network, skip it. + _, ok := existingNetworks[networkName] + if ok { + continue + } + + err := idc.NetworkConnect(ctx, networkName, containerName, setting) + if err != nil { + return err + } + } + + return nil +} + +func (idc *Impl) AttachNetwork(containerName string, networkNames []string) error { + if len(networkNames) == 0 { + return fmt.Errorf("provided network names list is empty") + } + + existingNetworks, err := idc.ExtractNetworksForContainer(containerName) + if err != nil { + return fmt.Errorf("could not get existing networks for container %s", containerName) + } + + ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) + defer cancel() + + for _, networkName := range networkNames { + // If the container is already part of this network, skip it. + _, ok := existingNetworks[networkName] + if ok { + continue + } + + // As there are no specific settings, use nil for the settings parameter. + err := idc.NetworkConnect(ctx, networkName, containerName, nil) + if err != nil { + return err + } + } + + return nil +} + +// StopAndRemoveDockerContainer will Stop and Remove the docker container +func (idc *Impl) StopAndRemoveDockerContainer() error { + dockerClient := idc + containerID := idc.containerID + + container, err := dockerClient.ContainerInspect(context.Background(), containerID) + if err != nil { + return fmt.Errorf("failed to inspect the docker container: %w", err) + } + + if container.State.Status == "running" { + err = dockerClient.ContainerStop(context.Background(), containerID, dockerContainerPkg.StopOptions{}) + if err != nil { + return fmt.Errorf("failed to stop the docker container: %w", err) + } + } + + removeOptions := types.ContainerRemoveOptions{ + RemoveVolumes: true, + Force: true, + } + + err = dockerClient.ContainerRemove(context.Background(), containerID, removeOptions) + if err != nil { + return fmt.Errorf("failed to remove the docker container: %w", err) + } + + idc.logger.Debug("Docker Container stopped and removed successfully.") + + return nil +} + +// NetworkExists checks if the given network exists locally or not +func (idc *Impl) NetworkExists(networkName string) (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) + defer cancel() + + // Retrieve all networks. + networks, err := idc.NetworkList(ctx, types.NetworkListOptions{}) + if err != nil { + return false, fmt.Errorf("error retrieving networks: %v", err) + } + + // Check if the specified network is in the list. + for _, net := range networks { + if net.Name == networkName { + return true, nil + } + } + + return false, nil +} + +// CreateNetwork creates a custom docker network of type bridge. +func (idc *Impl) CreateNetwork(networkName string) error { + ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) + defer cancel() + + _, err := idc.NetworkCreate(ctx, networkName, types.NetworkCreate{ + Driver: "bridge", + }) + + return err +} + +// Compose structure to represent all the fields of a Docker Compose file +type Compose struct { + Version string `yaml:"version,omitempty"` + Services yaml.Node `yaml:"services,omitempty"` + Networks yaml.Node `yaml:"networks,omitempty"` + Volumes yaml.Node `yaml:"volumes,omitempty"` + Configs yaml.Node `yaml:"configs,omitempty"` + Secrets yaml.Node `yaml:"secrets,omitempty"` +} + +func (idc *Impl) ReadComposeFile(filePath string) (*Compose, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + var compose Compose + err = yaml.Unmarshal(data, &compose) + if err != nil { + return nil, err + } + + return &compose, nil +} + +func (idc *Impl) WriteComposeFile(compose *Compose, path string) error { + data, err := yaml.Marshal(compose) + if err != nil { + return err + } + + // write data to file + + err = os.WriteFile(path, data, 0644) + if err != nil { + return err + } + return nil +} + +// HasRelativePath returns information about whether bind mounts if they are being used contain relative file names or not +func (idc *Impl) HasRelativePath(compose *Compose) bool { + if compose.Services.Content == nil { + return false + } + + for _, service := range compose.Services.Content { + for i, item := range service.Content { + + if i+1 >= len(service.Content) { + break + } + + if item.Value == "volumes" { + // volumeKeyNode := service.Content[i] or item + volumeValueNode := service.Content[i+1] + + // Loop over all the volume mounts + for _, volumeMount := range volumeValueNode.Content { + // If volume mount starts with ./ or ../ then it as a relative path so return true + if volumeMount.Kind == yaml.ScalarNode && (volumeMount.Value[:2] == "./" || volumeMount.Value[:3] == "../") { + return true + } + } + } + } + } + + return false + +} + +// GetNetworkInfo CheckNetworkInfo returns information about network name and also about whether the network is external or not in a docker-compose file. +func (idc *Impl) GetNetworkInfo(compose *Compose) *NetworkInfo { + if compose.Networks.Content == nil { + return nil + } + + var defaultNetwork string + + for i := 0; i < len(compose.Networks.Content); i += 2 { + if i+1 >= len(compose.Networks.Content) { + break + } + networkKeyNode := compose.Networks.Content[i] + networkValueNode := compose.Networks.Content[i+1] + + if defaultNetwork == "" { + defaultNetwork = networkKeyNode.Value + } + + isExternal := false + var externalName string + + for j := 0; j < len(networkValueNode.Content); j += 2 { + if j+1 >= len(networkValueNode.Content) { + break + } + propertyNode := networkValueNode.Content[j] + valueNode := networkValueNode.Content[j+1] + if propertyNode.Value == "external" { + if valueNode.Kind == yaml.ScalarNode && valueNode.Value == "true" { + isExternal = true + break + } else if valueNode.Kind == yaml.MappingNode { + for k := 0; k < len(valueNode.Content); k += 2 { + if k+1 >= len(valueNode.Content) { + break + } + subPropertyNode := valueNode.Content[k] + subValueNode := valueNode.Content[k+1] + if subPropertyNode.Value == "name" { + isExternal = true + externalName = subValueNode.Value + break + } + } + } + break + } + } + + if isExternal { + n := &NetworkInfo{External: true, Name: networkKeyNode.Value} + if externalName != "" { + n.Name = externalName + } + return n + } + } + + if defaultNetwork != "" { + return &NetworkInfo{External: false, Name: defaultNetwork} + } + + return nil +} + +// GetHostWorkingDirectory Inspects Keploy docker container to get bind mount for current directory +func (idc *Impl) GetHostWorkingDirectory() (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) + defer cancel() + + curDir, err := os.Getwd() + if err != nil { + utils.LogError(idc.logger, err, "failed to get current working directory") + return "", err + } + + container, err := idc.ContainerInspect(ctx, "keploy-v2") + if err != nil { + utils.LogError(idc.logger, err, "error inspecting keploy-v2 container") + return "", err + } + containerMounts := container.Mounts + // Loop through container mounts and find the mount for current directory in the container + for _, mount := range containerMounts { + if mount.Destination == curDir { + idc.logger.Debug(fmt.Sprintf("found mount for %s in keploy-v2 container", curDir), zap.Any("mount", mount)) + return mount.Source, nil + } + } + return "", fmt.Errorf("%s", fmt.Sprintf("could not find mount for %s in keploy-v2 container", curDir)) +} + +// ForceAbsolutePath replaces relative paths in bind mounts with absolute paths +func (idc *Impl) ForceAbsolutePath(c *Compose, basePath string) error { + hostWorkingDirectory, err := idc.GetHostWorkingDirectory() + if err != nil { + return err + } + + dockerComposeContext, err := filepath.Abs(filepath.Join(hostWorkingDirectory, basePath)) + if err != nil { + utils.LogError(idc.logger, err, "error getting absolute path for docker compose file") + return err + } + dockerComposeContext = filepath.Dir(dockerComposeContext) + idc.logger.Debug("docker compose file location in host filesystem", zap.Any("dockerComposeContext", dockerComposeContext)) + + // Loop through all services in compose file + for _, service := range c.Services.Content { + + for i, item := range service.Content { + + if i+1 >= len(service.Content) { + break + } + + if item.Value == "volumes" { + // volumeKeyNode := service.Content[i] or item + volumeValueNode := service.Content[i+1] + + // Loop over all the volume mounts + for _, volumeMount := range volumeValueNode.Content { + // If volume mount starts with ./ or ../ then it is a relative path + if volumeMount.Kind == yaml.ScalarNode && (volumeMount.Value[:2] == "./" || volumeMount.Value[:3] == "../") { + + // Replace the relative path with absolute path + absPath, err := filepath.Abs(filepath.Join(dockerComposeContext, volumeMount.Value)) + if err != nil { + return err + } + volumeMount.Value = absPath + } + } + } + } + } + return nil +} + +// MakeNetworkExternal makes the existing network of the user docker compose file external and save it to a new file +func (idc *Impl) MakeNetworkExternal(c *Compose) error { + // Iterate over all networks and check the 'external' flag. + if c.Networks.Content != nil { + for i := 0; i < len(c.Networks.Content); i += 2 { + if i+1 >= len(c.Networks.Content) { + break + } + // networkKeyNode := compose.Networks.Content[i] + networkValueNode := c.Networks.Content[i+1] + + // If it's a shorthand notation or null value, initialize it as an empty mapping node + if (networkValueNode.Kind == yaml.ScalarNode && networkValueNode.Value == "") || networkValueNode.Tag == "!!null" { + networkValueNode.Kind = yaml.MappingNode + networkValueNode.Tag = "" + networkValueNode.Content = make([]*yaml.Node, 0) + } + + externalFound := false + for index, propertyNode := range networkValueNode.Content { + if index+1 >= len(networkValueNode.Content) { + break + } + if propertyNode.Value == "external" { + externalFound = true + valueNode := networkValueNode.Content[index+1] + if valueNode.Kind == yaml.ScalarNode && (valueNode.Value == "false" || valueNode.Value == "") { + valueNode.Value = "true" + } + break + } + } + + if !externalFound { + networkValueNode.Content = append(networkValueNode.Content, + &yaml.Node{Kind: yaml.ScalarNode, Value: "external"}, + &yaml.Node{Kind: yaml.ScalarNode, Value: "true"}, + ) + } + } + } + return nil +} + +// SetKeployNetwork adds the keploy-network network to the new docker compose file and copy rest of the contents from +// existing user docker compose file +func (idc *Impl) SetKeployNetwork(c *Compose) (*NetworkInfo, error) { + + // Ensure that the top-level networks mapping exists. + if c.Networks.Content == nil { + c.Networks.Kind = yaml.MappingNode + c.Networks.Content = make([]*yaml.Node, 0) + } + networkInfo := &NetworkInfo{ + Name: "keploy-network", + External: true, + } + // Check if "keploy-network" already exists + exists := false + for i := 0; i < len(c.Networks.Content); i += 2 { + if c.Networks.Content[i].Value == "keploy-network" { + exists = true + break + } + } + + if !exists { + // Add the keploy-network with external: true + c.Networks.Content = append(c.Networks.Content, + &yaml.Node{Kind: yaml.ScalarNode, Value: "keploy-network"}, + &yaml.Node{Kind: yaml.MappingNode, Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "external"}, + {Kind: yaml.ScalarNode, Value: "true"}, + }}, + ) + } + + // Add or modify network for each service + for _, service := range c.Services.Content { + networksFound := false + for _, item := range service.Content { + if item.Value == "networks" { + networksFound = true + break + } + } + + if !networksFound { + service.Content = append(service.Content, + &yaml.Node{Kind: yaml.ScalarNode, Value: "networks"}, + &yaml.Node{ + Kind: yaml.SequenceNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "keploy-network"}, + }, + }, + ) + } else { + for _, item := range service.Content { + if item.Value == "networks" { + item.Content = append(item.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: "keploy-network"}) + } + } + } + } + return networkInfo, nil +} + +// IsContainerRunning check if the container is already running or not, required for docker start command. +func (idc *Impl) IsContainerRunning(containerName string) (bool, error) { + + ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) + defer cancel() + + containerJSON, err := idc.ContainerInspect(ctx, containerName) + if err != nil { + return false, err + } + + if containerJSON.State.Running { + return true, nil + } + return false, nil +} + +func (idc *Impl) CreateVolume(ctx context.Context, volumeName string, recreate bool) error { + // Set a timeout for the context + ctx, cancel := context.WithTimeout(ctx, idc.timeoutForDockerQuery) + defer cancel() + + // Check if the 'debugfs' volume exists + filter := filters.NewArgs() + filter.Add("name", volumeName) + volumeList, err := idc.VolumeList(ctx, volume.ListOptions{Filters: filter}) + if err != nil { + idc.logger.Error("failed to list docker volumes", zap.Error(err)) + return err + } + + if len(volumeList.Volumes) > 0 { + if !recreate { + idc.logger.Info("volume already exists", zap.Any("volume", volumeName)) + return err + } + + err := idc.VolumeRemove(ctx, volumeName, false) + if err != nil { + idc.logger.Error("failed to delete volume "+volumeName, zap.Error(err)) + return err + } + } + + // Create the 'debugfs' volume if it doesn't exist + _, err = idc.VolumeCreate(ctx, volume.CreateOptions{ + Name: volumeName, + Driver: "local", + DriverOpts: map[string]string{ + "type": volumeName, // Use "none" for local driver + "device": volumeName, + }, + }) + if err != nil { + idc.logger.Error("failed to create volume", zap.Any("volume", volumeName), zap.Error(err)) + return err + } + + idc.logger.Debug("volume created", zap.Any("volume", volumeName)) + return nil +} diff --git a/keploy/pkg/platform/docker/service.go b/keploy/pkg/platform/docker/service.go new file mode 100644 index 0000000..d41dea9 --- /dev/null +++ b/keploy/pkg/platform/docker/service.go @@ -0,0 +1,38 @@ +package docker + +import ( + "context" + + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" +) + +type Client interface { + client.APIClient + ExtractNetworksForContainer(containerName string) (map[string]*network.EndpointSettings, error) + ConnectContainerToNetworks(containerName string, settings map[string]*network.EndpointSettings) error + AttachNetwork(containerName string, networkName []string) error + StopAndRemoveDockerContainer() error + GetContainerID() string + SetContainerID(containerID string) + NetworkExists(network string) (bool, error) + + HasRelativePath(c *Compose) bool + ForceAbsolutePath(c *Compose, basePath string) error + + GetNetworkInfo(compose *Compose) *NetworkInfo + + CreateNetwork(network string) error + MakeNetworkExternal(c *Compose) error + SetKeployNetwork(c *Compose) (*NetworkInfo, error) + ReadComposeFile(filePath string) (*Compose, error) + WriteComposeFile(compose *Compose, path string) error + + IsContainerRunning(containerName string) (bool, error) + CreateVolume(ctx context.Context, volumeName string, recreate bool) error +} + +type NetworkInfo struct { + External bool + Name string +} diff --git a/keploy/pkg/platform/docker/util.go b/keploy/pkg/platform/docker/util.go new file mode 100644 index 0000000..f35f46e --- /dev/null +++ b/keploy/pkg/platform/docker/util.go @@ -0,0 +1,53 @@ +//go:build linux + +package docker + +import ( + "fmt" + "regexp" + + "go.keploy.io/server/v2/utils" +) + +func ParseDockerCmd(cmd string, kind utils.CmdType, idc Client) (string, string, error) { + + // Regular expression patterns + var containerNamePattern string + switch kind { + case utils.DockerStart: + containerNamePattern = `start\s+(?:-[^\s]+\s+)*([^\s]*)` + default: + containerNamePattern = `--name\s+([^\s]+)` + } + + networkNamePattern := `(--network|--net)\s+([^\s]+)` + + // Extract container name + containerNameRegex := regexp.MustCompile(containerNamePattern) + containerNameMatches := containerNameRegex.FindStringSubmatch(cmd) + if len(containerNameMatches) < 2 { + return "", "", fmt.Errorf("failed to parse container name") + } + containerName := containerNameMatches[1] + + if kind == utils.DockerStart { + networks, err := idc.ExtractNetworksForContainer(containerName) + if err != nil { + return containerName, "", err + } + for i := range networks { + return containerName, i, nil + } + return containerName, "", fmt.Errorf("failed to parse network name") + } + + // Extract network name + networkNameRegex := regexp.MustCompile(networkNamePattern) + networkNameMatches := networkNameRegex.FindStringSubmatch(cmd) + if len(networkNameMatches) < 3 { + return containerName, "", fmt.Errorf("failed to parse network name") + } + networkName := networkNameMatches[2] + + return containerName, networkName, nil +} diff --git a/keploy/pkg/platform/storage/storage.go b/keploy/pkg/platform/storage/storage.go new file mode 100644 index 0000000..4188771 --- /dev/null +++ b/keploy/pkg/platform/storage/storage.go @@ -0,0 +1,204 @@ +// Package storage defines methods for storage DB. +package storage + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/textproto" + "path/filepath" + "strings" + "sync" + "time" + + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type Storage struct { + serverURL string + logger *zap.Logger +} + +type MockUploadResponse struct { + IsSuccess bool `json:"isSuccess"` + Error string `json:"error"` +} + +func New(serverURL string, logger *zap.Logger) *Storage { + return &Storage{ + serverURL: serverURL, + logger: logger, + } +} + +func (s *Storage) Upload(ctx context.Context, file io.Reader, mockName string, appName string, token string) error { + + done := make(chan struct{}) + var once sync.Once + + closeDone := func() { + once.Do(func() { + close(done) + }) + } + + // Spinner goroutine + go func() { + spinnerChars := []rune{'|', '/', '-', '\\'} + i := 0 + for { + select { + case <-done: + fmt.Print("\r") // Clear spinner line after done + return + default: + fmt.Printf("\rUploading... %c", spinnerChars[i%len(spinnerChars)]) + i++ + time.Sleep(100 * time.Millisecond) + } + } + }() + defer closeDone() // Ensure we close the channel when the function exits + + // Create a multipart buffer and writer + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // Create a custom part header for the file field + partHeader := textproto.MIMEHeader{} + partHeader.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "mock", filepath.Base("mocks.yaml"))) + partHeader.Set("Content-Type", "application/octet-stream") + partHeader.Set("Content-Encoding", "gzip") // Explicitly declare compression + + part, err := writer.CreatePart(partHeader) + if err != nil { + return err + } + + // Compress file data with gzip and write into multipart part + gzipWriter := gzip.NewWriter(part) + if _, err := io.Copy(gzipWriter, file); err != nil { + return err + } + if err := gzipWriter.Close(); err != nil { + return err + } + + // Add other form fields + if err := writer.WriteField("appName", appName); err != nil { + s.logger.Error("Error writing appName field", zap.Error(err)) + return err + } + if err := writer.WriteField("mockName", mockName); err != nil { + s.logger.Error("Error writing mockName field", zap.Error(err)) + return err + } + if err := writer.Close(); err != nil { + s.logger.Error("Error closing writer", zap.Error(err)) + return err + } + + // Prepare the HTTP request + req, err := http.NewRequestWithContext(ctx, "POST", s.serverURL+"/mock/upload", body) + if err != nil { + return err + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Authorization", "Bearer "+token) + + // Execute the HTTP request + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer func() { + if err := resp.Body.Close(); err != nil { + utils.LogError(s.logger, err, "failed to close the http response body") + } + }() + + // Read the raw body + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + utils.LogError(s.logger, err, "failed to read the response body") + return err + } + + // Decode into struct from the raw bytes + var mockUploadResponse MockUploadResponse + if err := json.NewDecoder(bytes.NewReader(bodyBytes)).Decode(&mockUploadResponse); err != nil { + utils.LogError(s.logger, err, "failed to decode the response body", zap.Any("Response", resp), zap.String("body", string(bodyBytes))) + return err + } + + closeDone() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("upload failed with status code: %d and error %s", resp.StatusCode, mockUploadResponse.Error) + } + + if !mockUploadResponse.IsSuccess { + utils.LogError(s.logger, fmt.Errorf("upload failed: %s", mockUploadResponse.Error), "failed to upload the mock") + return fmt.Errorf("upload failed: %s", mockUploadResponse.Error) + } + + s.logger.Info("Mock uploaded successfully") + return nil +} + +func (s *Storage) Download(ctx context.Context, mockName string, appName string, userName string, jwtToken string) (io.Reader, error) { + // Create the HTTP request + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/mock/download?appName=%s&mockName=%s&userName=%s", s.serverURL, appName, mockName, userName), nil) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Bearer "+jwtToken) + + req.Header.Set("Accept-Encoding", "gzip") // Request gzip encoding + + // Execute the request + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + defer func() { + err := resp.Body.Close() + if err != nil { + utils.LogError(s.logger, err, "failed to close the http response body") + } + }() + // Read the response body to get the error message + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body and the resp code is: %d", resp.StatusCode) + } + return nil, fmt.Errorf("download failed with status code: %d, message: %s", resp.StatusCode, strings.TrimSpace(string(body))) + } + + // Check if the response is gzipped + if strings.EqualFold(resp.Header.Get("Content-Encoding"), "gzip") { + s.logger.Debug("mock response is gzipped") + gr, err := gzip.NewReader(resp.Body) + if err != nil { + defer func() { + err := resp.Body.Close() + if err != nil { + utils.LogError(s.logger, err, "failed to close the http response body") + } + }() + return nil, fmt.Errorf("failed to create gzip reader: %w", err) + } + return gr, nil // gr is an io.Reader, decompressing transparently + } + + return resp.Body, nil +} diff --git a/keploy/pkg/platform/telemetry/telemetry.go b/keploy/pkg/platform/telemetry/telemetry.go new file mode 100644 index 0000000..153e05e --- /dev/null +++ b/keploy/pkg/platform/telemetry/telemetry.go @@ -0,0 +1,185 @@ +// Package telemetry provides functionality for telemetry data collection. +package telemetry + +import ( + "bytes" + "net/http" + "runtime" + "sync" + "time" + + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +var teleURL = "https://telemetry.keploy.io/analytics" + +type Telemetry struct { + Enabled bool + OffMode bool + logger *zap.Logger + InstallationID string + KeployVersion string + GlobalMap sync.Map + client *http.Client +} + +type Options struct { + Enabled bool + Version string + GlobalMap sync.Map + InstallationID string +} + +func NewTelemetry(logger *zap.Logger, opt Options) *Telemetry { + return &Telemetry{ + Enabled: opt.Enabled, + logger: logger, + KeployVersion: opt.Version, + GlobalMap: opt.GlobalMap, + InstallationID: opt.InstallationID, + client: &http.Client{Timeout: 10 * time.Second}, + } +} + +func (tel *Telemetry) Ping() { + if !tel.Enabled { + return + } + go func() { + for { + tel.SendTelemetry("Ping") + time.Sleep(5 * time.Minute) + } + }() +} + +func (tel *Telemetry) TestSetRun(success int, failure int, testSet string, runStatus string) { + dataMap := &sync.Map{} + dataMap.Store("Passed-Tests", success) + dataMap.Store("Failed-Tests", failure) + dataMap.Store("Test-Set", testSet) + dataMap.Store("Run-Status", runStatus) + go tel.SendTelemetry("TestSetRun", dataMap) +} + +func (tel *Telemetry) TestRun(success int, failure int, testSets int, runStatus string) { + dataMap := &sync.Map{} + dataMap.Store("Passed-Tests", success) + dataMap.Store("Failed-Tests", failure) + dataMap.Store("Test-Sets", testSets) + dataMap.Store("Run-Status", runStatus) + go tel.SendTelemetry("TestRun", dataMap) +} + +// MockTestRun is Telemetry event for the Mocking feature test run +func (tel *Telemetry) MockTestRun(utilizedMocks int) { + dataMap := &sync.Map{} + dataMap.Store("Utilized-Mocks", utilizedMocks) + go tel.SendTelemetry("MockTestRun", dataMap) +} + +// RecordedTestSuite is Telemetry event for the tests and mocks that are recorded +func (tel *Telemetry) RecordedTestSuite(testSet string, testsTotal int, mockTotal map[string]int) { + dataMap := &sync.Map{} + dataMap.Store("test-set", testSet) + dataMap.Store("tests", testsTotal) + + mockMap := &sync.Map{} + for k, v := range mockTotal { + mockMap.Store(k, v) + } + dataMap.Store("mocks", mockMap) + + go tel.SendTelemetry("RecordedTestSuite", dataMap) +} + +func (tel *Telemetry) RecordedTestAndMocks() { + dataMap := &sync.Map{} + mapcheck := make(map[string]int) + dataMap.Store("mocks", mapcheck) + go tel.SendTelemetry("RecordedTestAndMocks", dataMap) +} + +func (tel *Telemetry) GenerateUT() { + dataMap := &sync.Map{} + go tel.SendTelemetry("GenerateUT", dataMap) +} + +// RecordedMocks is Telemetry event for the mocks that are recorded in the mocking feature +func (tel *Telemetry) RecordedMocks(mockTotal map[string]int) { + mockMap := &sync.Map{} + for k, v := range mockTotal { + mockMap.Store(k, v) + } + dataMap := &sync.Map{} + dataMap.Store("mocks", mockMap) + go tel.SendTelemetry("RecordedMocks", dataMap) +} + +func (tel *Telemetry) RecordedTestCaseMock(mockType string) { + dataMap := &sync.Map{} + dataMap.Store("mock", mockType) + go tel.SendTelemetry("RecordedTestCaseMock", dataMap) +} + +func (tel *Telemetry) SendTelemetry(eventType string, output ...*sync.Map) { + if tel.Enabled { + event := models.TeleEvent{ + EventType: eventType, + CreatedAt: time.Now().Unix(), + } + if len(output) > 0 { + event.Meta = output[0] + } else { + event.Meta = &sync.Map{} + } + + hasGlobalMap := false + tel.GlobalMap.Range(func(key, value interface{}) bool { + hasGlobalMap = true + return false // Stop iteration after finding the first element + }) + + if hasGlobalMap { + // event.Meta["global-map"] = syncMapToMap(tel.GlobalMap) + // If you want to nest the global map, you can do this (but the telemetry + // endpoint needs to support nested sync.Maps): + // event.Meta.Store("global-map", tel.GlobalMap) + // Otherwise, merge the global map into the event's meta map + tel.GlobalMap.Range(func(key, value interface{}) bool { + event.Meta.Store(key, value) + return true + }) + } + + event.InstallationID = tel.InstallationID + event.OS = runtime.GOOS + event.KeployVersion = tel.KeployVersion + event.Arch = runtime.GOARCH + bin, err := marshalEvent(event, tel.logger) + if err != nil { + tel.logger.Debug("failed to marshal event", zap.Error(err)) + return + } + + req, err := http.NewRequest(http.MethodPost, teleURL, bytes.NewBuffer(bin)) + if err != nil { + tel.logger.Debug("failed to create request for analytics", zap.Error(err)) + return + } + + req.Header.Set("Content-Type", "application/json; charset=utf-8") + + resp, err := tel.client.Do(req) + if err != nil { + tel.logger.Debug("failed to send request for analytics", zap.Error(err)) + return + } + _, err = unmarshalResp(resp, tel.logger) + if err != nil { + tel.logger.Debug("failed to unmarshal response", zap.Error(err)) + return + } + } +} diff --git a/keploy/pkg/platform/telemetry/utils.go b/keploy/pkg/platform/telemetry/utils.go new file mode 100644 index 0000000..b4229f4 --- /dev/null +++ b/keploy/pkg/platform/telemetry/utils.go @@ -0,0 +1,52 @@ +package telemetry + +import ( + "encoding/json" + "errors" + "io" + "net/http" + + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +func marshalEvent(event models.TeleEvent, log *zap.Logger) (bin []byte, err error) { + + bin, err = json.Marshal(event) + if err != nil { + log.Fatal("failed to marshal event struct into json", zap.Error(err)) + } + return +} + +func unmarshalResp(resp *http.Response, log *zap.Logger) (id string, err error) { + + defer func(Body io.ReadCloser) { + err = Body.Close() + if err != nil { + log.Debug("failed to close connecton reader", zap.String("url", "https://telemetry.keploy.io/analytics"), zap.Error(err)) + return + } + }(resp.Body) + + var res map[string]string + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Debug("failed to read response from telemetry server", zap.String("url", "https://telemetry.keploy.io/analytics"), zap.Error(err)) + return + } + + err = json.Unmarshal(body, &res) + if err != nil { + log.Debug("failed to read testcases from telemetry server", zap.Error(err)) + return + } + + id, ok := res["InstallationID"] + if !ok { + log.Debug("InstallationID not present") + err = errors.New("InstallationID not present") + return + } + return +} diff --git a/keploy/pkg/platform/yaml/configdb/testset/db.go b/keploy/pkg/platform/yaml/configdb/testset/db.go new file mode 100644 index 0000000..3cc4bd5 --- /dev/null +++ b/keploy/pkg/platform/yaml/configdb/testset/db.go @@ -0,0 +1,96 @@ +// Package testset provides functionality for working with keploy testset level configs like templates, post/pre script. +package testset + +import ( + "context" + "os" + "path/filepath" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/yaml" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +// Db is a generic struct to read and write testset config file +type Db[T any] struct { + logger *zap.Logger + path string +} + +func New[T any](logger *zap.Logger, path string) *Db[T] { + return &Db[T]{ + logger: logger, + path: path, + } +} + +func (db *Db[T]) Read(ctx context.Context, testSetID string) (T, error) { + filePath := filepath.Join(db.path, testSetID) + + var config T + data, err := yaml.ReadFile(ctx, db.logger, filePath, "config") + if err != nil { + return config, err + } + if err := yamlLib.Unmarshal(data, &config); err != nil { + utils.LogError(db.logger, err, "failed to unmarshal test-set config file", zap.String("testSet", testSetID)) + return config, err + } + + secretConfig, ok := any(config).(models.Secret) + + if !ok { + return config, nil + } + + secretValues, err := db.ReadSecret(ctx, testSetID) + if err != nil { + db.logger.Warn("Failed to read secret values, continuing without secrets", zap.String("testSet", testSetID), zap.Error(err)) + return config, err + } + + secretConfig.SetSecrets(secretValues) + + return config, nil +} + +func (db *Db[T]) Write(ctx context.Context, testSetID string, config T) error { + filePath := filepath.Join(db.path, testSetID) + data, err := yamlLib.Marshal(config) + if err != nil { + utils.LogError(db.logger, err, "failed to marshal test-set config file", zap.String("testSet", testSetID)) + return err + } + err = yaml.WriteFile(ctx, db.logger, filePath, "config", data, false) + if err != nil { + utils.LogError(db.logger, err, "failed to write test-set configuration in yaml file", zap.String("testSet", testSetID)) + return err + } + + return nil +} + +// ReadSecret reads the secret configuration for a test set +func (db *Db[T]) ReadSecret(ctx context.Context, testSetID string) (map[string]interface{}, error) { + filePath := filepath.Join(db.path, testSetID) + + secretPath := filepath.Join(filePath, "secret.yaml") + if _, err := os.Stat(secretPath); os.IsNotExist(err) { + return make(map[string]interface{}), nil + } + + data, err := yaml.ReadFile(ctx, db.logger, filePath, "secret") + if err != nil { + return nil, err + } + + var secretConfig map[string]interface{} + if err := yamlLib.Unmarshal(data, &secretConfig); err != nil { + utils.LogError(db.logger, err, "failed to unmarshal test-set secret file", zap.String("testSet", testSetID)) + return nil, err + } + + return secretConfig, nil +} diff --git a/keploy/pkg/platform/yaml/configdb/user/db.go b/keploy/pkg/platform/yaml/configdb/user/db.go new file mode 100644 index 0000000..5dc3fae --- /dev/null +++ b/keploy/pkg/platform/yaml/configdb/user/db.go @@ -0,0 +1,107 @@ +// Package user provides functionality for working with keploy user configs like installation id. +package user + +import ( + "context" + "os" + "runtime" + + "github.com/denisbrodbeck/machineid" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "gopkg.in/yaml.v2" + yamlLib "gopkg.in/yaml.v3" +) + +type KeployConfig struct { + UpdatePrompt string `yaml:"updatePrompt" json:"updatePrompt"` +} + +type Db struct { + logger *zap.Logger + cfg *config.Config +} + +func HomeDir() string { + configFolder := "/.keploy" + if runtime.GOOS == "windows" { + home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + return home + configFolder + } + return os.Getenv("HOME") + configFolder +} + +func (db *Db) ReadKeployConfig() (*KeployConfig, error) { + path := HomeDir() + "/keploy.yaml" + content, err := os.ReadFile(path) + if err != nil { + return nil, err + } + // Decode the yaml file + var data KeployConfig + err = yaml.Unmarshal(content, &data) + if err != nil { + utils.LogError(db.logger, err, "failed to unmarshal keploy.yaml") + return nil, err + } + return &data, nil +} + +func (db *Db) WriteKeployConfig(data *KeployConfig) error { + // Open the keploy.yaml file + path := HomeDir() + "/keploy.yaml" + file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return err + } + defer func() { + if err := file.Close(); err != nil { + db.logger.Error("failed to close file", zap.Error(err)) + } + }() + updatedData, err := yamlLib.Marshal(data) + if err != nil { + return err + } + // Truncate the file before writing to it. + err = file.Truncate(0) + if err != nil { + return err + } + _, err = file.Write(updatedData) + if err != nil { + return err + } + return nil +} + +func New(logger *zap.Logger, cfg *config.Config) *Db { + return &Db{ + logger: logger, + cfg: cfg, + } +} + +func (db *Db) GetInstallationID(_ context.Context) (string, error) { + var id string + var err error + inDocker := os.Getenv("KEPLOY_INDOCKER") + if inDocker == "true" { + id = os.Getenv("INSTALLATION_ID") + } else { + id, err = machineid.ID() + if err != nil { + db.logger.Debug("failed to get machine id", zap.Error(err)) + return "", nil + } + } + if id == "" { + db.logger.Debug("got empty machine id") + return "", nil + } + return id, nil +} diff --git a/keploy/pkg/platform/yaml/mockdb/db.go b/keploy/pkg/platform/yaml/mockdb/db.go new file mode 100644 index 0000000..22ce031 --- /dev/null +++ b/keploy/pkg/platform/yaml/mockdb/db.go @@ -0,0 +1,319 @@ +// Package mockdb provides a mock database implementation. +package mockdb + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "sync/atomic" + "time" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/yaml" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +type MockYaml struct { + MockPath string + MockName string + Logger *zap.Logger + idCounter int64 +} + +func New(Logger *zap.Logger, mockPath string, mockName string) *MockYaml { + return &MockYaml{ + MockPath: mockPath, + MockName: mockName, + Logger: Logger, + idCounter: -1, + } +} + +// UpdateMocks deletes the mocks from the mock file with given names +// +// mockNames is a map which contains the name of the mocks as key and a isConfig boolean as value +func (ys *MockYaml) UpdateMocks(ctx context.Context, testSetID string, mockNames map[string]models.MockState) error { + mockFileName := "mocks" + if ys.MockName != "" { + mockFileName = ys.MockName + } + path := filepath.Join(ys.MockPath, testSetID) + ys.Logger.Debug("logging the names of the unused mocks to be removed", zap.Any("mockNames", mockNames), zap.Any("for testset", testSetID), zap.Any("at path", filepath.Join(path, mockFileName+".yaml"))) + + // Read the mocks from the yaml file + mockPath, err := yaml.ValidatePath(filepath.Join(path, mockFileName+".yaml")) + if err != nil { + utils.LogError(ys.Logger, err, "failed to read mocks due to inaccessible path", zap.Any("at path", filepath.Join(path, mockFileName+".yaml"))) + return err + } + if _, err := os.Stat(mockPath); err != nil { + utils.LogError(ys.Logger, err, "failed to find the mocks yaml file") + return err + } + data, err := yaml.ReadFile(ctx, ys.Logger, path, mockFileName) + if err != nil { + utils.LogError(ys.Logger, err, "failed to read the mocks from yaml file", zap.Any("at path", filepath.Join(path, mockFileName+".yaml"))) + return err + } + + // decode the mocks read from the yaml file + dec := yamlLib.NewDecoder(bytes.NewReader(data)) + var mockYamls []*yaml.NetworkTrafficDoc + for { + var doc *yaml.NetworkTrafficDoc + err := dec.Decode(&doc) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + utils.LogError(ys.Logger, err, "failed to decode the yaml file documents", zap.Any("at path", filepath.Join(path, mockFileName+".yaml"))) + return fmt.Errorf("failed to decode the yaml file documents. error: %v", err.Error()) + } + mockYamls = append(mockYamls, doc) + } + mocks, err := decodeMocks(mockYamls, ys.Logger) + if err != nil { + return err + } + var newMocks []*models.Mock + for _, mock := range mocks { + if _, ok := mockNames[mock.Name]; ok { + newMocks = append(newMocks, mock) + continue + } + } + ys.Logger.Debug("logging the names of the used mocks", zap.Any("mockNames", newMocks), zap.Any("for testset", testSetID)) + + // remove the old mock yaml file + err = os.Remove(filepath.Join(path, mockFileName+".yaml")) + if err != nil { + return err + } + + // write the new mocks to the new yaml file + for _, newMock := range newMocks { + mockYaml, err := EncodeMock(newMock, ys.Logger) + if err != nil { + utils.LogError(ys.Logger, err, "failed to encode the mock to yaml", zap.Any("mock", newMock.Name), zap.Any("for testset", testSetID)) + return err + } + data, err = yamlLib.Marshal(&mockYaml) + if err != nil { + utils.LogError(ys.Logger, err, "failed to marshal the mock to yaml", zap.Any("mock", newMock.Name), zap.Any("for testset", testSetID)) + return err + } + err = yaml.WriteFile(ctx, ys.Logger, path, mockFileName, data, true) + if err != nil { + utils.LogError(ys.Logger, err, "failed to write the mock to yaml", zap.Any("mock", newMock.Name), zap.Any("for testset", testSetID)) + return err + } + } + return nil +} + +func (ys *MockYaml) InsertMock(ctx context.Context, mock *models.Mock, testSetID string) error { + mock.Name = fmt.Sprint("mock-", ys.getNextID()) + mockYaml, err := EncodeMock(mock, ys.Logger) + if err != nil { + return err + } + mockPath := filepath.Join(ys.MockPath, testSetID) + mockFileName := ys.MockName + if mockFileName == "" { + mockFileName = "mocks" + } + data, err := yamlLib.Marshal(&mockYaml) + if err != nil { + return err + } + + exists, err := yaml.FileExists(ctx, ys.Logger, mockPath, mockFileName) + if err != nil { + utils.LogError(ys.Logger, err, "failed to find yaml file", zap.String("path directory", mockPath), zap.String("yaml", mockFileName)) + return err + } + + if !exists { + data = append([]byte(utils.GetVersionAsComment()), data...) + } + + err = yaml.WriteFile(ctx, ys.Logger, mockPath, mockFileName, data, true) + if err != nil { + return err + } + return nil +} + +func (ys *MockYaml) GetFilteredMocks(ctx context.Context, testSetID string, afterTime time.Time, beforeTime time.Time) ([]*models.Mock, error) { + + var tcsMocks = make([]*models.Mock, 0) + mockFileName := "mocks" + if ys.MockName != "" { + mockFileName = ys.MockName + } + + path := filepath.Join(ys.MockPath, testSetID) + mockPath, err := yaml.ValidatePath(path + "/" + mockFileName + ".yaml") + if err != nil { + return nil, err + } + + if _, err := os.Stat(mockPath); err == nil { + data, err := yaml.ReadFile(ctx, ys.Logger, path, mockFileName) + if err != nil { + utils.LogError(ys.Logger, err, "failed to read the mocks from yaml file", zap.Any("session", filepath.Base(path)), zap.String("path", mockPath)) + return nil, err + } + if len(data) == 0 { + utils.LogError(ys.Logger, err, "failed to read the mocks from yaml file", zap.Any("session", filepath.Base(path)), zap.String("path", mockPath)) + return nil, fmt.Errorf("failed to get mocks, empty file") + } + dec := yamlLib.NewDecoder(bytes.NewReader(data)) + for { + var doc *yaml.NetworkTrafficDoc + err := dec.Decode(&doc) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, fmt.Errorf("failed to decode the yaml file documents. error: %v", err.Error()) + } + + // Decode each YAML document into models.Mock as it is read. + mocks, err := decodeMocks([]*yaml.NetworkTrafficDoc{doc}, ys.Logger) + if err != nil { + utils.LogError(ys.Logger, err, "failed to decode the config mocks from yaml doc", zap.Any("session", filepath.Base(path))) + return nil, err + } + + for _, mock := range mocks { + isFilteredMock := true + switch mock.Kind { + case "Generic": + isFilteredMock = false + case "Postgres": + isFilteredMock = false + case "Http": + isFilteredMock = false + case "Redis": + isFilteredMock = false + case "MySQL": + isFilteredMock = false + } + if mock.Spec.Metadata["type"] != "config" && isFilteredMock { + tcsMocks = append(tcsMocks, mock) + } + } + } + } + + filtered := pkg.FilterTcsMocks(ctx, ys.Logger, tcsMocks, afterTime, beforeTime) + return filtered, nil +} + +func (ys *MockYaml) GetUnFilteredMocks(ctx context.Context, testSetID string, afterTime time.Time, beforeTime time.Time) ([]*models.Mock, error) { + + var configMocks = make([]*models.Mock, 0) + + mockName := "mocks" + if ys.MockName != "" { + mockName = ys.MockName + } + + path := filepath.Join(ys.MockPath, testSetID) + + mockPath, err := yaml.ValidatePath(path + "/" + mockName + ".yaml") + if err != nil { + return nil, err + } + + if _, err := os.Stat(mockPath); err == nil { + data, err := yaml.ReadFile(ctx, ys.Logger, path, mockName) + if err != nil { + utils.LogError(ys.Logger, err, "failed to read the mocks from config yaml", zap.Any("session", filepath.Base(path))) + return nil, err + } + dec := yamlLib.NewDecoder(bytes.NewReader(data)) + for { + var doc *yaml.NetworkTrafficDoc + err := dec.Decode(&doc) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, fmt.Errorf("failed to decode the yaml file documents. error: %v", err.Error()) + } + + // Decode each YAML document into models.Mock as it is read. + mocks, err := decodeMocks([]*yaml.NetworkTrafficDoc{doc}, ys.Logger) + if err != nil { + utils.LogError(ys.Logger, err, "failed to decode the config mocks from yaml doc", zap.Any("session", filepath.Base(path))) + return nil, err + } + + for _, mock := range mocks { + isUnFilteredMock := false + switch mock.Kind { + case "Generic": + isUnFilteredMock = true + case "Postgres": + isUnFilteredMock = true + case "Http": + isUnFilteredMock = true + case "Redis": + isUnFilteredMock = true + case "MySQL": + isUnFilteredMock = true + } + if mock.Spec.Metadata["type"] == "config" || isUnFilteredMock { + configMocks = append(configMocks, mock) + } + } + } + } + + unfiltered := pkg.FilterConfigMocks(ctx, ys.Logger, configMocks, afterTime, beforeTime) + + return unfiltered, nil +} + +func (ys *MockYaml) getNextID() int64 { + return atomic.AddInt64(&ys.idCounter, 1) +} + +func (ys *MockYaml) GetHTTPMocks(ctx context.Context, testSetID string, mockPath string, mockFileName string) ([]*models.HTTPDoc, error) { + + if ys.MockName != "" { + ys.MockName = mockFileName + } + ys.MockPath = mockPath + + tcsMocks, err := ys.GetUnFilteredMocks(ctx, testSetID, time.Time{}, time.Time{}) + if err != nil { + return nil, err + } + + var httpMocks []*models.HTTPDoc + for _, mock := range tcsMocks { + if mock.Kind != "Http" { + continue + } + var httpMock models.HTTPDoc + httpMock.Kind = mock.GetKind() + httpMock.Name = mock.Name + httpMock.Spec.Request = *mock.Spec.HTTPReq + httpMock.Spec.Response = *mock.Spec.HTTPResp + httpMock.Spec.Metadata = mock.Spec.Metadata + httpMock.Version = string(mock.Version) + httpMocks = append(httpMocks, &httpMock) + } + + return httpMocks, nil +} diff --git a/keploy/pkg/platform/yaml/mockdb/util.go b/keploy/pkg/platform/yaml/mockdb/util.go new file mode 100644 index 0000000..c9d62e8 --- /dev/null +++ b/keploy/pkg/platform/yaml/mockdb/util.go @@ -0,0 +1,714 @@ +package mockdb + +import ( + "context" + "errors" + "strings" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/models/mysql" + "go.keploy.io/server/v2/pkg/platform/yaml" + "go.keploy.io/server/v2/utils" + "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" + "go.uber.org/zap" +) + +func EncodeMock(mock *models.Mock, logger *zap.Logger) (*yaml.NetworkTrafficDoc, error) { + + yamlDoc := yaml.NetworkTrafficDoc{ + Version: mock.Version, + Kind: mock.Kind, + Name: mock.Name, + ConnectionID: mock.ConnectionID, + } + switch mock.Kind { + case models.Mongo: + requests := []models.RequestYaml{} + for _, v := range mock.Spec.MongoRequests { + req := models.RequestYaml{ + Header: v.Header, + ReadDelay: v.ReadDelay, + } + err := req.Message.Encode(v.Message) + if err != nil { + utils.LogError(logger, err, "failed to encode mongo request wiremessage into yaml") + return nil, err + } + requests = append(requests, req) + } + responses := []models.ResponseYaml{} + for _, v := range mock.Spec.MongoResponses { + resp := models.ResponseYaml{ + Header: v.Header, + ReadDelay: v.ReadDelay, + } + err := resp.Message.Encode(v.Message) + if err != nil { + utils.LogError(logger, err, "failed to encode mongo response wiremessage into yaml") + return nil, err + } + responses = append(responses, resp) + } + mongoSpec := models.MongoSpec{ + Metadata: mock.Spec.Metadata, + Requests: requests, + Response: responses, + CreatedAt: mock.Spec.Created, + ReqTimestampMock: mock.Spec.ReqTimestampMock, + ResTimestampMock: mock.Spec.ResTimestampMock, + } + + err := yamlDoc.Spec.Encode(mongoSpec) + if err != nil { + utils.LogError(logger, err, "failed to marshal the mongo input-output as yaml") + return nil, err + } + + case models.HTTP: + httpSpec := models.HTTPSchema{ + Metadata: mock.Spec.Metadata, + Request: *mock.Spec.HTTPReq, + Response: *mock.Spec.HTTPResp, + Created: mock.Spec.Created, + ReqTimestampMock: mock.Spec.ReqTimestampMock, + ResTimestampMock: mock.Spec.ResTimestampMock, + } + err := yamlDoc.Spec.Encode(httpSpec) + if err != nil { + utils.LogError(logger, err, "failed to marshal the http input-output as yaml") + return nil, err + } + case models.GENERIC: + genericSpec := models.GenericSchema{ + Metadata: mock.Spec.Metadata, + GenericRequests: mock.Spec.GenericRequests, + GenericResponses: mock.Spec.GenericResponses, + ReqTimestampMock: mock.Spec.ReqTimestampMock, + ResTimestampMock: mock.Spec.ResTimestampMock, + } + err := yamlDoc.Spec.Encode(genericSpec) + if err != nil { + utils.LogError(logger, err, "failed to marshal the generic input-output as yaml") + return nil, err + } + case models.REDIS: + redisSpec := models.RedisSchema{ + Metadata: mock.Spec.Metadata, + RedisRequests: mock.Spec.RedisRequests, + RedisResponses: mock.Spec.RedisResponses, + ReqTimestampMock: mock.Spec.ReqTimestampMock, + ResTimestampMock: mock.Spec.ResTimestampMock, + } + err := yamlDoc.Spec.Encode(redisSpec) + if err != nil { + utils.LogError(logger, err, "failed to marshal the redis input-output as yaml") + return nil, err + } + case models.Postgres: + // case models.PostgresV2: + + postgresSpec := models.PostgresSpec{ + Metadata: mock.Spec.Metadata, + PostgresRequests: mock.Spec.PostgresRequests, + PostgresResponses: mock.Spec.PostgresResponses, + ReqTimestampMock: mock.Spec.ReqTimestampMock, + ResTimestampMock: mock.Spec.ResTimestampMock, + } + + err := yamlDoc.Spec.Encode(postgresSpec) + if err != nil { + utils.LogError(logger, err, "failed to marshal the postgres input-output as yaml") + return nil, err + } + case models.GRPC_EXPORT: + gRPCSpec := models.GrpcSpec{ + Metadata: mock.Spec.Metadata, + GrpcReq: *mock.Spec.GRPCReq, + GrpcResp: *mock.Spec.GRPCResp, + ReqTimestampMock: mock.Spec.ReqTimestampMock, + ResTimestampMock: mock.Spec.ResTimestampMock, + } + err := yamlDoc.Spec.Encode(gRPCSpec) + if err != nil { + utils.LogError(logger, err, "failed to marshal gRPC of external call into yaml") + return nil, err + } + case models.MySQL: + requests := []mysql.RequestYaml{} + for _, v := range mock.Spec.MySQLRequests { + + req := mysql.RequestYaml{ + Header: v.Header, + Meta: v.Meta, + } + err := req.Message.Encode(v.Message) + if err != nil { + utils.LogError(logger, err, "failed to encode mysql request wiremessage into yaml") + return nil, err + } + requests = append(requests, req) + } + responses := []mysql.ResponseYaml{} + for _, v := range mock.Spec.MySQLResponses { + resp := mysql.ResponseYaml{ + Header: v.Header, + Meta: v.Meta, + } + err := resp.Message.Encode(v.Message) + if err != nil { + utils.LogError(logger, err, "failed to encode mysql response wiremessage into yaml") + return nil, err + } + responses = append(responses, resp) + } + + sqlSpec := mysql.Spec{ + Metadata: mock.Spec.Metadata, + Requests: requests, + Response: responses, + CreatedAt: mock.Spec.Created, + ReqTimestampMock: mock.Spec.ReqTimestampMock, + ResTimestampMock: mock.Spec.ResTimestampMock, + } + err := yamlDoc.Spec.Encode(sqlSpec) + if err != nil { + utils.LogError(logger, err, "failed to marshal the MySQL input-output as yaml") + return nil, err + } + default: + utils.LogError(logger, nil, "failed to marshal the recorded mock into yaml due to invalid kind of mock") + return nil, errors.New("type of mock is invalid") + } + + return &yamlDoc, nil +} + +func decodeMocks(yamlMocks []*yaml.NetworkTrafficDoc, logger *zap.Logger) ([]*models.Mock, error) { + mocks := []*models.Mock{} + + for _, m := range yamlMocks { + mock := models.Mock{ + Version: m.Version, + Name: m.Name, + Kind: m.Kind, + ConnectionID: m.ConnectionID, + } + mockCheck := strings.Split(string(m.Kind), "-") + if len(mockCheck) > 1 { + logger.Debug("This dependency does not belong to open source version, will be skipped", zap.String("mock kind:", string(m.Kind))) + continue + } + switch m.Kind { + case models.HTTP: + httpSpec := models.HTTPSchema{} + err := m.Spec.Decode(&httpSpec) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal a yaml doc into http mock", zap.Any("mock name", m.Name)) + return nil, err + } + + mock.Spec = models.MockSpec{ + Metadata: httpSpec.Metadata, + HTTPReq: &httpSpec.Request, + HTTPResp: &httpSpec.Response, + Created: httpSpec.Created, + ReqTimestampMock: httpSpec.ReqTimestampMock, + ResTimestampMock: httpSpec.ResTimestampMock, + } + case models.Mongo: + mongoSpec := models.MongoSpec{} + err := m.Spec.Decode(&mongoSpec) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal a yaml doc into mongo mock", zap.Any("mock name", m.Name)) + return nil, err + } + + mockSpec, err := decodeMongoMessage(&mongoSpec, logger) + if err != nil { + return nil, err + } + mock.Spec = *mockSpec + case models.GRPC_EXPORT: + grpcSpec := models.GrpcSpec{} + err := m.Spec.Decode(&grpcSpec) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal a yaml doc into http mock", zap.Any("mock name", m.Name)) + return nil, err + } + mock.Spec = models.MockSpec{ + Metadata: grpcSpec.Metadata, + GRPCResp: &grpcSpec.GrpcResp, + GRPCReq: &grpcSpec.GrpcReq, + ReqTimestampMock: grpcSpec.ReqTimestampMock, + ResTimestampMock: grpcSpec.ResTimestampMock, + } + case models.GENERIC: + genericSpec := models.GenericSchema{} + err := m.Spec.Decode(&genericSpec) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal a yaml doc into generic mock", zap.Any("mock name", m.Name)) + return nil, err + } + mock.Spec = models.MockSpec{ + Metadata: genericSpec.Metadata, + GenericRequests: genericSpec.GenericRequests, + GenericResponses: genericSpec.GenericResponses, + ReqTimestampMock: genericSpec.ReqTimestampMock, + ResTimestampMock: genericSpec.ResTimestampMock, + } + case models.REDIS: + redisSpec := models.RedisSchema{} + err := m.Spec.Decode(&redisSpec) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal a yaml doc into redis mock", zap.Any("mock name", m.Name)) + return nil, err + } + mock.Spec = models.MockSpec{ + Metadata: redisSpec.Metadata, + RedisRequests: redisSpec.RedisRequests, + RedisResponses: redisSpec.RedisResponses, + ReqTimestampMock: redisSpec.ReqTimestampMock, + ResTimestampMock: redisSpec.ResTimestampMock, + } + + case models.Postgres: + // case models.PostgresV2: + + PostSpec := models.PostgresSpec{} + err := m.Spec.Decode(&PostSpec) + + if err != nil { + utils.LogError(logger, err, "failed to unmarshal a yaml doc into generic mock", zap.Any("mock name", m.Name)) + return nil, err + } + mock.Spec = models.MockSpec{ + Metadata: PostSpec.Metadata, + // OutputBinary: genericSpec.Objects, + PostgresRequests: PostSpec.PostgresRequests, + PostgresResponses: PostSpec.PostgresResponses, + ReqTimestampMock: PostSpec.ReqTimestampMock, + ResTimestampMock: PostSpec.ResTimestampMock, + } + case models.MySQL: + mySQLSpec := mysql.Spec{} + err := m.Spec.Decode(&mySQLSpec) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal a yaml doc into mysql mock", zap.Any("mock name", m.Name)) + return nil, err + } + + mockSpec, err := decodeMySQLMessage(context.Background(), logger, &mySQLSpec) + if err != nil { + return nil, err + } + mock.Spec = *mockSpec + default: + utils.LogError(logger, nil, "failed to unmarshal a mock yaml doc of unknown type", zap.Any("type", m.Kind)) + continue + } + mocks = append(mocks, &mock) + } + + return mocks, nil +} + +func decodeMySQLMessage(_ context.Context, logger *zap.Logger, yamlSpec *mysql.Spec) (*models.MockSpec, error) { + mockSpec := models.MockSpec{ + Metadata: yamlSpec.Metadata, + Created: yamlSpec.CreatedAt, + ReqTimestampMock: yamlSpec.ReqTimestampMock, + ResTimestampMock: yamlSpec.ResTimestampMock, + } + + // Decode the requests + + requests := []mysql.Request{} + for _, v := range yamlSpec.Requests { + req := mysql.Request{ + PacketBundle: mysql.PacketBundle{ + Header: v.Header, + Meta: v.Meta, + }, + } + + switch v.Header.Type { + // connection phase + + case mysql.SSLRequest: + msg := &mysql.SSLRequestPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql SSLRequestPacket") + return nil, err + } + req.Message = msg + + case mysql.HandshakeResponse41: + msg := &mysql.HandshakeResponse41Packet{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql HandshakeResponse41Packet") + return nil, err + } + req.Message = msg + + case mysql.CachingSha2PasswordToString(mysql.RequestPublicKey): + var msg string + err := v.Message.Decode(&msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql (string) RequestPublicKey") + return nil, err + } + req.Message = msg + + case mysql.EncryptedPassword: + var msg string + err := v.Message.Decode(&msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql (string) encrypted_password") + return nil, err + } + req.Message = msg + case mysql.PlainPassword: + var msg string + err := v.Message.Decode(&msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql (string) plain_password") + return nil, err + } + req.Message = msg + + // command phase + + // utility packets + case mysql.CommandStatusToString(mysql.COM_QUIT): + msg := &mysql.QuitPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql QuitPacket") + return nil, err + } + req.Message = msg + + case mysql.CommandStatusToString(mysql.COM_INIT_DB): + msg := &mysql.InitDBPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql InitDBPacket") + return nil, err + } + req.Message = msg + + case mysql.CommandStatusToString(mysql.COM_STATISTICS): + msg := &mysql.StatisticsPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StatisticsPacket") + return nil, err + } + req.Message = msg + + case mysql.CommandStatusToString(mysql.COM_DEBUG): + msg := &mysql.DebugPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql DebugPacket") + return nil, err + } + req.Message = msg + + case mysql.CommandStatusToString(mysql.COM_PING): + msg := &mysql.PingPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql PingPacket") + return nil, err + } + req.Message = msg + + case mysql.CommandStatusToString(mysql.COM_CHANGE_USER): + msg := &mysql.ChangeUserPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql ChangeUserPacket") + return nil, err + } + req.Message = msg + + case mysql.CommandStatusToString(mysql.COM_RESET_CONNECTION): + msg := &mysql.ResetConnectionPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql ResetConnectionPacket") + return nil, err + } + req.Message = msg + + // case mysql.CommandStatusToString(mysql.COM_SET_OPTION): // not supported yet + + // query packets + case mysql.CommandStatusToString(mysql.COM_QUERY): + msg := &mysql.QueryPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql QueryPacket") + return nil, err + } + req.Message = msg + + case mysql.CommandStatusToString(mysql.COM_STMT_PREPARE): + msg := &mysql.StmtPreparePacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StmtPreparePacket") + return nil, err + } + req.Message = msg + + case mysql.CommandStatusToString(mysql.COM_STMT_EXECUTE): + msg := &mysql.StmtExecutePacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StmtExecutePacket") + return nil, err + } + req.Message = msg + + // case mysql.CommandStatusToString(mysql.COM_FETCH): // not supported yet + + case mysql.CommandStatusToString(mysql.COM_STMT_CLOSE): + msg := &mysql.StmtClosePacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StmtClosePacket") + return nil, err + } + req.Message = msg + + case mysql.CommandStatusToString(mysql.COM_STMT_RESET): + msg := &mysql.StmtResetPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StmtResetPacket") + return nil, err + } + req.Message = msg + + case mysql.CommandStatusToString(mysql.COM_STMT_SEND_LONG_DATA): + msg := &mysql.StmtSendLongDataPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StmtSendLongDataPacket") + return nil, err + } + req.Message = msg + } + requests = append(requests, req) + } + + mockSpec.MySQLRequests = requests + + // Decode the responses + + responses := []mysql.Response{} + for _, v := range yamlSpec.Response { + + resp := mysql.Response{ + PacketBundle: mysql.PacketBundle{ + Header: v.Header, + Meta: v.Meta, + }, + } + + switch v.Header.Type { + // generic response + case mysql.StatusToString(mysql.EOF): + msg := &mysql.EOFPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mysql EOFPacket") + return nil, err + } + resp.Message = msg + + case mysql.StatusToString(mysql.ERR): + msg := &mysql.ERRPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mysql ERRPacket") + return nil, err + } + resp.Message = msg + + case mysql.StatusToString(mysql.OK): + msg := &mysql.OKPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mysql OKPacket") + return nil, err + } + resp.Message = msg + + // connection phase + case mysql.AuthStatusToString(mysql.HandshakeV10): + msg := &mysql.HandshakeV10Packet{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mysql HandshakeV10Packet") + return nil, err + } + resp.Message = msg + + case mysql.AuthStatusToString(mysql.AuthSwitchRequest): + msg := &mysql.AuthSwitchRequestPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mysql AuthSwitchRequestPacket") + return nil, err + } + resp.Message = msg + + case mysql.AuthStatusToString(mysql.AuthMoreData): + msg := &mysql.AuthMoreDataPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mysql AuthMoreDataPacket") + return nil, err + } + resp.Message = msg + + case mysql.AuthStatusToString(mysql.AuthNextFactor): // not supported yet + msg := &mysql.AuthNextFactorPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mysql AuthNextFactorPacket") + return nil, err + } + resp.Message = msg + + // command phase + case mysql.COM_STMT_PREPARE_OK: + msg := &mysql.StmtPrepareOkPacket{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mysql StmtPrepareOkPacket") + return nil, err + } + resp.Message = msg + + case string(mysql.Text): + msg := &mysql.TextResultSet{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mysql TextResultSet") + return nil, err + } + resp.Message = msg + + case string(mysql.Binary): + msg := &mysql.BinaryProtocolResultSet{} + err := v.Message.Decode(msg) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mysql BinaryProtocolResultSet") + return nil, err + } + resp.Message = msg + } + responses = append(responses, resp) + } + + mockSpec.MySQLResponses = responses + + return &mockSpec, nil +} + +func decodeMongoMessage(yamlSpec *models.MongoSpec, logger *zap.Logger) (*models.MockSpec, error) { + mockSpec := models.MockSpec{ + Metadata: yamlSpec.Metadata, + Created: yamlSpec.CreatedAt, + ReqTimestampMock: yamlSpec.ReqTimestampMock, + ResTimestampMock: yamlSpec.ResTimestampMock, + } + + // mongo request + requests := []models.MongoRequest{} + for _, v := range yamlSpec.Requests { + req := models.MongoRequest{ + Header: v.Header, + ReadDelay: v.ReadDelay, + } + // decode the yaml document to mongo request wiremessage + switch v.Header.Opcode { + case wiremessage.OpMsg: + requestMessage := &models.MongoOpMessage{} + err := v.Message.Decode(requestMessage) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpMsg request wiremessage") + return nil, err + } + req.Message = requestMessage + case wiremessage.OpReply: + requestMessage := &models.MongoOpReply{} + err := v.Message.Decode(requestMessage) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpReply request wiremessage") + return nil, err + } + req.Message = requestMessage + case wiremessage.OpQuery: + requestMessage := &models.MongoOpQuery{} + err := v.Message.Decode(requestMessage) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpQuery request wiremessage") + // return fmt.Errorf("failed to decode the mongo OpReply of mock with name: %s. error: %s", doc.Name, err.Error()) + return nil, err + } + req.Message = requestMessage + default: + } + requests = append(requests, req) + } + mockSpec.MongoRequests = requests + + // mongo response + responses := []models.MongoResponse{} + for _, v := range yamlSpec.Response { + resp := models.MongoResponse{ + Header: v.Header, + ReadDelay: v.ReadDelay, + } + // decode the yaml document to mongo response wiremessage + switch v.Header.Opcode { + case wiremessage.OpMsg: + responseMessage := &models.MongoOpMessage{} + err := v.Message.Decode(responseMessage) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpMsg response wiremessage") + // return fmt.Errorf("failed to decode the mongo OpMsg of mock with name: %s. error: %s", doc.Name, err.Error()) + return nil, err + } + resp.Message = responseMessage + case wiremessage.OpReply: + responseMessage := &models.MongoOpReply{} + err := v.Message.Decode(responseMessage) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpMsg response wiremessage") + return nil, err + } + resp.Message = responseMessage + case wiremessage.OpQuery: + responseMessage := &models.MongoOpQuery{} + err := v.Message.Decode(responseMessage) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpMsg response wiremessage") + // return fmt.Errorf("failed to decode the mongo OpMsg of mock with name: %s. error: %s", doc.Name, err.Error()) + return nil, err + } + resp.Message = responseMessage + default: + } + responses = append(responses, resp) + } + mockSpec.MongoResponses = responses + return &mockSpec, nil +} diff --git a/keploy/pkg/platform/yaml/openapidb/db.go b/keploy/pkg/platform/yaml/openapidb/db.go new file mode 100644 index 0000000..a9e2998 --- /dev/null +++ b/keploy/pkg/platform/yaml/openapidb/db.go @@ -0,0 +1,152 @@ +// Package openapidb provides a openAPI database implementation. +package openapidb + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/yaml" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +type OpenAPIYaml struct { + OpenAPIPath string + logger *zap.Logger +} + +func New(logger *zap.Logger, openAPIPath string) *OpenAPIYaml { + return &OpenAPIYaml{ + OpenAPIPath: openAPIPath, + logger: logger, + } +} +func (ts *OpenAPIYaml) GetTestCasesSchema(ctx context.Context, testSetID string, testPath string) ([]*models.OpenAPI, error) { + var path string + if testPath == "" { + path = filepath.Join(ts.OpenAPIPath, testSetID) + + } else { + path = filepath.Join(testPath, testSetID) + } + + tcs := []*models.OpenAPI{} + TestPath, err := yaml.ValidatePath(path) + if err != nil { + return nil, err + } + _, err = os.Stat(TestPath) + if err != nil { + ts.logger.Debug("no tests are recorded for the session", zap.String("index", testSetID)) + return nil, nil + } + dir, err := yaml.ReadDir(TestPath, fs.ModePerm) + if err != nil { + utils.LogError(ts.logger, err, "failed to open the directory containing yaml testcases", zap.Any("path", TestPath)) + return nil, err + } + files, err := dir.ReadDir(0) + if err != nil { + utils.LogError(ts.logger, err, "failed to read the file names of yaml testcases", zap.Any("path", TestPath)) + return nil, err + } + for _, j := range files { + + name := strings.TrimSuffix(j.Name(), filepath.Ext(j.Name())) + data, err := yaml.ReadFile(ctx, ts.logger, TestPath, name) + if err != nil { + utils.LogError(ts.logger, err, "failed to read the testcase from yaml") + return nil, err + } + + var testCase *models.OpenAPI + err = yamlLib.Unmarshal(data, &testCase) + if err != nil { + utils.LogError(ts.logger, err, "failed to unmarshall YAML data") + return nil, err + } + + tcs = append(tcs, testCase) + } + + return tcs, nil +} + +func (ts *OpenAPIYaml) GetMocksSchemas(ctx context.Context, testSetID string, mockPath string, mockFileName string) ([]*models.OpenAPI, error) { + + var tcsMocks = make([]*models.OpenAPI, 0) + + path := filepath.Join(mockPath, testSetID) + mockPath, err := yaml.ValidatePath(path + "/" + mockFileName + ".yaml") + if err != nil { + return nil, err + } + + if _, err := os.Stat(mockPath); err == nil { + var mockYamls []*models.OpenAPI + data, err := yaml.ReadFile(ctx, ts.logger, path, mockFileName) + if err != nil { + utils.LogError(ts.logger, err, "failed to read the mocks from config yaml", zap.Any("session", filepath.Base(path))) + return nil, err + } + dec := yamlLib.NewDecoder(bytes.NewReader(data)) + for { + var doc *models.OpenAPI + err := dec.Decode(&doc) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, fmt.Errorf("failed to decode the yaml file documents. error: %v", err.Error()) + } + mockYamls = append(mockYamls, doc) + } + if err != nil { + utils.LogError(ts.logger, err, "failed to decode the config mocks from yaml docs", zap.Any("session", filepath.Base(path))) + return nil, err + } + tcsMocks = mockYamls + } + + return tcsMocks, nil +} +func (ts *OpenAPIYaml) ChangePath(path string) { + + // ts.OpenAPIPath = "./keploy/" + ts.OpenAPIPath = path +} + +func (ts *OpenAPIYaml) WriteSchema(ctx context.Context, logger *zap.Logger, outputPath, name string, openapi models.OpenAPI, isAppend bool) error { + openapiYAML, err := yamlLib.Marshal(openapi) + if err != nil { + return err + } + _, err = os.Stat(outputPath) + if os.IsNotExist(err) { + err = os.MkdirAll(outputPath, os.ModePerm) + if err != nil { + utils.LogError(logger, err, "failed to create directory", zap.String("directory", outputPath)) + return err + } + logger.Info("Directory created", zap.String("directory", outputPath)) + } + + err = yaml.WriteFile(ctx, logger, outputPath, name, openapiYAML, isAppend) + if err != nil { + utils.LogError(logger, err, "failed to write OpenAPI YAML to a file", zap.String("outputPath", outputPath), zap.String("name", name)) + return err + } + + outputFilePath := outputPath + "/" + name + ".yaml" + logger.Info("OpenAPI YAML has been saved to ", zap.String("path", outputFilePath)) + return nil +} diff --git a/keploy/pkg/platform/yaml/reportdb/db.go b/keploy/pkg/platform/yaml/reportdb/db.go new file mode 100755 index 0000000..83ac851 --- /dev/null +++ b/keploy/pkg/platform/yaml/reportdb/db.go @@ -0,0 +1,138 @@ +// Package reportdb provides functionality for managing test reports in a database. +package reportdb + +import ( + "bytes" + "context" + "fmt" + "path/filepath" + "sync" + "time" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/yaml" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +type TestReport struct { + tests map[string]map[string][]models.TestResult + m sync.Mutex + Logger *zap.Logger + Path string + Name string +} + +func New(logger *zap.Logger, reportPath string) *TestReport { + return &TestReport{ + tests: make(map[string]map[string][]models.TestResult), + m: sync.Mutex{}, + Logger: logger, + Path: reportPath, + } +} + +func (fe *TestReport) ClearTestCaseResults(_ context.Context, testRunID string, testSetID string) { + fe.m.Lock() + defer fe.m.Unlock() + + fe.tests[testRunID] = make(map[string][]models.TestResult) +} + +func (fe *TestReport) GetAllTestRunIDs(ctx context.Context) ([]string, error) { + return yaml.ReadSessionIndices(ctx, fe.Path, fe.Logger, yaml.ModeDir) +} + +func (fe *TestReport) InsertTestCaseResult(_ context.Context, testRunID string, testSetID string, result *models.TestResult) error { + fe.m.Lock() + defer fe.m.Unlock() + + testSet := fe.tests[testRunID] + if testSet == nil { + testSet = make(map[string][]models.TestResult) + testSet[testSetID] = []models.TestResult{*result} + } else { + testSet[testSetID] = append(testSet[testSetID], *result) + } + fe.tests[testRunID] = testSet + return nil +} + +func (fe *TestReport) GetTestCaseResults(_ context.Context, testRunID string, testSetID string) ([]models.TestResult, error) { + testRun, ok := fe.tests[testRunID] + if !ok { + return []models.TestResult{}, fmt.Errorf("%s found no test results for test report with id: %s", utils.Emoji, testRunID) + } + testSetResults, ok := testRun[testSetID] + if !ok { + return []models.TestResult{}, fmt.Errorf("%s found no test results for test set with id: %s", utils.Emoji, testSetID) + } + return testSetResults, nil +} + +func (fe *TestReport) GetReport(ctx context.Context, testRunID string, testSetID string) (*models.TestReport, error) { + path := filepath.Join(fe.Path, testRunID) + reportName := testSetID + "-report" + _, err := yaml.ValidatePath(filepath.Join(path, reportName+".yaml")) + if err != nil { + return nil, err + } + data, err := yaml.ReadFile(ctx, fe.Logger, path, reportName) + if err != nil { + utils.LogError(fe.Logger, err, "failed to read the test-set report", zap.Any("reportName", reportName), zap.Any("session", filepath.Base(path))) + return nil, err + } + + decoder := yamlLib.NewDecoder(bytes.NewReader(data)) + var doc models.TestReport + err = decoder.Decode(&doc) + if err != nil { + return &models.TestReport{}, fmt.Errorf("%s failed to decode the yaml file documents. error: %v", utils.Emoji, err.Error()) + } + return &doc, nil +} + +func (fe *TestReport) InsertReport(ctx context.Context, testRunID string, testSetID string, testReport *models.TestReport) error { + + reportPath := filepath.Join(fe.Path, testRunID) + + if testReport.Name == "" { + testReport.Name = testSetID + "-report" + } + + testReport.CreatedAt = time.Now().Unix() + + data := []byte{} + d, err := yamlLib.Marshal(&testReport) + if err != nil { + return fmt.Errorf("%s failed to marshal document to yaml. error: %s", utils.Emoji, err.Error()) + } + data = append(data, d...) + data = append([]byte(utils.GetVersionAsComment()), data...) + + err = yaml.WriteFile(ctx, fe.Logger, reportPath, testReport.Name, data, false) + if err != nil { + utils.LogError(fe.Logger, err, "failed to write the report to yaml", zap.Any("session", filepath.Base(reportPath))) + return err + } + return nil +} + +func (fe *TestReport) UpdateReport(ctx context.Context, testRunID string, coverageReport any) error { + reportPath := filepath.Join(fe.Path, testRunID) + + data := []byte{} + d, err := yamlLib.Marshal(&coverageReport) + if err != nil { + return fmt.Errorf("%s failed to marshal document to yaml. error: %s", utils.Emoji, err.Error()) + } + data = append(data, d...) + + err = yaml.WriteFile(ctx, fe.Logger, reportPath, "coverage", data, false) + if err != nil { + utils.LogError(fe.Logger, err, "failed to write the coverage report to yaml", zap.Any("session", filepath.Base(reportPath))) + return err + } + return nil +} diff --git a/keploy/pkg/platform/yaml/testdb/db.go b/keploy/pkg/platform/yaml/testdb/db.go new file mode 100644 index 0000000..e542734 --- /dev/null +++ b/keploy/pkg/platform/yaml/testdb/db.go @@ -0,0 +1,262 @@ +// Package testdb provides functionality for working with test databases. +package testdb + +import ( + "bytes" + "context" + "fmt" + "io/fs" + "os" + "path/filepath" + "sort" + "strings" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/yaml" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +type TestYaml struct { + TcsPath string + logger *zap.Logger +} + +func New(logger *zap.Logger, tcsPath string) *TestYaml { + return &TestYaml{ + TcsPath: tcsPath, + logger: logger, + } +} + +type tcsInfo struct { + name string + path string +} + +func (ts *TestYaml) InsertTestCase(ctx context.Context, tc *models.TestCase, testSetID string, enableLog bool) error { + tcsInfo, err := ts.upsert(ctx, testSetID, tc) + if err != nil { + return err + } + + if enableLog { + ts.logger.Info("🟠 Keploy has captured test cases for the user's application.", zap.String("path", tcsInfo.path), zap.String("testcase name", tcsInfo.name)) + } + + return nil +} + +func (ts *TestYaml) GetAllTestSetIDs(ctx context.Context) ([]string, error) { + return yaml.ReadSessionIndices(ctx, ts.TcsPath, ts.logger, yaml.ModeDir) +} + +func (ts *TestYaml) GetReportTestSets(ctx context.Context, latestRunID string) ([]string, error) { + if latestRunID == "" { + ts.logger.Warn("No latest run ID provided, returning empty test set IDs") + return []string{}, nil + } + + runReportPath := filepath.Join(ts.TcsPath, "reports", latestRunID) + + return yaml.ReadSessionIndices(ctx, runReportPath, ts.logger, yaml.ModeFile) +} + +func (ts *TestYaml) GetTestCases(ctx context.Context, testSetID string) ([]*models.TestCase, error) { + path := filepath.Join(ts.TcsPath, testSetID, "tests") + + tcs := []*models.TestCase{} + TestPath, err := yaml.ValidatePath(path) + if err != nil { + return nil, err + } + _, err = os.Stat(TestPath) + if err != nil { + ts.logger.Debug("no tests are recorded for the session", zap.String("index", testSetID)) + return nil, nil + } + dir, err := yaml.ReadDir(TestPath, fs.ModePerm) + if err != nil { + utils.LogError(ts.logger, err, "failed to open the directory containing yaml testcases", zap.Any("path", TestPath)) + return nil, err + } + files, err := dir.ReadDir(0) + if err != nil { + utils.LogError(ts.logger, err, "failed to read the file names of yaml testcases", zap.Any("path", TestPath)) + return nil, err + } + for _, j := range files { + if filepath.Ext(j.Name()) != ".yaml" || strings.Contains(j.Name(), "mocks") { + continue + } + + name := strings.TrimSuffix(j.Name(), filepath.Ext(j.Name())) + data, err := yaml.ReadFile(ctx, ts.logger, TestPath, name) + if err != nil { + utils.LogError(ts.logger, err, "failed to read the testcase from yaml") + return nil, err + } + + if len(data) == 0 { + ts.logger.Warn("skipping empty testcase", zap.String("testcase name", name)) + continue + } + + var testCase *yaml.NetworkTrafficDoc + err = yamlLib.Unmarshal(data, &testCase) + if err != nil { + utils.LogError(ts.logger, err, "failed to unmarshall YAML data") + return nil, err + } + + if testCase == nil { + ts.logger.Warn("skipping invalid testCase yaml", zap.String("testcase name", name)) + continue + } + + tc, err := Decode(testCase, ts.logger) + if err != nil { + utils.LogError(ts.logger, err, "failed to decode the testcase") + return nil, err + } + tcs = append(tcs, tc) + } + sort.SliceStable(tcs, func(i, j int) bool { + return tcs[i].HTTPReq.Timestamp.Before(tcs[j].HTTPReq.Timestamp) + }) + return tcs, nil +} + +func (ts *TestYaml) UpdateTestCase(ctx context.Context, tc *models.TestCase, testSetID string, enableLog bool) error { + + tcsInfo, err := ts.upsert(ctx, testSetID, tc) + if err != nil { + return err + } + + if enableLog { + ts.logger.Info("🔄 Keploy has updated the test cases for the user's application.", zap.String("path", tcsInfo.path), zap.String("testcase name", tcsInfo.name)) + } + return nil +} + +func (ts *TestYaml) upsert(ctx context.Context, testSetID string, tc *models.TestCase) (tcsInfo, error) { + tcsPath := filepath.Join(ts.TcsPath, testSetID, "tests") + var tcsName string + if tc.Name == "" { + lastIndx, err := yaml.FindLastIndex(tcsPath, ts.logger) + if err != nil { + return tcsInfo{name: "", path: tcsPath}, err + } + tcsName = fmt.Sprintf("test-%v", lastIndx) + } else { + tcsName = tc.Name + } + yamlTc, err := EncodeTestcase(*tc, ts.logger) + if err != nil { + return tcsInfo{name: tcsName, path: tcsPath}, err + } + yamlTc.Name = tcsName + + var buf bytes.Buffer + encoder := yamlLib.NewEncoder(&buf) + encoder.SetIndent(2) // Set indent to 2 spaces to match the original style + err = encoder.Encode(&yamlTc) + if err != nil { + return tcsInfo{name: tcsName, path: tcsPath}, err + } + data := buf.Bytes() + + _, err = yaml.FileExists(ctx, ts.logger, tcsPath, tcsName) + if err != nil { + utils.LogError(ts.logger, err, "failed to find yaml file", zap.String("path directory", tcsPath), zap.String("yaml", tcsName)) + return tcsInfo{name: tcsName, path: tcsPath}, err + } + + data = append([]byte(utils.GetVersionAsComment()), data...) + + err = yaml.WriteFile(ctx, ts.logger, tcsPath, tcsName, data, false) + if err != nil { + utils.LogError(ts.logger, err, "failed to write testcase yaml file") + return tcsInfo{name: tcsName, path: tcsPath}, err + } + + return tcsInfo{name: tcsName, path: tcsPath}, nil +} + +func (ts *TestYaml) DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error { + path := filepath.Join(ts.TcsPath, testSetID, "tests") + for _, testCaseID := range testCaseIDs { + err := yaml.DeleteFile(ctx, ts.logger, path, testCaseID) + if err != nil { + ts.logger.Error("failed to delete the testcase", zap.String("testcase id", testCaseID), zap.String("testset id", testSetID)) + return err + } + } + return nil +} + +func (ts *TestYaml) DeleteTestSet(ctx context.Context, testSetID string) error { + path := filepath.Join(ts.TcsPath, testSetID) + err := yaml.DeleteDir(ctx, ts.logger, path) + if err != nil { + ts.logger.Error("failed to delete the testset", zap.String("testset id", testSetID)) + return err + } + return nil +} +func (ts *TestYaml) ChangePath(path string) { + + ts.TcsPath = path +} + +func (ts *TestYaml) UpdateAssertions(ctx context.Context, testCaseID string, testSetID string, assertions map[models.AssertionType]interface{}) error { + // get the test case and fill the assertion and update the test case + tcsPath := filepath.Join(ts.TcsPath, testSetID, "tests") + data, err := yaml.ReadFile(ctx, ts.logger, tcsPath, testCaseID) + if err != nil { + utils.LogError(ts.logger, err, "failed to read the testcase from yaml") + return err + } + if len(data) == 0 { + ts.logger.Warn("skipping empty testcase", zap.String("testcase name", testCaseID)) + return nil + } + var testCase *yaml.NetworkTrafficDoc + + err = yamlLib.Unmarshal(data, &testCase) + if err != nil { + utils.LogError(ts.logger, err, "failed to unmarshall YAML data") + return err + } + + if testCase == nil { + ts.logger.Warn("skipping invalid testCase yaml", zap.String("testcase name", testCaseID)) + return nil + } + + tc, err := Decode(testCase, ts.logger) + if err != nil { + utils.LogError(ts.logger, err, "failed to decode the testcase") + return err + } + tc.Assertions = assertions + yamlTc, err := EncodeTestcase(*tc, ts.logger) + if err != nil { + utils.LogError(ts.logger, err, "failed to encode the testcase") + return err + } + yamlTc.Name = testCaseID + data, err = yamlLib.Marshal(&yamlTc) + if err != nil { + utils.LogError(ts.logger, err, "failed to marshall the testcase") + return err + } + err = yaml.WriteFile(ctx, ts.logger, tcsPath, testCaseID, data, false) + if err != nil { + utils.LogError(ts.logger, err, "failed to write testcase yaml file") + return err + } + return nil +} diff --git a/keploy/pkg/platform/yaml/testdb/util.go b/keploy/pkg/platform/yaml/testdb/util.go new file mode 100644 index 0000000..170f55d --- /dev/null +++ b/keploy/pkg/platform/yaml/testdb/util.go @@ -0,0 +1,384 @@ +package testdb + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "reflect" + "regexp" + "strconv" + "strings" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/yaml" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +func EncodeTestcase(tc models.TestCase, logger *zap.Logger) (*yaml.NetworkTrafficDoc, error) { + logger.Debug("Starting test case encoding", + zap.String("kind", string(tc.Kind)), + zap.String("name", tc.Name)) + + doc := &yaml.NetworkTrafficDoc{ + Version: tc.Version, + Kind: tc.Kind, + Name: tc.Name, + } + + var noise map[string][]string + switch tc.Kind { + case models.HTTP: + logger.Debug("Encoding HTTP test case") + doc.Curl = tc.Curl + + // find noisy fields only for HTTP responses + m, err := FlattenHTTPResponse(pkg.ToHTTPHeader(tc.HTTPResp.Header), tc.HTTPResp.Body) + if err != nil { + msg := "error in flattening http response" + utils.LogError(logger, err, msg) + } + noise = tc.Noise + + if tc.Name == "" { + noiseFieldsFound := FindNoisyFields(m, func(_ string, vals []string) bool { + // check if k is date + for _, v := range vals { + if pkg.IsTime(v) { + return true + } + } + // maybe we need to concatenate the values + return pkg.IsTime(strings.Join(vals, ", ")) + }) + + for _, v := range noiseFieldsFound { + noise[v] = []string{} + } + } + + httpSchema := models.HTTPSchema{ + Request: tc.HTTPReq, + Response: tc.HTTPResp, + Created: tc.Created, + // need to check here for type here as well as push in other custom assertions + Assertions: func() map[models.AssertionType]interface{} { + a := map[models.AssertionType]interface{}{} + for k, v := range tc.Assertions { + a[k] = v + } + + if len(noise) > 0 { + a[models.NoiseAssertion] = noise + } + + // Optionally add other custom assertions if needed here + // Example: + // a[models.StatusCode] = tc.HTTPResp.StatusCode + + return a + }(), + } + if tc.Description != "" { + httpSchema.Metadata = map[string]string{ + "description": tc.Description, + } + } + err = doc.Spec.Encode(httpSchema) + if err != nil { + utils.LogError(logger, err, "failed to encode testcase into a yaml doc") + return nil, err + } + case models.GRPC_EXPORT: + logger.Debug("Encoding gRPC test case") + // For gRPC, use the noise directly from the test case + noise = tc.Noise + + // Create a YAML node for the gRPC schema + grpcSpec := models.GrpcSpec{ + GrpcReq: tc.GrpcReq, + GrpcResp: tc.GrpcResp, + Created: tc.Created, + // need to check here for type here as well as push in other custom assertions + Assertions: func() map[models.AssertionType]interface{} { + a := map[models.AssertionType]interface{}{} + if len(noise) > 0 { + a[models.NoiseAssertion] = noise + } + // Optionally add other custom assertions if needed here + // Example: + // a[models.StatusCode] = tc.HTTPResp.StatusCode + + return a + }(), + } + + logger.Debug("gRPC schema created", + zap.Any("request_headers", grpcSpec.GrpcReq.Headers), + zap.Any("response_headers", grpcSpec.GrpcResp.Headers), + zap.Int("request_body_length", len(grpcSpec.GrpcReq.Body.DecodedData)), + zap.Int("response_body_length", len(grpcSpec.GrpcResp.Body.DecodedData))) + + // Create a new YAML node and encode the gRPC schema + var node yamlLib.Node + err := node.Encode(grpcSpec) + if err != nil { + utils.LogError(logger, err, "failed to encode gRPC schema to YAML node") + return nil, err + } + + // Set the node as the spec + doc.Spec = node + logger.Debug("Successfully encoded gRPC test case") + default: + utils.LogError(logger, nil, "failed to marshal the testcase into yaml due to invalid kind of testcase") + return nil, errors.New("type of testcases is invalid") + } + return doc, nil +} + +func FindNoisyFields(m map[string][]string, comparator func(string, []string) bool) []string { + var noise []string + for k, v := range m { + if comparator(k, v) { + noise = append(noise, k) + } + } + return noise +} + +func FlattenHTTPResponse(h http.Header, body string) (map[string][]string, error) { + m := map[string][]string{} + for k, v := range h { + m["header."+k] = []string{strings.Join(v, "")} + } + err := AddHTTPBodyToMap(body, m) + if err != nil { + return m, err + } + return m, nil +} + +func AddHTTPBodyToMap(body string, m map[string][]string) error { + // add body + if json.Valid([]byte(body)) { + var result interface{} + + err := json.Unmarshal([]byte(body), &result) + if err != nil { + return err + } + j := Flatten(result) + for k, v := range j { + nk := "body" + if k != "" { + nk = nk + "." + k + } + m[nk] = v + } + } else { + // add it as raw text + m["body"] = []string{body} + } + return nil +} + +// Flatten takes a map and returns a new one where nested maps are replaced +// by dot-delimited keys. +// examples of valid jsons - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#examples +func Flatten(j interface{}) map[string][]string { + if j == nil { + return map[string][]string{"": {""}} + } + o := make(map[string][]string) + x := reflect.ValueOf(j) + switch x.Kind() { + case reflect.Map: + m, ok := j.(map[string]interface{}) + if !ok { + return map[string][]string{} + } + for k, v := range m { + nm := Flatten(v) + for nk, nv := range nm { + fk := k + if nk != "" { + fk = fk + "." + nk + } + o[fk] = nv + } + } + case reflect.Bool: + o[""] = []string{strconv.FormatBool(x.Bool())} + case reflect.Float64: + o[""] = []string{strconv.FormatFloat(x.Float(), 'E', -1, 64)} + case reflect.String: + o[""] = []string{x.String()} + case reflect.Slice: + child, ok := j.([]interface{}) + if !ok { + return map[string][]string{} + } + for _, av := range child { + nm := Flatten(av) + for nk, nv := range nm { + if ov, exists := o[nk]; exists { + o[nk] = append(ov, nv...) + } else { + o[nk] = nv + } + } + } + default: + fmt.Println(utils.Emoji, "found invalid value in json", j, x.Kind()) + } + return o +} + +func ContainsMatchingURL(urlMethods []string, urlStr string, requestURL string, requestMethod models.Method) (bool, error) { + urlMatched := false + parsedURL, err := url.Parse(requestURL) + if err != nil { + return false, err + } + + // Check for URL path and method + regex, err := regexp.Compile(urlStr) + if err != nil { + return false, err + } + + urlMatch := regex.MatchString(parsedURL.Path) + + if urlMatch && len(urlStr) != 0 { + urlMatched = true + } + + if len(urlMethods) != 0 && urlMatched { + urlMatched = false + for _, method := range urlMethods { + if string(method) == string(requestMethod) { + urlMatched = true + } + } + } + + return urlMatched, nil +} + +func HasBannedHeaders(object map[string]string, bannedHeaders map[string]string) (bool, error) { + for headerName, headerNameValue := range object { + for bannedHeaderName, bannedHeaderValue := range bannedHeaders { + regex, err := regexp.Compile(headerName) + if err != nil { + return false, err + } + + headerNameMatch := regex.MatchString(bannedHeaderName) + regex, err = regexp.Compile(bannedHeaderValue) + if err != nil { + return false, err + } + headerValueMatch := regex.MatchString(headerNameValue) + if headerNameMatch && headerValueMatch { + return true, nil + } + } + } + return false, nil +} + +func Decode(yamlTestcase *yaml.NetworkTrafficDoc, logger *zap.Logger) (*models.TestCase, error) { + tc := &models.TestCase{ + Version: yamlTestcase.Version, + Kind: yamlTestcase.Kind, + Name: yamlTestcase.Name, + Curl: yamlTestcase.Curl, + Noise: make(map[string][]string), + Assertions: make(map[models.AssertionType]interface{}), + } + + switch tc.Kind { + case models.HTTP: + + var httpSpec models.HTTPSchema + if err := yamlTestcase.Spec.Decode(&httpSpec); err != nil { + utils.LogError(logger, err, "failed to decode HTTP JSON spec") + return nil, err + } + tc.Created = httpSpec.Created + tc.HTTPReq = httpSpec.Request + tc.HTTPResp = httpSpec.Response + tc.Description = httpSpec.Metadata["description"] + + // single map-based loop for all assertions + for key, raw := range httpSpec.Assertions { + tc.Assertions[key] = raw + if key == models.NoiseAssertion { + noiseMap, ok := raw.(map[models.AssertionType]interface{}) + if !ok { + logger.Warn("noise assertion not in expected map[AssertionType]interface{}", zap.Any("raw", raw)) + continue + } + for kt, inner := range noiseMap { + field := string(kt) + // initialize slice + tc.Noise[field] = []string{} + arr, ok := inner.([]interface{}) + if !ok { + continue + } + for _, item := range arr { + if s, ok2 := item.(string); ok2 && s != "" { + tc.Noise[field] = append(tc.Noise[field], s) + } + } + } + } + } + + case models.GRPC_EXPORT: + var grpcSpec models.GrpcSpec + if err := yamlTestcase.Spec.Decode(&grpcSpec); err != nil { + utils.LogError(logger, err, "failed to decode gRPC spec") + return nil, err + } + tc.Created = grpcSpec.Created + tc.GrpcReq = grpcSpec.GrpcReq + tc.GrpcResp = grpcSpec.GrpcResp + + for key, raw := range grpcSpec.Assertions { + tc.Assertions[key] = raw + if key == models.NoiseAssertion { + noiseMap, ok := raw.(map[models.AssertionType]interface{}) + if !ok { + logger.Warn("noise assertion not in expected map[AssertionType]interface{}", zap.Any("raw", raw)) + continue + } + for kt, inner := range noiseMap { + field := string(kt) + tc.Noise[field] = []string{} + arr, ok := inner.([]interface{}) + if !ok { + continue + } + for _, item := range arr { + if s, ok2 := item.(string); ok2 && s != "" { + tc.Noise[field] = append(tc.Noise[field], s) + } + } + } + } + } + + default: + utils.LogError(logger, nil, "invalid testcase kind", zap.String("kind", string(tc.Kind))) + return nil, errors.New("invalid testcase kind") + } + + return tc, nil +} diff --git a/keploy/pkg/platform/yaml/utils.go b/keploy/pkg/platform/yaml/utils.go new file mode 100755 index 0000000..cab8bc8 --- /dev/null +++ b/keploy/pkg/platform/yaml/utils.go @@ -0,0 +1,403 @@ +// Package yaml provides utility functions for working with YAML files. +package yaml + +import ( + "context" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "path/filepath" + + "strconv" + "strings" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func CompareHeaders(h1 http.Header, h2 http.Header, res *[]models.HeaderResult, noise map[string]string) bool { + if res == nil { + return false + } + match := true + _, isHeaderNoisy := noise["header"] + for k, v := range h1 { + _, isNoisy := noise[k] + isNoisy = isNoisy || isHeaderNoisy + val, ok := h2[k] + if !isNoisy { + if !ok { + if checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: false, + Expected: models.Header{ + Key: k, + Value: v, + }, + Actual: models.Header{ + Key: k, + Value: nil, + }, + }) + } + + match = false + continue + } + if len(v) != len(val) { + if checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: false, + Expected: models.Header{ + Key: k, + Value: v, + }, + Actual: models.Header{ + Key: k, + Value: val, + }, + }) + } + match = false + continue + } + for i, e := range v { + if val[i] != e { + if checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: false, + Expected: models.Header{ + Key: k, + Value: v, + }, + Actual: models.Header{ + Key: k, + Value: val, + }, + }) + } + match = false + continue + } + } + } + if checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: true, + Expected: models.Header{ + Key: k, + Value: v, + }, + Actual: models.Header{ + Key: k, + Value: val, + }, + }) + } + } + for k, v := range h2 { + _, isNoisy := noise[k] + isNoisy = isNoisy || isHeaderNoisy + val, ok := h1[k] + if isNoisy && checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: true, + Expected: models.Header{ + Key: k, + Value: val, + }, + Actual: models.Header{ + Key: k, + Value: v, + }, + }) + continue + } + if !ok { + if checkKey(res, k) { + *res = append(*res, models.HeaderResult{ + Normal: false, + Expected: models.Header{ + Key: k, + Value: nil, + }, + Actual: models.Header{ + Key: k, + Value: v, + }, + }) + } + + match = false + } + } + return match +} + +func checkKey(res *[]models.HeaderResult, key string) bool { + for _, v := range *res { + if key == v.Expected.Key { + return false + } + } + return true +} + +func Contains(elems []string, v string) bool { + for _, s := range elems { + if v == s { + return true + } + } + return false +} + +func NewSessionIndex(path string, Logger *zap.Logger) (string, error) { + indx := 0 + dir, err := ReadDir(path, fs.FileMode(os.O_RDONLY)) + if err != nil { + Logger.Debug("creating a folder for the keploy generated testcases", zap.Error(err)) + return fmt.Sprintf("%s%v", models.TestSetPattern, indx), nil + } + + files, err := dir.ReadDir(0) + if err != nil { + return "", err + } + + for _, v := range files { + // fmt.Println("name for the file", v.Name()) + fileName := filepath.Base(v.Name()) + fileNamePackets := strings.Split(fileName, "-") + if len(fileNamePackets) == 3 { + fileIndx, err := strconv.Atoi(fileNamePackets[2]) + if err != nil { + Logger.Debug("failed to convert the index string to integer", zap.Error(err)) + continue + } + if indx < fileIndx+1 { + indx = fileIndx + 1 + } + } + } + return fmt.Sprintf("%s%v", models.TestSetPattern, indx), nil +} + +func ValidatePath(path string) (string, error) { + // Validate the input to prevent directory traversal attack + if strings.Contains(path, "..") { + return "", errors.New("invalid path: contains '..' indicating directory traversal") + } + return path, nil +} + +// FindLastIndex returns the index for the new yaml file by reading the yaml file names in the given path directory +func FindLastIndex(path string, _ *zap.Logger) (int, error) { + dir, err := ReadDir(path, fs.FileMode(os.O_RDONLY)) + if err != nil { + return 1, nil + } + files, err := dir.ReadDir(0) + if err != nil { + return 1, nil + } + + lastIndex := 0 + for _, v := range files { + if v.Name() == "mocks.yaml" || v.Name() == "config.yaml" { + continue + } + fileName := filepath.Base(v.Name()) + fileNameWithoutExt := fileName[:len(fileName)-len(filepath.Ext(fileName))] + fileNameParts := strings.Split(fileNameWithoutExt, "-") + if len(fileNameParts) != 2 || (fileNameParts[0] != "test" && fileNameParts[0] != "report") { + continue + } + indxStr := fileNameParts[1] + indx, err := strconv.Atoi(indxStr) + if err != nil { + continue + } + if indx > lastIndex { + lastIndex = indx + } + } + lastIndex++ + + return lastIndex, nil +} + +func ReadDir(path string, fileMode fs.FileMode) (*os.File, error) { + dir, err := os.OpenFile(path, os.O_RDONLY, fileMode) + if err != nil { + return nil, err + } + return dir, nil +} + +// CreateDir to create a directory if it doesn't exist +func CreateDir(path string, logger *zap.Logger) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + err := os.MkdirAll(path, os.ModePerm) + if err != nil { + utils.LogError(logger, err, "failed to create directory", zap.String("directory", path)) + return err + } + } + return nil +} + +// ReadYAMLFile to read and parse YAML file +func ReadYAMLFile(ctx context.Context, logger *zap.Logger, filePath string, fileName string, v interface{}, extType bool) error { + if !extType { + filePath = filepath.Join(filePath, fileName+".yml") + + } else { + filePath = filepath.Join(filePath, fileName+".yaml") + } + file, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("failed to read the file: %v", err) + } + + defer func() { + if err := file.Close(); err != nil { + utils.LogError(logger, err, "failed to close file", zap.String("file", filePath)) + } + }() + + cr := &ctxReader{ + ctx: ctx, + r: file, + } + + configData, err := io.ReadAll(cr) + if err != nil { + if err == ctx.Err() { + return err // Ignore context cancellation error + } + return fmt.Errorf("failed to read the file: %v", err) + } + + err = yaml.Unmarshal(configData, v) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal YAML", zap.String("file", filePath)) + return err + } + return nil +} + +// CopyFile copies a single file from src to dst +func CopyFile(src, dst string, rename bool, logger *zap.Logger) error { + srcFile, err := os.Open(src) + + if err != nil { + return err + } + defer func() { + if err := srcFile.Close(); err != nil { + utils.LogError(logger, err, "failed to close file", zap.String("file", srcFile.Name())) + } + }() + // If rename is true, generate a new name for the destination file + if rename { + dst = generateSchemaName(dst) + } + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer func() { + if err := dstFile.Close(); err != nil { + utils.LogError(logger, err, "failed to close file", zap.String("file", dstFile.Name())) + } + }() + + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err + } + + // Ensure the copied file has the same permissions as the original file + srcInfo, err := os.Stat(src) + if err != nil { + return err + } + err = os.Chmod(dst, srcInfo.Mode()) + if err != nil { + return err + } + + return nil +} + +// CopyDir recursively copies a directory tree, attempting to preserve permissions +func CopyDir(srcDir, destDir string, rename bool, logger *zap.Logger) error { + // Ensure the destination directory exists + if _, err := os.Stat(destDir); os.IsNotExist(err) { + err := os.MkdirAll(destDir, os.ModePerm) + if err != nil { + return err + } + } + entries, err := os.ReadDir(srcDir) + if err != nil { + return err + } + + for _, entry := range entries { + srcPath := filepath.Join(srcDir, entry.Name()) + destPath := filepath.Join(destDir, entry.Name()) + + info, err := entry.Info() + if err != nil { + return err + } + + if info.IsDir() { + err = os.MkdirAll(destPath, info.Mode()) + if err != nil { + return err + } + err = CopyDir(srcPath, destPath, rename, logger) + if err != nil { + return err + } + } else { + err = CopyFile(srcPath, destPath, rename, logger) + if err != nil { + return err + } + } + } + return nil +} + +// generateSchemaName generates a new schema name +func generateSchemaName(src string) string { + dir := filepath.Dir(src) + newName := "schema" + filepath.Ext(src) + return filepath.Join(dir, newName) +} + +func FileExists(_ context.Context, logger *zap.Logger, path string, fileName string) (bool, error) { + yamlPath, err := ValidatePath(filepath.Join(path, fileName+".yaml")) + if err != nil { + utils.LogError(logger, err, "failed to validate the yaml file path", zap.String("path directory", path), zap.String("yaml", fileName)) + return false, err + } + if _, err := os.Stat(yamlPath); err != nil { + if os.IsNotExist(err) { + return false, nil + } + utils.LogError(logger, err, "failed to check if the yaml file exists", zap.String("path directory", path), zap.String("yaml", fileName)) + return false, err + } + + return true, nil +} diff --git a/keploy/pkg/platform/yaml/yaml.go b/keploy/pkg/platform/yaml/yaml.go new file mode 100755 index 0000000..b1dc314 --- /dev/null +++ b/keploy/pkg/platform/yaml/yaml.go @@ -0,0 +1,232 @@ +package yaml + +import ( + "context" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +type IndexMode string + +const ( + ModeDir IndexMode = "dir" + ModeFile IndexMode = "file" +) + +// Ignored folders +const ( + FolderReports = "reports" + FolderTestReports = "testReports" + FolderSchema = "schema" +) + +// NetworkTrafficDoc stores the request-response data of a network call (ingress or egress) +type NetworkTrafficDoc struct { + Version models.Version `json:"version" yaml:"version"` + Kind models.Kind `json:"kind" yaml:"kind"` + Name string `json:"name" yaml:"name"` + Spec yamlLib.Node `json:"spec" yaml:"spec"` + Curl string `json:"curl" yaml:"curl,omitempty"` + ConnectionID string `json:"connectionId" yaml:"connectionId,omitempty"` +} + +// ctxReader wraps an io.Reader with a context for cancellation support +type ctxReader struct { + ctx context.Context + r io.Reader +} + +func (cr *ctxReader) Read(p []byte) (n int, err error) { + select { + case <-cr.ctx.Done(): + return 0, cr.ctx.Err() + default: + return cr.r.Read(p) + } +} + +// ctxWriter wraps an io.Writer with a context for cancellation support +type ctxWriter struct { + ctx context.Context + writer io.Writer +} + +func (cw *ctxWriter) Write(p []byte) (n int, err error) { + for len(p) > 0 { + var written int + written, err = cw.writer.Write(p) + n += written + if err != nil { + return n, err + } + p = p[written:] + } + return n, nil +} + +func WriteFile(ctx context.Context, logger *zap.Logger, path, fileName string, docData []byte, isAppend bool) error { + isFileEmpty, err := CreateYamlFile(ctx, logger, path, fileName) + if err != nil { + utils.LogError(logger, err, "failed to create a yaml file", zap.String("path directory", path), zap.String("yaml", fileName)) + return err + } + flag := os.O_WRONLY | os.O_TRUNC + if isAppend { + data := []byte("---\n") + if isFileEmpty { + data = []byte{} + } + docData = append(data, docData...) + flag = os.O_WRONLY | os.O_APPEND + } + yamlPath := filepath.Join(path, fileName+".yaml") + file, err := os.OpenFile(yamlPath, flag, fs.ModePerm) + if err != nil { + utils.LogError(logger, err, "failed to open file for writing", zap.String("file", yamlPath)) + return err + } + defer func() { + if err := file.Close(); err != nil { + utils.LogError(logger, err, "failed to close file", zap.String("file", yamlPath)) + } + }() + + cw := &ctxWriter{ + ctx: ctx, + writer: file, + } + + _, err = cw.Write(docData) + if err != nil { + if err == ctx.Err() { + return nil // Ignore context cancellation error + } + utils.LogError(logger, err, "failed to write the yaml document", zap.String("yaml file name", fileName)) + return err + } + return nil +} + +func ReadFile(ctx context.Context, logger *zap.Logger, path, name string) ([]byte, error) { + filePath := filepath.Join(path, name+".yaml") + file, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("failed to read the file: %v", err) + } + + defer func() { + if err := file.Close(); err != nil { + utils.LogError(logger, err, "failed to close file", zap.String("file", filePath)) + } + }() + + cr := &ctxReader{ + ctx: ctx, + r: file, + } + + data, err := io.ReadAll(cr) + if err != nil { + if err == ctx.Err() { + return nil, err // Ignore context cancellation error + } + return nil, fmt.Errorf("failed to read the file: %v", err) + } + return data, nil +} + +func CreateYamlFile(ctx context.Context, Logger *zap.Logger, path string, fileName string) (bool, error) { + yamlPath, err := ValidatePath(filepath.Join(path, fileName+".yaml")) + if err != nil { + utils.LogError(Logger, err, "failed to validate the yaml file path", zap.String("path directory", path), zap.String("yaml", fileName)) + return false, err + } + + if _, err := os.Stat(yamlPath); err != nil { + if ctx.Err() == nil || ctx.Err() == context.Canceled { + err = os.MkdirAll(filepath.Join(path), 0777) + if err != nil { + utils.LogError(Logger, err, "failed to create a directory for the yaml file", zap.String("path directory", path), zap.String("yaml", fileName)) + return false, err + } + file, err := os.OpenFile(yamlPath, os.O_CREATE, 0777) // Set file permissions to 777 + if err != nil { + utils.LogError(Logger, err, "failed to create a yaml file", zap.String("path directory", path), zap.String("yaml", fileName)) + return false, err + } + err = file.Close() + if err != nil { + utils.LogError(Logger, err, "failed to close the yaml file", zap.String("path directory", path), zap.String("yaml", fileName)) + return false, err + } + return true, nil + } + return false, err + } + return false, nil +} + +func ReadSessionIndices(ctx context.Context, path string, logger *zap.Logger, mode IndexMode) ([]string, error) { + var indices []string + + dir, err := ReadDir(path, fs.FileMode(os.O_RDONLY)) + if err != nil { + logger.Debug("creating a folder for the keploy generated testcases", zap.Error(err)) + return indices, nil + } + + files, err := dir.ReadDir(0) + if err != nil { + return indices, err + } + + for _, v := range files { + // Skip ignored folders + if v.Name() == FolderReports || v.Name() == FolderTestReports || v.Name() == FolderSchema { + continue + } + + name := v.Name() + + switch mode { + case ModeDir: + if v.IsDir() { + indices = append(indices, name) + } + case ModeFile: + if ext := filepath.Ext(name); ext != "" { + name = name[:len(name)-len(ext)] + } + indices = append(indices, name) + } + } + + return indices, nil +} + +func DeleteFile(_ context.Context, logger *zap.Logger, path, name string) error { + filePath := filepath.Join(path, name+".yaml") + err := os.Remove(filePath) + if err != nil { + utils.LogError(logger, err, "failed to delete the file", zap.String("file", filePath)) + return fmt.Errorf("failed to delete the file: %v", err) + } + return nil +} + +func DeleteDir(_ context.Context, logger *zap.Logger, path string) error { + err := os.RemoveAll(path) + if err != nil { + utils.LogError(logger, err, "failed to delete the directory", zap.String("path", path)) + return fmt.Errorf("failed to delete the directory: %v", err) + } + return nil +} diff --git a/keploy/pkg/service/README.md b/keploy/pkg/service/README.md new file mode 100755 index 0000000..26a04d6 --- /dev/null +++ b/keploy/pkg/service/README.md @@ -0,0 +1,5 @@ +# Service Package Documentation + +This package focuses on encapsulating core business operations in a +maintainable and reusable manner. This package calls the `platform` interface methods +to store the output of the service methods. \ No newline at end of file diff --git a/keploy/pkg/service/contract/consumer/consumer.go b/keploy/pkg/service/contract/consumer/consumer.go new file mode 100644 index 0000000..21cb307 --- /dev/null +++ b/keploy/pkg/service/contract/consumer/consumer.go @@ -0,0 +1,298 @@ +// Package consumer is a package for consumer driven contract testing +package consumer + +import ( + "fmt" + "os" + + "github.com/fatih/color" + "github.com/olekukonko/tablewriter" + "go.keploy.io/server/v2/config" + schemaMatcher "go.keploy.io/server/v2/pkg/matcher/schema" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type consumer struct { + logger *zap.Logger + config *config.Config +} + +// New creates a new instance of the consumer service +func New(logger *zap.Logger, config *config.Config) Service { + return &consumer{ + logger: logger, + config: config, + } +} +func (s *consumer) ValidateSchema(testsMapping map[string]map[string]*models.OpenAPI, mocksMapping []models.MockMapping) error { + + // Retrieve mocks and calculate scores for each service + scores, err := s.getMockScores(testsMapping, mocksMapping) + if err != nil { + return err + } + // Compare the scores and generate a summary + summary, err := s.ValidateMockAgainstTests(scores, testsMapping) + if err != nil { + return err + } + // Print the summary + generateSummaryTable(summary) + + return nil +} + +// getMockScores retrieves mocks and compares them with test cases, calculating scores. +func (s *consumer) getMockScores(testsMapping map[string]map[string]*models.OpenAPI, mocksMapping []models.MockMapping) (map[string]map[string]map[string]models.SchemaInfo, error) { + + // Initialize a map to store the scores for each service, mock set, and mock. + scores := make(map[string]map[string]map[string]models.SchemaInfo) + + for _, mapping := range mocksMapping { + // Initialize the service entry in the scores map if it doesn't already exist. + if scores[mapping.Service] == nil { + scores[mapping.Service] = make(map[string]map[string]models.SchemaInfo) + } + // Initialize the mock set entry if it hasn't been initialized yet. + if scores[mapping.Service][mapping.TestSetID] == nil { + scores[mapping.Service][mapping.TestSetID] = make(map[string]models.SchemaInfo) + } + + // Compare the mocks with test cases and calculate scores. + // The result is stored in the scores map under the respective service and mock set ID. + s.scoresForMocks(mapping.Mocks, scores[mapping.Service][mapping.TestSetID], testsMapping, mapping.TestSetID) + + } + + // Return the calculated scores. + return scores, nil +} + +// scoresForMocks compares mocks to test cases and assigns scores. +func (s *consumer) scoresForMocks(mocks []*models.OpenAPI, mockSet map[string]models.SchemaInfo, testsMapping map[string]map[string]*models.OpenAPI, mockSetID string) { + // Ensure mockSet is initialized before assigning + if mockSet == nil { + mockSet = make(map[string]models.SchemaInfo) + } + // Loop through each mock in the provided list of mocks. + for _, mock := range mocks { + // Initialize the mock's score to 0.0 and store the mock's data in the mockSet map. + // 'mockSet' is a map where the key is the mock title and the value is the SchemaInfo structure containing score and data. + mockSet[mock.Info.Title] = models.SchemaInfo{ + Score: 0.0, + Data: *mock, // Store the mock data here. + } + + // Loop through each test set (testSetID) in the testsMapping. + // testsMapping maps test set IDs to test case titles. + for testSetID, tests := range testsMapping { + // Loop through each test in the current test set. + for _, test := range tests { + // Call 'match2' to compare the mock with the current test. + // This function returns a candidateScore (how well the mock matches the test) and a pass boolean. + candidateScore, pass, err := schemaMatcher.Match(*mock, *test, testSetID, mockSetID, s.logger, models.IdentifyMode) + // Handle any errors encountered during the comparison process. + if err != nil { + // Log the error and continue with the next iteration, skipping the current comparison. + utils.LogError(s.logger, err, "Error in matching the two models") + continue + } + + // If the mock passed the comparison and the candidate score is greater than the current score: + if pass && candidateScore > mockSet[mock.Info.Title].Score { + // Update the mock's score and store the test case information in the mockSet. + // This keeps track of the best matching test case for the current mock. + mockSet[mock.Info.Title] = models.SchemaInfo{ + Service: "", // Optional: could store service info if needed. + TestSetID: testSetID, // Store the test set ID that provided the highest score. + Name: test.Info.Title, // Store the test case name (title). + Score: candidateScore, // Update the score with the highest candidate score. + Data: *mock, // Store the mock data. + } + } + } + } + } +} + +// ValidateMockAgainstTests compares mock results with test cases and generates a summary report +func (s *consumer) ValidateMockAgainstTests(scores map[string]map[string]map[string]models.SchemaInfo, testsMapping map[string]map[string]*models.OpenAPI) (models.Summary, error) { + var summary models.Summary + + // Defining color schemes for success, failure, and other statuses + notMatchedColor := color.New(color.FgHiRed).SprintFunc() + missedColor := color.New(color.FgHiYellow).SprintFunc() + successColor := color.New(color.FgHiGreen).SprintFunc() + serviceColor := color.New(color.FgHiBlue).SprintFunc() + + // Loop through the services in the scores map + // Each "service" represents a consumer service being validated + for service, mockSetIDs := range scores { + // Create a new service summary for each service + var serviceSummary models.ServiceSummary + serviceSummary.TestSets = make(map[string]models.Status) + serviceSummary.Service = service // Store the service name + + // Output the beginning of the validation for the current service + fmt.Println("==========================================") + fmt.Print("Starting Validation for Consumer Service: ") + fmt.Print(serviceColor(service)) // Print service name in blue + fmt.Println(" ....") + fmt.Println("==========================================") + + // Iterate over the mockSetIDs for each service (mock set contains multiple mocks) + for mockSetID, mockTest := range mockSetIDs { + if _, ok := serviceSummary.TestSets[mockSetID]; !ok { + // Initialize the Status struct if it doesn't already exist for the mockSetID + serviceSummary.TestSets[mockSetID] = models.Status{} + } + + // Iterate over each mock in the mockTest map + for _, mockInfo := range mockTest { + + // Print validation information only if the score is not zero + if mockInfo.Score != 0.0 { + fmt.Print("Validating '") + fmt.Print(serviceColor(service)) // Print the service name in blue + fmt.Printf("': (%s)/%s for (%s)/%s\n", mockSetID, mockInfo.Data.Info.Title, mockInfo.TestSetID, mockInfo.Name) + + } + + // Case 1: If the score is 1.0, the mock passed the validation + if mockInfo.Score == 1.0 { + // Retrieve the Status struct for the given mockSetID + status := serviceSummary.TestSets[mockSetID] + + // Append the passed mock title + status.Passed = append(status.Passed, mockInfo.Data.Info.Title) + + // Reassign the updated status back to the map + serviceSummary.TestSets[mockSetID] = status + serviceSummary.PassedCount++ // Increment the passed count + + // Print a success message in green + fmt.Print("Contract check ") + fmt.Print(successColor("passed")) // Print "passed" in green + fmt.Printf(" for the test '%s' / mock '%s'\n", mockInfo.Name, mockInfo.Data.Info.Title) + fmt.Println("--------------------------------------------------------------------") + // Case 2: If the score is between 0 and 1.0, the mock failed the validation + } else if mockInfo.Score > 0.0 { + // Retrieve the Status struct for the given mockSetID + status := serviceSummary.TestSets[mockSetID] + + // Append the failed mock title + status.Failed = append(status.Failed, mockInfo.Data.Info.Title) + + // Reassign the updated status back to the map + serviceSummary.TestSets[mockSetID] = status + serviceSummary.FailedCount++ // Increment the failed count + + // Print a failure message in red + fmt.Print("Contract check") + fmt.Print(notMatchedColor(" failed")) // Print "failed" in red + fmt.Printf(" for the test '%s' / mock '%s'\n", mockInfo.Name, mockInfo.Data.Info.Title) + + fmt.Println() + + // Additional information: Print consumer and current service comparison + fmt.Printf(" Current %s || Consumer %s\n", serviceColor(s.config.Contract.Mappings.Self), serviceColor(service)) + + // Perform comparison between the mock and test case again + _, _, err := schemaMatcher.Match(mockInfo.Data, *testsMapping[mockInfo.TestSetID][mockInfo.Name], mockInfo.TestSetID, mockSetID, s.logger, models.CompareMode) + if err != nil { + // If an error occurs during comparison, return it + utils.LogError(s.logger, err, "Error in matching the two models") + return models.Summary{}, err + } + + // Case 3: If the score is 0.0, there was no matching test case found + } else if mockInfo.Score == 0.0 { + // Retrieve the Status struct for the given mockSetID + status := serviceSummary.TestSets[mockSetID] + + // Append the missed mock title + status.Missed = append(status.Missed, mockInfo.Data.Info.Title) + + // Reassign the updated status back to the map + serviceSummary.TestSets[mockSetID] = status + serviceSummary.MissedCount++ // Increment the missed count + + // Print a "missed" message in yellow + fmt.Println(missedColor(fmt.Sprintf("No ideal test case found for the (%s)/'%s'", mockSetID, mockInfo.Data.Info.Title))) + fmt.Println("--------------------------------------------------------------------") + } + } + } + + // Append the completed service summary to the overall summary + summary.ServicesSummary = append(summary.ServicesSummary, serviceSummary) + } + + // Return the overall summary containing details of all services validated + return summary, nil +} + +func generateSummaryTable(summary models.Summary) { + notMatchedColor := color.New(color.FgHiRed).SprintFunc() + missedColor := color.New(color.FgHiYellow).SprintFunc() + successColor := color.New(color.FgHiGreen).SprintFunc() + serviceColor := color.New(color.FgHiBlue).SprintFunc() + + // Create a new tablewriter to format the output as a table + table := tablewriter.NewWriter(os.Stdout) + + // Set table headers + table.SetHeader([]string{"Consumer Service", "Consumer Service Test-set", "Mock-name", "Failed", "Passed", "Missed"}) + table.SetAlignment(tablewriter.ALIGN_CENTER) + table.SetAutoMergeCells(true) + // Loop through each service summary to populate the table + for idx, serviceSummary := range summary.ServicesSummary { + failedCount := serviceSummary.FailedCount + passedCount := serviceSummary.PassedCount + missedCount := serviceSummary.MissedCount + table.Append([]string{ + serviceColor(serviceSummary.Service), + "", + "", + notMatchedColor(failedCount), + successColor(passedCount), + missedColor(missedCount), + }) + for testSet, status := range serviceSummary.TestSets { + for _, mock := range status.Failed { + // Add rows for failed mocks + table.Append([]string{ + "", + testSet, + notMatchedColor(mock), + "", + "", "", + }) + } + + for _, mock := range status.Missed { + table.Append([]string{ + "", + testSet, + missedColor(mock), "", + "", "", + }) + } + table.Append([]string{ + "", + "", + "", "", + "", "", + }) + } + // Add a blank line (or border) after each service + if idx < len(summary.ServicesSummary)-1 { + table.Append([]string{"----------------", "----------------", "----------------", "----------------", "----------------", "----------------"}) + } + } + + // Render the table to stdout + table.Render() +} diff --git a/keploy/pkg/service/contract/consumer/service.go b/keploy/pkg/service/contract/consumer/service.go new file mode 100644 index 0000000..4453b0c --- /dev/null +++ b/keploy/pkg/service/contract/consumer/service.go @@ -0,0 +1,8 @@ +package consumer + +import "go.keploy.io/server/v2/pkg/models" + +// Service defines the consumer service interface +type Service interface { + ValidateSchema(testsMapping map[string]map[string]*models.OpenAPI, mocksMapping []models.MockMapping) error +} diff --git a/keploy/pkg/service/contract/contract.go b/keploy/pkg/service/contract/contract.go new file mode 100644 index 0000000..bec2603 --- /dev/null +++ b/keploy/pkg/service/contract/contract.go @@ -0,0 +1,685 @@ +// Package contract provides the implementation of the contract service +package contract + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/fatih/color" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/yaml" + "go.keploy.io/server/v2/pkg/service/contract/consumer" + "go.keploy.io/server/v2/pkg/service/contract/provider" + "go.keploy.io/server/v2/utils" + + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +// contractService implements the Service interface +type contract struct { + logger *zap.Logger + testDB TestDB + mockDB MockDB + openAPIDB OpenAPIDB + config *config.Config + consumer consumer.Service + provider provider.Service +} + +func New(logger *zap.Logger, testDB TestDB, mockDB MockDB, openAPIDB OpenAPIDB, config *config.Config) Service { + return &contract{ + logger: logger, + testDB: testDB, + mockDB: mockDB, + openAPIDB: openAPIDB, + config: config, + consumer: consumer.New(logger, config), + provider: provider.New(logger, config), + } +} + +func (s *contract) HTTPDocToOpenAPI(logger *zap.Logger, custom models.HTTPDoc) (models.OpenAPI, error) { + + var err error + // Convert response body to an object + var responseBodyObject map[string]interface{} + if custom.Spec.Response.Body != "" { + err := json.Unmarshal([]byte(custom.Spec.Response.Body), &responseBodyObject) + if err != nil { + return models.OpenAPI{}, err + } + + } + + // Get the type of each value in the response body object + responseTypes := ExtractVariableTypes(responseBodyObject) + + // Convert request body to an object + var requestBodyObject map[string]interface{} + if custom.Spec.Request.Body != "" { + err := json.Unmarshal([]byte(custom.Spec.Request.Body), &requestBodyObject) + if err != nil { + return models.OpenAPI{}, err + } + + } + + // Get the type of each value in the request body object + requestTypes := ExtractVariableTypes(requestBodyObject) + + // Generate response by status code + byCode := GenerateResponse(Response{ + Code: custom.Spec.Response.StatusCode, + Message: custom.Spec.Response.StatusMessage, + Types: responseTypes, + Body: responseBodyObject, + }) + + // Add parameters to the request + parameters := GenerateHeader(custom.Spec.Request.Header) + + // Extract In Path parameters + identifiers := ExtractIdentifiers(custom.Spec.Request.URL) + // Generate Dummy Names for the identifiers + dummyNames := GenerateDummyNamesForIdentifiers(identifiers) + // Add In Path parameters to the parameters + if len(identifiers) > 0 { + parameters = AppendInParameters(parameters, dummyNames, "path") + } + // Extract Query parameters + queryParams, err := ExtractQueryParams(custom.Spec.Request.URL) + if err != nil { + utils.LogError(logger, err, "failed to extract query parameters") + return models.OpenAPI{}, err + } + // Add Query parameters to the parameters + if len(queryParams) > 0 { + parameters = AppendInParameters(parameters, queryParams, "query") + } + // Generate Operation ID + operationID := generateUniqueID() + if operationID == "" { + err := fmt.Errorf("failed to generate unique ID") + utils.LogError(logger, err, "failed to generate unique ID") + return models.OpenAPI{}, err + } + // Determine if the request method is GET or POST + var pathItem models.PathItem + switch custom.Spec.Request.Method { + case "GET": + pathItem = models.PathItem{ + Get: &models.Operation{ + Summary: "Auto-generated operation", + Description: "Auto-generated from custom format", + OperationID: operationID, + Parameters: parameters, + Responses: byCode, + }, + } + case "POST": + pathItem = models.PathItem{ + Post: &models.Operation{ + Summary: "Auto-generated operation", + Description: "Auto-generated from custom format", + Parameters: parameters, + OperationID: operationID, + RequestBody: &models.RequestBody{ + Content: map[string]models.MediaType{ + "application/json": { + Schema: models.Schema{ + Type: "object", + Properties: requestTypes, + }, + Example: requestBodyObject, + }, + }, + }, + Responses: byCode, + }, + } + case "PUT": + pathItem.Put = &models.Operation{ + Summary: "Update an employee by ID", + Description: "Update an employee by ID", + Parameters: parameters, + OperationID: operationID, + RequestBody: &models.RequestBody{ + Content: map[string]models.MediaType{ + "application/json": { + Schema: models.Schema{ + Type: "object", + Properties: requestTypes, + }, + Example: (requestBodyObject), + }, + }, + }, + Responses: byCode, + } + case "PATCH": + pathItem.Patch = &models.Operation{ + Summary: "Auto-generated operation", + Description: "Auto-generated from custom format", + Parameters: parameters, + OperationID: operationID, + RequestBody: &models.RequestBody{ + Content: map[string]models.MediaType{ + "application/json": { + Schema: models.Schema{ + Type: "object", + Properties: requestTypes, + }, + Example: (requestBodyObject), + }, + }, + }, + Responses: byCode, + } + case "DELETE": + pathItem.Delete = &models.Operation{ + Summary: "Delete an employee by ID", + Description: "Delete an employee by ID", + OperationID: operationID, + Parameters: parameters, + Responses: byCode, + } + default: + utils.LogError(logger, err, "Unsupported Method") + return models.OpenAPI{}, err + } + + // Extract the URL path + parsedURL, hostName := ExtractURLPath(custom.Spec.Request.URL) + if parsedURL == "" { + utils.LogError(logger, err, "failed to extract URL path") + return models.OpenAPI{}, err + } + // Replace numeric identifiers in the path with dummy names (if exists) + parsedURL = ReplacePathIdentifiers(parsedURL, dummyNames) + //If it's mock so there is no hostname so put it temp + if hostName == "" { + hostName = "temp" + } + // Convert to OpenAPI format + openapi := models.OpenAPI{ + OpenAPI: "3.0.0", + Info: models.Info{ + Title: custom.Name, + Version: custom.Version, + Description: custom.Kind, + }, + Servers: []map[string]string{ + { + "url": hostName, + }, + }, + + Paths: map[string]models.PathItem{ + parsedURL: pathItem, + }, + Components: map[string]interface{}{}, + } + + return openapi, nil +} + +// GenerateMocksSchemas generates mock schemas based on the provided services and mappings. +func (s *contract) GenerateMocksSchemas(ctx context.Context, services []string, mappings map[string][]string) error { + + // Retrieve all test set IDs from the test database. + testSetsIDs, err := s.testDB.GetAllTestSetIDs(ctx) + if err != nil { + // Log and return error if test set IDs retrieval fails. + utils.LogError(s.logger, err, "failed to get test set IDs") + return err + } + + // If specific services are provided, ensure they exist in the mappings. + if len(services) != 0 { + for _, service := range services { + if _, exists := mappings[service]; !exists { + // Warn if the service is not found in the services mapping. + s.logger.Warn("Service not found in services mapping, no contract generation", zap.String("service", service)) + } + } + } + + // Loop through each test set ID to process the HTTP mocks. + for _, testSetID := range testSetsIDs { + // Retrieve HTTP mocks for the test set from the mock database. + httpMocks, err := s.mockDB.GetHTTPMocks(ctx, testSetID, s.config.Path, "mocks") + if err != nil { + // Log and return error if HTTP mock retrieval fails. + utils.LogError(s.logger, err, "failed to get HTTP mocks", zap.String("testSetID", testSetID)) + return err + } + + // Track duplicate mocks to avoid generating the same schema multiple times. + var duplicateServices []string + + // Loop through each HTTP mock to generate OpenAPI documentation. + for _, mock := range httpMocks { + var isAppend bool // Flag to indicate whether to append to existing mocks. + + // Loop through services and their mappings to find the relevant mock. + for service, serviceMappings := range mappings { + // If a specific service list is provided, skip services not in the list. + if !yaml.Contains(services, service) && len(services) != 0 { + continue + } + + var mappingFound bool // Flag to track if the mapping for the service is found. + + // Check if the mock's URL matches any service mapping. + for _, mapping := range serviceMappings { + if mapping == mock.Spec.Request.URL { + + // Check for duplicate services to append the mock to the existing mocks.yaml before. + if yaml.Contains(duplicateServices, service) { + isAppend = true + } else { + duplicateServices = append(duplicateServices, service) + } + + // Convert the HTTP mock to OpenAPI documentation. + openapi, err := s.HTTPDocToOpenAPI(s.logger, *mock) + if err != nil { + utils.LogError(s.logger, err, "failed to convert the yaml file to openapi") + return fmt.Errorf("failed to convert the yaml file to openapi") + } + + // Validate the generated OpenAPI schema. + err = validateSchema(openapi) + if err != nil { + utils.LogError(s.logger, err, "failed to validate the OpenAPI schema") + return err + } + + // Write the OpenAPI document to the specified directory. + err = s.openAPIDB.WriteSchema(ctx, s.logger, filepath.Join(s.config.Path, "schema", "mocks", service, testSetID), "mocks", openapi, isAppend) + if err != nil { + utils.LogError(s.logger, err, "failed to write the OpenAPI schema") + return err + } + + mappingFound = true // Mark the mapping as found. + break + } + } + + // Break the outer loop if the relevant mapping is found. + if mappingFound { + break + } + } + } + } + + return nil // Return nil if the function completes successfully. +} + +func (s *contract) GenerateTestsSchemas(ctx context.Context, selectedTests []string) error { + testSetsIDs, err := s.testDB.GetAllTestSetIDs(ctx) + if err != nil { + utils.LogError(s.logger, err, "failed to get test set IDs") + return err + } + + for _, testSetID := range testSetsIDs { + if !yaml.Contains(selectedTests, testSetID) && len(selectedTests) != 0 { + continue + } + + testCases, err := s.testDB.GetTestCases(ctx, testSetID) + if err != nil { + utils.LogError(s.logger, err, "failed to get test cases", zap.String("testSetID", testSetID)) + return err + } + for _, tc := range testCases { + var httpSpec models.HTTPDoc + httpSpec.Kind = string(tc.Kind) + httpSpec.Name = tc.Name + httpSpec.Spec.Request = tc.HTTPReq + httpSpec.Spec.Response = tc.HTTPResp + httpSpec.Version = string(tc.Version) + + openapi, err := s.HTTPDocToOpenAPI(s.logger, httpSpec) + if err != nil { + utils.LogError(s.logger, err, "failed to convert the yaml file to openapi") + return fmt.Errorf("failed to convert the yaml file to openapi") + } + // Validate the OpenAPI document + err = validateSchema(openapi) + if err != nil { + utils.LogError(s.logger, err, "failed to validate the OpenAPI schema") + return err + } + // Save it using the OpenAPIDB + err = s.openAPIDB.WriteSchema(ctx, s.logger, filepath.Join(s.config.Path, "schema", "tests", testSetID), tc.Name, openapi, false) + if err != nil { + utils.LogError(s.logger, err, "failed to write the OpenAPI schema") + return err + } + + } + + } + return nil +} + +func (s *contract) Generate(ctx context.Context, checkConfig bool) error { + if checkConfig && checkConfigFile(s.config.Contract.Mappings.ServicesMapping) != nil { + utils.LogError(s.logger, fmt.Errorf("unable to find services mappings in the config file"), "Unable to find services mappings in the config file") + return fmt.Errorf("unable to find services mappings in the config file") + } + + mappings := s.config.Contract.Mappings.ServicesMapping + serviceColor := color.New(color.FgYellow).SprintFunc() + fmt.Println(serviceColor("==========================================")) + fmt.Println(serviceColor(fmt.Sprintf("Starting Generating OpenAPI Schemas for Current Service: %s ....", s.config.Contract.Mappings.Self))) + fmt.Println(serviceColor("==========================================")) + + err := s.GenerateTestsSchemas(ctx, s.config.Contract.Tests) + if err != nil { + utils.LogError(s.logger, err, "failed to generate tests schemas") + return err + } + err = s.GenerateMocksSchemas(ctx, s.config.Contract.Services, mappings) + if err != nil { + utils.LogError(s.logger, err, "failed to generate mocks schemas") + return err + } + if err := saveServiceMappings(s.config.Contract.Mappings, filepath.Join(s.config.Path, "schema")); err != nil { + utils.LogError(s.logger, err, "failed to save service mappings") + return err + } + + return nil +} + +func (s *contract) DownloadTests(_ string) error { + + targetPath := "./Download/Tests" + if err := yaml.CreateDir(targetPath, s.logger); err != nil { + utils.LogError(s.logger, err, "failed to create directory", zap.String("directory", targetPath)) + return err + } + + cprFolder, err := filepath.Abs("../VirtualCPR") + if err != nil { + utils.LogError(s.logger, err, "failed to get absolute path", zap.String("path", "../VirtualCPR")) + return err + } + + // Loop through the services in the mappings in the config file + for service := range s.config.Contract.Mappings.ServicesMapping { + // Fetch the tests of those services from virtual cpr + testsPath := filepath.Join(cprFolder, service, "keploy", "schema", "tests") + // Copy this dir to the target path + serviceFolder := filepath.Join(targetPath, service) + if err := yaml.CopyDir(testsPath, serviceFolder, false, s.logger); err != nil { + utils.LogError(s.logger, err, "failed to copy directory", zap.String("directory", testsPath)) + return err + } + s.logger.Info("Service's tests (contracts) downloaded", zap.String("service", service)) + // Copy the Keploy version (HTTP) tests + keployTestsPath := filepath.Join(cprFolder, service, "keploy") + testEntries, err := os.ReadDir(keployTestsPath) + if err != nil { + utils.LogError(s.logger, err, "failed to read directory", zap.String("directory", keployTestsPath)) + return err + } + for _, testSetID := range testEntries { + if !testSetID.IsDir() || !strings.Contains(testSetID.Name(), "test") { + continue + } + // Copy the directory to the target path + if err := yaml.CopyDir(filepath.Join(keployTestsPath, testSetID.Name(), "tests"), filepath.Join(serviceFolder, "schema", testSetID.Name()), false, s.logger); err != nil { + utils.LogError(s.logger, err, "failed to copy directory", zap.String("directory", filepath.Join(keployTestsPath, testSetID.Name(), "tests"))) + return err + } + s.logger.Info("Service's HTTP tests downloaded", zap.String("service", service), zap.String("tests", testSetID.Name())) + + } + + } + return nil +} + +// DownloadMocks downloads the mocks for a specific service and stores them in the target path. +// The mocks are extracted from the VirtualCPR folder and saved in the "Download/Mocks" directory. +func (s *contract) DownloadMocks(ctx context.Context, _ string) error { + // Set the target path where the downloaded mocks will be stored + targetPath := "./Download/Mocks" + + // Create the target directory if it doesn't already exist + if err := yaml.CreateDir(targetPath, s.logger); err != nil { + utils.LogError(s.logger, err, "failed to create directory", zap.String("directory", targetPath)) + return err + } + + // Get the absolute path to the VirtualCPR folder + cprFolder, err := filepath.Abs("../VirtualCPR") + if err != nil { + utils.LogError(s.logger, err, "failed to get absolute path", zap.String("path", "../VirtualCPR")) + return err + } + + // Read all entries (files and directories) in the VirtualCPR folder + entries, err := os.ReadDir(cprFolder) + if err != nil { + utils.LogError(s.logger, err, "failed to read directory", zap.String("directory", cprFolder)) + return err + } + + // Loop through each entry in the VirtualCPR folder + for _, entry := range entries { + // If the entry is not a directory, skip it + if !entry.IsDir() { + continue + } + + // Extract the name of the current service (the one being processed) + var self = s.config.Contract.Mappings.Self + var schemaConfigMappings config.Mappings + + // Construct the path to the schema configuration file for the current service + configFilePath := filepath.Join(cprFolder, entry.Name(), "keploy", "schema") + + // Read the schema configuration YAML schemaConfigMappings + if err := yaml.ReadYAMLFile(ctx, s.logger, configFilePath, "serviceMappings", &schemaConfigMappings, true); err != nil { + utils.LogError(s.logger, err, "failed to read the schema configuration file", zap.String("file", "serviceMappings")) + return err + } + + // Check if the current service exists in the service mapping from the schema configuration + serviceFound := false + if _, exists := schemaConfigMappings.ServicesMapping[self]; exists { + serviceFound = true + } + + // If the service is not found in the mapping, skip to the next service + if !serviceFound { + continue + } + + // Create a directory for the current service inside the target path + serviceFolder := filepath.Join(targetPath, schemaConfigMappings.Self) + if err := yaml.CreateDir(serviceFolder, s.logger); err != nil { + utils.LogError(s.logger, err, "failed to create directory", zap.String("directory", serviceFolder)) + return err + } + + // Construct the path to the mock files for the current service + mocksSourcePath := filepath.Join(cprFolder, entry.Name(), "keploy", "schema", "mocks", self) + + // Log and display the start of the mock download process for the service + serviceColor := color.New(color.FgYellow).SprintFunc() + fmt.Println(serviceColor("==========================================")) + fmt.Println(serviceColor(fmt.Sprintf("Starting Downloading Mocks for Service: %s ....", entry.Name()))) + fmt.Println(serviceColor("==========================================")) + + // Copy the mock files from the source directory to the target directory + if err := yaml.CopyDir(mocksSourcePath, serviceFolder, true, s.logger); err != nil { + utils.LogError(s.logger, err, "failed to copy directory", zap.String("directory", mocksSourcePath)) + return err + } + + // Log that the mocks for the service have been downloaded + s.logger.Info("Service's schema mocks contracts downloaded", zap.String("service", entry.Name()), zap.String("mocks", mocksSourcePath)) + + // Move the Keploy version-specific mocks + // Read the contents of the "keploy" folder to find the mock folders + mocksFolders, err := os.ReadDir(filepath.Join(cprFolder, entry.Name(), "keploy")) + if err != nil { + utils.LogError(s.logger, err, "failed to read directory", zap.String("directory", cprFolder), zap.Error(err)) + return err + } + + // Loop through each folder inside the "keploy" folder + for _, mockFolder := range mocksFolders { + // If the folder is not a directory or does not contain "test" in its name, skip it + if !mockFolder.IsDir() || !strings.Contains(mockFolder.Name(), "test") { + continue + } + + // Retrieve the HTTP mocks from the mock database for the current test set + httpMocks, err := s.mockDB.GetHTTPMocks(ctx, mockFolder.Name(), filepath.Join(cprFolder, entry.Name(), "keploy"), "mocks") + if err != nil { + utils.LogError(s.logger, err, "failed to get HTTP mocks", zap.String("testSetID", mockFolder.Name()), zap.Error(err)) + return err + } + + // Filter the HTTP mocks based on the service URL mappings + var filteredMocks []*models.HTTPDoc + for _, mock := range httpMocks { + for _, service := range schemaConfigMappings.ServicesMapping[self] { + // Add the mock to the filtered list if the service URL matches + if service == mock.Spec.Request.URL { + filteredMocks = append(filteredMocks, mock) + break + } + } + } + + // Write the filtered mocks to the appropriate folder + var initialMock = true + for _, mock := range filteredMocks { + // Marshal the mock data to YAML format + mockYAML, err := yamlLib.Marshal(mock) + if err != nil { + utils.LogError(s.logger, err, "failed to marshal mock data", zap.Any("mock", mock)) + return err + } + + // Write the mock YAML file to the target service folder + err = yaml.WriteFile(ctx, s.logger, filepath.Join(serviceFolder, mockFolder.Name()), "mocks", mockYAML, !initialMock) + if err != nil { + utils.LogError(s.logger, err, "failed to write mock file", zap.String("service", entry.Name()), zap.String("testSetID", mockFolder.Name())) + return err + } + + // Ensure only the first file is marked as the initial mock + if initialMock { + initialMock = false + } + } + + // Log that the HTTP mocks for the service have been downloaded + s.logger.Info("Service's HTTP mocks contracts downloaded", zap.String("service", entry.Name()), zap.String("mocks", mockFolder.Name())) + } + } + + // Return nil to indicate success + return nil +} + +func (s *contract) Download(ctx context.Context, checkConfig bool) error { + + if checkConfig && checkConfigFile(s.config.Contract.Mappings.ServicesMapping) != nil { + utils.LogError(s.logger, fmt.Errorf("unable to find services mappings in the config file"), "Unable to find services mappings in the config file") + return fmt.Errorf("unable to find services mappings in the config file") + } + path := s.config.Contract.Path + // Validate the path + path, err := yaml.ValidatePath(path) + if err != nil { + utils.LogError(s.logger, err, "failed to validate path") + return fmt.Errorf("error in validating path") + } + driven := s.config.Contract.Driven + if driven == models.ProviderMode.String() { + err = s.DownloadTests(path) + if err != nil { + utils.LogError(s.logger, err, "failed to download tests") + return err + } + + } else if driven == models.ConsumerMode.String() { + err = s.DownloadMocks(ctx, path) + if err != nil { + utils.LogError(s.logger, err, "failed to download mocks") + return err + } + + } + + return nil +} + +func (s *contract) Validate(ctx context.Context) error { + if s.config.Contract.Mappings.Self == "" { + utils.LogError(s.logger, fmt.Errorf("self service is not defined in the config file"), "Self service is not defined in the config file") + return fmt.Errorf("self service is not defined in the config file") + } + + if s.config.Contract.Generate { + err := s.Generate(ctx, false) + if err != nil { + utils.LogError(s.logger, err, "failed to generate contract") + return err + } + } + if s.config.Contract.Download { + err := s.Download(ctx, false) + if err != nil { + utils.LogError(s.logger, err, "failed to download contract") + return err + } + } + if s.config.Contract.Driven == models.ConsumerMode.String() { + + // Retrieve tests from the schema folder + testsMapping, err := s.GetAllTestsSchema(ctx) + if err != nil { + utils.LogError(s.logger, err, "failed to get tests from schema") + return err + } + // Retrieve mocks of each service from the download folder + mocksSchemasDownloaded, err := s.GetAllDownloadedMocksSchemas(ctx) + if err != nil { + utils.LogError(s.logger, err, "failed to get downloaded mocks schemas") + return err + } + err = s.consumer.ValidateSchema(testsMapping, mocksSchemasDownloaded) + if err != nil { + utils.LogError(s.logger, err, "failed to validate schema") + return err + } + } else if s.config.Contract.Driven == models.ProviderMode.String() { + err := s.provider.ValidateSchema(ctx) + if err != nil { + utils.LogError(s.logger, err, "failed to validate schema") + return err + } + fmt.Println("Provider driven validation is not implemented yet") + } + + return nil +} diff --git a/keploy/pkg/service/contract/provider/provider.go b/keploy/pkg/service/contract/provider/provider.go new file mode 100644 index 0000000..3f0c0e8 --- /dev/null +++ b/keploy/pkg/service/contract/provider/provider.go @@ -0,0 +1,127 @@ +// Package provider is a package for provider driven contract testing +package provider + +import ( + "context" + + "go.keploy.io/server/v2/config" + "go.uber.org/zap" +) + +const IDENTIFYMODE = 0 +const COMPAREMODE = 1 + +type provider struct { + logger *zap.Logger + + config *config.Config +} + +// New creates a new instance of the consumer service +func New(logger *zap.Logger, config *config.Config) Service { + return &provider{ + logger: logger, + + config: config, + } +} + +func (s *provider) ValidateSchema(_ context.Context) error { + // downloadTestsFolder := filepath.Join("./Download", "Tests") + // entries, err := os.ReadDir(downloadTestsFolder) + // if err != nil { + // s.logger.Error("Failed to read directory", zap.String("directory", downloadTestsFolder), zap.Error(err)) + // return err + // } + // mocksFolder := filepath.Join("./keploy", "schema", "mocks") + // s.openAPIDB.ChangeTcPath(mocksFolder) + // services, err := os.ReadDir(mocksFolder) + // if err != nil { + // s.logger.Error("Failed to read directory", zap.String("directory", mocksFolder), zap.Error(err)) + // return err + // } + // var mocksMapping map[string]map[string]map[string]*models.OpenAPI = make(map[string]map[string]map[string]*models.OpenAPI) + // var mocks []*models.OpenAPI + // for _, service := range services { + // if !service.IsDir() { + // continue + // } + // testSetIDs, err := os.ReadDir(filepath.Join(mocksFolder, service.Name())) + // if err != nil { + // s.logger.Error("Failed to read directory", zap.String("directory", service.Name()), zap.Error(err)) + // return err + // } + // mocksMapping[service.Name()] = make(map[string]map[string]*models.OpenAPI) + // for _, testSetID := range testSetIDs { + + // if !testSetID.IsDir() { + // continue + // } + // mocks, err = s.openAPIDB.GetMocksSchemas(ctx, filepath.Join(service.Name(), testSetID.Name()), mocksFolder, "mocks") + // if err != nil { + // s.logger.Error("Failed to get HTTP mocks", zap.String("testSetID", testSetID.Name()), zap.Error(err)) + // return err + // } + // mocksMapping[service.Name()][testSetID.Name()] = make(map[string]*models.OpenAPI) + // for _, mock := range mocks { + // mocksMapping[service.Name()][testSetID.Name()][mock.Info.Title] = mock + // } + // } + // } + // var scores map[string]map[string]map[string]models.SchemaInfo = make(map[string]map[string]map[string]models.SchemaInfo) + // for _, entry := range entries { + // if entry.IsDir() { + // serviceFolder := filepath.Join(downloadTestsFolder, entry.Name()) + // testSetIDs, err := os.ReadDir(filepath.Join(serviceFolder, "schema", "tests")) + // if err != nil { + // s.logger.Error("Failed to read directory", zap.String("directory", serviceFolder), zap.Error(err)) + // return err + // } + // scores[entry.Name()] = make(map[string]map[string]models.SchemaInfo) + // for _, testSetID := range testSetIDs { + // if !testSetID.IsDir() { + // continue + // } + // tests, err := s.openAPIDB.GetTestCasesSchema(ctx, testSetID.Name(), filepath.Join(serviceFolder, "schema", "tests")) + // if err != nil { + // s.logger.Error("Failed to get test cases", zap.String("testSetID", testSetID.Name()), zap.Error(err)) + // return err + // } + // scores[entry.Name()][testSetID.Name()] = make(map[string]models.SchemaInfo) + // for _, test := range tests { + // // Take each test and get the ideal mock for it + // scores[entry.Name()][testSetID.Name()][test.Info.Title] = models.SchemaInfo{Score: 0.0, Data: *test} + // for providerService, mockSetIDs := range mocksMapping { + // for mockSetID, mocks := range mockSetIDs { + // for _, mock := range mocks { + // candidateScore, pass, err := match2(*test, *mock, mockSetID, testSetID.Name(), s.logger, IDENTIFYMODE) + // if err != nil { + // s.logger.Error("Error in matching the two models", zap.Error(err)) + // fmt.Println("test-set-id: ", testSetID.Name(), ", mock-set-id: ", mockSetID) + // return err + // } + // if pass && candidateScore > 0 { + // if candidateScore > scores[entry.Name()][testSetID.Name()][test.Info.Title].Score { + // idealMock := models.SchemaInfo{ + // Service: providerService, + // TestSetID: mockSetID, + // Name: mock.Info.Title, + // Score: candidateScore, + // Data: *test, + // } + // scores[entry.Name()][testSetID.Name()][test.Info.Title] = idealMock + // } + // } + + // } + // } + // } + + // } + // } + // } + // } + // // TODO: Validate the scores and generate a summary + + return nil +} diff --git a/keploy/pkg/service/contract/provider/service.go b/keploy/pkg/service/contract/provider/service.go new file mode 100644 index 0000000..9d95383 --- /dev/null +++ b/keploy/pkg/service/contract/provider/service.go @@ -0,0 +1,10 @@ +package provider + +import ( + "context" +) + +// Service defines the provider service interface +type Service interface { + ValidateSchema(ctx context.Context) error +} diff --git a/keploy/pkg/service/contract/schema.go b/keploy/pkg/service/contract/schema.go new file mode 100644 index 0000000..9696045 --- /dev/null +++ b/keploy/pkg/service/contract/schema.go @@ -0,0 +1,111 @@ +package contract + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/getkin/kin-openapi/openapi3" + "go.keploy.io/server/v2/pkg/models" + yamlLib "gopkg.in/yaml.v3" +) + +func validateSchema(openapi models.OpenAPI) error { + openapiYAML, err := yamlLib.Marshal(openapi) + if err != nil { + return err + } + // Validate using kin-openapi + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData(openapiYAML) + if err != nil { + return err + + } + // Validate the OpenAPI document + if err := doc.Validate(context.Background()); err != nil { + return err + } + + return nil +} + +// GetAllTestsSchema retrieves all the tests schema from the schema folder. +func (s *contract) GetAllTestsSchema(ctx context.Context) (map[string]map[string]*models.OpenAPI, error) { + testsFolder := filepath.Join("./keploy", "schema", "tests") + + s.openAPIDB.ChangePath(testsFolder) + testSetIDs, err := os.ReadDir(testsFolder) + if err != nil { + return nil, fmt.Errorf("failed to read tests directory: %w", err) + } + + testsMapping := make(map[string]map[string]*models.OpenAPI) + for _, testSetID := range testSetIDs { + if !testSetID.IsDir() { + continue + } + + tests, err := s.openAPIDB.GetTestCasesSchema(ctx, testSetID.Name(), "") + if err != nil { + return nil, fmt.Errorf("failed to get test cases for testSetID %s: %w", testSetID.Name(), err) + } + + testsMapping[testSetID.Name()] = make(map[string]*models.OpenAPI) + for _, test := range tests { + testsMapping[testSetID.Name()][test.Info.Title] = test + } + } + + return testsMapping, nil +} + +func (s *contract) GetAllDownloadedMocksSchemas(ctx context.Context) ([]models.MockMapping, error) { + // ***** TODO: See what part can be moved to DB layer ***** + downloadMocksFolder := filepath.Join("./Download", "Mocks") + + // Read the contents of the Download Mocks folder to get all service directories. + entries, err := os.ReadDir(downloadMocksFolder) + if err != nil { + // If there's an error reading the directory, return it. + return nil, fmt.Errorf("failed to read mocks directory: %w", err) + } + var mocksSchemasMapping []models.MockMapping + // Loop over each entry in the Download Mocks folder. + for _, entry := range entries { + // Check if the entry is a directory (indicating a service folder). + if entry.IsDir() { + // Define the path to the service folder (e.g., Download/Mocks/service-name). + serviceFolder := filepath.Join(downloadMocksFolder, entry.Name()) + + // Read the contents of the service folder to get mock set IDs (subdirectories). + mockSetIDs, err := os.ReadDir(serviceFolder) + if err != nil { + // If there's an error reading the service folder, return it. + return nil, fmt.Errorf("failed to read service directory %s: %w", serviceFolder, err) + } + + // Loop over each mock set ID in the service folder. + for _, mockSetID := range mockSetIDs { + // Ensure the mock set ID is a directory. + if !mockSetID.IsDir() { + continue + } + + // Retrieve the mocks for the given mock set ID (e.g., schema files in the folder). + mocks, err := s.openAPIDB.GetMocksSchemas(ctx, mockSetID.Name(), serviceFolder, "schema") + if err != nil { + // If there's an error retrieving mocks, return it. + return nil, fmt.Errorf("failed to get HTTP mocks for mockSetID %s: %w", mockSetID.Name(), err) + } + mocksSchemasMapping = append(mocksSchemasMapping, models.MockMapping{ + Service: entry.Name(), + TestSetID: mockSetID.Name(), + Mocks: mocks, + }) + } + } + } + return mocksSchemasMapping, nil +} diff --git a/keploy/pkg/service/contract/service.go b/keploy/pkg/service/contract/service.go new file mode 100644 index 0000000..5fcbad5 --- /dev/null +++ b/keploy/pkg/service/contract/service.go @@ -0,0 +1,30 @@ +package contract + +import ( + "context" + + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +// Service defines the contract service interface +type Service interface { + Generate(ctx context.Context, checkConfig bool) error + Download(ctx context.Context, checkConfig bool) error + Validate(ctx context.Context) error +} + +type TestDB interface { + GetAllTestSetIDs(ctx context.Context) ([]string, error) + GetTestCases(ctx context.Context, testSetID string) ([]*models.TestCase, error) + ChangePath(path string) +} +type MockDB interface { + GetHTTPMocks(ctx context.Context, testSetID string, mockPath string, mockFileName string) ([]*models.HTTPDoc, error) +} +type OpenAPIDB interface { + GetTestCasesSchema(ctx context.Context, testSetID string, testPath string) ([]*models.OpenAPI, error) + GetMocksSchemas(ctx context.Context, testSetID string, mockPath string, mockFileName string) ([]*models.OpenAPI, error) + WriteSchema(ctx context.Context, logger *zap.Logger, outputPath, name string, openapi models.OpenAPI, isAppend bool) error + ChangePath(path string) +} diff --git a/keploy/pkg/service/contract/utils.go b/keploy/pkg/service/contract/utils.go new file mode 100644 index 0000000..413f50c --- /dev/null +++ b/keploy/pkg/service/contract/utils.go @@ -0,0 +1,261 @@ +package contract + +import ( + "context" + "crypto/rand" + "encoding/hex" + "fmt" + "net/url" + "strconv" + "strings" + "time" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/yaml" + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +type Response struct { + Code int + Message string + Types map[string]map[string]interface{} + Body map[string]interface{} +} + +// ExtractVariableTypes returns the type of each variable in the object. +func ExtractVariableTypes(obj map[string]interface{}) map[string]map[string]interface{} { + types := make(map[string]map[string]interface{}, len(obj)) + + getType := func(value interface{}) string { + switch value.(type) { + case float64: + return "number" + case int, int32, int64: + return "integer" + case string: + return "string" + case bool: + return "boolean" + case map[string]interface{}: + return "object" + case []interface{}: + return "array" + default: + return "string" + } + } + + for key, value := range obj { + valueType := getType(value) + responseType := map[string]interface{}{ + "type": valueType, + } + + switch valueType { + case "object": + responseType["properties"] = ExtractVariableTypes(value.(map[string]interface{})) + case "array": + arrayItems := value.([]interface{}) + arrayType := "string" // Default to string if array is empty + + if len(arrayItems) > 0 { + firstElement := arrayItems[0] + arrayType = getType(firstElement) + if arrayType == "object" { + responseType["items"] = map[string]interface{}{ + "type": arrayType, + "properties": ExtractVariableTypes(firstElement.(map[string]interface{})), + } + types[key] = responseType + continue + } + } + responseType["items"] = map[string]interface{}{ + "type": arrayType, + } + } + + types[key] = responseType + } + + return types +} + +func GenerateResponse(response Response) map[string]models.ResponseItem { + byCode := map[string]models.ResponseItem{ + fmt.Sprintf("%d", response.Code): { + Description: response.Message, + Content: map[string]models.MediaType{ + "application/json": { + Schema: models.Schema{ + Type: "object", + Properties: response.Types, + }, + Example: (response.Body), + }, + }, + }, + } + return byCode +} + +func ExtractURLPath(URL string) (string, string) { + parsedURL, err := url.Parse(URL) + + if err != nil { + return "", "" + } + return parsedURL.Path, parsedURL.Host +} + +func GenerateHeader(header map[string]string) []models.Parameter { + var parameters []models.Parameter + for key, value := range header { + parameters = append(parameters, models.Parameter{ + Name: key, + In: "header", + Required: true, + Schema: models.ParamSchema{Type: "string"}, + Example: value, + }) + } + return parameters +} + +// isNumeric checks if a string is a valid numeric value (integer or float). +func isNumeric(s string) bool { + if _, err := strconv.Atoi(s); err == nil { + return true + } + if _, err := strconv.ParseFloat(s, 64); err == nil { + return true + } + return false +} + +// ExtractIdentifiers extracts numeric identifiers (integers or floats) from the path. +func ExtractIdentifiers(path string) []string { + segments := strings.Split(path, "/") + segments2 := strings.Split(segments[len(segments)-1], "?") + segments = append(segments[:len(segments)-1], segments2[0]) + var identifiers []string + for _, segment := range segments { + if segment != "" { + // Check if the segment is numeric (integer or float) + if isNumeric(segment) { + identifiers = append(identifiers, segment) + } + } + } + + return identifiers +} + +// ExtractQueryParams extracts the query parameters and their names from the URL. +func ExtractQueryParams(rawURL string) (map[string]string, error) { + parsedURL, err := url.Parse(rawURL) + if err != nil { + return nil, err + } + queryParams := parsedURL.Query() + params := make(map[string]string) + for key, values := range queryParams { + if len(values) > 0 { + // Take the first value if multiple are present + params[key] = values[0] + } + } + return params, nil +} + +// GenerateDummyNamesForIdentifiers generates dummy names for the path identifiers. +func GenerateDummyNamesForIdentifiers(identifiers []string) map[string]string { + dummyNames := make(map[string]string) + for i, id := range identifiers { + dummyName := fmt.Sprintf("param%d", i+1) + dummyNames[dummyName] = id + } + return dummyNames +} +func AppendInParameters(parameters []models.Parameter, inParameters map[string]string, paramType string) []models.Parameter { + + for key, value := range inParameters { + parameters = append(parameters, models.Parameter{ + Name: key, + In: paramType, + Required: true, + Schema: models.ParamSchema{Type: "string"}, + Example: value, + }) + } + + return parameters +} + +// ReplacePathIdentifiers replaces numeric identifiers in the path with their corresponding dummy names. +func ReplacePathIdentifiers(path string, dummyNames map[string]string) string { + segments := strings.Split(path, "/") + var replacedPath []string + for _, segment := range segments { + if segment != "" { + // Check if the segment is numeric (integer or float) + if isNumeric(segment) { + dummyName := "" + for key, value := range dummyNames { + if value == segment { + // i want to put '{' and '}' around the key + dummyName = "{" + key + "}" + break + } + } + if dummyName != "" { + replacedPath = append(replacedPath, dummyName) + } else { + replacedPath = append(replacedPath, segment) + } + } else { + replacedPath = append(replacedPath, segment) + } + } + } + finalPath := strings.Join(replacedPath, "/") + // Add slash at the beginning of the path + finalPath = "/" + finalPath + return finalPath +} + +func generateUniqueID() string { + b := make([]byte, 16) + _, err := rand.Read(b) + if err != nil { + // handle error + return "" + } + return hex.EncodeToString(b) + "-" + time.Now().Format("20060102150405") +} + +func checkConfigFile(servicesMapping map[string][]string) error { + // Check if the size of servicesMapping is less than 1 + if len(servicesMapping) < 1 { + return fmt.Errorf("services mapping must contain at least 1 services") + } + return nil +} + +func saveServiceMappings(servicesMapping config.Mappings, filePath string) error { + // Marshal the services mapping to YAML + servicesMappingYAML, err := yamlLib.Marshal(servicesMapping) + if err != nil { + return fmt.Errorf("failed to marshal services mapping: %w", err) + } + + // Write the services mapping to the specified file path + err = yaml.WriteFile(context.Background(), zap.NewNop(), filePath, "serviceMappings", servicesMappingYAML, false) + if err != nil { + return fmt.Errorf("failed to write services mapping to file: %w", err) + } + + return nil +} diff --git a/keploy/pkg/service/export/export.go b/keploy/pkg/service/export/export.go new file mode 100644 index 0000000..00928c3 --- /dev/null +++ b/keploy/pkg/service/export/export.go @@ -0,0 +1,269 @@ +// Package export contains the implementation of the export service which exports the curl commands from the YAML testcases to a Postman collection. +package export + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/fs" + "net/url" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/google/uuid" + yamlLib "gopkg.in/yaml.v3" + + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/yaml" + postmanimport "go.keploy.io/server/v2/pkg/service/import" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func ConvertKeployHTTPToPostmanCollection(logger *zap.Logger, http *models.HTTPSchema) map[string]interface{} { + var request postmanimport.PostmanRequest + var response postmanimport.PostmanResponse + + // Extract URL from the HTTP schema + extractedURL := http.Request.URL + + parsedURL, err := url.Parse(extractedURL) + if err != nil || parsedURL.Hostname() == "" { + utils.LogError(logger, err, "error parsing URL") + return nil + } + + request.URL = map[string]interface{}{ + "raw": parsedURL.String(), + "protocol": parsedURL.Scheme, + "host": []string{parsedURL.Hostname()}, + "port": parsedURL.Port(), + "path": []string{strings.TrimLeft(parsedURL.Path, "/")}, + "query": http.Request.URLParams, + } + request.Method = string(http.Request.Method) + + for key, header := range http.Request.Header { + request.Header = append(request.Header, map[string]interface{}{ + "key": key, + "value": header, + }) + } + + if http.Request.Form != nil { + formDataArray := []map[string]interface{}{} + for _, form := range http.Request.Form { + formDataArray = append(formDataArray, map[string]interface{}{ + "key": form.Key, + "values": form.Values, + }) + } + + request.Body.Mode = "formdata" + request.Body.Formdata = formDataArray + } else { + request.Body.Mode = "raw" + request.Body.Raw = http.Request.Body + } + + if strings.Contains(http.Request.Header["Content-Type"], "application/json") { + request.Body.Options = map[string]interface{}{ + "raw": map[string]interface{}{ + "headerFamily": "json", + "language": "json", + }, + } + } + + // Extract Response Headers + for key, header := range http.Response.Header { + response.Header = append(response.Header, map[string]string{ + "key": key, + "value": header, + }) + } + + response.Code = http.Response.StatusCode + response.Status = http.Response.StatusMessage + response.Body = http.Response.Body + response.OriginalRequest = &request + response.Name = http.Response.StatusMessage + + if strings.Contains(http.Response.Header["Content-Type"], "application/json") { + request.Body.Options = map[string]interface{}{ + "raw": map[string]interface{}{ + "headerFamily": "json", + "language": "json", + }, + } + } + + // Extract the last segment of the path as the name + pathSegments := strings.Split(strings.Trim(parsedURL.Path, "/"), "/") + // Create the name by joining segments with dashes + name := strings.Join(pathSegments, "-") + + return map[string]interface{}{ + "name": name, + "protocolProfileBehavior": map[string]interface{}{ + "disableBodyPruning": true, + }, + "request": request, + "response": []postmanimport.PostmanResponse{response}, + } +} + +type PostmanCollection struct { + Info struct { + PostmanID string `json:"_postman_id"` + Name string `json:"name"` + Schema string `json:"schema"` + } `json:"info"` + Items []interface{} `json:"item"` +} + +func Export(_ context.Context, logger *zap.Logger) error { + cwd, err := os.Getwd() + if err != nil { + utils.LogError(logger, err, "failed to get current working directory") + return err + } + // Correctly format the directory path to include "keploy" + keployDir := filepath.Join(cwd, "keploy") + + // Check if the directory exists + if _, err := os.Stat(keployDir); os.IsNotExist(err) { + utils.LogError(logger, err, "keploy directory does not exist") + return err + + } + dir, err := yaml.ReadDir(keployDir, fs.FileMode(os.O_RDONLY)) + if err != nil { + utils.LogError(logger, err, "failed to open the directory containing yaml testcases", zap.Any("path", keployDir)) + return err + } + + files, err := dir.ReadDir(0) + if err != nil { + utils.LogError(logger, err, "failed to read the file names of yaml testcases", zap.Any("path", keployDir)) + return err + + } + folderName := filepath.Base(cwd) + + collection := PostmanCollection{ + Info: struct { + PostmanID string `json:"_postman_id"` + Name string `json:"name"` + Schema string `json:"schema"` + }{ + PostmanID: uuid.New().String(), + Name: folderName, + Schema: "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + }, + } + for _, v := range files { + if v.Name() != "reports" && v.Name() != "testReports" && v.IsDir() { + testsDir := filepath.Join(keployDir, v.Name(), "tests") + if _, err := os.Stat(testsDir); os.IsNotExist(err) { + logger.Info("No tests found. Skipping export.", zap.String("path", testsDir)) + continue + } + // Read the "tests" subfolder + testFiles, err := os.ReadDir(testsDir) + if err != nil { + utils.LogError(logger, err, "failed to read the test files", zap.String("path", testsDir)) + continue + } + keployRequests := make(map[interface{}]int, 0) + for _, testFile := range testFiles { + if filepath.Ext(testFile.Name()) == ".yaml" { + filePath := filepath.Join(testsDir, testFile.Name()) + + // Read the YAML file + data, err := os.ReadFile(filePath) + if err != nil { + utils.LogError(logger, err, "failed to read the YAML file", zap.String("path", filePath)) + continue + } + + if len(data) == 0 { + logger.Warn("skippping empty testcase", zap.String("testcase name", testFile.Name())) + continue + } + + var testCase *yaml.NetworkTrafficDoc + err = yamlLib.Unmarshal(data, &testCase) + if err != nil { + utils.LogError(logger, err, "failed to unmarshall YAML data") + continue + } + if testCase == nil { + logger.Warn("skipping invalid testCase yaml", zap.String("testcase name", testFile.Name())) + continue + } + + var httpSchema models.HTTPSchema + + err = testCase.Spec.Decode(&httpSchema) + if err != nil { + utils.LogError(logger, err, "failed to decode the HTTP schema") + continue + } + + requestJSON := ConvertKeployHTTPToPostmanCollection(logger, &httpSchema) + // Convert the requestJSON to a string (assuming it's a map or complex type) + requestJSONString, err := json.Marshal(requestJSON) + if err != nil { + utils.LogError(logger, err, "failed to marshal requestJSON to string") + continue + } + keployRequests[string(requestJSONString)]++ + } + } + var uniqueRequests []interface{} + for request := range keployRequests { + var curlRequest interface{} + err := json.Unmarshal([]byte(request.(string)), &curlRequest) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the request JSON") + continue + } + uniqueRequests = append(uniqueRequests, curlRequest) + } + requestFile := map[string]interface{}{ + "name": v.Name(), + "item": uniqueRequests, + } + collection.Items = append(collection.Items, requestFile) + } + } + sort.SliceStable(collection.Items, func(i, j int) bool { + return collection.Items[i].(map[string]interface{})["name"].(string) < collection.Items[j].(map[string]interface{})["name"].(string) + }) + + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + encoder.SetEscapeHTML(false) // Disable HTML escaping + encoder.SetIndent("", " ") + + err = encoder.Encode(collection) + if err != nil { + utils.LogError(logger, err, "failed to encode the Postman collection") + return err + } + + outputData := buf.Bytes() + + if err := os.WriteFile("output.json", outputData, 0644); err != nil { + utils.LogError(logger, err, "failed to write the output JSON file") + return err + } + + fmt.Println("✅ Curls successfully exported to output.json 🎉") + + return nil +} diff --git a/keploy/pkg/service/import/import.go b/keploy/pkg/service/import/import.go new file mode 100644 index 0000000..57c2ee4 --- /dev/null +++ b/keploy/pkg/service/import/import.go @@ -0,0 +1,569 @@ +// Package postmanimport implements the import of a Postman collection to Keploy tests. +package postmanimport + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/yaml" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +const ( + postmanSchemaVersion = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + testSetNamePrefix = "test-set-" +) + +type PostmanImporter struct { + logger *zap.Logger + ctx context.Context + toCapture bool +} + +func NewPostmanImporter(ctx context.Context, logger *zap.Logger) *PostmanImporter { + return &PostmanImporter{ + logger: logger, + ctx: ctx, + toCapture: true, + } +} + +func (pi *PostmanImporter) Import(collectionPath, basePath string) error { + if err := pi.validateCollectionPath(collectionPath); err != nil { + return err + } + + collectionBytes, err := os.ReadFile(collectionPath) + if err != nil { + return fmt.Errorf("failed to read Postman collection file: %w", err) + } + + postmanCollection, err := pi.parsePostmanCollection(collectionBytes) + if err != nil { + return err + } + + pi.validatePostmanSchema(postmanCollection.Info.Schema) + + globalVariables := pi.extractGlobalVariables(postmanCollection.Variables) + + // Check for empty responses if basePath is not provided + emptyResponsesExist := pi.scanForEmptyResponses(postmanCollection) + if emptyResponsesExist { + if !pi.promptUserForCapture() { + pi.toCapture = false + pi.logger.Warn("Few test cases will be skipped as responses are missing from the collection") + } + } + + if err := pi.importTestSets(postmanCollection, globalVariables, basePath); err != nil { + return err + } + + pi.logger.Info("✅ Postman Collection Successfully Imported To Keploy Tests 🎉") + return nil +} + +func (pi *PostmanImporter) validateCollectionPath(path string) error { + if path == "" { + return fmt.Errorf("path to Postman collection cannot be empty") + } + + if !strings.HasSuffix(path, ".json") { + return fmt.Errorf("invalid file type: expected .json Postman collection") + } + + return nil +} + +func (pi *PostmanImporter) parsePostmanCollection(collectionBytes []byte) (*PostmanCollectionStruct, error) { + var postmanCollection PostmanCollectionStruct + if err := json.Unmarshal(collectionBytes, &postmanCollection); err != nil { + return nil, fmt.Errorf("failed to unmarshal Postman collection JSON: %w", err) + } + return &postmanCollection, nil +} + +func (pi *PostmanImporter) validatePostmanSchema(schema string) { + if schema != postmanSchemaVersion { + pi.logger.Warn("Postman Collection schema mismatch", zap.String("expected", postmanSchemaVersion), zap.String("actual", schema)) + } +} + +func (pi *PostmanImporter) extractGlobalVariables(variables []map[string]interface{}) map[string]string { + globalVariables := make(map[string]string) + + for _, variable := range variables { + // Skip disabled variables + if variable["disabled"] != nil && variable["disabled"].(bool) { + continue + } + + // Extract and validate variable key + key, ok := variable["key"].(string) + if !ok { + pi.logger.Error("Global variable key is not a string", zap.Any("key", variable["key"])) + continue + } + + // Extract and validate variable value + value, ok := variable["value"].(string) + if !ok { + pi.logger.Error("Global variable value is not a string", zap.Any("value", variable["value"])) + continue + } + + globalVariables[key] = value + } + + // Resolve variable dependencies + for key, value := range globalVariables { + globalVariables[key] = replaceTemplateVars(value, globalVariables) + } + + return globalVariables +} + +func (pi *PostmanImporter) sendRequest(req models.HTTPReq, basePath string) (models.HTTPResp, error) { + + var err error + if basePath != "" { + req.URL, err = utils.ReplaceBaseURL(req.URL, basePath) + if err != nil { + pi.logger.Error("failed to replace hostname", zap.Error(err)) + return models.HTTPResp{}, err + } + } + + httpReq, err := http.NewRequest(string(req.Method), req.URL, strings.NewReader(req.Body)) + if err != nil { + pi.logger.Error("failed to create HTTP request", zap.Error(err)) + return models.HTTPResp{}, err + } + + for key, value := range req.Header { + httpReq.Header.Set(key, value) + } + + // add timeout to the request + client := &http.Client{ + Timeout: 60 * time.Second, + } + + if req.ProtoMajor != 0 || req.ProtoMinor != 0 { + httpReq.ProtoMajor = req.ProtoMajor + httpReq.ProtoMinor = req.ProtoMinor + } + + resp, err := client.Do(httpReq) + if err != nil { + pi.logger.Error("failed to send HTTP request", zap.Error(err)) + return models.HTTPResp{}, err + } + + defer func() { + if cerr := resp.Body.Close(); cerr != nil { + pi.logger.Error("failed to close response body", zap.Error(cerr)) + } + }() + + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + pi.logger.Error("failed to read response body", zap.Error(err)) + return models.HTTPResp{}, err + } + + response := models.HTTPResp{ + StatusCode: resp.StatusCode, + StatusMessage: resp.Status, + Header: make(map[string]string), + Body: string(responseBody), + } + + for key, value := range resp.Header { + response.Header[key] = strings.Join(value, ",") + } + // Use the response.Body field to avoid unused write error + pi.logger.Info("Response Body", zap.String("body", response.Body)) + + return response, nil +} + +func (pi *PostmanImporter) importTestSets(collection *PostmanCollectionStruct, globalVariables map[string]string, basePath string) error { + cwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("failed to get current working directory: %w", err) + } + + testCounter := 0 + var itemsToProcess []TestData + + if len(collection.Items.PostmanItems) > 0 { + for _, item := range collection.Items.PostmanItems { + if len(item.Item) == 0 { + continue + } + + testSet := item.Name + if item.Name == "" { + testSet = pi.generateTestSetName() + } + + testSetPath := filepath.Join(cwd, "keploy", testSet) + testsPath := filepath.Join(testSetPath, "tests") + + for _, testItem := range item.Item { + if err := pi.processEmptyResponse(&testItem, globalVariables, basePath); err != nil { + return err + } + + if err := pi.writeTestData(testItem, testsPath, globalVariables, &testCounter); err != nil { + return fmt.Errorf("failed to write test data: %w", err) + } + } + + testCounter = 0 + } + } + + if len(collection.Items.TestDataItems) > 0 { + testSetName := pi.generateTestSetName() + testSetPath := filepath.Join(cwd, "keploy", testSetName) + testsPath := filepath.Join(testSetPath, "tests") + itemsToProcess = collection.Items.TestDataItems + + for _, testItem := range itemsToProcess { + if err := pi.processEmptyResponse(&testItem, globalVariables, basePath); err != nil { + return err + } + + if err := pi.writeTestData(testItem, testsPath, globalVariables, &testCounter); err != nil { + return fmt.Errorf("failed to write test data: %w", err) + } + } + + return nil + } + + return nil +} + +func (pi *PostmanImporter) generateTestSetName() string { + maxTestSetNumber := 0 + files, err := os.ReadDir(filepath.Join("keploy")) + if err == nil { + for _, file := range files { + if file.IsDir() && strings.HasPrefix(file.Name(), testSetNamePrefix) { + var testSetNumber int + if _, err := fmt.Sscanf(file.Name(), testSetNamePrefix+"%d", &testSetNumber); err == nil && testSetNumber > maxTestSetNumber { + maxTestSetNumber = testSetNumber + } + } + } + } + return fmt.Sprintf("%s%d", testSetNamePrefix, maxTestSetNumber+1) +} + +func (pi *PostmanImporter) scanForEmptyResponses(collection *PostmanCollectionStruct) bool { + + for _, item := range collection.Items.PostmanItems { + for _, testItem := range item.Item { + if len(testItem.Response) == 0 { + pi.logger.Warn("Empty response found", zap.String("testItem", testItem.Name)) + return true + } + } + } + for _, testItem := range collection.Items.TestDataItems { + if len(testItem.Response) == 0 { + pi.logger.Warn("Empty response found", zap.String("testItem", testItem.Name)) + return true + } + } + return false +} + +func (pi *PostmanImporter) promptUserForCapture() bool { + reader := bufio.NewReader(os.Stdin) + fmt.Print("Some responses are empty. We need to hit the server to record these responses. Is your server running? (yes/no): ") + response, err := reader.ReadString('\n') + if err != nil { + pi.logger.Error("Failed to read user input", zap.Error(err)) + return false + } + response = strings.TrimSpace(strings.ToLower(response)) + return response == "yes" +} + +func (pi *PostmanImporter) processEmptyResponse(testItem *TestData, globalVariables map[string]string, basePath string) error { + if len(testItem.Response) != 0 { + return nil + } + + if !pi.toCapture { + pi.logger.Info("Skipping request capture as basePath is not provided") + return nil + } + + req := constructRequest(&testItem.Request, globalVariables) + if req.URL != "" { + resp, err := pi.sendRequest(req, basePath) + if err != nil { + return fmt.Errorf("failed to send request: %w", err) + } + + var result []map[string]string + for key, value := range resp.Header { + result = append(result, map[string]string{ + "key": key, + "value": value, + }) + } + + response := PostmanResponse{ + Name: "New Request", + Body: resp.Body, + Status: resp.StatusMessage, + Code: resp.StatusCode, + OriginalRequest: &testItem.Request, + Header: result, + } + testItem.Response = append(testItem.Response, response) + return nil + } + pi.logger.Error("URL is empty", zap.String("testItem", testItem.Name)) + return fmt.Errorf("URL is empty") +} + +func (pi *PostmanImporter) writeTestData(testItem TestData, testsPath string, globalVariables map[string]string, testCounter *int) error { + for _, response := range testItem.Response { + testName := fmt.Sprintf("test-%d", *testCounter+1) + + requestSchema := constructRequest(response.OriginalRequest, globalVariables) + if response.OriginalRequest == nil { + requestSchema = constructRequest(&testItem.Request, globalVariables) + } + + responseSchema := constructResponse(response) + + testCase := &yaml.NetworkTrafficDoc{ + Version: models.GetVersion(), + Kind: models.HTTP, + Name: testItem.Name, + } + + if err := testCase.Spec.Encode(&models.HTTPSchema{ + Request: requestSchema, + Response: responseSchema, + }); err != nil { + return fmt.Errorf("failed to encode test case: %w", err) + } + + testCase.Curl = pkg.MakeCurlCommand(requestSchema) + + testResultBytes, err := yamlLib.Marshal(testCase) + if err != nil { + return fmt.Errorf("failed to marshal test result: %w", err) + } + + if err := yaml.WriteFile(pi.ctx, pi.logger, testsPath, testName, testResultBytes, false); err != nil { + return fmt.Errorf("failed to write test result: %w", err) + } + + (*testCounter)++ + } + return nil +} + +func constructRequest(req *PostmanRequest, variables map[string]string) models.HTTPReq { + if req == nil { + return models.HTTPReq{} + } + + headers := extractHeaders(req.Header) + url := extractURL(req.URL) + + requestSchema := models.HTTPReq{ + URL: replaceTemplateVars(url, variables), + Method: models.Method(req.Method), + Header: headers, + } + + // Process request body based on mode + switch req.Body.Mode { + case "raw": + requestSchema.Body = req.Body.Raw + case "urlencoded": + requestSchema.Body = processUrlencodedBody(req.Body.Urlencoded) + case "formdata": + requestSchema.Form = processFormdataBody(req.Body.Formdata) + } + + return requestSchema +} + +func extractHeaders(headers []map[string]interface{}) map[string]string { + headersMap := make(map[string]string) + for _, header := range headers { + headersMap[header["key"].(string)] = header["value"].(string) + } + return headersMap +} + +func extractURL(url interface{}) string { + switch v := url.(type) { + case string: + return v + case map[string]interface{}: + url := v["raw"].(string) + return url + default: + return "" + } +} + +func processUrlencodedBody(body []map[string]interface{}) string { + keyValues := []string{} + for _, item := range body { + keyValues = append(keyValues, item["key"].(string)+"="+item["value"].(string)) + } + return strings.Join(keyValues, "&") +} + +func processFormdataBody(body []map[string]interface{}) []models.FormData { + form := []models.FormData{} + for _, formData := range body { + form = append(form, models.FormData{ + Key: formData["key"].(string), + Values: []string{formData["value"].(string)}, + }) + } + return form +} + +func constructResponse(res PostmanResponse) models.HTTPResp { + headers := make(map[string]string) + for _, header := range res.Header { + headers[header["key"]] = header["value"] + } + + return models.HTTPResp{ + Body: res.Body, + StatusMessage: res.Status, + StatusCode: res.Code, + Header: headers, + } +} + +func replaceTemplateVars(input string, variables map[string]string) string { + re := regexp.MustCompile(`\{\{\s*(\w+)\s*\}\}`) + + return re.ReplaceAllStringFunc(input, func(match string) string { + submatches := re.FindStringSubmatch(match) + if len(submatches) > 1 { + if replacement, exists := variables[submatches[1]]; exists { + return replacement + } + } + return match + }) +} + +type PostmanCollectionStruct struct { + Info struct { + PostmanID string `json:"_postman_id"` + Name string `json:"name"` + Schema string `json:"schema"` + } `json:"info"` + Items ItemsContainer `json:"item"` + Variables []map[string]interface{} `json:"variable"` +} + +type PostmanCollection struct { + Info struct { + PostmanID string `json:"_postman_id"` + Name string `json:"name"` + Schema string `json:"schema"` + } `json:"info"` + Items json.RawMessage `json:"item"` + Variables []map[string]interface{} `json:"variable"` +} + +type ItemsContainer struct { + PostmanItems []PostmanItem + TestDataItems []TestData +} + +type PostmanItem struct { + Name string `json:"name"` + Variables []map[string]string `json:"variable"` + Item []TestData `json:"item"` +} + +type TestData struct { + Name string `json:"name"` + Request PostmanRequest `json:"request"` + Response []PostmanResponse `json:"response"` + Variables []map[string]interface{} `json:"variable"` +} + +type PostmanRequest struct { + Method string `json:"method"` + Header []map[string]interface{} `json:"header"` + Body PostmanRequestBody `json:"body"` + URL interface{} `json:"url"` +} + +type PostmanRequestBody struct { + Mode string `json:"mode"` + Raw string `json:"raw"` + Urlencoded []map[string]interface{} `json:"urlencoded"` + Formdata []map[string]interface{} `json:"formdata"` + Options map[string]interface{} `json:"options"` +} + +type PostmanResponse struct { + Name string `json:"name"` + Body string `json:"body"` + Status string `json:"status"` + Code int `json:"code"` + OriginalRequest *PostmanRequest `json:"originalRequest,omitempty"` + Header []map[string]string `json:"header"` +} + +func (ic *ItemsContainer) UnmarshalJSON(data []byte) error { + var postmanItems []PostmanItem + if err := json.Unmarshal(data, &postmanItems); err == nil { + ic.PostmanItems = postmanItems + } + + var items []TestData + + if err := json.Unmarshal(data, &items); err != nil { + return err + } + + ic.TestDataItems = items + + return nil +} + +func (ic ItemsContainer) MarshalJSON() ([]byte, error) { + if len(ic.PostmanItems) > 0 { + return json.Marshal(ic.PostmanItems) + } + return json.Marshal(ic.TestDataItems) +} diff --git a/keploy/pkg/service/load/dashboard_exposer.go b/keploy/pkg/service/load/dashboard_exposer.go new file mode 100644 index 0000000..d05a1f6 --- /dev/null +++ b/keploy/pkg/service/load/dashboard_exposer.go @@ -0,0 +1,118 @@ +package load + +import ( + "context" + "embed" + "io/fs" + "log" + "net/http" + "os" + "os/exec" + "path" + "strconv" + "strings" + + "github.com/pkg/browser" + "go.keploy.io/server/v2/config" + "go.uber.org/zap" +) + +type DashboardExposer struct { + config *config.Config + logger *zap.Logger + port int + instanceID string + instanceURL string +} + +func NewDashboardExposer(cfg *config.Config, logger *zap.Logger, loadTestID string) *DashboardExposer { + return &DashboardExposer{ + config: cfg, + logger: logger, + port: 3000, // Default port for the dashboard, until it's configured otherwise // TODO + instanceID: loadTestID, + instanceURL: "http://localhost:3000/?dashboard=" + loadTestID, + } +} + +func (de *DashboardExposer) Expose(ctx context.Context) { + dashboardServer := &http.Server{ + Addr: ":" + strconv.Itoa(de.port), + Handler: de.handler(), + } + + de.logger.Info("Starting dashboard server", zap.String("URL", "http://localhost:"+strconv.Itoa(de.port))) + + go func() { + if err := dashboardServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + de.logger.Error("Dashboard server failed", zap.Error(err)) + } + }() + + de.logger.Info("Opening browser on dashboard", zap.String("URL", de.instanceURL)) + de.openBrowser() + + go func() { + <-ctx.Done() + de.logger.Info("Shutting down dashboard exposer...") + if err := dashboardServer.Shutdown(context.Background()); err != nil { + de.logger.Error("Failed to shutdown dashboard server", zap.Error(err)) + } + }() +} + +// ========================================================================================================= + +func (de *DashboardExposer) openBrowser() { + if isWSL() { + err := exec.Command("cmd.exe", "/c", "start", de.instanceURL).Run() + if err != nil { + de.logger.Error("Failed to open browser in WSL", zap.Error(err)) + } + } else { + err := browser.OpenURL(de.instanceURL) + if err != nil { + de.logger.Error("Failed to open browser", zap.Error(err)) + } + } +} + +func isWSL() bool { + data, err := os.ReadFile("/proc/version") + if err != nil { + return false + } + content := strings.ToLower(string(data)) + return strings.Contains(content, "microsoft") || strings.Contains(content, "wsl") +} + +// ========================================================================================================= + +//go:embed out/* +var content embed.FS + +// fileSystem returns an http.FileSystem rooted at /. +func (de *DashboardExposer) fileSystem() http.FileSystem { + fsys, err := fs.Sub(content, "out") + if err != nil { + log.Fatal("embed fs setup failed:", err) + } + return http.FS(fsys) +} + +// Handler returns a ready-to-use http.Handler that: +// - Serves compressed assets transparently (if the browser supports it) +// - Falls back to index.html for unknown routes (handy for client-side routers) +func (de *DashboardExposer) handler() http.Handler { + fs := de.fileSystem() + fileServer := http.FileServer(fs) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Try the exact path first + if _, err := fs.Open(path.Clean(r.URL.Path)); err != nil { + // Not found? send SPA entry point + r.URL.Path = "/" + } + fileServer.ServeHTTP(w, r) + }) +} diff --git a/keploy/pkg/service/load/exporter.go b/keploy/pkg/service/load/exporter.go new file mode 100644 index 0000000..a187155 --- /dev/null +++ b/keploy/pkg/service/load/exporter.go @@ -0,0 +1,205 @@ +package load + +import ( + "context" + "encoding/json" + "net" + "net/http" + "strconv" + "sync" + "time" + + "github.com/gorilla/mux" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/service/secure" + "go.keploy.io/server/v2/pkg/service/testsuite" + "go.uber.org/zap" +) + +// Exporter Load Test Token, it contains unique identifier for the load test with the load test information. +type LTToken struct { + ID string `json:"id"` + URL string `json:"url"` + Title string `json:"title"` + CreatedAt time.Time `json:"created_at"` + Description string `json:"description"` + LoadOptions testsuite.LoadOptions `json:"load_options"` + SecurityReport *secure.SecurityReport `json:"security_report,omitempty"` +} + +type Exporter struct { + config *config.Config + logger *zap.Logger + ltToken *LTToken + isServed bool + vusReport []VUReport + mu sync.RWMutex +} + +// Exporter is responsible for providing endpoint to export the load test report in JSON format. +func NewExporter(cfg *config.Config, logger *zap.Logger, vus int, ltToken *LTToken) *Exporter { + return &Exporter{ + config: cfg, + logger: logger, + ltToken: ltToken, + isServed: false, + vusReport: make([]VUReport, vus), + } +} + +func (e *Exporter) GetMetrics(vuReport VUReport) { + e.mu.Lock() + defer e.mu.Unlock() + e.vusReport[vuReport.VUID] = vuReport + e.logger.Debug("VU Report collected", zap.Int("VUID", vuReport.VUID)) +} + +func (e *Exporter) StartServer(ctx context.Context) error { + // To serve LT Token to the dashboard + // ========================================================================================== + tokenRouter := mux.NewRouter() + tokenRouter.HandleFunc("/dashboards", e.HandleGETDashboards).Methods("GET") + + tokenServer := &http.Server{ + Addr: ":2345", + Handler: tokenRouter, + } + go func() { + tokenPortOK := false + for !tokenPortOK { + listener, err := net.Listen("tcp", ":2345") + if err != nil { + time.Sleep(100 * time.Millisecond) + continue + } + tokenPortOK = true + listener.Close() + } + go func() { + for { + e.mu.RLock() + isServed := e.isServed + e.mu.RUnlock() + + if isServed { + e.logger.Info("Dashboard token served successfully, shutting down token server") + tokenServer.Shutdown(context.Background()) + return + } + time.Sleep(100 * time.Millisecond) + } + }() + e.logger.Info("Starting token server on port 2345") + err := tokenServer.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + e.logger.Error("Failed to start token server", zap.Error(err)) + } + }() + // ========================================================================================== + + metricsRouter := mux.NewRouter() + metricsRouter.HandleFunc("/metrics", e.metricsHandler).Methods("GET") + + port := 9090 + portOK := false + for !portOK { + listener, err := net.Listen("tcp", ":"+strconv.Itoa(port)) + if err != nil { + e.logger.Warn("Failed to listen on port", zap.String("port", strconv.Itoa(port)), zap.Error(err)) + port++ + continue + } + portOK = true + listener.Close() + } + + metricsServer := &http.Server{ + Addr: ":" + strconv.Itoa(port), + Handler: metricsRouter, + } + + e.ltToken.URL = "http://localhost:" + strconv.Itoa(port) + "/metrics" + + go func() { + defer func() { + if r := recover(); r != nil { + e.logger.Error("Metrics server panicked", zap.Any("recover", r)) + } + }() + e.logger.Info("Metrics server starting on port", zap.Int("port", port)) + err := metricsServer.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + e.logger.Error("Failed to start metrics server", zap.Error(err)) + } + }() + + go func() { + <-ctx.Done() + e.logger.Info("Shutting down metrics server...") + // wait 1 second for the server to shutdown gracefully + ctxShutdown, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + if err := metricsServer.Shutdown(ctxShutdown); err != nil { + e.logger.Error("Failed to shutdown metrics server", zap.Error(err)) + } + }() + + return nil +} + +func (e *Exporter) metricsHandler(res http.ResponseWriter, req *http.Request) { + res.Header().Set("Access-Control-Allow-Origin", "*") + res.Header().Set("Access-Control-Allow-Methods", "GET") + res.Header().Set("Content-Type", "application/json") + + // vusReportCopy := make([]VUReport, len(e.vusReport)) + // for i, report := range e.vusReport { + // vusReportCopy[i].VUID = report.VUID + // vusReportCopy[i].TSExecCount = report.TSExecCount + // vusReportCopy[i].TSExecFailure = report.TSExecFailure + // vusReportCopy[i].TSExecTime = make([]time.Duration, len(report.TSExecTime)) + // copy(vusReportCopy[i].TSExecTime, report.TSExecTime) + // vusReportCopy[i].Steps = make([]StepReport, len(report.Steps)) + // for j, step := range report.Steps { + // vusReportCopy[i].Steps[j].StepName = step.StepName + // vusReportCopy[i].Steps[j].StepCount = step.StepCount + // vusReportCopy[i].Steps[j].StepFailure = step.StepFailure + // vusReportCopy[i].Steps[j].StepResponseTime = make([]time.Duration, len(step.StepResponseTime)) + // copy(vusReportCopy[i].Steps[j].StepResponseTime, step.StepResponseTime) + // vusReportCopy[i].Steps[j].StepBytesIn = step.StepBytesIn + // vusReportCopy[i].Steps[j].StepBytesOut = step.StepBytesOut + // } + // } + + encoder := json.NewEncoder(res) + encoder.SetEscapeHTML(false) + e.mu.RLock() + err := encoder.Encode(e.vusReport) + defer e.mu.RUnlock() + if err != nil { + e.logger.Error("Failed to encode VU reports", zap.Error(err)) + res.WriteHeader(http.StatusInternalServerError) + return + } +} + +func (e *Exporter) HandleGETDashboards(res http.ResponseWriter, req *http.Request) { + res.Header().Set("Access-Control-Allow-Origin", "*") + res.Header().Set("Access-Control-Allow-Methods", "GET") + res.Header().Set("Content-Type", "application/json") + + e.mu.Lock() + defer e.mu.Unlock() + + // Marshal the LTToken to JSON + tokenData, err := json.Marshal(e.ltToken) + if err != nil { + e.logger.Error("Failed to marshal LTToken", zap.Error(err)) + res.WriteHeader(http.StatusInternalServerError) + e.isServed = true + return + } + + res.Write(tokenData) + e.isServed = true +} diff --git a/keploy/pkg/service/load/load.go b/keploy/pkg/service/load/load.go new file mode 100644 index 0000000..b60d233 --- /dev/null +++ b/keploy/pkg/service/load/load.go @@ -0,0 +1,239 @@ +package load + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/service/secure" + "go.keploy.io/server/v2/pkg/service/testsuite" + "go.uber.org/zap" +) + +type LTReport struct { + TestSuiteFile string `json:"test_suite_file"` + VUs int `json:"vus"` + Duration string `json:"duration"` + RPS int `json:"rps"` + Steps []StepThresholdReport `json:"steps"` +} + +type LoadTester struct { + config *config.Config + logger *zap.Logger + testsuite *testsuite.TestSuite + loadTestID string + profile string + vus int + duration string + rps int +} + +func NewLoadTester(cfg *config.Config, logger *zap.Logger) (*LoadTester, error) { + testsuitePath := filepath.Join(cfg.TestSuite.TSPath, cfg.TestSuite.TSFile) + logger.Info("Parsing TestSuite File", zap.String("path", testsuitePath)) + + testsuite, err := testsuite.TSParser(testsuitePath) + if err != nil { + logger.Error("Failed to parse TestSuite file", zap.Error(err)) + return nil, fmt.Errorf("failed to parse TestSuite file: %w", err) + } + + if testsuite.Spec.Load.Profile == "" { + testsuite.Spec.Load.Profile = "constant_vus" + logger.Info("Load profile not specified, defaulting to 'constant_vus'") + } + + return &LoadTester{ + config: cfg, + logger: logger, + testsuite: &testsuite, + loadTestID: time.Now().Format("20060102_150405"), + profile: testsuite.Spec.Load.Profile, + vus: testsuite.Spec.Load.VUs, + duration: testsuite.Spec.Load.Duration, + rps: testsuite.Spec.Load.RPS, + }, nil +} + +func (lt *LoadTester) Start(ctx context.Context) error { + // looks for CLI overrides + if ctx.Value("vus") != nil && ctx.Value("vus") != 1 && lt.profile == "constant_vus" { + lt.vus = ctx.Value("vus").(int) + lt.logger.Debug("Overriding VUs from CLI", zap.Int("vus", lt.vus)) + } + if ctx.Value("duration") != nil && ctx.Value("duration") != "" && lt.profile == "constant_vus" { + lt.duration = ctx.Value("duration").(string) + lt.logger.Debug("Overriding duration from CLI", zap.String("duration", lt.duration)) + } + if ctx.Value("rps") != nil && ctx.Value("rps") != 0 && lt.profile == "constant_vus" { + lt.rps = ctx.Value("rps").(int) + lt.logger.Debug("Overriding RPS from CLI", zap.Int("rps", lt.rps)) + } + + // init load options with values from testsuite spec and CLI overrides + loadOptions := &testsuite.LoadOptions{ + Profile: lt.profile, + VUs: lt.vus, + Duration: lt.duration, + RPS: lt.rps, + Stages: lt.testsuite.Spec.Load.Stages, + Thresholds: lt.testsuite.Spec.Load.Thresholds, + } + + ltToken := <Token{ + ID: lt.loadTestID, + URL: "http://localhost:9090/metrics", + Title: lt.testsuite.Name, + Description: lt.testsuite.Spec.Metadata.Description, + LoadOptions: *loadOptions, + } + + lt.logger.Info("Starting load test", + zap.Int("vus", lt.vus), + zap.String("duration", lt.duration), + zap.Int("rps", lt.rps), + ) + + securityChecker, err := secure.NewSecurityChecker(lt.config, lt.logger) + if err != nil { + lt.logger.Error("Failed to create security checker", zap.Error(err)) + } + + securityReport, err := securityChecker.Start(ctx) + if err != nil { + lt.logger.Error("Failed to start security checker", zap.Error(err)) + } + + ltToken.SecurityReport = securityReport + + dashboardExposer := NewDashboardExposer(lt.config, lt.logger, lt.loadTestID) + exporter := NewExporter(lt.config, lt.logger, lt.vus, ltToken) + mc := NewMetricsCollector(lt.config, lt.logger, lt.vus) + scheduler := NewScheduler(lt.logger, lt.config, loadOptions, lt.testsuite, mc) + + if err := scheduler.Run(ctx, exporter, dashboardExposer); err != nil { + lt.logger.Error("Failed to run load test", zap.Error(err)) + return fmt.Errorf("failed to run load test: %w", err) + } + + steps := mc.SetStepsMetrics() + te := NewThresholdEvaluator(lt.config, lt.logger, lt.testsuite) + report := te.Evaluate(steps) + + lt.printCLISummary(report) + + ltReport := LTReport{ + TestSuiteFile: lt.config.TestSuite.TSFile, + VUs: lt.vus, + Duration: lt.duration, + RPS: lt.rps, + Steps: report, + } + + err = lt.saveJSONReport(ltReport) + if err != nil { + lt.logger.Error("Failed to save JSON report", zap.Error(err)) + } + + lt.logger.Info("Load test completed", zap.String("tsFile", lt.config.TestSuite.TSFile)) + return nil +} + +func (lt *LoadTester) printCLISummary(report []StepThresholdReport) { + lt.logger.Info("Load test summary", + zap.String("tsFile", lt.config.TestSuite.TSFile), + zap.Int("vus", lt.vus), + zap.String("duration", lt.duration), + zap.Int("rps", lt.rps), + ) + + // Total Requests: 3,000 + // Failures: 18 (0.6%) + // P95 Latency: 460ms + // Data Sent: 1.2 MB + // Data Received: 5.4 MB + + // Thresholds: + // ✓ http_req_duration_p95 < 500ms + // ✗ http_req_failed_rate <= 1% + // ✓ data_received > 1MB + + // Test Result: ❌ FAILED (1 critical threshold breached) + + for _, stepReport := range report { + thresholdStatus := make(map[string]int) + testResultStatus := "PASSED" + + fmt.Println("Step:", stepReport.StepName) + fmt.Printf(" Total Requests: %d\n", stepReport.TotalRequests) + fmt.Printf(" Failures: %d (%.2f%%)\n", stepReport.TotalFailures, + float64(stepReport.TotalFailures)/float64(stepReport.TotalRequests)*100) + fmt.Printf(" P95 Latency: %s\n", stepReport.P95Latency) + fmt.Printf(" Data Sent: %.2f MB\n", float64(stepReport.TotalBytesOut)/(1024*1024)) + fmt.Printf(" Data Received: %.2f MB\n", float64(stepReport.TotalBytesIn)/(1024*1024)) + fmt.Println(" Thresholds:") + + for _, th := range stepReport.Thresholds { + status := "✓" + if !th.Pass { + status = "✗" + thresholdStatus[th.Severity]++ + testResultStatus = "FAILED" + } + fmt.Printf(" %s %-25s %-25s \tActual(%v)\n", status, th.Metric, fmt.Sprintf("condition(%s)", th.Condition), th.Actual) + } + + if testResultStatus == "FAILED" { + fmt.Printf(" Test Result: ❌ %s ", testResultStatus) + if thresholdStatus["critical"] > 0 { + fmt.Printf("(%d critical threshold breached) ", thresholdStatus["critical"]) + } + if thresholdStatus["high"] > 0 { + fmt.Printf("(%d high threshold breached) ", thresholdStatus["high"]) + } + if thresholdStatus["medium"] > 0 { + fmt.Printf("(%d medium threshold breached) ", thresholdStatus["medium"]) + } + if thresholdStatus["low"] > 0 { + fmt.Printf("(%d low threshold breached) ", thresholdStatus["low"]) + } + fmt.Printf("\n") + } else { + fmt.Printf(" Test Result: ✅ %s\n", testResultStatus) + } + fmt.Println(strings.Repeat("-", 100)) + } +} + +func (lt *LoadTester) saveJSONReport(report LTReport) error { + err := os.MkdirAll(filepath.Join("keploy", "load", "reports"), 0755) + if err != nil { + lt.logger.Error("Failed to create reports directory", zap.Error(err)) + return fmt.Errorf("failed to create reports directory: %w", err) + } + filePath := filepath.Join("keploy", "load", "reports", + fmt.Sprintf("%s_%s.json", time.Now().Format("20060102_150405"), strings.TrimSuffix(lt.config.TestSuite.TSFile, filepath.Ext(lt.config.TestSuite.TSFile)))) + file, err := os.Create(filePath) + if err != nil { + lt.logger.Error("Failed to create output file", zap.Error(err)) + return fmt.Errorf("failed to create output file: %w", err) + } + defer file.Close() + + encoder := json.NewEncoder(file) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", " ") + if err := encoder.Encode(report); err != nil { + lt.logger.Error("Failed to encode report to JSON", zap.Error(err)) + return fmt.Errorf("failed to encode report to JSON: %w", err) + } + + lt.logger.Info("Report saved successfully", zap.String("output", filePath)) + return nil +} diff --git a/keploy/pkg/service/load/metrics_collector.go b/keploy/pkg/service/load/metrics_collector.go new file mode 100644 index 0000000..c259051 --- /dev/null +++ b/keploy/pkg/service/load/metrics_collector.go @@ -0,0 +1,92 @@ +package load + +import ( + "sync" + "time" + + "go.keploy.io/server/v2/config" + "go.uber.org/zap" +) + +type MetricsCollector struct { + config *config.Config + logger *zap.Logger + VUsReports []VUReport + mu sync.RWMutex +} + +type StepMetrics struct { + StepName string + StepCount int + StepFailure int + StepResponseTime []time.Duration + StepBytesIn int64 + StepBytesOut int64 +} + +func NewMetricsCollector(config *config.Config, logger *zap.Logger, vus int) *MetricsCollector { + return &MetricsCollector{ + config: config, + logger: logger, + VUsReports: make([]VUReport, vus), + } +} + +func (mc *MetricsCollector) SetStepsMetrics() []StepMetrics { + mc.mu.RLock() + defer mc.mu.RUnlock() + + if len(mc.VUsReports) == 0 || len(mc.VUsReports[0].Steps) == 0 { + mc.logger.Warn("No VU reports or steps available for metrics calculation") + return nil + } + + steps := make([]StepMetrics, len(mc.VUsReports[0].Steps)) + for i, vuReport := range mc.VUsReports { + for j, step := range vuReport.Steps { + // Initialize per step metrics. step 1, 2, 3 and so on. it the same step across all VUs but with different results. + if i == 0 { + steps[j] = StepMetrics{ + StepName: step.StepName, + StepCount: 0, + StepFailure: 0, + StepResponseTime: make([]time.Duration, 0), + StepBytesIn: 0, + StepBytesOut: 0, + } + } + // collecting the results from different VUs into one place to operate on later. + steps[j].StepCount += step.StepCount + steps[j].StepFailure += step.StepFailure + steps[j].StepResponseTime = append(steps[j].StepResponseTime, step.StepResponseTime...) + steps[j].StepBytesIn += step.StepBytesIn + steps[j].StepBytesOut += step.StepBytesOut + } + } + + for _, step := range steps { + mc.logger.Debug("Step Metrics", + zap.String("stepName", step.StepName), + zap.Int("stepCount", step.StepCount), + zap.Int("stepFailure", step.StepFailure), + zap.Any("stepResponseTime", step.StepResponseTime), + zap.Int64("stepBytesIn", step.StepBytesIn), + zap.Int64("stepBytesOut", step.StepBytesOut), + ) + } + + return steps +} + +func (mc *MetricsCollector) CollectVUReport(vuReport *VUReport) { + mc.mu.Lock() + defer mc.mu.Unlock() + mc.VUsReports[vuReport.VUID] = *vuReport + mc.logger.Debug("VU Report collected", + zap.Int("vuID", vuReport.VUID), + zap.Int("tsExecCount", vuReport.TSExecCount), + zap.Int("tsExecFailure", vuReport.TSExecFailure), + zap.Any("tsExecTime", vuReport.TSExecTime), + zap.Int("totalVUs", len(mc.VUsReports)), + ) +} diff --git a/keploy/pkg/service/load/out/404.html b/keploy/pkg/service/load/out/404.html new file mode 100644 index 0000000..f9517cb --- /dev/null +++ b/keploy/pkg/service/load/out/404.html @@ -0,0 +1 @@ +404: This page could not be found.KLT Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/keploy/pkg/service/load/out/404/index.html b/keploy/pkg/service/load/out/404/index.html new file mode 100644 index 0000000..f9517cb --- /dev/null +++ b/keploy/pkg/service/load/out/404/index.html @@ -0,0 +1 @@ +404: This page could not be found.KLT Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_buildManifest.js b/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_buildManifest.js new file mode 100644 index 0000000..d1a8a87 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_buildManifest.js @@ -0,0 +1 @@ +self.__BUILD_MANIFEST=function(e,r,t){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:2,errorRate:1e-4,numBits:39,numHashes:14,bitArray:[0,1,1,0,r,e,e,r,r,e,e,r,e,e,e,r,r,e,e,e,e,r,e,r,r,r,r,e,e,e,r,e,r,e,r,e,e,e,r]},__routerFilterDynamic:{numItems:r,errorRate:1e-4,numBits:r,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-03529f2c21436739.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB(); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_ssgManifest.js b/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_ssgManifest.js new file mode 100644 index 0000000..5b3ff59 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_ssgManifest.js @@ -0,0 +1 @@ +self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB() \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js b/keploy/pkg/service/load/out/_next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js new file mode 100644 index 0000000..697b728 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[441],{9248:(e,n,t)=>{var r,l=t(9509),a=t(6206),o=t(2115),u=t(7650);function i(e){var n="https://react.dev/errors/"+e;if(1I||(e.current=R[I],R[I]=null,I--)}function j(e,n){R[++I]=e.current,e.current=n}var V=U(null),H=U(null),Q=U(null),B=U(null);function W(e,n){switch(j(Q,n),j(H,e),j(V,null),n.nodeType){case 9:case 11:e=(e=n.documentElement)&&(e=e.namespaceURI)?sh(e):0;break;default:if(e=n.tagName,n=n.namespaceURI)e=sg(n=sh(n),e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}$(V),j(V,e)}function q(){$(V),$(H),$(Q)}function K(e){null!==e.memoizedState&&j(B,e);var n=V.current,t=sg(n,e.type);n!==t&&(j(H,e),j(V,t))}function Y(e){H.current===e&&($(V),$(H)),B.current===e&&($(B),s9._currentValue=A)}function X(e){if(void 0===nI)try{throw Error()}catch(e){var n=e.stack.trim().match(/\n( *(at )?)/);nI=n&&n[1]||"",nU=-1)":-1l||i[r]!==s[l]){var c="\n"+i[r].replace(" at new "," at ");return e.displayName&&c.includes("")&&(c=c.replace("",e.displayName)),c}while(1<=r&&0<=l);break}}}finally{G=!1,Error.prepareStackTrace=t}return(t=e?e.displayName||e.name:"")?X(t):""}function J(e){try{var n="",t=null;do n+=function(e,n){switch(e.tag){case 26:case 27:case 5:return X(e.type);case 16:return X("Lazy");case 13:return e.child!==n&&null!==n?X("Suspense Fallback"):X("Suspense");case 19:return X("SuspenseList");case 0:case 15:return Z(e.type,!1);case 11:return Z(e.type.render,!1);case 1:return Z(e.type,!0);case 31:return X("Activity");default:return""}}(e,t),t=e,e=e.return;while(e);return n}catch(e){return"\nError generating stack: "+e.message+"\n"+e.stack}}var ee=Object.prototype.hasOwnProperty,en=a.unstable_scheduleCallback,et=a.unstable_cancelCallback,er=a.unstable_shouldYield,el=a.unstable_requestPaint,ea=a.unstable_now,eo=a.unstable_getCurrentPriorityLevel,eu=a.unstable_ImmediatePriority,ei=a.unstable_UserBlockingPriority,es=a.unstable_NormalPriority,ec=a.unstable_LowPriority,ef=a.unstable_IdlePriority,ed=a.log,ep=a.unstable_setDisableYieldValue,em=null,eh=null;function eg(e){if("function"==typeof ed&&ep(e),eh&&"function"==typeof eh.setStrictMode)try{eh.setStrictMode(em,e)}catch(e){}}var ey=Math.clz32?Math.clz32:function(e){return 0==(e>>>=0)?32:31-(ev(e)/eb|0)|0},ev=Math.log,eb=Math.LN2,ek=256,ew=4194304;function eS(e){var n=42&e;if(0!==n)return n;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return 4194048&e;case 4194304:case 8388608:case 0x1000000:case 0x2000000:return 0x3c00000&e;case 0x4000000:return 0x4000000;case 0x8000000:return 0x8000000;case 0x10000000:return 0x10000000;case 0x20000000:return 0x20000000;case 0x40000000:return 0;default:return e}}function ex(e,n,t){var r=e.pendingLanes;if(0===r)return 0;var l=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var u=0x7ffffff&r;return 0!==u?0!=(r=u&~a)?l=eS(r):0!=(o&=u)?l=eS(o):t||0!=(t=u&~e)&&(l=eS(t)):0!=(u=r&~a)?l=eS(u):0!==o?l=eS(o):t||0!=(t=r&~e)&&(l=eS(t)),0===l?0:0!==n&&n!==l&&0==(n&a)&&((a=l&-l)>=(t=n&-n)||32===a&&0!=(4194048&t))?n:l}function eE(e,n){return 0==(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&n)}function ez(){var e=ek;return 0==(4194048&(ek<<=1))&&(ek=256),e}function eC(){var e=ew;return 0==(0x3c00000&(ew<<=1))&&(ew=4194304),e}function eP(e){for(var n=[],t=0;31>t;t++)n.push(e);return n}function eN(e,n){e.pendingLanes|=n,0x10000000!==n&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function eL(e,n,t){e.pendingLanes|=n,e.suspendedLanes&=~n;var r=31-ey(n);e.entangledLanes|=n,e.entanglements[r]=0x40000000|e.entanglements[r]|4194090&t}function eT(e,n){var t=e.entangledLanes|=n;for(e=e.entanglements;t;){var r=31-ey(t),l=1<=tr),to=!1;function tu(e,n){switch(e){case"keyup":return -1!==tn.indexOf(n.keyCode);case"keydown":return 229!==n.keyCode;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function ti(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var ts=!1,tc={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function tf(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===n?!!tc[e.type]:"textarea"===n}function td(e,n,t,r){nw?nS?nS.push(r):nS=[r]:nw=r,0<(n=st(n,"onChange")).length&&(t=new nQ("onChange","change",null,t,r),e.push({event:t,listeners:n}))}var tp=null,tm=null;function th(e){i4(e,0)}function tg(e){if(ne(eK(e)))return e}function ty(e,n){if("change"===e)return n}var tv=!1;if(nP){if(nP){var tb="oninput"in document;if(!tb){var tk=document.createElement("div");tk.setAttribute("oninput","return;"),tb="function"==typeof tk.oninput}r=tb}else r=!1;tv=r&&(!document.documentMode||9=n)return{node:r,offset:n-e};e=t}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=tL(r)}}function t_(e){e=null!=e&&null!=e.ownerDocument&&null!=e.ownerDocument.defaultView?e.ownerDocument.defaultView:window;for(var n=nn(e.document);n instanceof e.HTMLIFrameElement;){try{var t="string"==typeof n.contentWindow.location.href}catch(e){t=!1}if(t)e=n.contentWindow;else break;n=nn(e.document)}return n}function tF(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return n&&("input"===n&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===n||"true"===e.contentEditable)}var tD=nP&&"documentMode"in document&&11>=document.documentMode,tO=null,tM=null,tA=null,tR=!1;function tI(e,n,t){var r=t.window===t?t.document:9===t.nodeType?t:t.ownerDocument;tR||null==tO||tO!==nn(r)||(r="selectionStart"in(r=tO)&&tF(r)?{start:r.selectionStart,end:r.selectionEnd}:{anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},tA&&tN(tA,r)||(tA=r,0<(r=st(tM,"onSelect")).length&&(n=new nQ("onSelect","select",null,n,t),e.push({event:n,listeners:r}),n.target=tO)))}function tU(e,n){var t={};return t[e.toLowerCase()]=n.toLowerCase(),t["Webkit"+e]="webkit"+n,t["Moz"+e]="moz"+n,t}var t$={animationend:tU("Animation","AnimationEnd"),animationiteration:tU("Animation","AnimationIteration"),animationstart:tU("Animation","AnimationStart"),transitionrun:tU("Transition","TransitionRun"),transitionstart:tU("Transition","TransitionStart"),transitioncancel:tU("Transition","TransitionCancel"),transitionend:tU("Transition","TransitionEnd")},tj={},tV={};function tH(e){if(tj[e])return tj[e];if(!t$[e])return e;var n,t=t$[e];for(n in t)if(t.hasOwnProperty(n)&&n in tV)return tj[e]=t[n];return e}nP&&(tV=document.createElement("div").style,"AnimationEvent"in window||(delete t$.animationend.animation,delete t$.animationiteration.animation,delete t$.animationstart.animation),"TransitionEvent"in window||delete t$.transitionend.transition);var tQ=tH("animationend"),tB=tH("animationiteration"),tW=tH("animationstart"),tq=tH("transitionrun"),tK=tH("transitionstart"),tY=tH("transitioncancel"),tX=tH("transitionend"),tG=new Map,tZ="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function tJ(e,n){tG.set(e,n),eJ(n,[e])}tZ.push("scrollEnd");var t0="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var n=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(n))return}else if("object"==typeof l&&"function"==typeof l.emit)return void l.emit("uncaughtException",e);console.error(e)},t1=[],t2=0,t3=0;function t4(){for(var e=t2,n=t3=t2=0;n>=o,l-=o,rk=1<<32-ey(n)+l|t<h?(g=f,f=null):g=f.sibling;var y=p(l,f,u[h],i);if(null===y){null===f&&(f=g);break}e&&f&&null===y.alternate&&n(l,f),o=a(y,o,h),null===c?s=y:c.sibling=y,c=y,f=g}if(h===u.length)return t(l,f),rL&&rS(l,h),s;if(null===f){for(;hg?(y=h,h=null):y=h.sibling;var b=p(l,h,v.value,s);if(null===b){null===h&&(h=y);break}e&&h&&null===b.alternate&&n(l,h),o=a(b,o,g),null===f?c=b:f.sibling=b,f=b,h=y}if(v.done)return t(l,h),rL&&rS(l,g),c;if(null===h){for(;!v.done;g++,v=u.next())null!==(v=d(l,v.value,s))&&(o=a(v,o,g),null===f?c=v:f.sibling=v,f=v);return rL&&rS(l,g),c}for(h=r(h);!v.done;g++,v=u.next())null!==(v=m(h,l,g,v.value,s))&&(e&&null!==v.alternate&&h.delete(null===v.key?g:v.key),o=a(v,o,g),null===f?c=v:f.sibling=v,f=v);return e&&h.forEach(function(e){return n(l,e)}),rL&&rS(l,g),c}(s,c,f=b.call(f),h)}if("function"==typeof f.then)return u(s,c,lg(f),h);if(f.$$typeof===S)return u(s,c,rG(s,f),h);lv(s,f)}return"string"==typeof f&&""!==f||"number"==typeof f||"bigint"==typeof f?(f=""+f,null!==c&&6===c.tag?(t(s,c.sibling),(h=l(c,f)).return=s):(t(s,c),(h=ri(f,s.mode,h)).return=s),o(s=h)):t(s,c)}(u,s,c,f);return lm=null,h}catch(e){if(e===la||e===lu)throw e;var b=rt(29,e,null,u.mode);return b.lanes=f,b.return=u,b}finally{}}}var lw=lk(!0),lS=lk(!1),lx=!1;function lE(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function lz(e,n){e=e.updateQueue,n.updateQueue===e&&(n.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function lC(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function lP(e,n,t){var r=e.updateQueue;if(null===r)return null;if(r=r.shared,0!=(2&uO)){var l=r.pending;return null===l?n.next=n:(n.next=l.next,l.next=n),r.pending=n,n=t7(e),t9(e,null,t),n}return t8(e,r,n,t),t7(e)}function lN(e,n,t){if(null!==(n=n.updateQueue)&&(n=n.shared,0!=(4194048&t))){var r=n.lanes;r&=e.pendingLanes,t|=r,n.lanes=t,eT(e,t)}}function lL(e,n){var t=e.updateQueue,r=e.alternate;if(null!==r&&t===(r=r.updateQueue)){var l=null,a=null;if(null!==(t=t.firstBaseUpdate)){do{var o={lane:t.lane,tag:t.tag,payload:t.payload,callback:null,next:null};null===a?l=a=o:a=a.next=o,t=t.next}while(null!==t);null===a?l=a=n:a=a.next=n}else l=a=n;t={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=t;return}null===(e=t.lastBaseUpdate)?t.firstBaseUpdate=n:e.next=n,t.lastBaseUpdate=n}var lT=!1;function l_(){if(lT){var e=r9;if(null!==e)throw e}}function lF(e,n,t,r){lT=!1;var l=e.updateQueue;lx=!1;var a=l.firstBaseUpdate,o=l.lastBaseUpdate,u=l.shared.pending;if(null!==u){l.shared.pending=null;var i=u,s=i.next;i.next=null,null===o?a=s:o.next=s,o=i;var c=e.alternate;null!==c&&(u=(c=c.updateQueue).lastBaseUpdate)!==o&&(null===u?c.firstBaseUpdate=s:u.next=s,c.lastBaseUpdate=i)}if(null!==a){var f=l.baseState;for(o=0,c=s=i=null,u=a;;){var d=-0x20000001&u.lane,p=d!==u.lane;if(p?(uR&d)===d:(r&d)===d){0!==d&&d===r6&&(lT=!0),null!==c&&(c=c.next={lane:0,tag:u.tag,payload:u.payload,callback:null,next:null});e:{var h=e,g=u;switch(d=n,g.tag){case 1:if("function"==typeof(h=g.payload)){f=h.call(t,f,d);break e}f=h;break e;case 3:h.flags=-65537&h.flags|128;case 0:if(null==(d="function"==typeof(h=g.payload)?h.call(t,f,d):h))break e;f=m({},f,d);break e;case 2:lx=!0}}null!==(d=u.callback)&&(e.flags|=64,p&&(e.flags|=8192),null===(p=l.callbacks)?l.callbacks=[d]:p.push(d))}else p={lane:d,tag:u.tag,payload:u.payload,callback:u.callback,next:null},null===c?(s=c=p,i=f):c=c.next=p,o|=d;if(null===(u=u.next))if(null===(u=l.shared.pending))break;else u=(p=u).next,p.next=null,l.lastBaseUpdate=p,l.shared.pending=null}null===c&&(i=f),l.baseState=i,l.firstBaseUpdate=s,l.lastBaseUpdate=c,null===a&&(l.shared.lanes=0),uB|=o,e.lanes=o,e.memoizedState=f}}function lD(e,n){if("function"!=typeof e)throw Error(i(191,e));e.call(n)}function lO(e,n){var t=e.callbacks;if(null!==t)for(e.callbacks=null,e=0;ea?a:8;var o=O.T,u={};O.T=u,a9(e,!1,n,t);try{var i=l(),s=O.S;if(null!==s&&s(u,i),null!==i&&"object"==typeof i&&"function"==typeof i.then){var c,f,d=(c=[],f={status:"pending",value:null,reason:null,then:function(e){c.push(e)}},i.then(function(){f.status="fulfilled",f.value=r;for(var e=0;e title"))),sf(a,r,t),a[eR]=e,eX(a),r=a;break e;case"link":var o=s0("link","href",l).get(r+(t.href||""));if(o){for(var u=0;u<\/script>",a=a.removeChild(a.firstChild);break;case"select":a="string"==typeof r.is?o.createElement("select",{is:r.is}):o.createElement("select"),r.multiple?a.multiple=!0:r.size&&(a.size=r.size);break;default:a="string"==typeof r.is?o.createElement(l,{is:r.is}):o.createElement(l)}}a[eR]=n,a[eI]=r;e:for(o=n.child;null!==o;){if(5===o.tag||6===o.tag)a.appendChild(o.stateNode);else if(4!==o.tag&&27!==o.tag&&null!==o.child){o.child.return=o,o=o.child;continue}if(o===n)break;for(;null===o.sibling;){if(null===o.return||o.return===n)break e;o=o.return}o.sibling.return=o.return,o=o.sibling}switch(n.stateNode=a,sf(a,l,r),l){case"button":case"input":case"select":case"textarea":r=!!r.autoFocus;break;case"img":r=!0;break;default:r=!1}r&&oq(n)}}return oZ(n),oK(n,n.type,null===e?null:e.memoizedProps,n.pendingProps,t),null;case 6:if(e&&null!=n.stateNode)e.memoizedProps!==r&&oq(n);else{if("string"!=typeof r&&null===n.stateNode)throw Error(i(166));if(e=Q.current,rA(n)){if(e=n.stateNode,t=n.memoizedProps,r=null,null!==(l=rP))switch(l.tag){case 27:case 5:r=l.memoizedProps}e[eR]=n,(e=!!(e.nodeValue===t||null!==r&&!0===r.suppressHydrationWarning||si(e.nodeValue,t)))||rD(n,!0)}else(e=sm(e).createTextNode(r))[eR]=n,n.stateNode=e}return oZ(n),null;case 31:if(t=n.memoizedState,null===e||null!==e.memoizedState){if(r=rA(n),null!==t){if(null===e){if(!r)throw Error(i(318));if(!(e=null!==(e=n.memoizedState)?e.dehydrated:null))throw Error(i(557));e[eR]=n}else rR(),0==(128&n.flags)&&(n.memoizedState=null),n.flags|=4;oZ(n),e=!1}else t=rI(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=t),e=!0;if(!e){if(256&n.flags)return lW(n),n;return lW(n),null}if(0!=(128&n.flags))throw Error(i(558))}return oZ(n),null;case 13:if(r=n.memoizedState,null===e||null!==e.memoizedState&&null!==e.memoizedState.dehydrated){if(l=rA(n),null!==r&&null!==r.dehydrated){if(null===e){if(!l)throw Error(i(318));if(!(l=null!==(l=n.memoizedState)?l.dehydrated:null))throw Error(i(317));l[eR]=n}else rR(),0==(128&n.flags)&&(n.memoizedState=null),n.flags|=4;oZ(n),l=!1}else l=rI(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=l),l=!0;if(!l){if(256&n.flags)return lW(n),n;return lW(n),null}}if(lW(n),0!=(128&n.flags))return n.lanes=t,n;return t=null!==r,e=null!==e&&null!==e.memoizedState,t&&(r=n.child,l=null,null!==r.alternate&&null!==r.alternate.memoizedState&&null!==r.alternate.memoizedState.cachePool&&(l=r.alternate.memoizedState.cachePool.pool),a=null,null!==r.memoizedState&&null!==r.memoizedState.cachePool&&(a=r.memoizedState.cachePool.pool),a!==l&&(r.flags|=2048)),t!==e&&t&&(n.child.flags|=8192),oX(n,n.updateQueue),oZ(n),null;case 4:return q(),null===e&&i9(n.stateNode.containerInfo),oZ(n),null;case 10:return rQ(n.type),oZ(n),null;case 19:if($(lq),null===(r=n.memoizedState))return oZ(n),null;if(l=0!=(128&n.flags),null===(a=r.rendering))if(l)oG(r,!1);else{if(0!==uQ||null!==e&&0!=(128&e.flags))for(e=n.child;null!==e;){if(null!==(a=lK(e))){for(n.flags|=128,oG(r,!1),n.updateQueue=e=a.updateQueue,oX(n,e),n.subtreeFlags=0,e=t,t=n.child;null!==t;)ra(t,e),t=t.sibling;return j(lq,1&lq.current|2),rL&&rS(n,r.treeForkCount),n.child}e=e.sibling}null!==r.tail&&ea()>u0&&(n.flags|=128,l=!0,oG(r,!1),n.lanes=4194304)}else{if(!l)if(null!==(e=lK(a))){if(n.flags|=128,l=!0,n.updateQueue=e=e.updateQueue,oX(n,e),oG(r,!0),null===r.tail&&"hidden"===r.tailMode&&!a.alternate&&!rL)return oZ(n),null}else 2*ea()-r.renderingStartTime>u0&&0x20000000!==t&&(n.flags|=128,l=!0,oG(r,!1),n.lanes=4194304);r.isBackwards?(a.sibling=n.child,n.child=a):(null!==(e=r.last)?e.sibling=a:n.child=a,r.last=a)}if(null!==r.tail)return e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=ea(),e.sibling=null,t=lq.current,j(lq,l?1&t|2:1&t),rL&&rS(n,r.treeForkCount),e;return oZ(n),null;case 22:case 23:return lW(n),lU(),r=null!==n.memoizedState,null!==e?null!==e.memoizedState!==r&&(n.flags|=8192):r&&(n.flags|=8192),r?0!=(0x20000000&t)&&0==(128&n.flags)&&(oZ(n),6&n.subtreeFlags&&(n.flags|=8192)):oZ(n),null!==(t=n.updateQueue)&&oX(n,t.retryQueue),t=null,null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(t=e.memoizedState.cachePool.pool),r=null,null!==n.memoizedState&&null!==n.memoizedState.cachePool&&(r=n.memoizedState.cachePool.pool),r!==t&&(n.flags|=2048),null!==e&&$(ln),null;case 24:return t=null,null!==e&&(t=e.memoizedState.cache),n.memoizedState.cache!==t&&(n.flags|=2048),rQ(r2),oZ(n),null;case 25:case 30:return null}throw Error(i(156,n.tag))}(n.alternate,n,uH);if(null!==t){uA=t;return}if(null!==(n=n.sibling)){uA=n;return}uA=n=e}while(null!==n);0===uQ&&(uQ=5)}function ix(e,n){do{var t=function(e,n){switch(rz(n),n.tag){case 1:return 65536&(e=n.flags)?(n.flags=-65537&e|128,n):null;case 3:return rQ(r2),q(),0!=(65536&(e=n.flags))&&0==(128&e)?(n.flags=-65537&e|128,n):null;case 26:case 27:case 5:return Y(n),null;case 31:if(null!==n.memoizedState){if(lW(n),null===n.alternate)throw Error(i(340));rR()}return 65536&(e=n.flags)?(n.flags=-65537&e|128,n):null;case 13:if(lW(n),null!==(e=n.memoizedState)&&null!==e.dehydrated){if(null===n.alternate)throw Error(i(340));rR()}return 65536&(e=n.flags)?(n.flags=-65537&e|128,n):null;case 19:return $(lq),null;case 4:return q(),null;case 10:return rQ(n.type),null;case 22:case 23:return lW(n),lU(),null!==e&&$(ln),65536&(e=n.flags)?(n.flags=-65537&e|128,n):null;case 24:return rQ(r2),null;default:return null}}(e.alternate,e);if(null!==t){t.flags&=32767,uA=t;return}if(null!==(t=e.return)&&(t.flags|=32768,t.subtreeFlags=0,t.deletions=null),!n&&null!==(e=e.sibling)){uA=e;return}uA=e=t}while(null!==e);uQ=6,uA=null}function iE(e,n,t,r,l,a,o,u,s){e.cancelPendingCommit=null;do iL();while(0!==u3);if(0!=(6&uO))throw Error(i(327));if(null!==n){if(n===e.current)throw Error(i(177));if(!function(e,n,t,r,l,a){var o=e.pendingLanes;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=t,e.entangledLanes&=t,e.errorRecoveryDisabledLanes&=t,e.shellSuspendCounter=0;var u=e.entanglements,i=e.expirationTimes,s=e.hiddenUpdates;for(t=o&~t;0g&&(o=g,g=h,h=o);var y=tT(u,h),v=tT(u,g);if(y&&v&&(1!==p.rangeCount||p.anchorNode!==y.node||p.anchorOffset!==y.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var b=f.createRange();b.setStart(y.node,y.offset),p.removeAllRanges(),h>g?(p.addRange(b),p.extend(v.node,v.offset)):(b.setEnd(v.node,v.offset),p.addRange(b))}}}}for(f=[],p=u;p=p.parentNode;)1===p.nodeType&&f.push({element:p,left:p.scrollLeft,top:p.scrollTop});for("function"==typeof u.focus&&u.focus(),u=0;ut?32:t,O.T=null,t=u9,u9=null;var a=u4,o=u5;if(u3=0,u8=u4=null,u5=0,0!=(6&uO))throw Error(i(331));var u=uO;if(uO|=4,uT(a.current),ux(a,a.current,o,t),uO=u,iB(0,!1),eh&&"function"==typeof eh.onPostCommitFiberRoot)try{eh.onPostCommitFiberRoot(em,a)}catch(e){}return!0}finally{M.p=l,O.T=r,iN(e,n)}}function i_(e,n,t){n=rd(t,n),n=og(e.stateNode,n,2),null!==(e=lP(e,n,2))&&(eN(e,2),iQ(e))}function iF(e,n,t){if(3===e.tag)i_(e,e,t);else for(;null!==n;){if(3===n.tag){i_(n,e,t);break}if(1===n.tag){var r=n.stateNode;if("function"==typeof n.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===u2||!u2.has(r))){e=rd(t,e),null!==(r=lP(n,t=oy(2),2))&&(ov(t,r,n,e),eN(r,2),iQ(r));break}}n=n.return}}function iD(e,n,t){var r=e.pingCache;if(null===r){r=e.pingCache=new uD;var l=new Set;r.set(n,l)}else void 0===(l=r.get(n))&&(l=new Set,r.set(n,l));l.has(t)||(uV=!0,l.add(t),e=iO.bind(null,e,n,t),n.then(e,e))}function iO(e,n,t){var r=e.pingCache;null!==r&&r.delete(n),e.pingedLanes|=e.suspendedLanes&t,e.warmLanes&=~t,uM===e&&(uR&t)===t&&(4===uQ||3===uQ&&(0x3c00000&uR)===uR&&300>ea()-uJ?0==(2&uO)&&id(e,0):uq|=t,uY===uR&&(uY=0)),iQ(e)}function iM(e,n){0===n&&(n=eC()),null!==(e=t6(e,n))&&(eN(e,n),iQ(e))}function iA(e){var n=e.memoizedState,t=0;null!==n&&(t=n.retryLane),iM(e,t)}function iR(e,n){var t=0;switch(e.tag){case 31:case 13:var r=e.stateNode,l=e.memoizedState;null!==l&&(t=l.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}null!==r&&r.delete(n),iM(e,t)}var iI=null,iU=null,i$=!1,ij=!1,iV=!1,iH=0;function iQ(e){e!==iU&&null===e.next&&(null===iU?iI=iU=e:iU=iU.next=e),ij=!0,i$||(i$=!0,sS(function(){0!=(6&uO)?en(eu,iW):iq()}))}function iB(e,n){if(!iV&&ij){iV=!0;do for(var t=!1,r=iI;null!==r;){if(!n)if(0!==e){var l=r.pendingLanes;if(0===l)var a=0;else{var o=r.suspendedLanes,u=r.pingedLanes;a=0xc000095&(a=(1<<31-ey(42|e)+1)-1&(l&~(o&~u)))?0xc000095&a|1:a?2|a:0}0!==a&&(t=!0,iX(r,a))}else a=uR,0==(3&(a=ex(r,r===uM?a:0,null!==r.cancelPendingCommit||-1!==r.timeoutHandle)))||eE(r,a)||(t=!0,iX(r,a));r=r.next}while(t);iV=!1}}function iW(){iq()}function iq(){ij=i$=!1;var e,n=0;0===iH||((e=window.event)&&"popstate"===e.type?e===sv||(sv=e,0):(sv=null,1))||(n=iH);for(var t=ea(),r=null,l=iI;null!==l;){var a=l.next,o=iK(l,t);0===o?(l.next=null,null===r?iI=a:r.next=a,null===a&&(iU=r)):(r=l,(0!==n||0!=(3&o))&&(ij=!0)),l=a}0!==u3&&5!==u3||iB(n,!1),0!==iH&&(iH=0)}function iK(e,n){for(var t=e.suspendedLanes,r=e.pingedLanes,l=e.expirationTimes,a=-0x3c00001&e.pendingLanes;0 title"):null)}function s2(e){return"stylesheet"!==e.type||0!=(3&e.state.loading)}var s3=null;function s4(){if(this.count--,0===this.count){if(this.stylesheets)s5(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var s8=null;function s5(e,n){e.stylesheets=null,null!==e.unsuspend&&(e.count++,s8=new Map,n.forEach(s6,e),s8=null,s4.call(e))}function s6(e,n){if(!(4&n.state.loading)){var t=s8.get(e);if(t)var r=t.get(null);else{t=new Map,s8.set(e,t);for(var l=e.querySelectorAll("link[data-precedence],style[data-precedence]"),a=0;a{"use strict";r.d(t,{B8:()=>j,bL:()=>A,l9:()=>T});var n=r(2115),i=r(5185),o=r(6081),a=r(9196),l=r(6101),u=r(2712),s=e=>{let{present:t,children:r}=e,i=function(e){var t,r;let[i,o]=n.useState(),a=n.useRef(null),l=n.useRef(e),s=n.useRef("none"),[f,d]=(t=e?"mounted":"unmounted",r={mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}},n.useReducer((e,t)=>{let n=r[e][t];return null!=n?n:e},t));return n.useEffect(()=>{let e=c(a.current);s.current="mounted"===f?e:"none"},[f]),(0,u.N)(()=>{let t=a.current,r=l.current;if(r!==e){let n=s.current,i=c(t);e?d("MOUNT"):"none"===i||(null==t?void 0:t.display)==="none"?d("UNMOUNT"):r&&n!==i?d("ANIMATION_OUT"):d("UNMOUNT"),l.current=e}},[e,d]),(0,u.N)(()=>{if(i){var e;let t,r=null!=(e=i.ownerDocument.defaultView)?e:window,n=e=>{let n=c(a.current).includes(e.animationName);if(e.target===i&&n&&(d("ANIMATION_END"),!l.current)){let e=i.style.animationFillMode;i.style.animationFillMode="forwards",t=r.setTimeout(()=>{"forwards"===i.style.animationFillMode&&(i.style.animationFillMode=e)})}},o=e=>{e.target===i&&(s.current=c(a.current))};return i.addEventListener("animationstart",o),i.addEventListener("animationcancel",n),i.addEventListener("animationend",n),()=>{r.clearTimeout(t),i.removeEventListener("animationstart",o),i.removeEventListener("animationcancel",n),i.removeEventListener("animationend",n)}}d("ANIMATION_END")},[i,d]),{isPresent:["mounted","unmountSuspended"].includes(f),ref:n.useCallback(e=>{a.current=e?getComputedStyle(e):null,o(e)},[])}}(t),o="function"==typeof r?r({present:i.isPresent}):n.Children.only(r),a=(0,l.s)(i.ref,function(e){var t,r;let n=null==(t=Object.getOwnPropertyDescriptor(e.props,"ref"))?void 0:t.get,i=n&&"isReactWarning"in n&&n.isReactWarning;return i?e.ref:(i=(n=null==(r=Object.getOwnPropertyDescriptor(e,"ref"))?void 0:r.get)&&"isReactWarning"in n&&n.isReactWarning)?e.props.ref:e.props.ref||e.ref}(o));return"function"==typeof r||i.isPresent?n.cloneElement(o,{ref:a}):null};function c(e){return(null==e?void 0:e.animationName)||"none"}s.displayName="Presence";var f=r(3655),d=r(4315),p=r(5845),h=r(1285),g=r(5155),v="Tabs",[m,y]=(0,o.A)(v,[a.RG]),b=(0,a.RG)(),[w,x]=m(v),S=n.forwardRef((e,t)=>{let{__scopeTabs:r,value:n,onValueChange:i,defaultValue:o,orientation:a="horizontal",dir:l,activationMode:u="automatic",...s}=e,c=(0,d.jH)(l),[m,y]=(0,p.i)({prop:n,onChange:i,defaultProp:null!=o?o:"",caller:v});return(0,g.jsx)(w,{scope:r,baseId:(0,h.B)(),value:m,onValueChange:y,orientation:a,dir:c,activationMode:u,children:(0,g.jsx)(f.sG.div,{dir:c,"data-orientation":a,...s,ref:t})})});S.displayName=v;var O="TabsList",E=n.forwardRef((e,t)=>{let{__scopeTabs:r,loop:n=!0,...i}=e,o=x(O,r),l=b(r);return(0,g.jsx)(a.bL,{asChild:!0,...l,orientation:o.orientation,dir:o.dir,loop:n,children:(0,g.jsx)(f.sG.div,{role:"tablist","aria-orientation":o.orientation,...i,ref:t})})});E.displayName=O;var C="TabsTrigger",M=n.forwardRef((e,t)=>{let{__scopeTabs:r,value:n,disabled:o=!1,...l}=e,u=x(C,r),s=b(r),c=_(u.baseId,n),d=P(u.baseId,n),p=n===u.value;return(0,g.jsx)(a.q7,{asChild:!0,...s,focusable:!o,active:p,children:(0,g.jsx)(f.sG.button,{type:"button",role:"tab","aria-selected":p,"aria-controls":d,"data-state":p?"active":"inactive","data-disabled":o?"":void 0,disabled:o,id:c,...l,ref:t,onMouseDown:(0,i.m)(e.onMouseDown,e=>{o||0!==e.button||!1!==e.ctrlKey?e.preventDefault():u.onValueChange(n)}),onKeyDown:(0,i.m)(e.onKeyDown,e=>{[" ","Enter"].includes(e.key)&&u.onValueChange(n)}),onFocus:(0,i.m)(e.onFocus,()=>{let e="manual"!==u.activationMode;p||o||!e||u.onValueChange(n)})})})});M.displayName=C;var k="TabsContent";function _(e,t){return"".concat(e,"-trigger-").concat(t)}function P(e,t){return"".concat(e,"-content-").concat(t)}n.forwardRef((e,t)=>{let{__scopeTabs:r,value:i,forceMount:o,children:a,...l}=e,u=x(k,r),c=_(u.baseId,i),d=P(u.baseId,i),p=i===u.value,h=n.useRef(p);return n.useEffect(()=>{let e=requestAnimationFrame(()=>h.current=!1);return()=>cancelAnimationFrame(e)},[]),(0,g.jsx)(s,{present:o||p,children:r=>{let{present:n}=r;return(0,g.jsx)(f.sG.div,{"data-state":p?"active":"inactive","data-orientation":u.orientation,role:"tabpanel","aria-labelledby":c,hidden:!n,id:d,tabIndex:0,...l,ref:t,style:{...e.style,animationDuration:h.current?"0s":void 0},children:n&&a})}})}).displayName=k;var A=S,j=E,T=M},52:(e,t,r)=>{"use strict";function n(e){return`Minified Redux error #${e}; visit https://redux.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}r.d(t,{HY:()=>s,Qd:()=>l,Tw:()=>f,Zz:()=>c,ve:()=>d,y$:()=>u});var i="function"==typeof Symbol&&Symbol.observable||"@@observable",o=()=>Math.random().toString(36).substring(7).split("").join("."),a={INIT:`@@redux/INIT${o()}`,REPLACE:`@@redux/REPLACE${o()}`,PROBE_UNKNOWN_ACTION:()=>`@@redux/PROBE_UNKNOWN_ACTION${o()}`};function l(e){if("object"!=typeof e||null===e)return!1;let t=e;for(;null!==Object.getPrototypeOf(t);)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t||null===Object.getPrototypeOf(e)}function u(e,t,r){if("function"!=typeof e)throw Error(n(2));if("function"==typeof t&&"function"==typeof r||"function"==typeof r&&"function"==typeof arguments[3])throw Error(n(0));if("function"==typeof t&&void 0===r&&(r=t,t=void 0),void 0!==r){if("function"!=typeof r)throw Error(n(1));return r(u)(e,t)}let o=e,s=t,c=new Map,f=c,d=0,p=!1;function h(){f===c&&(f=new Map,c.forEach((e,t)=>{f.set(t,e)}))}function g(){if(p)throw Error(n(3));return s}function v(e){if("function"!=typeof e)throw Error(n(4));if(p)throw Error(n(5));let t=!0;h();let r=d++;return f.set(r,e),function(){if(t){if(p)throw Error(n(6));t=!1,h(),f.delete(r),c=null}}}function m(e){if(!l(e))throw Error(n(7));if(void 0===e.type)throw Error(n(8));if("string"!=typeof e.type)throw Error(n(17));if(p)throw Error(n(9));try{p=!0,s=o(s,e)}finally{p=!1}return(c=f).forEach(e=>{e()}),e}return m({type:a.INIT}),{dispatch:m,subscribe:v,getState:g,replaceReducer:function(e){if("function"!=typeof e)throw Error(n(10));o=e,m({type:a.REPLACE})},[i]:function(){return{subscribe(e){if("object"!=typeof e||null===e)throw Error(n(11));function t(){e.next&&e.next(g())}return t(),{unsubscribe:v(t)}},[i](){return this}}}}}function s(e){let t,r=Object.keys(e),i={};for(let t=0;t{let t=i[e];if(void 0===t(void 0,{type:a.INIT}))throw Error(n(12));if(void 0===t(void 0,{type:a.PROBE_UNKNOWN_ACTION()}))throw Error(n(13))})}catch(e){t=e}return function(e={},r){if(t)throw t;let a=!1,l={};for(let t=0;te:1===e.length?e[0]:e.reduce((e,t)=>(...r)=>e(t(...r)))}function f(...e){return t=>(r,i)=>{let o=t(r,i),a=()=>{throw Error(n(15))},l={getState:o.getState,dispatch:(e,...t)=>a(e,...t)};return a=c(...e.map(e=>e(l)))(o.dispatch),{...o,dispatch:a}}}function d(e){return l(e)&&"type"in e&&"string"==typeof e.type}},177:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(5160);t.isArguments=function(e){return null!==e&&"object"==typeof e&&"[object Arguments]"===n.getTag(e)}},215:(e,t,r)=>{"use strict";r.d(t,{BZ:()=>eo,eE:()=>es,Xb:()=>ea,JG:()=>ed,A2:()=>ei,yn:()=>ec,Dn:()=>E,gL:()=>q,fl:()=>Y,R4:()=>Q,Re:()=>S,n4:()=>A});var n=r(8924),i=r(2183),o=r(7238),a=r(9827),l=r(356),u=r(8478),s=r(6377),c=r(8190),f=r(6523),d=r(530),p=r(1928),h=r(841),g=r(4968),v=r(2589),m=r(6124),y=r(5146),b=r(6670),w=r(5714),x=r(4013),S=e=>{var t=(0,o.fz)(e);return"horizontal"===t?"xAxis":"vertical"===t?"yAxis":"centric"===t?"angleAxis":"radiusAxis"},O=e=>e.tooltip.settings.axisId,E=e=>{var t=S(e),r=O(e);return(0,i.Hd)(e,t,r)},C=(0,n.Mz)([E,o.fz,i.um,u.iO,S],i.sr),M=(0,n.Mz)([e=>e.graphicalItems.cartesianItems,e=>e.graphicalItems.polarItems],(e,t)=>[...e,...t]),k=(0,n.Mz)([S,O],i.eo),_=(0,n.Mz)([M,E,k],i.ec),P=(0,n.Mz)([_],i.rj),A=(0,n.Mz)([P,l.LF],i.Nk),j=(0,n.Mz)([A,E,_],i.fb),T=(0,n.Mz)([E],i.S5),R=(0,n.Mz)([A,_,u.eC],i.MK),z=(0,n.Mz)([R,l.LF,S],i.pM),D=(0,n.Mz)([_],i.IO),I=(0,n.Mz)([A,E,D,S],i.kz),N=(0,n.Mz)([i.Kr,S,O],i.P9),L=(0,n.Mz)([N,S],i.Oz),F=(0,n.Mz)([i.gT,S,O],i.P9),$=(0,n.Mz)([F,S],i.q),B=(0,n.Mz)([i.$X,S,O],i.P9),V=(0,n.Mz)([B,S],i.bb),U=(0,n.Mz)([L,V,$],i.yi),H=(0,n.Mz)([E,T,z,I,U],i.wL),G=(0,n.Mz)([E,o.fz,A,j,u.eC,S,H],i.tP),Z=(0,n.Mz)([G,E,C],i.xp),W=(0,n.Mz)([E,G,Z,S],i.g1),K=e=>{var t=S(e),r=O(e);return(0,i.D5)(e,t,r,!1)},q=(0,n.Mz)([E,K],c.I),Y=(0,n.Mz)([E,C,W,q],i.Qn),X=(0,n.Mz)([o.fz,j,E,S],i.tF),J=(0,n.Mz)([o.fz,j,E,S],i.iv),Q=(0,n.Mz)([o.fz,E,C,Y,K,X,J,S],(e,t,r,n,i,o,l,u)=>{if(t){var{type:c}=t,f=(0,a._L)(e,u);if(n){var d="scaleBand"===r&&n.bandwidth?n.bandwidth()/2:2,p="category"===c&&n.bandwidth?n.bandwidth()/d:0;return(p="angleAxis"===u&&null!=i&&(null==i?void 0:i.length)>=2?2*(0,s.sA)(i[0]-i[1])*p:p,f&&l)?l.map((e,t)=>({coordinate:n(e)+p,value:e,index:t,offset:p})):n.domain().map((e,t)=>({coordinate:n(e)+p,value:o?o[e]:e,index:t,offset:p}))}}}),ee=(0,n.Mz)([f.xH,f.Hw,e=>e.tooltip.settings],(e,t,r)=>(0,f.$g)(r.shared,e,t)),et=e=>e.tooltip.settings.trigger,er=e=>e.tooltip.settings.defaultIndex,en=(0,n.Mz)([w.J,ee,et,er],p.i),ei=(0,n.Mz)([en,A],h.P),eo=(0,n.Mz)([Q,ei],d.E),ea=(0,n.Mz)([en],e=>{if(e)return e.dataKey}),el=(0,n.Mz)([w.J,ee,et,er],y.q),eu=(0,n.Mz)([v.Lp,v.A$,o.fz,m.HZ,Q,er,el,b.x],g.o),es=(0,n.Mz)([en,eu],(e,t)=>null!=e&&e.coordinate?e.coordinate:t),ec=(0,n.Mz)([en],e=>e.active),ef=(0,n.Mz)([el,ei,l.LF,E,eo,b.x,ee],x.N),ed=(0,n.Mz)([ef],e=>{if(null!=e)return Array.from(new Set(e.map(e=>e.payload).filter(e=>null!=e)))})},220:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.last=function(e){return e[e.length-1]}},241:(e,t,r)=>{e.exports=r(2434).sortBy},294:(e,t)=>{"use strict";var r="function"==typeof Symbol&&Symbol.for,n=r?Symbol.for("react.element"):60103,i=r?Symbol.for("react.portal"):60106,o=r?Symbol.for("react.fragment"):60107,a=r?Symbol.for("react.strict_mode"):60108,l=r?Symbol.for("react.profiler"):60114,u=r?Symbol.for("react.provider"):60109,s=r?Symbol.for("react.context"):60110,c=r?Symbol.for("react.async_mode"):60111,f=r?Symbol.for("react.concurrent_mode"):60111,d=r?Symbol.for("react.forward_ref"):60112,p=r?Symbol.for("react.suspense"):60113,h=(r&&Symbol.for("react.suspense_list"),r?Symbol.for("react.memo"):60115),g=r?Symbol.for("react.lazy"):60116;function v(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case n:switch(e=e.type){case c:case f:case o:case l:case a:case p:return e;default:switch(e=e&&e.$$typeof){case s:case d:case g:case h:case u:return e;default:return t}}case i:return t}}}r&&Symbol.for("react.block"),r&&Symbol.for("react.fundamental"),r&&Symbol.for("react.responder"),r&&Symbol.for("react.scope");t.isFragment=function(e){return v(e)===o}},330:(e,t,r)=>{"use strict";e.exports=r(294)},356:(e,t,r)=>{"use strict";r.d(t,{HS:()=>a,LF:()=>i});var n=r(8924),i=e=>e.chartData,o=(0,n.Mz)([i],e=>{var t=null!=e.chartData?e.chartData.length-1:0;return{chartData:e.chartData,computedData:e.computedData,dataEndIndex:t,dataStartIndex:0}}),a=(e,t,r,n)=>n?o(e):i(e)},379:(e,t,r)=>{"use strict";r.d(t,{J:()=>m,Z:()=>v});var n=r(2115),i=r(2596),o=r(9095),a=r(788),l=r(6377),u=r(5641),s=r(7238),c=["offset"],f=["labelRef"];function d(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;nnull!=e&&"function"==typeof e;function m(e){var t,{offset:r=5}=e,p=h({offset:r},d(e,c)),{viewBox:v,position:m,value:y,children:b,content:w,className:x="",textBreakAll:S,labelRef:O}=p,E=(0,s.sk)(),C=v||E;if(!C||(0,l.uy)(y)&&(0,l.uy)(b)&&!(0,n.isValidElement)(w)&&"function"!=typeof w)return null;if((0,n.isValidElement)(w)){var{labelRef:M}=p,k=d(p,f);return(0,n.cloneElement)(w,k)}if("function"==typeof w){if(t=(0,n.createElement)(w,p),(0,n.isValidElement)(t))return t}else t=(e=>{var{value:t,formatter:r}=e,n=(0,l.uy)(e.children)?t:e.children;return"function"==typeof r?r(n):n})(p);var _="cx"in C&&(0,l.Et)(C.cx),P=(0,a.J9)(p,!0);if(_&&("insideStart"===m||"insideEnd"===m||"end"===m))return((e,t,r)=>{let o,a;var s,c,{position:f,viewBox:d,offset:p,className:h}=e,{cx:v,cy:m,innerRadius:y,outerRadius:b,startAngle:w,endAngle:x,clockWise:S}=d,O=(y+b)/2,E=(o=w,a=x,(0,l.sA)(a-o)*Math.min(Math.abs(a-o),360)),C=E>=0?1:-1;"insideStart"===f?(s=w+C*p,c=S):"insideEnd"===f?(s=x-C*p,c=!S):"end"===f&&(s=x+C*p,c=S),c=E<=0?c:!c;var M=(0,u.IZ)(v,m,O,s),k=(0,u.IZ)(v,m,O,s+(c?1:-1)*359),_="M".concat(M.x,",").concat(M.y,"\n A").concat(O,",").concat(O,",0,1,").concat(+!c,",\n ").concat(k.x,",").concat(k.y),P=(0,l.uy)(e.id)?(0,l.NF)("recharts-radial-line-"):e.id;return n.createElement("text",g({},r,{dominantBaseline:"central",className:(0,i.$)("recharts-radial-bar-label",h)}),n.createElement("defs",null,n.createElement("path",{id:P,d:_})),n.createElement("textPath",{xlinkHref:"#".concat(P)},t))})(p,t,P);var A=_?(e=>{var{viewBox:t,offset:r,position:n}=e,{cx:i,cy:o,innerRadius:a,outerRadius:l,startAngle:s,endAngle:c}=t,f=(s+c)/2;if("outside"===n){var{x:d,y:p}=(0,u.IZ)(i,o,l+r,f);return{x:d,y:p,textAnchor:d>=i?"start":"end",verticalAnchor:"middle"}}if("center"===n)return{x:i,y:o,textAnchor:"middle",verticalAnchor:"middle"};if("centerTop"===n)return{x:i,y:o,textAnchor:"middle",verticalAnchor:"start"};if("centerBottom"===n)return{x:i,y:o,textAnchor:"middle",verticalAnchor:"end"};var{x:h,y:g}=(0,u.IZ)(i,o,(a+l)/2,f);return{x:h,y:g,textAnchor:"middle",verticalAnchor:"middle"}})(p):((e,t)=>{var{parentViewBox:r,offset:n,position:i}=e,{x:o,y:a,width:u,height:s}=t,c=s>=0?1:-1,f=c*n,d=c>0?"end":"start",p=c>0?"start":"end",g=u>=0?1:-1,v=g*n,m=g>0?"end":"start",y=g>0?"start":"end";if("top"===i)return h(h({},{x:o+u/2,y:a-c*n,textAnchor:"middle",verticalAnchor:d}),r?{height:Math.max(a-r.y,0),width:u}:{});if("bottom"===i)return h(h({},{x:o+u/2,y:a+s+f,textAnchor:"middle",verticalAnchor:p}),r?{height:Math.max(r.y+r.height-(a+s),0),width:u}:{});if("left"===i){var b={x:o-v,y:a+s/2,textAnchor:m,verticalAnchor:"middle"};return h(h({},b),r?{width:Math.max(b.x-r.x,0),height:s}:{})}if("right"===i){var w={x:o+u+v,y:a+s/2,textAnchor:y,verticalAnchor:"middle"};return h(h({},w),r?{width:Math.max(r.x+r.width-w.x,0),height:s}:{})}var x=r?{width:u,height:s}:{};return"insideLeft"===i?h({x:o+v,y:a+s/2,textAnchor:y,verticalAnchor:"middle"},x):"insideRight"===i?h({x:o+u-v,y:a+s/2,textAnchor:m,verticalAnchor:"middle"},x):"insideTop"===i?h({x:o+u/2,y:a+f,textAnchor:"middle",verticalAnchor:p},x):"insideBottom"===i?h({x:o+u/2,y:a+s-f,textAnchor:"middle",verticalAnchor:d},x):"insideTopLeft"===i?h({x:o+v,y:a+f,textAnchor:y,verticalAnchor:p},x):"insideTopRight"===i?h({x:o+u-v,y:a+f,textAnchor:m,verticalAnchor:p},x):"insideBottomLeft"===i?h({x:o+v,y:a+s-f,textAnchor:y,verticalAnchor:d},x):"insideBottomRight"===i?h({x:o+u-v,y:a+s-f,textAnchor:m,verticalAnchor:d},x):i&&"object"==typeof i&&((0,l.Et)(i.x)||(0,l._3)(i.x))&&((0,l.Et)(i.y)||(0,l._3)(i.y))?h({x:o+(0,l.F4)(i.x,u),y:a+(0,l.F4)(i.y,s),textAnchor:"end",verticalAnchor:"end"},x):h({x:o+u/2,y:a+s/2,textAnchor:"middle",verticalAnchor:"middle"},x)})(p,C);return n.createElement(o.E,g({ref:O,className:(0,i.$)("recharts-label",x)},P,A,{breakAll:S}),t)}m.displayName="Label";var y=e=>{var{cx:t,cy:r,angle:n,startAngle:i,endAngle:o,r:a,radius:u,innerRadius:s,outerRadius:c,x:f,y:d,top:p,left:h,width:g,height:v,clockWise:m,labelViewBox:y}=e;if(y)return y;if((0,l.Et)(g)&&(0,l.Et)(v)){if((0,l.Et)(f)&&(0,l.Et)(d))return{x:f,y:d,width:g,height:v};if((0,l.Et)(p)&&(0,l.Et)(h))return{x:p,y:h,width:g,height:v}}return(0,l.Et)(f)&&(0,l.Et)(d)?{x:f,y:d,width:0,height:0}:(0,l.Et)(t)&&(0,l.Et)(r)?{cx:t,cy:r,startAngle:i||n||0,endAngle:o||n||0,innerRadius:s||0,outerRadius:c||u||a||0,clockWise:m}:e.viewBox?e.viewBox:void 0};m.parseViewBox=y,m.renderCallByParent=function(e,t){var r=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(!e||!e.children&&r&&!e.label)return null;var{children:i,labelRef:o}=e,u=y(e),s=(0,a.aS)(i,m).map((e,r)=>(0,n.cloneElement)(e,{viewBox:t||u,key:"label-".concat(r)}));return r?[((e,t,r)=>{if(!e)return null;var i={viewBox:t,labelRef:r};return!0===e?n.createElement(m,g({key:"label-implicit"},i)):(0,l.vh)(e)?n.createElement(m,g({key:"label-implicit",value:e},i)):(0,n.isValidElement)(e)?e.type===m?(0,n.cloneElement)(e,h({key:"label-implicit"},i)):n.createElement(m,g({key:"label-implicit",content:e},i)):v(e)?n.createElement(m,g({key:"label-implicit",content:e},i)):e&&"object"==typeof e?n.createElement(m,g({},e,{key:"label-implicit"},i)):null})(e.label,t||u,o),...s]:s}},400:(e,t,r)=>{e.exports=r(2962).throttle},402:(e,t,r)=>{"use strict";r.d(t,{_G:()=>c,be:()=>a,gB:()=>p,gl:()=>w});var n=r(2115),i=r(5143),o=r(8266);function a(e,t,r){let n=e.slice();return n.splice(r<0?n.length+r:r,0,n.splice(t,1)[0]),n}function l(e){return null!==e&&e>=0}let u=e=>{let{rects:t,activeIndex:r,overIndex:n,index:i}=e,o=a(t,n,r),l=t[i],u=o[i];return u&&l?{x:u.left-l.left,y:u.top-l.top,scaleX:u.width/l.width,scaleY:u.height/l.height}:null},s={scaleX:1,scaleY:1},c=e=>{var t;let{activeIndex:r,activeNodeRect:n,index:i,rects:o,overIndex:a}=e,l=null!=(t=o[r])?t:n;if(!l)return null;if(i===r){let e=o[a];return e?{x:0,y:rr&&i<=a?{x:0,y:-l.height-u,...s}:i=a?{x:0,y:l.height+u,...s}:{x:0,y:0,...s}},f="Sortable",d=n.createContext({activeIndex:-1,containerId:f,disableTransforms:!1,items:[],overIndex:-1,useDragOverlay:!1,sortedRects:[],strategy:u,disabled:{draggable:!1,droppable:!1}});function p(e){let{children:t,id:r,items:a,strategy:l=u,disabled:s=!1}=e,{active:c,dragOverlay:p,droppableRects:h,over:g,measureDroppableContainers:v}=(0,i.fF)(),m=(0,o.YG)(f,r),y=null!==p.rect,b=(0,n.useMemo)(()=>a.map(e=>"object"==typeof e&&"id"in e?e.id:e),[a]),w=null!=c,x=c?b.indexOf(c.id):-1,S=g?b.indexOf(g.id):-1,O=(0,n.useRef)(b),E=!function(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let r=0;r{E&&w&&v(b)},[E,b,w,v]),(0,n.useEffect)(()=>{O.current=b},[b]);let k=(0,n.useMemo)(()=>({activeIndex:x,containerId:m,disabled:M,disableTransforms:C,items:b,overIndex:S,useDragOverlay:y,sortedRects:b.reduce((e,t,r)=>{let n=h.get(t);return n&&(e[r]=n),e},Array(b.length)),strategy:l}),[x,m,M.draggable,M.droppable,C,b,S,h,y,l]);return n.createElement(d.Provider,{value:k},t)}let h=e=>{let{id:t,items:r,activeIndex:n,overIndex:i}=e;return a(r,n,i).indexOf(t)},g=e=>{let{containerId:t,isSorting:r,wasDragging:n,index:i,items:o,newIndex:a,previousItems:l,previousContainerId:u,transition:s}=e;return!!s&&!!n&&(l===o||i!==a)&&(!!r||a!==i&&t===u)},v={duration:200,easing:"ease"},m="transform",y=o.Ks.Transition.toString({property:m,duration:0,easing:"linear"}),b={roleDescription:"sortable"};function w(e){var t,r,a,u;let{animateLayoutChanges:s=g,attributes:c,disabled:f,data:p,getNewIndex:w=h,id:x,strategy:S,resizeObserverConfig:O,transition:E=v}=e,{items:C,containerId:M,activeIndex:k,disabled:_,disableTransforms:P,sortedRects:A,overIndex:j,useDragOverlay:T,strategy:R}=(0,n.useContext)(d),z=(t=f,r=_,"boolean"==typeof t?{draggable:t,droppable:!1}:{draggable:null!=(a=null==t?void 0:t.draggable)?a:r.draggable,droppable:null!=(u=null==t?void 0:t.droppable)?u:r.droppable}),D=C.indexOf(x),I=(0,n.useMemo)(()=>({sortable:{containerId:M,index:D,items:C},...p}),[M,p,D,C]),N=(0,n.useMemo)(()=>C.slice(C.indexOf(x)),[C,x]),{rect:L,node:F,isOver:$,setNodeRef:B}=(0,i.zM)({id:x,data:I,disabled:z.droppable,resizeObserverConfig:{updateMeasurementsFor:N,...O}}),{active:V,activatorEvent:U,activeNodeRect:H,attributes:G,setNodeRef:Z,listeners:W,isDragging:K,over:q,setActivatorNodeRef:Y,transform:X}=(0,i.PM)({id:x,data:I,attributes:{...b,...c},disabled:z.draggable}),J=(0,o.jn)(B,Z),Q=!!V,ee=Q&&!P&&l(k)&&l(j),et=!T&&K,er=et&&ee?X:null,en=ee?null!=er?er:(null!=S?S:R)({rects:A,activeNodeRect:H,activeIndex:k,overIndex:j,index:D}):null,ei=l(k)&&l(j)?w({id:x,items:C,activeIndex:k,overIndex:j}):D,eo=null==V?void 0:V.id,ea=(0,n.useRef)({activeId:eo,items:C,newIndex:ei,containerId:M}),el=C!==ea.current.items,eu=s({active:V,containerId:M,isDragging:K,isSorting:Q,id:x,index:D,items:C,newIndex:ea.current.newIndex,previousItems:ea.current.items,previousContainerId:ea.current.containerId,transition:E,wasDragging:null!=ea.current.activeId}),es=function(e){let{disabled:t,index:r,node:a,rect:l}=e,[u,s]=(0,n.useState)(null),c=(0,n.useRef)(r);return(0,o.Es)(()=>{if(!t&&r!==c.current&&a.current){let e=l.current;if(e){let t=(0,i.Sj)(a.current,{ignoreTransform:!0}),r={x:e.left-t.left,y:e.top-t.top,scaleX:e.width/t.width,scaleY:e.height/t.height};(r.x||r.y)&&s(r)}}r!==c.current&&(c.current=r)},[t,r,a,l]),(0,n.useEffect)(()=>{u&&s(null)},[u]),u}({disabled:!eu,index:D,node:F,rect:L});return(0,n.useEffect)(()=>{Q&&ea.current.newIndex!==ei&&(ea.current.newIndex=ei),M!==ea.current.containerId&&(ea.current.containerId=M),C!==ea.current.items&&(ea.current.items=C)},[Q,ei,M,C]),(0,n.useEffect)(()=>{if(eo===ea.current.activeId)return;if(null!=eo&&null==ea.current.activeId){ea.current.activeId=eo;return}let e=setTimeout(()=>{ea.current.activeId=eo},50);return()=>clearTimeout(e)},[eo]),{active:V,activeIndex:k,attributes:G,data:I,rect:L,index:D,newIndex:ei,items:C,isOver:$,isSorting:Q,isDragging:K,listeners:W,node:F,overIndex:j,over:q,setNodeRef:J,setActivatorNodeRef:Y,setDroppableNodeRef:B,setDraggableNodeRef:Z,transform:null!=es?es:en,transition:es||el&&ea.current.newIndex===D?y:(!et||(0,o.kx)(U))&&E&&(Q||eu)?o.Ks.Transition.toString({...E,property:m}):void 0}}i.vL.Down,i.vL.Right,i.vL.Up,i.vL.Left},464:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("rabbit",[["path",{d:"M13 16a3 3 0 0 1 2.24 5",key:"1epib5"}],["path",{d:"M18 12h.01",key:"yjnet6"}],["path",{d:"M18 21h-8a4 4 0 0 1-4-4 7 7 0 0 1 7-7h.2L9.6 6.4a1 1 0 1 1 2.8-2.8L15.8 7h.2c3.3 0 6 2.7 6 6v1a2 2 0 0 1-2 2h-1a3 3 0 0 0-3 3",key:"ue9ozu"}],["path",{d:"M20 8.54V4a2 2 0 1 0-4 0v3",key:"49iql8"}],["path",{d:"M7.612 12.524a3 3 0 1 0-1.6 4.3",key:"1e33i0"}]])},512:(e,t,r)=>{e.exports=r(7547).uniqBy},530:(e,t,r)=>{"use strict";r.d(t,{E:()=>i});var n=r(6377),i=(e,t)=>{var r,i=Number(t);if(!(0,n.M8)(i)&&null!=t)return i>=0?null==e||null==(r=e[i])?void 0:r.value:void 0}},574:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chart-area",[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"M7 11.207a.5.5 0 0 1 .146-.353l2-2a.5.5 0 0 1 .708 0l3.292 3.292a.5.5 0 0 0 .708 0l4.292-4.292a.5.5 0 0 1 .854.353V16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1z",key:"q0gr47"}]])},646:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("circle-check-big",[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]])},656:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8179),i=r(9279);t.isArrayLikeObject=function(e){return i.isObjectLike(e)&&n.isArrayLike(e)}},668:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isSymbol=function(e){return"symbol"==typeof e||e instanceof Symbol}},675:(e,t,r)=>{"use strict";r.d(t,{R:()=>n});var n=function(e,t){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i{"use strict";r.d(t,{I:()=>B});var n=r(2115);function i(){}function o(e,t,r){e._context.bezierCurveTo((2*e._x0+e._x1)/3,(2*e._y0+e._y1)/3,(e._x0+2*e._x1)/3,(e._y0+2*e._y1)/3,(e._x0+4*e._x1+t)/6,(e._y0+4*e._y1+r)/6)}function a(e){this._context=e}function l(e){this._context=e}function u(e){this._context=e}a.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:o(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e*=1,t*=1,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:o(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}},l.prototype={areaStart:i,areaEnd:i,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(e,t){switch(e*=1,t*=1,this._point){case 0:this._point=1,this._x2=e,this._y2=t;break;case 1:this._point=2,this._x3=e,this._y3=t;break;case 2:this._point=3,this._x4=e,this._y4=t,this._context.moveTo((this._x0+4*this._x1+e)/6,(this._y0+4*this._y1+t)/6);break;default:o(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}},u.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e*=1,t*=1,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var r=(this._x0+4*this._x1+e)/6,n=(this._y0+4*this._y1+t)/6;this._line?this._context.lineTo(r,n):this._context.moveTo(r,n);break;case 3:this._point=4;default:o(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};class s{constructor(e,t){this._context=e,this._x=t}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line}point(e,t){switch(e*=1,t*=1,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._x?this._context.bezierCurveTo(this._x0=(this._x0+e)/2,this._y0,this._x0,t,e,t):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+t)/2,e,this._y0,e,t)}this._x0=e,this._y0=t}}function c(e){this._context=e}function f(e){this._context=e}function d(e){return new f(e)}c.prototype={areaStart:i,areaEnd:i,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(e,t){e*=1,t*=1,this._point?this._context.lineTo(e,t):(this._point=1,this._context.moveTo(e,t))}};function p(e,t,r){var n=e._x1-e._x0,i=t-e._x1,o=(e._y1-e._y0)/(n||i<0&&-0),a=(r-e._y1)/(i||n<0&&-0);return((o<0?-1:1)+(a<0?-1:1))*Math.min(Math.abs(o),Math.abs(a),.5*Math.abs((o*i+a*n)/(n+i)))||0}function h(e,t){var r=e._x1-e._x0;return r?(3*(e._y1-e._y0)/r-t)/2:t}function g(e,t,r){var n=e._x0,i=e._y0,o=e._x1,a=e._y1,l=(o-n)/3;e._context.bezierCurveTo(n+l,i+l*t,o-l,a-l*r,o,a)}function v(e){this._context=e}function m(e){this._context=new y(e)}function y(e){this._context=e}function b(e){this._context=e}function w(e){var t,r,n=e.length-1,i=Array(n),o=Array(n),a=Array(n);for(i[0]=0,o[0]=2,a[0]=e[0]+2*e[1],t=1;t=0;--t)i[t]=(a[t]-i[t+1])/o[t];for(t=0,o[n-1]=(e[n]+i[n-1])/2;t=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(e,t){switch(e*=1,t*=1,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,t),this._context.lineTo(e,t);else{var r=this._x*(1-this._t)+e*this._t;this._context.lineTo(r,this._y),this._context.lineTo(r,t)}}this._x=e,this._y=t}};var S=r(9819),O=r(5654),E=r(1847);function C(e){return e[0]}function M(e){return e[1]}function k(e,t){var r=(0,O.A)(!0),n=null,i=d,o=null,a=(0,E.i)(l);function l(l){var u,s,c,f=(l=(0,S.A)(l)).length,d=!1;for(null==n&&(o=i(c=a())),u=0;u<=f;++u)!(u=f;--d)l.point(m[d],y[d]);l.lineEnd(),l.areaEnd()}v&&(m[c]=+e(p,c,s),y[c]=+t(p,c,s),l.point(n?+n(p,c,s):m[c],r?+r(p,c,s):y[c]))}if(h)return l=null,h+""||null}function c(){return k().defined(i).curve(a).context(o)}return e="function"==typeof e?e:void 0===e?C:(0,O.A)(+e),t="function"==typeof t?t:void 0===t?(0,O.A)(0):(0,O.A)(+t),r="function"==typeof r?r:void 0===r?M:(0,O.A)(+r),s.x=function(t){return arguments.length?(e="function"==typeof t?t:(0,O.A)(+t),n=null,s):e},s.x0=function(t){return arguments.length?(e="function"==typeof t?t:(0,O.A)(+t),s):e},s.x1=function(e){return arguments.length?(n=null==e?null:"function"==typeof e?e:(0,O.A)(+e),s):n},s.y=function(e){return arguments.length?(t="function"==typeof e?e:(0,O.A)(+e),r=null,s):t},s.y0=function(e){return arguments.length?(t="function"==typeof e?e:(0,O.A)(+e),s):t},s.y1=function(e){return arguments.length?(r=null==e?null:"function"==typeof e?e:(0,O.A)(+e),s):r},s.lineX0=s.lineY0=function(){return c().x(e).y(t)},s.lineY1=function(){return c().x(e).y(r)},s.lineX1=function(){return c().x(n).y(t)},s.defined=function(e){return arguments.length?(i="function"==typeof e?e:(0,O.A)(!!e),s):i},s.curve=function(e){return arguments.length?(a=e,null!=o&&(l=a(o)),s):a},s.context=function(e){return arguments.length?(null==e?o=l=null:l=a(o=e),s):o},s}var P=r(2596),A=r(3597),j=r(788),T=r(6377),R=r(8892);function z(){return(z=Object.assign?Object.assign.bind():function(e){for(var t=1;t(0,R.H)(e.x)&&(0,R.H)(e.y),F=e=>e.x,$=e=>e.y,B=e=>{var{className:t,points:r,path:i,pathRef:o}=e;if((!r||!r.length)&&!i)return null;var a=r&&r.length?(e=>{var t,{type:r="linear",points:n=[],baseLine:i,layout:o,connectNulls:a=!1}=e,l=((e,t)=>{if("function"==typeof e)return e;var r="curve".concat((0,T.Zb)(e));return("curveMonotone"===r||"curveBump"===r)&&t?N["".concat(r).concat("vertical"===t?"Y":"X")]:N[r]||d})(r,o),u=a?n.filter(L):n;if(Array.isArray(i)){var s=a?i.filter(e=>L(e)):i,c=u.map((e,t)=>I(I({},e),{},{base:s[t]}));return(t="vertical"===o?_().y($).x1(F).x0(e=>e.base.x):_().x(F).y1($).y0(e=>e.base.y)).defined(L).curve(l),t(c)}return(t="vertical"===o&&(0,T.Et)(i)?_().y($).x1(F).x0(i):(0,T.Et)(i)?_().x(F).y1($).y0(i):k().x(F).y($)).defined(L).curve(l),t(u)})(e):i;return n.createElement("path",z({},(0,j.J9)(e,!1),(0,A._U)(e),{className:(0,P.$)("recharts-curve",t),d:null===a?void 0:a,ref:o}))}},788:(e,t,r)=>{"use strict";r.d(t,{J9:()=>g,aS:()=>p,y$:()=>h});var n=r(5672),i=r.n(n),o=r(2115),a=r(330),l=r(6377),u=r(3597),s=e=>"string"==typeof e?e:e?e.displayName||e.name||"Component":"",c=null,f=null,d=e=>{if(e===c&&Array.isArray(f))return f;var t=[];return o.Children.forEach(e,e=>{(0,l.uy)(e)||((0,a.isFragment)(e)?t=t.concat(d(e.props.children)):t.push(e))}),f=t,c=e,t};function p(e,t){var r=[],n=[];return n=Array.isArray(t)?t.map(e=>s(e)):[s(t)],d(e).forEach(e=>{var t=i()(e,"type.displayName")||i()(e,"type.name");-1!==n.indexOf(t)&&r.push(e)}),r}var h=e=>!e||"object"!=typeof e||!("clipDot"in e)||!!e.clipDot,g=(e,t,r)=>{if(!e||"function"==typeof e||"boolean"==typeof e)return null;var n=e;if((0,o.isValidElement)(e)&&(n=e.props),"object"!=typeof n&&"function"!=typeof n)return null;var i={};return Object.keys(n).forEach(e=>{var o;((e,t,r,n)=>{var i,o=null!=(i=n&&(null===u.VU||void 0===u.VU?void 0:u.VU[n]))?i:[];return t.startsWith("data-")||"function"!=typeof e&&(n&&o.includes(t)||u.QQ.includes(t))||r&&u.j2.includes(t)})(null==(o=n)?void 0:o[e],e,t,r)&&(i[e]=n[e])}),i}},841:(e,t,r)=>{"use strict";r.d(t,{P:()=>i});var n=r(8892),i=(e,t)=>{var r=null==e?void 0:e.index;if(null==r)return null;var i=Number(r);if(!(0,n.H)(i))return r;var o=Infinity;return t.length>0&&(o=t.length-1),String(Math.max(0,Math.min(i,o)))}},885:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isTypedArray=function(e){return ArrayBuffer.isView(e)&&!(e instanceof DataView)}},921:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(7040),i=r(4545),o=r(6200),a=r(4072);t.get=function e(t,r,l){if(null==t)return l;switch(typeof r){case"string":{if(n.isUnsafeProperty(r))return l;let o=t[r];if(void 0===o)if(i.isDeepKey(r))return e(t,a.toPath(r),l);else return l;return o}case"number":case"symbol":{"number"==typeof r&&(r=o.toKey(r));let e=t[r];if(void 0===e)return l;return e}default:{if(Array.isArray(r)){var u=t,s=r,c=l;if(0===s.length)return c;let e=u;for(let t=0;t{"use strict";r.d(t,{b:()=>l});var n=r(2115),i=r(3655),o=r(5155),a=n.forwardRef((e,t)=>(0,o.jsx)(i.sG.label,{...e,ref:t,onMouseDown:t=>{var r;t.target.closest("button, input, select, textarea")||(null==(r=e.onMouseDown)||r.call(e,t),!t.defaultPrevented&&t.detail>1&&t.preventDefault())}}));a.displayName="Label";var l=a},972:(e,t,r)=>{"use strict";r.d(t,{C:()=>l,U:()=>u});var n=r(8924),i=r(6124),o=r(2589),a=r(6377),l=e=>e.brush,u=(0,n.Mz)([l,i.HZ,o.HK],(e,t,r)=>({height:e.height,x:(0,a.Et)(e.x)?e.x:t.left,y:(0,a.Et)(e.y)?e.y:t.top+t.height+t.brushBottom-((null==r?void 0:r.bottom)||0),width:(0,a.Et)(e.width)?e.width:t.width}))},1032:(e,t,r)=>{"use strict";function n(e,t){return"function"==typeof e?e(t):e}function i(e,t){return r=>{t.setState(t=>({...t,[e]:n(r,t[e])}))}}function o(e){return e instanceof Function}function a(e,t,r){let n,i=[];return o=>{let a,l;r.key&&r.debug&&(a=Date.now());let u=e(o);if(!(u.length!==i.length||u.some((e,t)=>i[t]!==e)))return n;if(i=u,r.key&&r.debug&&(l=Date.now()),n=t(...u),null==r||null==r.onChange||r.onChange(n),r.key&&r.debug&&null!=r&&r.debug()){let e=Math.round((Date.now()-a)*100)/100,t=Math.round((Date.now()-l)*100)/100,n=t/16,i=(e,t)=>{for(e=String(e);e.length{var r;return null!=(r=null==e?void 0:e.debugAll)?r:e[t]},key:!1,onChange:n}}r.d(t,{HT:()=>H,ZR:()=>U,h5:()=>Y,hM:()=>K,kQ:()=>Z,kW:()=>q,oS:()=>W});let u="debugHeaders";function s(e,t,r){var n;let i={id:null!=(n=r.id)?n:t.id,column:t,index:r.index,isPlaceholder:!!r.isPlaceholder,placeholderId:r.placeholderId,depth:r.depth,subHeaders:[],colSpan:0,rowSpan:0,headerGroup:null,getLeafHeaders:()=>{let e=[],t=r=>{r.subHeaders&&r.subHeaders.length&&r.subHeaders.map(t),e.push(r)};return t(i),e},getContext:()=>({table:e,header:i,column:t})};return e._features.forEach(t=>{null==t.createHeader||t.createHeader(i,e)}),i}function c(e,t,r,n){var i,o;let a=0,l=function(e,t){void 0===t&&(t=1),a=Math.max(a,t),e.filter(e=>e.getIsVisible()).forEach(e=>{var r;null!=(r=e.columns)&&r.length&&l(e.columns,t+1)},0)};l(e);let u=[],c=(e,t)=>{let i={depth:t,id:[n,`${t}`].filter(Boolean).join("_"),headers:[]},o=[];e.forEach(e=>{let a,l=[...o].reverse()[0],u=e.column.depth===i.depth,c=!1;if(u&&e.column.parent?a=e.column.parent:(a=e.column,c=!0),l&&(null==l?void 0:l.column)===a)l.subHeaders.push(e);else{let i=s(r,a,{id:[n,t,a.id,null==e?void 0:e.id].filter(Boolean).join("_"),isPlaceholder:c,placeholderId:c?`${o.filter(e=>e.column===a).length}`:void 0,depth:t,index:o.length});i.subHeaders.push(e),o.push(i)}i.headers.push(e),e.headerGroup=i}),u.push(i),t>0&&c(o,t-1)};c(t.map((e,t)=>s(r,e,{depth:a,index:t})),a-1),u.reverse();let f=e=>e.filter(e=>e.column.getIsVisible()).map(e=>{let t=0,r=0,n=[0];return e.subHeaders&&e.subHeaders.length?(n=[],f(e.subHeaders).forEach(e=>{let{colSpan:r,rowSpan:i}=e;t+=r,n.push(i)})):t=1,r+=Math.min(...n),e.colSpan=t,e.rowSpan=r,{colSpan:t,rowSpan:r}});return f(null!=(i=null==(o=u[0])?void 0:o.headers)?i:[]),u}let f=(e,t,r,n,i,o,u)=>{let s={id:t,index:n,original:r,depth:i,parentId:u,_valuesCache:{},_uniqueValuesCache:{},getValue:t=>{if(s._valuesCache.hasOwnProperty(t))return s._valuesCache[t];let r=e.getColumn(t);if(null!=r&&r.accessorFn)return s._valuesCache[t]=r.accessorFn(s.original,n),s._valuesCache[t]},getUniqueValues:t=>{if(s._uniqueValuesCache.hasOwnProperty(t))return s._uniqueValuesCache[t];let r=e.getColumn(t);if(null!=r&&r.accessorFn)return r.columnDef.getUniqueValues?s._uniqueValuesCache[t]=r.columnDef.getUniqueValues(s.original,n):s._uniqueValuesCache[t]=[s.getValue(t)],s._uniqueValuesCache[t]},renderValue:t=>{var r;return null!=(r=s.getValue(t))?r:e.options.renderFallbackValue},subRows:null!=o?o:[],getLeafRows:()=>(function(e,t){let r=[],n=e=>{e.forEach(e=>{r.push(e);let i=t(e);null!=i&&i.length&&n(i)})};return n(e),r})(s.subRows,e=>e.subRows),getParentRow:()=>s.parentId?e.getRow(s.parentId,!0):void 0,getParentRows:()=>{let e=[],t=s;for(;;){let r=t.getParentRow();if(!r)break;e.push(r),t=r}return e.reverse()},getAllCells:a(()=>[e.getAllLeafColumns()],t=>t.map(t=>(function(e,t,r,n){let i={id:`${t.id}_${r.id}`,row:t,column:r,getValue:()=>t.getValue(n),renderValue:()=>{var t;return null!=(t=i.getValue())?t:e.options.renderFallbackValue},getContext:a(()=>[e,r,t,i],(e,t,r,n)=>({table:e,column:t,row:r,cell:n,getValue:n.getValue,renderValue:n.renderValue}),l(e.options,"debugCells","cell.getContext"))};return e._features.forEach(n=>{null==n.createCell||n.createCell(i,r,t,e)},{}),i})(e,s,t,t.id)),l(e.options,"debugRows","getAllCells")),_getAllCellsByColumnId:a(()=>[s.getAllCells()],e=>e.reduce((e,t)=>(e[t.column.id]=t,e),{}),l(e.options,"debugRows","getAllCellsByColumnId"))};for(let t=0;t{var n,i;let o=null==r||null==(n=r.toString())?void 0:n.toLowerCase();return!!(null==(i=e.getValue(t))||null==(i=i.toString())||null==(i=i.toLowerCase())?void 0:i.includes(o))};d.autoRemove=e=>S(e);let p=(e,t,r)=>{var n;return!!(null==(n=e.getValue(t))||null==(n=n.toString())?void 0:n.includes(r))};p.autoRemove=e=>S(e);let h=(e,t,r)=>{var n;return(null==(n=e.getValue(t))||null==(n=n.toString())?void 0:n.toLowerCase())===(null==r?void 0:r.toLowerCase())};h.autoRemove=e=>S(e);let g=(e,t,r)=>{var n;return null==(n=e.getValue(t))?void 0:n.includes(r)};g.autoRemove=e=>S(e);let v=(e,t,r)=>!r.some(r=>{var n;return!(null!=(n=e.getValue(t))&&n.includes(r))});v.autoRemove=e=>S(e)||!(null!=e&&e.length);let m=(e,t,r)=>r.some(r=>{var n;return null==(n=e.getValue(t))?void 0:n.includes(r)});m.autoRemove=e=>S(e)||!(null!=e&&e.length);let y=(e,t,r)=>e.getValue(t)===r;y.autoRemove=e=>S(e);let b=(e,t,r)=>e.getValue(t)==r;b.autoRemove=e=>S(e);let w=(e,t,r)=>{let[n,i]=r,o=e.getValue(t);return o>=n&&o<=i};w.resolveFilterValue=e=>{let[t,r]=e,n="number"!=typeof t?parseFloat(t):t,i="number"!=typeof r?parseFloat(r):r,o=null===t||Number.isNaN(n)?-1/0:n,a=null===r||Number.isNaN(i)?1/0:i;if(o>a){let e=o;o=a,a=e}return[o,a]},w.autoRemove=e=>S(e)||S(e[0])&&S(e[1]);let x={includesString:d,includesStringSensitive:p,equalsString:h,arrIncludes:g,arrIncludesAll:v,arrIncludesSome:m,equals:y,weakEquals:b,inNumberRange:w};function S(e){return null==e||""===e}function O(e,t,r){return!!e&&!!e.autoRemove&&e.autoRemove(t,r)||void 0===t||"string"==typeof t&&!t}let E={sum:(e,t,r)=>r.reduce((t,r)=>{let n=r.getValue(e);return t+("number"==typeof n?n:0)},0),min:(e,t,r)=>{let n;return r.forEach(t=>{let r=t.getValue(e);null!=r&&(n>r||void 0===n&&r>=r)&&(n=r)}),n},max:(e,t,r)=>{let n;return r.forEach(t=>{let r=t.getValue(e);null!=r&&(n=r)&&(n=r)}),n},extent:(e,t,r)=>{let n,i;return r.forEach(t=>{let r=t.getValue(e);null!=r&&(void 0===n?r>=r&&(n=i=r):(n>r&&(n=r),i{let r=0,n=0;if(t.forEach(t=>{let i=t.getValue(e);null!=i&&(i*=1)>=i&&(++r,n+=i)}),r)return n/r},median:(e,t)=>{if(!t.length)return;let r=t.map(t=>t.getValue(e));if(!function(e){return Array.isArray(e)&&e.every(e=>"number"==typeof e)}(r))return;if(1===r.length)return r[0];let n=Math.floor(r.length/2),i=r.sort((e,t)=>e-t);return r.length%2!=0?i[n]:(i[n-1]+i[n])/2},unique:(e,t)=>Array.from(new Set(t.map(t=>t.getValue(e))).values()),uniqueCount:(e,t)=>new Set(t.map(t=>t.getValue(e))).size,count:(e,t)=>t.length},C=()=>({left:[],right:[]}),M={size:150,minSize:20,maxSize:Number.MAX_SAFE_INTEGER},k=()=>({startOffset:null,startSize:null,deltaOffset:null,deltaPercentage:null,isResizingColumn:!1,columnSizingStart:[]}),_=null;function P(e){return"touchstart"===e.type}function A(e,t){return t?"center"===t?e.getCenterVisibleLeafColumns():"left"===t?e.getLeftVisibleLeafColumns():e.getRightVisibleLeafColumns():e.getVisibleLeafColumns()}let j=()=>({pageIndex:0,pageSize:10}),T=()=>({top:[],bottom:[]}),R=(e,t,r,n,i)=>{var o;let a=i.getRow(t,!0);r?(a.getCanMultiSelect()||Object.keys(e).forEach(t=>delete e[t]),a.getCanSelect()&&(e[t]=!0)):delete e[t],n&&null!=(o=a.subRows)&&o.length&&a.getCanSelectSubRows()&&a.subRows.forEach(t=>R(e,t.id,r,n,i))};function z(e,t){let r=e.getState().rowSelection,n=[],i={},o=function(e,t){return e.map(e=>{var t;let a=D(e,r);if(a&&(n.push(e),i[e.id]=e),null!=(t=e.subRows)&&t.length&&(e={...e,subRows:o(e.subRows)}),a)return e}).filter(Boolean)};return{rows:o(t.rows),flatRows:n,rowsById:i}}function D(e,t){var r;return null!=(r=t[e.id])&&r}function I(e,t,r){var n;if(!(null!=(n=e.subRows)&&n.length))return!1;let i=!0,o=!1;return e.subRows.forEach(e=>{if((!o||i)&&(e.getCanSelect()&&(D(e,t)?o=!0:i=!1),e.subRows&&e.subRows.length)){let r=I(e,t);"all"===r?o=!0:("some"===r&&(o=!0),i=!1)}}),i?"all":!!o&&"some"}let N=/([0-9]+)/gm;function L(e,t){return e===t?0:e>t?1:-1}function F(e){return"number"==typeof e?isNaN(e)||e===1/0||e===-1/0?"":String(e):"string"==typeof e?e:""}function $(e,t){let r=e.split(N).filter(Boolean),n=t.split(N).filter(Boolean);for(;r.length&&n.length;){let e=r.shift(),t=n.shift(),i=parseInt(e,10),o=parseInt(t,10),a=[i,o].sort();if(isNaN(a[0])){if(e>t)return 1;if(t>e)return -1;continue}if(isNaN(a[1]))return isNaN(i)?-1:1;if(i>o)return 1;if(o>i)return -1}return r.length-n.length}let B={alphanumeric:(e,t,r)=>$(F(e.getValue(r)).toLowerCase(),F(t.getValue(r)).toLowerCase()),alphanumericCaseSensitive:(e,t,r)=>$(F(e.getValue(r)),F(t.getValue(r))),text:(e,t,r)=>L(F(e.getValue(r)).toLowerCase(),F(t.getValue(r)).toLowerCase()),textCaseSensitive:(e,t,r)=>L(F(e.getValue(r)),F(t.getValue(r))),datetime:(e,t,r)=>{let n=e.getValue(r),i=t.getValue(r);return n>i?1:nL(e.getValue(r),t.getValue(r))},V=[{createTable:e=>{e.getHeaderGroups=a(()=>[e.getAllColumns(),e.getVisibleLeafColumns(),e.getState().columnPinning.left,e.getState().columnPinning.right],(t,r,n,i)=>{var o,a;let l=null!=(o=null==n?void 0:n.map(e=>r.find(t=>t.id===e)).filter(Boolean))?o:[],u=null!=(a=null==i?void 0:i.map(e=>r.find(t=>t.id===e)).filter(Boolean))?a:[];return c(t,[...l,...r.filter(e=>!(null!=n&&n.includes(e.id))&&!(null!=i&&i.includes(e.id))),...u],e)},l(e.options,u,"getHeaderGroups")),e.getCenterHeaderGroups=a(()=>[e.getAllColumns(),e.getVisibleLeafColumns(),e.getState().columnPinning.left,e.getState().columnPinning.right],(t,r,n,i)=>c(t,r=r.filter(e=>!(null!=n&&n.includes(e.id))&&!(null!=i&&i.includes(e.id))),e,"center"),l(e.options,u,"getCenterHeaderGroups")),e.getLeftHeaderGroups=a(()=>[e.getAllColumns(),e.getVisibleLeafColumns(),e.getState().columnPinning.left],(t,r,n)=>{var i;return c(t,null!=(i=null==n?void 0:n.map(e=>r.find(t=>t.id===e)).filter(Boolean))?i:[],e,"left")},l(e.options,u,"getLeftHeaderGroups")),e.getRightHeaderGroups=a(()=>[e.getAllColumns(),e.getVisibleLeafColumns(),e.getState().columnPinning.right],(t,r,n)=>{var i;return c(t,null!=(i=null==n?void 0:n.map(e=>r.find(t=>t.id===e)).filter(Boolean))?i:[],e,"right")},l(e.options,u,"getRightHeaderGroups")),e.getFooterGroups=a(()=>[e.getHeaderGroups()],e=>[...e].reverse(),l(e.options,u,"getFooterGroups")),e.getLeftFooterGroups=a(()=>[e.getLeftHeaderGroups()],e=>[...e].reverse(),l(e.options,u,"getLeftFooterGroups")),e.getCenterFooterGroups=a(()=>[e.getCenterHeaderGroups()],e=>[...e].reverse(),l(e.options,u,"getCenterFooterGroups")),e.getRightFooterGroups=a(()=>[e.getRightHeaderGroups()],e=>[...e].reverse(),l(e.options,u,"getRightFooterGroups")),e.getFlatHeaders=a(()=>[e.getHeaderGroups()],e=>e.map(e=>e.headers).flat(),l(e.options,u,"getFlatHeaders")),e.getLeftFlatHeaders=a(()=>[e.getLeftHeaderGroups()],e=>e.map(e=>e.headers).flat(),l(e.options,u,"getLeftFlatHeaders")),e.getCenterFlatHeaders=a(()=>[e.getCenterHeaderGroups()],e=>e.map(e=>e.headers).flat(),l(e.options,u,"getCenterFlatHeaders")),e.getRightFlatHeaders=a(()=>[e.getRightHeaderGroups()],e=>e.map(e=>e.headers).flat(),l(e.options,u,"getRightFlatHeaders")),e.getCenterLeafHeaders=a(()=>[e.getCenterFlatHeaders()],e=>e.filter(e=>{var t;return!(null!=(t=e.subHeaders)&&t.length)}),l(e.options,u,"getCenterLeafHeaders")),e.getLeftLeafHeaders=a(()=>[e.getLeftFlatHeaders()],e=>e.filter(e=>{var t;return!(null!=(t=e.subHeaders)&&t.length)}),l(e.options,u,"getLeftLeafHeaders")),e.getRightLeafHeaders=a(()=>[e.getRightFlatHeaders()],e=>e.filter(e=>{var t;return!(null!=(t=e.subHeaders)&&t.length)}),l(e.options,u,"getRightLeafHeaders")),e.getLeafHeaders=a(()=>[e.getLeftHeaderGroups(),e.getCenterHeaderGroups(),e.getRightHeaderGroups()],(e,t,r)=>{var n,i,o,a,l,u;return[...null!=(n=null==(i=e[0])?void 0:i.headers)?n:[],...null!=(o=null==(a=t[0])?void 0:a.headers)?o:[],...null!=(l=null==(u=r[0])?void 0:u.headers)?l:[]].map(e=>e.getLeafHeaders()).flat()},l(e.options,u,"getLeafHeaders"))}},{getInitialState:e=>({columnVisibility:{},...e}),getDefaultOptions:e=>({onColumnVisibilityChange:i("columnVisibility",e)}),createColumn:(e,t)=>{e.toggleVisibility=r=>{e.getCanHide()&&t.setColumnVisibility(t=>({...t,[e.id]:null!=r?r:!e.getIsVisible()}))},e.getIsVisible=()=>{var r,n;let i=e.columns;return null==(r=i.length?i.some(e=>e.getIsVisible()):null==(n=t.getState().columnVisibility)?void 0:n[e.id])||r},e.getCanHide=()=>{var r,n;return(null==(r=e.columnDef.enableHiding)||r)&&(null==(n=t.options.enableHiding)||n)},e.getToggleVisibilityHandler=()=>t=>{null==e.toggleVisibility||e.toggleVisibility(t.target.checked)}},createRow:(e,t)=>{e._getAllVisibleCells=a(()=>[e.getAllCells(),t.getState().columnVisibility],e=>e.filter(e=>e.column.getIsVisible()),l(t.options,"debugRows","_getAllVisibleCells")),e.getVisibleCells=a(()=>[e.getLeftVisibleCells(),e.getCenterVisibleCells(),e.getRightVisibleCells()],(e,t,r)=>[...e,...t,...r],l(t.options,"debugRows","getVisibleCells"))},createTable:e=>{let t=(t,r)=>a(()=>[r(),r().filter(e=>e.getIsVisible()).map(e=>e.id).join("_")],e=>e.filter(e=>null==e.getIsVisible?void 0:e.getIsVisible()),l(e.options,"debugColumns",t));e.getVisibleFlatColumns=t("getVisibleFlatColumns",()=>e.getAllFlatColumns()),e.getVisibleLeafColumns=t("getVisibleLeafColumns",()=>e.getAllLeafColumns()),e.getLeftVisibleLeafColumns=t("getLeftVisibleLeafColumns",()=>e.getLeftLeafColumns()),e.getRightVisibleLeafColumns=t("getRightVisibleLeafColumns",()=>e.getRightLeafColumns()),e.getCenterVisibleLeafColumns=t("getCenterVisibleLeafColumns",()=>e.getCenterLeafColumns()),e.setColumnVisibility=t=>null==e.options.onColumnVisibilityChange?void 0:e.options.onColumnVisibilityChange(t),e.resetColumnVisibility=t=>{var r;e.setColumnVisibility(t?{}:null!=(r=e.initialState.columnVisibility)?r:{})},e.toggleAllColumnsVisible=t=>{var r;t=null!=(r=t)?r:!e.getIsAllColumnsVisible(),e.setColumnVisibility(e.getAllLeafColumns().reduce((e,r)=>({...e,[r.id]:t||!(null!=r.getCanHide&&r.getCanHide())}),{}))},e.getIsAllColumnsVisible=()=>!e.getAllLeafColumns().some(e=>!(null!=e.getIsVisible&&e.getIsVisible())),e.getIsSomeColumnsVisible=()=>e.getAllLeafColumns().some(e=>null==e.getIsVisible?void 0:e.getIsVisible()),e.getToggleAllColumnsVisibilityHandler=()=>t=>{var r;e.toggleAllColumnsVisible(null==(r=t.target)?void 0:r.checked)}}},{getInitialState:e=>({columnOrder:[],...e}),getDefaultOptions:e=>({onColumnOrderChange:i("columnOrder",e)}),createColumn:(e,t)=>{e.getIndex=a(e=>[A(t,e)],t=>t.findIndex(t=>t.id===e.id),l(t.options,"debugColumns","getIndex")),e.getIsFirstColumn=r=>{var n;return(null==(n=A(t,r)[0])?void 0:n.id)===e.id},e.getIsLastColumn=r=>{var n;let i=A(t,r);return(null==(n=i[i.length-1])?void 0:n.id)===e.id}},createTable:e=>{e.setColumnOrder=t=>null==e.options.onColumnOrderChange?void 0:e.options.onColumnOrderChange(t),e.resetColumnOrder=t=>{var r;e.setColumnOrder(t?[]:null!=(r=e.initialState.columnOrder)?r:[])},e._getOrderColumnsFn=a(()=>[e.getState().columnOrder,e.getState().grouping,e.options.groupedColumnMode],(e,t,r)=>n=>{let i=[];if(null!=e&&e.length){let t=[...e],r=[...n];for(;r.length&&t.length;){let e=t.shift(),n=r.findIndex(t=>t.id===e);n>-1&&i.push(r.splice(n,1)[0])}i=[...i,...r]}else i=n;return function(e,t,r){if(!(null!=t&&t.length)||!r)return e;let n=e.filter(e=>!t.includes(e.id));return"remove"===r?n:[...t.map(t=>e.find(e=>e.id===t)).filter(Boolean),...n]}(i,t,r)},l(e.options,"debugTable","_getOrderColumnsFn"))}},{getInitialState:e=>({columnPinning:C(),...e}),getDefaultOptions:e=>({onColumnPinningChange:i("columnPinning",e)}),createColumn:(e,t)=>{e.pin=r=>{let n=e.getLeafColumns().map(e=>e.id).filter(Boolean);t.setColumnPinning(e=>{var t,i,o,a,l,u;return"right"===r?{left:(null!=(o=null==e?void 0:e.left)?o:[]).filter(e=>!(null!=n&&n.includes(e))),right:[...(null!=(a=null==e?void 0:e.right)?a:[]).filter(e=>!(null!=n&&n.includes(e))),...n]}:"left"===r?{left:[...(null!=(l=null==e?void 0:e.left)?l:[]).filter(e=>!(null!=n&&n.includes(e))),...n],right:(null!=(u=null==e?void 0:e.right)?u:[]).filter(e=>!(null!=n&&n.includes(e)))}:{left:(null!=(t=null==e?void 0:e.left)?t:[]).filter(e=>!(null!=n&&n.includes(e))),right:(null!=(i=null==e?void 0:e.right)?i:[]).filter(e=>!(null!=n&&n.includes(e)))}})},e.getCanPin=()=>e.getLeafColumns().some(e=>{var r,n,i;return(null==(r=e.columnDef.enablePinning)||r)&&(null==(n=null!=(i=t.options.enableColumnPinning)?i:t.options.enablePinning)||n)}),e.getIsPinned=()=>{let r=e.getLeafColumns().map(e=>e.id),{left:n,right:i}=t.getState().columnPinning,o=r.some(e=>null==n?void 0:n.includes(e)),a=r.some(e=>null==i?void 0:i.includes(e));return o?"left":!!a&&"right"},e.getPinnedIndex=()=>{var r,n;let i=e.getIsPinned();return i?null!=(r=null==(n=t.getState().columnPinning)||null==(n=n[i])?void 0:n.indexOf(e.id))?r:-1:0}},createRow:(e,t)=>{e.getCenterVisibleCells=a(()=>[e._getAllVisibleCells(),t.getState().columnPinning.left,t.getState().columnPinning.right],(e,t,r)=>{let n=[...null!=t?t:[],...null!=r?r:[]];return e.filter(e=>!n.includes(e.column.id))},l(t.options,"debugRows","getCenterVisibleCells")),e.getLeftVisibleCells=a(()=>[e._getAllVisibleCells(),t.getState().columnPinning.left],(e,t)=>(null!=t?t:[]).map(t=>e.find(e=>e.column.id===t)).filter(Boolean).map(e=>({...e,position:"left"})),l(t.options,"debugRows","getLeftVisibleCells")),e.getRightVisibleCells=a(()=>[e._getAllVisibleCells(),t.getState().columnPinning.right],(e,t)=>(null!=t?t:[]).map(t=>e.find(e=>e.column.id===t)).filter(Boolean).map(e=>({...e,position:"right"})),l(t.options,"debugRows","getRightVisibleCells"))},createTable:e=>{e.setColumnPinning=t=>null==e.options.onColumnPinningChange?void 0:e.options.onColumnPinningChange(t),e.resetColumnPinning=t=>{var r,n;return e.setColumnPinning(t?C():null!=(r=null==(n=e.initialState)?void 0:n.columnPinning)?r:C())},e.getIsSomeColumnsPinned=t=>{var r,n,i;let o=e.getState().columnPinning;return t?!!(null==(r=o[t])?void 0:r.length):!!((null==(n=o.left)?void 0:n.length)||(null==(i=o.right)?void 0:i.length))},e.getLeftLeafColumns=a(()=>[e.getAllLeafColumns(),e.getState().columnPinning.left],(e,t)=>(null!=t?t:[]).map(t=>e.find(e=>e.id===t)).filter(Boolean),l(e.options,"debugColumns","getLeftLeafColumns")),e.getRightLeafColumns=a(()=>[e.getAllLeafColumns(),e.getState().columnPinning.right],(e,t)=>(null!=t?t:[]).map(t=>e.find(e=>e.id===t)).filter(Boolean),l(e.options,"debugColumns","getRightLeafColumns")),e.getCenterLeafColumns=a(()=>[e.getAllLeafColumns(),e.getState().columnPinning.left,e.getState().columnPinning.right],(e,t,r)=>{let n=[...null!=t?t:[],...null!=r?r:[]];return e.filter(e=>!n.includes(e.id))},l(e.options,"debugColumns","getCenterLeafColumns"))}},{createColumn:(e,t)=>{e._getFacetedRowModel=t.options.getFacetedRowModel&&t.options.getFacetedRowModel(t,e.id),e.getFacetedRowModel=()=>e._getFacetedRowModel?e._getFacetedRowModel():t.getPreFilteredRowModel(),e._getFacetedUniqueValues=t.options.getFacetedUniqueValues&&t.options.getFacetedUniqueValues(t,e.id),e.getFacetedUniqueValues=()=>e._getFacetedUniqueValues?e._getFacetedUniqueValues():new Map,e._getFacetedMinMaxValues=t.options.getFacetedMinMaxValues&&t.options.getFacetedMinMaxValues(t,e.id),e.getFacetedMinMaxValues=()=>{if(e._getFacetedMinMaxValues)return e._getFacetedMinMaxValues()}}},{getDefaultColumnDef:()=>({filterFn:"auto"}),getInitialState:e=>({columnFilters:[],...e}),getDefaultOptions:e=>({onColumnFiltersChange:i("columnFilters",e),filterFromLeafRows:!1,maxLeafRowFilterDepth:100}),createColumn:(e,t)=>{e.getAutoFilterFn=()=>{let r=t.getCoreRowModel().flatRows[0],n=null==r?void 0:r.getValue(e.id);return"string"==typeof n?x.includesString:"number"==typeof n?x.inNumberRange:"boolean"==typeof n||null!==n&&"object"==typeof n?x.equals:Array.isArray(n)?x.arrIncludes:x.weakEquals},e.getFilterFn=()=>{var r,n;return o(e.columnDef.filterFn)?e.columnDef.filterFn:"auto"===e.columnDef.filterFn?e.getAutoFilterFn():null!=(r=null==(n=t.options.filterFns)?void 0:n[e.columnDef.filterFn])?r:x[e.columnDef.filterFn]},e.getCanFilter=()=>{var r,n,i;return(null==(r=e.columnDef.enableColumnFilter)||r)&&(null==(n=t.options.enableColumnFilters)||n)&&(null==(i=t.options.enableFilters)||i)&&!!e.accessorFn},e.getIsFiltered=()=>e.getFilterIndex()>-1,e.getFilterValue=()=>{var r;return null==(r=t.getState().columnFilters)||null==(r=r.find(t=>t.id===e.id))?void 0:r.value},e.getFilterIndex=()=>{var r,n;return null!=(r=null==(n=t.getState().columnFilters)?void 0:n.findIndex(t=>t.id===e.id))?r:-1},e.setFilterValue=r=>{t.setColumnFilters(t=>{var i,o;let a=e.getFilterFn(),l=null==t?void 0:t.find(t=>t.id===e.id),u=n(r,l?l.value:void 0);if(O(a,u,e))return null!=(i=null==t?void 0:t.filter(t=>t.id!==e.id))?i:[];let s={id:e.id,value:u};return l?null!=(o=null==t?void 0:t.map(t=>t.id===e.id?s:t))?o:[]:null!=t&&t.length?[...t,s]:[s]})}},createRow:(e,t)=>{e.columnFilters={},e.columnFiltersMeta={}},createTable:e=>{e.setColumnFilters=t=>{let r=e.getAllLeafColumns();null==e.options.onColumnFiltersChange||e.options.onColumnFiltersChange(e=>{var i;return null==(i=n(t,e))?void 0:i.filter(e=>{let t=r.find(t=>t.id===e.id);return!(t&&O(t.getFilterFn(),e.value,t))&&!0})})},e.resetColumnFilters=t=>{var r,n;e.setColumnFilters(t?[]:null!=(r=null==(n=e.initialState)?void 0:n.columnFilters)?r:[])},e.getPreFilteredRowModel=()=>e.getCoreRowModel(),e.getFilteredRowModel=()=>(!e._getFilteredRowModel&&e.options.getFilteredRowModel&&(e._getFilteredRowModel=e.options.getFilteredRowModel(e)),e.options.manualFiltering||!e._getFilteredRowModel)?e.getPreFilteredRowModel():e._getFilteredRowModel()}},{createTable:e=>{e._getGlobalFacetedRowModel=e.options.getFacetedRowModel&&e.options.getFacetedRowModel(e,"__global__"),e.getGlobalFacetedRowModel=()=>e.options.manualFiltering||!e._getGlobalFacetedRowModel?e.getPreFilteredRowModel():e._getGlobalFacetedRowModel(),e._getGlobalFacetedUniqueValues=e.options.getFacetedUniqueValues&&e.options.getFacetedUniqueValues(e,"__global__"),e.getGlobalFacetedUniqueValues=()=>e._getGlobalFacetedUniqueValues?e._getGlobalFacetedUniqueValues():new Map,e._getGlobalFacetedMinMaxValues=e.options.getFacetedMinMaxValues&&e.options.getFacetedMinMaxValues(e,"__global__"),e.getGlobalFacetedMinMaxValues=()=>{if(e._getGlobalFacetedMinMaxValues)return e._getGlobalFacetedMinMaxValues()}}},{getInitialState:e=>({globalFilter:void 0,...e}),getDefaultOptions:e=>({onGlobalFilterChange:i("globalFilter",e),globalFilterFn:"auto",getColumnCanGlobalFilter:t=>{var r;let n=null==(r=e.getCoreRowModel().flatRows[0])||null==(r=r._getAllCellsByColumnId()[t.id])?void 0:r.getValue();return"string"==typeof n||"number"==typeof n}}),createColumn:(e,t)=>{e.getCanGlobalFilter=()=>{var r,n,i,o;return(null==(r=e.columnDef.enableGlobalFilter)||r)&&(null==(n=t.options.enableGlobalFilter)||n)&&(null==(i=t.options.enableFilters)||i)&&(null==(o=null==t.options.getColumnCanGlobalFilter?void 0:t.options.getColumnCanGlobalFilter(e))||o)&&!!e.accessorFn}},createTable:e=>{e.getGlobalAutoFilterFn=()=>x.includesString,e.getGlobalFilterFn=()=>{var t,r;let{globalFilterFn:n}=e.options;return o(n)?n:"auto"===n?e.getGlobalAutoFilterFn():null!=(t=null==(r=e.options.filterFns)?void 0:r[n])?t:x[n]},e.setGlobalFilter=t=>{null==e.options.onGlobalFilterChange||e.options.onGlobalFilterChange(t)},e.resetGlobalFilter=t=>{e.setGlobalFilter(t?void 0:e.initialState.globalFilter)}}},{getInitialState:e=>({sorting:[],...e}),getDefaultColumnDef:()=>({sortingFn:"auto",sortUndefined:1}),getDefaultOptions:e=>({onSortingChange:i("sorting",e),isMultiSortEvent:e=>e.shiftKey}),createColumn:(e,t)=>{e.getAutoSortingFn=()=>{let r=t.getFilteredRowModel().flatRows.slice(10),n=!1;for(let t of r){let r=null==t?void 0:t.getValue(e.id);if("[object Date]"===Object.prototype.toString.call(r))return B.datetime;if("string"==typeof r&&(n=!0,r.split(N).length>1))return B.alphanumeric}return n?B.text:B.basic},e.getAutoSortDir=()=>{let r=t.getFilteredRowModel().flatRows[0];return"string"==typeof(null==r?void 0:r.getValue(e.id))?"asc":"desc"},e.getSortingFn=()=>{var r,n;if(!e)throw Error();return o(e.columnDef.sortingFn)?e.columnDef.sortingFn:"auto"===e.columnDef.sortingFn?e.getAutoSortingFn():null!=(r=null==(n=t.options.sortingFns)?void 0:n[e.columnDef.sortingFn])?r:B[e.columnDef.sortingFn]},e.toggleSorting=(r,n)=>{let i=e.getNextSortingOrder(),o=null!=r;t.setSorting(a=>{let l,u=null==a?void 0:a.find(t=>t.id===e.id),s=null==a?void 0:a.findIndex(t=>t.id===e.id),c=[],f=o?r:"desc"===i;if("toggle"!=(l=null!=a&&a.length&&e.getCanMultiSort()&&n?u?"toggle":"add":null!=a&&a.length&&s!==a.length-1?"replace":u?"toggle":"replace")||o||i||(l="remove"),"add"===l){var d;(c=[...a,{id:e.id,desc:f}]).splice(0,c.length-(null!=(d=t.options.maxMultiSortColCount)?d:Number.MAX_SAFE_INTEGER))}else c="toggle"===l?a.map(t=>t.id===e.id?{...t,desc:f}:t):"remove"===l?a.filter(t=>t.id!==e.id):[{id:e.id,desc:f}];return c})},e.getFirstSortDir=()=>{var r,n;return(null!=(r=null!=(n=e.columnDef.sortDescFirst)?n:t.options.sortDescFirst)?r:"desc"===e.getAutoSortDir())?"desc":"asc"},e.getNextSortingOrder=r=>{var n,i;let o=e.getFirstSortDir(),a=e.getIsSorted();return a?(a===o||null!=(n=t.options.enableSortingRemoval)&&!n||!!r&&null!=(i=t.options.enableMultiRemove)&&!i)&&("desc"===a?"asc":"desc"):o},e.getCanSort=()=>{var r,n;return(null==(r=e.columnDef.enableSorting)||r)&&(null==(n=t.options.enableSorting)||n)&&!!e.accessorFn},e.getCanMultiSort=()=>{var r,n;return null!=(r=null!=(n=e.columnDef.enableMultiSort)?n:t.options.enableMultiSort)?r:!!e.accessorFn},e.getIsSorted=()=>{var r;let n=null==(r=t.getState().sorting)?void 0:r.find(t=>t.id===e.id);return!!n&&(n.desc?"desc":"asc")},e.getSortIndex=()=>{var r,n;return null!=(r=null==(n=t.getState().sorting)?void 0:n.findIndex(t=>t.id===e.id))?r:-1},e.clearSorting=()=>{t.setSorting(t=>null!=t&&t.length?t.filter(t=>t.id!==e.id):[])},e.getToggleSortingHandler=()=>{let r=e.getCanSort();return n=>{r&&(null==n.persist||n.persist(),null==e.toggleSorting||e.toggleSorting(void 0,!!e.getCanMultiSort()&&(null==t.options.isMultiSortEvent?void 0:t.options.isMultiSortEvent(n))))}}},createTable:e=>{e.setSorting=t=>null==e.options.onSortingChange?void 0:e.options.onSortingChange(t),e.resetSorting=t=>{var r,n;e.setSorting(t?[]:null!=(r=null==(n=e.initialState)?void 0:n.sorting)?r:[])},e.getPreSortedRowModel=()=>e.getGroupedRowModel(),e.getSortedRowModel=()=>(!e._getSortedRowModel&&e.options.getSortedRowModel&&(e._getSortedRowModel=e.options.getSortedRowModel(e)),e.options.manualSorting||!e._getSortedRowModel)?e.getPreSortedRowModel():e._getSortedRowModel()}},{getDefaultColumnDef:()=>({aggregatedCell:e=>{var t,r;return null!=(t=null==(r=e.getValue())||null==r.toString?void 0:r.toString())?t:null},aggregationFn:"auto"}),getInitialState:e=>({grouping:[],...e}),getDefaultOptions:e=>({onGroupingChange:i("grouping",e),groupedColumnMode:"reorder"}),createColumn:(e,t)=>{e.toggleGrouping=()=>{t.setGrouping(t=>null!=t&&t.includes(e.id)?t.filter(t=>t!==e.id):[...null!=t?t:[],e.id])},e.getCanGroup=()=>{var r,n;return(null==(r=e.columnDef.enableGrouping)||r)&&(null==(n=t.options.enableGrouping)||n)&&(!!e.accessorFn||!!e.columnDef.getGroupingValue)},e.getIsGrouped=()=>{var r;return null==(r=t.getState().grouping)?void 0:r.includes(e.id)},e.getGroupedIndex=()=>{var r;return null==(r=t.getState().grouping)?void 0:r.indexOf(e.id)},e.getToggleGroupingHandler=()=>{let t=e.getCanGroup();return()=>{t&&e.toggleGrouping()}},e.getAutoAggregationFn=()=>{let r=t.getCoreRowModel().flatRows[0],n=null==r?void 0:r.getValue(e.id);return"number"==typeof n?E.sum:"[object Date]"===Object.prototype.toString.call(n)?E.extent:void 0},e.getAggregationFn=()=>{var r,n;if(!e)throw Error();return o(e.columnDef.aggregationFn)?e.columnDef.aggregationFn:"auto"===e.columnDef.aggregationFn?e.getAutoAggregationFn():null!=(r=null==(n=t.options.aggregationFns)?void 0:n[e.columnDef.aggregationFn])?r:E[e.columnDef.aggregationFn]}},createTable:e=>{e.setGrouping=t=>null==e.options.onGroupingChange?void 0:e.options.onGroupingChange(t),e.resetGrouping=t=>{var r,n;e.setGrouping(t?[]:null!=(r=null==(n=e.initialState)?void 0:n.grouping)?r:[])},e.getPreGroupedRowModel=()=>e.getFilteredRowModel(),e.getGroupedRowModel=()=>(!e._getGroupedRowModel&&e.options.getGroupedRowModel&&(e._getGroupedRowModel=e.options.getGroupedRowModel(e)),e.options.manualGrouping||!e._getGroupedRowModel)?e.getPreGroupedRowModel():e._getGroupedRowModel()},createRow:(e,t)=>{e.getIsGrouped=()=>!!e.groupingColumnId,e.getGroupingValue=r=>{if(e._groupingValuesCache.hasOwnProperty(r))return e._groupingValuesCache[r];let n=t.getColumn(r);return null!=n&&n.columnDef.getGroupingValue?(e._groupingValuesCache[r]=n.columnDef.getGroupingValue(e.original),e._groupingValuesCache[r]):e.getValue(r)},e._groupingValuesCache={}},createCell:(e,t,r,n)=>{e.getIsGrouped=()=>t.getIsGrouped()&&t.id===r.groupingColumnId,e.getIsPlaceholder=()=>!e.getIsGrouped()&&t.getIsGrouped(),e.getIsAggregated=()=>{var t;return!e.getIsGrouped()&&!e.getIsPlaceholder()&&!!(null!=(t=r.subRows)&&t.length)}}},{getInitialState:e=>({expanded:{},...e}),getDefaultOptions:e=>({onExpandedChange:i("expanded",e),paginateExpandedRows:!0}),createTable:e=>{let t=!1,r=!1;e._autoResetExpanded=()=>{var n,i;if(!t)return void e._queue(()=>{t=!0});if(null!=(n=null!=(i=e.options.autoResetAll)?i:e.options.autoResetExpanded)?n:!e.options.manualExpanding){if(r)return;r=!0,e._queue(()=>{e.resetExpanded(),r=!1})}},e.setExpanded=t=>null==e.options.onExpandedChange?void 0:e.options.onExpandedChange(t),e.toggleAllRowsExpanded=t=>{(null!=t?t:!e.getIsAllRowsExpanded())?e.setExpanded(!0):e.setExpanded({})},e.resetExpanded=t=>{var r,n;e.setExpanded(t?{}:null!=(r=null==(n=e.initialState)?void 0:n.expanded)?r:{})},e.getCanSomeRowsExpand=()=>e.getPrePaginationRowModel().flatRows.some(e=>e.getCanExpand()),e.getToggleAllRowsExpandedHandler=()=>t=>{null==t.persist||t.persist(),e.toggleAllRowsExpanded()},e.getIsSomeRowsExpanded=()=>{let t=e.getState().expanded;return!0===t||Object.values(t).some(Boolean)},e.getIsAllRowsExpanded=()=>{let t=e.getState().expanded;return"boolean"==typeof t?!0===t:!(!Object.keys(t).length||e.getRowModel().flatRows.some(e=>!e.getIsExpanded()))},e.getExpandedDepth=()=>{let t=0;return(!0===e.getState().expanded?Object.keys(e.getRowModel().rowsById):Object.keys(e.getState().expanded)).forEach(e=>{let r=e.split(".");t=Math.max(t,r.length)}),t},e.getPreExpandedRowModel=()=>e.getSortedRowModel(),e.getExpandedRowModel=()=>(!e._getExpandedRowModel&&e.options.getExpandedRowModel&&(e._getExpandedRowModel=e.options.getExpandedRowModel(e)),e.options.manualExpanding||!e._getExpandedRowModel)?e.getPreExpandedRowModel():e._getExpandedRowModel()},createRow:(e,t)=>{e.toggleExpanded=r=>{t.setExpanded(n=>{var i;let o=!0===n||!!(null!=n&&n[e.id]),a={};if(!0===n?Object.keys(t.getRowModel().rowsById).forEach(e=>{a[e]=!0}):a=n,r=null!=(i=r)?i:!o,!o&&r)return{...a,[e.id]:!0};if(o&&!r){let{[e.id]:t,...r}=a;return r}return n})},e.getIsExpanded=()=>{var r;let n=t.getState().expanded;return!!(null!=(r=null==t.options.getIsRowExpanded?void 0:t.options.getIsRowExpanded(e))?r:!0===n||(null==n?void 0:n[e.id]))},e.getCanExpand=()=>{var r,n,i;return null!=(r=null==t.options.getRowCanExpand?void 0:t.options.getRowCanExpand(e))?r:(null==(n=t.options.enableExpanding)||n)&&!!(null!=(i=e.subRows)&&i.length)},e.getIsAllParentsExpanded=()=>{let r=!0,n=e;for(;r&&n.parentId;)r=(n=t.getRow(n.parentId,!0)).getIsExpanded();return r},e.getToggleExpandedHandler=()=>{let t=e.getCanExpand();return()=>{t&&e.toggleExpanded()}}}},{getInitialState:e=>({...e,pagination:{...j(),...null==e?void 0:e.pagination}}),getDefaultOptions:e=>({onPaginationChange:i("pagination",e)}),createTable:e=>{let t=!1,r=!1;e._autoResetPageIndex=()=>{var n,i;if(!t)return void e._queue(()=>{t=!0});if(null!=(n=null!=(i=e.options.autoResetAll)?i:e.options.autoResetPageIndex)?n:!e.options.manualPagination){if(r)return;r=!0,e._queue(()=>{e.resetPageIndex(),r=!1})}},e.setPagination=t=>null==e.options.onPaginationChange?void 0:e.options.onPaginationChange(e=>n(t,e)),e.resetPagination=t=>{var r;e.setPagination(t?j():null!=(r=e.initialState.pagination)?r:j())},e.setPageIndex=t=>{e.setPagination(r=>{let i=n(t,r.pageIndex);return i=Math.max(0,Math.min(i,void 0===e.options.pageCount||-1===e.options.pageCount?Number.MAX_SAFE_INTEGER:e.options.pageCount-1)),{...r,pageIndex:i}})},e.resetPageIndex=t=>{var r,n;e.setPageIndex(t?0:null!=(r=null==(n=e.initialState)||null==(n=n.pagination)?void 0:n.pageIndex)?r:0)},e.resetPageSize=t=>{var r,n;e.setPageSize(t?10:null!=(r=null==(n=e.initialState)||null==(n=n.pagination)?void 0:n.pageSize)?r:10)},e.setPageSize=t=>{e.setPagination(e=>{let r=Math.max(1,n(t,e.pageSize)),i=Math.floor(e.pageSize*e.pageIndex/r);return{...e,pageIndex:i,pageSize:r}})},e.setPageCount=t=>e.setPagination(r=>{var i;let o=n(t,null!=(i=e.options.pageCount)?i:-1);return"number"==typeof o&&(o=Math.max(-1,o)),{...r,pageCount:o}}),e.getPageOptions=a(()=>[e.getPageCount()],e=>{let t=[];return e&&e>0&&(t=[...Array(e)].fill(null).map((e,t)=>t)),t},l(e.options,"debugTable","getPageOptions")),e.getCanPreviousPage=()=>e.getState().pagination.pageIndex>0,e.getCanNextPage=()=>{let{pageIndex:t}=e.getState().pagination,r=e.getPageCount();return -1===r||0!==r&&te.setPageIndex(e=>e-1),e.nextPage=()=>e.setPageIndex(e=>e+1),e.firstPage=()=>e.setPageIndex(0),e.lastPage=()=>e.setPageIndex(e.getPageCount()-1),e.getPrePaginationRowModel=()=>e.getExpandedRowModel(),e.getPaginationRowModel=()=>(!e._getPaginationRowModel&&e.options.getPaginationRowModel&&(e._getPaginationRowModel=e.options.getPaginationRowModel(e)),e.options.manualPagination||!e._getPaginationRowModel)?e.getPrePaginationRowModel():e._getPaginationRowModel(),e.getPageCount=()=>{var t;return null!=(t=e.options.pageCount)?t:Math.ceil(e.getRowCount()/e.getState().pagination.pageSize)},e.getRowCount=()=>{var t;return null!=(t=e.options.rowCount)?t:e.getPrePaginationRowModel().rows.length}}},{getInitialState:e=>({rowPinning:T(),...e}),getDefaultOptions:e=>({onRowPinningChange:i("rowPinning",e)}),createRow:(e,t)=>{e.pin=(r,n,i)=>{let o=n?e.getLeafRows().map(e=>{let{id:t}=e;return t}):[],a=new Set([...i?e.getParentRows().map(e=>{let{id:t}=e;return t}):[],e.id,...o]);t.setRowPinning(e=>{var t,n,i,o,l,u;return"bottom"===r?{top:(null!=(i=null==e?void 0:e.top)?i:[]).filter(e=>!(null!=a&&a.has(e))),bottom:[...(null!=(o=null==e?void 0:e.bottom)?o:[]).filter(e=>!(null!=a&&a.has(e))),...Array.from(a)]}:"top"===r?{top:[...(null!=(l=null==e?void 0:e.top)?l:[]).filter(e=>!(null!=a&&a.has(e))),...Array.from(a)],bottom:(null!=(u=null==e?void 0:e.bottom)?u:[]).filter(e=>!(null!=a&&a.has(e)))}:{top:(null!=(t=null==e?void 0:e.top)?t:[]).filter(e=>!(null!=a&&a.has(e))),bottom:(null!=(n=null==e?void 0:e.bottom)?n:[]).filter(e=>!(null!=a&&a.has(e)))}})},e.getCanPin=()=>{var r;let{enableRowPinning:n,enablePinning:i}=t.options;return"function"==typeof n?n(e):null==(r=null!=n?n:i)||r},e.getIsPinned=()=>{let r=[e.id],{top:n,bottom:i}=t.getState().rowPinning,o=r.some(e=>null==n?void 0:n.includes(e)),a=r.some(e=>null==i?void 0:i.includes(e));return o?"top":!!a&&"bottom"},e.getPinnedIndex=()=>{var r,n;let i=e.getIsPinned();if(!i)return -1;let o=null==(r="top"===i?t.getTopRows():t.getBottomRows())?void 0:r.map(e=>{let{id:t}=e;return t});return null!=(n=null==o?void 0:o.indexOf(e.id))?n:-1}},createTable:e=>{e.setRowPinning=t=>null==e.options.onRowPinningChange?void 0:e.options.onRowPinningChange(t),e.resetRowPinning=t=>{var r,n;return e.setRowPinning(t?T():null!=(r=null==(n=e.initialState)?void 0:n.rowPinning)?r:T())},e.getIsSomeRowsPinned=t=>{var r,n,i;let o=e.getState().rowPinning;return t?!!(null==(r=o[t])?void 0:r.length):!!((null==(n=o.top)?void 0:n.length)||(null==(i=o.bottom)?void 0:i.length))},e._getPinnedRows=(t,r,n)=>{var i;return(null==(i=e.options.keepPinnedRows)||i?(null!=r?r:[]).map(t=>{let r=e.getRow(t,!0);return r.getIsAllParentsExpanded()?r:null}):(null!=r?r:[]).map(e=>t.find(t=>t.id===e))).filter(Boolean).map(e=>({...e,position:n}))},e.getTopRows=a(()=>[e.getRowModel().rows,e.getState().rowPinning.top],(t,r)=>e._getPinnedRows(t,r,"top"),l(e.options,"debugRows","getTopRows")),e.getBottomRows=a(()=>[e.getRowModel().rows,e.getState().rowPinning.bottom],(t,r)=>e._getPinnedRows(t,r,"bottom"),l(e.options,"debugRows","getBottomRows")),e.getCenterRows=a(()=>[e.getRowModel().rows,e.getState().rowPinning.top,e.getState().rowPinning.bottom],(e,t,r)=>{let n=new Set([...null!=t?t:[],...null!=r?r:[]]);return e.filter(e=>!n.has(e.id))},l(e.options,"debugRows","getCenterRows"))}},{getInitialState:e=>({rowSelection:{},...e}),getDefaultOptions:e=>({onRowSelectionChange:i("rowSelection",e),enableRowSelection:!0,enableMultiRowSelection:!0,enableSubRowSelection:!0}),createTable:e=>{e.setRowSelection=t=>null==e.options.onRowSelectionChange?void 0:e.options.onRowSelectionChange(t),e.resetRowSelection=t=>{var r;return e.setRowSelection(t?{}:null!=(r=e.initialState.rowSelection)?r:{})},e.toggleAllRowsSelected=t=>{e.setRowSelection(r=>{t=void 0!==t?t:!e.getIsAllRowsSelected();let n={...r},i=e.getPreGroupedRowModel().flatRows;return t?i.forEach(e=>{e.getCanSelect()&&(n[e.id]=!0)}):i.forEach(e=>{delete n[e.id]}),n})},e.toggleAllPageRowsSelected=t=>e.setRowSelection(r=>{let n=void 0!==t?t:!e.getIsAllPageRowsSelected(),i={...r};return e.getRowModel().rows.forEach(t=>{R(i,t.id,n,!0,e)}),i}),e.getPreSelectedRowModel=()=>e.getCoreRowModel(),e.getSelectedRowModel=a(()=>[e.getState().rowSelection,e.getCoreRowModel()],(t,r)=>Object.keys(t).length?z(e,r):{rows:[],flatRows:[],rowsById:{}},l(e.options,"debugTable","getSelectedRowModel")),e.getFilteredSelectedRowModel=a(()=>[e.getState().rowSelection,e.getFilteredRowModel()],(t,r)=>Object.keys(t).length?z(e,r):{rows:[],flatRows:[],rowsById:{}},l(e.options,"debugTable","getFilteredSelectedRowModel")),e.getGroupedSelectedRowModel=a(()=>[e.getState().rowSelection,e.getSortedRowModel()],(t,r)=>Object.keys(t).length?z(e,r):{rows:[],flatRows:[],rowsById:{}},l(e.options,"debugTable","getGroupedSelectedRowModel")),e.getIsAllRowsSelected=()=>{let t=e.getFilteredRowModel().flatRows,{rowSelection:r}=e.getState(),n=!!(t.length&&Object.keys(r).length);return n&&t.some(e=>e.getCanSelect()&&!r[e.id])&&(n=!1),n},e.getIsAllPageRowsSelected=()=>{let t=e.getPaginationRowModel().flatRows.filter(e=>e.getCanSelect()),{rowSelection:r}=e.getState(),n=!!t.length;return n&&t.some(e=>!r[e.id])&&(n=!1),n},e.getIsSomeRowsSelected=()=>{var t;let r=Object.keys(null!=(t=e.getState().rowSelection)?t:{}).length;return r>0&&r{let t=e.getPaginationRowModel().flatRows;return!e.getIsAllPageRowsSelected()&&t.filter(e=>e.getCanSelect()).some(e=>e.getIsSelected()||e.getIsSomeSelected())},e.getToggleAllRowsSelectedHandler=()=>t=>{e.toggleAllRowsSelected(t.target.checked)},e.getToggleAllPageRowsSelectedHandler=()=>t=>{e.toggleAllPageRowsSelected(t.target.checked)}},createRow:(e,t)=>{e.toggleSelected=(r,n)=>{let i=e.getIsSelected();t.setRowSelection(o=>{var a;if(r=void 0!==r?r:!i,e.getCanSelect()&&i===r)return o;let l={...o};return R(l,e.id,r,null==(a=null==n?void 0:n.selectChildren)||a,t),l})},e.getIsSelected=()=>{let{rowSelection:r}=t.getState();return D(e,r)},e.getIsSomeSelected=()=>{let{rowSelection:r}=t.getState();return"some"===I(e,r)},e.getIsAllSubRowsSelected=()=>{let{rowSelection:r}=t.getState();return"all"===I(e,r)},e.getCanSelect=()=>{var r;return"function"==typeof t.options.enableRowSelection?t.options.enableRowSelection(e):null==(r=t.options.enableRowSelection)||r},e.getCanSelectSubRows=()=>{var r;return"function"==typeof t.options.enableSubRowSelection?t.options.enableSubRowSelection(e):null==(r=t.options.enableSubRowSelection)||r},e.getCanMultiSelect=()=>{var r;return"function"==typeof t.options.enableMultiRowSelection?t.options.enableMultiRowSelection(e):null==(r=t.options.enableMultiRowSelection)||r},e.getToggleSelectedHandler=()=>{let t=e.getCanSelect();return r=>{var n;t&&e.toggleSelected(null==(n=r.target)?void 0:n.checked)}}}},{getDefaultColumnDef:()=>M,getInitialState:e=>({columnSizing:{},columnSizingInfo:k(),...e}),getDefaultOptions:e=>({columnResizeMode:"onEnd",columnResizeDirection:"ltr",onColumnSizingChange:i("columnSizing",e),onColumnSizingInfoChange:i("columnSizingInfo",e)}),createColumn:(e,t)=>{e.getSize=()=>{var r,n,i;let o=t.getState().columnSizing[e.id];return Math.min(Math.max(null!=(r=e.columnDef.minSize)?r:M.minSize,null!=(n=null!=o?o:e.columnDef.size)?n:M.size),null!=(i=e.columnDef.maxSize)?i:M.maxSize)},e.getStart=a(e=>[e,A(t,e),t.getState().columnSizing],(t,r)=>r.slice(0,e.getIndex(t)).reduce((e,t)=>e+t.getSize(),0),l(t.options,"debugColumns","getStart")),e.getAfter=a(e=>[e,A(t,e),t.getState().columnSizing],(t,r)=>r.slice(e.getIndex(t)+1).reduce((e,t)=>e+t.getSize(),0),l(t.options,"debugColumns","getAfter")),e.resetSize=()=>{t.setColumnSizing(t=>{let{[e.id]:r,...n}=t;return n})},e.getCanResize=()=>{var r,n;return(null==(r=e.columnDef.enableResizing)||r)&&(null==(n=t.options.enableColumnResizing)||n)},e.getIsResizing=()=>t.getState().columnSizingInfo.isResizingColumn===e.id},createHeader:(e,t)=>{e.getSize=()=>{let t=0,r=e=>{if(e.subHeaders.length)e.subHeaders.forEach(r);else{var n;t+=null!=(n=e.column.getSize())?n:0}};return r(e),t},e.getStart=()=>{if(e.index>0){let t=e.headerGroup.headers[e.index-1];return t.getStart()+t.getSize()}return 0},e.getResizeHandler=r=>{let n=t.getColumn(e.column.id),i=null==n?void 0:n.getCanResize();return o=>{if(!n||!i||(null==o.persist||o.persist(),P(o)&&o.touches&&o.touches.length>1))return;let a=e.getSize(),l=e?e.getLeafHeaders().map(e=>[e.column.id,e.column.getSize()]):[[n.id,n.getSize()]],u=P(o)?Math.round(o.touches[0].clientX):o.clientX,s={},c=(e,r)=>{"number"==typeof r&&(t.setColumnSizingInfo(e=>{var n,i;let o="rtl"===t.options.columnResizeDirection?-1:1,a=(r-(null!=(n=null==e?void 0:e.startOffset)?n:0))*o,l=Math.max(a/(null!=(i=null==e?void 0:e.startSize)?i:0),-.999999);return e.columnSizingStart.forEach(e=>{let[t,r]=e;s[t]=Math.round(100*Math.max(r+r*l,0))/100}),{...e,deltaOffset:a,deltaPercentage:l}}),("onChange"===t.options.columnResizeMode||"end"===e)&&t.setColumnSizing(e=>({...e,...s})))},f=e=>c("move",e),d=e=>{c("end",e),t.setColumnSizingInfo(e=>({...e,isResizingColumn:!1,startOffset:null,startSize:null,deltaOffset:null,deltaPercentage:null,columnSizingStart:[]}))},p=r||("undefined"!=typeof document?document:null),h={moveHandler:e=>f(e.clientX),upHandler:e=>{null==p||p.removeEventListener("mousemove",h.moveHandler),null==p||p.removeEventListener("mouseup",h.upHandler),d(e.clientX)}},g={moveHandler:e=>(e.cancelable&&(e.preventDefault(),e.stopPropagation()),f(e.touches[0].clientX),!1),upHandler:e=>{var t;null==p||p.removeEventListener("touchmove",g.moveHandler),null==p||p.removeEventListener("touchend",g.upHandler),e.cancelable&&(e.preventDefault(),e.stopPropagation()),d(null==(t=e.touches[0])?void 0:t.clientX)}},v=!!function(){if("boolean"==typeof _)return _;let e=!1;try{let t=()=>{};window.addEventListener("test",t,{get passive(){return e=!0,!1}}),window.removeEventListener("test",t)}catch(t){e=!1}return _=e}()&&{passive:!1};P(o)?(null==p||p.addEventListener("touchmove",g.moveHandler,v),null==p||p.addEventListener("touchend",g.upHandler,v)):(null==p||p.addEventListener("mousemove",h.moveHandler,v),null==p||p.addEventListener("mouseup",h.upHandler,v)),t.setColumnSizingInfo(e=>({...e,startOffset:u,startSize:a,deltaOffset:0,deltaPercentage:0,columnSizingStart:l,isResizingColumn:n.id}))}}},createTable:e=>{e.setColumnSizing=t=>null==e.options.onColumnSizingChange?void 0:e.options.onColumnSizingChange(t),e.setColumnSizingInfo=t=>null==e.options.onColumnSizingInfoChange?void 0:e.options.onColumnSizingInfoChange(t),e.resetColumnSizing=t=>{var r;e.setColumnSizing(t?{}:null!=(r=e.initialState.columnSizing)?r:{})},e.resetHeaderSizeInfo=t=>{var r;e.setColumnSizingInfo(t?k():null!=(r=e.initialState.columnSizingInfo)?r:k())},e.getTotalSize=()=>{var t,r;return null!=(t=null==(r=e.getHeaderGroups()[0])?void 0:r.headers.reduce((e,t)=>e+t.getSize(),0))?t:0},e.getLeftTotalSize=()=>{var t,r;return null!=(t=null==(r=e.getLeftHeaderGroups()[0])?void 0:r.headers.reduce((e,t)=>e+t.getSize(),0))?t:0},e.getCenterTotalSize=()=>{var t,r;return null!=(t=null==(r=e.getCenterHeaderGroups()[0])?void 0:r.headers.reduce((e,t)=>e+t.getSize(),0))?t:0},e.getRightTotalSize=()=>{var t,r;return null!=(t=null==(r=e.getRightHeaderGroups()[0])?void 0:r.headers.reduce((e,t)=>e+t.getSize(),0))?t:0}}}];function U(e){var t,r;let i=[...V,...null!=(t=e._features)?t:[]],o={_features:i},u=o._features.reduce((e,t)=>Object.assign(e,null==t.getDefaultOptions?void 0:t.getDefaultOptions(o)),{}),s={...null!=(r=e.initialState)?r:{}};o._features.forEach(e=>{var t;s=null!=(t=null==e.getInitialState?void 0:e.getInitialState(s))?t:s});let c=[],f=!1,d={_features:i,options:{...u,...e},initialState:s,_queue:e=>{c.push(e),f||(f=!0,Promise.resolve().then(()=>{for(;c.length;)c.shift()();f=!1}).catch(e=>setTimeout(()=>{throw e})))},reset:()=>{o.setState(o.initialState)},setOptions:e=>{var t;t=n(e,o.options),o.options=o.options.mergeOptions?o.options.mergeOptions(u,t):{...u,...t}},getState:()=>o.options.state,setState:e=>{null==o.options.onStateChange||o.options.onStateChange(e)},_getRowId:(e,t,r)=>{var n;return null!=(n=null==o.options.getRowId?void 0:o.options.getRowId(e,t,r))?n:`${r?[r.id,t].join("."):t}`},getCoreRowModel:()=>(o._getCoreRowModel||(o._getCoreRowModel=o.options.getCoreRowModel(o)),o._getCoreRowModel()),getRowModel:()=>o.getPaginationRowModel(),getRow:(e,t)=>{let r=(t?o.getPrePaginationRowModel():o.getRowModel()).rowsById[e];if(!r&&!(r=o.getCoreRowModel().rowsById[e]))throw Error();return r},_getDefaultColumnDef:a(()=>[o.options.defaultColumn],e=>{var t;return e=null!=(t=e)?t:{},{header:e=>{let t=e.header.column.columnDef;return t.accessorKey?t.accessorKey:t.accessorFn?t.id:null},cell:e=>{var t,r;return null!=(t=null==(r=e.renderValue())||null==r.toString?void 0:r.toString())?t:null},...o._features.reduce((e,t)=>Object.assign(e,null==t.getDefaultColumnDef?void 0:t.getDefaultColumnDef()),{}),...e}},l(e,"debugColumns","_getDefaultColumnDef")),_getColumnDefs:()=>o.options.columns,getAllColumns:a(()=>[o._getColumnDefs()],e=>{let t=function(e,r,n){return void 0===n&&(n=0),e.map(e=>{let i=function(e,t,r,n){var i,o;let u,s={...e._getDefaultColumnDef(),...t},c=s.accessorKey,f=null!=(i=null!=(o=s.id)?o:c?"function"==typeof String.prototype.replaceAll?c.replaceAll(".","_"):c.replace(/\./g,"_"):void 0)?i:"string"==typeof s.header?s.header:void 0;if(s.accessorFn?u=s.accessorFn:c&&(u=c.includes(".")?e=>{let t=e;for(let e of c.split(".")){var r;t=null==(r=t)?void 0:r[e]}return t}:e=>e[s.accessorKey]),!f)throw Error();let d={id:`${String(f)}`,accessorFn:u,parent:n,depth:r,columnDef:s,columns:[],getFlatColumns:a(()=>[!0],()=>{var e;return[d,...null==(e=d.columns)?void 0:e.flatMap(e=>e.getFlatColumns())]},l(e.options,"debugColumns","column.getFlatColumns")),getLeafColumns:a(()=>[e._getOrderColumnsFn()],e=>{var t;return null!=(t=d.columns)&&t.length?e(d.columns.flatMap(e=>e.getLeafColumns())):[d]},l(e.options,"debugColumns","column.getLeafColumns"))};for(let t of e._features)null==t.createColumn||t.createColumn(d,e);return d}(o,e,n,r);return i.columns=e.columns?t(e.columns,i,n+1):[],i})};return t(e)},l(e,"debugColumns","getAllColumns")),getAllFlatColumns:a(()=>[o.getAllColumns()],e=>e.flatMap(e=>e.getFlatColumns()),l(e,"debugColumns","getAllFlatColumns")),_getAllFlatColumnsById:a(()=>[o.getAllFlatColumns()],e=>e.reduce((e,t)=>(e[t.id]=t,e),{}),l(e,"debugColumns","getAllFlatColumnsById")),getAllLeafColumns:a(()=>[o.getAllColumns(),o._getOrderColumnsFn()],(e,t)=>t(e.flatMap(e=>e.getLeafColumns())),l(e,"debugColumns","getAllLeafColumns")),getColumn:e=>o._getAllFlatColumnsById()[e]};Object.assign(o,d);for(let e=0;ea(()=>[e.options.data],t=>{let r={rows:[],flatRows:[],rowsById:{}},n=function(t,i,o){void 0===i&&(i=0);let a=[];for(let u=0;ue._autoResetPageIndex()))}function G(e,t,r){return r.options.filterFromLeafRows?function(e,t,r){var n;let i=[],o={},a=null!=(n=r.options.maxLeafRowFilterDepth)?n:100,l=function(e,n){void 0===n&&(n=0);let u=[];for(let c=0;ca(()=>[e.getPreFilteredRowModel(),e.getState().columnFilters,e.getState().globalFilter,e.getFilteredRowModel()],(r,n,i)=>{if(!r.rows.length||!(null!=n&&n.length)&&!i)return r;let o=[...n.map(e=>e.id).filter(e=>e!==t),i?"__global__":void 0].filter(Boolean);return G(r.rows,e=>{for(let t=0;ta(()=>{var r;return[null==(r=e.getColumn(t))?void 0:r.getFacetedRowModel()]},e=>{if(!e)return new Map;let r=new Map;for(let i=0;ia(()=>[e.getPreFilteredRowModel(),e.getState().columnFilters,e.getState().globalFilter],(t,r,n)=>{let i,o;if(!t.rows.length||!(null!=r&&r.length)&&!n){for(let e=0;e{var r;let n=e.getColumn(t.id);if(!n)return;let i=n.getFilterFn();i&&a.push({id:t.id,filterFn:i,resolvedValue:null!=(r=null==i.resolveFilterValue?void 0:i.resolveFilterValue(t.value))?r:t.value})});let u=(null!=r?r:[]).map(e=>e.id),s=e.getGlobalFilterFn(),c=e.getAllLeafColumns().filter(e=>e.getCanGlobalFilter());n&&s&&c.length&&(u.push("__global__"),c.forEach(e=>{var t;l.push({id:e.id,filterFn:s,resolvedValue:null!=(t=null==s.resolveFilterValue?void 0:s.resolveFilterValue(n))?t:n})}));for(let e=0;e{r.columnFiltersMeta[t]=e})}if(l.length){for(let e=0;e{r.columnFiltersMeta[t]=e})){r.columnFilters.__global__=!0;break}}!0!==r.columnFilters.__global__&&(r.columnFilters.__global__=!1)}}return G(t.rows,e=>{for(let t=0;te._autoResetPageIndex()))}function q(e){return e=>a(()=>[e.getState().pagination,e.getPrePaginationRowModel(),e.options.paginateExpandedRows?void 0:e.getState().expanded],(t,r)=>{let n;if(!r.rows.length)return r;let{pageSize:i,pageIndex:o}=t,{rows:a,flatRows:l,rowsById:u}=r,s=i*o;a=a.slice(s,s+i),(n=e.options.paginateExpandedRows?{rows:a,flatRows:l,rowsById:u}:function(e){let t=[],r=e=>{var n;t.push(e),null!=(n=e.subRows)&&n.length&&e.getIsExpanded()&&e.subRows.forEach(r)};return e.rows.forEach(r),{rows:t,flatRows:e.flatRows,rowsById:e.rowsById}}({rows:a,flatRows:l,rowsById:u})).flatRows=[];let c=e=>{n.flatRows.push(e),e.subRows.length&&e.subRows.forEach(c)};return n.rows.forEach(c),n},l(e.options,"debugTable","getPaginationRowModel"))}function Y(){return e=>a(()=>[e.getState().sorting,e.getPreSortedRowModel()],(t,r)=>{if(!r.rows.length||!(null!=t&&t.length))return r;let n=e.getState().sorting,i=[],o=n.filter(t=>{var r;return null==(r=e.getColumn(t.id))?void 0:r.getCanSort()}),a={};o.forEach(t=>{let r=e.getColumn(t.id);r&&(a[t.id]={sortUndefined:r.columnDef.sortUndefined,invertSorting:r.columnDef.invertSorting,sortingFn:r.getSortingFn()})});let l=e=>{let t=e.map(e=>({...e}));return t.sort((e,t)=>{for(let n=0;n{var t;i.push(e),null!=(t=e.subRows)&&t.length&&(e.subRows=l(e.subRows))}),t};return{rows:l(r.rows),flatRows:i,rowsById:r.rowsById}},l(e.options,"debugTable","getSortedRowModel",()=>e._autoResetPageIndex()))}},1147:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isPlainObject=function(e){if(!e||"object"!=typeof e)return!1;let t=Object.getPrototypeOf(e);return(null===t||t===Object.prototype||null===Object.getPrototypeOf(t))&&"[object Object]"===Object.prototype.toString.call(e)}},1206:(e,t,r)=>{"use strict";r.d(t,{FN:()=>n}),r(8266);let n=e=>{let{transform:t}=e;return{...t,x:0}}},1243:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("triangle-alert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]])},1285:(e,t,r)=>{"use strict";r.d(t,{B:()=>u});var n,i=r(2115),o=r(2712),a=(n||(n=r.t(i,2)))[" useId ".trim().toString()]||(()=>void 0),l=0;function u(e){let[t,r]=i.useState(a());return(0,o.N)(()=>{e||r(e=>e??String(l++))},[e]),e||(t?`radix-${t}`:"")}},1414:(e,t,r)=>{"use strict";e.exports=r(2436)},1551:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(668),i=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,o=/^\w*$/;t.isKey=function(e,t){return!Array.isArray(e)&&(!!("number"==typeof e||"boolean"==typeof e||null==e||n.isSymbol(e))||"string"==typeof e&&(o.test(e)||!i.test(e))||null!=t&&Object.hasOwn(t,e))}},1571:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(2465),i=r(2194),o=r(4804),a=r(4517);t.iteratee=function(e){if(null==e)return n.identity;switch(typeof e){case"function":return e;case"object":if(Array.isArray(e)&&2===e.length)return a.matchesProperty(e[0],e[1]);return o.matches(e);case"string":case"symbol":case"number":return i.property(e)}}},1643:(e,t,r)=>{"use strict";r.d(t,{m:()=>n});var n={isSsr:!("undefined"!=typeof window&&window.document&&window.document.createElement&&window.setTimeout)}},1807:(e,t,r)=>{"use strict";r.d(t,{r:()=>o});var n=r(2115),i=(0,n.createContext)(null),o=()=>null!=(0,n.useContext)(i)},1847:(e,t,r)=>{"use strict";r.d(t,{i:()=>u});let n=Math.PI,i=2*n,o=i-1e-6;function a(e){this._+=e[0];for(let t=1,r=e.length;t=0))throw Error(`invalid digits: ${e}`);if(t>15)return a;let r=10**t;return function(e){this._+=e[0];for(let t=1,n=e.length;t1e-6)if(Math.abs(f*u-s*c)>1e-6&&o){let p=r-a,h=i-l,g=u*u+s*s,v=Math.sqrt(g),m=Math.sqrt(d),y=o*Math.tan((n-Math.acos((g+d-(p*p+h*h))/(2*v*m)))/2),b=y/m,w=y/v;Math.abs(b-1)>1e-6&&this._append`L${e+b*c},${t+b*f}`,this._append`A${o},${o},0,0,${+(f*p>c*h)},${this._x1=e+w*u},${this._y1=t+w*s}`}else this._append`L${this._x1=e},${this._y1=t}`}arc(e,t,r,a,l,u){if(e*=1,t*=1,r*=1,u=!!u,r<0)throw Error(`negative radius: ${r}`);let s=r*Math.cos(a),c=r*Math.sin(a),f=e+s,d=t+c,p=1^u,h=u?a-l:l-a;null===this._x1?this._append`M${f},${d}`:(Math.abs(this._x1-f)>1e-6||Math.abs(this._y1-d)>1e-6)&&this._append`L${f},${d}`,r&&(h<0&&(h=h%i+i),h>o?this._append`A${r},${r},0,1,${p},${e-s},${t-c}A${r},${r},0,1,${p},${this._x1=f},${this._y1=d}`:h>1e-6&&this._append`A${r},${r},0,${+(h>=n)},${p},${this._x1=e+r*Math.cos(l)},${this._y1=t+r*Math.sin(l)}`)}rect(e,t,r,n){this._append`M${this._x0=this._x1=+e},${this._y0=this._y1=+t}h${r*=1}v${+n}h${-r}Z`}toString(){return this._}}function u(e){let t=3;return e.digits=function(r){if(!arguments.length)return t;if(null==r)t=null;else{let e=Math.floor(r);if(!(e>=0))throw RangeError(`invalid digits: ${r}`);t=e}return e},()=>new l(t)}l.prototype},1918:(e,t,r)=>{"use strict";r.d(t,{UC:()=>r4,In:()=>r5,q7:()=>r8,VF:()=>r7,p4:()=>r9,ZL:()=>r6,bL:()=>r0,wn:()=>nt,PP:()=>ne,l9:()=>r1,WT:()=>r2,LM:()=>r3});var n,i,o,a=r(2115),l=r(7650);function u(e,[t,r]){return Math.min(r,Math.max(t,e))}var s=r(5185),c=r(7328),f=r(6101),d=r(6081),p=r(4315),h=r(3655),g=r(9033),v=r(5155),m="dismissableLayer.update",y=a.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),b=a.forwardRef((e,t)=>{var r,n;let{disableOutsidePointerEvents:o=!1,onEscapeKeyDown:l,onPointerDownOutside:u,onFocusOutside:c,onInteractOutside:d,onDismiss:p,...b}=e,S=a.useContext(y),[O,E]=a.useState(null),C=null!=(n=null==O?void 0:O.ownerDocument)?n:null==(r=globalThis)?void 0:r.document,[,M]=a.useState({}),k=(0,f.s)(t,e=>E(e)),_=Array.from(S.layers),[P]=[...S.layersWithOutsidePointerEventsDisabled].slice(-1),A=_.indexOf(P),j=O?_.indexOf(O):-1,T=S.layersWithOutsidePointerEventsDisabled.size>0,R=j>=A,z=function(e){var t;let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null==(t=globalThis)?void 0:t.document,n=(0,g.c)(e),i=a.useRef(!1),o=a.useRef(()=>{});return a.useEffect(()=>{let e=e=>{if(e.target&&!i.current){let t=function(){x("dismissableLayer.pointerDownOutside",n,i,{discrete:!0})},i={originalEvent:e};"touch"===e.pointerType?(r.removeEventListener("click",o.current),o.current=t,r.addEventListener("click",o.current,{once:!0})):t()}else r.removeEventListener("click",o.current);i.current=!1},t=window.setTimeout(()=>{r.addEventListener("pointerdown",e)},0);return()=>{window.clearTimeout(t),r.removeEventListener("pointerdown",e),r.removeEventListener("click",o.current)}},[r,n]),{onPointerDownCapture:()=>i.current=!0}}(e=>{let t=e.target,r=[...S.branches].some(e=>e.contains(t));R&&!r&&(null==u||u(e),null==d||d(e),e.defaultPrevented||null==p||p())},C),D=function(e){var t;let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null==(t=globalThis)?void 0:t.document,n=(0,g.c)(e),i=a.useRef(!1);return a.useEffect(()=>{let e=e=>{e.target&&!i.current&&x("dismissableLayer.focusOutside",n,{originalEvent:e},{discrete:!1})};return r.addEventListener("focusin",e),()=>r.removeEventListener("focusin",e)},[r,n]),{onFocusCapture:()=>i.current=!0,onBlurCapture:()=>i.current=!1}}(e=>{let t=e.target;![...S.branches].some(e=>e.contains(t))&&(null==c||c(e),null==d||d(e),e.defaultPrevented||null==p||p())},C);return!function(e,t=globalThis?.document){let r=(0,g.c)(e);a.useEffect(()=>{let e=e=>{"Escape"===e.key&&r(e)};return t.addEventListener("keydown",e,{capture:!0}),()=>t.removeEventListener("keydown",e,{capture:!0})},[r,t])}(e=>{j===S.layers.size-1&&(null==l||l(e),!e.defaultPrevented&&p&&(e.preventDefault(),p()))},C),a.useEffect(()=>{if(O)return o&&(0===S.layersWithOutsidePointerEventsDisabled.size&&(i=C.body.style.pointerEvents,C.body.style.pointerEvents="none"),S.layersWithOutsidePointerEventsDisabled.add(O)),S.layers.add(O),w(),()=>{o&&1===S.layersWithOutsidePointerEventsDisabled.size&&(C.body.style.pointerEvents=i)}},[O,C,o,S]),a.useEffect(()=>()=>{O&&(S.layers.delete(O),S.layersWithOutsidePointerEventsDisabled.delete(O),w())},[O,S]),a.useEffect(()=>{let e=()=>M({});return document.addEventListener(m,e),()=>document.removeEventListener(m,e)},[]),(0,v.jsx)(h.sG.div,{...b,ref:k,style:{pointerEvents:T?R?"auto":"none":void 0,...e.style},onFocusCapture:(0,s.m)(e.onFocusCapture,D.onFocusCapture),onBlurCapture:(0,s.m)(e.onBlurCapture,D.onBlurCapture),onPointerDownCapture:(0,s.m)(e.onPointerDownCapture,z.onPointerDownCapture)})});function w(){let e=new CustomEvent(m);document.dispatchEvent(e)}function x(e,t,r,n){let{discrete:i}=n,o=r.originalEvent.target,a=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:r});t&&o.addEventListener(e,t,{once:!0}),i?(0,h.hO)(o,a):o.dispatchEvent(a)}b.displayName="DismissableLayer",a.forwardRef((e,t)=>{let r=a.useContext(y),n=a.useRef(null),i=(0,f.s)(t,n);return a.useEffect(()=>{let e=n.current;if(e)return r.branches.add(e),()=>{r.branches.delete(e)}},[r.branches]),(0,v.jsx)(h.sG.div,{...e,ref:i})}).displayName="DismissableLayerBranch";var S=0;function O(){let e=document.createElement("span");return e.setAttribute("data-radix-focus-guard",""),e.tabIndex=0,e.style.outline="none",e.style.opacity="0",e.style.position="fixed",e.style.pointerEvents="none",e}var E="focusScope.autoFocusOnMount",C="focusScope.autoFocusOnUnmount",M={bubbles:!1,cancelable:!0},k=a.forwardRef((e,t)=>{let{loop:r=!1,trapped:n=!1,onMountAutoFocus:i,onUnmountAutoFocus:o,...l}=e,[u,s]=a.useState(null),c=(0,g.c)(i),d=(0,g.c)(o),p=a.useRef(null),m=(0,f.s)(t,e=>s(e)),y=a.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;a.useEffect(()=>{if(n){let e=function(e){if(y.paused||!u)return;let t=e.target;u.contains(t)?p.current=t:A(p.current,{select:!0})},t=function(e){if(y.paused||!u)return;let t=e.relatedTarget;null!==t&&(u.contains(t)||A(p.current,{select:!0}))};document.addEventListener("focusin",e),document.addEventListener("focusout",t);let r=new MutationObserver(function(e){if(document.activeElement===document.body)for(let t of e)t.removedNodes.length>0&&A(u)});return u&&r.observe(u,{childList:!0,subtree:!0}),()=>{document.removeEventListener("focusin",e),document.removeEventListener("focusout",t),r.disconnect()}}},[n,u,y.paused]),a.useEffect(()=>{if(u){j.add(y);let e=document.activeElement;if(!u.contains(e)){let t=new CustomEvent(E,M);u.addEventListener(E,c),u.dispatchEvent(t),t.defaultPrevented||(function(e){let{select:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=document.activeElement;for(let n of e)if(A(n,{select:t}),document.activeElement!==r)return}(_(u).filter(e=>"A"!==e.tagName),{select:!0}),document.activeElement===e&&A(u))}return()=>{u.removeEventListener(E,c),setTimeout(()=>{let t=new CustomEvent(C,M);u.addEventListener(C,d),u.dispatchEvent(t),t.defaultPrevented||A(null!=e?e:document.body,{select:!0}),u.removeEventListener(C,d),j.remove(y)},0)}}},[u,c,d,y]);let b=a.useCallback(e=>{if(!r&&!n||y.paused)return;let t="Tab"===e.key&&!e.altKey&&!e.ctrlKey&&!e.metaKey,i=document.activeElement;if(t&&i){let t=e.currentTarget,[n,o]=function(e){let t=_(e);return[P(t,e),P(t.reverse(),e)]}(t);n&&o?e.shiftKey||i!==o?e.shiftKey&&i===n&&(e.preventDefault(),r&&A(o,{select:!0})):(e.preventDefault(),r&&A(n,{select:!0})):i===t&&e.preventDefault()}},[r,n,y.paused]);return(0,v.jsx)(h.sG.div,{tabIndex:-1,...l,ref:m,onKeyDown:b})});function _(e){let t=[],r=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:e=>{let t="INPUT"===e.tagName&&"hidden"===e.type;return e.disabled||e.hidden||t?NodeFilter.FILTER_SKIP:e.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;r.nextNode();)t.push(r.currentNode);return t}function P(e,t){for(let r of e)if(!function(e,t){let{upTo:r}=t;if("hidden"===getComputedStyle(e).visibility)return!0;for(;e&&(void 0===r||e!==r);){if("none"===getComputedStyle(e).display)return!0;e=e.parentElement}return!1}(r,{upTo:t}))return r}function A(e){let{select:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(e&&e.focus){var r;let n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&(r=e)instanceof HTMLInputElement&&"select"in r&&t&&e.select()}}k.displayName="FocusScope";var j=function(){let e=[];return{add(t){let r=e[0];t!==r&&(null==r||r.pause()),(e=T(e,t)).unshift(t)},remove(t){var r;null==(r=(e=T(e,t))[0])||r.resume()}}}();function T(e,t){let r=[...e],n=r.indexOf(t);return -1!==n&&r.splice(n,1),r}var R=r(1285);let z=["top","right","bottom","left"],D=Math.min,I=Math.max,N=Math.round,L=Math.floor,F=e=>({x:e,y:e}),$={left:"right",right:"left",bottom:"top",top:"bottom"},B={start:"end",end:"start"};function V(e,t){return"function"==typeof e?e(t):e}function U(e){return e.split("-")[0]}function H(e){return e.split("-")[1]}function G(e){return"x"===e?"y":"x"}function Z(e){return"y"===e?"height":"width"}let W=new Set(["top","bottom"]);function K(e){return W.has(U(e))?"y":"x"}function q(e){return e.replace(/start|end/g,e=>B[e])}let Y=["left","right"],X=["right","left"],J=["top","bottom"],Q=["bottom","top"];function ee(e){return e.replace(/left|right|bottom|top/g,e=>$[e])}function et(e){return"number"!=typeof e?{top:0,right:0,bottom:0,left:0,...e}:{top:e,right:e,bottom:e,left:e}}function er(e){let{x:t,y:r,width:n,height:i}=e;return{width:n,height:i,top:r,left:t,right:t+n,bottom:r+i,x:t,y:r}}function en(e,t,r){let n,{reference:i,floating:o}=e,a=K(t),l=G(K(t)),u=Z(l),s=U(t),c="y"===a,f=i.x+i.width/2-o.width/2,d=i.y+i.height/2-o.height/2,p=i[u]/2-o[u]/2;switch(s){case"top":n={x:f,y:i.y-o.height};break;case"bottom":n={x:f,y:i.y+i.height};break;case"right":n={x:i.x+i.width,y:d};break;case"left":n={x:i.x-o.width,y:d};break;default:n={x:i.x,y:i.y}}switch(H(t)){case"start":n[l]-=p*(r&&c?-1:1);break;case"end":n[l]+=p*(r&&c?-1:1)}return n}let ei=async(e,t,r)=>{let{placement:n="bottom",strategy:i="absolute",middleware:o=[],platform:a}=r,l=o.filter(Boolean),u=await (null==a.isRTL?void 0:a.isRTL(t)),s=await a.getElementRects({reference:e,floating:t,strategy:i}),{x:c,y:f}=en(s,n,u),d=n,p={},h=0;for(let r=0;re[t]>=0)}let eu=new Set(["left","top"]);async function es(e,t){let{placement:r,platform:n,elements:i}=e,o=await (null==n.isRTL?void 0:n.isRTL(i.floating)),a=U(r),l=H(r),u="y"===K(r),s=eu.has(a)?-1:1,c=o&&u?-1:1,f=V(t,e),{mainAxis:d,crossAxis:p,alignmentAxis:h}="number"==typeof f?{mainAxis:f,crossAxis:0,alignmentAxis:null}:{mainAxis:f.mainAxis||0,crossAxis:f.crossAxis||0,alignmentAxis:f.alignmentAxis};return l&&"number"==typeof h&&(p="end"===l?-1*h:h),u?{x:p*c,y:d*s}:{x:d*s,y:p*c}}function ec(){return"undefined"!=typeof window}function ef(e){return eh(e)?(e.nodeName||"").toLowerCase():"#document"}function ed(e){var t;return(null==e||null==(t=e.ownerDocument)?void 0:t.defaultView)||window}function ep(e){var t;return null==(t=(eh(e)?e.ownerDocument:e.document)||window.document)?void 0:t.documentElement}function eh(e){return!!ec()&&(e instanceof Node||e instanceof ed(e).Node)}function eg(e){return!!ec()&&(e instanceof Element||e instanceof ed(e).Element)}function ev(e){return!!ec()&&(e instanceof HTMLElement||e instanceof ed(e).HTMLElement)}function em(e){return!!ec()&&"undefined"!=typeof ShadowRoot&&(e instanceof ShadowRoot||e instanceof ed(e).ShadowRoot)}let ey=new Set(["inline","contents"]);function eb(e){let{overflow:t,overflowX:r,overflowY:n,display:i}=eA(e);return/auto|scroll|overlay|hidden|clip/.test(t+n+r)&&!ey.has(i)}let ew=new Set(["table","td","th"]),ex=[":popover-open",":modal"];function eS(e){return ex.some(t=>{try{return e.matches(t)}catch(e){return!1}})}let eO=["transform","translate","scale","rotate","perspective"],eE=["transform","translate","scale","rotate","perspective","filter"],eC=["paint","layout","strict","content"];function eM(e){let t=ek(),r=eg(e)?eA(e):e;return eO.some(e=>!!r[e]&&"none"!==r[e])||!!r.containerType&&"normal"!==r.containerType||!t&&!!r.backdropFilter&&"none"!==r.backdropFilter||!t&&!!r.filter&&"none"!==r.filter||eE.some(e=>(r.willChange||"").includes(e))||eC.some(e=>(r.contain||"").includes(e))}function ek(){return"undefined"!=typeof CSS&&!!CSS.supports&&CSS.supports("-webkit-backdrop-filter","none")}let e_=new Set(["html","body","#document"]);function eP(e){return e_.has(ef(e))}function eA(e){return ed(e).getComputedStyle(e)}function ej(e){return eg(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function eT(e){if("html"===ef(e))return e;let t=e.assignedSlot||e.parentNode||em(e)&&e.host||ep(e);return em(t)?t.host:t}function eR(e,t,r){var n;void 0===t&&(t=[]),void 0===r&&(r=!0);let i=function e(t){let r=eT(t);return eP(r)?t.ownerDocument?t.ownerDocument.body:t.body:ev(r)&&eb(r)?r:e(r)}(e),o=i===(null==(n=e.ownerDocument)?void 0:n.body),a=ed(i);if(o){let e=ez(a);return t.concat(a,a.visualViewport||[],eb(i)?i:[],e&&r?eR(e):[])}return t.concat(i,eR(i,[],r))}function ez(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function eD(e){let t=eA(e),r=parseFloat(t.width)||0,n=parseFloat(t.height)||0,i=ev(e),o=i?e.offsetWidth:r,a=i?e.offsetHeight:n,l=N(r)!==o||N(n)!==a;return l&&(r=o,n=a),{width:r,height:n,$:l}}function eI(e){return eg(e)?e:e.contextElement}function eN(e){let t=eI(e);if(!ev(t))return F(1);let r=t.getBoundingClientRect(),{width:n,height:i,$:o}=eD(t),a=(o?N(r.width):r.width)/n,l=(o?N(r.height):r.height)/i;return a&&Number.isFinite(a)||(a=1),l&&Number.isFinite(l)||(l=1),{x:a,y:l}}let eL=F(0);function eF(e){let t=ed(e);return ek()&&t.visualViewport?{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}:eL}function e$(e,t,r,n){var i;void 0===t&&(t=!1),void 0===r&&(r=!1);let o=e.getBoundingClientRect(),a=eI(e),l=F(1);t&&(n?eg(n)&&(l=eN(n)):l=eN(e));let u=(void 0===(i=r)&&(i=!1),n&&(!i||n===ed(a))&&i)?eF(a):F(0),s=(o.left+u.x)/l.x,c=(o.top+u.y)/l.y,f=o.width/l.x,d=o.height/l.y;if(a){let e=ed(a),t=n&&eg(n)?ed(n):n,r=e,i=ez(r);for(;i&&n&&t!==r;){let e=eN(i),t=i.getBoundingClientRect(),n=eA(i),o=t.left+(i.clientLeft+parseFloat(n.paddingLeft))*e.x,a=t.top+(i.clientTop+parseFloat(n.paddingTop))*e.y;s*=e.x,c*=e.y,f*=e.x,d*=e.y,s+=o,c+=a,i=ez(r=ed(i))}}return er({width:f,height:d,x:s,y:c})}function eB(e,t){let r=ej(e).scrollLeft;return t?t.left+r:e$(ep(e)).left+r}function eV(e,t,r){void 0===r&&(r=!1);let n=e.getBoundingClientRect();return{x:n.left+t.scrollLeft-(r?0:eB(e,n)),y:n.top+t.scrollTop}}let eU=new Set(["absolute","fixed"]);function eH(e,t,r){let n;if("viewport"===t)n=function(e,t){let r=ed(e),n=ep(e),i=r.visualViewport,o=n.clientWidth,a=n.clientHeight,l=0,u=0;if(i){o=i.width,a=i.height;let e=ek();(!e||e&&"fixed"===t)&&(l=i.offsetLeft,u=i.offsetTop)}return{width:o,height:a,x:l,y:u}}(e,r);else if("document"===t)n=function(e){let t=ep(e),r=ej(e),n=e.ownerDocument.body,i=I(t.scrollWidth,t.clientWidth,n.scrollWidth,n.clientWidth),o=I(t.scrollHeight,t.clientHeight,n.scrollHeight,n.clientHeight),a=-r.scrollLeft+eB(e),l=-r.scrollTop;return"rtl"===eA(n).direction&&(a+=I(t.clientWidth,n.clientWidth)-i),{width:i,height:o,x:a,y:l}}(ep(e));else if(eg(t))n=function(e,t){let r=e$(e,!0,"fixed"===t),n=r.top+e.clientTop,i=r.left+e.clientLeft,o=ev(e)?eN(e):F(1),a=e.clientWidth*o.x,l=e.clientHeight*o.y;return{width:a,height:l,x:i*o.x,y:n*o.y}}(t,r);else{let r=eF(e);n={x:t.x-r.x,y:t.y-r.y,width:t.width,height:t.height}}return er(n)}function eG(e){return"static"===eA(e).position}function eZ(e,t){if(!ev(e)||"fixed"===eA(e).position)return null;if(t)return t(e);let r=e.offsetParent;return ep(e)===r&&(r=r.ownerDocument.body),r}function eW(e,t){var r;let n=ed(e);if(eS(e))return n;if(!ev(e)){let t=eT(e);for(;t&&!eP(t);){if(eg(t)&&!eG(t))return t;t=eT(t)}return n}let i=eZ(e,t);for(;i&&(r=i,ew.has(ef(r)))&&eG(i);)i=eZ(i,t);return i&&eP(i)&&eG(i)&&!eM(i)?n:i||function(e){let t=eT(e);for(;ev(t)&&!eP(t);){if(eM(t))return t;if(eS(t))break;t=eT(t)}return null}(e)||n}let eK=async function(e){let t=this.getOffsetParent||eW,r=this.getDimensions,n=await r(e.floating);return{reference:function(e,t,r){let n=ev(t),i=ep(t),o="fixed"===r,a=e$(e,!0,o,t),l={scrollLeft:0,scrollTop:0},u=F(0);if(n||!n&&!o)if(("body"!==ef(t)||eb(i))&&(l=ej(t)),n){let e=e$(t,!0,o,t);u.x=e.x+t.clientLeft,u.y=e.y+t.clientTop}else i&&(u.x=eB(i));o&&!n&&i&&(u.x=eB(i));let s=!i||n||o?F(0):eV(i,l);return{x:a.left+l.scrollLeft-u.x-s.x,y:a.top+l.scrollTop-u.y-s.y,width:a.width,height:a.height}}(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:n.width,height:n.height}}},eq={convertOffsetParentRelativeRectToViewportRelativeRect:function(e){let{elements:t,rect:r,offsetParent:n,strategy:i}=e,o="fixed"===i,a=ep(n),l=!!t&&eS(t.floating);if(n===a||l&&o)return r;let u={scrollLeft:0,scrollTop:0},s=F(1),c=F(0),f=ev(n);if((f||!f&&!o)&&(("body"!==ef(n)||eb(a))&&(u=ej(n)),ev(n))){let e=e$(n);s=eN(n),c.x=e.x+n.clientLeft,c.y=e.y+n.clientTop}let d=!a||f||o?F(0):eV(a,u,!0);return{width:r.width*s.x,height:r.height*s.y,x:r.x*s.x-u.scrollLeft*s.x+c.x+d.x,y:r.y*s.y-u.scrollTop*s.y+c.y+d.y}},getDocumentElement:ep,getClippingRect:function(e){let{element:t,boundary:r,rootBoundary:n,strategy:i}=e,o=[..."clippingAncestors"===r?eS(t)?[]:function(e,t){let r=t.get(e);if(r)return r;let n=eR(e,[],!1).filter(e=>eg(e)&&"body"!==ef(e)),i=null,o="fixed"===eA(e).position,a=o?eT(e):e;for(;eg(a)&&!eP(a);){let t=eA(a),r=eM(a);r||"fixed"!==t.position||(i=null),(o?!r&&!i:!r&&"static"===t.position&&!!i&&eU.has(i.position)||eb(a)&&!r&&function e(t,r){let n=eT(t);return!(n===r||!eg(n)||eP(n))&&("fixed"===eA(n).position||e(n,r))}(e,a))?n=n.filter(e=>e!==a):i=t,a=eT(a)}return t.set(e,n),n}(t,this._c):[].concat(r),n],a=o[0],l=o.reduce((e,r)=>{let n=eH(t,r,i);return e.top=I(n.top,e.top),e.right=D(n.right,e.right),e.bottom=D(n.bottom,e.bottom),e.left=I(n.left,e.left),e},eH(t,a,i));return{width:l.right-l.left,height:l.bottom-l.top,x:l.left,y:l.top}},getOffsetParent:eW,getElementRects:eK,getClientRects:function(e){return Array.from(e.getClientRects())},getDimensions:function(e){let{width:t,height:r}=eD(e);return{width:t,height:r}},getScale:eN,isElement:eg,isRTL:function(e){return"rtl"===eA(e).direction}};function eY(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}let eX=e=>({name:"arrow",options:e,async fn(t){let{x:r,y:n,placement:i,rects:o,platform:a,elements:l,middlewareData:u}=t,{element:s,padding:c=0}=V(e,t)||{};if(null==s)return{};let f=et(c),d={x:r,y:n},p=G(K(i)),h=Z(p),g=await a.getDimensions(s),v="y"===p,m=v?"clientHeight":"clientWidth",y=o.reference[h]+o.reference[p]-d[p]-o.floating[h],b=d[p]-o.reference[p],w=await (null==a.getOffsetParent?void 0:a.getOffsetParent(s)),x=w?w[m]:0;x&&await (null==a.isElement?void 0:a.isElement(w))||(x=l.floating[m]||o.floating[h]);let S=x/2-g[h]/2-1,O=D(f[v?"top":"left"],S),E=D(f[v?"bottom":"right"],S),C=x-g[h]-E,M=x/2-g[h]/2+(y/2-b/2),k=I(O,D(M,C)),_=!u.arrow&&null!=H(i)&&M!==k&&o.reference[h]/2-(M{t.current=e}),t}var e5=a.forwardRef((e,t)=>{let{children:r,width:n=10,height:i=5,...o}=e;return(0,v.jsx)(h.sG.svg,{...o,ref:t,width:n,height:i,viewBox:"0 0 30 10",preserveAspectRatio:"none",children:e.asChild?r:(0,v.jsx)("polygon",{points:"0,0 30,0 15,10"})})});e5.displayName="Arrow";var e6=r(2712),e4="Popper",[e3,e8]=(0,d.A)(e4),[e9,e7]=e3(e4),te=e=>{let{__scopePopper:t,children:r}=e,[n,i]=a.useState(null);return(0,v.jsx)(e9,{scope:t,anchor:n,onAnchorChange:i,children:r})};te.displayName=e4;var tt="PopperAnchor",tr=a.forwardRef((e,t)=>{let{__scopePopper:r,virtualRef:n,...i}=e,o=e7(tt,r),l=a.useRef(null),u=(0,f.s)(t,l);return a.useEffect(()=>{o.onAnchorChange((null==n?void 0:n.current)||l.current)}),n?null:(0,v.jsx)(h.sG.div,{...i,ref:u})});tr.displayName=tt;var tn="PopperContent",[ti,to]=e3(tn),ta=a.forwardRef((e,t)=>{var r,n,i,o,u,s,c,d;let{__scopePopper:p,side:m="bottom",sideOffset:y=0,align:b="center",alignOffset:w=0,arrowPadding:x=0,avoidCollisions:S=!0,collisionBoundary:O=[],collisionPadding:E=0,sticky:C="partial",hideWhenDetached:M=!1,updatePositionStrategy:k="optimized",onPlaced:_,...P}=e,A=e7(tn,p),[j,T]=a.useState(null),R=(0,f.s)(t,e=>T(e)),[z,N]=a.useState(null),F=function(e){let[t,r]=a.useState(void 0);return(0,e6.N)(()=>{if(e){r({width:e.offsetWidth,height:e.offsetHeight});let t=new ResizeObserver(t=>{let n,i;if(!Array.isArray(t)||!t.length)return;let o=t[0];if("borderBoxSize"in o){let e=o.borderBoxSize,t=Array.isArray(e)?e[0]:e;n=t.inlineSize,i=t.blockSize}else n=e.offsetWidth,i=e.offsetHeight;r({width:n,height:i})});return t.observe(e,{box:"border-box"}),()=>t.unobserve(e)}r(void 0)},[e]),t}(z),$=null!=(c=null==F?void 0:F.width)?c:0,B=null!=(d=null==F?void 0:F.height)?d:0,W="number"==typeof E?E:{top:0,right:0,bottom:0,left:0,...E},et=Array.isArray(O)?O:[O],er=et.length>0,en={padding:W,boundary:et.filter(tc),altBoundary:er},{refs:ec,floatingStyles:ef,placement:ed,isPositioned:eh,middlewareData:eg}=function(e){void 0===e&&(e={});let{placement:t="bottom",strategy:r="absolute",middleware:n=[],platform:i,elements:{reference:o,floating:u}={},transform:s=!0,whileElementsMounted:c,open:f}=e,[d,p]=a.useState({x:0,y:0,strategy:r,placement:t,middlewareData:{},isPositioned:!1}),[h,g]=a.useState(n);eQ(h,n)||g(n);let[v,m]=a.useState(null),[y,b]=a.useState(null),w=a.useCallback(e=>{e!==E.current&&(E.current=e,m(e))},[]),x=a.useCallback(e=>{e!==C.current&&(C.current=e,b(e))},[]),S=o||v,O=u||y,E=a.useRef(null),C=a.useRef(null),M=a.useRef(d),k=null!=c,_=e2(c),P=e2(i),A=e2(f),j=a.useCallback(()=>{if(!E.current||!C.current)return;let e={placement:t,strategy:r,middleware:h};P.current&&(e.platform=P.current),((e,t,r)=>{let n=new Map,i={platform:eq,...r},o={...i.platform,_c:n};return ei(e,t,{...i,platform:o})})(E.current,C.current,e).then(e=>{let t={...e,isPositioned:!1!==A.current};T.current&&!eQ(M.current,t)&&(M.current=t,l.flushSync(()=>{p(t)}))})},[h,t,r,P,A]);eJ(()=>{!1===f&&M.current.isPositioned&&(M.current.isPositioned=!1,p(e=>({...e,isPositioned:!1})))},[f]);let T=a.useRef(!1);eJ(()=>(T.current=!0,()=>{T.current=!1}),[]),eJ(()=>{if(S&&(E.current=S),O&&(C.current=O),S&&O){if(_.current)return _.current(S,O,j);j()}},[S,O,j,_,k]);let R=a.useMemo(()=>({reference:E,floating:C,setReference:w,setFloating:x}),[w,x]),z=a.useMemo(()=>({reference:S,floating:O}),[S,O]),D=a.useMemo(()=>{let e={position:r,left:0,top:0};if(!z.floating)return e;let t=e1(z.floating,d.x),n=e1(z.floating,d.y);return s?{...e,transform:"translate("+t+"px, "+n+"px)",...e0(z.floating)>=1.5&&{willChange:"transform"}}:{position:r,left:t,top:n}},[r,s,z.floating,d.x,d.y]);return a.useMemo(()=>({...d,update:j,refs:R,elements:z,floatingStyles:D}),[d,j,R,z,D])}({strategy:"fixed",placement:m+("center"!==b?"-"+b:""),whileElementsMounted:function(){for(var e=arguments.length,t=Array(e),r=0;r{o&&e.addEventListener("scroll",r,{passive:!0}),a&&e.addEventListener("resize",r)});let d=c&&u?function(e,t){let r,n=null,i=ep(e);function o(){var e;clearTimeout(r),null==(e=n)||e.disconnect(),n=null}return!function a(l,u){void 0===l&&(l=!1),void 0===u&&(u=1),o();let s=e.getBoundingClientRect(),{left:c,top:f,width:d,height:p}=s;if(l||t(),!d||!p)return;let h=L(f),g=L(i.clientWidth-(c+d)),v={rootMargin:-h+"px "+-g+"px "+-L(i.clientHeight-(f+p))+"px "+-L(c)+"px",threshold:I(0,D(1,u))||1},m=!0;function y(t){let n=t[0].intersectionRatio;if(n!==u){if(!m)return a();n?a(!1,n):r=setTimeout(()=>{a(!1,1e-7)},1e3)}1!==n||eY(s,e.getBoundingClientRect())||a(),m=!1}try{n=new IntersectionObserver(y,{...v,root:i.ownerDocument})}catch(e){n=new IntersectionObserver(y,v)}n.observe(e)}(!0),o}(c,r):null,p=-1,h=null;l&&(h=new ResizeObserver(e=>{let[n]=e;n&&n.target===c&&h&&(h.unobserve(t),cancelAnimationFrame(p),p=requestAnimationFrame(()=>{var e;null==(e=h)||e.observe(t)})),r()}),c&&!s&&h.observe(c),h.observe(t));let g=s?e$(e):null;return s&&function t(){let n=e$(e);g&&!eY(g,n)&&r(),g=n,i=requestAnimationFrame(t)}(),r(),()=>{var e;f.forEach(e=>{o&&e.removeEventListener("scroll",r),a&&e.removeEventListener("resize",r)}),null==d||d(),null==(e=h)||e.disconnect(),h=null,s&&cancelAnimationFrame(i)}}(...t,{animationFrame:"always"===k})},elements:{reference:A.anchor},middleware:[((e,t)=>({...function(e){return void 0===e&&(e=0),{name:"offset",options:e,async fn(t){var r,n;let{x:i,y:o,placement:a,middlewareData:l}=t,u=await es(t,e);return a===(null==(r=l.offset)?void 0:r.placement)&&null!=(n=l.arrow)&&n.alignmentOffset?{}:{x:i+u.x,y:o+u.y,data:{...u,placement:a}}}}}(e),options:[e,t]}))({mainAxis:y+B,alignmentAxis:w}),S&&((e,t)=>({...function(e){return void 0===e&&(e={}),{name:"shift",options:e,async fn(t){let{x:r,y:n,placement:i}=t,{mainAxis:o=!0,crossAxis:a=!1,limiter:l={fn:e=>{let{x:t,y:r}=e;return{x:t,y:r}}},...u}=V(e,t),s={x:r,y:n},c=await eo(t,u),f=K(U(i)),d=G(f),p=s[d],h=s[f];if(o){let e="y"===d?"top":"left",t="y"===d?"bottom":"right",r=p+c[e],n=p-c[t];p=I(r,D(p,n))}if(a){let e="y"===f?"top":"left",t="y"===f?"bottom":"right",r=h+c[e],n=h-c[t];h=I(r,D(h,n))}let g=l.fn({...t,[d]:p,[f]:h});return{...g,data:{x:g.x-r,y:g.y-n,enabled:{[d]:o,[f]:a}}}}}}(e),options:[e,t]}))({mainAxis:!0,crossAxis:!1,limiter:"partial"===C?((e,t)=>({...function(e){return void 0===e&&(e={}),{options:e,fn(t){let{x:r,y:n,placement:i,rects:o,middlewareData:a}=t,{offset:l=0,mainAxis:u=!0,crossAxis:s=!0}=V(e,t),c={x:r,y:n},f=K(i),d=G(f),p=c[d],h=c[f],g=V(l,t),v="number"==typeof g?{mainAxis:g,crossAxis:0}:{mainAxis:0,crossAxis:0,...g};if(u){let e="y"===d?"height":"width",t=o.reference[d]-o.floating[e]+v.mainAxis,r=o.reference[d]+o.reference[e]-v.mainAxis;pr&&(p=r)}if(s){var m,y;let e="y"===d?"width":"height",t=eu.has(U(i)),r=o.reference[f]-o.floating[e]+(t&&(null==(m=a.offset)?void 0:m[f])||0)+(t?0:v.crossAxis),n=o.reference[f]+o.reference[e]+(t?0:(null==(y=a.offset)?void 0:y[f])||0)-(t?v.crossAxis:0);hn&&(h=n)}return{[d]:p,[f]:h}}}}(e),options:[e,t]}))():void 0,...en}),S&&((e,t)=>({...function(e){return void 0===e&&(e={}),{name:"flip",options:e,async fn(t){var r,n,i,o,a;let{placement:l,middlewareData:u,rects:s,initialPlacement:c,platform:f,elements:d}=t,{mainAxis:p=!0,crossAxis:h=!0,fallbackPlacements:g,fallbackStrategy:v="bestFit",fallbackAxisSideDirection:m="none",flipAlignment:y=!0,...b}=V(e,t);if(null!=(r=u.arrow)&&r.alignmentOffset)return{};let w=U(l),x=K(c),S=U(c)===c,O=await (null==f.isRTL?void 0:f.isRTL(d.floating)),E=g||(S||!y?[ee(c)]:function(e){let t=ee(e);return[q(e),t,q(t)]}(c)),C="none"!==m;!g&&C&&E.push(...function(e,t,r,n){let i=H(e),o=function(e,t,r){switch(e){case"top":case"bottom":if(r)return t?X:Y;return t?Y:X;case"left":case"right":return t?J:Q;default:return[]}}(U(e),"start"===r,n);return i&&(o=o.map(e=>e+"-"+i),t&&(o=o.concat(o.map(q)))),o}(c,y,m,O));let M=[c,...E],k=await eo(t,b),_=[],P=(null==(n=u.flip)?void 0:n.overflows)||[];if(p&&_.push(k[w]),h){let e=function(e,t,r){void 0===r&&(r=!1);let n=H(e),i=G(K(e)),o=Z(i),a="x"===i?n===(r?"end":"start")?"right":"left":"start"===n?"bottom":"top";return t.reference[o]>t.floating[o]&&(a=ee(a)),[a,ee(a)]}(l,s,O);_.push(k[e[0]],k[e[1]])}if(P=[...P,{placement:l,overflows:_}],!_.every(e=>e<=0)){let e=((null==(i=u.flip)?void 0:i.index)||0)+1,t=M[e];if(t&&("alignment"!==h||x===K(t)||P.every(e=>e.overflows[0]>0&&K(e.placement)===x)))return{data:{index:e,overflows:P},reset:{placement:t}};let r=null==(o=P.filter(e=>e.overflows[0]<=0).sort((e,t)=>e.overflows[1]-t.overflows[1])[0])?void 0:o.placement;if(!r)switch(v){case"bestFit":{let e=null==(a=P.filter(e=>{if(C){let t=K(e.placement);return t===x||"y"===t}return!0}).map(e=>[e.placement,e.overflows.filter(e=>e>0).reduce((e,t)=>e+t,0)]).sort((e,t)=>e[1]-t[1])[0])?void 0:a[0];e&&(r=e);break}case"initialPlacement":r=c}if(l!==r)return{reset:{placement:r}}}return{}}}}(e),options:[e,t]}))({...en}),((e,t)=>({...function(e){return void 0===e&&(e={}),{name:"size",options:e,async fn(t){var r,n;let i,o,{placement:a,rects:l,platform:u,elements:s}=t,{apply:c=()=>{},...f}=V(e,t),d=await eo(t,f),p=U(a),h=H(a),g="y"===K(a),{width:v,height:m}=l.floating;"top"===p||"bottom"===p?(i=p,o=h===(await (null==u.isRTL?void 0:u.isRTL(s.floating))?"start":"end")?"left":"right"):(o=p,i="end"===h?"top":"bottom");let y=m-d.top-d.bottom,b=v-d.left-d.right,w=D(m-d[i],y),x=D(v-d[o],b),S=!t.middlewareData.shift,O=w,E=x;if(null!=(r=t.middlewareData.shift)&&r.enabled.x&&(E=b),null!=(n=t.middlewareData.shift)&&n.enabled.y&&(O=y),S&&!h){let e=I(d.left,0),t=I(d.right,0),r=I(d.top,0),n=I(d.bottom,0);g?E=v-2*(0!==e||0!==t?e+t:I(d.left,d.right)):O=m-2*(0!==r||0!==n?r+n:I(d.top,d.bottom))}await c({...t,availableWidth:E,availableHeight:O});let C=await u.getDimensions(s.floating);return v!==C.width||m!==C.height?{reset:{rects:!0}}:{}}}}(e),options:[e,t]}))({...en,apply:e=>{let{elements:t,rects:r,availableWidth:n,availableHeight:i}=e,{width:o,height:a}=r.reference,l=t.floating.style;l.setProperty("--radix-popper-available-width","".concat(n,"px")),l.setProperty("--radix-popper-available-height","".concat(i,"px")),l.setProperty("--radix-popper-anchor-width","".concat(o,"px")),l.setProperty("--radix-popper-anchor-height","".concat(a,"px"))}}),z&&((e,t)=>({...(e=>({name:"arrow",options:e,fn(t){let{element:r,padding:n}="function"==typeof e?e(t):e;return r&&({}).hasOwnProperty.call(r,"current")?null!=r.current?eX({element:r.current,padding:n}).fn(t):{}:r?eX({element:r,padding:n}).fn(t):{}}}))(e),options:[e,t]}))({element:z,padding:x}),tf({arrowWidth:$,arrowHeight:B}),M&&((e,t)=>({...function(e){return void 0===e&&(e={}),{name:"hide",options:e,async fn(t){let{rects:r}=t,{strategy:n="referenceHidden",...i}=V(e,t);switch(n){case"referenceHidden":{let e=ea(await eo(t,{...i,elementContext:"reference"}),r.reference);return{data:{referenceHiddenOffsets:e,referenceHidden:el(e)}}}case"escaped":{let e=ea(await eo(t,{...i,altBoundary:!0}),r.floating);return{data:{escapedOffsets:e,escaped:el(e)}}}default:return{}}}}}(e),options:[e,t]}))({strategy:"referenceHidden",...en})]}),[ev,em]=td(ed),ey=(0,g.c)(_);(0,e6.N)(()=>{eh&&(null==ey||ey())},[eh,ey]);let eb=null==(r=eg.arrow)?void 0:r.x,ew=null==(n=eg.arrow)?void 0:n.y,ex=(null==(i=eg.arrow)?void 0:i.centerOffset)!==0,[eS,eO]=a.useState();return(0,e6.N)(()=>{j&&eO(window.getComputedStyle(j).zIndex)},[j]),(0,v.jsx)("div",{ref:ec.setFloating,"data-radix-popper-content-wrapper":"",style:{...ef,transform:eh?ef.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:eS,"--radix-popper-transform-origin":[null==(o=eg.transformOrigin)?void 0:o.x,null==(u=eg.transformOrigin)?void 0:u.y].join(" "),...(null==(s=eg.hide)?void 0:s.referenceHidden)&&{visibility:"hidden",pointerEvents:"none"}},dir:e.dir,children:(0,v.jsx)(ti,{scope:p,placedSide:ev,onArrowChange:N,arrowX:eb,arrowY:ew,shouldHideArrow:ex,children:(0,v.jsx)(h.sG.div,{"data-side":ev,"data-align":em,...P,ref:R,style:{...P.style,animation:eh?void 0:"none"}})})})});ta.displayName=tn;var tl="PopperArrow",tu={top:"bottom",right:"left",bottom:"top",left:"right"},ts=a.forwardRef(function(e,t){let{__scopePopper:r,...n}=e,i=to(tl,r),o=tu[i.placedSide];return(0,v.jsx)("span",{ref:i.onArrowChange,style:{position:"absolute",left:i.arrowX,top:i.arrowY,[o]:0,transformOrigin:{top:"",right:"0 0",bottom:"center 0",left:"100% 0"}[i.placedSide],transform:{top:"translateY(100%)",right:"translateY(50%) rotate(90deg) translateX(-50%)",bottom:"rotate(180deg)",left:"translateY(50%) rotate(-90deg) translateX(50%)"}[i.placedSide],visibility:i.shouldHideArrow?"hidden":void 0},children:(0,v.jsx)(e5,{...n,ref:t,style:{...n.style,display:"block"}})})});function tc(e){return null!==e}ts.displayName=tl;var tf=e=>({name:"transformOrigin",options:e,fn(t){var r,n,i,o,a;let{placement:l,rects:u,middlewareData:s}=t,c=(null==(r=s.arrow)?void 0:r.centerOffset)!==0,f=c?0:e.arrowWidth,d=c?0:e.arrowHeight,[p,h]=td(l),g={start:"0%",center:"50%",end:"100%"}[h],v=(null!=(o=null==(n=s.arrow)?void 0:n.x)?o:0)+f/2,m=(null!=(a=null==(i=s.arrow)?void 0:i.y)?a:0)+d/2,y="",b="";return"bottom"===p?(y=c?g:"".concat(v,"px"),b="".concat(-d,"px")):"top"===p?(y=c?g:"".concat(v,"px"),b="".concat(u.floating.height+d,"px")):"right"===p?(y="".concat(-d,"px"),b=c?g:"".concat(m,"px")):"left"===p&&(y="".concat(u.floating.width+d,"px"),b=c?g:"".concat(m,"px")),{data:{x:y,y:b}}}});function td(e){let[t,r="center"]=e.split("-");return[t,r]}var tp=a.forwardRef((e,t)=>{var r,n;let{container:i,...o}=e,[u,s]=a.useState(!1);(0,e6.N)(()=>s(!0),[]);let c=i||u&&(null==(n=globalThis)||null==(r=n.document)?void 0:r.body);return c?l.createPortal((0,v.jsx)(h.sG.div,{...o,ref:t}),c):null});tp.displayName="Portal";var th=r(9708),tg=r(5845),tv=Object.freeze({position:"absolute",border:0,width:1,height:1,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",wordWrap:"normal"});a.forwardRef((e,t)=>(0,v.jsx)(h.sG.span,{...e,ref:t,style:{...tv,...e.style}})).displayName="VisuallyHidden";var tm=new WeakMap,ty=new WeakMap,tb={},tw=0,tx=function(e){return e&&(e.host||tx(e.parentNode))},tS=function(e,t,r,n){var i=(Array.isArray(e)?e:[e]).map(function(e){if(t.contains(e))return e;var r=tx(e);return r&&t.contains(r)?r:(console.error("aria-hidden",e,"in not contained inside",t,". Doing nothing"),null)}).filter(function(e){return!!e});tb[r]||(tb[r]=new WeakMap);var o=tb[r],a=[],l=new Set,u=new Set(i),s=function(e){!e||l.has(e)||(l.add(e),s(e.parentNode))};i.forEach(s);var c=function(e){!e||u.has(e)||Array.prototype.forEach.call(e.children,function(e){if(l.has(e))c(e);else try{var t=e.getAttribute(n),i=null!==t&&"false"!==t,u=(tm.get(e)||0)+1,s=(o.get(e)||0)+1;tm.set(e,u),o.set(e,s),a.push(e),1===u&&i&&ty.set(e,!0),1===s&&e.setAttribute(r,"true"),i||e.setAttribute(n,"true")}catch(t){console.error("aria-hidden: cannot operate on ",e,t)}})};return c(t),l.clear(),tw++,function(){a.forEach(function(e){var t=tm.get(e)-1,i=o.get(e)-1;tm.set(e,t),o.set(e,i),t||(ty.has(e)||e.removeAttribute(n),ty.delete(e)),i||e.removeAttribute(r)}),--tw||(tm=new WeakMap,tm=new WeakMap,ty=new WeakMap,tb={})}},tO=function(e,t,r){void 0===r&&(r="data-aria-hidden");var n=Array.from(Array.isArray(e)?e:[e]),i=t||("undefined"==typeof document?null:(Array.isArray(e)?e[0]:e).ownerDocument.body);return i?(n.push.apply(n,Array.from(i.querySelectorAll("[aria-live], script"))),tS(n,i,r,"aria-hidden")):function(){return null}},tE=function(){return(tE=Object.assign||function(e){for(var t,r=1,n=arguments.length;rt.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,n=Object.getOwnPropertySymbols(e);it.indexOf(n[i])&&Object.prototype.propertyIsEnumerable.call(e,n[i])&&(r[n[i]]=e[n[i]]);return r}Object.create;Object.create;var tM=("function"==typeof SuppressedError&&SuppressedError,"right-scroll-bar-position"),tk="width-before-scroll-bar";function t_(e,t){return"function"==typeof e?e(t):e&&(e.current=t),e}var tP="undefined"!=typeof window?a.useLayoutEffect:a.useEffect,tA=new WeakMap;function tj(e){return e}var tT=function(e){void 0===e&&(e={});var t,r,n,i=(void 0===t&&(t=tj),r=[],n=!1,{read:function(){if(n)throw Error("Sidecar: could not `read` from an `assigned` medium. `read` could be used only with `useMedium`.");return r.length?r[r.length-1]:null},useMedium:function(e){var i=t(e,n);return r.push(i),function(){r=r.filter(function(e){return e!==i})}},assignSyncMedium:function(e){for(n=!0;r.length;){var t=r;r=[],t.forEach(e)}r={push:function(t){return e(t)},filter:function(){return r}}},assignMedium:function(e){n=!0;var t=[];if(r.length){var i=r;r=[],i.forEach(e),t=r}var o=function(){var r=t;t=[],r.forEach(e)},a=function(){return Promise.resolve().then(o)};a(),r={push:function(e){t.push(e),a()},filter:function(e){return t=t.filter(e),r}}}});return i.options=tE({async:!0,ssr:!1},e),i}(),tR=function(){},tz=a.forwardRef(function(e,t){var r,n,i,o,l=a.useRef(null),u=a.useState({onScrollCapture:tR,onWheelCapture:tR,onTouchMoveCapture:tR}),s=u[0],c=u[1],f=e.forwardProps,d=e.children,p=e.className,h=e.removeScrollBar,g=e.enabled,v=e.shards,m=e.sideCar,y=e.noRelative,b=e.noIsolation,w=e.inert,x=e.allowPinchZoom,S=e.as,O=e.gapMode,E=tC(e,["forwardProps","children","className","removeScrollBar","enabled","shards","sideCar","noRelative","noIsolation","inert","allowPinchZoom","as","gapMode"]),C=(r=[l,t],n=function(e){return r.forEach(function(t){return t_(t,e)})},(i=(0,a.useState)(function(){return{value:null,callback:n,facade:{get current(){return i.value},set current(value){var e=i.value;e!==value&&(i.value=value,i.callback(value,e))}}}})[0]).callback=n,o=i.facade,tP(function(){var e=tA.get(o);if(e){var t=new Set(e),n=new Set(r),i=o.current;t.forEach(function(e){n.has(e)||t_(e,null)}),n.forEach(function(e){t.has(e)||t_(e,i)})}tA.set(o,r)},[r]),o),M=tE(tE({},E),s);return a.createElement(a.Fragment,null,g&&a.createElement(m,{sideCar:tT,removeScrollBar:h,shards:v,noRelative:y,noIsolation:b,inert:w,setCallbacks:c,allowPinchZoom:!!x,lockRef:l,gapMode:O}),f?a.cloneElement(a.Children.only(d),tE(tE({},M),{ref:C})):a.createElement(void 0===S?"div":S,tE({},M,{className:p,ref:C}),d))});tz.defaultProps={enabled:!0,removeScrollBar:!0,inert:!1},tz.classNames={fullWidth:tk,zeroRight:tM};var tD=function(e){var t=e.sideCar,r=tC(e,["sideCar"]);if(!t)throw Error("Sidecar: please provide `sideCar` property to import the right car");var n=t.read();if(!n)throw Error("Sidecar medium not found");return a.createElement(n,tE({},r))};tD.isSideCarExport=!0;var tI=function(){var e=0,t=null;return{add:function(n){if(0==e&&(t=function(){if(!document)return null;var e=document.createElement("style");e.type="text/css";var t=o||r.nc;return t&&e.setAttribute("nonce",t),e}())){var i,a;(i=t).styleSheet?i.styleSheet.cssText=n:i.appendChild(document.createTextNode(n)),a=t,(document.head||document.getElementsByTagName("head")[0]).appendChild(a)}e++},remove:function(){--e||!t||(t.parentNode&&t.parentNode.removeChild(t),t=null)}}},tN=function(){var e=tI();return function(t,r){a.useEffect(function(){return e.add(t),function(){e.remove()}},[t&&r])}},tL=function(){var e=tN();return function(t){return e(t.styles,t.dynamic),null}},tF={left:0,top:0,right:0,gap:0},t$=function(e){return parseInt(e||"",10)||0},tB=function(e){var t=window.getComputedStyle(document.body),r=t["padding"===e?"paddingLeft":"marginLeft"],n=t["padding"===e?"paddingTop":"marginTop"],i=t["padding"===e?"paddingRight":"marginRight"];return[t$(r),t$(n),t$(i)]},tV=function(e){if(void 0===e&&(e="margin"),"undefined"==typeof window)return tF;var t=tB(e),r=document.documentElement.clientWidth,n=window.innerWidth;return{left:t[0],top:t[1],right:t[2],gap:Math.max(0,n-r+t[2]-t[0])}},tU=tL(),tH="data-scroll-locked",tG=function(e,t,r,n){var i=e.left,o=e.top,a=e.right,l=e.gap;return void 0===r&&(r="margin"),"\n .".concat("with-scroll-bars-hidden"," {\n overflow: hidden ").concat(n,";\n padding-right: ").concat(l,"px ").concat(n,";\n }\n body[").concat(tH,"] {\n overflow: hidden ").concat(n,";\n overscroll-behavior: contain;\n ").concat([t&&"position: relative ".concat(n,";"),"margin"===r&&"\n padding-left: ".concat(i,"px;\n padding-top: ").concat(o,"px;\n padding-right: ").concat(a,"px;\n margin-left:0;\n margin-top:0;\n margin-right: ").concat(l,"px ").concat(n,";\n "),"padding"===r&&"padding-right: ".concat(l,"px ").concat(n,";")].filter(Boolean).join(""),"\n }\n \n .").concat(tM," {\n right: ").concat(l,"px ").concat(n,";\n }\n \n .").concat(tk," {\n margin-right: ").concat(l,"px ").concat(n,";\n }\n \n .").concat(tM," .").concat(tM," {\n right: 0 ").concat(n,";\n }\n \n .").concat(tk," .").concat(tk," {\n margin-right: 0 ").concat(n,";\n }\n \n body[").concat(tH,"] {\n ").concat("--removed-body-scroll-bar-size",": ").concat(l,"px;\n }\n")},tZ=function(){var e=parseInt(document.body.getAttribute(tH)||"0",10);return isFinite(e)?e:0},tW=function(){a.useEffect(function(){return document.body.setAttribute(tH,(tZ()+1).toString()),function(){var e=tZ()-1;e<=0?document.body.removeAttribute(tH):document.body.setAttribute(tH,e.toString())}},[])},tK=function(e){var t=e.noRelative,r=e.noImportant,n=e.gapMode,i=void 0===n?"margin":n;tW();var o=a.useMemo(function(){return tV(i)},[i]);return a.createElement(tU,{styles:tG(o,!t,i,r?"":"!important")})},tq=!1;if("undefined"!=typeof window)try{var tY=Object.defineProperty({},"passive",{get:function(){return tq=!0,!0}});window.addEventListener("test",tY,tY),window.removeEventListener("test",tY,tY)}catch(e){tq=!1}var tX=!!tq&&{passive:!1},tJ=function(e,t){if(!(e instanceof Element))return!1;var r=window.getComputedStyle(e);return"hidden"!==r[t]&&(r.overflowY!==r.overflowX||"TEXTAREA"===e.tagName||"visible"!==r[t])},tQ=function(e,t){var r=t.ownerDocument,n=t;do{if("undefined"!=typeof ShadowRoot&&n instanceof ShadowRoot&&(n=n.host),t0(e,n)){var i=t1(e,n);if(i[1]>i[2])return!0}n=n.parentNode}while(n&&n!==r.body);return!1},t0=function(e,t){return"v"===e?tJ(t,"overflowY"):tJ(t,"overflowX")},t1=function(e,t){return"v"===e?[t.scrollTop,t.scrollHeight,t.clientHeight]:[t.scrollLeft,t.scrollWidth,t.clientWidth]},t2=function(e,t,r,n,i){var o,a=(o=window.getComputedStyle(t).direction,"h"===e&&"rtl"===o?-1:1),l=a*n,u=r.target,s=t.contains(u),c=!1,f=l>0,d=0,p=0;do{if(!u)break;var h=t1(e,u),g=h[0],v=h[1]-h[2]-a*g;(g||v)&&t0(e,u)&&(d+=v,p+=g);var m=u.parentNode;u=m&&m.nodeType===Node.DOCUMENT_FRAGMENT_NODE?m.host:m}while(!s&&u!==document.body||s&&(t.contains(u)||t===u));return f&&(i&&1>Math.abs(d)||!i&&l>d)?c=!0:!f&&(i&&1>Math.abs(p)||!i&&-l>p)&&(c=!0),c},t5=function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},t6=function(e){return[e.deltaX,e.deltaY]},t4=function(e){return e&&"current"in e?e.current:e},t3=0,t8=[];let t9=(n=function(e){var t=a.useRef([]),r=a.useRef([0,0]),n=a.useRef(),i=a.useState(t3++)[0],o=a.useState(tL)[0],l=a.useRef(e);a.useEffect(function(){l.current=e},[e]),a.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(i));var t=(function(e,t,r){if(r||2==arguments.length)for(var n,i=0,o=t.length;iMath.abs(s)?"h":"v";if("touches"in e&&"h"===f&&"range"===c.type)return!1;var d=tQ(f,c);if(!d)return!0;if(d?i=f:(i="v"===f?"h":"v",d=tQ(f,c)),!d)return!1;if(!n.current&&"changedTouches"in e&&(u||s)&&(n.current=i),!i)return!0;var p=n.current||i;return t2(p,t,e,"h"===p?u:s,!0)},[]),s=a.useCallback(function(e){if(t8.length&&t8[t8.length-1]===o){var r="deltaY"in e?t6(e):t5(e),n=t.current.filter(function(t){var n;return t.name===e.type&&(t.target===e.target||e.target===t.shadowParent)&&(n=t.delta,n[0]===r[0]&&n[1]===r[1])})[0];if(n&&n.should){e.cancelable&&e.preventDefault();return}if(!n){var i=(l.current.shards||[]).map(t4).filter(Boolean).filter(function(t){return t.contains(e.target)});(i.length>0?u(e,i[0]):!l.current.noIsolation)&&e.cancelable&&e.preventDefault()}}},[]),c=a.useCallback(function(e,r,n,i){var o={name:e,delta:r,target:n,should:i,shadowParent:function(e){for(var t=null;null!==e;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}(n)};t.current.push(o),setTimeout(function(){t.current=t.current.filter(function(e){return e!==o})},1)},[]),f=a.useCallback(function(e){r.current=t5(e),n.current=void 0},[]),d=a.useCallback(function(t){c(t.type,t6(t),t.target,u(t,e.lockRef.current))},[]),p=a.useCallback(function(t){c(t.type,t5(t),t.target,u(t,e.lockRef.current))},[]);a.useEffect(function(){return t8.push(o),e.setCallbacks({onScrollCapture:d,onWheelCapture:d,onTouchMoveCapture:p}),document.addEventListener("wheel",s,tX),document.addEventListener("touchmove",s,tX),document.addEventListener("touchstart",f,tX),function(){t8=t8.filter(function(e){return e!==o}),document.removeEventListener("wheel",s,tX),document.removeEventListener("touchmove",s,tX),document.removeEventListener("touchstart",f,tX)}},[]);var h=e.removeScrollBar,g=e.inert;return a.createElement(a.Fragment,null,g?a.createElement(o,{styles:"\n .block-interactivity-".concat(i," {pointer-events: none;}\n .allow-interactivity-").concat(i," {pointer-events: all;}\n")}):null,h?a.createElement(tK,{noRelative:e.noRelative,gapMode:e.gapMode}):null)},tT.useMedium(n),tD);var t7=a.forwardRef(function(e,t){return a.createElement(tz,tE({},e,{ref:t,sideCar:t9}))});t7.classNames=tz.classNames;var re=[" ","Enter","ArrowUp","ArrowDown"],rt=[" ","Enter"],rr="Select",[rn,ri,ro]=(0,c.N)(rr),[ra,rl]=(0,d.A)(rr,[ro,e8]),ru=e8(),[rs,rc]=ra(rr),[rf,rd]=ra(rr),rp=e=>{let{__scopeSelect:t,children:r,open:n,defaultOpen:i,onOpenChange:o,value:l,defaultValue:u,onValueChange:s,dir:c,name:f,autoComplete:d,disabled:h,required:g,form:m}=e,y=ru(t),[b,w]=a.useState(null),[x,S]=a.useState(null),[O,E]=a.useState(!1),C=(0,p.jH)(c),[M,k]=(0,tg.i)({prop:n,defaultProp:null!=i&&i,onChange:o,caller:rr}),[_,P]=(0,tg.i)({prop:l,defaultProp:u,onChange:s,caller:rr}),A=a.useRef(null),j=!b||m||!!b.closest("form"),[T,z]=a.useState(new Set),D=Array.from(T).map(e=>e.props.value).join(";");return(0,v.jsx)(te,{...y,children:(0,v.jsxs)(rs,{required:g,scope:t,trigger:b,onTriggerChange:w,valueNode:x,onValueNodeChange:S,valueNodeHasChildren:O,onValueNodeHasChildrenChange:E,contentId:(0,R.B)(),value:_,onValueChange:P,open:M,onOpenChange:k,dir:C,triggerPointerDownPosRef:A,disabled:h,children:[(0,v.jsx)(rn.Provider,{scope:t,children:(0,v.jsx)(rf,{scope:e.__scopeSelect,onNativeOptionAdd:a.useCallback(e=>{z(t=>new Set(t).add(e))},[]),onNativeOptionRemove:a.useCallback(e=>{z(t=>{let r=new Set(t);return r.delete(e),r})},[]),children:r})}),j?(0,v.jsxs)(rY,{"aria-hidden":!0,required:g,tabIndex:-1,name:f,autoComplete:d,value:_,onChange:e=>P(e.target.value),disabled:h,form:m,children:[void 0===_?(0,v.jsx)("option",{value:""}):null,Array.from(T)]},D):null]})})};rp.displayName=rr;var rh="SelectTrigger",rg=a.forwardRef((e,t)=>{let{__scopeSelect:r,disabled:n=!1,...i}=e,o=ru(r),l=rc(rh,r),u=l.disabled||n,c=(0,f.s)(t,l.onTriggerChange),d=ri(r),p=a.useRef("touch"),[g,m,y]=rJ(e=>{let t=d().filter(e=>!e.disabled),r=t.find(e=>e.value===l.value),n=rQ(t,e,r);void 0!==n&&l.onValueChange(n.value)}),b=e=>{u||(l.onOpenChange(!0),y()),e&&(l.triggerPointerDownPosRef.current={x:Math.round(e.pageX),y:Math.round(e.pageY)})};return(0,v.jsx)(tr,{asChild:!0,...o,children:(0,v.jsx)(h.sG.button,{type:"button",role:"combobox","aria-controls":l.contentId,"aria-expanded":l.open,"aria-required":l.required,"aria-autocomplete":"none",dir:l.dir,"data-state":l.open?"open":"closed",disabled:u,"data-disabled":u?"":void 0,"data-placeholder":rX(l.value)?"":void 0,...i,ref:c,onClick:(0,s.m)(i.onClick,e=>{e.currentTarget.focus(),"mouse"!==p.current&&b(e)}),onPointerDown:(0,s.m)(i.onPointerDown,e=>{p.current=e.pointerType;let t=e.target;t.hasPointerCapture(e.pointerId)&&t.releasePointerCapture(e.pointerId),0===e.button&&!1===e.ctrlKey&&"mouse"===e.pointerType&&(b(e),e.preventDefault())}),onKeyDown:(0,s.m)(i.onKeyDown,e=>{let t=""!==g.current;e.ctrlKey||e.altKey||e.metaKey||1!==e.key.length||m(e.key),(!t||" "!==e.key)&&re.includes(e.key)&&(b(),e.preventDefault())})})})});rg.displayName=rh;var rv="SelectValue",rm=a.forwardRef((e,t)=>{let{__scopeSelect:r,className:n,style:i,children:o,placeholder:a="",...l}=e,u=rc(rv,r),{onValueNodeHasChildrenChange:s}=u,c=void 0!==o,d=(0,f.s)(t,u.onValueNodeChange);return(0,e6.N)(()=>{s(c)},[s,c]),(0,v.jsx)(h.sG.span,{...l,ref:d,style:{pointerEvents:"none"},children:rX(u.value)?(0,v.jsx)(v.Fragment,{children:a}):o})});rm.displayName=rv;var ry=a.forwardRef((e,t)=>{let{__scopeSelect:r,children:n,...i}=e;return(0,v.jsx)(h.sG.span,{"aria-hidden":!0,...i,ref:t,children:n||"▼"})});ry.displayName="SelectIcon";var rb=e=>(0,v.jsx)(tp,{asChild:!0,...e});rb.displayName="SelectPortal";var rw="SelectContent",rx=a.forwardRef((e,t)=>{let r=rc(rw,e.__scopeSelect),[n,i]=a.useState();return((0,e6.N)(()=>{i(new DocumentFragment)},[]),r.open)?(0,v.jsx)(rC,{...e,ref:t}):n?l.createPortal((0,v.jsx)(rS,{scope:e.__scopeSelect,children:(0,v.jsx)(rn.Slot,{scope:e.__scopeSelect,children:(0,v.jsx)("div",{children:e.children})})}),n):null});rx.displayName=rw;var[rS,rO]=ra(rw),rE=(0,th.TL)("SelectContent.RemoveScroll"),rC=a.forwardRef((e,t)=>{let{__scopeSelect:r,position:n="item-aligned",onCloseAutoFocus:i,onEscapeKeyDown:o,onPointerDownOutside:l,side:u,sideOffset:c,align:d,alignOffset:p,arrowPadding:h,collisionBoundary:g,collisionPadding:m,sticky:y,hideWhenDetached:w,avoidCollisions:x,...E}=e,C=rc(rw,r),[M,_]=a.useState(null),[P,A]=a.useState(null),j=(0,f.s)(t,e=>_(e)),[T,R]=a.useState(null),[z,D]=a.useState(null),I=ri(r),[N,L]=a.useState(!1),F=a.useRef(!1);a.useEffect(()=>{if(M)return tO(M)},[M]),a.useEffect(()=>{var e,t;let r=document.querySelectorAll("[data-radix-focus-guard]");return document.body.insertAdjacentElement("afterbegin",null!=(e=r[0])?e:O()),document.body.insertAdjacentElement("beforeend",null!=(t=r[1])?t:O()),S++,()=>{1===S&&document.querySelectorAll("[data-radix-focus-guard]").forEach(e=>e.remove()),S--}},[]);let $=a.useCallback(e=>{let[t,...r]=I().map(e=>e.ref.current),[n]=r.slice(-1),i=document.activeElement;for(let r of e)if(r===i||(null==r||r.scrollIntoView({block:"nearest"}),r===t&&P&&(P.scrollTop=0),r===n&&P&&(P.scrollTop=P.scrollHeight),null==r||r.focus(),document.activeElement!==i))return},[I,P]),B=a.useCallback(()=>$([T,M]),[$,T,M]);a.useEffect(()=>{N&&B()},[N,B]);let{onOpenChange:V,triggerPointerDownPosRef:U}=C;a.useEffect(()=>{if(M){let e={x:0,y:0},t=t=>{var r,n,i,o;e={x:Math.abs(Math.round(t.pageX)-(null!=(i=null==(r=U.current)?void 0:r.x)?i:0)),y:Math.abs(Math.round(t.pageY)-(null!=(o=null==(n=U.current)?void 0:n.y)?o:0))}},r=r=>{e.x<=10&&e.y<=10?r.preventDefault():M.contains(r.target)||V(!1),document.removeEventListener("pointermove",t),U.current=null};return null!==U.current&&(document.addEventListener("pointermove",t),document.addEventListener("pointerup",r,{capture:!0,once:!0})),()=>{document.removeEventListener("pointermove",t),document.removeEventListener("pointerup",r,{capture:!0})}}},[M,V,U]),a.useEffect(()=>{let e=()=>V(!1);return window.addEventListener("blur",e),window.addEventListener("resize",e),()=>{window.removeEventListener("blur",e),window.removeEventListener("resize",e)}},[V]);let[H,G]=rJ(e=>{let t=I().filter(e=>!e.disabled),r=t.find(e=>e.ref.current===document.activeElement),n=rQ(t,e,r);n&&setTimeout(()=>n.ref.current.focus())}),Z=a.useCallback((e,t,r)=>{let n=!F.current&&!r;(void 0!==C.value&&C.value===t||n)&&(R(e),n&&(F.current=!0))},[C.value]),W=a.useCallback(()=>null==M?void 0:M.focus(),[M]),K=a.useCallback((e,t,r)=>{let n=!F.current&&!r;(void 0!==C.value&&C.value===t||n)&&D(e)},[C.value]),q="popper"===n?rk:rM,Y=q===rk?{side:u,sideOffset:c,align:d,alignOffset:p,arrowPadding:h,collisionBoundary:g,collisionPadding:m,sticky:y,hideWhenDetached:w,avoidCollisions:x}:{};return(0,v.jsx)(rS,{scope:r,content:M,viewport:P,onViewportChange:A,itemRefCallback:Z,selectedItem:T,onItemLeave:W,itemTextRefCallback:K,focusSelectedItem:B,selectedItemText:z,position:n,isPositioned:N,searchRef:H,children:(0,v.jsx)(t7,{as:rE,allowPinchZoom:!0,children:(0,v.jsx)(k,{asChild:!0,trapped:C.open,onMountAutoFocus:e=>{e.preventDefault()},onUnmountAutoFocus:(0,s.m)(i,e=>{var t;null==(t=C.trigger)||t.focus({preventScroll:!0}),e.preventDefault()}),children:(0,v.jsx)(b,{asChild:!0,disableOutsidePointerEvents:!0,onEscapeKeyDown:o,onPointerDownOutside:l,onFocusOutside:e=>e.preventDefault(),onDismiss:()=>C.onOpenChange(!1),children:(0,v.jsx)(q,{role:"listbox",id:C.contentId,"data-state":C.open?"open":"closed",dir:C.dir,onContextMenu:e=>e.preventDefault(),...E,...Y,onPlaced:()=>L(!0),ref:j,style:{display:"flex",flexDirection:"column",outline:"none",...E.style},onKeyDown:(0,s.m)(E.onKeyDown,e=>{let t=e.ctrlKey||e.altKey||e.metaKey;if("Tab"===e.key&&e.preventDefault(),t||1!==e.key.length||G(e.key),["ArrowUp","ArrowDown","Home","End"].includes(e.key)){let t=I().filter(e=>!e.disabled).map(e=>e.ref.current);if(["ArrowUp","End"].includes(e.key)&&(t=t.slice().reverse()),["ArrowUp","ArrowDown"].includes(e.key)){let r=e.target,n=t.indexOf(r);t=t.slice(n+1)}setTimeout(()=>$(t)),e.preventDefault()}})})})})})})});rC.displayName="SelectContentImpl";var rM=a.forwardRef((e,t)=>{let{__scopeSelect:r,onPlaced:n,...i}=e,o=rc(rw,r),l=rO(rw,r),[s,c]=a.useState(null),[d,p]=a.useState(null),g=(0,f.s)(t,e=>p(e)),m=ri(r),y=a.useRef(!1),b=a.useRef(!0),{viewport:w,selectedItem:x,selectedItemText:S,focusSelectedItem:O}=l,E=a.useCallback(()=>{if(o.trigger&&o.valueNode&&s&&d&&w&&x&&S){let e=o.trigger.getBoundingClientRect(),t=d.getBoundingClientRect(),r=o.valueNode.getBoundingClientRect(),i=S.getBoundingClientRect();if("rtl"!==o.dir){let n=i.left-t.left,o=r.left-n,a=e.left-o,l=e.width+a,c=Math.max(l,t.width),f=u(o,[10,Math.max(10,window.innerWidth-10-c)]);s.style.minWidth=l+"px",s.style.left=f+"px"}else{let n=t.right-i.right,o=window.innerWidth-r.right-n,a=window.innerWidth-e.right-o,l=e.width+a,c=Math.max(l,t.width),f=u(o,[10,Math.max(10,window.innerWidth-10-c)]);s.style.minWidth=l+"px",s.style.right=f+"px"}let a=m(),l=window.innerHeight-20,c=w.scrollHeight,f=window.getComputedStyle(d),p=parseInt(f.borderTopWidth,10),h=parseInt(f.paddingTop,10),g=parseInt(f.borderBottomWidth,10),v=p+h+c+parseInt(f.paddingBottom,10)+g,b=Math.min(5*x.offsetHeight,v),O=window.getComputedStyle(w),E=parseInt(O.paddingTop,10),C=parseInt(O.paddingBottom,10),M=e.top+e.height/2-10,k=x.offsetHeight/2,_=p+h+(x.offsetTop+k);if(_<=M){let e=a.length>0&&x===a[a.length-1].ref.current;s.style.bottom="0px";let t=Math.max(l-M,k+(e?C:0)+(d.clientHeight-w.offsetTop-w.offsetHeight)+g);s.style.height=_+t+"px"}else{let e=a.length>0&&x===a[0].ref.current;s.style.top="0px";let t=Math.max(M,p+w.offsetTop+(e?E:0)+k);s.style.height=t+(v-_)+"px",w.scrollTop=_-M+w.offsetTop}s.style.margin="".concat(10,"px 0"),s.style.minHeight=b+"px",s.style.maxHeight=l+"px",null==n||n(),requestAnimationFrame(()=>y.current=!0)}},[m,o.trigger,o.valueNode,s,d,w,x,S,o.dir,n]);(0,e6.N)(()=>E(),[E]);let[C,M]=a.useState();(0,e6.N)(()=>{d&&M(window.getComputedStyle(d).zIndex)},[d]);let k=a.useCallback(e=>{e&&!0===b.current&&(E(),null==O||O(),b.current=!1)},[E,O]);return(0,v.jsx)(r_,{scope:r,contentWrapper:s,shouldExpandOnScrollRef:y,onScrollButtonChange:k,children:(0,v.jsx)("div",{ref:c,style:{display:"flex",flexDirection:"column",position:"fixed",zIndex:C},children:(0,v.jsx)(h.sG.div,{...i,ref:g,style:{boxSizing:"border-box",maxHeight:"100%",...i.style}})})})});rM.displayName="SelectItemAlignedPosition";var rk=a.forwardRef((e,t)=>{let{__scopeSelect:r,align:n="start",collisionPadding:i=10,...o}=e,a=ru(r);return(0,v.jsx)(ta,{...a,...o,ref:t,align:n,collisionPadding:i,style:{boxSizing:"border-box",...o.style,"--radix-select-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-select-content-available-width":"var(--radix-popper-available-width)","--radix-select-content-available-height":"var(--radix-popper-available-height)","--radix-select-trigger-width":"var(--radix-popper-anchor-width)","--radix-select-trigger-height":"var(--radix-popper-anchor-height)"}})});rk.displayName="SelectPopperPosition";var[r_,rP]=ra(rw,{}),rA="SelectViewport",rj=a.forwardRef((e,t)=>{let{__scopeSelect:r,nonce:n,...i}=e,o=rO(rA,r),l=rP(rA,r),u=(0,f.s)(t,o.onViewportChange),c=a.useRef(0);return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)("style",{dangerouslySetInnerHTML:{__html:"[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}"},nonce:n}),(0,v.jsx)(rn.Slot,{scope:r,children:(0,v.jsx)(h.sG.div,{"data-radix-select-viewport":"",role:"presentation",...i,ref:u,style:{position:"relative",flex:1,overflow:"hidden auto",...i.style},onScroll:(0,s.m)(i.onScroll,e=>{let t=e.currentTarget,{contentWrapper:r,shouldExpandOnScrollRef:n}=l;if((null==n?void 0:n.current)&&r){let e=Math.abs(c.current-t.scrollTop);if(e>0){let n=window.innerHeight-20,i=Math.max(parseFloat(r.style.minHeight),parseFloat(r.style.height));if(i0?l:0,r.style.justifyContent="flex-end")}}}c.current=t.scrollTop})})})]})});rj.displayName=rA;var rT="SelectGroup",[rR,rz]=ra(rT);a.forwardRef((e,t)=>{let{__scopeSelect:r,...n}=e,i=(0,R.B)();return(0,v.jsx)(rR,{scope:r,id:i,children:(0,v.jsx)(h.sG.div,{role:"group","aria-labelledby":i,...n,ref:t})})}).displayName=rT;var rD="SelectLabel";a.forwardRef((e,t)=>{let{__scopeSelect:r,...n}=e,i=rz(rD,r);return(0,v.jsx)(h.sG.div,{id:i.id,...n,ref:t})}).displayName=rD;var rI="SelectItem",[rN,rL]=ra(rI),rF=a.forwardRef((e,t)=>{let{__scopeSelect:r,value:n,disabled:i=!1,textValue:o,...l}=e,u=rc(rI,r),c=rO(rI,r),d=u.value===n,[p,g]=a.useState(null!=o?o:""),[m,y]=a.useState(!1),b=(0,f.s)(t,e=>{var t;return null==(t=c.itemRefCallback)?void 0:t.call(c,e,n,i)}),w=(0,R.B)(),x=a.useRef("touch"),S=()=>{i||(u.onValueChange(n),u.onOpenChange(!1))};if(""===n)throw Error("A must have a value prop that is not an empty string. This is because the Select value can be set to an empty string to clear the selection and show the placeholder.");return(0,v.jsx)(rN,{scope:r,value:n,disabled:i,textId:w,isSelected:d,onItemTextChange:a.useCallback(e=>{g(t=>{var r;return t||(null!=(r=null==e?void 0:e.textContent)?r:"").trim()})},[]),children:(0,v.jsx)(rn.ItemSlot,{scope:r,value:n,disabled:i,textValue:p,children:(0,v.jsx)(h.sG.div,{role:"option","aria-labelledby":w,"data-highlighted":m?"":void 0,"aria-selected":d&&m,"data-state":d?"checked":"unchecked","aria-disabled":i||void 0,"data-disabled":i?"":void 0,tabIndex:i?void 0:-1,...l,ref:b,onFocus:(0,s.m)(l.onFocus,()=>y(!0)),onBlur:(0,s.m)(l.onBlur,()=>y(!1)),onClick:(0,s.m)(l.onClick,()=>{"mouse"!==x.current&&S()}),onPointerUp:(0,s.m)(l.onPointerUp,()=>{"mouse"===x.current&&S()}),onPointerDown:(0,s.m)(l.onPointerDown,e=>{x.current=e.pointerType}),onPointerMove:(0,s.m)(l.onPointerMove,e=>{if(x.current=e.pointerType,i){var t;null==(t=c.onItemLeave)||t.call(c)}else"mouse"===x.current&&e.currentTarget.focus({preventScroll:!0})}),onPointerLeave:(0,s.m)(l.onPointerLeave,e=>{if(e.currentTarget===document.activeElement){var t;null==(t=c.onItemLeave)||t.call(c)}}),onKeyDown:(0,s.m)(l.onKeyDown,e=>{var t;((null==(t=c.searchRef)?void 0:t.current)===""||" "!==e.key)&&(rt.includes(e.key)&&S()," "===e.key&&e.preventDefault())})})})})});rF.displayName=rI;var r$="SelectItemText",rB=a.forwardRef((e,t)=>{let{__scopeSelect:r,className:n,style:i,...o}=e,u=rc(r$,r),s=rO(r$,r),c=rL(r$,r),d=rd(r$,r),[p,g]=a.useState(null),m=(0,f.s)(t,e=>g(e),c.onItemTextChange,e=>{var t;return null==(t=s.itemTextRefCallback)?void 0:t.call(s,e,c.value,c.disabled)}),y=null==p?void 0:p.textContent,b=a.useMemo(()=>(0,v.jsx)("option",{value:c.value,disabled:c.disabled,children:y},c.value),[c.disabled,c.value,y]),{onNativeOptionAdd:w,onNativeOptionRemove:x}=d;return(0,e6.N)(()=>(w(b),()=>x(b)),[w,x,b]),(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(h.sG.span,{id:c.textId,...o,ref:m}),c.isSelected&&u.valueNode&&!u.valueNodeHasChildren?l.createPortal(o.children,u.valueNode):null]})});rB.displayName=r$;var rV="SelectItemIndicator",rU=a.forwardRef((e,t)=>{let{__scopeSelect:r,...n}=e;return rL(rV,r).isSelected?(0,v.jsx)(h.sG.span,{"aria-hidden":!0,...n,ref:t}):null});rU.displayName=rV;var rH="SelectScrollUpButton",rG=a.forwardRef((e,t)=>{let r=rO(rH,e.__scopeSelect),n=rP(rH,e.__scopeSelect),[i,o]=a.useState(!1),l=(0,f.s)(t,n.onScrollButtonChange);return(0,e6.N)(()=>{if(r.viewport&&r.isPositioned){let e=function(){o(t.scrollTop>0)},t=r.viewport;return e(),t.addEventListener("scroll",e),()=>t.removeEventListener("scroll",e)}},[r.viewport,r.isPositioned]),i?(0,v.jsx)(rK,{...e,ref:l,onAutoScroll:()=>{let{viewport:e,selectedItem:t}=r;e&&t&&(e.scrollTop=e.scrollTop-t.offsetHeight)}}):null});rG.displayName=rH;var rZ="SelectScrollDownButton",rW=a.forwardRef((e,t)=>{let r=rO(rZ,e.__scopeSelect),n=rP(rZ,e.__scopeSelect),[i,o]=a.useState(!1),l=(0,f.s)(t,n.onScrollButtonChange);return(0,e6.N)(()=>{if(r.viewport&&r.isPositioned){let e=function(){let e=t.scrollHeight-t.clientHeight;o(Math.ceil(t.scrollTop)t.removeEventListener("scroll",e)}},[r.viewport,r.isPositioned]),i?(0,v.jsx)(rK,{...e,ref:l,onAutoScroll:()=>{let{viewport:e,selectedItem:t}=r;e&&t&&(e.scrollTop=e.scrollTop+t.offsetHeight)}}):null});rW.displayName=rZ;var rK=a.forwardRef((e,t)=>{let{__scopeSelect:r,onAutoScroll:n,...i}=e,o=rO("SelectScrollButton",r),l=a.useRef(null),u=ri(r),c=a.useCallback(()=>{null!==l.current&&(window.clearInterval(l.current),l.current=null)},[]);return a.useEffect(()=>()=>c(),[c]),(0,e6.N)(()=>{var e;let t=u().find(e=>e.ref.current===document.activeElement);null==t||null==(e=t.ref.current)||e.scrollIntoView({block:"nearest"})},[u]),(0,v.jsx)(h.sG.div,{"aria-hidden":!0,...i,ref:t,style:{flexShrink:0,...i.style},onPointerDown:(0,s.m)(i.onPointerDown,()=>{null===l.current&&(l.current=window.setInterval(n,50))}),onPointerMove:(0,s.m)(i.onPointerMove,()=>{var e;null==(e=o.onItemLeave)||e.call(o),null===l.current&&(l.current=window.setInterval(n,50))}),onPointerLeave:(0,s.m)(i.onPointerLeave,()=>{c()})})});a.forwardRef((e,t)=>{let{__scopeSelect:r,...n}=e;return(0,v.jsx)(h.sG.div,{"aria-hidden":!0,...n,ref:t})}).displayName="SelectSeparator";var rq="SelectArrow";a.forwardRef((e,t)=>{let{__scopeSelect:r,...n}=e,i=ru(r),o=rc(rq,r),a=rO(rq,r);return o.open&&"popper"===a.position?(0,v.jsx)(ts,{...i,...n,ref:t}):null}).displayName=rq;var rY=a.forwardRef((e,t)=>{let{__scopeSelect:r,value:n,...i}=e,o=a.useRef(null),l=(0,f.s)(t,o),u=function(e){let t=a.useRef({value:e,previous:e});return a.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}(n);return a.useEffect(()=>{let e=o.current;if(!e)return;let t=Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype,"value").set;if(u!==n&&t){let r=new Event("change",{bubbles:!0});t.call(e,n),e.dispatchEvent(r)}},[u,n]),(0,v.jsx)(h.sG.select,{...i,style:{...tv,...i.style},ref:l,defaultValue:n})});function rX(e){return""===e||void 0===e}function rJ(e){let t=(0,g.c)(e),r=a.useRef(""),n=a.useRef(0),i=a.useCallback(e=>{let i=r.current+e;t(i),function e(t){r.current=t,window.clearTimeout(n.current),""!==t&&(n.current=window.setTimeout(()=>e(""),1e3))}(i)},[t]),o=a.useCallback(()=>{r.current="",window.clearTimeout(n.current)},[]);return a.useEffect(()=>()=>window.clearTimeout(n.current),[]),[r,i,o]}function rQ(e,t,r){var n,i;let o=t.length>1&&Array.from(t).every(e=>e===t[0])?t[0]:t,a=r?e.indexOf(r):-1,l=(n=e,i=Math.max(a,0),n.map((e,t)=>n[(i+t)%n.length]));1===o.length&&(l=l.filter(e=>e!==r));let u=l.find(e=>e.textValue.toLowerCase().startsWith(o.toLowerCase()));return u!==r?u:void 0}rY.displayName="SelectBubbleInput";var r0=rp,r1=rg,r2=rm,r5=ry,r6=rb,r4=rx,r3=rj,r8=rF,r9=rB,r7=rU,ne=rG,nt=rW},1928:(e,t,r)=>{"use strict";r.d(t,{i:()=>a});var n=r(4890);function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function o(e){for(var t=1;t{if(null==t)return n.k_;var a=function(e,t,r){return"axis"===t?"click"===r?e.axisInteraction.click:e.axisInteraction.hover:"click"===r?e.itemInteraction.click:e.itemInteraction.hover}(e,t,r);if(null==a)return n.k_;if(a.active)return a;if(e.keyboardInteraction.active)return e.keyboardInteraction;if(e.syncInteraction.active&&null!=e.syncInteraction.index)return e.syncInteraction;var l=!0===e.settings.active;if(null!=a.index){if(l)return o(o({},a),{},{active:!0})}else if(null!=i)return{active:!0,coordinate:void 0,dataKey:void 0,index:i};return o(o({},n.k_),{},{coordinate:a.coordinate})}},1971:(e,t,r)=>{"use strict";r.d(t,{G:()=>f,j:()=>l});var n=r(5643),i=r(2115),o=r(5064),a=e=>e,l=()=>{var e=(0,i.useContext)(o.E);return e?e.store.dispatch:a},u=()=>{},s=()=>u,c=(e,t)=>e===t;function f(e){var t=(0,i.useContext)(o.E);return(0,n.useSyncExternalStoreWithSelector)(t?t.subscription.addNestedSub:s,t?t.store.getState:u,t?t.store.getState:u,t?e:u,c)}},1992:(e,t,r)=>{"use strict";r(4993)},2085:(e,t,r)=>{"use strict";r.d(t,{F:()=>a});var n=r(2596);let i=e=>"boolean"==typeof e?`${e}`:0===e?"0":e,o=n.$,a=(e,t)=>r=>{var n;if((null==t?void 0:t.variants)==null)return o(e,null==r?void 0:r.class,null==r?void 0:r.className);let{variants:a,defaultVariants:l}=t,u=Object.keys(a).map(e=>{let t=null==r?void 0:r[e],n=null==l?void 0:l[e];if(null===t)return null;let o=i(t)||i(n);return a[e][o]}),s=r&&Object.entries(r).reduce((e,t)=>{let[r,n]=t;return void 0===n||(e[r]=n),e},{});return o(e,u,null==t||null==(n=t.compoundVariants)?void 0:n.reduce((e,t)=>{let{class:r,className:n,...i}=t;return Object.entries(i).every(e=>{let[t,r]=e;return Array.isArray(r)?r.includes({...l,...s}[t]):({...l,...s})[t]===r})?[...e,r,n]:e},[]),null==r?void 0:r.class,null==r?void 0:r.className)}},2183:(e,t,r)=>{"use strict";r.d(t,{kz:()=>ir,fb:()=>n6,q:()=>iy,tP:()=>ik,g1:()=>iz,iv:()=>iQ,Nk:()=>n2,pM:()=>ie,Oz:()=>iv,tF:()=>iX,rj:()=>n0,ec:()=>nY,bb:()=>iw,xp:()=>iT,wL:()=>iE,sr:()=>iP,Qn:()=>ij,MK:()=>n9,IO:()=>nJ,P9:()=>is,S5:()=>ia,PU:()=>nL,cd:()=>n$,eo:()=>nW,yi:()=>il,ZB:()=>i1,D5:()=>iV,Hd:()=>nG,Gx:()=>i5,_y:()=>i4,um:()=>nZ,gT:()=>id,Kr:()=>iu,$X:()=>ih,TC:()=>n7,CR:()=>i2,ld:()=>nK,Rl:()=>nF,sf:()=>nB});var n,i,o,a,l,u,s,c={};r.r(c),r.d(c,{scaleBand:()=>x,scaleDiverging:()=>function e(){var t=eG(r3()(ek));return t.copy=function(){return r5(t,e())},g.apply(t,arguments)},scaleDivergingLog:()=>function e(){var t=eQ(r3()).domain([.1,1,10]);return t.copy=function(){return r5(t,e()).base(t.base())},g.apply(t,arguments)},scaleDivergingPow:()=>r8,scaleDivergingSqrt:()=>r9,scaleDivergingSymlog:()=>function e(){var t=e2(r3());return t.copy=function(){return r5(t,e()).constant(t.constant())},g.apply(t,arguments)},scaleIdentity:()=>function e(t){var r;function n(e){return null==e||isNaN(e*=1)?r:e}return n.invert=n,n.domain=n.range=function(e){return arguments.length?(t=Array.from(e,eC),n):t.slice()},n.unknown=function(e){return arguments.length?(r=e,n):r},n.copy=function(){return e(t).unknown(r)},t=arguments.length?Array.from(t,eC):[0,1],eG(n)},scaleImplicit:()=>b,scaleLinear:()=>function e(){var t=eR();return t.copy=function(){return ej(t,e())},h.apply(t,arguments),eG(t)},scaleLog:()=>function e(){let t=eQ(eT()).domain([1,10]);return t.copy=()=>ej(t,e()).base(t.base()),h.apply(t,arguments),t},scaleOrdinal:()=>w,scalePoint:()=>S,scalePow:()=>e8,scaleQuantile:()=>function e(){var t,r=[],n=[],i=[];function o(){var e=0,t=Math.max(1,n.length);for(i=Array(t-1);++e=1)return+r(e[n-1],n-1,e);var n,i=(n-1)*t,o=Math.floor(i),a=+r(e[o],o,e);return a+(r(e[o+1],o+1,e)-a)*(i-o)}}(r,e/t);return a}function a(e){return null==e||isNaN(e*=1)?t:n[I(i,e)]}return a.invertExtent=function(e){var t=n.indexOf(e);return t<0?[NaN,NaN]:[t>0?i[t-1]:r[0],tfunction e(){var t,r=0,n=1,i=1,o=[.5],a=[0,1];function l(e){return null!=e&&e<=e?a[I(o,e,0,i)]:t}function u(){var e=-1;for(o=Array(i);++e=i?[o[i-1],n]:[o[t-1],o[t]]},l.unknown=function(e){return arguments.length&&(t=e),l},l.thresholds=function(){return o.slice()},l.copy=function(){return e().domain([r,n]).range(a).unknown(t)},h.apply(eG(l),arguments)},scaleRadial:()=>function e(){var t,r=eR(),n=[0,1],i=!1;function o(e){var n,o=Math.sign(n=r(e))*Math.sqrt(Math.abs(n));return isNaN(o)?t:i?Math.round(o):o}return o.invert=function(e){return r.invert(e7(e))},o.domain=function(e){return arguments.length?(r.domain(e),o):r.domain()},o.range=function(e){return arguments.length?(r.range((n=Array.from(e,eC)).map(e7)),o):n.slice()},o.rangeRound=function(e){return o.range(e).round(!0)},o.round=function(e){return arguments.length?(i=!!e,o):i},o.clamp=function(e){return arguments.length?(r.clamp(e),o):r.clamp()},o.unknown=function(e){return arguments.length?(t=e,o):t},o.copy=function(){return e(r.domain(),n).round(i).clamp(r.clamp()).unknown(t)},h.apply(o,arguments),eG(o)},scaleSequential:()=>function e(){var t=eG(r2()(ek));return t.copy=function(){return r5(t,e())},g.apply(t,arguments)},scaleSequentialLog:()=>function e(){var t=eQ(r2()).domain([1,10]);return t.copy=function(){return r5(t,e()).base(t.base())},g.apply(t,arguments)},scaleSequentialPow:()=>r6,scaleSequentialQuantile:()=>function e(){var t=[],r=ek;function n(e){if(null!=e&&!isNaN(e*=1))return r((I(t,e,1)-1)/(t.length-1))}return n.domain=function(e){if(!arguments.length)return t.slice();for(let r of(t=[],e))null==r||isNaN(r*=1)||t.push(r);return t.sort(A),n},n.interpolator=function(e){return arguments.length?(r=e,n):r},n.range=function(){return t.map((e,n)=>r(n/(t.length-1)))},n.quantiles=function(e){return Array.from({length:e+1},(r,n)=>(function(e,t,r){if(!(!(n=(e=Float64Array.from(function*(e,t){if(void 0===t)for(let t of e)null!=t&&(t*=1)>=t&&(yield t);else{let r=-1;for(let n of e)null!=(n=t(n,++r,e))&&(n*=1)>=n&&(yield n)}}(e,void 0))).length)||isNaN(t*=1))){if(t<=0||n<2)return tt(e);if(t>=1)return te(e);var n,i=(n-1)*t,o=Math.floor(i),a=te((function e(t,r,n=0,i=1/0,o){if(r=Math.floor(r),n=Math.floor(Math.max(0,n)),i=Math.floor(Math.min(t.length-1,i)),!(n<=r&&r<=i))return t;for(o=void 0===o?tr:function(e=A){if(e===A)return tr;if("function"!=typeof e)throw TypeError("compare is not a function");return(t,r)=>{let n=e(t,r);return n||0===n?n:(0===e(r,r))-(0===e(t,t))}}(o);i>n;){if(i-n>600){let a=i-n+1,l=r-n+1,u=Math.log(a),s=.5*Math.exp(2*u/3),c=.5*Math.sqrt(u*s*(a-s)/a)*(l-a/2<0?-1:1),f=Math.max(n,Math.floor(r-l*s/a+c)),d=Math.min(i,Math.floor(r+(a-l)*s/a+c));e(t,r,f,d,o)}let a=t[r],l=n,u=i;for(tn(t,n,r),o(t[i],a)>0&&tn(t,n,i);lo(t[l],a);)++l;for(;o(t[u],a)>0;)--u}0===o(t[n],a)?tn(t,n,u):tn(t,++u,i),u<=r&&(n=u+1),r<=u&&(i=u-1)}return t})(e,o).subarray(0,o+1));return a+(tt(e.subarray(o+1))-a)*(i-o)}})(t,n/e))},n.copy=function(){return e(r).domain(t)},g.apply(n,arguments)},scaleSequentialSqrt:()=>r4,scaleSequentialSymlog:()=>function e(){var t=e2(r2());return t.copy=function(){return r5(t,e()).constant(t.constant())},g.apply(t,arguments)},scaleSqrt:()=>e9,scaleSymlog:()=>function e(){var t=e2(eT());return t.copy=function(){return ej(t,e()).constant(t.constant())},h.apply(t,arguments)},scaleThreshold:()=>function e(){var t,r=[.5],n=[0,1],i=1;function o(e){return null!=e&&e<=e?n[I(r,e,0,i)]:t}return o.domain=function(e){return arguments.length?(i=Math.min((r=Array.from(e)).length,n.length-1),o):r.slice()},o.range=function(e){return arguments.length?(n=Array.from(e),i=Math.min(r.length,n.length-1),o):n.slice()},o.invertExtent=function(e){var t=n.indexOf(e);return[r[t-1],r[t]]},o.unknown=function(e){return arguments.length?(t=e,o):t},o.copy=function(){return e().domain(r).range(n).unknown(t)},h.apply(o,arguments)},scaleTime:()=>r0,scaleUtc:()=>r1,tickFormat:()=>eH});var f=r(8924),d=r(3949),p=r.n(d);function h(e,t){switch(arguments.length){case 0:break;case 1:this.range(e);break;default:this.range(t).domain(e)}return this}function g(e,t){switch(arguments.length){case 0:break;case 1:"function"==typeof e?this.interpolator(e):this.range(e);break;default:this.domain(e),"function"==typeof t?this.interpolator(t):this.range(t)}return this}class v extends Map{constructor(e,t=y){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:t}}),null!=e)for(let[t,r]of e)this.set(t,r)}get(e){return super.get(m(this,e))}has(e){return super.has(m(this,e))}set(e,t){return super.set(function({_intern:e,_key:t},r){let n=t(r);return e.has(n)?e.get(n):(e.set(n,r),r)}(this,e),t)}delete(e){return super.delete(function({_intern:e,_key:t},r){let n=t(r);return e.has(n)&&(r=e.get(n),e.delete(n)),r}(this,e))}}function m({_intern:e,_key:t},r){let n=t(r);return e.has(n)?e.get(n):r}function y(e){return null!==e&&"object"==typeof e?e.valueOf():e}let b=Symbol("implicit");function w(){var e=new v,t=[],r=[],n=b;function i(i){let o=e.get(i);if(void 0===o){if(n!==b)return n;e.set(i,o=t.push(i)-1)}return r[o%r.length]}return i.domain=function(r){if(!arguments.length)return t.slice();for(let n of(t=[],e=new v,r))e.has(n)||e.set(n,t.push(n)-1);return i},i.range=function(e){return arguments.length?(r=Array.from(e),i):r.slice()},i.unknown=function(e){return arguments.length?(n=e,i):n},i.copy=function(){return w(t,r).unknown(n)},h.apply(i,arguments),i}function x(){var e,t,r=w().unknown(void 0),n=r.domain,i=r.range,o=0,a=1,l=!1,u=0,s=0,c=.5;function f(){var r=n().length,f=a=O?10:u>=E?5:u>=C?2:1;return(l<0?(n=Math.round(e*(o=Math.pow(10,-l)/s)),i=Math.round(t*o),n/ot&&--i,o=-o):(n=Math.round(e/(o=Math.pow(10,l)*s)),i=Math.round(t/o),n*ot&&--i),i0))return[];if(e===t)return[e];let n=t=i))return[];let l=o-i+1,u=Array(l);if(n)if(a<0)for(let e=0;et?1:e>=t?0:NaN}function j(e,t){return null==e||null==t?NaN:te?1:t>=e?0:NaN}function T(e){let t,r,n;function i(e,n,o=0,a=e.length){if(o>>1;0>r(e[t],n)?o=t+1:a=t}while(oA(e(t),r),n=(t,r)=>e(t)-r):(t=e===A||e===j?e:R,r=e,n=e),{left:i,center:function(e,t,r=0,o=e.length){let a=i(e,t,r,o-1);return a>r&&n(e[a-1],t)>-n(e[a],t)?a-1:a},right:function(e,n,i=0,o=e.length){if(i>>1;0>=r(e[t],n)?i=t+1:o=t}while(i>8&15|t>>4&240,t>>4&15|240&t,(15&t)<<4|15&t,1):8===r?et(t>>24&255,t>>16&255,t>>8&255,(255&t)/255):4===r?et(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|240&t,((15&t)<<4|15&t)/255):null):(t=H.exec(e))?new en(t[1],t[2],t[3],1):(t=G.exec(e))?new en(255*t[1]/100,255*t[2]/100,255*t[3]/100,1):(t=Z.exec(e))?et(t[1],t[2],t[3],t[4]):(t=W.exec(e))?et(255*t[1]/100,255*t[2]/100,255*t[3]/100,t[4]):(t=K.exec(e))?es(t[1],t[2]/100,t[3]/100,1):(t=q.exec(e))?es(t[1],t[2]/100,t[3]/100,t[4]):Y.hasOwnProperty(e)?ee(Y[e]):"transparent"===e?new en(NaN,NaN,NaN,0):null}function ee(e){return new en(e>>16&255,e>>8&255,255&e,1)}function et(e,t,r,n){return n<=0&&(e=t=r=NaN),new en(e,t,r,n)}function er(e,t,r,n){var i;return 1==arguments.length?((i=e)instanceof F||(i=Q(i)),i)?new en((i=i.rgb()).r,i.g,i.b,i.opacity):new en:new en(e,t,r,null==n?1:n)}function en(e,t,r,n){this.r=+e,this.g=+t,this.b=+r,this.opacity=+n}function ei(){return`#${eu(this.r)}${eu(this.g)}${eu(this.b)}`}function eo(){let e=ea(this.opacity);return`${1===e?"rgb(":"rgba("}${el(this.r)}, ${el(this.g)}, ${el(this.b)}${1===e?")":`, ${e})`}`}function ea(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function el(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function eu(e){return((e=el(e))<16?"0":"")+e.toString(16)}function es(e,t,r,n){return n<=0?e=t=r=NaN:r<=0||r>=1?e=t=NaN:t<=0&&(e=NaN),new ef(e,t,r,n)}function ec(e){if(e instanceof ef)return new ef(e.h,e.s,e.l,e.opacity);if(e instanceof F||(e=Q(e)),!e)return new ef;if(e instanceof ef)return e;var t=(e=e.rgb()).r/255,r=e.g/255,n=e.b/255,i=Math.min(t,r,n),o=Math.max(t,r,n),a=NaN,l=o-i,u=(o+i)/2;return l?(a=t===o?(r-n)/l+(r0&&u<1?0:a,new ef(a,l,u,e.opacity)}function ef(e,t,r,n){this.h=+e,this.s=+t,this.l=+r,this.opacity=+n}function ed(e){return(e=(e||0)%360)<0?e+360:e}function ep(e){return Math.max(0,Math.min(1,e||0))}function eh(e,t,r){return(e<60?t+(r-t)*e/60:e<180?r:e<240?t+(r-t)*(240-e)/60:t)*255}function eg(e,t,r,n,i){var o=e*e,a=o*e;return((1-3*e+3*o-a)*t+(4-6*o+3*a)*r+(1+3*e+3*o-3*a)*n+a*i)/6}N(F,Q,{copy(e){return Object.assign(new this.constructor,this,e)},displayable(){return this.rgb().displayable()},hex:X,formatHex:X,formatHex8:function(){return this.rgb().formatHex8()},formatHsl:function(){return ec(this).formatHsl()},formatRgb:J,toString:J}),N(en,er,L(F,{brighter(e){return e=null==e?1.4285714285714286:Math.pow(1.4285714285714286,e),new en(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=null==e?.7:Math.pow(.7,e),new en(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new en(el(this.r),el(this.g),el(this.b),ea(this.opacity))},displayable(){return -.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:ei,formatHex:ei,formatHex8:function(){return`#${eu(this.r)}${eu(this.g)}${eu(this.b)}${eu((isNaN(this.opacity)?1:this.opacity)*255)}`},formatRgb:eo,toString:eo})),N(ef,function(e,t,r,n){return 1==arguments.length?ec(e):new ef(e,t,r,null==n?1:n)},L(F,{brighter(e){return e=null==e?1.4285714285714286:Math.pow(1.4285714285714286,e),new ef(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=null==e?.7:Math.pow(.7,e),new ef(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,t=isNaN(e)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*t,i=2*r-n;return new en(eh(e>=240?e-240:e+120,i,n),eh(e,i,n),eh(e<120?e+240:e-120,i,n),this.opacity)},clamp(){return new ef(ed(this.h),ep(this.s),ep(this.l),ea(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){let e=ea(this.opacity);return`${1===e?"hsl(":"hsla("}${ed(this.h)}, ${100*ep(this.s)}%, ${100*ep(this.l)}%${1===e?")":`, ${e})`}`}}));let ev=e=>()=>e;function em(e,t){var r=t-e;return r?function(t){return e+t*r}:ev(isNaN(e)?t:e)}let ey=function e(t){var r,n=1==(r=+t)?em:function(e,t){var n,i,o;return t-e?(n=e,i=t,n=Math.pow(n,o=r),i=Math.pow(i,o)-n,o=1/o,function(e){return Math.pow(n+e*i,o)}):ev(isNaN(e)?t:e)};function i(e,t){var r=n((e=er(e)).r,(t=er(t)).r),i=n(e.g,t.g),o=n(e.b,t.b),a=em(e.opacity,t.opacity);return function(t){return e.r=r(t),e.g=i(t),e.b=o(t),e.opacity=a(t),e+""}}return i.gamma=e,i}(1);function eb(e){return function(t){var r,n,i=t.length,o=Array(i),a=Array(i),l=Array(i);for(r=0;r=1?(r=1,t-1):Math.floor(r*t),i=e[n],o=e[n+1],a=n>0?e[n-1]:2*i-o,l=nl&&(a=t.slice(l,a),s[u]?s[u]+=a:s[++u]=a),(i=i[0])===(o=o[0])?s[u]?s[u]+=o:s[++u]=o:(s[++u]=null,c.push({i:u,x:ew(i,o)})),l=eS.lastIndex;return lt&&(r=e,e=t,t=r),s=function(r){return Math.max(e,Math.min(t,r))}),n=u>2?eA:eP,i=o=null,f}function f(t){return null==t||isNaN(t*=1)?r:(i||(i=n(a.map(e),l,u)))(e(s(t)))}return f.invert=function(r){return s(t((o||(o=n(l,a.map(e),ew)))(r)))},f.domain=function(e){return arguments.length?(a=Array.from(e,eC),c()):a.slice()},f.range=function(e){return arguments.length?(l=Array.from(e),c()):l.slice()},f.rangeRound=function(e){return l=Array.from(e),u=eE,c()},f.clamp=function(e){return arguments.length?(s=!!e||ek,c()):s!==ek},f.interpolate=function(e){return arguments.length?(u=e,c()):u},f.unknown=function(e){return arguments.length?(r=e,f):r},function(r,n){return e=r,t=n,c()}}function eR(){return eT()(ek,ek)}var ez=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function eD(e){var t;if(!(t=ez.exec(e)))throw Error("invalid format: "+e);return new eI({fill:t[1],align:t[2],sign:t[3],symbol:t[4],zero:t[5],width:t[6],comma:t[7],precision:t[8]&&t[8].slice(1),trim:t[9],type:t[10]})}function eI(e){this.fill=void 0===e.fill?" ":e.fill+"",this.align=void 0===e.align?">":e.align+"",this.sign=void 0===e.sign?"-":e.sign+"",this.symbol=void 0===e.symbol?"":e.symbol+"",this.zero=!!e.zero,this.width=void 0===e.width?void 0:+e.width,this.comma=!!e.comma,this.precision=void 0===e.precision?void 0:+e.precision,this.trim=!!e.trim,this.type=void 0===e.type?"":e.type+""}function eN(e,t){if((r=(e=t?e.toExponential(t-1):e.toExponential()).indexOf("e"))<0)return null;var r,n=e.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+e.slice(r+1)]}function eL(e){return(e=eN(Math.abs(e)))?e[1]:NaN}function eF(e,t){var r=eN(e,t);if(!r)return e+"";var n=r[0],i=r[1];return i<0?"0."+Array(-i).join("0")+n:n.length>i+1?n.slice(0,i+1)+"."+n.slice(i+1):n+Array(i-n.length+2).join("0")}eD.prototype=eI.prototype,eI.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};let e$={"%":(e,t)=>(100*e).toFixed(t),b:e=>Math.round(e).toString(2),c:e=>e+"",d:function(e){return Math.abs(e=Math.round(e))>=1e21?e.toLocaleString("en").replace(/,/g,""):e.toString(10)},e:(e,t)=>e.toExponential(t),f:(e,t)=>e.toFixed(t),g:(e,t)=>e.toPrecision(t),o:e=>Math.round(e).toString(8),p:(e,t)=>eF(100*e,t),r:eF,s:function(e,t){var r=eN(e,t);if(!r)return e+"";var i=r[0],o=r[1],a=o-(n=3*Math.max(-8,Math.min(8,Math.floor(o/3))))+1,l=i.length;return a===l?i:a>l?i+Array(a-l+1).join("0"):a>0?i.slice(0,a)+"."+i.slice(a):"0."+Array(1-a).join("0")+eN(e,Math.max(0,t+a-1))[0]},X:e=>Math.round(e).toString(16).toUpperCase(),x:e=>Math.round(e).toString(16)};function eB(e){return e}var eV=Array.prototype.map,eU=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"];function eH(e,t,r,n){var i,l,u=P(e,t,r);switch((n=eD(null==n?",f":n)).type){case"s":var s=Math.max(Math.abs(e),Math.abs(t));return null!=n.precision||isNaN(l=Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(eL(s)/3)))-eL(Math.abs(u))))||(n.precision=l),a(n,s);case"":case"e":case"g":case"p":case"r":null!=n.precision||isNaN(l=Math.max(0,eL(Math.abs(Math.max(Math.abs(e),Math.abs(t)))-(i=Math.abs(i=u)))-eL(i))+1)||(n.precision=l-("e"===n.type));break;case"f":case"%":null!=n.precision||isNaN(l=Math.max(0,-eL(Math.abs(u))))||(n.precision=l-("%"===n.type)*2)}return o(n)}function eG(e){var t=e.domain;return e.ticks=function(e){var r=t();return k(r[0],r[r.length-1],null==e?10:e)},e.tickFormat=function(e,r){var n=t();return eH(n[0],n[n.length-1],null==e?10:e,r)},e.nice=function(r){null==r&&(r=10);var n,i,o=t(),a=0,l=o.length-1,u=o[a],s=o[l],c=10;for(s0;){if((i=_(u,s,r))===n)return o[a]=u,o[l]=s,t(o);if(i>0)u=Math.floor(u/i)*i,s=Math.ceil(s/i)*i;else if(i<0)u=Math.ceil(u*i)/i,s=Math.floor(s*i)/i;else break;n=i}return e},e}function eZ(e,t){e=e.slice();var r,n=0,i=e.length-1,o=e[n],a=e[i];return a-e(-t,r)}function eQ(e){let t,r,n=e(eW,eK),i=n.domain,a=10;function l(){var o,l;return t=(o=a)===Math.E?Math.log:10===o&&Math.log10||2===o&&Math.log2||(o=Math.log(o),e=>Math.log(e)/o),r=10===(l=a)?eX:l===Math.E?Math.exp:e=>Math.pow(l,e),i()[0]<0?(t=eJ(t),r=eJ(r),e(eq,eY)):e(eW,eK),n}return n.base=function(e){return arguments.length?(a=+e,l()):a},n.domain=function(e){return arguments.length?(i(e),l()):i()},n.ticks=e=>{let n,o,l=i(),u=l[0],s=l[l.length-1],c=s0){for(;f<=d;++f)for(n=1;ns)break;h.push(o)}}else for(;f<=d;++f)for(n=a-1;n>=1;--n)if(!((o=f>0?n/r(-f):n*r(f))s)break;h.push(o)}2*h.length{if(null==e&&(e=10),null==i&&(i=10===a?"s":","),"function"!=typeof i&&(a%1||null!=(i=eD(i)).precision||(i.trim=!0),i=o(i)),e===1/0)return i;let l=Math.max(1,a*e/n.ticks().length);return e=>{let n=e/r(Math.round(t(e)));return n*ai(eZ(i(),{floor:e=>r(Math.floor(t(e))),ceil:e=>r(Math.ceil(t(e)))})),n}function e0(e){return function(t){return Math.sign(t)*Math.log1p(Math.abs(t/e))}}function e1(e){return function(t){return Math.sign(t)*Math.expm1(Math.abs(t))*e}}function e2(e){var t=1,r=e(e0(1),e1(t));return r.constant=function(r){return arguments.length?e(e0(t=+r),e1(t)):t},eG(r)}function e5(e){return function(t){return t<0?-Math.pow(-t,e):Math.pow(t,e)}}function e6(e){return e<0?-Math.sqrt(-e):Math.sqrt(e)}function e4(e){return e<0?-e*e:e*e}function e3(e){var t=e(ek,ek),r=1;return t.exponent=function(t){return arguments.length?1==(r=+t)?e(ek,ek):.5===r?e(e6,e4):e(e5(r),e5(1/r)):r},eG(t)}function e8(){var e=e3(eT());return e.copy=function(){return ej(e,e8()).exponent(e.exponent())},h.apply(e,arguments),e}function e9(){return e8.apply(null,arguments).exponent(.5)}function e7(e){return Math.sign(e)*e*e}function te(e,t){let r;if(void 0===t)for(let t of e)null!=t&&(r=t)&&(r=t);else{let n=-1;for(let i of e)null!=(i=t(i,++n,e))&&(r=i)&&(r=i)}return r}function tt(e,t){let r;if(void 0===t)for(let t of e)null!=t&&(r>t||void 0===r&&t>=t)&&(r=t);else{let n=-1;for(let i of e)null!=(i=t(i,++n,e))&&(r>i||void 0===r&&i>=i)&&(r=i)}return r}function tr(e,t){return(null==e||!(e>=e))-(null==t||!(t>=t))||(et))}function tn(e,t,r){let n=e[t];e[t]=e[r],e[r]=n}o=(i=function(e){var t,r,i,o=void 0===e.grouping||void 0===e.thousands?eB:(t=eV.call(e.grouping,Number),r=e.thousands+"",function(e,n){for(var i=e.length,o=[],a=0,l=t[0],u=0;i>0&&l>0&&(u+l+1>n&&(l=Math.max(1,n-u)),o.push(e.substring(i-=l,i+l)),!((u+=l+1)>n));)l=t[a=(a+1)%t.length];return o.reverse().join(r)}),a=void 0===e.currency?"":e.currency[0]+"",l=void 0===e.currency?"":e.currency[1]+"",u=void 0===e.decimal?".":e.decimal+"",s=void 0===e.numerals?eB:(i=eV.call(e.numerals,String),function(e){return e.replace(/[0-9]/g,function(e){return i[+e]})}),c=void 0===e.percent?"%":e.percent+"",f=void 0===e.minus?"−":e.minus+"",d=void 0===e.nan?"NaN":e.nan+"";function p(e){var t=(e=eD(e)).fill,r=e.align,i=e.sign,p=e.symbol,h=e.zero,g=e.width,v=e.comma,m=e.precision,y=e.trim,b=e.type;"n"===b?(v=!0,b="g"):e$[b]||(void 0===m&&(m=12),y=!0,b="g"),(h||"0"===t&&"="===r)&&(h=!0,t="0",r="=");var w="$"===p?a:"#"===p&&/[boxX]/.test(b)?"0"+b.toLowerCase():"",x="$"===p?l:/[%p]/.test(b)?c:"",S=e$[b],O=/[defgprs%]/.test(b);function E(e){var a,l,c,p=w,E=x;if("c"===b)E=S(e)+E,e="";else{var C=(e*=1)<0||1/e<0;if(e=isNaN(e)?d:S(Math.abs(e),m),y&&(e=function(e){e:for(var t,r=e.length,n=1,i=-1;n0&&(i=0)}return i>0?e.slice(0,i)+e.slice(t+1):e}(e)),C&&0==+e&&"+"!==i&&(C=!1),p=(C?"("===i?i:f:"-"===i||"("===i?"":i)+p,E=("s"===b?eU[8+n/3]:"")+E+(C&&"("===i?")":""),O){for(a=-1,l=e.length;++a(c=e.charCodeAt(a))||c>57){E=(46===c?u+e.slice(a+1):e.slice(a))+E,e=e.slice(0,a);break}}}v&&!h&&(e=o(e,1/0));var M=p.length+e.length+E.length,k=M>1)+p+e+E+k.slice(M);break;default:e=k+p+e+E}return s(e)}return m=void 0===m?6:/[gprs]/.test(b)?Math.max(1,Math.min(21,m)):Math.max(0,Math.min(20,m)),E.toString=function(){return e+""},E}return{format:p,formatPrefix:function(e,t){var r=p(((e=eD(e)).type="f",e)),n=3*Math.max(-8,Math.min(8,Math.floor(eL(t)/3))),i=Math.pow(10,-n),o=eU[8+n/3];return function(e){return r(i*e)+o}}}}({thousands:",",grouping:[3],currency:["$",""]})).format,a=i.formatPrefix;let ti=new Date,to=new Date;function ta(e,t,r,n){function i(t){return e(t=0==arguments.length?new Date:new Date(+t)),t}return i.floor=t=>(e(t=new Date(+t)),t),i.ceil=r=>(e(r=new Date(r-1)),t(r,1),e(r),r),i.round=e=>{let t=i(e),r=i.ceil(e);return e-t(t(e=new Date(+e),null==r?1:Math.floor(r)),e),i.range=(r,n,o)=>{let a,l=[];if(r=i.ceil(r),o=null==o?1:Math.floor(o),!(r0))return l;do l.push(a=new Date(+r)),t(r,o),e(r);while(ata(t=>{if(t>=t)for(;e(t),!r(t);)t.setTime(t-1)},(e,n)=>{if(e>=e)if(n<0)for(;++n<=0;)for(;t(e,-1),!r(e););else for(;--n>=0;)for(;t(e,1),!r(e););}),r&&(i.count=(t,n)=>(ti.setTime(+t),to.setTime(+n),e(ti),e(to),Math.floor(r(ti,to))),i.every=e=>isFinite(e=Math.floor(e))&&e>0?e>1?i.filter(n?t=>n(t)%e==0:t=>i.count(0,t)%e==0):i:null),i}let tl=ta(()=>{},(e,t)=>{e.setTime(+e+t)},(e,t)=>t-e);tl.every=e=>isFinite(e=Math.floor(e))&&e>0?e>1?ta(t=>{t.setTime(Math.floor(t/e)*e)},(t,r)=>{t.setTime(+t+r*e)},(t,r)=>(r-t)/e):tl:null,tl.range;let tu=ta(e=>{e.setTime(e-e.getMilliseconds())},(e,t)=>{e.setTime(+e+1e3*t)},(e,t)=>(t-e)/1e3,e=>e.getUTCSeconds());tu.range;let ts=ta(e=>{e.setTime(e-e.getMilliseconds()-1e3*e.getSeconds())},(e,t)=>{e.setTime(+e+6e4*t)},(e,t)=>(t-e)/6e4,e=>e.getMinutes());ts.range;let tc=ta(e=>{e.setUTCSeconds(0,0)},(e,t)=>{e.setTime(+e+6e4*t)},(e,t)=>(t-e)/6e4,e=>e.getUTCMinutes());tc.range;let tf=ta(e=>{e.setTime(e-e.getMilliseconds()-1e3*e.getSeconds()-6e4*e.getMinutes())},(e,t)=>{e.setTime(+e+36e5*t)},(e,t)=>(t-e)/36e5,e=>e.getHours());tf.range;let td=ta(e=>{e.setUTCMinutes(0,0,0)},(e,t)=>{e.setTime(+e+36e5*t)},(e,t)=>(t-e)/36e5,e=>e.getUTCHours());td.range;let tp=ta(e=>e.setHours(0,0,0,0),(e,t)=>e.setDate(e.getDate()+t),(e,t)=>(t-e-(t.getTimezoneOffset()-e.getTimezoneOffset())*6e4)/864e5,e=>e.getDate()-1);tp.range;let th=ta(e=>{e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+t)},(e,t)=>(t-e)/864e5,e=>e.getUTCDate()-1);th.range;let tg=ta(e=>{e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+t)},(e,t)=>(t-e)/864e5,e=>Math.floor(e/864e5));function tv(e){return ta(t=>{t.setDate(t.getDate()-(t.getDay()+7-e)%7),t.setHours(0,0,0,0)},(e,t)=>{e.setDate(e.getDate()+7*t)},(e,t)=>(t-e-(t.getTimezoneOffset()-e.getTimezoneOffset())*6e4)/6048e5)}tg.range;let tm=tv(0),ty=tv(1),tb=tv(2),tw=tv(3),tx=tv(4),tS=tv(5),tO=tv(6);function tE(e){return ta(t=>{t.setUTCDate(t.getUTCDate()-(t.getUTCDay()+7-e)%7),t.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+7*t)},(e,t)=>(t-e)/6048e5)}tm.range,ty.range,tb.range,tw.range,tx.range,tS.range,tO.range;let tC=tE(0),tM=tE(1),tk=tE(2),t_=tE(3),tP=tE(4),tA=tE(5),tj=tE(6);tC.range,tM.range,tk.range,t_.range,tP.range,tA.range,tj.range;let tT=ta(e=>{e.setDate(1),e.setHours(0,0,0,0)},(e,t)=>{e.setMonth(e.getMonth()+t)},(e,t)=>t.getMonth()-e.getMonth()+(t.getFullYear()-e.getFullYear())*12,e=>e.getMonth());tT.range;let tR=ta(e=>{e.setUTCDate(1),e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCMonth(e.getUTCMonth()+t)},(e,t)=>t.getUTCMonth()-e.getUTCMonth()+(t.getUTCFullYear()-e.getUTCFullYear())*12,e=>e.getUTCMonth());tR.range;let tz=ta(e=>{e.setMonth(0,1),e.setHours(0,0,0,0)},(e,t)=>{e.setFullYear(e.getFullYear()+t)},(e,t)=>t.getFullYear()-e.getFullYear(),e=>e.getFullYear());tz.every=e=>isFinite(e=Math.floor(e))&&e>0?ta(t=>{t.setFullYear(Math.floor(t.getFullYear()/e)*e),t.setMonth(0,1),t.setHours(0,0,0,0)},(t,r)=>{t.setFullYear(t.getFullYear()+r*e)}):null,tz.range;let tD=ta(e=>{e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCFullYear(e.getUTCFullYear()+t)},(e,t)=>t.getUTCFullYear()-e.getUTCFullYear(),e=>e.getUTCFullYear());function tI(e,t,r,n,i,o){let a=[[tu,1,1e3],[tu,5,5e3],[tu,15,15e3],[tu,30,3e4],[o,1,6e4],[o,5,3e5],[o,15,9e5],[o,30,18e5],[i,1,36e5],[i,3,108e5],[i,6,216e5],[i,12,432e5],[n,1,864e5],[n,2,1728e5],[r,1,6048e5],[t,1,2592e6],[t,3,7776e6],[e,1,31536e6]];function l(t,r,n){let i=Math.abs(r-t)/n,o=T(([,,e])=>e).right(a,i);if(o===a.length)return e.every(P(t/31536e6,r/31536e6,n));if(0===o)return tl.every(Math.max(P(t,r,n),1));let[l,u]=a[i/a[o-1][2]isFinite(e=Math.floor(e))&&e>0?ta(t=>{t.setUTCFullYear(Math.floor(t.getUTCFullYear()/e)*e),t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,r)=>{t.setUTCFullYear(t.getUTCFullYear()+r*e)}):null,tD.range;let[tN,tL]=tI(tD,tR,tC,tg,td,tc),[tF,t$]=tI(tz,tT,tm,tp,tf,ts);function tB(e){if(0<=e.y&&e.y<100){var t=new Date(-1,e.m,e.d,e.H,e.M,e.S,e.L);return t.setFullYear(e.y),t}return new Date(e.y,e.m,e.d,e.H,e.M,e.S,e.L)}function tV(e){if(0<=e.y&&e.y<100){var t=new Date(Date.UTC(-1,e.m,e.d,e.H,e.M,e.S,e.L));return t.setUTCFullYear(e.y),t}return new Date(Date.UTC(e.y,e.m,e.d,e.H,e.M,e.S,e.L))}function tU(e,t,r){return{y:e,m:t,d:r,H:0,M:0,S:0,L:0}}var tH={"-":"",_:" ",0:"0"},tG=/^\s*\d+/,tZ=/^%/,tW=/[\\^$*+?|[\]().{}]/g;function tK(e,t,r){var n=e<0?"-":"",i=(n?-e:e)+"",o=i.length;return n+(o[e.toLowerCase(),t]))}function tJ(e,t,r){var n=tG.exec(t.slice(r,r+1));return n?(e.w=+n[0],r+n[0].length):-1}function tQ(e,t,r){var n=tG.exec(t.slice(r,r+1));return n?(e.u=+n[0],r+n[0].length):-1}function t0(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.U=+n[0],r+n[0].length):-1}function t1(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.V=+n[0],r+n[0].length):-1}function t2(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.W=+n[0],r+n[0].length):-1}function t5(e,t,r){var n=tG.exec(t.slice(r,r+4));return n?(e.y=+n[0],r+n[0].length):-1}function t6(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function t4(e,t,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(t.slice(r,r+6));return n?(e.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function t3(e,t,r){var n=tG.exec(t.slice(r,r+1));return n?(e.q=3*n[0]-3,r+n[0].length):-1}function t8(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.m=n[0]-1,r+n[0].length):-1}function t9(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.d=+n[0],r+n[0].length):-1}function t7(e,t,r){var n=tG.exec(t.slice(r,r+3));return n?(e.m=0,e.d=+n[0],r+n[0].length):-1}function re(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.H=+n[0],r+n[0].length):-1}function rt(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.M=+n[0],r+n[0].length):-1}function rr(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.S=+n[0],r+n[0].length):-1}function rn(e,t,r){var n=tG.exec(t.slice(r,r+3));return n?(e.L=+n[0],r+n[0].length):-1}function ri(e,t,r){var n=tG.exec(t.slice(r,r+6));return n?(e.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function ro(e,t,r){var n=tZ.exec(t.slice(r,r+1));return n?r+n[0].length:-1}function ra(e,t,r){var n=tG.exec(t.slice(r));return n?(e.Q=+n[0],r+n[0].length):-1}function rl(e,t,r){var n=tG.exec(t.slice(r));return n?(e.s=+n[0],r+n[0].length):-1}function ru(e,t){return tK(e.getDate(),t,2)}function rs(e,t){return tK(e.getHours(),t,2)}function rc(e,t){return tK(e.getHours()%12||12,t,2)}function rf(e,t){return tK(1+tp.count(tz(e),e),t,3)}function rd(e,t){return tK(e.getMilliseconds(),t,3)}function rp(e,t){return rd(e,t)+"000"}function rh(e,t){return tK(e.getMonth()+1,t,2)}function rg(e,t){return tK(e.getMinutes(),t,2)}function rv(e,t){return tK(e.getSeconds(),t,2)}function rm(e){var t=e.getDay();return 0===t?7:t}function ry(e,t){return tK(tm.count(tz(e)-1,e),t,2)}function rb(e){var t=e.getDay();return t>=4||0===t?tx(e):tx.ceil(e)}function rw(e,t){return e=rb(e),tK(tx.count(tz(e),e)+(4===tz(e).getDay()),t,2)}function rx(e){return e.getDay()}function rS(e,t){return tK(ty.count(tz(e)-1,e),t,2)}function rO(e,t){return tK(e.getFullYear()%100,t,2)}function rE(e,t){return tK((e=rb(e)).getFullYear()%100,t,2)}function rC(e,t){return tK(e.getFullYear()%1e4,t,4)}function rM(e,t){var r=e.getDay();return tK((e=r>=4||0===r?tx(e):tx.ceil(e)).getFullYear()%1e4,t,4)}function rk(e){var t=e.getTimezoneOffset();return(t>0?"-":(t*=-1,"+"))+tK(t/60|0,"0",2)+tK(t%60,"0",2)}function r_(e,t){return tK(e.getUTCDate(),t,2)}function rP(e,t){return tK(e.getUTCHours(),t,2)}function rA(e,t){return tK(e.getUTCHours()%12||12,t,2)}function rj(e,t){return tK(1+th.count(tD(e),e),t,3)}function rT(e,t){return tK(e.getUTCMilliseconds(),t,3)}function rR(e,t){return rT(e,t)+"000"}function rz(e,t){return tK(e.getUTCMonth()+1,t,2)}function rD(e,t){return tK(e.getUTCMinutes(),t,2)}function rI(e,t){return tK(e.getUTCSeconds(),t,2)}function rN(e){var t=e.getUTCDay();return 0===t?7:t}function rL(e,t){return tK(tC.count(tD(e)-1,e),t,2)}function rF(e){var t=e.getUTCDay();return t>=4||0===t?tP(e):tP.ceil(e)}function r$(e,t){return e=rF(e),tK(tP.count(tD(e),e)+(4===tD(e).getUTCDay()),t,2)}function rB(e){return e.getUTCDay()}function rV(e,t){return tK(tM.count(tD(e)-1,e),t,2)}function rU(e,t){return tK(e.getUTCFullYear()%100,t,2)}function rH(e,t){return tK((e=rF(e)).getUTCFullYear()%100,t,2)}function rG(e,t){return tK(e.getUTCFullYear()%1e4,t,4)}function rZ(e,t){var r=e.getUTCDay();return tK((e=r>=4||0===r?tP(e):tP.ceil(e)).getUTCFullYear()%1e4,t,4)}function rW(){return"+0000"}function rK(){return"%"}function rq(e){return+e}function rY(e){return Math.floor(e/1e3)}function rX(e){return new Date(e)}function rJ(e){return e instanceof Date?+e:+new Date(+e)}function rQ(e,t,r,n,i,o,a,l,u,s){var c=eR(),f=c.invert,d=c.domain,p=s(".%L"),h=s(":%S"),g=s("%I:%M"),v=s("%I %p"),m=s("%a %d"),y=s("%b %d"),b=s("%B"),w=s("%Y");function x(e){return(u(e)=12)]},q:function(e){return 1+~~(e.getMonth()/3)},Q:rq,s:rY,S:rv,u:rm,U:ry,V:rw,w:rx,W:rS,x:null,X:null,y:rO,Y:rC,Z:rk,"%":rK},w={a:function(e){return a[e.getUTCDay()]},A:function(e){return o[e.getUTCDay()]},b:function(e){return u[e.getUTCMonth()]},B:function(e){return l[e.getUTCMonth()]},c:null,d:r_,e:r_,f:rR,g:rH,G:rZ,H:rP,I:rA,j:rj,L:rT,m:rz,M:rD,p:function(e){return i[+(e.getUTCHours()>=12)]},q:function(e){return 1+~~(e.getUTCMonth()/3)},Q:rq,s:rY,S:rI,u:rN,U:rL,V:r$,w:rB,W:rV,x:null,X:null,y:rU,Y:rG,Z:rW,"%":rK},x={a:function(e,t,r){var n=p.exec(t.slice(r));return n?(e.w=h.get(n[0].toLowerCase()),r+n[0].length):-1},A:function(e,t,r){var n=f.exec(t.slice(r));return n?(e.w=d.get(n[0].toLowerCase()),r+n[0].length):-1},b:function(e,t,r){var n=m.exec(t.slice(r));return n?(e.m=y.get(n[0].toLowerCase()),r+n[0].length):-1},B:function(e,t,r){var n=g.exec(t.slice(r));return n?(e.m=v.get(n[0].toLowerCase()),r+n[0].length):-1},c:function(e,r,n){return E(e,t,r,n)},d:t9,e:t9,f:ri,g:t6,G:t5,H:re,I:re,j:t7,L:rn,m:t8,M:rt,p:function(e,t,r){var n=s.exec(t.slice(r));return n?(e.p=c.get(n[0].toLowerCase()),r+n[0].length):-1},q:t3,Q:ra,s:rl,S:rr,u:tQ,U:t0,V:t1,w:tJ,W:t2,x:function(e,t,n){return E(e,r,t,n)},X:function(e,t,r){return E(e,n,t,r)},y:t6,Y:t5,Z:t4,"%":ro};function S(e,t){return function(r){var n,i,o,a=[],l=-1,u=0,s=e.length;for(r instanceof Date||(r=new Date(+r));++l53)return null;"w"in o||(o.w=1),"Z"in o?(n=(i=(n=tV(tU(o.y,0,1))).getUTCDay())>4||0===i?tM.ceil(n):tM(n),n=th.offset(n,(o.V-1)*7),o.y=n.getUTCFullYear(),o.m=n.getUTCMonth(),o.d=n.getUTCDate()+(o.w+6)%7):(n=(i=(n=tB(tU(o.y,0,1))).getDay())>4||0===i?ty.ceil(n):ty(n),n=tp.offset(n,(o.V-1)*7),o.y=n.getFullYear(),o.m=n.getMonth(),o.d=n.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:+("W"in o)),i="Z"in o?tV(tU(o.y,0,1)).getUTCDay():tB(tU(o.y,0,1)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,tV(o)):tB(o)}}function E(e,t,r,n){for(var i,o,a=0,l=t.length,u=r.length;a=u)return -1;if(37===(i=t.charCodeAt(a++))){if(!(o=x[(i=t.charAt(a++))in tH?t.charAt(a++):i])||(n=o(e,r,n))<0)return -1}else if(i!=r.charCodeAt(n++))return -1}return n}return b.x=S(r,b),b.X=S(n,b),b.c=S(t,b),w.x=S(r,w),w.X=S(n,w),w.c=S(t,w),{format:function(e){var t=S(e+="",b);return t.toString=function(){return e},t},parse:function(e){var t=O(e+="",!1);return t.toString=function(){return e},t},utcFormat:function(e){var t=S(e+="",w);return t.toString=function(){return e},t},utcParse:function(e){var t=O(e+="",!0);return t.toString=function(){return e},t}}}({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]})).format,l.parse,s=l.utcFormat,l.utcParse;var r7=r(7238),ne=r(9827),nt=r(356),nr=r(6377),nn=r(8892);function ni(e){if(Array.isArray(e)&&2===e.length){var[t,r]=e;if((0,nn.H)(t)&&(0,nn.H)(r))return!0}return!1}function no(e,t,r){return r?e:[Math.min(e[0],t[0]),Math.max(e[1],t[1])]}var na=r(8870),nl=r.n(na),nu=e=>e,ns={},nc=e=>function t(){let r;return 0==arguments.length||1==arguments.length&&(r=arguments.length<=0?void 0:arguments[0],r===ns)?t:e(...arguments)},nf=(e,t)=>1===e?t:nc(function(){for(var r=arguments.length,n=Array(r),i=0;ie!==ns).length;return o>=e?t(...n):nf(e-o,nc(function(){for(var e=arguments.length,r=Array(e),i=0;ie===ns?r.shift():e),...r)}))}),nd=e=>nf(e.length,e),np=(e,t)=>{for(var r=[],n=e;nArray.isArray(t)?t.map(e):Object.keys(t).map(e=>t[e]).map(e)),ng=function(){for(var e=arguments.length,t=Array(e),r=0;rt(e),i(...arguments))}},nv=e=>Array.isArray(e)?e.reverse():e.split("").reverse().join(""),nm=e=>{var t=null,r=null;return function(){for(var n=arguments.length,i=Array(n),o=0;o{var n;return e===(null==(n=t)?void 0:n[r])})?r:(t=i,r=e(...i))}};function ny(e){return 0===e?1:Math.floor(new(nl())(e).abs().log(10).toNumber())+1}function nb(e,t,r){for(var n=new(nl())(e),i=0,o=[];n.lt(t)&&i<1e5;)o.push(n.toNumber()),n=n.add(r),i++;return o}nd((e,t,r)=>{var n=+e;return n+r*(t-n)}),nd((e,t,r)=>{var n=t-e;return(r-e)/(n=n||1/0)}),nd((e,t,r)=>{var n=t-e;return Math.max(0,Math.min(1,(r-e)/(n=n||1/0)))});var nw=e=>{var[t,r]=e,[n,i]=[t,r];return t>r&&([n,i]=[r,t]),[n,i]},nx=(e,t,r)=>{if(e.lte(0))return new(nl())(0);var n=ny(e.toNumber()),i=new(nl())(10).pow(n),o=e.div(i),a=1!==n?.05:.1,l=new(nl())(Math.ceil(o.div(a).toNumber())).add(r).mul(a).mul(i);return new(nl())(t?l.toNumber():Math.ceil(l.toNumber()))},nS=function(e,t,r,n){var i,o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0;if(!Number.isFinite((t-e)/(r-1)))return{step:new(nl())(0),tickMin:new(nl())(0),tickMax:new(nl())(0)};var a=nx(new(nl())(t).sub(e).div(r-1),n,o),l=Math.ceil((i=e<=0&&t>=0?new(nl())(0):(i=new(nl())(e).add(t).div(2)).sub(new(nl())(i).mod(a))).sub(e).div(a).toNumber()),u=Math.ceil(new(nl())(t).sub(i).div(a).toNumber()),s=l+u+1;return s>r?nS(e,t,r,n,o+1):(s0?u+(r-s):u,l=t>0?l:l+(r-s)),{step:a,tickMin:i.sub(new(nl())(l).mul(a)),tickMax:i.add(new(nl())(u).mul(a))})},nO=nm(function(e){var[t,r]=e,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2],o=Math.max(n,2),[a,l]=nw([t,r]);if(a===-1/0||l===1/0){var u=l===1/0?[a,...np(0,n-1).map(()=>1/0)]:[...np(0,n-1).map(()=>-1/0),l];return t>r?nv(u):u}if(a===l){var s=new(nl())(1),c=new(nl())(a);if(!c.isint()&&i){var f=Math.abs(a);f<1?(s=new(nl())(10).pow(ny(a)-1),c=new(nl())(Math.floor(c.div(s).toNumber())).mul(s)):f>1&&(c=new(nl())(Math.floor(a)))}else 0===a?c=new(nl())(Math.floor((n-1)/2)):i||(c=new(nl())(Math.floor(a)));var d=Math.floor((n-1)/2);return ng(nh(e=>c.add(new(nl())(e-d).mul(s)).toNumber()),np)(0,n)}var{step:p,tickMin:h,tickMax:g}=nS(a,l,o,i,0),v=nb(h,g.add(new(nl())(.1).mul(p)),p);return t>r?nv(v):v}),nE=nm(function(e,t){var[r,n]=e,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2],[o,a]=nw([r,n]);if(o===-1/0||a===1/0)return[r,n];if(o===a)return[o];var l=Math.max(t,2),u=nx(new(nl())(a).sub(o).div(l-1),i,0),s=[...nb(new(nl())(o),new(nl())(a),u),a];return!1===i&&(s=s.map(e=>Math.round(e))),r>n?nv(s):s}),nC=r(2589),nM=r(6908),nk=r(6124),n_=r(972),nP=r(8478),nA=r(7062),nj=(e,t)=>t,nT=(e,t,r)=>r,nR=r(8190),nz=r(4421);function nD(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function nI(e){for(var t=1;t{var r=e.cartesianAxis.xAxis[t];return null==r?nL:r},n$={allowDataOverflow:!1,allowDecimals:!0,allowDuplicatedCategory:!0,angle:0,dataKey:void 0,domain:nN,hide:!0,id:0,includeHidden:!1,interval:"preserveEnd",minTickGap:5,mirror:!1,name:void 0,orientation:"left",padding:{top:0,bottom:0},reversed:!1,scale:"auto",tick:!0,tickCount:5,tickFormatter:void 0,ticks:void 0,type:"number",unit:void 0,width:nz.tQ},nB=(e,t)=>{var r=e.cartesianAxis.yAxis[t];return null==r?n$:r},nV={domain:[0,"auto"],includeHidden:!1,reversed:!1,allowDataOverflow:!1,allowDuplicatedCategory:!1,dataKey:void 0,id:0,name:"",range:[64,64],scale:"auto",type:"number",unit:""},nU=(e,t)=>{var r=e.cartesianAxis.zAxis[t];return null==r?nV:r},nH=(e,t,r)=>{switch(t){case"xAxis":return nF(e,r);case"yAxis":return nB(e,r);case"zAxis":return nU(e,r);case"angleAxis":return(0,nA.Be)(e,r);case"radiusAxis":return(0,nA.Gl)(e,r);default:throw Error("Unexpected axis type: ".concat(t))}},nG=(e,t,r)=>{switch(t){case"xAxis":return nF(e,r);case"yAxis":return nB(e,r);case"angleAxis":return(0,nA.Be)(e,r);case"radiusAxis":return(0,nA.Gl)(e,r);default:throw Error("Unexpected axis type: ".concat(t))}},nZ=e=>e.graphicalItems.countOfBars>0;function nW(e,t){return r=>{switch(e){case"xAxis":return"xAxisId"in r&&r.xAxisId===t;case"yAxis":return"yAxisId"in r&&r.yAxisId===t;case"zAxis":return"zAxisId"in r&&r.zAxisId===t;case"angleAxis":return"angleAxisId"in r&&r.angleAxisId===t;case"radiusAxis":return"radiusAxisId"in r&&r.radiusAxisId===t;default:return!1}}}var nK=e=>e.graphicalItems.cartesianItems,nq=(0,f.Mz)([nj,nT],nW),nY=(e,t,r)=>e.filter(r).filter(e=>(null==t?void 0:t.includeHidden)===!0||!e.hide),nX=(0,f.Mz)([nK,nH,nq],nY),nJ=e=>e.filter(e=>void 0===e.stackId),nQ=(0,f.Mz)([nX],nJ),n0=e=>e.map(e=>e.data).filter(Boolean).flat(1),n1=(0,f.Mz)([nX],n0),n2=(e,t)=>{var{chartData:r=[],dataStartIndex:n,dataEndIndex:i}=t;return e.length>0?e:r.slice(n,i+1)},n5=(0,f.Mz)([n1,nt.HS],n2),n6=(e,t,r)=>(null==t?void 0:t.dataKey)!=null?e.map(e=>({value:(0,ne.kr)(e,t.dataKey)})):r.length>0?r.map(e=>e.dataKey).flatMap(t=>e.map(e=>({value:(0,ne.kr)(e,t)}))):e.map(e=>({value:e})),n4=(0,f.Mz)([n5,nH,nX],n6);function n3(e,t){switch(e){case"xAxis":return"x"===t.direction;case"yAxis":return"y"===t.direction;default:return!1}}function n8(e){return e.filter(e=>(0,nr.vh)(e)||e instanceof Date).map(Number).filter(e=>!1===(0,nr.M8)(e))}var n9=(e,t,r)=>Object.fromEntries(Object.entries(t.reduce((e,t)=>(null==t.stackId||(null==e[t.stackId]&&(e[t.stackId]=[]),e[t.stackId].push(t)),e),{})).map(t=>{var[n,i]=t,o=i.map(e=>e.dataKey);return[n,{stackedData:(0,ne.yy)(e,o,r),graphicalItems:i}]})),n7=(0,f.Mz)([n5,nX,nP.eC],n9),ie=(e,t,r)=>{var{dataStartIndex:n,dataEndIndex:i}=t;if("zAxis"!==r){var o=(0,ne.Mk)(e,n,i);if(null==o||0!==o[0]||0!==o[1])return o}},it=(0,f.Mz)([n7,nt.LF,nj],ie),ir=(e,t,r,n)=>r.length>0?e.flatMap(e=>r.flatMap(r=>{var i,o,a=null==(i=r.errorBars)?void 0:i.filter(e=>n3(n,e)),l=(0,ne.kr)(e,null!=(o=t.dataKey)?o:r.dataKey);return{value:l,errorDomain:function(e,t,r){return!r||"number"!=typeof t||(0,nr.M8)(t)||!r.length?[]:n8(r.flatMap(r=>{var n,i,o=(0,ne.kr)(e,r.dataKey);if(Array.isArray(o)?[n,i]=o:n=i=o,(0,nn.H)(n)&&(0,nn.H)(i))return[t-n,t+i]}))}(e,l,a)}})).filter(Boolean):(null==t?void 0:t.dataKey)!=null?e.map(e=>({value:(0,ne.kr)(e,t.dataKey),errorDomain:[]})):e.map(e=>({value:e,errorDomain:[]})),ii=(0,f.Mz)(n5,nH,nQ,nj,ir);function io(e){var{value:t}=e;if((0,nr.vh)(t)||t instanceof Date)return t}var ia=e=>{var t;if(null==e||!("domain"in e))return nN;if(null!=e.domain)return e.domain;if(null!=e.ticks){if("number"===e.type){var r=n8(e.ticks);return[Math.min(...r),Math.max(...r)]}if("category"===e.type)return e.ticks.map(String)}return null!=(t=null==e?void 0:e.domain)?t:nN},il=function(){for(var e=arguments.length,t=Array(e),r=0;re.referenceElements.dots,is=(e,t,r)=>e.filter(e=>"extendDomain"===e.ifOverflow).filter(e=>"xAxis"===t?e.xAxisId===r:e.yAxisId===r),ic=(0,f.Mz)([iu,nj,nT],is),id=e=>e.referenceElements.areas,ip=(0,f.Mz)([id,nj,nT],is),ih=e=>e.referenceElements.lines,ig=(0,f.Mz)([ih,nj,nT],is),iv=(e,t)=>{var r=n8(e.map(e=>"xAxis"===t?e.x:e.y));if(0!==r.length)return[Math.min(...r),Math.max(...r)]},im=(0,f.Mz)(ic,nj,iv),iy=(e,t)=>{var r=n8(e.flatMap(e=>["xAxis"===t?e.x1:e.y1,"xAxis"===t?e.x2:e.y2]));if(0!==r.length)return[Math.min(...r),Math.max(...r)]},ib=(0,f.Mz)([ip,nj],iy),iw=(e,t)=>{var r=n8(e.map(e=>"xAxis"===t?e.x:e.y));if(0!==r.length)return[Math.min(...r),Math.max(...r)]},ix=(0,f.Mz)(ig,nj,iw),iS=(0,f.Mz)(im,ix,ib,(e,t,r)=>il(e,r,t)),iO=(0,f.Mz)([nH],ia),iE=(e,t,r,n,i)=>{var o=function(e,t){if(t&&"function"!=typeof e&&Array.isArray(e)&&2===e.length){var r,n,[i,o]=e;if((0,nn.H)(i))r=i;else if("function"==typeof i)return;if((0,nn.H)(o))n=o;else if("function"==typeof o)return;var a=[r,n];if(ni(a))return a}}(t,e.allowDataOverflow);return null!=o?o:function(e,t,r){if(r||null!=t){if("function"==typeof e&&null!=t)try{var n=e(t,r);if(ni(n))return no(n,t,r)}catch(e){}if(Array.isArray(e)&&2===e.length){var i,o,[a,l]=e;if("auto"===a)null!=t&&(i=Math.min(...t));else if((0,nr.Et)(a))i=a;else if("function"==typeof a)try{null!=t&&(i=a(null==t?void 0:t[0]))}catch(e){}else if("string"==typeof a&&ne.IH.test(a)){var u=ne.IH.exec(a);if(null==u||null==t)i=void 0;else{var s=+u[1];i=t[0]-s}}else i=null==t?void 0:t[0];if("auto"===l)null!=t&&(o=Math.max(...t));else if((0,nr.Et)(l))o=l;else if("function"==typeof l)try{null!=t&&(o=l(null==t?void 0:t[1]))}catch(e){}else if("string"==typeof l&&ne.qx.test(l)){var c=ne.qx.exec(l);if(null==c||null==t)o=void 0;else{var f=+c[1];o=t[1]+f}}else o=null==t?void 0:t[1];var d=[i,o];if(ni(d))return null==t?d:no(d,t,r)}}}(t,il(r,i,(e=>{var t=n8(e.flatMap(e=>[e.value,e.errorDomain]).flat(1));if(0!==t.length)return[Math.min(...t),Math.max(...t)]})(n)),e.allowDataOverflow)},iC=(0,f.Mz)([nH,iO,it,ii,iS],iE),iM=[0,1],ik=(e,t,r,n,i,o,a)=>{if(null!=e&&null!=r&&0!==r.length){var{dataKey:l,type:u}=e,s=(0,ne._L)(t,o);return s&&null==l?p()(0,r.length):"category"===u?((e,t,r)=>{var n=e.map(io).filter(e=>null!=e);return r&&(null==t.dataKey||t.allowDuplicatedCategory&&(0,nr.CG)(n))?p()(0,e.length):t.allowDuplicatedCategory?n:Array.from(new Set(n))})(n,e,s):"expand"===i?iM:a}},i_=(0,f.Mz)([nH,r7.fz,n5,n4,nP.eC,nj,iC],ik),iP=(e,t,r,n,i)=>{if(null!=e){var{scale:o,type:a}=e;if("auto"===o)return"radial"===t&&"radiusAxis"===i?"band":"radial"===t&&"angleAxis"===i?"linear":"category"===a&&n&&(n.indexOf("LineChart")>=0||n.indexOf("AreaChart")>=0||n.indexOf("ComposedChart")>=0&&!r)?"point":"category"===a?"band":"linear";if("string"==typeof o){var l="scale".concat((0,nr.Zb)(o));return l in c?l:"point"}}},iA=(0,f.Mz)([nH,r7.fz,nZ,nP.iO,nj],iP);function ij(e,t,r,n){if(null!=r&&null!=n){if("function"==typeof e.scale)return e.scale.copy().domain(r).range(n);var i=function(e){if(null!=e){if(e in c)return c[e]();var t="scale".concat((0,nr.Zb)(e));if(t in c)return c[t]()}}(t);if(null!=i){var o=i.domain(r).range(n);return(0,ne.YB)(o),o}}}var iT=(e,t,r)=>{var n=ia(t);if("auto"===r||"linear"===r){if(null!=t&&t.tickCount&&Array.isArray(n)&&("auto"===n[0]||"auto"===n[1])&&ni(e))return nO(e,t.tickCount,t.allowDecimals);if(null!=t&&t.tickCount&&"number"===t.type&&ni(e))return nE(e,t.tickCount,t.allowDecimals)}},iR=(0,f.Mz)([i_,nG,iA],iT),iz=(e,t,r,n)=>"angleAxis"!==n&&(null==e?void 0:e.type)==="number"&&ni(t)&&Array.isArray(r)&&r.length>0?[Math.min(t[0],r[0]),Math.max(t[1],r[r.length-1])]:t,iD=(0,f.Mz)([nH,i_,iR,nj],iz),iI=(0,f.Mz)(n4,nH,(e,t)=>{if(t&&"number"===t.type){var r=1/0,n=Array.from(n8(e.map(e=>e.value))).sort((e,t)=>e-t);if(n.length<2)return 1/0;var i=n[n.length-1]-n[0];if(0===i)return 1/0;for(var o=0;on,(e,t,r,n,i)=>{if(!(0,nn.H)(e))return 0;var o="vertical"===t?n.height:n.width;if("gap"===i)return e*o/2;if("no-gap"===i){var a=(0,nr.F4)(r,e*o),l=e*o/2;return l-a-(l-a)/o*a}return 0}),iL=(0,f.Mz)(nF,(e,t)=>{var r=nF(e,t);return null==r||"string"!=typeof r.padding?0:iN(e,"xAxis",t,r.padding)},(e,t)=>{if(null==e)return{left:0,right:0};var r,n,{padding:i}=e;return"string"==typeof i?{left:t,right:t}:{left:(null!=(r=i.left)?r:0)+t,right:(null!=(n=i.right)?n:0)+t}}),iF=(0,f.Mz)(nB,(e,t)=>{var r=nB(e,t);return null==r||"string"!=typeof r.padding?0:iN(e,"yAxis",t,r.padding)},(e,t)=>{if(null==e)return{top:0,bottom:0};var r,n,{padding:i}=e;return"string"==typeof i?{top:t,bottom:t}:{top:(null!=(r=i.top)?r:0)+t,bottom:(null!=(n=i.bottom)?n:0)+t}}),i$=(0,f.Mz)([nk.HZ,iL,n_.U,n_.C,(e,t,r)=>r],(e,t,r,n,i)=>{var{padding:o}=n;return i?[o.left,r.width-o.right]:[e.left+t.left,e.left+e.width-t.right]}),iB=(0,f.Mz)([nk.HZ,r7.fz,iF,n_.U,n_.C,(e,t,r)=>r],(e,t,r,n,i,o)=>{var{padding:a}=i;return o?[n.height-a.bottom,a.top]:"horizontal"===t?[e.top+e.height-r.bottom,e.top+r.top]:[e.top+r.top,e.top+e.height-r.bottom]}),iV=(e,t,r,n)=>{var i;switch(t){case"xAxis":return i$(e,r,n);case"yAxis":return iB(e,r,n);case"zAxis":return null==(i=nU(e,r))?void 0:i.range;case"angleAxis":return(0,nA.Cv)(e);case"radiusAxis":return(0,nA.Dc)(e,r);default:return}},iU=(0,f.Mz)([nH,iV],nR.I),iH=(0,f.Mz)([nH,iA,iD,iU],ij);function iG(e,t){return e.idt.id)}(0,f.Mz)(nX,nj,(e,t)=>e.flatMap(e=>{var t;return null!=(t=e.errorBars)?t:[]}).filter(e=>n3(t,e)));var iZ=(e,t)=>t,iW=(e,t,r)=>r,iK=(0,f.Mz)(nM.h,iZ,iW,(e,t,r)=>e.filter(e=>e.orientation===t).filter(e=>e.mirror===r).sort(iG)),iq=(0,f.Mz)(nM.W,iZ,iW,(e,t,r)=>e.filter(e=>e.orientation===t).filter(e=>e.mirror===r).sort(iG)),iY=(e,t)=>({width:e.width,height:t.height});(0,f.Mz)(nk.HZ,nF,iY),(0,f.Mz)(nC.A$,nk.HZ,iK,iZ,iW,(e,t,r,n,i)=>{var o,a={};return r.forEach(r=>{var l=iY(t,r);null==o&&(o=((e,t,r)=>{switch(t){case"top":return e.top;case"bottom":return r-e.bottom;default:return 0}})(t,n,e));var u="top"===n&&!i||"bottom"===n&&i;a[r.id]=o-Number(u)*l.height,o+=(u?-1:1)*l.height}),a}),(0,f.Mz)(nC.Lp,nk.HZ,iq,iZ,iW,(e,t,r,n,i)=>{var o,a={};return r.forEach(r=>{var l=((e,t)=>({width:"number"==typeof t.width?t.width:nz.tQ,height:e.height}))(t,r);null==o&&(o=((e,t,r)=>{switch(t){case"left":return e.left;case"right":return r-e.right;default:return 0}})(t,n,e));var u="left"===n&&!i||"right"===n&&i;a[r.id]=o-Number(u)*l.width,o+=(u?-1:1)*l.width}),a}),(0,f.Mz)(nk.HZ,nB,(e,t)=>({width:"number"==typeof t.width?t.width:nz.tQ,height:e.height}));var iX=(e,t,r,n)=>{if(null!=r){var{allowDuplicatedCategory:i,type:o,dataKey:a}=r,l=(0,ne._L)(e,n),u=t.map(e=>e.value);if(a&&l&&"category"===o&&i&&(0,nr.CG)(u))return u}},iJ=(0,f.Mz)([r7.fz,n4,nH,nj],iX),iQ=(e,t,r,n)=>{if(null!=r&&null!=r.dataKey){var{type:i,scale:o}=r;if((0,ne._L)(e,n)&&("number"===i||"auto"!==o))return t.map(e=>e.value)}},i0=(0,f.Mz)([r7.fz,n4,nG,nj],iQ),i1=(0,f.Mz)([r7.fz,(e,t,r)=>{switch(t){case"xAxis":return nF(e,r);case"yAxis":return nB(e,r);default:throw Error("Unexpected axis type: ".concat(t))}},iA,iH,iJ,i0,iV,iR,nj],(e,t,r,n,i,o,a,l,u)=>{if(null==t)return null;var s=(0,ne._L)(e,u);return{angle:t.angle,interval:t.interval,minTickGap:t.minTickGap,orientation:t.orientation,tick:t.tick,tickCount:t.tickCount,tickFormatter:t.tickFormatter,ticks:t.ticks,type:t.type,unit:t.unit,axisType:u,categoricalDomain:o,duplicateDomain:i,isCategorical:s,niceTicks:l,range:a,realScaleType:r,scale:n}});(0,f.Mz)([r7.fz,nG,iA,iH,iR,iV,iJ,i0,nj],(e,t,r,n,i,o,a,l,u)=>{if(null!=t&&null!=n){var s=(0,ne._L)(e,u),{type:c,ticks:f,tickCount:d}=t,p="scaleBand"===r&&"function"==typeof n.bandwidth?n.bandwidth()/2:2,h="category"===c&&n.bandwidth?n.bandwidth()/p:0;h="angleAxis"===u&&null!=o&&o.length>=2?2*(0,nr.sA)(o[0]-o[1])*h:h;var g=f||i;return g?g.map((e,t)=>({index:t,coordinate:n(a?a.indexOf(e):e)+h,value:e,offset:h})).filter(e=>!(0,nr.M8)(e.coordinate)):s&&l?l.map((e,t)=>({coordinate:n(e)+h,value:e,index:t,offset:h})):n.ticks?n.ticks(d).map(e=>({coordinate:n(e)+h,value:e,offset:h})):n.domain().map((e,t)=>({coordinate:n(e)+h,value:a?a[e]:e,index:t,offset:h}))}});var i2=(0,f.Mz)([r7.fz,nG,iH,iV,iJ,i0,nj],(e,t,r,n,i,o,a)=>{if(null!=t&&null!=r&&null!=n&&n[0]!==n[1]){var l=(0,ne._L)(e,a),{tickCount:u}=t,s=0;return(s="angleAxis"===a&&(null==n?void 0:n.length)>=2?2*(0,nr.sA)(n[0]-n[1])*s:s,l&&o)?o.map((e,t)=>({coordinate:r(e)+s,value:e,index:t,offset:s})):r.ticks?r.ticks(u).map(e=>({coordinate:r(e)+s,value:e,offset:s})):r.domain().map((e,t)=>({coordinate:r(e)+s,value:i?i[e]:e,index:t,offset:s}))}}),i5=(0,f.Mz)(nH,iH,(e,t)=>{if(null!=e&&null!=t)return nI(nI({},e),{},{scale:t})}),i6=(0,f.Mz)([nH,iA,i_,iU],ij);(0,f.Mz)((e,t,r)=>nU(e,r),i6,(e,t)=>{if(null!=e&&null!=t)return nI(nI({},e),{},{scale:t})});var i4=(0,f.Mz)([r7.fz,nM.h,nM.W],(e,t,r)=>{switch(e){case"horizontal":return t.some(e=>e.reversed)?"right-to-left":"left-to-right";case"vertical":return r.some(e=>e.reversed)?"bottom-to-top":"top-to-bottom";case"centric":case"radial":return"left-to-right";default:return}})},2188:(e,t,r)=>{e.exports=r(5252).isEqual},2194:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(921);t.property=function(e){return function(t){return n.get(t,e)}}},2248:(e,t,r)=>{"use strict";r.d(t,{Vi:()=>c,ZF:()=>s,g5:()=>u,iZ:()=>p});var n=r(5710),i=r(4532),o=(0,n.Z0)({name:"graphicalItems",initialState:{countOfBars:0,cartesianItems:[],polarItems:[]},reducers:{addBar(e){e.countOfBars+=1},removeBar(e){e.countOfBars-=1},addCartesianGraphicalItem(e,t){e.cartesianItems.push((0,i.h4)(t.payload))},replaceCartesianGraphicalItem(e,t){var{prev:r,next:n}=t.payload,o=(0,i.ss)(e).cartesianItems.indexOf((0,i.h4)(r));o>-1&&(e.cartesianItems[o]=(0,i.h4)(n))},removeCartesianGraphicalItem(e,t){var r=(0,i.ss)(e).cartesianItems.indexOf((0,i.h4)(t.payload));r>-1&&e.cartesianItems.splice(r,1)},addPolarGraphicalItem(e,t){e.polarItems.push((0,i.h4)(t.payload))},removePolarGraphicalItem(e,t){var r=(0,i.ss)(e).polarItems.indexOf((0,i.h4)(t.payload));r>-1&&e.polarItems.splice(r,1)}}}),{addBar:a,removeBar:l,addCartesianGraphicalItem:u,replaceCartesianGraphicalItem:s,removeCartesianGraphicalItem:c,addPolarGraphicalItem:f,removePolarGraphicalItem:d}=o.actions,p=o.reducer},2278:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevrons-left",[["path",{d:"m11 17-5-5 5-5",key:"13zhaf"}],["path",{d:"m18 17-5-5 5-5",key:"h8a8et"}]])},2319:(e,t,r)=>{"use strict";r.d(t,{s:()=>et});var n=r(2115),i=r(7650),o=r(8060),a=r(2596),l=r(2790);let u=Math.cos,s=Math.sin,c=Math.sqrt,f=Math.PI,d=2*f,p={draw(e,t){let r=c(t/f);e.moveTo(r,0),e.arc(0,0,r,0,d)}},h=c(1/3),g=2*h,v=s(f/10)/s(7*f/10),m=s(d/10)*v,y=-u(d/10)*v,b=c(3),w=c(3)/2,x=1/c(12),S=(x/2+1)*3;var O=r(5654),E=r(1847);c(3),c(3);var C=r(788),M=r(6377),k=["type","size","sizeType"];function _(){return(_=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{type:t="circle",size:r=64,sizeType:i="area"}=e,o=A(A({},function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{var e=j["symbol".concat((0,M.Zb)(t))]||p;return(function(e,t){let r=null,n=(0,E.i)(i);function i(){let i;if(r||(r=i=n()),e.apply(this,arguments).draw(r,+t.apply(this,arguments)),i)return r=null,i+""||null}return e="function"==typeof e?e:(0,O.A)(e||p),t="function"==typeof t?t:(0,O.A)(void 0===t?64:+t),i.type=function(t){return arguments.length?(e="function"==typeof t?t:(0,O.A)(t),i):e},i.size=function(e){return arguments.length?(t="function"==typeof e?e:(0,O.A)(+e),i):t},i.context=function(e){return arguments.length?(r=null==e?null:e,i):r},i})().type(e).size(((e,t,r)=>{if("area"===t)return e;switch(r){case"cross":return 5*e*e/9;case"diamond":return .5*e*e/Math.sqrt(3);case"square":return e*e;case"star":var n=18*T;return 1.25*e*e*(Math.tan(n)-Math.tan(2*n)*Math.tan(n)**2);case"triangle":return Math.sqrt(3)*e*e/4;case"wye":return(21-10*Math.sqrt(3))*e*e/8;default:return Math.PI*e*e/4}})(r,i,t))()})()})):null};R.registerSymbol=(e,t)=>{j["symbol".concat((0,M.Zb)(e))]=t};var z=r(3597);function D(){return(D=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var d=e.formatter||i,p=(0,a.$)({"recharts-legend-item":!0,["legend-item-".concat(r)]:!0,inactive:e.inactive});if("none"===e.type)return null;var h=e.inactive?o:e.color,g=d?d(e.value,e,r):e.value;return n.createElement("li",D({className:p,style:c,key:"legend-item-".concat(r)},(0,z.XC)(this.props,e,r)),n.createElement(l.u,{width:t,height:t,viewBox:s,style:f,"aria-label":"".concat(g," legend icon")},this.renderIcon(e,u)),n.createElement("span",{className:"recharts-legend-item-text",style:{color:h}},g))})}render(){var{payload:e,layout:t,align:r}=this.props;return e&&e.length?n.createElement("ul",{className:"recharts-default-legend",style:{padding:0,margin:0,textAlign:"horizontal"===t?r:"left"}},this.renderItems()):null}}N(L,"displayName","Legend"),N(L,"defaultProps",{align:"center",iconSize:14,inactiveColor:"#ccc",layout:"horizontal",verticalAlign:"middle"});var F=r(2494),$=r(1971),B=r(5803),V=r(7918),U=r(7238),H=r(2634),G=["contextPayload"];function Z(){return(Z=Object.assign?Object.assign.bind():function(e){for(var t=1;t{t((0,H.h1)(e))},[t,e]),null}function Q(e){var t=(0,$.j)();return(0,n.useEffect)(()=>(t((0,H.hx)(e)),()=>{t((0,H.hx)({width:0,height:0}))}),[t,e]),null}function ee(e){var t=(0,$.G)(B.g0),r=(0,o.M)(),a=(0,U.Kp)(),{width:l,height:u,wrapperStyle:s,portal:c}=e,[f,d]=(0,V.V)([t]),p=(0,U.yi)(),h=(0,U.rY)(),g=p-(a.left||0)-(a.right||0),v=et.getWidthOrHeight(e.layout,u,l,g),m=c?s:K(K({position:"absolute",width:(null==v?void 0:v.width)||l||"auto",height:(null==v?void 0:v.height)||u||"auto"},function(e,t,r,n,i,o){var a,l,{layout:u,align:s,verticalAlign:c}=t;return e&&(void 0!==e.left&&null!==e.left||void 0!==e.right&&null!==e.right)||(a="center"===s&&"vertical"===u?{left:((n||0)-o.width)/2}:"right"===s?{right:r&&r.right||0}:{left:r&&r.left||0}),e&&(void 0!==e.top&&null!==e.top||void 0!==e.bottom&&null!==e.bottom)||(l="middle"===c?{top:((i||0)-o.height)/2}:"bottom"===c?{bottom:r&&r.bottom||0}:{top:r&&r.top||0}),K(K({},a),l)}(s,e,a,p,h,f)),s),y=null!=c?c:r;if(null==y)return null;var b=n.createElement("div",{className:"recharts-legend-wrapper",style:m,ref:d},n.createElement(J,{layout:e.layout,align:e.align,verticalAlign:e.verticalAlign,itemSorter:e.itemSorter}),n.createElement(Q,{width:f.width,height:f.height}),n.createElement(X,Z({},e,v,{margin:a,chartWidth:p,chartHeight:h,contextPayload:t})));return(0,i.createPortal)(b,y)}class et extends n.PureComponent{static getWidthOrHeight(e,t,r,n){return"vertical"===e&&(0,M.Et)(t)?{height:t}:"horizontal"===e?{width:r||n}:null}render(){return n.createElement(ee,this.props)}}q(et,"displayName","Legend"),q(et,"defaultProps",{align:"center",iconSize:14,itemSorter:"value",layout:"horizontal",verticalAlign:"bottom"})},2348:(e,t,r)=>{"use strict";r.d(t,{W:()=>u});var n=r(2115),i=r(2596),o=r(788),a=["children","className"];function l(){return(l=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:r,className:u}=e,s=function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevron-left",[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]])},2384:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isObject=function(e){return null!==e&&("object"==typeof e||"function"==typeof e)}},2429:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(4117);t.cloneDeep=function(e){return n.cloneDeepWithImpl(e,void 0,e,new Map,void 0)}},2434:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(7064),i=r(5998),o=r(4373);t.sortBy=function(e,...t){let r=t.length;return r>1&&o.isIterateeCall(e,t[0],t[1])?t=[]:r>2&&o.isIterateeCall(t[0],t[1],t[2])&&(t=[t[0]]),n.orderBy(e,i.flatten(t),["asc"])}},2436:(e,t,r)=>{"use strict";var n=r(2115),i="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},o=n.useState,a=n.useEffect,l=n.useLayoutEffect,u=n.useDebugValue;function s(e){var t=e.getSnapshot;e=e.value;try{var r=t();return!i(e,r)}catch(e){return!0}}var c="undefined"==typeof window||void 0===window.document||void 0===window.document.createElement?function(e,t){return t()}:function(e,t){var r=t(),n=o({inst:{value:r,getSnapshot:t}}),i=n[0].inst,c=n[1];return l(function(){i.value=r,i.getSnapshot=t,s(i)&&c({inst:i})},[e,r,t]),a(function(){return s(i)&&c({inst:i}),e(function(){s(i)&&c({inst:i})})},[e]),u(r),r};t.useSyncExternalStore=void 0!==n.useSyncExternalStore?n.useSyncExternalStore:c},2465:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.identity=function(e){return e}},2493:(e,t,r)=>{"use strict";r.d(t,{q7:()=>P,bL:()=>_});var n=r(2115),i=r(6081),o=r(3655),a=r(9196),l=r(5185),u=r(5845),s=r(5155),c="Toggle",f=n.forwardRef((e,t)=>{let{pressed:r,defaultPressed:n,onPressedChange:i,...a}=e,[f,d]=(0,u.i)({prop:r,onChange:i,defaultProp:null!=n&&n,caller:c});return(0,s.jsx)(o.sG.button,{type:"button","aria-pressed":f,"data-state":f?"on":"off","data-disabled":e.disabled?"":void 0,...a,ref:t,onClick:(0,l.m)(e.onClick,()=>{e.disabled||d(!f)})})});f.displayName=c;var d=r(4315),p="ToggleGroup",[h,g]=(0,i.A)(p,[a.RG]),v=(0,a.RG)(),m=n.forwardRef((e,t)=>{let{type:r,...n}=e;if("single"===r)return(0,s.jsx)(w,{...n,ref:t});if("multiple"===r)return(0,s.jsx)(x,{...n,ref:t});throw Error("Missing prop `type` expected on `".concat(p,"`"))});m.displayName=p;var[y,b]=h(p),w=n.forwardRef((e,t)=>{let{value:r,defaultValue:i,onValueChange:o=()=>{},...a}=e,[l,c]=(0,u.i)({prop:r,defaultProp:null!=i?i:"",onChange:o,caller:p});return(0,s.jsx)(y,{scope:e.__scopeToggleGroup,type:"single",value:n.useMemo(()=>l?[l]:[],[l]),onItemActivate:c,onItemDeactivate:n.useCallback(()=>c(""),[c]),children:(0,s.jsx)(E,{...a,ref:t})})}),x=n.forwardRef((e,t)=>{let{value:r,defaultValue:i,onValueChange:o=()=>{},...a}=e,[l,c]=(0,u.i)({prop:r,defaultProp:null!=i?i:[],onChange:o,caller:p}),f=n.useCallback(e=>c(function(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return[...t,e]}),[c]),d=n.useCallback(e=>c(function(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return t.filter(t=>t!==e)}),[c]);return(0,s.jsx)(y,{scope:e.__scopeToggleGroup,type:"multiple",value:l,onItemActivate:f,onItemDeactivate:d,children:(0,s.jsx)(E,{...a,ref:t})})});m.displayName=p;var[S,O]=h(p),E=n.forwardRef((e,t)=>{let{__scopeToggleGroup:r,disabled:n=!1,rovingFocus:i=!0,orientation:l,dir:u,loop:c=!0,...f}=e,p=v(r),h=(0,d.jH)(u),g={role:"group",dir:h,...f};return(0,s.jsx)(S,{scope:r,rovingFocus:i,disabled:n,children:i?(0,s.jsx)(a.bL,{asChild:!0,...p,orientation:l,dir:h,loop:c,children:(0,s.jsx)(o.sG.div,{...g,ref:t})}):(0,s.jsx)(o.sG.div,{...g,ref:t})})}),C="ToggleGroupItem",M=n.forwardRef((e,t)=>{let r=b(C,e.__scopeToggleGroup),i=O(C,e.__scopeToggleGroup),o=v(e.__scopeToggleGroup),l=r.value.includes(e.value),u=i.disabled||e.disabled,c={...e,pressed:l,disabled:u},f=n.useRef(null);return i.rovingFocus?(0,s.jsx)(a.q7,{asChild:!0,...o,focusable:!u,active:l,ref:f,children:(0,s.jsx)(k,{...c,ref:t})}):(0,s.jsx)(k,{...c,ref:t})});M.displayName=C;var k=n.forwardRef((e,t)=>{let{__scopeToggleGroup:r,value:n,...i}=e,o=b(C,r),a={role:"radio","aria-checked":e.pressed,"aria-pressed":void 0},l="single"===o.type?a:void 0;return(0,s.jsx)(f,{...l,...i,ref:t,onPressedChange:e=>{e?o.onItemActivate(n):o.onItemDeactivate(n)}})}),_=m,P=M},2494:(e,t,r)=>{"use strict";r.d(t,{s:()=>o});var n=r(512),i=r.n(n);function o(e,t,r){return!0===t?i()(e,r):"function"==typeof t?i()(e,t):e}},2520:(e,t,r)=>{"use strict";var n=r(9641).Buffer;Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let i=r(1147),o=r(8221),a=r(5160),l=r(2721),u=r(3616);t.isEqualWith=function(e,t,r){return function e(t,r,s,c,f,d,p){let h=p(t,r,s,c,f,d);if(void 0!==h)return h;if(typeof t==typeof r)switch(typeof t){case"bigint":case"string":case"boolean":case"symbol":case"undefined":case"function":return t===r;case"number":return t===r||Object.is(t,r)}return function t(r,s,c,f){if(Object.is(r,s))return!0;let d=a.getTag(r),p=a.getTag(s);if(d===l.argumentsTag&&(d=l.objectTag),p===l.argumentsTag&&(p=l.objectTag),d!==p)return!1;switch(d){case l.stringTag:return r.toString()===s.toString();case l.numberTag:{let e=r.valueOf(),t=s.valueOf();return u.eq(e,t)}case l.booleanTag:case l.dateTag:case l.symbolTag:return Object.is(r.valueOf(),s.valueOf());case l.regexpTag:return r.source===s.source&&r.flags===s.flags;case l.functionTag:return r===s}let h=(c=c??new Map).get(r),g=c.get(s);if(null!=h&&null!=g)return h===s;c.set(r,s),c.set(s,r);try{switch(d){case l.mapTag:if(r.size!==s.size)return!1;for(let[t,n]of r.entries())if(!s.has(t)||!e(n,s.get(t),t,r,s,c,f))return!1;return!0;case l.setTag:{if(r.size!==s.size)return!1;let t=Array.from(r.values()),n=Array.from(s.values());for(let i=0;ie(o,t,void 0,r,s,c,f));if(-1===a)return!1;n.splice(a,1)}return!0}case l.arrayTag:case l.uint8ArrayTag:case l.uint8ClampedArrayTag:case l.uint16ArrayTag:case l.uint32ArrayTag:case l.bigUint64ArrayTag:case l.int8ArrayTag:case l.int16ArrayTag:case l.int32ArrayTag:case l.bigInt64ArrayTag:case l.float32ArrayTag:case l.float64ArrayTag:if(void 0!==n&&n.isBuffer(r)!==n.isBuffer(s)||r.length!==s.length)return!1;for(let t=0;t{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("trash-2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]])},2589:(e,t,r)=>{"use strict";r.d(t,{A$:()=>i,HK:()=>a,Lp:()=>n,et:()=>o});var n=e=>e.layout.width,i=e=>e.layout.height,o=e=>e.layout.scale,a=e=>e.layout.margin},2596:(e,t,r)=>{"use strict";function n(){for(var e,t,r=0,n="",i=arguments.length;rn})},2634:(e,t,r)=>{"use strict";r.d(t,{CU:()=>c,Lx:()=>u,h1:()=>l,hx:()=>a,u3:()=>s});var n=r(5710),i=r(4532),o=(0,n.Z0)({name:"legend",initialState:{settings:{layout:"horizontal",align:"center",verticalAlign:"middle",itemSorter:"value"},size:{width:0,height:0},payload:[]},reducers:{setLegendSize(e,t){e.size.width=t.payload.width,e.size.height=t.payload.height},setLegendSettings(e,t){e.settings.align=t.payload.align,e.settings.layout=t.payload.layout,e.settings.verticalAlign=t.payload.verticalAlign,e.settings.itemSorter=t.payload.itemSorter},addLegendPayload(e,t){e.payload.push((0,i.h4)(t.payload))},removeLegendPayload(e,t){var r=(0,i.ss)(e).payload.indexOf((0,i.h4)(t.payload));r>-1&&e.payload.splice(r,1)}}}),{setLegendSize:a,setLegendSettings:l,addLegendPayload:u,removeLegendPayload:s}=o.actions,c=o.reducer},2661:e=>{"use strict";var t=Object.prototype.hasOwnProperty,r="~";function n(){}function i(e,t,r){this.fn=e,this.context=t,this.once=r||!1}function o(e,t,n,o,a){if("function"!=typeof n)throw TypeError("The listener must be a function");var l=new i(n,o||e,a),u=r?r+t:t;return e._events[u]?e._events[u].fn?e._events[u]=[e._events[u],l]:e._events[u].push(l):(e._events[u]=l,e._eventsCount++),e}function a(e,t){0==--e._eventsCount?e._events=new n:delete e._events[t]}function l(){this._events=new n,this._eventsCount=0}Object.create&&(n.prototype=Object.create(null),new n().__proto__||(r=!1)),l.prototype.eventNames=function(){var e,n,i=[];if(0===this._eventsCount)return i;for(n in e=this._events)t.call(e,n)&&i.push(r?n.slice(1):n);return Object.getOwnPropertySymbols?i.concat(Object.getOwnPropertySymbols(e)):i},l.prototype.listeners=function(e){var t=r?r+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var i=0,o=n.length,a=Array(o);i{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(668);t.toNumber=function(e){return n.isSymbol(e)?NaN:Number(e)}},2712:(e,t,r)=>{"use strict";r.d(t,{N:()=>i});var n=r(2115),i=globalThis?.document?n.useLayoutEffect:()=>{}},2721:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.argumentsTag="[object Arguments]",t.arrayBufferTag="[object ArrayBuffer]",t.arrayTag="[object Array]",t.bigInt64ArrayTag="[object BigInt64Array]",t.bigUint64ArrayTag="[object BigUint64Array]",t.booleanTag="[object Boolean]",t.dataViewTag="[object DataView]",t.dateTag="[object Date]",t.errorTag="[object Error]",t.float32ArrayTag="[object Float32Array]",t.float64ArrayTag="[object Float64Array]",t.functionTag="[object Function]",t.int16ArrayTag="[object Int16Array]",t.int32ArrayTag="[object Int32Array]",t.int8ArrayTag="[object Int8Array]",t.mapTag="[object Map]",t.numberTag="[object Number]",t.objectTag="[object Object]",t.regexpTag="[object RegExp]",t.setTag="[object Set]",t.stringTag="[object String]",t.symbolTag="[object Symbol]",t.uint16ArrayTag="[object Uint16Array]",t.uint32ArrayTag="[object Uint32Array]",t.uint8ArrayTag="[object Uint8Array]",t.uint8ClampedArrayTag="[object Uint8ClampedArray]"},2744:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8132),i=r(2384),o=r(6633),a=r(3616);function l(e,t,r,n){if(t===e)return!0;switch(typeof t){case"object":return function(e,t,r,n){if(null==t)return!0;if(Array.isArray(t))return u(e,t,r,n);if(t instanceof Map){var i=e,a=t,l=r,c=n;if(0===a.size)return!0;if(!(i instanceof Map))return!1;for(let[e,t]of a.entries())if(!1===l(i.get(e),t,e,i,a,c))return!1;return!0}if(t instanceof Set)return s(e,t,r,n);let f=Object.keys(t);if(null==e)return 0===f.length;if(0===f.length)return!0;if(n&&n.has(t))return n.get(t)===e;n&&n.set(t,e);try{for(let i=0;i0)return l(e,{...t},r,n);return a.eq(e,t);default:if(!i.isObject(e))return a.eq(e,t);if("string"==typeof t)return""===t;return!0}}function u(e,t,r,n){if(0===t.length)return!0;if(!Array.isArray(e))return!1;let i=new Set;for(let o=0;o{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevrons-right",[["path",{d:"m6 17 5-5-5-5",key:"xnjwq"}],["path",{d:"m13 17 5-5-5-5",key:"17xmmf"}]])},2790:(e,t,r)=>{"use strict";r.d(t,{u:()=>u});var n=r(2115),i=r(2596),o=r(788),a=["children","width","height","viewBox","className","style","title","desc"];function l(){return(l=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:r,width:u,height:s,viewBox:c,className:f,style:d,title:p,desc:h}=e,g=function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8673);t.throttle=function(e,t=0,r={}){let{leading:i=!0,trailing:o=!0}=r;return n.debounce(e,t,{leading:i,maxWait:t,trailing:o})}},3052:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevron-right",[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]])},3205:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(4545),i=r(8412),o=r(177),a=r(4072);t.has=function(e,t){let r;if(0===(r=Array.isArray(t)?t:"string"==typeof t&&n.isDeepKey(t)&&e?.[t]==null?a.toPath(t):[t]).length)return!1;let l=e;for(let e=0;e{"use strict";function n(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function i(e,t){var r=function(e){for(var t=1;t(void 0===e[r]&&void 0!==t[r]&&(e[r]=t[r]),e),r)}r.d(t,{e:()=>i})},3406:(e,t,r)=>{"use strict";r.d(t,{d:()=>et});var n=r(2115),i=r(675),o=r(6377),a=r(788),l=r(9827),u=r(6605),s=r(1643);class c{static create(e){return new c(e)}get domain(){return this.scale.domain}get range(){return this.scale.range}get rangeMin(){return this.range()[0]}get rangeMax(){return this.range()[1]}get bandwidth(){return this.scale.bandwidth}apply(e){var{bandAware:t,position:r}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(void 0!==e){if(r)switch(r){case"start":default:return this.scale(e);case"middle":var n=this.bandwidth?this.bandwidth()/2:0;return this.scale(e)+n;case"end":var i=this.bandwidth?this.bandwidth():0;return this.scale(e)+i}if(t){var o=this.bandwidth?this.bandwidth()/2:0;return this.scale(e)+o}return this.scale(e)}}isInRange(e){var t=this.range(),r=t[0],n=t[t.length-1];return r<=n?e>=r&&e<=n:e>=n&&e<=r}constructor(e){this.scale=e}}!function(e,t,r){var n;(t="symbol"==typeof(n=function(e,t){if("object"!=typeof e||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var n=r.call(e,t||"default");if("object"!=typeof n)return n;throw TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(t,"string"))?n:n+"")in e?Object.defineProperty(e,t,{value:1e-4,enumerable:!0,configurable:!0,writable:!0}):e[t]=1e-4}(c,"EPS",1e-4);var f=function(e){var{width:t,height:r}=e,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,i=(n%180+180)%180*Math.PI/180,o=Math.atan(r/t);return Math.abs(i>o&&ie*i)return!1;var o=r();return e*(t-e*o/2-n)>=0&&e*(t+e*o/2-i)<=0}function h(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function g(e){for(var t=1;t{var i,o="function"==typeof m?m(e.value,n):e.value;return"width"===w?(i=(0,u.P)(o,{fontSize:t,letterSpacing:r}),f({width:i.width+x.width,height:i.height+x.height},b)):(0,u.P)(o,{fontSize:t,letterSpacing:r})[w]},O=a.length>=2?(0,o.sA)(a[1].coordinate-a[0].coordinate):1,E=function(e,t,r){var n="width"===r,{x:i,y:o,width:a,height:l}=e;return 1===t?{start:n?i:o,end:n?i+a:o+l}:{start:n?i+a:o+l,end:n?i:o}}(l,O,w);return"equidistantPreserveStart"===v?function(e,t,r,n,i){for(var o,a=(n||[]).slice(),{start:l,end:u}=t,s=0,c=1,f=l;c<=a.length;)if(o=function(){var t,o=null==n?void 0:n[s];if(void 0===o)return{v:d(n,c)};var a=s,h=()=>(void 0===t&&(t=r(o,a)),t),g=o.coordinate,v=0===s||p(e,g,h,f,u);v||(s=0,f=l,c+=1),v&&(f=g+e*(h()/2+i),s+=c)}())return o.v;return[]}(O,E,S,a,c):("preserveStart"===v||"preserveStartEnd"===v?function(e,t,r,n,i,o){var a=(n||[]).slice(),l=a.length,{start:u,end:s}=t;if(o){var c=n[l-1],f=r(c,l-1),d=e*(c.coordinate+e*f/2-s);a[l-1]=c=g(g({},c),{},{tickCoord:d>0?c.coordinate-d*e:c.coordinate}),p(e,c.tickCoord,()=>f,u,s)&&(s=c.tickCoord-e*(f/2+i),a[l-1]=g(g({},c),{},{isShow:!0}))}for(var h=o?l-1:l,v=function(t){var n,o=a[t],l=()=>(void 0===n&&(n=r(o,t)),n);if(0===t){var c=e*(o.coordinate-e*l()/2-u);a[t]=o=g(g({},o),{},{tickCoord:c<0?o.coordinate-c*e:o.coordinate})}else a[t]=o=g(g({},o),{},{tickCoord:o.coordinate});p(e,o.tickCoord,l,u,s)&&(u=o.tickCoord+e*(l()/2+i),a[t]=g(g({},o),{},{isShow:!0}))},m=0;m(void 0===n&&(n=r(s,t)),n);if(t===a-1){var f=e*(s.coordinate+e*c()/2-u);o[t]=s=g(g({},s),{},{tickCoord:f>0?s.coordinate-f*e:s.coordinate})}else o[t]=s=g(g({},s),{},{tickCoord:s.coordinate});p(e,s.tickCoord,c,l,u)&&(u=s.tickCoord-e*(c()/2+i),o[t]=g(g({},s),{},{isShow:!0}))},c=a-1;c>=0;c--)s(c);return o}(O,E,S,a,c)).filter(e=>e.isShow)}var m=r(5672),y=r.n(m),b=r(2596);function w(e,t){for(var r in e)if(({}).hasOwnProperty.call(e,r)&&(!({}).hasOwnProperty.call(t,r)||e[r]!==t[r]))return!1;for(var n in t)if(({}).hasOwnProperty.call(t,n)&&!({}).hasOwnProperty.call(e,n))return!1;return!0}var x=r(2348),S=r(9095),O=r(379),E=r(3597),C=["viewBox"],M=["viewBox"];function k(){return(k=Object.assign?Object.assign.bind():function(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:[],{tickLine:i,stroke:o,tick:l,tickFormatter:u,unit:s}=this.props,c=v(P(P({},this.props),{},{ticks:r}),e,t),f=this.getTickTextAnchor(),d=this.getTickVerticalAnchor(),p=(0,a.J9)(this.props,!1),h=(0,a.J9)(l,!1),g=P(P({},p),{},{fill:"none"},(0,a.J9)(i,!1)),m=c.map((e,t)=>{var{line:r,tick:a}=this.getTickLineCoord(e),v=P(P(P(P({textAnchor:f,verticalAnchor:d},p),{},{stroke:"none",fill:o},h),a),{},{index:t,payload:e,visibleTicksCount:c.length,tickFormatter:u});return n.createElement(x.W,k({className:"recharts-cartesian-axis-tick",key:"tick-".concat(e.value,"-").concat(e.coordinate,"-").concat(e.tickCoord)},(0,E.XC)(this.props,e,t)),i&&n.createElement("line",k({},g,r,{className:(0,b.$)("recharts-cartesian-axis-tick-line",y()(i,"className"))})),l&&T.renderTickItem(l,v,"".concat("function"==typeof u?u(e.value,t):e.value).concat(s||"")))});return m.length>0?n.createElement("g",{className:"recharts-cartesian-axis-ticks"},m):null}render(){var{axisLine:e,width:t,height:r,className:i,hide:o}=this.props;if(o)return null;var{ticks:a}=this.props;return null!=t&&t<=0||null!=r&&r<=0?null:n.createElement(x.W,{className:(0,b.$)("recharts-cartesian-axis",i),ref:e=>{if(e){var t=e.getElementsByClassName("recharts-cartesian-axis-tick-value");this.tickRefs.current=Array.from(t);var r=t[0];if(r){var n=window.getComputedStyle(r).fontSize,i=window.getComputedStyle(r).letterSpacing;(n!==this.state.fontSize||i!==this.state.letterSpacing)&&this.setState({fontSize:window.getComputedStyle(r).fontSize,letterSpacing:window.getComputedStyle(r).letterSpacing})}}}},e&&this.renderAxisLine(),this.renderTicks(this.state.fontSize,this.state.letterSpacing,a),O.J.renderCallByParent(this.props))}constructor(e){super(e),this.tickRefs=n.createRef(),this.tickRefs.current=[],this.state={fontSize:"",letterSpacing:""}}}j(T,"displayName","CartesianAxis"),j(T,"defaultProps",{x:0,y:0,width:0,height:0,viewBox:{x:0,y:0,width:0,height:0},orientation:"bottom",ticks:[],stroke:"#666",tickLine:!0,axisLine:!0,tick:!0,mirror:!1,minTickGap:5,tickSize:6,tickMargin:2,interval:"preserveEnd"});var R=r(7238),z=r(2183),D=r(1971),I=r(1807),N=r(3389),L=["x1","y1","x2","y2","key"],F=["offset"],$=["xAxisId","yAxisId"],B=["xAxisId","yAxisId"];function V(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function U(e){for(var t=1;t{var{fill:t}=e;if(!t||"none"===t)return null;var{fillOpacity:r,x:i,y:o,width:a,height:l,ry:u}=e;return n.createElement("rect",{x:i,y:o,ry:u,width:a,height:l,stroke:"none",fill:t,fillOpacity:r,className:"recharts-cartesian-grid-bg"})};function W(e,t){var r;if(n.isValidElement(e))r=n.cloneElement(e,t);else if("function"==typeof e)r=e(t);else{var{x1:i,y1:o,x2:l,y2:u,key:s}=t,c=G(t,L),f=(0,a.J9)(c,!1),{offset:d}=f,p=G(f,F);r=n.createElement("line",H({},p,{x1:i,y1:o,x2:l,y2:u,fill:"none",key:s}))}return r}function K(e){var{x:t,width:r,horizontal:i=!0,horizontalPoints:o}=e;if(!i||!o||!o.length)return null;var{xAxisId:a,yAxisId:l}=e,u=G(e,$),s=o.map((e,n)=>W(i,U(U({},u),{},{x1:t,y1:e,x2:t+r,y2:e,key:"line-".concat(n),index:n})));return n.createElement("g",{className:"recharts-cartesian-grid-horizontal"},s)}function q(e){var{y:t,height:r,vertical:i=!0,verticalPoints:o}=e;if(!i||!o||!o.length)return null;var{xAxisId:a,yAxisId:l}=e,u=G(e,B),s=o.map((e,n)=>W(i,U(U({},u),{},{x1:e,y1:t,x2:e,y2:t+r,key:"line-".concat(n),index:n})));return n.createElement("g",{className:"recharts-cartesian-grid-vertical"},s)}function Y(e){var{horizontalFill:t,fillOpacity:r,x:i,y:o,width:a,height:l,horizontalPoints:u,horizontal:s=!0}=e;if(!s||!t||!t.length)return null;var c=u.map(e=>Math.round(e+o-o)).sort((e,t)=>e-t);o!==c[0]&&c.unshift(0);var f=c.map((e,u)=>{var s=c[u+1]?c[u+1]-e:o+l-e;if(s<=0)return null;var f=u%t.length;return n.createElement("rect",{key:"react-".concat(u),y:e,x:i,height:s,width:a,stroke:"none",fill:t[f],fillOpacity:r,className:"recharts-cartesian-grid-bg"})});return n.createElement("g",{className:"recharts-cartesian-gridstripes-horizontal"},f)}function X(e){var{vertical:t=!0,verticalFill:r,fillOpacity:i,x:o,y:a,width:l,height:u,verticalPoints:s}=e;if(!t||!r||!r.length)return null;var c=s.map(e=>Math.round(e+o-o)).sort((e,t)=>e-t);o!==c[0]&&c.unshift(0);var f=c.map((e,t)=>{var s=c[t+1]?c[t+1]-e:o+l-e;if(s<=0)return null;var f=t%r.length;return n.createElement("rect",{key:"react-".concat(t),x:e,y:a,width:s,height:u,stroke:"none",fill:r[f],fillOpacity:i,className:"recharts-cartesian-grid-bg"})});return n.createElement("g",{className:"recharts-cartesian-gridstripes-vertical"},f)}var J=(e,t)=>{var{xAxis:r,width:n,height:i,offset:o}=e;return(0,l.PW)(v(U(U(U({},T.defaultProps),r),{},{ticks:(0,l.Rh)(r,!0),viewBox:{x:0,y:0,width:n,height:i}})),o.left,o.left+o.width,t)},Q=(e,t)=>{var{yAxis:r,width:n,height:i,offset:o}=e;return(0,l.PW)(v(U(U(U({},T.defaultProps),r),{},{ticks:(0,l.Rh)(r,!0),viewBox:{x:0,y:0,width:n,height:i}})),o.top,o.top+o.height,t)},ee={horizontal:!0,vertical:!0,horizontalPoints:[],verticalPoints:[],stroke:"#ccc",fill:"none",verticalFill:[],horizontalFill:[],xAxisId:0,yAxisId:0};function et(e){var t=(0,R.yi)(),r=(0,R.rY)(),a=(0,R.W7)(),l=U(U({},(0,N.e)(e,ee)),{},{x:(0,o.Et)(e.x)?e.x:a.left,y:(0,o.Et)(e.y)?e.y:a.top,width:(0,o.Et)(e.width)?e.width:a.width,height:(0,o.Et)(e.height)?e.height:a.height}),{xAxisId:u,yAxisId:s,x:c,y:f,width:d,height:p,syncWithTicks:h,horizontalValues:g,verticalValues:v}=l,m=(0,I.r)(),y=(0,D.G)(e=>(0,z.ZB)(e,"xAxis",u,m)),b=(0,D.G)(e=>(0,z.ZB)(e,"yAxis",s,m));if(!(0,o.Et)(d)||d<=0||!(0,o.Et)(p)||p<=0||!(0,o.Et)(c)||c!==+c||!(0,o.Et)(f)||f!==+f)return null;var w=l.verticalCoordinatesGenerator||J,x=l.horizontalCoordinatesGenerator||Q,{horizontalPoints:S,verticalPoints:O}=l;if((!S||!S.length)&&"function"==typeof x){var E=g&&g.length,C=x({yAxis:b?U(U({},b),{},{ticks:E?g:b.ticks}):void 0,width:t,height:r,offset:a},!!E||h);(0,i.R)(Array.isArray(C),"horizontalCoordinatesGenerator should return Array but instead it returned [".concat(typeof C,"]")),Array.isArray(C)&&(S=C)}if((!O||!O.length)&&"function"==typeof w){var M=v&&v.length,k=w({xAxis:y?U(U({},y),{},{ticks:M?v:y.ticks}):void 0,width:t,height:r,offset:a},!!M||h);(0,i.R)(Array.isArray(k),"verticalCoordinatesGenerator should return Array but instead it returned [".concat(typeof k,"]")),Array.isArray(k)&&(O=k)}return n.createElement("g",{className:"recharts-cartesian-grid"},n.createElement(Z,{fill:l.fill,fillOpacity:l.fillOpacity,x:l.x,y:l.y,width:l.width,height:l.height,ry:l.ry}),n.createElement(Y,H({},l,{horizontalPoints:S})),n.createElement(X,H({},l,{verticalPoints:O})),n.createElement(K,H({},l,{offset:a,horizontalPoints:S,xAxis:y,yAxis:b})),n.createElement(q,H({},l,{offset:a,verticalPoints:O,xAxis:y,yAxis:b})))}et.displayName="CartesianGrid"},3540:(e,t,r)=>{"use strict";r.d(t,{u:()=>f});var n=r(2596),i=r(2115),o=r(400),a=r.n(o),l=r(6377),u=r(675);function s(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function c(e){for(var t=1;t{var{aspect:r,initialDimension:o={width:-1,height:-1},width:s="100%",height:f="100%",minWidth:d=0,minHeight:p,maxHeight:h,children:g,debounce:v=0,id:m,className:y,onResize:b,style:w={}}=e,x=(0,i.useRef)(null),S=(0,i.useRef)();S.current=b,(0,i.useImperativeHandle)(t,()=>x.current);var[O,E]=(0,i.useState)({containerWidth:o.width,containerHeight:o.height}),C=(0,i.useCallback)((e,t)=>{E(r=>{var n=Math.round(e),i=Math.round(t);return r.containerWidth===n&&r.containerHeight===i?r:{containerWidth:n,containerHeight:i}})},[]);(0,i.useEffect)(()=>{var e=e=>{var t,{width:r,height:n}=e[0].contentRect;C(r,n),null==(t=S.current)||t.call(S,r,n)};v>0&&(e=a()(e,v,{trailing:!0,leading:!1}));var t=new ResizeObserver(e),{width:r,height:n}=x.current.getBoundingClientRect();return C(r,n),t.observe(x.current),()=>{t.disconnect()}},[C,v]);var M=(0,i.useMemo)(()=>{var{containerWidth:e,containerHeight:t}=O;if(e<0||t<0)return null;(0,u.R)((0,l._3)(s)||(0,l._3)(f),"The width(%s) and height(%s) are both fixed numbers,\n maybe you don't need to use a ResponsiveContainer.",s,f),(0,u.R)(!r||r>0,"The aspect(%s) must be greater than zero.",r);var n=(0,l._3)(s)?e:s,o=(0,l._3)(f)?t:f;return r&&r>0&&(n?o=n/r:o&&(n=o*r),h&&o>h&&(o=h)),(0,u.R)(n>0||o>0,"The width(%s) and height(%s) of chart should be greater than 0,\n please check the style of container, or the props width(%s) and height(%s),\n or add a minWidth(%s) or minHeight(%s) or use aspect(%s) to control the\n height and width.",n,o,s,f,d,p,r),i.Children.map(g,e=>(0,i.cloneElement)(e,{width:n,height:o,style:c({width:n,height:o},e.props.style)}))},[r,g,f,h,p,d,O,s]);return i.createElement("div",{id:m?"".concat(m):void 0,className:(0,n.$)("recharts-responsive-container",y),style:c(c({},w),{},{width:s,height:f,minWidth:d,minHeight:p,maxHeight:h}),ref:x},i.createElement("div",{style:{width:0,height:0,overflow:"visible"}},M))})},3597:(e,t,r)=>{"use strict";r.d(t,{QQ:()=>i,VU:()=>a,XC:()=>s,_U:()=>u,j2:()=>l});var n=r(2115),i=["aria-activedescendant","aria-atomic","aria-autocomplete","aria-busy","aria-checked","aria-colcount","aria-colindex","aria-colspan","aria-controls","aria-current","aria-describedby","aria-details","aria-disabled","aria-errormessage","aria-expanded","aria-flowto","aria-haspopup","aria-hidden","aria-invalid","aria-keyshortcuts","aria-label","aria-labelledby","aria-level","aria-live","aria-modal","aria-multiline","aria-multiselectable","aria-orientation","aria-owns","aria-placeholder","aria-posinset","aria-pressed","aria-readonly","aria-relevant","aria-required","aria-roledescription","aria-rowcount","aria-rowindex","aria-rowspan","aria-selected","aria-setsize","aria-sort","aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext","className","color","height","id","lang","max","media","method","min","name","style","target","width","role","tabIndex","accentHeight","accumulate","additive","alignmentBaseline","allowReorder","alphabetic","amplitude","arabicForm","ascent","attributeName","attributeType","autoReverse","azimuth","baseFrequency","baselineShift","baseProfile","bbox","begin","bias","by","calcMode","capHeight","clip","clipPath","clipPathUnits","clipRule","colorInterpolation","colorInterpolationFilters","colorProfile","colorRendering","contentScriptType","contentStyleType","cursor","cx","cy","d","decelerate","descent","diffuseConstant","direction","display","divisor","dominantBaseline","dur","dx","dy","edgeMode","elevation","enableBackground","end","exponent","externalResourcesRequired","fill","fillOpacity","fillRule","filter","filterRes","filterUnits","floodColor","floodOpacity","focusable","fontFamily","fontSize","fontSizeAdjust","fontStretch","fontStyle","fontVariant","fontWeight","format","from","fx","fy","g1","g2","glyphName","glyphOrientationHorizontal","glyphOrientationVertical","glyphRef","gradientTransform","gradientUnits","hanging","horizAdvX","horizOriginX","href","ideographic","imageRendering","in2","in","intercept","k1","k2","k3","k4","k","kernelMatrix","kernelUnitLength","kerning","keyPoints","keySplines","keyTimes","lengthAdjust","letterSpacing","lightingColor","limitingConeAngle","local","markerEnd","markerHeight","markerMid","markerStart","markerUnits","markerWidth","mask","maskContentUnits","maskUnits","mathematical","mode","numOctaves","offset","opacity","operator","order","orient","orientation","origin","overflow","overlinePosition","overlineThickness","paintOrder","panose1","pathLength","patternContentUnits","patternTransform","patternUnits","pointerEvents","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","r","radius","refX","refY","renderingIntent","repeatCount","repeatDur","requiredExtensions","requiredFeatures","restart","result","rotate","rx","ry","seed","shapeRendering","slope","spacing","specularConstant","specularExponent","speed","spreadMethod","startOffset","stdDeviation","stemh","stemv","stitchTiles","stopColor","stopOpacity","strikethroughPosition","strikethroughThickness","string","stroke","strokeDasharray","strokeDashoffset","strokeLinecap","strokeLinejoin","strokeMiterlimit","strokeOpacity","strokeWidth","surfaceScale","systemLanguage","tableValues","targetX","targetY","textAnchor","textDecoration","textLength","textRendering","to","transform","u1","u2","underlinePosition","underlineThickness","unicode","unicodeBidi","unicodeRange","unitsPerEm","vAlphabetic","values","vectorEffect","version","vertAdvY","vertOriginX","vertOriginY","vHanging","vIdeographic","viewTarget","visibility","vMathematical","widths","wordSpacing","writingMode","x1","x2","x","xChannelSelector","xHeight","xlinkActuate","xlinkArcrole","xlinkHref","xlinkRole","xlinkShow","xlinkTitle","xlinkType","xmlBase","xmlLang","xmlns","xmlnsXlink","xmlSpace","y1","y2","y","yChannelSelector","z","zoomAndPan","ref","key","angle"],o=["points","pathLength"],a={svg:["viewBox","children"],polygon:o,polyline:o},l=["dangerouslySetInnerHTML","onCopy","onCopyCapture","onCut","onCutCapture","onPaste","onPasteCapture","onCompositionEnd","onCompositionEndCapture","onCompositionStart","onCompositionStartCapture","onCompositionUpdate","onCompositionUpdateCapture","onFocus","onFocusCapture","onBlur","onBlurCapture","onChange","onChangeCapture","onBeforeInput","onBeforeInputCapture","onInput","onInputCapture","onReset","onResetCapture","onSubmit","onSubmitCapture","onInvalid","onInvalidCapture","onLoad","onLoadCapture","onError","onErrorCapture","onKeyDown","onKeyDownCapture","onKeyPress","onKeyPressCapture","onKeyUp","onKeyUpCapture","onAbort","onAbortCapture","onCanPlay","onCanPlayCapture","onCanPlayThrough","onCanPlayThroughCapture","onDurationChange","onDurationChangeCapture","onEmptied","onEmptiedCapture","onEncrypted","onEncryptedCapture","onEnded","onEndedCapture","onLoadedData","onLoadedDataCapture","onLoadedMetadata","onLoadedMetadataCapture","onLoadStart","onLoadStartCapture","onPause","onPauseCapture","onPlay","onPlayCapture","onPlaying","onPlayingCapture","onProgress","onProgressCapture","onRateChange","onRateChangeCapture","onSeeked","onSeekedCapture","onSeeking","onSeekingCapture","onStalled","onStalledCapture","onSuspend","onSuspendCapture","onTimeUpdate","onTimeUpdateCapture","onVolumeChange","onVolumeChangeCapture","onWaiting","onWaitingCapture","onAuxClick","onAuxClickCapture","onClick","onClickCapture","onContextMenu","onContextMenuCapture","onDoubleClick","onDoubleClickCapture","onDrag","onDragCapture","onDragEnd","onDragEndCapture","onDragEnter","onDragEnterCapture","onDragExit","onDragExitCapture","onDragLeave","onDragLeaveCapture","onDragOver","onDragOverCapture","onDragStart","onDragStartCapture","onDrop","onDropCapture","onMouseDown","onMouseDownCapture","onMouseEnter","onMouseLeave","onMouseMove","onMouseMoveCapture","onMouseOut","onMouseOutCapture","onMouseOver","onMouseOverCapture","onMouseUp","onMouseUpCapture","onSelect","onSelectCapture","onTouchCancel","onTouchCancelCapture","onTouchEnd","onTouchEndCapture","onTouchMove","onTouchMoveCapture","onTouchStart","onTouchStartCapture","onPointerDown","onPointerDownCapture","onPointerMove","onPointerMoveCapture","onPointerUp","onPointerUpCapture","onPointerCancel","onPointerCancelCapture","onPointerEnter","onPointerEnterCapture","onPointerLeave","onPointerLeaveCapture","onPointerOver","onPointerOverCapture","onPointerOut","onPointerOutCapture","onGotPointerCapture","onGotPointerCaptureCapture","onLostPointerCapture","onLostPointerCaptureCapture","onScroll","onScrollCapture","onWheel","onWheelCapture","onAnimationStart","onAnimationStartCapture","onAnimationEnd","onAnimationEndCapture","onAnimationIteration","onAnimationIterationCapture","onTransitionEnd","onTransitionEndCapture"],u=(e,t)=>{if(!e||"function"==typeof e||"boolean"==typeof e)return null;var r=e;if((0,n.isValidElement)(e)&&(r=e.props),"object"!=typeof r&&"function"!=typeof r)return null;var i={};return Object.keys(r).forEach(e=>{l.includes(e)&&(i[e]=t||(t=>r[e](r,t)))}),i},s=(e,t,r)=>{if(null===e||"object"!=typeof e&&"function"!=typeof e)return null;var n=null;return Object.keys(e).forEach(i=>{var o=e[i];l.includes(i)&&"function"==typeof o&&(n||(n={}),n[i]=e=>(o(t,r,e),null))}),n}},3616:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.eq=function(e,t){return e===t||Number.isNaN(e)&&Number.isNaN(t)}},3655:(e,t,r)=>{"use strict";r.d(t,{hO:()=>u,sG:()=>l});var n=r(2115),i=r(7650),o=r(9708),a=r(5155),l=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"].reduce((e,t)=>{let r=(0,o.TL)(`Primitive.${t}`),i=n.forwardRef((e,n)=>{let{asChild:i,...o}=e;return"undefined"!=typeof window&&(window[Symbol.for("radix-ui")]=!0),(0,a.jsx)(i?r:t,{...o,ref:n})});return i.displayName=`Primitive.${t}`,{...e,[t]:i}},{});function u(e,t){e&&i.flushSync(()=>e.dispatchEvent(t))}},3676:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.uniqBy=function(e,t){let r=new Map;for(let n=0;n{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("external-link",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]])},3949:(e,t,r)=>{e.exports=r(9901).range},4013:(e,t,r)=>{"use strict";r.d(t,{N:()=>l});var n=r(6377),i=r(9827);function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function a(e){for(var t=1;t{if(null!=t&&null!=u){var{chartData:c,computedData:f,dataStartIndex:d,dataEndIndex:p}=r;return e.reduce((e,r)=>{var h,g,v,m,y,{dataDefinedOnItem:b,settings:w}=r,x=function(e,t,r){return Array.isArray(e)&&e&&t+r!==0?e.slice(t,r+1):e}((h=b,g=c,null!=h?h:g),d,p),S=null!=(v=null==w?void 0:w.dataKey)?v:null==o?void 0:o.dataKey,O=null==w?void 0:w.nameKey;return Array.isArray(m=null!=o&&o.dataKey&&Array.isArray(x)&&!Array.isArray(x[0])&&"axis"===s?(0,n.eP)(x,o.dataKey,l):u(x,t,f,O))?m.forEach(t=>{var r=a(a({},w),{},{name:t.name,unit:t.unit,color:void 0,fill:void 0});e.push((0,i.GF)({tooltipEntrySettings:r,dataKey:t.dataKey,payload:t.payload,value:(0,i.kr)(t.payload,t.dataKey),name:t.name}))}):e.push((0,i.GF)({tooltipEntrySettings:w,dataKey:S,payload:m,value:(0,i.kr)(m,S),name:null!=(y=(0,i.kr)(m,O))?y:null==w?void 0:w.name})),e},[])}}},4072:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.toPath=function(e){let t=[],r=e.length;if(0===r)return t;let n=0,i="",o="",a=!1;for(46===e.charCodeAt(0)&&(t.push(""),n++);n{"use strict";var n=r(9641).Buffer;Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let i=r(8221),o=r(5160),a=r(2721),l=r(6633),u=r(885);function s(e,t,r,i=new Map,f){let d=f?.(e,t,r,i);if(null!=d)return d;if(l.isPrimitive(e))return e;if(i.has(e))return i.get(e);if(Array.isArray(e)){let t=Array(e.length);i.set(e,t);for(let n=0;n{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("clock",[["path",{d:"M12 6v6l4 2",key:"mmk7yg"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]])},4315:(e,t,r)=>{"use strict";r.d(t,{jH:()=>o});var n=r(2115);r(5155);var i=n.createContext(void 0);function o(e){let t=n.useContext(i);return e||t||"ltr"}},4373:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8412),i=r(8179),o=r(2384),a=r(3616);t.isIterateeCall=function(e,t,r){return!!o.isObject(r)&&(!!("number"==typeof t&&i.isArrayLike(r)&&n.isIndex(t))&&t{"use strict";r.d(t,{F0:()=>n,tQ:()=>o,um:()=>i});var n="data-recharts-item-index",i="data-recharts-item-data-key",o=60},4460:(e,t,r)=>{"use strict";r.d(t,{i:()=>k});var n=r(2115),i=r(2188),o=r.n(i),a=(e,t)=>[0,3*e,3*t-6*e,3*e-3*t+1],l=(e,t)=>e.map((e,r)=>e*t**r).reduce((e,t)=>e+t),u=(e,t)=>r=>l(a(e,t),r),s=function(){let e,t;for(var r,n,i,o,s=arguments.length,c=Array(s),f=0;fparseFloat(e)))}else 4===c.length&&([r,i,n,o]=c);var p=u(r,n),h=u(i,o),g=(e=r,t=n,r=>l([...a(e,t).map((e,t)=>e*t).slice(1),0],r)),v=e=>e>1?1:e<0?0:e,m=e=>{for(var t=e>1?1:e,r=t,n=0;n<8;++n){var i=p(r)-t,o=g(r);if(1e-4>Math.abs(i-t)||o<1e-4)break;r=v(r-i/o)}return h(r)};return m.isStepper=!1,m},c=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},{stiff:t=100,damping:r=8,dt:n=17}=e,i=(e,i,o)=>{var a=o+(-(e-i)*t-o*r)*n/1e3,l=o*n/1e3+e;return 1e-4>Math.abs(l-i)&&1e-4>Math.abs(a)?[i,0]:[l,a]};return i.isStepper=!0,i.dt=n,i};function f(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function d(e){for(var t=1;tObject.keys(t).reduce((r,n)=>d(d({},r),{},{[n]:e(n,t[n])}),{});function h(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function g(e){for(var t=1;te+(t-e)*r,m=e=>{var{from:t,to:r}=e;return t!==r},y=(e,t,r)=>{var n=p((t,r)=>{if(m(r)){var[n,i]=e(r.from,r.to,r.velocity);return g(g({},r),{},{from:n,velocity:i})}return r},t);return r<1?p((e,t)=>m(t)?g(g({},t),{},{velocity:v(t.velocity,n[e].velocity,r),from:v(t.from,n[e].from,r)}):t,t):y(e,n,r-1)};class b{setTimeout(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,r=performance.now(),n=null,i=o=>{o-r>=t?e(o):"function"==typeof requestAnimationFrame&&(n=requestAnimationFrame(i))};return n=requestAnimationFrame(i),()=>{cancelAnimationFrame(n)}}}var w=["children","begin","duration","attributeName","easing","isActive","from","to","canBegin","onAnimationEnd","shouldReAnimate","onAnimationReStart","animationManager"];function x(){return(x=Object.assign?Object.assign.bind():function(e){for(var t=1;t{if("string"==typeof e)switch(e){case"ease":case"ease-in-out":case"ease-out":case"ease-in":case"linear":return s(e);case"spring":return c();default:if("cubic-bezier"===e.split("(")[0])return s(e)}return"function"==typeof e?e:null})(I),A=this.changeStyle,j=this.manager.getTimeoutController(),T=[Object.keys(R),Object.keys(z)].reduce((e,t)=>e.filter(e=>t.includes(e))),!0===P.isStepper?(t=R,r=z,n=P,i=T,o=A,a=j,u=i.reduce((e,n)=>g(g({},e),{},{[n]:{from:t[n],velocity:0,to:r[n]}}),{}),f=null,d=e=>{l||(l=e);var i=(e-l)/n.dt;u=y(n,u,i),o(g(g(g({},t),r),p((e,t)=>t.from,u))),l=e,Object.values(u).filter(m).length&&(f=a.setTimeout(d))},()=>(f=a.setTimeout(d),()=>{f()})):(h=R,b=z,w=P,x=D,S=T,O=A,E=j,M=null,k=S.reduce((e,t)=>g(g({},e),{},{[t]:[h[t],b[t]]}),{}),_=e=>{C||(C=e);var t=(e-C)/x,r=p((e,r)=>v(...r,w(t)),k);if(O(g(g(g({},h),b),r)),t<1)M=E.setTimeout(_);else{var n=p((e,t)=>v(...t,w(1)),k);O(g(g(g({},h),b),n))}},()=>(M=E.setTimeout(_),()=>{M()})));this.manager.start([F,N,()=>{this.stopJSAnimation=$()},D,L])}runAnimation(e){let t;var{begin:r,duration:n,attributeName:i,to:o,easing:a,onAnimationStart:l,onAnimationEnd:u,children:s}=e;if(this.unSubscribe=this.manager.subscribe(this.handleStyleChange),"function"==typeof a||"function"==typeof s||"spring"===a)return void this.runJSAnimation(e);var c=i?{[i]:o}:o,f=(t=Object.keys(c),t.map(e=>"".concat(e.replace(/([A-Z])/g,e=>"-".concat(e.toLowerCase()))," ").concat(n,"ms ").concat(a)).join(","));this.manager.start([l,r,O(O({},c),{},{transition:f}),n,u])}render(){var e=this.props,{children:t,begin:r,duration:i,attributeName:o,easing:a,isActive:l,from:u,to:s,canBegin:c,onAnimationEnd:f,shouldReAnimate:d,onAnimationReStart:p,animationManager:h}=e,g=function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{var{style:t={},className:r}=e.props;return(0,n.cloneElement)(e,O(O({},g),{},{style:O(O({},t),m),className:r}))};return 1===v?y(n.Children.only(t)):n.createElement("div",null,n.Children.map(t,e=>y(e)))}constructor(e,t){super(e,t),E(this,"mounted",!1),E(this,"manager",null),E(this,"stopJSAnimation",null),E(this,"unSubscribe",null);var{isActive:r,attributeName:n,from:i,to:o,children:a,duration:l,animationManager:u}=this.props;if(this.manager=u,this.handleStyleChange=this.handleStyleChange.bind(this),this.changeStyle=this.changeStyle.bind(this),!r||l<=0){this.state={style:{}},"function"==typeof a&&(this.state={style:o});return}if(i){if("function"==typeof a){this.state={style:i};return}this.state={style:n?{[n]:i}:i}}else this.state={style:{}}}}E(C,"displayName","Animate"),E(C,"defaultProps",{begin:0,duration:1e3,attributeName:"",easing:"ease",isActive:!0,canBegin:!0,onAnimationEnd:()=>{},onAnimationStart:()=>{}});var M=(0,n.createContext)(null);function k(e){var t,r,i,o,a,l,u,s=(0,n.useContext)(M);return n.createElement(C,x({},e,{animationManager:null!=(l=null!=(u=e.animationManager)?u:s)?l:(t=new b,r=()=>null,i=!1,o=null,a=e=>{if(!i){if(Array.isArray(e)){if(!e.length)return;var[n,...l]=e;if("number"==typeof n){o=t.setTimeout(a.bind(null,l),n);return}a(n),o=t.setTimeout(a.bind(null,l));return}"object"==typeof e&&r(e),"function"==typeof e&&e()}},{stop:()=>{i=!0},start:e=>{i=!1,o&&(o(),o=null),a(e)},subscribe:e=>(r=e,()=>{r=()=>null}),getTimeoutController:()=>t})}))}},4487:(e,t,r)=>{"use strict";r.d(t,{LV:()=>l,M:()=>o,hq:()=>i});var n=(0,r(5710).Z0)({name:"chartData",initialState:{chartData:void 0,computedData:void 0,dataStartIndex:0,dataEndIndex:0},reducers:{setChartData(e,t){if(e.chartData=t.payload,null==t.payload){e.dataStartIndex=0,e.dataEndIndex=0;return}t.payload.length>0&&e.dataEndIndex!==t.payload.length-1&&(e.dataEndIndex=t.payload.length-1)},setComputedData(e,t){e.computedData=t.payload},setDataStartEndIndexes(e,t){var{startIndex:r,endIndex:n}=t.payload;null!=r&&(e.dataStartIndex=r),null!=n&&(e.dataEndIndex=n)}}}),{setChartData:i,setDataStartEndIndexes:o,setComputedData:a}=n.actions,l=n.reducer},4517:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8132),i=r(6200),o=r(7298),a=r(921),l=r(3205);t.matchesProperty=function(e,t){switch(typeof e){case"object":Object.is(e?.valueOf(),-0)&&(e="-0");break;case"number":e=i.toKey(e)}return t=o.cloneDeep(t),function(r){let i=a.get(r,e);return void 0===i?l.has(r,e):void 0===t?void 0===i:n.isMatch(i,t)}}},4532:(e,t,r)=>{"use strict";r.d(t,{Qx:()=>s,a6:()=>c,h4:()=>G,jM:()=>H,ss:()=>V});var n,i=Symbol.for("immer-nothing"),o=Symbol.for("immer-draftable"),a=Symbol.for("immer-state");function l(e,...t){throw Error(`[Immer] minified error nr: ${e}. Full error at: https://bit.ly/3cXEKWf`)}var u=Object.getPrototypeOf;function s(e){return!!e&&!!e[a]}function c(e){return!!e&&(d(e)||Array.isArray(e)||!!e[o]||!!e.constructor?.[o]||m(e)||y(e))}var f=Object.prototype.constructor.toString();function d(e){if(!e||"object"!=typeof e)return!1;let t=u(e);if(null===t)return!0;let r=Object.hasOwnProperty.call(t,"constructor")&&t.constructor;return r===Object||"function"==typeof r&&Function.toString.call(r)===f}function p(e,t){0===h(e)?Reflect.ownKeys(e).forEach(r=>{t(r,e[r],e)}):e.forEach((r,n)=>t(n,r,e))}function h(e){let t=e[a];return t?t.type_:Array.isArray(e)?1:m(e)?2:3*!!y(e)}function g(e,t){return 2===h(e)?e.has(t):Object.prototype.hasOwnProperty.call(e,t)}function v(e,t,r){let n=h(e);2===n?e.set(t,r):3===n?e.add(r):e[t]=r}function m(e){return e instanceof Map}function y(e){return e instanceof Set}function b(e){return e.copy_||e.base_}function w(e,t){if(m(e))return new Map(e);if(y(e))return new Set(e);if(Array.isArray(e))return Array.prototype.slice.call(e);let r=d(e);if(!0!==t&&("class_only"!==t||r)){let t=u(e);return null!==t&&r?{...e}:Object.assign(Object.create(t),e)}{let t=Object.getOwnPropertyDescriptors(e);delete t[a];let r=Reflect.ownKeys(t);for(let n=0;n1&&(e.set=e.add=e.clear=e.delete=S),Object.freeze(e),t&&Object.entries(e).forEach(([e,t])=>x(t,!0))),e}function S(){l(2)}function O(e){return Object.isFrozen(e)}var E={};function C(e){let t=E[e];return t||l(0,e),t}function M(e,t){t&&(C("Patches"),e.patches_=[],e.inversePatches_=[],e.patchListener_=t)}function k(e){_(e),e.drafts_.forEach(A),e.drafts_=null}function _(e){e===n&&(n=e.parent_)}function P(e){return n={drafts_:[],parent_:n,immer_:e,canAutoFreeze_:!0,unfinalizedDrafts_:0}}function A(e){let t=e[a];0===t.type_||1===t.type_?t.revoke_():t.revoked_=!0}function j(e,t){t.unfinalizedDrafts_=t.drafts_.length;let r=t.drafts_[0];return void 0!==e&&e!==r?(r[a].modified_&&(k(t),l(4)),c(e)&&(e=T(t,e),t.parent_||z(t,e)),t.patches_&&C("Patches").generateReplacementPatches_(r[a].base_,e,t.patches_,t.inversePatches_)):e=T(t,r,[]),k(t),t.patches_&&t.patchListener_(t.patches_,t.inversePatches_),e!==i?e:void 0}function T(e,t,r){if(O(t))return t;let n=t[a];if(!n)return p(t,(i,o)=>R(e,n,t,i,o,r)),t;if(n.scope_!==e)return t;if(!n.modified_)return z(e,n.base_,!0),n.base_;if(!n.finalized_){n.finalized_=!0,n.scope_.unfinalizedDrafts_--;let t=n.copy_,i=t,o=!1;3===n.type_&&(i=new Set(t),t.clear(),o=!0),p(i,(i,a)=>R(e,n,t,i,a,r,o)),z(e,t,!1),r&&e.patches_&&C("Patches").generatePatches_(n,r,e.patches_,e.inversePatches_)}return n.copy_}function R(e,t,r,n,i,o,a){if(s(i)){let a=T(e,i,o&&t&&3!==t.type_&&!g(t.assigned_,n)?o.concat(n):void 0);if(v(r,n,a),!s(a))return;e.canAutoFreeze_=!1}else a&&r.add(i);if(c(i)&&!O(i)){if(!e.immer_.autoFreeze_&&e.unfinalizedDrafts_<1)return;T(e,i),(!t||!t.scope_.parent_)&&"symbol"!=typeof n&&Object.prototype.propertyIsEnumerable.call(r,n)&&z(e,i)}}function z(e,t,r=!1){!e.parent_&&e.immer_.autoFreeze_&&e.canAutoFreeze_&&x(t,r)}var D={get(e,t){if(t===a)return e;let r=b(e);if(!g(r,t)){var n=e,i=r,o=t;let a=L(i,o);return a?"value"in a?a.value:a.get?.call(n.draft_):void 0}let l=r[t];return e.finalized_||!c(l)?l:l===N(e.base_,t)?($(e),e.copy_[t]=B(l,e)):l},has:(e,t)=>t in b(e),ownKeys:e=>Reflect.ownKeys(b(e)),set(e,t,r){let n=L(b(e),t);if(n?.set)return n.set.call(e.draft_,r),!0;if(!e.modified_){let n=N(b(e),t),i=n?.[a];if(i&&i.base_===r)return e.copy_[t]=r,e.assigned_[t]=!1,!0;if((r===n?0!==r||1/r==1/n:r!=r&&n!=n)&&(void 0!==r||g(e.base_,t)))return!0;$(e),F(e)}return!!(e.copy_[t]===r&&(void 0!==r||t in e.copy_)||Number.isNaN(r)&&Number.isNaN(e.copy_[t]))||(e.copy_[t]=r,e.assigned_[t]=!0,!0)},deleteProperty:(e,t)=>(void 0!==N(e.base_,t)||t in e.base_?(e.assigned_[t]=!1,$(e),F(e)):delete e.assigned_[t],e.copy_&&delete e.copy_[t],!0),getOwnPropertyDescriptor(e,t){let r=b(e),n=Reflect.getOwnPropertyDescriptor(r,t);return n?{writable:!0,configurable:1!==e.type_||"length"!==t,enumerable:n.enumerable,value:r[t]}:n},defineProperty(){l(11)},getPrototypeOf:e=>u(e.base_),setPrototypeOf(){l(12)}},I={};function N(e,t){let r=e[a];return(r?b(r):e)[t]}function L(e,t){if(!(t in e))return;let r=u(e);for(;r;){let e=Object.getOwnPropertyDescriptor(r,t);if(e)return e;r=u(r)}}function F(e){!e.modified_&&(e.modified_=!0,e.parent_&&F(e.parent_))}function $(e){e.copy_||(e.copy_=w(e.base_,e.scope_.immer_.useStrictShallowCopy_))}function B(e,t){let r=m(e)?C("MapSet").proxyMap_(e,t):y(e)?C("MapSet").proxySet_(e,t):function(e,t){let r=Array.isArray(e),i={type_:+!!r,scope_:t?t.scope_:n,modified_:!1,finalized_:!1,assigned_:{},parent_:t,base_:e,draft_:null,copy_:null,revoke_:null,isManual_:!1},o=i,a=D;r&&(o=[i],a=I);let{revoke:l,proxy:u}=Proxy.revocable(o,a);return i.draft_=u,i.revoke_=l,u}(e,t);return(t?t.scope_:n).drafts_.push(r),r}function V(e){return s(e)||l(10,e),function e(t){let r;if(!c(t)||O(t))return t;let n=t[a];if(n){if(!n.modified_)return n.base_;n.finalized_=!0,r=w(t,n.scope_.immer_.useStrictShallowCopy_)}else r=w(t,!0);return p(r,(t,n)=>{v(r,t,e(n))}),n&&(n.finalized_=!1),r}(e)}p(D,(e,t)=>{I[e]=function(){return arguments[0]=arguments[0][0],t.apply(this,arguments)}}),I.deleteProperty=function(e,t){return I.set.call(this,e,t,void 0)},I.set=function(e,t,r){return D.set.call(this,e[0],t,r,e[0])};var U=new class{constructor(e){this.autoFreeze_=!0,this.useStrictShallowCopy_=!1,this.produce=(e,t,r)=>{let n;if("function"==typeof e&&"function"!=typeof t){let r=t;t=e;let n=this;return function(e=r,...i){return n.produce(e,e=>t.call(this,e,...i))}}if("function"!=typeof t&&l(6),void 0!==r&&"function"!=typeof r&&l(7),c(e)){let i=P(this),o=B(e,void 0),a=!0;try{n=t(o),a=!1}finally{a?k(i):_(i)}return M(i,r),j(n,i)}if(e&&"object"==typeof e)l(1,e);else{if(void 0===(n=t(e))&&(n=e),n===i&&(n=void 0),this.autoFreeze_&&x(n,!0),r){let t=[],i=[];C("Patches").generateReplacementPatches_(e,n,t,i),r(t,i)}return n}},this.produceWithPatches=(e,t)=>{let r,n;return"function"==typeof e?(t,...r)=>this.produceWithPatches(t,t=>e(t,...r)):[this.produce(e,t,(e,t)=>{r=e,n=t}),r,n]},"boolean"==typeof e?.autoFreeze&&this.setAutoFreeze(e.autoFreeze),"boolean"==typeof e?.useStrictShallowCopy&&this.setUseStrictShallowCopy(e.useStrictShallowCopy)}createDraft(e){c(e)||l(8),s(e)&&(e=V(e));let t=P(this),r=B(e,void 0);return r[a].isManual_=!0,_(t),r}finishDraft(e,t){let r=e&&e[a];r&&r.isManual_||l(9);let{scope_:n}=r;return M(n,t),j(void 0,n)}setAutoFreeze(e){this.autoFreeze_=e}setUseStrictShallowCopy(e){this.useStrictShallowCopy_=e}applyPatches(e,t){let r;for(r=t.length-1;r>=0;r--){let n=t[r];if(0===n.path.length&&"replace"===n.op){e=n.value;break}}r>-1&&(t=t.slice(r+1));let n=C("Patches").applyPatches_;return s(e)?n(e,t):this.produce(e,e=>n(e,t))}},H=U.produce;function G(e){return e}U.produceWithPatches.bind(U),U.setAutoFreeze.bind(U),U.setUseStrictShallowCopy.bind(U),U.applyPatches.bind(U),U.createDraft.bind(U),U.finishDraft.bind(U)},4545:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isDeepKey=function(e){switch(typeof e){case"number":case"symbol":return!1;case"string":return e.includes(".")||e.includes("[")||e.includes("]")}}},4664:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(2694);t.toFinite=function(e){return e?(e=n.toNumber(e))===1/0||e===-1/0?(e<0?-1:1)*Number.MAX_VALUE:e==e?e:0:0===e?e:0}},4732:(e,t,r)=>{"use strict";r.d(t,{BZ:()=>R,aX:()=>I,dS:()=>T,dp:()=>P,fW:()=>S,pg:()=>j,r1:()=>M,u9:()=>z,yn:()=>D});var n=r(8924),i=r(241),o=r.n(i),a=r(1971),l=r(9827),u=r(356),s=r(215),c=r(8478),f=r(7238),d=r(6124),p=r(2589),h=r(530),g=r(1928),v=r(841),m=r(4968),y=r(5146),b=r(6670),w=r(5714),x=r(4013),S=()=>(0,a.G)(c.iO),O=(e,t)=>t,E=(e,t,r)=>r,C=(e,t,r,n)=>n,M=(0,n.Mz)(s.R4,e=>o()(e,e=>e.coordinate)),k=(0,n.Mz)([w.J,O,E,C],g.i),_=(0,n.Mz)([k,s.n4],v.P),P=(e,t,r)=>{if(null!=t){var n=(0,w.J)(e);return"axis"===t?"hover"===r?n.axisInteraction.hover.dataKey:n.axisInteraction.click.dataKey:"hover"===r?n.itemInteraction.hover.dataKey:n.itemInteraction.click.dataKey}},A=(0,n.Mz)([w.J,O,E,C],y.q),j=(0,n.Mz)([p.Lp,p.A$,f.fz,d.HZ,s.R4,C,A,b.x],m.o),T=(0,n.Mz)([k,j],(e,t)=>{var r;return null!=(r=e.coordinate)?r:t}),R=(0,n.Mz)(s.R4,_,h.E),z=(0,n.Mz)([A,_,u.LF,s.Dn,R,b.x,O],x.N),D=(0,n.Mz)([k],e=>({isActive:e.active,activeIndex:e.index})),I=(e,t,r,n,i,o,a,u)=>{if(e&&t&&n&&i&&o){var s=(0,l.r4)(e.chartX,e.chartY,t,r,u);if(s){var c=(0,l.SW)(s,t),f=(0,l.gH)(c,a,o,n,i),d=(0,l.bk)(t,o,f,s);return{activeIndex:String(f),activeCoordinate:d}}}}},4804:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8132),i=r(2429);t.matches=function(e){return e=i.cloneDeep(e),t=>n.isMatch(t,e)}},4861:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("circle-x",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]])},4890:(e,t,r)=>{"use strict";r.d(t,{E1:()=>v,En:()=>y,Ix:()=>l,Nt:()=>h,RD:()=>c,UF:()=>s,XB:()=>u,jF:()=>g,k_:()=>o,o4:()=>m,xS:()=>d});var n=r(5710),i=r(4532),o={active:!1,index:null,dataKey:void 0,coordinate:void 0},a=(0,n.Z0)({name:"tooltip",initialState:{itemInteraction:{click:o,hover:o},axisInteraction:{click:o,hover:o},keyboardInteraction:o,syncInteraction:{active:!1,index:null,dataKey:void 0,label:void 0,coordinate:void 0},tooltipItemPayloads:[],settings:{shared:void 0,trigger:"hover",axisId:0,active:!1,defaultIndex:void 0}},reducers:{addTooltipEntrySettings(e,t){e.tooltipItemPayloads.push((0,i.h4)(t.payload))},removeTooltipEntrySettings(e,t){var r=(0,i.ss)(e).tooltipItemPayloads.indexOf((0,i.h4)(t.payload));r>-1&&e.tooltipItemPayloads.splice(r,1)},setTooltipSettingsState(e,t){e.settings=t.payload},setActiveMouseOverItemIndex(e,t){e.syncInteraction.active=!1,e.keyboardInteraction.active=!1,e.itemInteraction.hover.active=!0,e.itemInteraction.hover.index=t.payload.activeIndex,e.itemInteraction.hover.dataKey=t.payload.activeDataKey,e.itemInteraction.hover.coordinate=t.payload.activeCoordinate},mouseLeaveChart(e){e.itemInteraction.hover.active=!1,e.axisInteraction.hover.active=!1},mouseLeaveItem(e){e.itemInteraction.hover.active=!1},setActiveClickItemIndex(e,t){e.syncInteraction.active=!1,e.itemInteraction.click.active=!0,e.keyboardInteraction.active=!1,e.itemInteraction.click.index=t.payload.activeIndex,e.itemInteraction.click.dataKey=t.payload.activeDataKey,e.itemInteraction.click.coordinate=t.payload.activeCoordinate},setMouseOverAxisIndex(e,t){e.syncInteraction.active=!1,e.axisInteraction.hover.active=!0,e.keyboardInteraction.active=!1,e.axisInteraction.hover.index=t.payload.activeIndex,e.axisInteraction.hover.dataKey=t.payload.activeDataKey,e.axisInteraction.hover.coordinate=t.payload.activeCoordinate},setMouseClickAxisIndex(e,t){e.syncInteraction.active=!1,e.keyboardInteraction.active=!1,e.axisInteraction.click.active=!0,e.axisInteraction.click.index=t.payload.activeIndex,e.axisInteraction.click.dataKey=t.payload.activeDataKey,e.axisInteraction.click.coordinate=t.payload.activeCoordinate},setSyncInteraction(e,t){e.syncInteraction=t.payload},setKeyboardInteraction(e,t){e.keyboardInteraction.active=t.payload.active,e.keyboardInteraction.index=t.payload.activeIndex,e.keyboardInteraction.coordinate=t.payload.activeCoordinate,e.keyboardInteraction.dataKey=t.payload.activeDataKey}}}),{addTooltipEntrySettings:l,removeTooltipEntrySettings:u,setTooltipSettingsState:s,setActiveMouseOverItemIndex:c,mouseLeaveItem:f,mouseLeaveChart:d,setActiveClickItemIndex:p,setMouseOverAxisIndex:h,setMouseClickAxisIndex:g,setSyncInteraction:v,setKeyboardInteraction:m}=a.actions,y=a.reducer},4968:(e,t,r)=>{"use strict";r.d(t,{o:()=>n});var n=(e,t,r,n,i,o,a,l)=>{if(null!=o&&null!=l){var u=a[0],s=null==u?void 0:l(u.positions,o);if(null!=s)return s;var c=null==i?void 0:i[Number(o)];if(c)if("horizontal"===r)return{x:c.coordinate,y:(n.top+t)/2};else return{x:(n.left+e)/2,y:c.coordinate}}}},4986:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.toArray=function(e){return Array.isArray(e)?e:Array.from(e)}},4993:(e,t,r)=>{"use strict";var n=r(2115);"function"==typeof Object.is&&Object.is,n.useSyncExternalStore,n.useRef,n.useEffect,n.useMemo,n.useDebugValue},5064:(e,t,r)=>{"use strict";r.d(t,{E:()=>n});var n=(0,r(2115).createContext)(null)},5115:(e,t,r)=>{"use strict";r.d(t,{$:()=>i,X:()=>o});var n=r(2115),i=(0,n.createContext)(null),o=()=>(0,n.useContext)(i)},5143:(e,t,r)=>{"use strict";r.d(t,{Mp:()=>eR,vL:()=>a,uN:()=>et,cA:()=>eu,IG:()=>ec,fp:()=>A,Sj:()=>D,fF:()=>eN,PM:()=>eI,zM:()=>eF,MS:()=>E,FR:()=>C});var n,i,o,a,l,u,s,c,f,d,p=r(2115),h=r(7650),g=r(8266);let v={display:"none"};function m(e){let{id:t,value:r}=e;return p.createElement("div",{id:t,style:v},r)}function y(e){let{id:t,announcement:r,ariaLiveType:n="assertive"}=e;return p.createElement("div",{id:t,style:{position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"},role:"status","aria-live":n,"aria-atomic":!0},r)}let b=(0,p.createContext)(null),w={draggable:"\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n "},x={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:r}=e;return r?"Draggable item "+t.id+" was moved over droppable area "+r.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:r}=e;return r?"Draggable item "+t.id+" was dropped over droppable area "+r.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function S(e){let{announcements:t=x,container:r,hiddenTextDescribedById:n,screenReaderInstructions:i=w}=e,{announce:o,announcement:a}=function(){let[e,t]=(0,p.useState)("");return{announce:(0,p.useCallback)(e=>{null!=e&&t(e)},[]),announcement:e}}(),l=(0,g.YG)("DndLiveRegion"),[u,s]=(0,p.useState)(!1);(0,p.useEffect)(()=>{s(!0)},[]);var c=(0,p.useMemo)(()=>({onDragStart(e){let{active:r}=e;o(t.onDragStart({active:r}))},onDragMove(e){let{active:r,over:n}=e;t.onDragMove&&o(t.onDragMove({active:r,over:n}))},onDragOver(e){let{active:r,over:n}=e;o(t.onDragOver({active:r,over:n}))},onDragEnd(e){let{active:r,over:n}=e;o(t.onDragEnd({active:r,over:n}))},onDragCancel(e){let{active:r,over:n}=e;o(t.onDragCancel({active:r,over:n}))}}),[o,t]);let f=(0,p.useContext)(b);if((0,p.useEffect)(()=>{if(!f)throw Error("useDndMonitor must be used within a children of ");return f(c)},[c,f]),!u)return null;let d=p.createElement(p.Fragment,null,p.createElement(m,{id:n,value:i.draggable}),p.createElement(y,{id:l,announcement:a}));return r?(0,h.createPortal)(d,r):d}function O(){}function E(e,t){return(0,p.useMemo)(()=>({sensor:e,options:null!=t?t:{}}),[e,t])}function C(){for(var e=arguments.length,t=Array(e),r=0;r[...t].filter(e=>null!=e),[...t])}!function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"}(n||(n={}));let M=Object.freeze({x:0,y:0});function k(e,t){let{data:{value:r}}=e,{data:{value:n}}=t;return r-n}function _(e,t){let{data:{value:r}}=e,{data:{value:n}}=t;return n-r}function P(e,t,r){return void 0===t&&(t=e.left),void 0===r&&(r=e.top),{x:t+.5*e.width,y:r+.5*e.height}}let A=e=>{let{collisionRect:t,droppableRects:r,droppableContainers:n}=e,i=P(t,t.left,t.top),o=[];for(let e of n){let{id:t}=e,n=r.get(t);if(n){let r=function(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}(P(n),i);o.push({id:t,data:{droppableContainer:e,value:r}})}}return o.sort(k)},j=e=>{let{collisionRect:t,droppableRects:r,droppableContainers:n}=e,i=[];for(let e of n){let{id:n}=e,o=r.get(n);if(o){let r=function(e,t){let r=Math.max(t.top,e.top),n=Math.max(t.left,e.left),i=Math.min(t.left+t.width,e.left+e.width),o=Math.min(t.top+t.height,e.top+e.height);if(n0&&i.push({id:n,data:{droppableContainer:e,value:r}})}}return i.sort(_)};function T(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:M}let R=function(e){return function(t){for(var r=arguments.length,n=Array(r>1?r-1:0),i=1;i({...t,top:t.top+e*r.y,bottom:t.bottom+e*r.y,left:t.left+e*r.x,right:t.right+e*r.x}),{...t})}}(1),z={ignoreTransform:!1};function D(e,t){void 0===t&&(t=z);let r=e.getBoundingClientRect();if(t.ignoreTransform){let{transform:t,transformOrigin:n}=(0,g.zk)(e).getComputedStyle(e);t&&(r=function(e,t,r){let n=function(e){if(e.startsWith("matrix3d(")){let t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}if(e.startsWith("matrix(")){let t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}(t);if(!n)return e;let{scaleX:i,scaleY:o,x:a,y:l}=n,u=e.left-a-(1-i)*parseFloat(r),s=e.top-l-(1-o)*parseFloat(r.slice(r.indexOf(" ")+1)),c=i?e.width/i:e.width,f=o?e.height/o:e.height;return{width:c,height:f,top:s,right:u+c,bottom:s+f,left:u}}(r,t,n))}let{top:n,left:i,width:o,height:a,bottom:l,right:u}=r;return{top:n,left:i,width:o,height:a,bottom:l,right:u}}function I(e){return D(e,{ignoreTransform:!0})}function N(e,t){let r=[];return e?function n(i){var o;if(null!=t&&r.length>=t||!i)return r;if((0,g.wz)(i)&&null!=i.scrollingElement&&!r.includes(i.scrollingElement))return r.push(i.scrollingElement),r;if(!(0,g.sb)(i)||(0,g.xZ)(i)||r.includes(i))return r;let a=(0,g.zk)(e).getComputedStyle(i);return(i!==e&&function(e,t){void 0===t&&(t=(0,g.zk)(e).getComputedStyle(e));let r=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(e=>{let n=t[e];return"string"==typeof n&&r.test(n)})}(i,a)&&r.push(i),void 0===(o=a)&&(o=(0,g.zk)(i).getComputedStyle(i)),"fixed"===o.position)?r:n(i.parentNode)}(e):r}function L(e){let[t]=N(e,1);return null!=t?t:null}function F(e){return g.Sw&&e?(0,g.l6)(e)?e:(0,g.Ll)(e)?(0,g.wz)(e)||e===(0,g.TW)(e).scrollingElement?window:(0,g.sb)(e)?e:null:null:null}function $(e){return(0,g.l6)(e)?e.scrollX:e.scrollLeft}function B(e){return(0,g.l6)(e)?e.scrollY:e.scrollTop}function V(e){return{x:$(e),y:B(e)}}function U(e){return!!g.Sw&&!!e&&e===document.scrollingElement}function H(e){let t={x:0,y:0},r=U(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},n={x:e.scrollWidth-r.width,y:e.scrollHeight-r.height},i=e.scrollTop<=t.y,o=e.scrollLeft<=t.x;return{isTop:i,isLeft:o,isBottom:e.scrollTop>=n.y,isRight:e.scrollLeft>=n.x,maxScroll:n,minScroll:t}}!function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"}(i||(i={}));let G={x:.2,y:.2};function Z(e){return e.reduce((e,t)=>(0,g.WQ)(e,V(t)),M)}let W=[["x",["left","right"],function(e){return e.reduce((e,t)=>e+$(t),0)}],["y",["top","bottom"],function(e){return e.reduce((e,t)=>e+B(t),0)}]];class K{constructor(e,t){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;let r=N(t),n=Z(r);for(let[t,i,o]of(this.rect={...e},this.width=e.width,this.height=e.height,W))for(let e of i)Object.defineProperty(this,e,{get:()=>{let i=o(r),a=n[t]-i;return this.rect[e]+a},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class q{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(e=>{var t;return null==(t=this.target)?void 0:t.removeEventListener(...e)})},this.target=e}add(e,t,r){var n;null==(n=this.target)||n.addEventListener(e,t,r),this.listeners.push([e,t,r])}}function Y(e,t){let r=Math.abs(e.x),n=Math.abs(e.y);return"number"==typeof t?Math.sqrt(r**2+n**2)>t:"x"in t&&"y"in t?r>t.x&&n>t.y:"x"in t?r>t.x:"y"in t&&n>t.y}function X(e){e.preventDefault()}function J(e){e.stopPropagation()}!function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"}(o||(o={})),function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"}(a||(a={}));let Q={start:[a.Space,a.Enter],cancel:[a.Esc],end:[a.Space,a.Enter,a.Tab]},ee=(e,t)=>{let{currentCoordinates:r}=t;switch(e.code){case a.Right:return{...r,x:r.x+25};case a.Left:return{...r,x:r.x-25};case a.Down:return{...r,y:r.y+25};case a.Up:return{...r,y:r.y-25}}};class et{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;let{event:{target:t}}=e;this.props=e,this.listeners=new q((0,g.TW)(t)),this.windowListeners=new q((0,g.zk)(t)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(o.Resize,this.handleCancel),this.windowListeners.add(o.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(o.Keydown,this.handleKeyDown))}handleStart(){let{activeNode:e,onStart:t}=this.props,r=e.node.current;r&&function(e,t){if(void 0===t&&(t=D),!e)return;let{top:r,left:n,bottom:i,right:o}=t(e);L(e)&&(i<=0||o<=0||r>=window.innerHeight||n>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}(r),t(M)}handleKeyDown(e){if((0,g.kx)(e)){let{active:t,context:r,options:n}=this.props,{keyboardCodes:i=Q,coordinateGetter:o=ee,scrollBehavior:l="smooth"}=n,{code:u}=e;if(i.end.includes(u))return void this.handleEnd(e);if(i.cancel.includes(u))return void this.handleCancel(e);let{collisionRect:s}=r.current,c=s?{x:s.left,y:s.top}:M;this.referenceCoordinates||(this.referenceCoordinates=c);let f=o(e,{active:t,context:r.current,currentCoordinates:c});if(f){let t=(0,g.Re)(f,c),n={x:0,y:0},{scrollableAncestors:i}=r.current;for(let r of i){let i=e.code,{isTop:o,isRight:u,isLeft:s,isBottom:c,maxScroll:d,minScroll:p}=H(r),h=function(e){if(e===document.scrollingElement){let{innerWidth:e,innerHeight:t}=window;return{top:0,left:0,right:e,bottom:t,width:e,height:t}}let{top:t,left:r,right:n,bottom:i}=e.getBoundingClientRect();return{top:t,left:r,right:n,bottom:i,width:e.clientWidth,height:e.clientHeight}}(r),g={x:Math.min(i===a.Right?h.right-h.width/2:h.right,Math.max(i===a.Right?h.left:h.left+h.width/2,f.x)),y:Math.min(i===a.Down?h.bottom-h.height/2:h.bottom,Math.max(i===a.Down?h.top:h.top+h.height/2,f.y))},v=i===a.Right&&!u||i===a.Left&&!s,m=i===a.Down&&!c||i===a.Up&&!o;if(v&&g.x!==f.x){let e=r.scrollLeft+t.x,o=i===a.Right&&e<=d.x||i===a.Left&&e>=p.x;if(o&&!t.y)return void r.scrollTo({left:e,behavior:l});o?n.x=r.scrollLeft-e:n.x=i===a.Right?r.scrollLeft-d.x:r.scrollLeft-p.x,n.x&&r.scrollBy({left:-n.x,behavior:l});break}if(m&&g.y!==f.y){let e=r.scrollTop+t.y,o=i===a.Down&&e<=d.y||i===a.Up&&e>=p.y;if(o&&!t.x)return void r.scrollTo({top:e,behavior:l});o?n.y=r.scrollTop-e:n.y=i===a.Down?r.scrollTop-d.y:r.scrollTop-p.y,n.y&&r.scrollBy({top:-n.y,behavior:l});break}}this.handleMove(e,(0,g.WQ)((0,g.Re)(f,this.referenceCoordinates),n))}}}handleMove(e,t){let{onMove:r}=this.props;e.preventDefault(),r(t)}handleEnd(e){let{onEnd:t}=this.props;e.preventDefault(),this.detach(),t()}handleCancel(e){let{onCancel:t}=this.props;e.preventDefault(),this.detach(),t()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}function er(e){return!!(e&&"distance"in e)}function en(e){return!!(e&&"delay"in e)}et.activators=[{eventName:"onKeyDown",handler:(e,t,r)=>{let{keyboardCodes:n=Q,onActivation:i}=t,{active:o}=r,{code:a}=e.nativeEvent;if(n.start.includes(a)){let t=o.activatorNode.current;return(!t||e.target===t)&&(e.preventDefault(),null==i||i({event:e.nativeEvent}),!0)}return!1}}];class ei{constructor(e,t,r){var n;void 0===r&&(r=function(e){let{EventTarget:t}=(0,g.zk)(e);return e instanceof t?e:(0,g.TW)(e)}(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=t;let{event:i}=e,{target:o}=i;this.props=e,this.events=t,this.document=(0,g.TW)(o),this.documentListeners=new q(this.document),this.listeners=new q(r),this.windowListeners=new q((0,g.zk)(o)),this.initialCoordinates=null!=(n=(0,g.e_)(i))?n:M,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){let{events:e,props:{options:{activationConstraint:t,bypassActivationConstraint:r}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(o.Resize,this.handleCancel),this.windowListeners.add(o.DragStart,X),this.windowListeners.add(o.VisibilityChange,this.handleCancel),this.windowListeners.add(o.ContextMenu,X),this.documentListeners.add(o.Keydown,this.handleKeydown),t){if(null!=r&&r({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(en(t)){this.timeoutId=setTimeout(this.handleStart,t.delay),this.handlePending(t);return}if(er(t))return void this.handlePending(t)}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),null!==this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,t){let{active:r,onPending:n}=this.props;n(r,e,this.initialCoordinates,t)}handleStart(){let{initialCoordinates:e}=this,{onStart:t}=this.props;e&&(this.activated=!0,this.documentListeners.add(o.Click,J,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(o.SelectionChange,this.removeTextSelection),t(e))}handleMove(e){var t;let{activated:r,initialCoordinates:n,props:i}=this,{onMove:o,options:{activationConstraint:a}}=i;if(!n)return;let l=null!=(t=(0,g.e_)(e))?t:M,u=(0,g.Re)(n,l);if(!r&&a){if(er(a)){if(null!=a.tolerance&&Y(u,a.tolerance))return this.handleCancel();if(Y(u,a.distance))return this.handleStart()}return en(a)&&Y(u,a.tolerance)?this.handleCancel():void this.handlePending(a,u)}e.cancelable&&e.preventDefault(),o(l)}handleEnd(){let{onAbort:e,onEnd:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleCancel(){let{onAbort:e,onCancel:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleKeydown(e){e.code===a.Esc&&this.handleCancel()}removeTextSelection(){var e;null==(e=this.document.getSelection())||e.removeAllRanges()}}let eo={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class ea extends ei{constructor(e){let{event:t}=e;super(e,eo,(0,g.TW)(t.target))}}ea.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t;return!!r.isPrimary&&0===r.button&&(null==n||n({event:r}),!0)}}];let el={move:{name:"mousemove"},end:{name:"mouseup"}};!function(e){e[e.RightClick=2]="RightClick"}(l||(l={}));class eu extends ei{constructor(e){super(e,el,(0,g.TW)(e.event.target))}}eu.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t;return r.button!==l.RightClick&&(null==n||n({event:r}),!0)}}];let es={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class ec extends ei{constructor(e){super(e,es)}static setup(){return window.addEventListener(es.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(es.move.name,e)};function e(){}}}ec.activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t,{touches:i}=r;return!(i.length>1)&&(null==n||n({event:r}),!0)}}],function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"}(u||(u={})),function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"}(s||(s={}));let ef={x:{[i.Backward]:!1,[i.Forward]:!1},y:{[i.Backward]:!1,[i.Forward]:!1}};!function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"}(c||(c={})),(f||(f={})).Optimized="optimized";let ed=new Map;function ep(e,t){return(0,g.KG)(r=>e?r||("function"==typeof t?t(e):e):null,[t,e])}function eh(e){let{callback:t,disabled:r}=e,n=(0,g._q)(t),i=(0,p.useMemo)(()=>{if(r||"undefined"==typeof window||void 0===window.ResizeObserver)return;let{ResizeObserver:e}=window;return new e(n)},[r]);return(0,p.useEffect)(()=>()=>null==i?void 0:i.disconnect(),[i]),i}function eg(e){return new K(D(e),e)}function ev(e,t,r){void 0===t&&(t=eg);let[n,i]=(0,p.useState)(null);function o(){i(n=>{if(!e)return null;if(!1===e.isConnected){var i;return null!=(i=null!=n?n:r)?i:null}let o=t(e);return JSON.stringify(n)===JSON.stringify(o)?n:o})}let a=function(e){let{callback:t,disabled:r}=e,n=(0,g._q)(t),i=(0,p.useMemo)(()=>{if(r||"undefined"==typeof window||void 0===window.MutationObserver)return;let{MutationObserver:e}=window;return new e(n)},[n,r]);return(0,p.useEffect)(()=>()=>null==i?void 0:i.disconnect(),[i]),i}({callback(t){if(e)for(let r of t){let{type:t,target:n}=r;if("childList"===t&&n instanceof HTMLElement&&n.contains(e)){o();break}}}}),l=eh({callback:o});return(0,g.Es)(()=>{o(),e?(null==l||l.observe(e),null==a||a.observe(document.body,{childList:!0,subtree:!0})):(null==l||l.disconnect(),null==a||a.disconnect())},[e]),n}let em=[];function ey(e,t){void 0===t&&(t=[]);let r=(0,p.useRef)(null);return(0,p.useEffect)(()=>{r.current=null},t),(0,p.useEffect)(()=>{let t=e!==M;t&&!r.current&&(r.current=e),!t&&r.current&&(r.current=null)},[e]),r.current?(0,g.Re)(e,r.current):M}function eb(e){return(0,p.useMemo)(()=>e?function(e){let t=e.innerWidth,r=e.innerHeight;return{top:0,left:0,right:t,bottom:r,width:t,height:r}}(e):null,[e])}let ew=[],ex=[{sensor:ea,options:{}},{sensor:et,options:{}}],eS={current:{}},eO={draggable:{measure:I},droppable:{measure:I,strategy:c.WhileDragging,frequency:f.Optimized},dragOverlay:{measure:D}};class eE extends Map{get(e){var t;return null!=e&&null!=(t=super.get(e))?t:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:t}=e;return!t})}getNodeFor(e){var t,r;return null!=(t=null==(r=this.get(e))?void 0:r.node.current)?t:void 0}}let eC={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new eE,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:O},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:eO,measureDroppableContainers:O,windowRect:null,measuringScheduled:!1},eM={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:O,draggableNodes:new Map,over:null,measureDroppableContainers:O},ek=(0,p.createContext)(eM),e_=(0,p.createContext)(eC);function eP(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new eE}}}function eA(e,t){switch(t.type){case n.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case n.DragMove:if(null==e.draggable.active)return e;return{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case n.DragEnd:case n.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case n.RegisterDroppable:{let{element:r}=t,{id:n}=r,i=new eE(e.droppable.containers);return i.set(n,r),{...e,droppable:{...e.droppable,containers:i}}}case n.SetDroppableDisabled:{let{id:r,key:n,disabled:i}=t,o=e.droppable.containers.get(r);if(!o||n!==o.key)return e;let a=new eE(e.droppable.containers);return a.set(r,{...o,disabled:i}),{...e,droppable:{...e.droppable,containers:a}}}case n.UnregisterDroppable:{let{id:r,key:n}=t,i=e.droppable.containers.get(r);if(!i||n!==i.key)return e;let o=new eE(e.droppable.containers);return o.delete(r),{...e,droppable:{...e.droppable,containers:o}}}default:return e}}function ej(e){let{disabled:t}=e,{active:r,activatorEvent:n,draggableNodes:i}=(0,p.useContext)(ek),o=(0,g.ZC)(n),a=(0,g.ZC)(null==r?void 0:r.id);return(0,p.useEffect)(()=>{if(!t&&!n&&o&&null!=a){if(!(0,g.kx)(o)||document.activeElement===o.target)return;let e=i.get(a);if(!e)return;let{activatorNode:t,node:r}=e;(t.current||r.current)&&requestAnimationFrame(()=>{for(let e of[t.current,r.current]){if(!e)continue;let t=(0,g.ag)(e);if(t){t.focus();break}}})}},[n,t,i,a,o]),null}let eT=(0,p.createContext)({...M,scaleX:1,scaleY:1});!function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"}(d||(d={}));let eR=(0,p.memo)(function(e){var t,r,o,a,l,f;let{id:v,accessibility:m,autoScroll:y=!0,children:w,sensors:x=ex,collisionDetection:O=j,measuring:E,modifiers:C,...k}=e,[_,P]=(0,p.useReducer)(eA,void 0,eP),[A,z]=function(){let[e]=(0,p.useState)(()=>new Set),t=(0,p.useCallback)(t=>(e.add(t),()=>e.delete(t)),[e]);return[(0,p.useCallback)(t=>{let{type:r,event:n}=t;e.forEach(e=>{var t;return null==(t=e[r])?void 0:t.call(e,n)})},[e]),t]}(),[I,$]=(0,p.useState)(d.Uninitialized),B=I===d.Initialized,{draggable:{active:W,nodes:q,translate:Y},droppable:{containers:X}}=_,J=null!=W?q.get(W):null,Q=(0,p.useRef)({initial:null,translated:null}),ee=(0,p.useMemo)(()=>{var e;return null!=W?{id:W,data:null!=(e=null==J?void 0:J.data)?e:eS,rect:Q}:null},[W,J]),et=(0,p.useRef)(null),[er,en]=(0,p.useState)(null),[ei,eo]=(0,p.useState)(null),ea=(0,g.YN)(k,Object.values(k)),el=(0,g.YG)("DndDescribedBy",v),eu=(0,p.useMemo)(()=>X.getEnabled(),[X]),es=(0,p.useMemo)(()=>({draggable:{...eO.draggable,...null==E?void 0:E.draggable},droppable:{...eO.droppable,...null==E?void 0:E.droppable},dragOverlay:{...eO.dragOverlay,...null==E?void 0:E.dragOverlay}}),[null==E?void 0:E.draggable,null==E?void 0:E.droppable,null==E?void 0:E.dragOverlay]),{droppableRects:ec,measureDroppableContainers:eg,measuringScheduled:eE}=function(e,t){let{dragging:r,dependencies:n,config:i}=t,[o,a]=(0,p.useState)(null),{frequency:l,measure:u,strategy:s}=i,f=(0,p.useRef)(e),d=function(){switch(s){case c.Always:return!1;case c.BeforeDragging:return r;default:return!r}}(),h=(0,g.YN)(d),v=(0,p.useCallback)(function(e){void 0===e&&(e=[]),h.current||a(t=>null===t?e:t.concat(e.filter(e=>!t.includes(e))))},[h]),m=(0,p.useRef)(null),y=(0,g.KG)(t=>{if(d&&!r)return ed;if(!t||t===ed||f.current!==e||null!=o){let t=new Map;for(let r of e){if(!r)continue;if(o&&o.length>0&&!o.includes(r.id)&&r.rect.current){t.set(r.id,r.rect.current);continue}let e=r.node.current,n=e?new K(u(e),e):null;r.rect.current=n,n&&t.set(r.id,n)}return t}return t},[e,o,r,d,u]);return(0,p.useEffect)(()=>{f.current=e},[e]),(0,p.useEffect)(()=>{d||v()},[r,d]),(0,p.useEffect)(()=>{o&&o.length>0&&a(null)},[JSON.stringify(o)]),(0,p.useEffect)(()=>{d||"number"!=typeof l||null!==m.current||(m.current=setTimeout(()=>{v(),m.current=null},l))},[l,d,v,...n]),{droppableRects:y,measureDroppableContainers:v,measuringScheduled:null!=o}}(eu,{dragging:B,dependencies:[Y.x,Y.y],config:es.droppable}),eC=function(e,t){let r=null!=t?e.get(t):void 0,n=r?r.node.current:null;return(0,g.KG)(e=>{var r;return null==t?null:null!=(r=null!=n?n:e)?r:null},[n,t])}(q,W),eM=(0,p.useMemo)(()=>ei?(0,g.e_)(ei):null,[ei]),eR=function(){let e=(null==er?void 0:er.autoScrollEnabled)===!1,t="object"==typeof y?!1===y.enabled:!1===y,r=B&&!e&&!t;return"object"==typeof y?{...y,enabled:r}:{enabled:r}}(),ez=ep(eC,es.draggable.measure);!function(e){let{activeNode:t,measure:r,initialRect:n,config:i=!0}=e,o=(0,p.useRef)(!1),{x:a,y:l}="boolean"==typeof i?{x:i,y:i}:i;(0,g.Es)(()=>{if(!a&&!l||!t){o.current=!1;return}if(o.current||!n)return;let e=null==t?void 0:t.node.current;if(!e||!1===e.isConnected)return;let i=T(r(e),n);if(a||(i.x=0),l||(i.y=0),o.current=!0,Math.abs(i.x)>0||Math.abs(i.y)>0){let t=L(e);t&&t.scrollBy({top:i.y,left:i.x})}},[t,a,l,n,r])}({activeNode:null!=W?q.get(W):null,config:eR.layoutShiftCompensation,initialRect:ez,measure:es.draggable.measure});let eD=ev(eC,es.draggable.measure,ez),eI=ev(eC?eC.parentElement:null),eN=(0,p.useRef)({activatorEvent:null,active:null,activeNode:eC,collisionRect:null,collisions:null,droppableRects:ec,draggableNodes:q,draggingNode:null,draggingNodeRect:null,droppableContainers:X,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),eL=X.getNodeFor(null==(t=eN.current.over)?void 0:t.id),eF=function(e){let{measure:t}=e,[r,n]=(0,p.useState)(null),i=eh({callback:(0,p.useCallback)(e=>{for(let{target:r}of e)if((0,g.sb)(r)){n(e=>{let n=t(r);return e?{...e,width:n.width,height:n.height}:n});break}},[t])}),o=(0,p.useCallback)(e=>{let r=function(e){if(!e)return null;if(e.children.length>1)return e;let t=e.children[0];return(0,g.sb)(t)?t:e}(e);null==i||i.disconnect(),r&&(null==i||i.observe(r)),n(r?t(r):null)},[t,i]),[a,l]=(0,g.lk)(o);return(0,p.useMemo)(()=>({nodeRef:a,rect:r,setRef:l}),[r,a,l])}({measure:es.dragOverlay.measure}),e$=null!=(r=eF.nodeRef.current)?r:eC,eB=B?null!=(o=eF.rect)?o:eD:null,eV=!!(eF.nodeRef.current&&eF.rect),eU=function(e){let t=ep(e);return T(e,t)}(eV?null:eD),eH=eb(e$?(0,g.zk)(e$):null),eG=function(e){let t=(0,p.useRef)(e),r=(0,g.KG)(r=>e?r&&r!==em&&e&&t.current&&e.parentNode===t.current.parentNode?r:N(e):em,[e]);return(0,p.useEffect)(()=>{t.current=e},[e]),r}(B?null!=eL?eL:eC:null),eZ=function(e,t){void 0===t&&(t=D);let[r]=e,n=eb(r?(0,g.zk)(r):null),[i,o]=(0,p.useState)(ew);function a(){o(()=>e.length?e.map(e=>U(e)?n:new K(t(e),e)):ew)}let l=eh({callback:a});return(0,g.Es)(()=>{null==l||l.disconnect(),a(),e.forEach(e=>null==l?void 0:l.observe(e))},[e]),i}(eG),eW=function(e,t){let{transform:r,...n}=t;return null!=e&&e.length?e.reduce((e,t)=>t({transform:e,...n}),r):r}(C,{transform:{x:Y.x-eU.x,y:Y.y-eU.y,scaleX:1,scaleY:1},activatorEvent:ei,active:ee,activeNodeRect:eD,containerNodeRect:eI,draggingNodeRect:eB,over:eN.current.over,overlayNodeRect:eF.rect,scrollableAncestors:eG,scrollableAncestorRects:eZ,windowRect:eH}),eK=eM?(0,g.WQ)(eM,Y):null,eq=function(e){let[t,r]=(0,p.useState)(null),n=(0,p.useRef)(e),i=(0,p.useCallback)(e=>{let t=F(e.target);t&&r(e=>e?(e.set(t,V(t)),new Map(e)):null)},[]);return(0,p.useEffect)(()=>{let t=n.current;if(e!==t){o(t);let a=e.map(e=>{let t=F(e);return t?(t.addEventListener("scroll",i,{passive:!0}),[t,V(t)]):null}).filter(e=>null!=e);r(a.length?new Map(a):null),n.current=e}return()=>{o(e),o(t)};function o(e){e.forEach(e=>{let t=F(e);null==t||t.removeEventListener("scroll",i)})}},[i,e]),(0,p.useMemo)(()=>e.length?t?Array.from(t.values()).reduce((e,t)=>(0,g.WQ)(e,t),M):Z(e):M,[e,t])}(eG),eY=ey(eq),eX=ey(eq,[eD]),eJ=(0,g.WQ)(eW,eY),eQ=eB?R(eB,eW):null,e0=ee&&eQ?O({active:ee,collisionRect:eQ,droppableRects:ec,droppableContainers:eu,pointerCoordinates:eK}):null,e1=function(e,t){if(!e||0===e.length)return null;let[r]=e;return r.id}(e0,"id"),[e2,e5]=(0,p.useState)(null),e6=(l=eV?eW:(0,g.WQ)(eW,eX),f=null!=(a=null==e2?void 0:e2.rect)?a:null,{...l,scaleX:f&&eD?f.width/eD.width:1,scaleY:f&&eD?f.height/eD.height:1}),e4=(0,p.useRef)(null),e3=(0,p.useCallback)((e,t)=>{let{sensor:r,options:i}=t;if(null==et.current)return;let o=q.get(et.current);if(!o)return;let a=e.nativeEvent,l=new r({active:et.current,activeNode:o,event:a,options:i,context:eN,onAbort(e){if(!q.get(e))return;let{onDragAbort:t}=ea.current,r={id:e};null==t||t(r),A({type:"onDragAbort",event:r})},onPending(e,t,r,n){if(!q.get(e))return;let{onDragPending:i}=ea.current,o={id:e,constraint:t,initialCoordinates:r,offset:n};null==i||i(o),A({type:"onDragPending",event:o})},onStart(e){let t=et.current;if(null==t)return;let r=q.get(t);if(!r)return;let{onDragStart:i}=ea.current,o={activatorEvent:a,active:{id:t,data:r.data,rect:Q}};(0,h.unstable_batchedUpdates)(()=>{null==i||i(o),$(d.Initializing),P({type:n.DragStart,initialCoordinates:e,active:t}),A({type:"onDragStart",event:o}),en(e4.current),eo(a)})},onMove(e){P({type:n.DragMove,coordinates:e})},onEnd:u(n.DragEnd),onCancel:u(n.DragCancel)});function u(e){return async function(){let{active:t,collisions:r,over:i,scrollAdjustedTranslate:o}=eN.current,l=null;if(t&&o){let{cancelDrop:u}=ea.current;l={activatorEvent:a,active:t,collisions:r,delta:o,over:i},e===n.DragEnd&&"function"==typeof u&&await Promise.resolve(u(l))&&(e=n.DragCancel)}et.current=null,(0,h.unstable_batchedUpdates)(()=>{P({type:e}),$(d.Uninitialized),e5(null),en(null),eo(null),e4.current=null;let t=e===n.DragEnd?"onDragEnd":"onDragCancel";if(l){let e=ea.current[t];null==e||e(l),A({type:t,event:l})}})}}e4.current=l},[q]),e8=(0,p.useCallback)((e,t)=>(r,n)=>{let i=r.nativeEvent,o=q.get(n);null!==et.current||!o||i.dndKit||i.defaultPrevented||!0===e(r,t.options,{active:o})&&(i.dndKit={capturedBy:t.sensor},et.current=n,e3(r,t))},[q,e3]),e9=(0,p.useMemo)(()=>x.reduce((e,t)=>{let{sensor:r}=t;return[...e,...r.activators.map(e=>({eventName:e.eventName,handler:e8(e.handler,t)}))]},[]),[x,e8]);(0,p.useEffect)(()=>{if(!g.Sw)return;let e=x.map(e=>{let{sensor:t}=e;return null==t.setup?void 0:t.setup()});return()=>{for(let t of e)null==t||t()}},x.map(e=>{let{sensor:t}=e;return t})),(0,g.Es)(()=>{eD&&I===d.Initializing&&$(d.Initialized)},[eD,I]),(0,p.useEffect)(()=>{let{onDragMove:e}=ea.current,{active:t,activatorEvent:r,collisions:n,over:i}=eN.current;if(!t||!r)return;let o={active:t,activatorEvent:r,collisions:n,delta:{x:eJ.x,y:eJ.y},over:i};(0,h.unstable_batchedUpdates)(()=>{null==e||e(o),A({type:"onDragMove",event:o})})},[eJ.x,eJ.y]),(0,p.useEffect)(()=>{let{active:e,activatorEvent:t,collisions:r,droppableContainers:n,scrollAdjustedTranslate:i}=eN.current;if(!e||null==et.current||!t||!i)return;let{onDragOver:o}=ea.current,a=n.get(e1),l=a&&a.rect.current?{id:a.id,rect:a.rect.current,data:a.data,disabled:a.disabled}:null,u={active:e,activatorEvent:t,collisions:r,delta:{x:i.x,y:i.y},over:l};(0,h.unstable_batchedUpdates)(()=>{e5(l),null==o||o(u),A({type:"onDragOver",event:u})})},[e1]),(0,g.Es)(()=>{eN.current={activatorEvent:ei,active:ee,activeNode:eC,collisionRect:eQ,collisions:e0,droppableRects:ec,draggableNodes:q,draggingNode:e$,draggingNodeRect:eB,droppableContainers:X,over:e2,scrollableAncestors:eG,scrollAdjustedTranslate:eJ},Q.current={initial:eB,translated:eQ}},[ee,eC,e0,eQ,q,e$,eB,ec,X,e2,eG,eJ]),function(e){let{acceleration:t,activator:r=u.Pointer,canScroll:n,draggingRect:o,enabled:a,interval:l=5,order:c=s.TreeOrder,pointerCoordinates:f,scrollableAncestors:d,scrollableAncestorRects:h,delta:v,threshold:m}=e,y=function(e){let{delta:t,disabled:r}=e,n=(0,g.ZC)(t);return(0,g.KG)(e=>{if(r||!n||!e)return ef;let o={x:Math.sign(t.x-n.x),y:Math.sign(t.y-n.y)};return{x:{[i.Backward]:e.x[i.Backward]||-1===o.x,[i.Forward]:e.x[i.Forward]||1===o.x},y:{[i.Backward]:e.y[i.Backward]||-1===o.y,[i.Forward]:e.y[i.Forward]||1===o.y}}},[r,t,n])}({delta:v,disabled:!a}),[b,w]=(0,g.$$)(),x=(0,p.useRef)({x:0,y:0}),S=(0,p.useRef)({x:0,y:0}),O=(0,p.useMemo)(()=>{switch(r){case u.Pointer:return f?{top:f.y,bottom:f.y,left:f.x,right:f.x}:null;case u.DraggableRect:return o}},[r,o,f]),E=(0,p.useRef)(null),C=(0,p.useCallback)(()=>{let e=E.current;if(!e)return;let t=x.current.x*S.current.x,r=x.current.y*S.current.y;e.scrollBy(t,r)},[]),M=(0,p.useMemo)(()=>c===s.TreeOrder?[...d].reverse():d,[c,d]);(0,p.useEffect)(()=>{if(!a||!d.length||!O)return void w();for(let e of M){if((null==n?void 0:n(e))===!1)continue;let r=h[d.indexOf(e)];if(!r)continue;let{direction:o,speed:a}=function(e,t,r,n,o){let{top:a,left:l,right:u,bottom:s}=r;void 0===n&&(n=10),void 0===o&&(o=G);let{isTop:c,isBottom:f,isLeft:d,isRight:p}=H(e),h={x:0,y:0},g={x:0,y:0},v={height:t.height*o.y,width:t.width*o.x};return!c&&a<=t.top+v.height?(h.y=i.Backward,g.y=n*Math.abs((t.top+v.height-a)/v.height)):!f&&s>=t.bottom-v.height&&(h.y=i.Forward,g.y=n*Math.abs((t.bottom-v.height-s)/v.height)),!p&&u>=t.right-v.width?(h.x=i.Forward,g.x=n*Math.abs((t.right-v.width-u)/v.width)):!d&&l<=t.left+v.width&&(h.x=i.Backward,g.x=n*Math.abs((t.left+v.width-l)/v.width)),{direction:h,speed:g}}(e,r,O,t,m);for(let e of["x","y"])y[e][o[e]]||(a[e]=0,o[e]=0);if(a.x>0||a.y>0){w(),E.current=e,b(C,l),x.current=a,S.current=o;return}}x.current={x:0,y:0},S.current={x:0,y:0},w()},[t,C,n,w,a,l,JSON.stringify(O),JSON.stringify(y),b,d,M,h,JSON.stringify(m)])}({...eR,delta:Y,draggingRect:eQ,pointerCoordinates:eK,scrollableAncestors:eG,scrollableAncestorRects:eZ});let e7=(0,p.useMemo)(()=>({active:ee,activeNode:eC,activeNodeRect:eD,activatorEvent:ei,collisions:e0,containerNodeRect:eI,dragOverlay:eF,draggableNodes:q,droppableContainers:X,droppableRects:ec,over:e2,measureDroppableContainers:eg,scrollableAncestors:eG,scrollableAncestorRects:eZ,measuringConfiguration:es,measuringScheduled:eE,windowRect:eH}),[ee,eC,eD,ei,e0,eI,eF,q,X,ec,e2,eg,eG,eZ,es,eE,eH]),te=(0,p.useMemo)(()=>({activatorEvent:ei,activators:e9,active:ee,activeNodeRect:eD,ariaDescribedById:{draggable:el},dispatch:P,draggableNodes:q,over:e2,measureDroppableContainers:eg}),[ei,e9,ee,eD,P,el,q,e2,eg]);return p.createElement(b.Provider,{value:z},p.createElement(ek.Provider,{value:te},p.createElement(e_.Provider,{value:e7},p.createElement(eT.Provider,{value:e6},w)),p.createElement(ej,{disabled:(null==m?void 0:m.restoreFocus)===!1})),p.createElement(S,{...m,hiddenTextDescribedById:el}))}),ez=(0,p.createContext)(null),eD="button";function eI(e){let{id:t,data:r,disabled:n=!1,attributes:i}=e,o=(0,g.YG)("Draggable"),{activators:a,activatorEvent:l,active:u,activeNodeRect:s,ariaDescribedById:c,draggableNodes:f,over:d}=(0,p.useContext)(ek),{role:h=eD,roleDescription:v="draggable",tabIndex:m=0}=null!=i?i:{},y=(null==u?void 0:u.id)===t,b=(0,p.useContext)(y?eT:ez),[w,x]=(0,g.lk)(),[S,O]=(0,g.lk)(),E=(0,p.useMemo)(()=>a.reduce((e,r)=>{let{eventName:n,handler:i}=r;return e[n]=e=>{i(e,t)},e},{}),[a,t]),C=(0,g.YN)(r);return(0,g.Es)(()=>(f.set(t,{id:t,key:o,node:w,activatorNode:S,data:C}),()=>{let e=f.get(t);e&&e.key===o&&f.delete(t)}),[f,t]),{active:u,activatorEvent:l,activeNodeRect:s,attributes:(0,p.useMemo)(()=>({role:h,tabIndex:m,"aria-disabled":n,"aria-pressed":!!y&&h===eD||void 0,"aria-roledescription":v,"aria-describedby":c.draggable}),[n,h,m,y,v,c.draggable]),isDragging:y,listeners:n?void 0:E,node:w,over:d,setNodeRef:x,setActivatorNodeRef:O,transform:b}}function eN(){return(0,p.useContext)(e_)}let eL={timeout:25};function eF(e){let{data:t,disabled:r=!1,id:i,resizeObserverConfig:o}=e,a=(0,g.YG)("Droppable"),{active:l,dispatch:u,over:s,measureDroppableContainers:c}=(0,p.useContext)(ek),f=(0,p.useRef)({disabled:r}),d=(0,p.useRef)(!1),h=(0,p.useRef)(null),v=(0,p.useRef)(null),{disabled:m,updateMeasurementsFor:y,timeout:b}={...eL,...o},w=(0,g.YN)(null!=y?y:i),x=eh({callback:(0,p.useCallback)(()=>{if(!d.current){d.current=!0;return}null!=v.current&&clearTimeout(v.current),v.current=setTimeout(()=>{c(Array.isArray(w.current)?w.current:[w.current]),v.current=null},b)},[b]),disabled:m||!l}),S=(0,p.useCallback)((e,t)=>{x&&(t&&(x.unobserve(t),d.current=!1),e&&x.observe(e))},[x]),[O,E]=(0,g.lk)(S),C=(0,g.YN)(t);return(0,p.useEffect)(()=>{x&&O.current&&(x.disconnect(),d.current=!1,x.observe(O.current))},[O,x]),(0,p.useEffect)(()=>(u({type:n.RegisterDroppable,element:{id:i,key:a,disabled:r,node:O,rect:h,data:C}}),()=>u({type:n.UnregisterDroppable,key:a,id:i})),[i]),(0,p.useEffect)(()=>{r!==f.current.disabled&&(u({type:n.SetDroppableDisabled,id:i,key:a,disabled:r}),f.current.disabled=r)},[i,a,r,u]),{active:l,rect:h,isOver:(null==s?void 0:s.id)===i,node:O,over:s,setNodeRef:E}}},5146:(e,t,r)=>{"use strict";r.d(t,{q:()=>n});var n=(e,t,r,n)=>{var i;return"axis"===t?e.tooltipItemPayloads:0===e.tooltipItemPayloads.length?[]:null==(i="hover"===r?e.itemInteraction.hover.dataKey:e.itemInteraction.click.dataKey)&&null!=n?[e.tooltipItemPayloads[0]]:e.tooltipItemPayloads.filter(e=>{var t;return(null==(t=e.settings)?void 0:t.dataKey)===i})}},5148:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.noop=function(){}},5160:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.getTag=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":Object.prototype.toString.call(e)}},5181:(e,t)=>{"use strict";function r(e){return"symbol"==typeof e?1:null===e?2:void 0===e?3:4*(e!=e)}Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.compareValues=(e,t,n)=>{if(e!==t){let i=r(e),o=r(t);if(i===o&&0===i){if(et)return"desc"===n?-1:1}return"desc"===n?o-i:i-o}return 0}},5185:(e,t,r)=>{"use strict";function n(e,t,{checkForDefaultPrevented:r=!0}={}){return function(n){if(e?.(n),!1===r||!n.defaultPrevented)return t?.(n)}}r.d(t,{m:()=>n})},5196:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]])},5252:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(2520),i=r(5148);t.isEqual=function(e,t){return n.isEqualWith(e,t,i.noop)}},5525:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("shield",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]])},5604:(e,t,r)=>{"use strict";function n(e,t,r){function n(r,n){var i;for(let o in Object.defineProperty(r,"_zod",{value:r._zod??{},enumerable:!1}),(i=r._zod).traits??(i.traits=new Set),r._zod.traits.add(e),t(r,n),a.prototype)o in r||Object.defineProperty(r,o,{value:a.prototype[o].bind(r)});r._zod.constr=a,r._zod.def=n}let i=r?.Parent??Object;class o extends i{}function a(e){var t;let i=r?.Parent?new o:this;for(let r of(n(i,e),(t=i._zod).deferred??(t.deferred=[]),i._zod.deferred))r();return i}return Object.defineProperty(o,"name",{value:e}),Object.defineProperty(a,"init",{value:n}),Object.defineProperty(a,Symbol.hasInstance,{value:t=>!!r?.Parent&&t instanceof r.Parent||t?._zod?.traits?.has(e)}),Object.defineProperty(a,"name",{value:e}),a}r.d(t,{EB:()=>tZ,YO:()=>rd,zM:()=>rl,k5:()=>rw,ai:()=>rn,Ik:()=>rh,g1:()=>ry,Yj:()=>tG}),Object.freeze({status:"aborted"}),Symbol("zod_brand");class i extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}let o={};function a(e){return e&&Object.assign(o,e),o}let l=/^[cC][^\s-]{8,}$/,u=/^[0-9a-z]+$/,s=/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,c=/^[0-9a-vA-V]{20}$/,f=/^[A-Za-z0-9]{27}$/,d=/^[a-zA-Z0-9_-]{21}$/,p=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,h=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,g=e=>e?RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$/,v=/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,m=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,y=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})$/,b=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,w=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,x=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,S=/^[A-Za-z0-9_-]*$/,O=/^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/,E=/^\+(?:[0-9]){6,14}[0-9]$/,C="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))",M=RegExp(`^${C}$`);function k(e){let t="(?:[01]\\d|2[0-3]):[0-5]\\d";return"number"==typeof e.precision?-1===e.precision?`${t}`:0===e.precision?`${t}:[0-5]\\d`:`${t}:[0-5]\\d\\.\\d{${e.precision}}`:`${t}(?::[0-5]\\d(?:\\.\\d+)?)?`}let _=/^\d+$/,P=/^-?\d+(?:\.\d+)?/i,A=/true|false/i,j=/^[^A-Z]*$/,T=/^[^a-z]*$/;function R(e,t){return"bigint"==typeof t?t.toString():t}function z(e){return{get value(){{let t=e();return Object.defineProperty(this,"value",{value:t}),t}}}}function D(e){let t=+!!e.startsWith("^"),r=e.endsWith("$")?e.length-1:e.length;return e.slice(t,r)}function I(e,t,r){Object.defineProperty(e,t,{get(){{let n=r();return e[t]=n,n}},set(r){Object.defineProperty(e,t,{value:r})},configurable:!0})}function N(e,t,r){Object.defineProperty(e,t,{value:r,writable:!0,enumerable:!0,configurable:!0})}function L(e){return JSON.stringify(e)}let F=Error.captureStackTrace?Error.captureStackTrace:(...e)=>{};function $(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}let B=z(()=>{if("undefined"!=typeof navigator&&navigator?.userAgent?.includes("Cloudflare"))return!1;try{return Function(""),!0}catch(e){return!1}});function V(e){if(!1===$(e))return!1;let t=e.constructor;if(void 0===t)return!0;let r=t.prototype;return!1!==$(r)&&!1!==Object.prototype.hasOwnProperty.call(r,"isPrototypeOf")}let U=new Set(["string","number","symbol"]);function H(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function G(e,t,r){let n=new e._zod.constr(t??e._zod.def);return(!t||r?.parent)&&(n._zod.parent=e),n}function Z(e){if(!e)return{};if("string"==typeof e)return{error:()=>e};if(e?.message!==void 0){if(e?.error!==void 0)throw Error("Cannot specify both `message` and `error` params");e.error=e.message}return(delete e.message,"string"==typeof e.error)?{...e,error:()=>e.error}:e}let W={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-0x80000000,0x7fffffff],uint32:[0,0xffffffff],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]};function K(e,t=0){for(let r=t;r(t.path??(t.path=[]),t.path.unshift(e),t))}function Y(e){return"string"==typeof e?e:e?.message}function X(e,t,r){let n={...e,path:e.path??[]};return e.message||(n.message=Y(e.inst?._zod.def?.error?.(e))??Y(t?.error?.(e))??Y(r.customError?.(e))??Y(r.localeError?.(e))??"Invalid input"),delete n.inst,delete n.continue,t?.reportInput||delete n.input,n}function J(e){return Array.isArray(e)?"array":"string"==typeof e?"string":"unknown"}function Q(...e){let[t,r,n]=e;return"string"==typeof t?{message:t,code:"custom",input:r,inst:n}:{...t}}let ee=n("$ZodCheck",(e,t)=>{var r;e._zod??(e._zod={}),e._zod.def=t,(r=e._zod).onattach??(r.onattach=[])}),et={number:"number",bigint:"bigint",object:"date"},er=n("$ZodCheckLessThan",(e,t)=>{ee.init(e,t);let r=et[typeof t.value];e._zod.onattach.push(e=>{let r=e._zod.bag,n=(t.inclusive?r.maximum:r.exclusiveMaximum)??1/0;t.value{(t.inclusive?n.value<=t.value:n.value{ee.init(e,t);let r=et[typeof t.value];e._zod.onattach.push(e=>{let r=e._zod.bag,n=(t.inclusive?r.minimum:r.exclusiveMinimum)??-1/0;t.value>n&&(t.inclusive?r.minimum=t.value:r.exclusiveMinimum=t.value)}),e._zod.check=n=>{(t.inclusive?n.value>=t.value:n.value>t.value)||n.issues.push({origin:r,code:"too_small",minimum:t.value,input:n.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),ei=n("$ZodCheckMultipleOf",(e,t)=>{ee.init(e,t),e._zod.onattach.push(e=>{var r;(r=e._zod.bag).multipleOf??(r.multipleOf=t.value)}),e._zod.check=r=>{if(typeof r.value!=typeof t.value)throw Error("Cannot mix number and bigint in multiple_of check.");("bigint"==typeof r.value?r.value%t.value===BigInt(0):0===function(e,t){let r=(e.toString().split(".")[1]||"").length,n=(t.toString().split(".")[1]||"").length,i=r>n?r:n;return Number.parseInt(e.toFixed(i).replace(".",""))%Number.parseInt(t.toFixed(i).replace(".",""))/10**i}(r.value,t.value))||r.issues.push({origin:typeof r.value,code:"not_multiple_of",divisor:t.value,input:r.value,inst:e,continue:!t.abort})}}),eo=n("$ZodCheckNumberFormat",(e,t)=>{ee.init(e,t),t.format=t.format||"float64";let r=t.format?.includes("int"),n=r?"int":"number",[i,o]=W[t.format];e._zod.onattach.push(e=>{let n=e._zod.bag;n.format=t.format,n.minimum=i,n.maximum=o,r&&(n.pattern=_)}),e._zod.check=a=>{let l=a.value;if(r){if(!Number.isInteger(l))return void a.issues.push({expected:n,format:t.format,code:"invalid_type",input:l,inst:e});if(!Number.isSafeInteger(l))return void(l>0?a.issues.push({input:l,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:n,continue:!t.abort}):a.issues.push({input:l,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:n,continue:!t.abort}))}lo&&a.issues.push({origin:"number",input:l,code:"too_big",maximum:o,inst:e})}}),ea=n("$ZodCheckMaxLength",(e,t)=>{var r;ee.init(e,t),(r=e._zod.def).when??(r.when=e=>{let t=e.value;return null!=t&&void 0!==t.length}),e._zod.onattach.push(e=>{let r=e._zod.bag.maximum??1/0;t.maximum{let n=r.value;if(n.length<=t.maximum)return;let i=J(n);r.issues.push({origin:i,code:"too_big",maximum:t.maximum,inclusive:!0,input:n,inst:e,continue:!t.abort})}}),el=n("$ZodCheckMinLength",(e,t)=>{var r;ee.init(e,t),(r=e._zod.def).when??(r.when=e=>{let t=e.value;return null!=t&&void 0!==t.length}),e._zod.onattach.push(e=>{let r=e._zod.bag.minimum??-1/0;t.minimum>r&&(e._zod.bag.minimum=t.minimum)}),e._zod.check=r=>{let n=r.value;if(n.length>=t.minimum)return;let i=J(n);r.issues.push({origin:i,code:"too_small",minimum:t.minimum,inclusive:!0,input:n,inst:e,continue:!t.abort})}}),eu=n("$ZodCheckLengthEquals",(e,t)=>{var r;ee.init(e,t),(r=e._zod.def).when??(r.when=e=>{let t=e.value;return null!=t&&void 0!==t.length}),e._zod.onattach.push(e=>{let r=e._zod.bag;r.minimum=t.length,r.maximum=t.length,r.length=t.length}),e._zod.check=r=>{let n=r.value,i=n.length;if(i===t.length)return;let o=J(n),a=i>t.length;r.issues.push({origin:o,...a?{code:"too_big",maximum:t.length}:{code:"too_small",minimum:t.length},inclusive:!0,exact:!0,input:r.value,inst:e,continue:!t.abort})}}),es=n("$ZodCheckStringFormat",(e,t)=>{var r,n;ee.init(e,t),e._zod.onattach.push(e=>{let r=e._zod.bag;r.format=t.format,t.pattern&&(r.patterns??(r.patterns=new Set),r.patterns.add(t.pattern))}),t.pattern?(r=e._zod).check??(r.check=r=>{t.pattern.lastIndex=0,t.pattern.test(r.value)||r.issues.push({origin:"string",code:"invalid_format",format:t.format,input:r.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(n=e._zod).check??(n.check=()=>{})}),ec=n("$ZodCheckRegex",(e,t)=>{es.init(e,t),e._zod.check=r=>{t.pattern.lastIndex=0,t.pattern.test(r.value)||r.issues.push({origin:"string",code:"invalid_format",format:"regex",input:r.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),ef=n("$ZodCheckLowerCase",(e,t)=>{t.pattern??(t.pattern=j),es.init(e,t)}),ed=n("$ZodCheckUpperCase",(e,t)=>{t.pattern??(t.pattern=T),es.init(e,t)}),ep=n("$ZodCheckIncludes",(e,t)=>{ee.init(e,t);let r=H(t.includes),n=new RegExp("number"==typeof t.position?`^.{${t.position}}${r}`:r);t.pattern=n,e._zod.onattach.push(e=>{let t=e._zod.bag;t.patterns??(t.patterns=new Set),t.patterns.add(n)}),e._zod.check=r=>{r.value.includes(t.includes,t.position)||r.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:t.includes,input:r.value,inst:e,continue:!t.abort})}}),eh=n("$ZodCheckStartsWith",(e,t)=>{ee.init(e,t);let r=RegExp(`^${H(t.prefix)}.*`);t.pattern??(t.pattern=r),e._zod.onattach.push(e=>{let t=e._zod.bag;t.patterns??(t.patterns=new Set),t.patterns.add(r)}),e._zod.check=r=>{r.value.startsWith(t.prefix)||r.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:t.prefix,input:r.value,inst:e,continue:!t.abort})}}),eg=n("$ZodCheckEndsWith",(e,t)=>{ee.init(e,t);let r=RegExp(`.*${H(t.suffix)}$`);t.pattern??(t.pattern=r),e._zod.onattach.push(e=>{let t=e._zod.bag;t.patterns??(t.patterns=new Set),t.patterns.add(r)}),e._zod.check=r=>{r.value.endsWith(t.suffix)||r.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:t.suffix,input:r.value,inst:e,continue:!t.abort})}}),ev=n("$ZodCheckOverwrite",(e,t)=>{ee.init(e,t),e._zod.check=e=>{e.value=t.tx(e.value)}});class em{constructor(e=[]){this.content=[],this.indent=0,this&&(this.args=e)}indented(e){this.indent+=1,e(this),this.indent-=1}write(e){if("function"==typeof e){e(this,{execution:"sync"}),e(this,{execution:"async"});return}let t=e.split("\n").filter(e=>e),r=Math.min(...t.map(e=>e.length-e.trimStart().length));for(let e of t.map(e=>e.slice(r)).map(e=>" ".repeat(2*this.indent)+e))this.content.push(e)}compile(){return Function(...this?.args,[...(this?.content??[""]).map(e=>` ${e}`)].join("\n"))}}let ey=(e,t)=>{e.name="$ZodError",Object.defineProperty(e,"_zod",{value:e._zod,enumerable:!1}),Object.defineProperty(e,"issues",{value:t,enumerable:!1}),Object.defineProperty(e,"message",{get:()=>JSON.stringify(t,R,2),enumerable:!0}),Object.defineProperty(e,"toString",{value:()=>e.message,enumerable:!1})},eb=n("$ZodError",ey),ew=n("$ZodError",ey,{Parent:Error}),ex=e=>(t,r,n)=>{let o=n?{...n,async:!1}:{async:!1},l=t._zod.run({value:r,issues:[]},o);if(l instanceof Promise)throw new i;return l.issues.length?{success:!1,error:new(e??eb)(l.issues.map(e=>X(e,o,a())))}:{success:!0,data:l.value}},eS=ex(ew),eO=e=>async(t,r,n)=>{let i=n?Object.assign(n,{async:!0}):{async:!0},o=t._zod.run({value:r,issues:[]},i);return o instanceof Promise&&(o=await o),o.issues.length?{success:!1,error:new e(o.issues.map(e=>X(e,i,a())))}:{success:!0,data:o.value}},eE=eO(ew),eC={major:4,minor:0,patch:5},eM=n("$ZodType",(e,t)=>{var r;e??(e={}),e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=eC;let n=[...e._zod.def.checks??[]];for(let t of(e._zod.traits.has("$ZodCheck")&&n.unshift(e),n))for(let r of t._zod.onattach)r(e);if(0===n.length)(r=e._zod).deferred??(r.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,r)=>{let n,o=K(e);for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(o)continue;let t=e.issues.length,l=a._zod.check(e);if(l instanceof Promise&&r?.async===!1)throw new i;if(n||l instanceof Promise)n=(n??Promise.resolve()).then(async()=>{await l,e.issues.length!==t&&(o||(o=K(e,t)))});else{if(e.issues.length===t)continue;o||(o=K(e,t))}}return n?n.then(()=>e):e};e._zod.run=(r,o)=>{let a=e._zod.parse(r,o);if(a instanceof Promise){if(!1===o.async)throw new i;return a.then(e=>t(e,n,o))}return t(a,n,o)}}e["~standard"]={validate:t=>{try{let r=eS(e,t);return r.success?{value:r.data}:{issues:r.error?.issues}}catch(r){return eE(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:"zod",version:1}}),ek=n("$ZodString",(e,t)=>{eM.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??(e=>{let t=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??""}}`:"[\\s\\S]*";return RegExp(`^${t}$`)})(e._zod.bag),e._zod.parse=(r,n)=>{if(t.coerce)try{r.value=String(r.value)}catch(e){}return"string"==typeof r.value||r.issues.push({expected:"string",code:"invalid_type",input:r.value,inst:e}),r}}),e_=n("$ZodStringFormat",(e,t)=>{es.init(e,t),ek.init(e,t)}),eP=n("$ZodGUID",(e,t)=>{t.pattern??(t.pattern=h),e_.init(e,t)}),eA=n("$ZodUUID",(e,t)=>{if(t.version){let e={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[t.version];if(void 0===e)throw Error(`Invalid UUID version: "${t.version}"`);t.pattern??(t.pattern=g(e))}else t.pattern??(t.pattern=g());e_.init(e,t)}),ej=n("$ZodEmail",(e,t)=>{t.pattern??(t.pattern=v),e_.init(e,t)}),eT=n("$ZodURL",(e,t)=>{e_.init(e,t),e._zod.check=r=>{try{let n=r.value,i=new URL(n),o=i.href;t.hostname&&(t.hostname.lastIndex=0,t.hostname.test(i.hostname)||r.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:O.source,input:r.value,inst:e,continue:!t.abort})),t.protocol&&(t.protocol.lastIndex=0,t.protocol.test(i.protocol.endsWith(":")?i.protocol.slice(0,-1):i.protocol)||r.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:t.protocol.source,input:r.value,inst:e,continue:!t.abort})),!n.endsWith("/")&&o.endsWith("/")?r.value=o.slice(0,-1):r.value=o;return}catch(n){r.issues.push({code:"invalid_format",format:"url",input:r.value,inst:e,continue:!t.abort})}}}),eR=n("$ZodEmoji",(e,t)=>{t.pattern??(t.pattern=RegExp("^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$","u")),e_.init(e,t)}),ez=n("$ZodNanoID",(e,t)=>{t.pattern??(t.pattern=d),e_.init(e,t)}),eD=n("$ZodCUID",(e,t)=>{t.pattern??(t.pattern=l),e_.init(e,t)}),eI=n("$ZodCUID2",(e,t)=>{t.pattern??(t.pattern=u),e_.init(e,t)}),eN=n("$ZodULID",(e,t)=>{t.pattern??(t.pattern=s),e_.init(e,t)}),eL=n("$ZodXID",(e,t)=>{t.pattern??(t.pattern=c),e_.init(e,t)}),eF=n("$ZodKSUID",(e,t)=>{t.pattern??(t.pattern=f),e_.init(e,t)}),e$=n("$ZodISODateTime",(e,t)=>{t.pattern??(t.pattern=function(e){let t=k({precision:e.precision}),r=["Z"];e.local&&r.push(""),e.offset&&r.push("([+-]\\d{2}:\\d{2})");let n=`${t}(?:${r.join("|")})`;return RegExp(`^${C}T(?:${n})$`)}(t)),e_.init(e,t)}),eB=n("$ZodISODate",(e,t)=>{t.pattern??(t.pattern=M),e_.init(e,t)}),eV=n("$ZodISOTime",(e,t)=>{t.pattern??(t.pattern=RegExp(`^${k(t)}$`)),e_.init(e,t)}),eU=n("$ZodISODuration",(e,t)=>{t.pattern??(t.pattern=p),e_.init(e,t)}),eH=n("$ZodIPv4",(e,t)=>{t.pattern??(t.pattern=m),e_.init(e,t),e._zod.onattach.push(e=>{e._zod.bag.format="ipv4"})}),eG=n("$ZodIPv6",(e,t)=>{t.pattern??(t.pattern=y),e_.init(e,t),e._zod.onattach.push(e=>{e._zod.bag.format="ipv6"}),e._zod.check=r=>{try{new URL(`http://[${r.value}]`)}catch{r.issues.push({code:"invalid_format",format:"ipv6",input:r.value,inst:e,continue:!t.abort})}}}),eZ=n("$ZodCIDRv4",(e,t)=>{t.pattern??(t.pattern=b),e_.init(e,t)}),eW=n("$ZodCIDRv6",(e,t)=>{t.pattern??(t.pattern=w),e_.init(e,t),e._zod.check=r=>{let[n,i]=r.value.split("/");try{if(!i)throw Error();let e=Number(i);if(`${e}`!==i||e<0||e>128)throw Error();new URL(`http://[${n}]`)}catch{r.issues.push({code:"invalid_format",format:"cidrv6",input:r.value,inst:e,continue:!t.abort})}}});function eK(e){if(""===e)return!0;if(e.length%4!=0)return!1;try{return atob(e),!0}catch{return!1}}let eq=n("$ZodBase64",(e,t)=>{t.pattern??(t.pattern=x),e_.init(e,t),e._zod.onattach.push(e=>{e._zod.bag.contentEncoding="base64"}),e._zod.check=r=>{eK(r.value)||r.issues.push({code:"invalid_format",format:"base64",input:r.value,inst:e,continue:!t.abort})}}),eY=n("$ZodBase64URL",(e,t)=>{t.pattern??(t.pattern=S),e_.init(e,t),e._zod.onattach.push(e=>{e._zod.bag.contentEncoding="base64url"}),e._zod.check=r=>{!function(e){if(!S.test(e))return!1;let t=e.replace(/[-_]/g,e=>"-"===e?"+":"/");return eK(t.padEnd(4*Math.ceil(t.length/4),"="))}(r.value)&&r.issues.push({code:"invalid_format",format:"base64url",input:r.value,inst:e,continue:!t.abort})}}),eX=n("$ZodE164",(e,t)=>{t.pattern??(t.pattern=E),e_.init(e,t)}),eJ=n("$ZodJWT",(e,t)=>{e_.init(e,t),e._zod.check=r=>{!function(e,t=null){try{let r=e.split(".");if(3!==r.length)return!1;let[n]=r;if(!n)return!1;let i=JSON.parse(atob(n));if("typ"in i&&i?.typ!=="JWT"||!i.alg||t&&(!("alg"in i)||i.alg!==t))return!1;return!0}catch{return!1}}(r.value,t.alg)&&r.issues.push({code:"invalid_format",format:"jwt",input:r.value,inst:e,continue:!t.abort})}}),eQ=n("$ZodNumber",(e,t)=>{eM.init(e,t),e._zod.pattern=e._zod.bag.pattern??P,e._zod.parse=(r,n)=>{if(t.coerce)try{r.value=Number(r.value)}catch(e){}let i=r.value;if("number"==typeof i&&!Number.isNaN(i)&&Number.isFinite(i))return r;let o="number"==typeof i?Number.isNaN(i)?"NaN":Number.isFinite(i)?void 0:"Infinity":void 0;return r.issues.push({expected:"number",code:"invalid_type",input:i,inst:e,...o?{received:o}:{}}),r}}),e0=n("$ZodNumber",(e,t)=>{eo.init(e,t),eQ.init(e,t)}),e1=n("$ZodBoolean",(e,t)=>{eM.init(e,t),e._zod.pattern=A,e._zod.parse=(r,n)=>{if(t.coerce)try{r.value=!!r.value}catch(e){}let i=r.value;return"boolean"==typeof i||r.issues.push({expected:"boolean",code:"invalid_type",input:i,inst:e}),r}}),e2=n("$ZodUnknown",(e,t)=>{eM.init(e,t),e._zod.parse=e=>e}),e5=n("$ZodNever",(e,t)=>{eM.init(e,t),e._zod.parse=(t,r)=>(t.issues.push({expected:"never",code:"invalid_type",input:t.value,inst:e}),t)});function e6(e,t,r){e.issues.length&&t.issues.push(...q(r,e.issues)),t.value[r]=e.value}let e4=n("$ZodArray",(e,t)=>{eM.init(e,t),e._zod.parse=(r,n)=>{let i=r.value;if(!Array.isArray(i))return r.issues.push({expected:"array",code:"invalid_type",input:i,inst:e}),r;r.value=Array(i.length);let o=[];for(let e=0;ee6(t,r,e))):e6(l,r,e)}return o.length?Promise.all(o).then(()=>r):r}});function e3(e,t,r){e.issues.length&&t.issues.push(...q(r,e.issues)),t.value[r]=e.value}function e8(e,t,r,n){e.issues.length?void 0===n[r]?r in n?t.value[r]=void 0:t.value[r]=e.value:t.issues.push(...q(r,e.issues)):void 0===e.value?r in n&&(t.value[r]=void 0):t.value[r]=e.value}let e9=n("$ZodObject",(e,t)=>{let r,n;eM.init(e,t);let i=z(()=>{let e=Object.keys(t.shape);for(let r of e)if(!(t.shape[r]instanceof eM))throw Error(`Invalid element at key "${r}": expected a Zod schema`);let r=function(e){return Object.keys(e).filter(t=>"optional"===e[t]._zod.optin&&"optional"===e[t]._zod.optout)}(t.shape);return{shape:t.shape,keys:e,keySet:new Set(e),numKeys:e.length,optionalKeys:new Set(r)}});I(e._zod,"propValues",()=>{let e=t.shape,r={};for(let t in e){let n=e[t]._zod;if(n.values)for(let e of(r[t]??(r[t]=new Set),n.values))r[t].add(e)}return r});let a=!o.jitless,l=a&&B.value,u=t.catchall;e._zod.parse=(o,s)=>{n??(n=i.value);let c=o.value;if(!$(c))return o.issues.push({expected:"object",code:"invalid_type",input:c,inst:e}),o;let f=[];if(a&&l&&s?.async===!1&&!0!==s.jitless)r||(r=(e=>{let t=new em(["shape","payload","ctx"]),r=i.value,n=e=>{let t=L(e);return`shape[${t}]._zod.run({ value: input[${t}], issues: [] }, ctx)`};t.write("const input = payload.value;");let o=Object.create(null),a=0;for(let e of r.keys)o[e]=`key_${a++}`;for(let e of(t.write("const newResult = {}"),r.keys))if(r.optionalKeys.has(e)){let r=o[e];t.write(`const ${r} = ${n(e)};`);let i=L(e);t.write(` + if (${r}.issues.length) { + if (input[${i}] === undefined) { + if (${i} in input) { + newResult[${i}] = undefined; + } + } else { + payload.issues = payload.issues.concat( + ${r}.issues.map((iss) => ({ + ...iss, + path: iss.path ? [${i}, ...iss.path] : [${i}], + })) + ); + } + } else if (${r}.value === undefined) { + if (${i} in input) newResult[${i}] = undefined; + } else { + newResult[${i}] = ${r}.value; + } + `)}else{let r=o[e];t.write(`const ${r} = ${n(e)};`),t.write(` + if (${r}.issues.length) payload.issues = payload.issues.concat(${r}.issues.map(iss => ({ + ...iss, + path: iss.path ? [${L(e)}, ...iss.path] : [${L(e)}] + })));`),t.write(`newResult[${L(e)}] = ${r}.value`)}t.write("payload.value = newResult;"),t.write("return payload;");let l=t.compile();return(t,r)=>l(e,t,r)})(t.shape)),o=r(o,s);else{o.value={};let e=n.shape;for(let t of n.keys){let r=e[t],n=r._zod.run({value:c[t],issues:[]},s),i="optional"===r._zod.optin&&"optional"===r._zod.optout;n instanceof Promise?f.push(n.then(e=>i?e8(e,o,t,c):e3(e,o,t))):i?e8(n,o,t,c):e3(n,o,t)}}if(!u)return f.length?Promise.all(f).then(()=>o):o;let d=[],p=n.keySet,h=u._zod,g=h.def.type;for(let e of Object.keys(c)){if(p.has(e))continue;if("never"===g){d.push(e);continue}let t=h.run({value:c[e],issues:[]},s);t instanceof Promise?f.push(t.then(t=>e3(t,o,e))):e3(t,o,e)}return(d.length&&o.issues.push({code:"unrecognized_keys",keys:d,input:c,inst:e}),f.length)?Promise.all(f).then(()=>o):o}});function e7(e,t,r,n){for(let r of e)if(0===r.issues.length)return t.value=r.value,t;return t.issues.push({code:"invalid_union",input:t.value,inst:r,errors:e.map(e=>e.issues.map(e=>X(e,n,a())))}),t}let te=n("$ZodUnion",(e,t)=>{eM.init(e,t),I(e._zod,"optin",()=>t.options.some(e=>"optional"===e._zod.optin)?"optional":void 0),I(e._zod,"optout",()=>t.options.some(e=>"optional"===e._zod.optout)?"optional":void 0),I(e._zod,"values",()=>{if(t.options.every(e=>e._zod.values))return new Set(t.options.flatMap(e=>Array.from(e._zod.values)))}),I(e._zod,"pattern",()=>{if(t.options.every(e=>e._zod.pattern)){let e=t.options.map(e=>e._zod.pattern);return RegExp(`^(${e.map(e=>D(e.source)).join("|")})$`)}}),e._zod.parse=(r,n)=>{let i=!1,o=[];for(let e of t.options){let t=e._zod.run({value:r.value,issues:[]},n);if(t instanceof Promise)o.push(t),i=!0;else{if(0===t.issues.length)return t;o.push(t)}}return i?Promise.all(o).then(t=>e7(t,r,e,n)):e7(o,r,e,n)}}),tt=n("$ZodIntersection",(e,t)=>{eM.init(e,t),e._zod.parse=(e,r)=>{let n=e.value,i=t.left._zod.run({value:n,issues:[]},r),o=t.right._zod.run({value:n,issues:[]},r);return i instanceof Promise||o instanceof Promise?Promise.all([i,o]).then(([t,r])=>tr(e,t,r)):tr(e,i,o)}});function tr(e,t,r){if(t.issues.length&&e.issues.push(...t.issues),r.issues.length&&e.issues.push(...r.issues),K(e))return e;let n=function e(t,r){if(t===r||t instanceof Date&&r instanceof Date&&+t==+r)return{valid:!0,data:t};if(V(t)&&V(r)){let n=Object.keys(r),i=Object.keys(t).filter(e=>-1!==n.indexOf(e)),o={...t,...r};for(let n of i){let i=e(t[n],r[n]);if(!i.valid)return{valid:!1,mergeErrorPath:[n,...i.mergeErrorPath]};o[n]=i.data}return{valid:!0,data:o}}if(Array.isArray(t)&&Array.isArray(r)){if(t.length!==r.length)return{valid:!1,mergeErrorPath:[]};let n=[];for(let i=0;i{eM.init(e,t),e._zod.parse=(r,n)=>{let i=r.value;if(!V(i))return r.issues.push({expected:"record",code:"invalid_type",input:i,inst:e}),r;let o=[];if(t.keyType._zod.values){let a,l=t.keyType._zod.values;for(let e of(r.value={},l))if("string"==typeof e||"number"==typeof e||"symbol"==typeof e){let a=t.valueType._zod.run({value:i[e],issues:[]},n);a instanceof Promise?o.push(a.then(t=>{t.issues.length&&r.issues.push(...q(e,t.issues)),r.value[e]=t.value})):(a.issues.length&&r.issues.push(...q(e,a.issues)),r.value[e]=a.value)}for(let e in i)l.has(e)||(a=a??[]).push(e);a&&a.length>0&&r.issues.push({code:"unrecognized_keys",input:i,inst:e,keys:a})}else for(let l of(r.value={},Reflect.ownKeys(i))){if("__proto__"===l)continue;let u=t.keyType._zod.run({value:l,issues:[]},n);if(u instanceof Promise)throw Error("Async schemas not supported in object keys currently");if(u.issues.length){r.issues.push({origin:"record",code:"invalid_key",issues:u.issues.map(e=>X(e,n,a())),input:l,path:[l],inst:e}),r.value[u.value]=u.value;continue}let s=t.valueType._zod.run({value:i[l],issues:[]},n);s instanceof Promise?o.push(s.then(e=>{e.issues.length&&r.issues.push(...q(l,e.issues)),r.value[u.value]=e.value})):(s.issues.length&&r.issues.push(...q(l,s.issues)),r.value[u.value]=s.value)}return o.length?Promise.all(o).then(()=>r):r}}),ti=n("$ZodEnum",(e,t)=>{eM.init(e,t);let r=function(e){let t=Object.values(e).filter(e=>"number"==typeof e);return Object.entries(e).filter(([e,r])=>-1===t.indexOf(+e)).map(([e,t])=>t)}(t.entries);e._zod.values=new Set(r),e._zod.pattern=RegExp(`^(${r.filter(e=>U.has(typeof e)).map(e=>"string"==typeof e?H(e):e.toString()).join("|")})$`),e._zod.parse=(t,n)=>{let i=t.value;return e._zod.values.has(i)||t.issues.push({code:"invalid_value",values:r,input:i,inst:e}),t}}),to=n("$ZodTransform",(e,t)=>{eM.init(e,t),e._zod.parse=(e,r)=>{let n=t.transform(e.value,e);if(r.async)return(n instanceof Promise?n:Promise.resolve(n)).then(t=>(e.value=t,e));if(n instanceof Promise)throw new i;return e.value=n,e}}),ta=n("$ZodOptional",(e,t)=>{eM.init(e,t),e._zod.optin="optional",e._zod.optout="optional",I(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),I(e._zod,"pattern",()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${D(e.source)})?$`):void 0}),e._zod.parse=(e,r)=>"optional"===t.innerType._zod.optin?t.innerType._zod.run(e,r):void 0===e.value?e:t.innerType._zod.run(e,r)}),tl=n("$ZodNullable",(e,t)=>{eM.init(e,t),I(e._zod,"optin",()=>t.innerType._zod.optin),I(e._zod,"optout",()=>t.innerType._zod.optout),I(e._zod,"pattern",()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${D(e.source)}|null)$`):void 0}),I(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(e,r)=>null===e.value?e:t.innerType._zod.run(e,r)}),tu=n("$ZodDefault",(e,t)=>{eM.init(e,t),e._zod.optin="optional",I(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(e,r)=>{if(void 0===e.value)return e.value=t.defaultValue,e;let n=t.innerType._zod.run(e,r);return n instanceof Promise?n.then(e=>ts(e,t)):ts(n,t)}});function ts(e,t){return void 0===e.value&&(e.value=t.defaultValue),e}let tc=n("$ZodPrefault",(e,t)=>{eM.init(e,t),e._zod.optin="optional",I(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(e,r)=>(void 0===e.value&&(e.value=t.defaultValue),t.innerType._zod.run(e,r))}),tf=n("$ZodNonOptional",(e,t)=>{eM.init(e,t),I(e._zod,"values",()=>{let e=t.innerType._zod.values;return e?new Set([...e].filter(e=>void 0!==e)):void 0}),e._zod.parse=(r,n)=>{let i=t.innerType._zod.run(r,n);return i instanceof Promise?i.then(t=>td(t,e)):td(i,e)}});function td(e,t){return e.issues.length||void 0!==e.value||e.issues.push({code:"invalid_type",expected:"nonoptional",input:e.value,inst:t}),e}let tp=n("$ZodCatch",(e,t)=>{eM.init(e,t),e._zod.optin="optional",I(e._zod,"optout",()=>t.innerType._zod.optout),I(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(e,r)=>{let n=t.innerType._zod.run(e,r);return n instanceof Promise?n.then(n=>(e.value=n.value,n.issues.length&&(e.value=t.catchValue({...e,error:{issues:n.issues.map(e=>X(e,r,a()))},input:e.value}),e.issues=[]),e)):(e.value=n.value,n.issues.length&&(e.value=t.catchValue({...e,error:{issues:n.issues.map(e=>X(e,r,a()))},input:e.value}),e.issues=[]),e)}}),th=n("$ZodPipe",(e,t)=>{eM.init(e,t),I(e._zod,"values",()=>t.in._zod.values),I(e._zod,"optin",()=>t.in._zod.optin),I(e._zod,"optout",()=>t.out._zod.optout),I(e._zod,"propValues",()=>t.in._zod.propValues),e._zod.parse=(e,r)=>{let n=t.in._zod.run(e,r);return n instanceof Promise?n.then(e=>tg(e,t,r)):tg(n,t,r)}});function tg(e,t,r){return K(e)?e:t.out._zod.run({value:e.value,issues:e.issues},r)}let tv=n("$ZodReadonly",(e,t)=>{eM.init(e,t),I(e._zod,"propValues",()=>t.innerType._zod.propValues),I(e._zod,"values",()=>t.innerType._zod.values),I(e._zod,"optin",()=>t.innerType._zod.optin),I(e._zod,"optout",()=>t.innerType._zod.optout),e._zod.parse=(e,r)=>{let n=t.innerType._zod.run(e,r);return n instanceof Promise?n.then(tm):tm(n)}});function tm(e){return e.value=Object.freeze(e.value),e}let ty=n("$ZodCustom",(e,t)=>{ee.init(e,t),eM.init(e,t),e._zod.parse=(e,t)=>e,e._zod.check=r=>{let n=r.value,i=t.fn(n);if(i instanceof Promise)return i.then(t=>tb(t,r,n,e));tb(i,r,n,e)}});function tb(e,t,r,n){if(!e){let e={code:"custom",input:r,inst:n,path:[...n._zod.def.path??[]],continue:!n._zod.def.abort};n._zod.def.params&&(e.params=n._zod.def.params),t.issues.push(Q(e))}}Symbol("ZodOutput"),Symbol("ZodInput");class tw{constructor(){this._map=new Map,this._idmap=new Map}add(e,...t){let r=t[0];if(this._map.set(e,r),r&&"object"==typeof r&&"id"in r){if(this._idmap.has(r.id))throw Error(`ID ${r.id} already exists in the registry`);this._idmap.set(r.id,e)}return this}clear(){return this._map=new Map,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&"object"==typeof t&&"id"in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let r={...this.get(t)??{}};return delete r.id,{...r,...this._map.get(e)}}return this._map.get(e)}has(e){return this._map.has(e)}}let tx=new tw;function tS(e,t){return new e({type:"string",format:"guid",check:"string_format",abort:!1,...Z(t)})}function tO(e,t){return new er({check:"less_than",...Z(t),value:e,inclusive:!1})}function tE(e,t){return new er({check:"less_than",...Z(t),value:e,inclusive:!0})}function tC(e,t){return new en({check:"greater_than",...Z(t),value:e,inclusive:!1})}function tM(e,t){return new en({check:"greater_than",...Z(t),value:e,inclusive:!0})}function tk(e,t){return new ei({check:"multiple_of",...Z(t),value:e})}function t_(e,t){return new ea({check:"max_length",...Z(t),maximum:e})}function tP(e,t){return new el({check:"min_length",...Z(t),minimum:e})}function tA(e,t){return new eu({check:"length_equals",...Z(t),length:e})}function tj(e){return new ev({check:"overwrite",tx:e})}let tT=n("ZodISODateTime",(e,t)=>{e$.init(e,t),tZ.init(e,t)}),tR=n("ZodISODate",(e,t)=>{eB.init(e,t),tZ.init(e,t)}),tz=n("ZodISOTime",(e,t)=>{eV.init(e,t),tZ.init(e,t)}),tD=n("ZodISODuration",(e,t)=>{eU.init(e,t),tZ.init(e,t)}),tI=(e,t)=>{eb.init(e,t),e.name="ZodError",Object.defineProperties(e,{format:{value:t=>(function(e,t){let r=t||function(e){return e.message},n={_errors:[]},i=e=>{for(let t of e.issues)if("invalid_union"===t.code&&t.errors.length)t.errors.map(e=>i({issues:e}));else if("invalid_key"===t.code)i({issues:t.issues});else if("invalid_element"===t.code)i({issues:t.issues});else if(0===t.path.length)n._errors.push(r(t));else{let e=n,i=0;for(;i(function(e,t=e=>e.message){let r={},n=[];for(let i of e.issues)i.path.length>0?(r[i.path[0]]=r[i.path[0]]||[],r[i.path[0]].push(t(i))):n.push(t(i));return{formErrors:n,fieldErrors:r}})(e,t)},addIssue:{value:t=>e.issues.push(t)},addIssues:{value:t=>e.issues.push(...t)},isEmpty:{get:()=>0===e.issues.length}})};n("ZodError",tI);let tN=n("ZodError",tI,{Parent:Error}),tL=(e,t,r,n)=>{let o=r?Object.assign(r,{async:!1}):{async:!1},l=e._zod.run({value:t,issues:[]},o);if(l instanceof Promise)throw new i;if(l.issues.length){let e=new(n?.Err??tN)(l.issues.map(e=>X(e,o,a())));throw F(e,n?.callee),e}return l.value},tF=async(e,t,r,n)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},o=e._zod.run({value:t,issues:[]},i);if(o instanceof Promise&&(o=await o),o.issues.length){let e=new(n?.Err??tN)(o.issues.map(e=>X(e,i,a())));throw F(e,n?.callee),e}return o.value},t$=ex(tN),tB=eO(tN),tV=n("ZodType",(e,t)=>(eM.init(e,t),e.def=t,Object.defineProperty(e,"_def",{value:t}),e.check=(...r)=>e.clone({...t,checks:[...t.checks??[],...r.map(e=>"function"==typeof e?{_zod:{check:e,def:{check:"custom"},onattach:[]}}:e)]}),e.clone=(t,r)=>G(e,t,r),e.brand=()=>e,e.register=(t,r)=>(t.add(e,r),e),e.parse=(t,r)=>tL(e,t,r,{callee:e.parse}),e.safeParse=(t,r)=>t$(e,t,r),e.parseAsync=async(t,r)=>tF(e,t,r,{callee:e.parseAsync}),e.safeParseAsync=async(t,r)=>tB(e,t,r),e.spa=e.safeParseAsync,e.refine=(t,r)=>e.check(function(e,t={}){return new rR({type:"custom",check:"custom",fn:e,...Z(t)})}(t,r)),e.superRefine=t=>e.check(function(e){let t=function(e){let t=new ee({check:"custom"});return t._zod.check=e,t}(r=>(r.addIssue=e=>{"string"==typeof e?r.issues.push(Q(e,r.value,t._zod.def)):(e.fatal&&(e.continue=!1),e.code??(e.code="custom"),e.input??(e.input=r.value),e.inst??(e.inst=t),e.continue??(e.continue=!t._zod.def.abort),r.issues.push(Q(e)))},e(r.value,r)));return t}(t)),e.overwrite=t=>e.check(tj(t)),e.optional=()=>rO(e),e.nullable=()=>rC(e),e.nullish=()=>rO(rC(e)),e.nonoptional=t=>new r_({type:"nonoptional",innerType:e,...Z(t)}),e.array=()=>rd(e),e.or=t=>new rg({type:"union",options:[e,t],...Z(void 0)}),e.and=t=>new rv({type:"intersection",left:e,right:t}),e.transform=t=>rj(e,new rx({type:"transform",transform:t})),e.default=t=>(function(e,t){return new rM({type:"default",innerType:e,get defaultValue(){return"function"==typeof t?t():t}})})(e,t),e.prefault=t=>(function(e,t){return new rk({type:"prefault",innerType:e,get defaultValue(){return"function"==typeof t?t():t}})})(e,t),e.catch=t=>(function(e,t){return new rP({type:"catch",innerType:e,catchValue:"function"==typeof t?t:()=>t})})(e,t),e.pipe=t=>rj(e,t),e.readonly=()=>new rT({type:"readonly",innerType:e}),e.describe=t=>{let r=e.clone();return tx.add(r,{description:t}),r},Object.defineProperty(e,"description",{get:()=>tx.get(e)?.description,configurable:!0}),e.meta=(...t)=>{if(0===t.length)return tx.get(e);let r=e.clone();return tx.add(r,t[0]),r},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e)),tU=n("_ZodString",(e,t)=>{ek.init(e,t),tV.init(e,t);let r=e._zod.bag;e.format=r.format??null,e.minLength=r.minimum??null,e.maxLength=r.maximum??null,e.regex=(...t)=>e.check(function(e,t){return new ec({check:"string_format",format:"regex",...Z(t),pattern:e})}(...t)),e.includes=(...t)=>e.check(function(e,t){return new ep({check:"string_format",format:"includes",...Z(t),includes:e})}(...t)),e.startsWith=(...t)=>e.check(function(e,t){return new eh({check:"string_format",format:"starts_with",...Z(t),prefix:e})}(...t)),e.endsWith=(...t)=>e.check(function(e,t){return new eg({check:"string_format",format:"ends_with",...Z(t),suffix:e})}(...t)),e.min=(...t)=>e.check(tP(...t)),e.max=(...t)=>e.check(t_(...t)),e.length=(...t)=>e.check(tA(...t)),e.nonempty=(...t)=>e.check(tP(1,...t)),e.lowercase=t=>e.check(new ef({check:"string_format",format:"lowercase",...Z(t)})),e.uppercase=t=>e.check(new ed({check:"string_format",format:"uppercase",...Z(t)})),e.trim=()=>e.check(tj(e=>e.trim())),e.normalize=(...t)=>e.check(function(e){return tj(t=>t.normalize(e))}(...t)),e.toLowerCase=()=>e.check(tj(e=>e.toLowerCase())),e.toUpperCase=()=>e.check(tj(e=>e.toUpperCase()))}),tH=n("ZodString",(e,t)=>{ek.init(e,t),tU.init(e,t),e.email=t=>e.check(new tW({type:"string",format:"email",check:"string_format",abort:!1,...Z(t)})),e.url=t=>e.check(new tY({type:"string",format:"url",check:"string_format",abort:!1,...Z(t)})),e.jwt=t=>e.check(new rt({type:"string",format:"jwt",check:"string_format",abort:!1,...Z(t)})),e.emoji=t=>e.check(new tX({type:"string",format:"emoji",check:"string_format",abort:!1,...Z(t)})),e.guid=t=>e.check(tS(tK,t)),e.uuid=t=>e.check(new tq({type:"string",format:"uuid",check:"string_format",abort:!1,...Z(t)})),e.uuidv4=t=>e.check(new tq({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",...Z(t)})),e.uuidv6=t=>e.check(new tq({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v6",...Z(t)})),e.uuidv7=t=>e.check(new tq({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v7",...Z(t)})),e.nanoid=t=>e.check(new tJ({type:"string",format:"nanoid",check:"string_format",abort:!1,...Z(t)})),e.guid=t=>e.check(tS(tK,t)),e.cuid=t=>e.check(new tQ({type:"string",format:"cuid",check:"string_format",abort:!1,...Z(t)})),e.cuid2=t=>e.check(new t0({type:"string",format:"cuid2",check:"string_format",abort:!1,...Z(t)})),e.ulid=t=>e.check(new t1({type:"string",format:"ulid",check:"string_format",abort:!1,...Z(t)})),e.base64=t=>e.check(new t9({type:"string",format:"base64",check:"string_format",abort:!1,...Z(t)})),e.base64url=t=>e.check(new t7({type:"string",format:"base64url",check:"string_format",abort:!1,...Z(t)})),e.xid=t=>e.check(new t2({type:"string",format:"xid",check:"string_format",abort:!1,...Z(t)})),e.ksuid=t=>e.check(new t5({type:"string",format:"ksuid",check:"string_format",abort:!1,...Z(t)})),e.ipv4=t=>e.check(new t6({type:"string",format:"ipv4",check:"string_format",abort:!1,...Z(t)})),e.ipv6=t=>e.check(new t4({type:"string",format:"ipv6",check:"string_format",abort:!1,...Z(t)})),e.cidrv4=t=>e.check(new t3({type:"string",format:"cidrv4",check:"string_format",abort:!1,...Z(t)})),e.cidrv6=t=>e.check(new t8({type:"string",format:"cidrv6",check:"string_format",abort:!1,...Z(t)})),e.e164=t=>e.check(new re({type:"string",format:"e164",check:"string_format",abort:!1,...Z(t)})),e.datetime=t=>e.check(new tT({type:"string",format:"datetime",check:"string_format",offset:!1,local:!1,precision:null,...Z(t)})),e.date=t=>e.check(new tR({type:"string",format:"date",check:"string_format",...Z(t)})),e.time=t=>e.check(new tz({type:"string",format:"time",check:"string_format",precision:null,...Z(t)})),e.duration=t=>e.check(new tD({type:"string",format:"duration",check:"string_format",...Z(t)}))});function tG(e){return new tH({type:"string",...Z(e)})}let tZ=n("ZodStringFormat",(e,t)=>{e_.init(e,t),tU.init(e,t)}),tW=n("ZodEmail",(e,t)=>{ej.init(e,t),tZ.init(e,t)}),tK=n("ZodGUID",(e,t)=>{eP.init(e,t),tZ.init(e,t)}),tq=n("ZodUUID",(e,t)=>{eA.init(e,t),tZ.init(e,t)}),tY=n("ZodURL",(e,t)=>{eT.init(e,t),tZ.init(e,t)}),tX=n("ZodEmoji",(e,t)=>{eR.init(e,t),tZ.init(e,t)}),tJ=n("ZodNanoID",(e,t)=>{ez.init(e,t),tZ.init(e,t)}),tQ=n("ZodCUID",(e,t)=>{eD.init(e,t),tZ.init(e,t)}),t0=n("ZodCUID2",(e,t)=>{eI.init(e,t),tZ.init(e,t)}),t1=n("ZodULID",(e,t)=>{eN.init(e,t),tZ.init(e,t)}),t2=n("ZodXID",(e,t)=>{eL.init(e,t),tZ.init(e,t)}),t5=n("ZodKSUID",(e,t)=>{eF.init(e,t),tZ.init(e,t)}),t6=n("ZodIPv4",(e,t)=>{eH.init(e,t),tZ.init(e,t)}),t4=n("ZodIPv6",(e,t)=>{eG.init(e,t),tZ.init(e,t)}),t3=n("ZodCIDRv4",(e,t)=>{eZ.init(e,t),tZ.init(e,t)}),t8=n("ZodCIDRv6",(e,t)=>{eW.init(e,t),tZ.init(e,t)}),t9=n("ZodBase64",(e,t)=>{eq.init(e,t),tZ.init(e,t)}),t7=n("ZodBase64URL",(e,t)=>{eY.init(e,t),tZ.init(e,t)}),re=n("ZodE164",(e,t)=>{eX.init(e,t),tZ.init(e,t)}),rt=n("ZodJWT",(e,t)=>{eJ.init(e,t),tZ.init(e,t)}),rr=n("ZodNumber",(e,t)=>{eQ.init(e,t),tV.init(e,t),e.gt=(t,r)=>e.check(tC(t,r)),e.gte=(t,r)=>e.check(tM(t,r)),e.min=(t,r)=>e.check(tM(t,r)),e.lt=(t,r)=>e.check(tO(t,r)),e.lte=(t,r)=>e.check(tE(t,r)),e.max=(t,r)=>e.check(tE(t,r)),e.int=t=>e.check(ro(t)),e.safe=t=>e.check(ro(t)),e.positive=t=>e.check(tC(0,t)),e.nonnegative=t=>e.check(tM(0,t)),e.negative=t=>e.check(tO(0,t)),e.nonpositive=t=>e.check(tE(0,t)),e.multipleOf=(t,r)=>e.check(tk(t,r)),e.step=(t,r)=>e.check(tk(t,r)),e.finite=()=>e;let r=e._zod.bag;e.minValue=Math.max(r.minimum??-1/0,r.exclusiveMinimum??-1/0)??null,e.maxValue=Math.min(r.maximum??1/0,r.exclusiveMaximum??1/0)??null,e.isInt=(r.format??"").includes("int")||Number.isSafeInteger(r.multipleOf??.5),e.isFinite=!0,e.format=r.format??null});function rn(e){return new rr({type:"number",checks:[],...Z(e)})}let ri=n("ZodNumberFormat",(e,t)=>{e0.init(e,t),rr.init(e,t)});function ro(e){return new ri({type:"number",check:"number_format",abort:!1,format:"safeint",...Z(e)})}let ra=n("ZodBoolean",(e,t)=>{e1.init(e,t),tV.init(e,t)});function rl(e){return new ra({type:"boolean",...Z(e)})}let ru=n("ZodUnknown",(e,t)=>{e2.init(e,t),tV.init(e,t)});function rs(){return new ru({type:"unknown"})}let rc=n("ZodNever",(e,t)=>{e5.init(e,t),tV.init(e,t)}),rf=n("ZodArray",(e,t)=>{e4.init(e,t),tV.init(e,t),e.element=t.element,e.min=(t,r)=>e.check(tP(t,r)),e.nonempty=t=>e.check(tP(1,t)),e.max=(t,r)=>e.check(t_(t,r)),e.length=(t,r)=>e.check(tA(t,r)),e.unwrap=()=>e.element});function rd(e,t){return new rf({type:"array",element:e,...Z(t)})}let rp=n("ZodObject",(e,t)=>{e9.init(e,t),tV.init(e,t),I(e,"shape",()=>t.shape),e.keyof=()=>rw(Object.keys(e._zod.def.shape)),e.catchall=t=>e.clone({...e._zod.def,catchall:t}),e.passthrough=()=>e.clone({...e._zod.def,catchall:rs()}),e.loose=()=>e.clone({...e._zod.def,catchall:rs()}),e.strict=()=>e.clone({...e._zod.def,catchall:new rc({type:"never",...Z(void 0)})}),e.strip=()=>e.clone({...e._zod.def,catchall:void 0}),e.extend=t=>(function(e,t){if(!V(t))throw Error("Invalid input to extend: expected a plain object");let r={...e._zod.def,get shape(){let r={...e._zod.def.shape,...t};return N(this,"shape",r),r},checks:[]};return G(e,r)})(e,t),e.merge=t=>(function(e,t){return G(e,{...e._zod.def,get shape(){let r={...e._zod.def.shape,...t._zod.def.shape};return N(this,"shape",r),r},catchall:t._zod.def.catchall,checks:[]})})(e,t),e.pick=t=>(function(e,t){let r={},n=e._zod.def;for(let e in t){if(!(e in n.shape))throw Error(`Unrecognized key: "${e}"`);t[e]&&(r[e]=n.shape[e])}return G(e,{...e._zod.def,shape:r,checks:[]})})(e,t),e.omit=t=>(function(e,t){let r={...e._zod.def.shape},n=e._zod.def;for(let e in t){if(!(e in n.shape))throw Error(`Unrecognized key: "${e}"`);t[e]&&delete r[e]}return G(e,{...e._zod.def,shape:r,checks:[]})})(e,t),e.partial=(...t)=>(function(e,t,r){let n=t._zod.def.shape,i={...n};if(r)for(let t in r){if(!(t in n))throw Error(`Unrecognized key: "${t}"`);r[t]&&(i[t]=e?new e({type:"optional",innerType:n[t]}):n[t])}else for(let t in n)i[t]=e?new e({type:"optional",innerType:n[t]}):n[t];return G(t,{...t._zod.def,shape:i,checks:[]})})(rS,e,t[0]),e.required=(...t)=>(function(e,t,r){let n=t._zod.def.shape,i={...n};if(r)for(let t in r){if(!(t in i))throw Error(`Unrecognized key: "${t}"`);r[t]&&(i[t]=new e({type:"nonoptional",innerType:n[t]}))}else for(let t in n)i[t]=new e({type:"nonoptional",innerType:n[t]});return G(t,{...t._zod.def,shape:i,checks:[]})})(r_,e,t[0])});function rh(e,t){return new rp({type:"object",get shape(){return N(this,"shape",{...e}),this.shape},...Z(t)})}let rg=n("ZodUnion",(e,t)=>{te.init(e,t),tV.init(e,t),e.options=t.options}),rv=n("ZodIntersection",(e,t)=>{tt.init(e,t),tV.init(e,t)}),rm=n("ZodRecord",(e,t)=>{tn.init(e,t),tV.init(e,t),e.keyType=t.keyType,e.valueType=t.valueType});function ry(e,t,r){return new rm({type:"record",keyType:e,valueType:t,...Z(r)})}let rb=n("ZodEnum",(e,t)=>{ti.init(e,t),tV.init(e,t),e.enum=t.entries,e.options=Object.values(t.entries);let r=new Set(Object.keys(t.entries));e.extract=(e,n)=>{let i={};for(let n of e)if(r.has(n))i[n]=t.entries[n];else throw Error(`Key ${n} not found in enum`);return new rb({...t,checks:[],...Z(n),entries:i})},e.exclude=(e,n)=>{let i={...t.entries};for(let t of e)if(r.has(t))delete i[t];else throw Error(`Key ${t} not found in enum`);return new rb({...t,checks:[],...Z(n),entries:i})}});function rw(e,t){return new rb({type:"enum",entries:Array.isArray(e)?Object.fromEntries(e.map(e=>[e,e])):e,...Z(t)})}let rx=n("ZodTransform",(e,t)=>{to.init(e,t),tV.init(e,t),e._zod.parse=(r,n)=>{r.addIssue=n=>{"string"==typeof n?r.issues.push(Q(n,r.value,t)):(n.fatal&&(n.continue=!1),n.code??(n.code="custom"),n.input??(n.input=r.value),n.inst??(n.inst=e),n.continue??(n.continue=!0),r.issues.push(Q(n)))};let i=t.transform(r.value,r);return i instanceof Promise?i.then(e=>(r.value=e,r)):(r.value=i,r)}}),rS=n("ZodOptional",(e,t)=>{ta.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType});function rO(e){return new rS({type:"optional",innerType:e})}let rE=n("ZodNullable",(e,t)=>{tl.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType});function rC(e){return new rE({type:"nullable",innerType:e})}let rM=n("ZodDefault",(e,t)=>{tu.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap}),rk=n("ZodPrefault",(e,t)=>{tc.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType}),r_=n("ZodNonOptional",(e,t)=>{tf.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType}),rP=n("ZodCatch",(e,t)=>{tp.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap}),rA=n("ZodPipe",(e,t)=>{th.init(e,t),tV.init(e,t),e.in=t.in,e.out=t.out});function rj(e,t){return new rA({type:"pipe",in:e,out:t})}let rT=n("ZodReadonly",(e,t)=>{tv.init(e,t),tV.init(e,t)}),rR=n("ZodCustom",(e,t)=>{ty.init(e,t),tV.init(e,t)})},5641:(e,t,r)=>{"use strict";function n(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function i(e){for(var t=1;ta,Kg:()=>o,lY:()=>l,yy:()=>u}),r(2115);var o=Math.PI/180,a=(e,t,r,n)=>({x:e+Math.cos(-o*n)*r,y:t+Math.sin(-o*n)*r}),l=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{top:0,right:0,bottom:0,left:0,width:0,height:0,brushBottom:0};return Math.min(Math.abs(e-(r.left||0)-(r.right||0)),Math.abs(t-(r.top||0)-(r.bottom||0)))/2},u=(e,t)=>{var r,{x:n,y:o}=e,{radius:a,angle:l}=((e,t)=>{var{x:r,y:n}=e,{cx:i,cy:o}=t,a=((e,t)=>{var{x:r,y:n}=e,{x:i,y:o}=t;return Math.sqrt((r-i)**2+(n-o)**2)})({x:r,y:n},{x:i,y:o});if(a<=0)return{radius:a,angle:0};var l=Math.acos((r-i)/a);return n>o&&(l=2*Math.PI-l),{radius:a,angle:180*l/Math.PI,angleInRadian:l}})({x:n,y:o},t),{innerRadius:u,outerRadius:s}=t;if(as||0===a)return null;var{startAngle:c,endAngle:f}=(e=>{var{startAngle:t,endAngle:r}=e,n=Math.min(Math.floor(t/360),Math.floor(r/360));return{startAngle:t-360*n,endAngle:r-360*n}})(t),d=l;if(c<=f){for(;d>f;)d-=360;for(;d=c&&d<=f}else{for(;d>c;)d-=360;for(;d=f&&d<=c}return r?i(i({},t),{},{radius:a,angle:((e,t)=>{var{startAngle:r,endAngle:n}=t;return e+360*Math.min(Math.floor(r/360),Math.floor(n/360))})(d,t)}):null}},5643:(e,t,r)=>{"use strict";e.exports=r(6115)},5654:(e,t,r)=>{"use strict";function n(e){return function(){return e}}r.d(t,{A:()=>n})},5672:(e,t,r)=>{e.exports=r(921).get},5695:(e,t,r)=>{"use strict";var n=r(8999);r.o(n,"useRouter")&&r.d(t,{useRouter:function(){return n.useRouter}}),r.o(n,"useSearchParams")&&r.d(t,{useSearchParams:function(){return n.useSearchParams}})},5710:(e,t,r)=>{"use strict";r.d(t,{U1:()=>p,VP:()=>u,Nc:()=>q,Z0:()=>m});var n=r(52);function i(e){return({dispatch:t,getState:r})=>n=>i=>"function"==typeof i?i(t,r,e):n(i)}var o=i(),a=r(4532),l=(r(9509),"undefined"!=typeof window&&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__:function(){if(0!=arguments.length)return"object"==typeof arguments[0]?n.Zz:n.Zz.apply(null,arguments)});function u(e,t){function r(...n){if(t){let r=t(...n);if(!r)throw Error(Y(0));return{type:e,payload:r.payload,..."meta"in r&&{meta:r.meta},..."error"in r&&{error:r.error}}}return{type:e,payload:n[0]}}return r.toString=()=>`${e}`,r.type=e,r.match=t=>(0,n.ve)(t)&&t.type===e,r}"undefined"!=typeof window&&window.__REDUX_DEVTOOLS_EXTENSION__&&window.__REDUX_DEVTOOLS_EXTENSION__;var s=class e extends Array{constructor(...t){super(...t),Object.setPrototypeOf(this,e.prototype)}static get[Symbol.species](){return e}concat(...e){return super.concat.apply(this,e)}prepend(...t){return 1===t.length&&Array.isArray(t[0])?new e(...t[0].concat(this)):new e(...t.concat(this))}};function c(e){return(0,a.a6)(e)?(0,a.jM)(e,()=>{}):e}function f(e,t,r){return e.has(t)?e.get(t):e.set(t,r(t)).get(t)}var d=e=>t=>{setTimeout(t,e)};function p(e){let t,r,a,u=function(e){let{thunk:t=!0,immutableCheck:r=!0,serializableCheck:n=!0,actionCreatorCheck:a=!0}=e??{},l=new s;return t&&("boolean"==typeof t?l.push(o):l.push(i(t.extraArgument))),l},{reducer:c,middleware:f,devTools:p=!0,duplicateMiddlewareCheck:h=!0,preloadedState:g,enhancers:v}=e||{};if("function"==typeof c)t=c;else if((0,n.Qd)(c))t=(0,n.HY)(c);else throw Error(Y(1));r="function"==typeof f?f(u):u();let m=n.Zz;p&&(m=l({trace:!1,..."object"==typeof p&&p}));let y=(a=(0,n.Tw)(...r),function(e){let{autoBatch:t=!0}=e??{},r=new s(a);return t&&r.push(((e={type:"raf"})=>t=>(...r)=>{let n=t(...r),i=!0,o=!1,a=!1,l=new Set,u="tick"===e.type?queueMicrotask:"raf"===e.type?"undefined"!=typeof window&&window.requestAnimationFrame?window.requestAnimationFrame:d(10):"callback"===e.type?e.queueNotification:d(e.timeout),s=()=>{a=!1,o&&(o=!1,l.forEach(e=>e()))};return Object.assign({},n,{subscribe(e){let t=n.subscribe(()=>i&&e());return l.add(e),()=>{t(),l.delete(e)}},dispatch(e){try{return(o=!(i=!e?.meta?.RTK_autoBatch))&&!a&&(a=!0,u(s)),n.dispatch(e)}finally{i=!0}}})})("object"==typeof t?t:void 0)),r}),b=m(..."function"==typeof v?v(y):y());return(0,n.y$)(t,g,b)}function h(e){let t,r={},n=[],i={addCase(e,t){let n="string"==typeof e?e:e.type;if(!n)throw Error(Y(28));if(n in r)throw Error(Y(29));return r[n]=t,i},addMatcher:(e,t)=>(n.push({matcher:e,reducer:t}),i),addDefaultCase:e=>(t=e,i)};return e(i),[r,n,t]}var g=Symbol.for("rtk-slice-createasyncthunk"),v=(e=>(e.reducer="reducer",e.reducerWithPrepare="reducerWithPrepare",e.asyncThunk="asyncThunk",e))(v||{}),m=function({creators:e}={}){let t=e?.asyncThunk?.[g];return function(e){let r,{name:n,reducerPath:i=n}=e;if(!n)throw Error(Y(11));let o=("function"==typeof e.reducers?e.reducers(function(){function e(e,t){return{_reducerDefinitionType:"asyncThunk",payloadCreator:e,...t}}return e.withTypes=()=>e,{reducer:e=>Object.assign({[e.name]:(...t)=>e(...t)}[e.name],{_reducerDefinitionType:"reducer"}),preparedReducer:(e,t)=>({_reducerDefinitionType:"reducerWithPrepare",prepare:e,reducer:t}),asyncThunk:e}}()):e.reducers)||{},l=Object.keys(o),s={},d={},p={},g=[],v={addCase(e,t){let r="string"==typeof e?e:e.type;if(!r)throw Error(Y(12));if(r in d)throw Error(Y(13));return d[r]=t,v},addMatcher:(e,t)=>(g.push({matcher:e,reducer:t}),v),exposeAction:(e,t)=>(p[e]=t,v),exposeCaseReducer:(e,t)=>(s[e]=t,v)};function m(){let[t={},r=[],n]="function"==typeof e.extraReducers?h(e.extraReducers):[e.extraReducers],i={...t,...d};return function(e,t){let r,[n,i,o]=h(t);if("function"==typeof e)r=()=>c(e());else{let t=c(e);r=()=>t}function l(e=r(),t){let u=[n[t.type],...i.filter(({matcher:e})=>e(t)).map(({reducer:e})=>e)];return 0===u.filter(e=>!!e).length&&(u=[o]),u.reduce((e,r)=>{if(r)if((0,a.Qx)(e)){let n=r(e,t);return void 0===n?e:n}else{if((0,a.a6)(e))return(0,a.jM)(e,e=>r(e,t));let n=r(e,t);if(void 0===n){if(null===e)return e;throw Error("A case reducer on a non-draftable value must not return undefined")}return n}return e},e)}return l.getInitialState=r,l}(e.initialState,e=>{for(let t in i)e.addCase(t,i[t]);for(let t of g)e.addMatcher(t.matcher,t.reducer);for(let t of r)e.addMatcher(t.matcher,t.reducer);n&&e.addDefaultCase(n)})}l.forEach(r=>{let i=o[r],a={reducerName:r,type:`${n}/${r}`,createNotation:"function"==typeof e.reducers};"asyncThunk"===i._reducerDefinitionType?function({type:e,reducerName:t},r,n,i){if(!i)throw Error(Y(18));let{payloadCreator:o,fulfilled:a,pending:l,rejected:u,settled:s,options:c}=r,f=i(e,o,c);n.exposeAction(t,f),a&&n.addCase(f.fulfilled,a),l&&n.addCase(f.pending,l),u&&n.addCase(f.rejected,u),s&&n.addMatcher(f.settled,s),n.exposeCaseReducer(t,{fulfilled:a||y,pending:l||y,rejected:u||y,settled:s||y})}(a,i,v,t):function({type:e,reducerName:t,createNotation:r},n,i){let o,a;if("reducer"in n){if(r&&"reducerWithPrepare"!==n._reducerDefinitionType)throw Error(Y(17));o=n.reducer,a=n.prepare}else o=n;i.addCase(e,o).exposeCaseReducer(t,o).exposeAction(t,a?u(e,a):u(e))}(a,i,v)});let b=e=>e,w=new Map,x=new WeakMap;function S(e,t){return r||(r=m()),r(e,t)}function O(){return r||(r=m()),r.getInitialState()}function E(t,r=!1){function n(e){let i=e[t];return void 0===i&&r&&(i=f(x,n,O)),i}function i(t=b){let n=f(w,r,()=>new WeakMap);return f(n,t,()=>{let n={};for(let[i,o]of Object.entries(e.selectors??{}))n[i]=function(e,t,r,n){function i(o,...a){let l=t(o);return void 0===l&&n&&(l=r()),e(l,...a)}return i.unwrapped=e,i}(o,t,()=>f(x,t,O),r);return n})}return{reducerPath:t,getSelectors:i,get selectors(){return i(n)},selectSlice:n}}let C={name:n,reducer:S,actions:p,caseReducers:s,getInitialState:O,...E(i),injectInto(e,{reducerPath:t,...r}={}){let n=t??i;return e.inject({reducerPath:n,reducer:S},r),{...C,...E(n,!0)}}};return C}}();function y(){}var b="listener",w="completed",x="cancelled",S=`task-${x}`,O=`task-${w}`,E=`${b}-${x}`,C=`${b}-${w}`,M=class{constructor(e){this.code=e,this.message=`task ${x} (reason: ${e})`}name="TaskAbortError";message},k=(e,t)=>{if("function"!=typeof e)throw TypeError(Y(32))},_=()=>{},P=(e,t=_)=>(e.catch(t),e),A=(e,t)=>(e.addEventListener("abort",t,{once:!0}),()=>e.removeEventListener("abort",t)),j=(e,t)=>{let r=e.signal;r.aborted||("reason"in r||Object.defineProperty(r,"reason",{enumerable:!0,value:t,configurable:!0,writable:!0}),e.abort(t))},T=e=>{if(e.aborted){let{reason:t}=e;throw new M(t)}};function R(e,t){let r=_;return new Promise((n,i)=>{let o=()=>i(new M(e.reason));if(e.aborted)return void o();r=A(e,o),t.finally(()=>r()).then(n,i)}).finally(()=>{r=_})}var z=async(e,t)=>{try{await Promise.resolve();let t=await e();return{status:"ok",value:t}}catch(e){return{status:e instanceof M?"cancelled":"rejected",error:e}}finally{t?.()}},D=e=>t=>P(R(e,t).then(t=>(T(e),t))),I=e=>{let t=D(e);return e=>t(new Promise(t=>setTimeout(t,e)))},{assign:N}=Object,L={},F="listenerMiddleware",$=e=>{let{type:t,actionCreator:r,matcher:n,predicate:i,effect:o}=e;if(t)i=u(t).match;else if(r)t=r.type,i=r.match;else if(n)i=n;else if(i);else throw Error(Y(21));return k(o,"options.listener"),{predicate:i,type:t,effect:o}},B=N(e=>{let{type:t,predicate:r,effect:n}=$(e);return{id:((e=21)=>{let t="",r=e;for(;r--;)t+="ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW"[64*Math.random()|0];return t})(),effect:n,type:t,predicate:r,pending:new Set,unsubscribe:()=>{throw Error(Y(22))}}},{withTypes:()=>B}),V=(e,t)=>{let{type:r,effect:n,predicate:i}=$(t);return Array.from(e.values()).find(e=>("string"==typeof r?e.type===r:e.predicate===i)&&e.effect===n)},U=e=>{e.pending.forEach(e=>{j(e,E)})},H=(e,t,r)=>{try{e(t,r)}catch(e){setTimeout(()=>{throw e},0)}},G=N(u(`${F}/add`),{withTypes:()=>G}),Z=u(`${F}/removeAll`),W=N(u(`${F}/remove`),{withTypes:()=>W}),K=(...e)=>{console.error(`${F}/error`,...e)},q=(e={})=>{let t=new Map,{extra:r,onError:i=K}=e;k(i,"onError");let o=e=>(e=>(e.unsubscribe=()=>t.delete(e.id),t.set(e.id,e),t=>{e.unsubscribe(),t?.cancelActive&&U(e)}))(V(t,e)??B(e));N(o,{withTypes:()=>o});let a=e=>{let r=V(t,e);return r&&(r.unsubscribe(),e.cancelActive&&U(r)),!!r};N(a,{withTypes:()=>a});let l=async(e,n,a,l)=>{let u=new AbortController,s=((e,t)=>{let r=async(r,n)=>{T(t);let i=()=>{},o=[new Promise((t,n)=>{let o=e({predicate:r,effect:(e,r)=>{r.unsubscribe(),t([e,r.getState(),r.getOriginalState()])}});i=()=>{o(),n()}})];null!=n&&o.push(new Promise(e=>setTimeout(e,n,null)));try{let e=await R(t,Promise.race(o));return T(t),e}finally{i()}};return(e,t)=>P(r(e,t))})(o,u.signal),c=[];try{e.pending.add(u),await Promise.resolve(e.effect(n,N({},a,{getOriginalState:l,condition:(e,t)=>s(e,t).then(Boolean),take:s,delay:I(u.signal),pause:D(u.signal),extra:r,signal:u.signal,fork:((e,t)=>(r,n)=>{k(r,"taskExecutor");let i=new AbortController;A(e,()=>j(i,e.reason));let o=z(async()=>{T(e),T(i.signal);let t=await r({pause:D(i.signal),delay:I(i.signal),signal:i.signal});return T(i.signal),t},()=>j(i,O));return n?.autoJoin&&t.push(o.catch(_)),{result:D(e)(o),cancel(){j(i,S)}}})(u.signal,c),unsubscribe:e.unsubscribe,subscribe:()=>{t.set(e.id,e)},cancelActiveListeners:()=>{e.pending.forEach((e,t,r)=>{e!==u&&(j(e,E),r.delete(e))})},cancel:()=>{j(u,E),e.pending.delete(u)},throwIfCancelled:()=>{T(u.signal)}})))}catch(e){e instanceof M||H(i,e,{raisedBy:"effect"})}finally{await Promise.all(c),j(u,C),e.pending.delete(u)}},u=(e=>()=>{e.forEach(U),e.clear()})(t);return{middleware:e=>r=>s=>{let c;if(!(0,n.ve)(s))return r(s);if(G.match(s))return o(s.payload);if(Z.match(s))return void u();if(W.match(s))return a(s.payload);let f=e.getState(),d=()=>{if(f===L)throw Error(Y(23));return f};try{if(c=r(s),t.size>0){let r=e.getState();for(let n of Array.from(t.values())){let t=!1;try{t=n.predicate(s,r,f)}catch(e){t=!1,H(i,e,{raisedBy:"predicate"})}t&&l(n,s,e,d)}}}finally{f=L}return c},startListening:o,stopListening:a,clearListeners:u}};function Y(e){return`Minified Redux Toolkit error #${e}; visit https://redux-toolkit.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}Symbol.for("rtk-state-proxy-original")},5714:(e,t,r)=>{"use strict";r.d(t,{J:()=>n});var n=e=>e.tooltip},5803:(e,t,r)=>{"use strict";r.d(t,{dc:()=>l,ff:()=>a,g0:()=>u});var n=r(8924),i=r(241),o=r.n(i),a=e=>e.legend.settings,l=e=>e.legend.size,u=(0,n.Mz)([e=>e.legend.payload,a],(e,t)=>{var{itemSorter:r}=t,n=e.flat(1);return r?o()(n,r):n})},5845:(e,t,r)=>{"use strict";r.d(t,{i:()=>l});var n,i=r(2115),o=r(2712),a=(n||(n=r.t(i,2)))[" useInsertionEffect ".trim().toString()]||o.N;function l({prop:e,defaultProp:t,onChange:r=()=>{},caller:n}){let[o,l,u]=function({defaultProp:e,onChange:t}){let[r,n]=i.useState(e),o=i.useRef(r),l=i.useRef(t);return a(()=>{l.current=t},[t]),i.useEffect(()=>{o.current!==r&&(l.current?.(r),o.current=r)},[r,o]),[r,n,l]}({defaultProp:t,onChange:r}),s=void 0!==e,c=s?e:o;{let t=i.useRef(void 0!==e);i.useEffect(()=>{let e=t.current;if(e!==s){let t=s?"controlled":"uncontrolled";console.warn(`${n} is changing from ${e?"controlled":"uncontrolled"} to ${t}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`)}t.current=s},[s,n])}return[c,i.useCallback(t=>{if(s){let r="function"==typeof t?t(e):t;r!==e&&u.current?.(r)}else l(t)},[s,e,l,u])]}Symbol("RADIX:SYNC_STATE")},5998:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.flatten=function(e,t=1){let r=[],n=Math.floor(t),i=(e,t)=>{for(let o=0;o{"use strict";r.d(t,{A:()=>o});var n=r(2115),i=r(5155);function o(e,t=[]){let r=[],a=()=>{let t=r.map(e=>n.createContext(e));return function(r){let i=r?.[e]||t;return n.useMemo(()=>({[`__scope${e}`]:{...r,[e]:i}}),[r,i])}};return a.scopeName=e,[function(t,o){let a=n.createContext(o),l=r.length;r=[...r,o];let u=t=>{let{scope:r,children:o,...u}=t,s=r?.[e]?.[l]||a,c=n.useMemo(()=>u,Object.values(u));return(0,i.jsx)(s.Provider,{value:c,children:o})};return u.displayName=t+"Provider",[u,function(r,i){let u=i?.[e]?.[l]||a,s=n.useContext(u);if(s)return s;if(void 0!==o)return o;throw Error(`\`${r}\` must be used within \`${t}\``)}]},function(...e){let t=e[0];if(1===e.length)return t;let r=()=>{let r=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let i=r.reduce((t,{useScope:r,scopeName:n})=>{let i=r(e)[`__scope${n}`];return{...t,...i}},{});return n.useMemo(()=>({[`__scope${t.scopeName}`]:i}),[i])}};return r.scopeName=t.scopeName,r}(a,...t)]}},6101:(e,t,r)=>{"use strict";r.d(t,{s:()=>a,t:()=>o});var n=r(2115);function i(e,t){if("function"==typeof e)return e(t);null!=e&&(e.current=t)}function o(...e){return t=>{let r=!1,n=e.map(e=>{let n=i(e,t);return r||"function"!=typeof n||(r=!0),n});if(r)return()=>{for(let t=0;t{"use strict";var n=r(2115),i=r(1414),o="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},a=i.useSyncExternalStore,l=n.useRef,u=n.useEffect,s=n.useMemo,c=n.useDebugValue;t.useSyncExternalStoreWithSelector=function(e,t,r,n,i){var f=l(null);if(null===f.current){var d={hasValue:!1,value:null};f.current=d}else d=f.current;var p=a(e,(f=s(function(){function e(e){if(!u){if(u=!0,a=e,e=n(e),void 0!==i&&d.hasValue){var t=d.value;if(i(t,e))return l=t}return l=e}if(t=l,o(a,e))return t;var r=n(e);return void 0!==i&&i(t,r)?(a=e,t):(a=e,l=r)}var a,l,u=!1,s=void 0===r?null:r;return[function(){return e(t())},null===s?void 0:function(){return e(s())}]},[t,r,n,i]))[0],f[1]);return u(function(){d.hasValue=!0,d.value=p},[p]),c(p),p}},6124:(e,t,r)=>{"use strict";r.d(t,{Ds:()=>h,HZ:()=>p});var n=r(8924),i=r(5672),o=r.n(i),a=r(5803),l=r(9827),u=r(2589),s=r(6908),c=r(4421);function f(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function d(e){for(var t=1;te.brush.height,s.h,s.W,a.ff,a.dc],(e,t,r,n,i,a,u,s)=>{var f=a.reduce((e,t)=>{var{orientation:r}=t;if(!t.mirror&&!t.hide){var n="number"==typeof t.width?t.width:c.tQ;return d(d({},e),{},{[r]:e[r]+n})}return e},{left:r.left||0,right:r.right||0}),p=i.reduce((e,t)=>{var{orientation:r}=t;return t.mirror||t.hide?e:d(d({},e),{},{[r]:o()(e,"".concat(r))+t.height})},{top:r.top||0,bottom:r.bottom||0}),h=d(d({},p),f),g=h.bottom;h.bottom+=n;var v=e-(h=(0,l.s0)(h,u,s)).left-h.right,m=t-h.top-h.bottom;return d(d({brushBottom:g},h),{},{width:Math.max(v,0),height:Math.max(m,0)})}),h=(0,n.Mz)(p,e=>({x:e.left,y:e.top,width:e.width,height:e.height}));(0,n.Mz)(u.Lp,u.A$,(e,t)=>({x:0,y:0,width:e,height:t}))},6200:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.toKey=function(e){return"string"==typeof e||"symbol"==typeof e?e:Object.is(e?.valueOf?.(),-0)?"-0":String(e)}},6268:(e,t,r)=>{"use strict";r.d(t,{Kv:()=>o,N4:()=>a});var n=r(2115),i=r(1032);function o(e,t){var r,i,o;return e?"function"==typeof(i=r=e)&&(()=>{let e=Object.getPrototypeOf(i);return e.prototype&&e.prototype.isReactComponent})()||"function"==typeof r||"object"==typeof(o=r)&&"symbol"==typeof o.$$typeof&&["react.memo","react.forward_ref"].includes(o.$$typeof.description)?n.createElement(e,t):e:null}function a(e){let t={state:{},onStateChange:()=>{},renderFallbackValue:null,...e},[r]=n.useState(()=>({current:(0,i.ZR)(t)})),[o,a]=n.useState(()=>r.current.initialState);return r.current.setOptions(t=>({...t,...e,state:{...o,...e.state},onStateChange:t=>{a(t),null==e.onStateChange||e.onStateChange(t)}})),r.current}},6377:(e,t,r)=>{"use strict";r.d(t,{CG:()=>p,Et:()=>u,F4:()=>d,GW:()=>h,M8:()=>a,NF:()=>f,Zb:()=>m,_3:()=>l,eP:()=>g,sA:()=>o,uy:()=>v,vh:()=>s});var n=r(5672),i=r.n(n),o=e=>0===e?0:e>0?1:-1,a=e=>"number"==typeof e&&e!=+e,l=e=>"string"==typeof e&&e.indexOf("%")===e.length-1,u=e=>("number"==typeof e||e instanceof Number)&&!a(e),s=e=>u(e)||"string"==typeof e,c=0,f=e=>{var t=++c;return"".concat(e||"").concat(t)},d=function(e,t){var r,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(!u(e)&&"string"!=typeof e)return n;if(l(e)){if(null==t)return n;var o=e.indexOf("%");r=t*parseFloat(e.slice(0,o))/100}else r=+e;return a(r)&&(r=n),i&&null!=t&&r>t&&(r=t),r},p=e=>{if(!Array.isArray(e))return!1;for(var t=e.length,r={},n=0;ne&&("function"==typeof t?t(e):i()(e,t))===r)}var v=e=>null==e,m=e=>v(e)?e:"".concat(e.charAt(0).toUpperCase()).concat(e.slice(1))},6474:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevron-down",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]])},6523:(e,t,r)=>{"use strict";r.d(t,{$g:()=>a,Hw:()=>o,Td:()=>u,au:()=>l,xH:()=>i});var n=r(1971),i=e=>e.options.defaultTooltipEventType,o=e=>e.options.validateTooltipEventTypes;function a(e,t,r){if(null==e)return t;var n=e?"axis":"item";return null==r?t:r.includes(n)?n:t}function l(e,t){return a(t,i(e),o(e))}function u(e){return(0,n.G)(t=>l(t,e))}},6605:(e,t,r)=>{"use strict";r.d(t,{P:()=>s});var n=r(1643);function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function o(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};if(null==e||n.m.isSsr)return{width:0,height:0};var i=(Object.keys(t=o({},r)).forEach(e=>{t[e]||delete t[e]}),t),s=JSON.stringify({text:e,copyStyle:i});if(a.widthCache[s])return a.widthCache[s];try{var c=document.getElementById(u);c||((c=document.createElement("span")).setAttribute("id",u),c.setAttribute("aria-hidden","true"),document.body.appendChild(c));var f=o(o({},l),i);Object.assign(c.style,f),c.textContent="".concat(e);var d=c.getBoundingClientRect(),p={width:d.width,height:d.height};return a.widthCache[s]=p,++a.cacheCount>2e3&&(a.cacheCount=0,a.widthCache={}),p}catch(e){return{width:0,height:0}}}},6633:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isPrimitive=function(e){return null==e||"object"!=typeof e&&"function"!=typeof e}},6641:(e,t,r)=>{"use strict";r.d(t,{dl:()=>u,lJ:()=>l,uN:()=>o});var n=r(5710),i=r(6377);function o(e,t){if(t){var r=Number.parseInt(t,10);if(!(0,i.M8)(r))return null==e?void 0:e[r]}}var a=(0,n.Z0)({name:"options",initialState:{chartName:"",tooltipPayloadSearcher:void 0,eventEmitter:void 0,defaultTooltipEventType:"axis"},reducers:{createEventEmitter:e=>{null==e.eventEmitter&&(e.eventEmitter=Symbol("rechartsEventEmitter"))}}}),l=a.reducer,{createEventEmitter:u}=a.actions},6670:(e,t,r)=>{"use strict";r.d(t,{x:()=>n});var n=e=>e.options.tooltipPayloadSearcher},6752:(e,t,r)=>{"use strict";r.d(t,{$:()=>i});var n=r(1971),i=()=>(0,n.G)(e=>e.rootProps.accessibilityLayer)},6785:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("target",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"6",key:"1vlfrh"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}]])},6850:(e,t,r)=>{"use strict";r.d(t,{l3:()=>m,m7:()=>y});var n=r(2115),i=r(1971),o=r(8478),a=new(r(2661)),l="recharts.syncEvent.tooltip",u="recharts.syncEvent.brush",s=r(6641),c=r(4890),f=r(4732),d=r(215);function p(e){return e.tooltip.syncInteraction}var h=r(7238),g=r(4487),v=()=>{};function m(){var e,t,r,f,p,m,y,b,w,x,S,O=(0,i.j)();(0,n.useEffect)(()=>{O((0,s.dl)())},[O]),e=(0,i.G)(o.lZ),t=(0,i.G)(o.pH),r=(0,i.j)(),f=(0,i.G)(o.hX),p=(0,i.G)(d.R4),m=(0,h.WX)(),y=(0,h.sk)(),b=(0,i.G)(e=>e.rootProps.className),(0,n.useEffect)(()=>{if(null==e)return v;var n=(n,i,o)=>{if(t!==o&&e===n){if("index"===f)return void r(i);if(null!=p){if("function"==typeof f){var a,l=f(p,{activeTooltipIndex:null==i.payload.index?void 0:Number(i.payload.index),isTooltipActive:i.payload.active,activeIndex:null==i.payload.index?void 0:Number(i.payload.index),activeLabel:i.payload.label,activeDataKey:i.payload.dataKey,activeCoordinate:i.payload.coordinate});a=p[l]}else"value"===f&&(a=p.find(e=>String(e.value)===i.payload.label));var{coordinate:u}=i.payload;if(null==a||!1===i.payload.active||null==u||null==y)return void r((0,c.E1)({active:!1,coordinate:void 0,dataKey:void 0,index:null,label:void 0}));var{x:s,y:d}=u,h=Math.min(s,y.x+y.width),g=Math.min(d,y.y+y.height),v={x:"horizontal"===m?a.coordinate:h,y:"horizontal"===m?g:a.coordinate};r((0,c.E1)({active:i.payload.active,coordinate:v,dataKey:i.payload.dataKey,index:String(a.index),label:i.payload.label}))}}};return a.on(l,n),()=>{a.off(l,n)}},[b,r,t,e,f,p,m,y]),w=(0,i.G)(o.lZ),x=(0,i.G)(o.pH),S=(0,i.j)(),(0,n.useEffect)(()=>{if(null==w)return v;var e=(e,t,r)=>{x!==r&&w===e&&S((0,g.M)(t))};return a.on(u,e),()=>{a.off(u,e)}},[S,x,w])}function y(e,t,r,u,s,d){var h=(0,i.G)(r=>(0,f.dp)(r,e,t)),g=(0,i.G)(o.pH),v=(0,i.G)(o.lZ),m=(0,i.G)(o.hX),y=(0,i.G)(p),b=null==y?void 0:y.active;(0,n.useEffect)(()=>{if(!b&&null!=v&&null!=g){var e=(0,c.E1)({active:d,coordinate:r,dataKey:h,index:s,label:"number"==typeof u?String(u):u});a.emit(l,v,e,g)}},[b,r,h,s,u,g,v,m,d])}},6908:(e,t,r)=>{"use strict";r.d(t,{W:()=>o,h:()=>i});var n=r(8924),i=(0,n.Mz)(e=>e.cartesianAxis.xAxis,e=>Object.values(e)),o=(0,n.Mz)(e=>e.cartesianAxis.yAxis,e=>Object.values(e))},7040:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isUnsafeProperty=function(e){return"__proto__"===e}},7062:(e,t,r)=>{"use strict";r.d(t,{Be:()=>v,Cv:()=>S,D0:()=>E,Gl:()=>m,Dc:()=>O});var n=r(8924),i=r(2589),o=r(6124),a=r(5641),l=r(6377),u={allowDuplicatedCategory:!0,angleAxisId:0,reversed:!1,scale:"auto",tick:!0,type:"category"},s={allowDataOverflow:!1,allowDuplicatedCategory:!0,radiusAxisId:0,scale:"auto",tick:!0,tickCount:5,type:"number"},c=r(8190),f=r(7238),d={allowDataOverflow:!1,allowDecimals:!1,allowDuplicatedCategory:!1,dataKey:void 0,domain:void 0,id:u.angleAxisId,includeHidden:!1,name:void 0,reversed:u.reversed,scale:u.scale,tick:u.tick,tickCount:void 0,ticks:void 0,type:u.type,unit:void 0},p={allowDataOverflow:s.allowDataOverflow,allowDecimals:!1,allowDuplicatedCategory:s.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:s.radiusAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:s.scale,tick:s.tick,tickCount:s.tickCount,ticks:void 0,type:s.type,unit:void 0},h={allowDataOverflow:!1,allowDecimals:!1,allowDuplicatedCategory:u.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:u.angleAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:u.scale,tick:u.tick,tickCount:void 0,ticks:void 0,type:"number",unit:void 0},g={allowDataOverflow:s.allowDataOverflow,allowDecimals:!1,allowDuplicatedCategory:s.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:s.radiusAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:s.scale,tick:s.tick,tickCount:s.tickCount,ticks:void 0,type:"category",unit:void 0},v=(e,t)=>null!=e.polarAxis.angleAxis[t]?e.polarAxis.angleAxis[t]:"radial"===e.layout.layoutType?h:d,m=(e,t)=>null!=e.polarAxis.radiusAxis[t]?e.polarAxis.radiusAxis[t]:"radial"===e.layout.layoutType?g:p,y=e=>e.polarOptions,b=(0,n.Mz)([i.Lp,i.A$,o.HZ],a.lY),w=(0,n.Mz)([y,b],(e,t)=>{if(null!=e)return(0,l.F4)(e.innerRadius,t,0)}),x=(0,n.Mz)([y,b],(e,t)=>{if(null!=e)return(0,l.F4)(e.outerRadius,t,.8*t)}),S=(0,n.Mz)([y],e=>{if(null==e)return[0,0];var{startAngle:t,endAngle:r}=e;return[t,r]});(0,n.Mz)([v,S],c.I);var O=(0,n.Mz)([b,w,x],(e,t,r)=>{if(null!=e&&null!=t&&null!=r)return[t,r]});(0,n.Mz)([m,O],c.I);var E=(0,n.Mz)([f.fz,y,w,x,i.Lp,i.A$],(e,t,r,n,i,o)=>{if(("centric"===e||"radial"===e)&&null!=t&&null!=r&&null!=n){var{cx:a,cy:u,startAngle:s,endAngle:c}=t;return{cx:(0,l.F4)(a,i,i/2),cy:(0,l.F4)(u,o,o/2),innerRadius:r,outerRadius:n,startAngle:s,endAngle:c,clockWise:!1}}})},7064:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(5181),i=r(1551),o=r(4072);t.orderBy=function(e,t,r,a){if(null==e)return[];r=a?void 0:r,Array.isArray(e)||(e=Object.values(e)),Array.isArray(t)||(t=null==t?[null]:[t]),0===t.length&&(t=[null]),Array.isArray(r)||(r=null==r?[]:[r]),r=r.map(e=>String(e));let l=(e,t)=>{let r=e;for(let e=0;e(Array.isArray(e)&&1===e.length&&(e=e[0]),null==e||"function"==typeof e||Array.isArray(e)||i.isKey(e))?e:{key:e,path:o.toPath(e)});return e.map(e=>({original:e,criteria:u.map(t=>{var r,n;return r=t,null==(n=e)||null==r?n:"object"==typeof r&&"key"in r?Object.hasOwn(n,r.key)?n[r.key]:l(n,r.path):"function"==typeof r?r(n):Array.isArray(r)?l(n,r):"object"==typeof n?n[r]:n})})).slice().sort((e,t)=>{for(let i=0;ie.original)}},7156:(e,t,r)=>{"use strict";r.d(t,{Gk:()=>ek,Vf:()=>eM});var n=r(2115),i=r(2596),o=r(688),a=r(3597),l=r(788);function u(){return(u=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{cx:t,cy:r,r:o,className:s}=e,c=(0,i.$)("recharts-dot",s);return t===+t&&r===+r&&o===+o?n.createElement("circle",u({},(0,l.J9)(e,!1),(0,a._U)(e),{className:c,cx:t,cy:r,r:o})):null},c=r(2348),f=r(8080),d=r.n(f),p=r(379),h=r(9827),g=r(6377),v=["valueAccessor"],m=["data","dataKey","clockWise","id","textBreakAll"];function y(){return(y=Object.assign?Object.assign.bind():function(e){for(var t=1;tArray.isArray(e.value)?d()(e.value):e.value;function O(e){var{valueAccessor:t=S}=e,r=x(e,v),{data:i,dataKey:o,clockWise:a,id:u,textBreakAll:s}=r,f=x(r,m);return i&&i.length?n.createElement(c.W,{className:"recharts-label-list"},i.map((e,r)=>{var i=(0,g.uy)(o)?t(e,r):(0,h.kr)(e&&e.payload,o),c=(0,g.uy)(u)?{}:{id:"".concat(u,"-").concat(r)};return n.createElement(p.J,y({},(0,l.J9)(e,!0),f,c,{parentViewBox:e.parentViewBox,value:i,textBreakAll:s,viewBox:p.J.parseViewBox((0,g.uy)(a)?e:w(w({},e),{},{clockWise:a})),key:"label-".concat(r),index:r}))})):null}O.displayName="LabelList",O.renderCallByParent=function(e,t){var r,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(!e||!e.children&&i&&!e.label)return null;var{children:o}=e,a=(0,l.aS)(o,O).map((e,r)=>(0,n.cloneElement)(e,{data:t,key:"labelList-".concat(r)}));return i?[(r=e.label,r?!0===r?n.createElement(O,{key:"labelList-implicit",data:t}):n.isValidElement(r)||(0,p.Z)(r)?n.createElement(O,{key:"labelList-implicit",data:t,content:r}):"object"==typeof r?n.createElement(O,y({data:t},r,{key:"labelList-implicit"})):null:null),...a]:a};var E=r(1643),C=r(1971),M=r(215),k=r(8234);function _(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function P(e){for(var t=1;tf.includes(e.payload));return(0,g.uy)(d)?null:(e=>{var t,{point:r,childIndex:i,mainColor:o,activeDot:u,dataKey:f}=e;if(!1===u||null==r.x||null==r.y)return null;var d=P(P({index:i,dataKey:f,cx:r.x,cy:r.y,r:4,fill:null!=o?o:"none",strokeWidth:2,stroke:"#fff",payload:r.payload,value:r.value},(0,l.J9)(u,!1)),(0,a._U)(u));return t=(0,n.isValidElement)(u)?(0,n.cloneElement)(u,d):"function"==typeof u?u(d):n.createElement(s,d),n.createElement(c.W,{className:"recharts-active-dot"},t)})({point:d,childIndex:Number(u),mainColor:r,dataKey:o,activeDot:i})}var j=r(4890),T=r(1807);function R(e){var{fn:t,args:r}=e,i=(0,C.j)(),o=(0,T.r)();return(0,n.useEffect)(()=>{if(!o){var e=t(r);return i((0,j.Ix)(e)),()=>{i((0,j.XB)(e))}}},[t,r,i,o]),null}var z=r(2248);function D(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function I(e){for(var t=1;t{var n=I(I({},e),{},{stackId:(0,h.$8)(e.stackId)});null===r.current?t((0,z.g5)(n)):r.current!==n&&t((0,z.ZF)({prev:r.current,next:n})),r.current=n},[t,e]),(0,n.useEffect)(()=>()=>{r.current&&(t((0,z.Vi)(r.current)),r.current=null)},[t]),null}var L=()=>{},F=(0,n.createContext)({addErrorBar:L,removeErrorBar:L}),$=e=>{var{children:t,xAxisId:r,yAxisId:i,zAxisId:o,dataKey:a,data:l,stackId:u,hide:s,type:c,barSize:f}=e,[d,p]=n.useState([]),h=(0,n.useCallback)(e=>{p(t=>[...t,e])},[p]),g=(0,n.useCallback)(e=>{p(t=>t.filter(t=>t!==e))},[p]),v=(0,T.r)();return n.createElement(F.Provider,{value:{addErrorBar:h,removeErrorBar:g}},n.createElement(N,{type:c,data:l,xAxisId:r,yAxisId:i,zAxisId:o,dataKey:a,errorBars:d,stackId:u,hide:s,barSize:f,isPanorama:v}),t)},B=r(2183);function V(e,t){var r,n,i=(0,C.G)(t=>(0,B.Rl)(t,e)),o=(0,C.G)(e=>(0,B.sf)(e,t)),a=null!=(r=null==i?void 0:i.allowDataOverflow)?r:B.PU.allowDataOverflow,l=null!=(n=null==o?void 0:o.allowDataOverflow)?n:B.cd.allowDataOverflow;return{needClip:a||l,needClipX:a,needClipY:l}}function U(e){var{xAxisId:t,yAxisId:r,clipPathId:i}=e,o=(0,k.oM)(),{needClipX:a,needClipY:l,needClip:u}=V(t,r);if(!u)return null;var{x:s,y:c,width:f,height:d}=o;return n.createElement("clipPath",{id:"clipPath-".concat(i)},n.createElement("rect",{x:a?s:s-f/2,y:l?c:c-d/2,width:a?f:2*f,height:l?d:2*d}))}var H=r(8924),G=r(7238),Z=r(356),W=(e,t,r,n)=>(0,B.Gx)(e,"xAxis",t,n),K=(e,t,r,n)=>(0,B.CR)(e,"xAxis",t,n),q=(e,t,r,n)=>(0,B.Gx)(e,"yAxis",r,n),Y=(e,t,r,n)=>(0,B.CR)(e,"yAxis",r,n),X=(0,H.Mz)([G.fz,W,q,K,Y],(e,t,r,n,i)=>(0,h._L)(e,"xAxis")?(0,h.Hj)(t,n,!1):(0,h.Hj)(r,i,!1)),J=(0,H.Mz)([B.ld,(e,t,r,n,i)=>i],(e,t)=>{if(e.some(e=>"area"===e.type&&t.dataKey===e.dataKey&&(0,h.$8)(t.stackId)===e.stackId&&t.data===e.data))return t}),Q=(0,H.Mz)([G.fz,W,q,K,Y,(e,t,r,n,i)=>{var o,a,l=(0,G.fz)(e);if(null!=(a=(0,h._L)(l,"xAxis")?(0,B.TC)(e,"yAxis",r,n):(0,B.TC)(e,"xAxis",t,n))){var{dataKey:u,stackId:s}=i;if(null!=s){var c=null==(o=a[s])?void 0:o.stackedData;return null==c?void 0:c.find(e=>e.key===u)}}},Z.HS,X,J],(e,t,r,n,i,o,a,l,u)=>{var s,{chartData:c,dataStartIndex:f,dataEndIndex:d}=a;if(null!=u&&("horizontal"===e||"vertical"===e)&&null!=t&&null!=r&&null!=n&&null!=i&&0!==n.length&&0!==i.length&&null!=l){var{data:p}=u;if(null!=(s=p&&p.length>0?p:null==c?void 0:c.slice(f,d+1)))return eM({layout:e,xAxis:t,yAxis:r,xAxisTicks:n,yAxisTicks:i,dataStartIndex:f,areaSettings:u,stackedData:o,displayedData:s,chartBaseValue:void 0,bandSize:l})}}),ee=r(4732),et=r(2634),er=()=>{};function en(e){var{legendPayload:t}=e,r=(0,C.j)(),i=(0,T.r)();return(0,n.useEffect)(()=>i?er:(r((0,et.Lx)(t)),()=>{r((0,et.u3)(t))}),[r,i,t]),null}var ei=r(3389),eo=r(8892),ea=r(4460),el=["layout","type","stroke","connectNulls","isRange"],eu=["activeDot","animationBegin","animationDuration","animationEasing","connectNulls","dot","fill","fillOpacity","hide","isAnimationActive","legendType","stroke","xAxisId","yAxisId"];function es(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{var o,a=ef(ef(ef({key:"dot-".concat(t),r:3},p),h),{},{index:t,cx:e.x,cy:e.y,dataKey:f,value:e.value,payload:e.payload,points:r});if(n.isValidElement(u))o=n.cloneElement(u,a);else if("function"==typeof u)o=u(a);else{var l=(0,i.$)("recharts-area-dot","boolean"!=typeof u?u.className:"");o=n.createElement(s,ep({},a,{className:l}))}return o}),v={clipPath:a?"url(#clipPath-".concat(d?"":"dots-").concat(t,")"):void 0};return n.createElement(c.W,ep({className:"recharts-area-dots"},v),g)}function em(e){var{points:t,baseLine:r,needClip:i,clipPathId:a,props:u,showLabels:s}=e,{layout:f,type:d,stroke:p,connectNulls:h,isRange:g}=u,v=es(u,el);return n.createElement(n.Fragment,null,(null==t?void 0:t.length)>1&&n.createElement(c.W,{clipPath:i?"url(#clipPath-".concat(a,")"):void 0},n.createElement(o.I,ep({},(0,l.J9)(v,!0),{points:t,connectNulls:h,type:d,baseLine:r,layout:f,stroke:"none",className:"recharts-area-area"})),"none"!==p&&n.createElement(o.I,ep({},(0,l.J9)(u,!1),{className:"recharts-area-curve",layout:f,type:d,connectNulls:h,fill:"none",points:t})),"none"!==p&&g&&n.createElement(o.I,ep({},(0,l.J9)(u,!1),{className:"recharts-area-curve",layout:f,type:d,connectNulls:h,fill:"none",points:r}))),n.createElement(ev,{points:t,props:u,clipPathId:a}),s&&O.renderCallByParent(u,t))}function ey(e){var{alpha:t,baseLine:r,points:i,strokeWidth:o}=e,a=i[0].y,l=i[i.length-1].y;if(!(0,eo.H)(a)||!(0,eo.H)(l))return null;var u=t*Math.abs(a-l),s=Math.max(...i.map(e=>e.x||0));return((0,g.Et)(r)?s=Math.max(r,s):r&&Array.isArray(r)&&r.length&&(s=Math.max(...r.map(e=>e.x||0),s)),(0,g.Et)(s))?n.createElement("rect",{x:0,y:ae.y||0));return((0,g.Et)(r)?s=Math.max(r,s):r&&Array.isArray(r)&&r.length&&(s=Math.max(...r.map(e=>e.y||0),s)),(0,g.Et)(s))?n.createElement("rect",{x:a1&&void 0!==arguments[1]?arguments[1]:"animation-",r=(0,n.useRef)((0,g.NF)(t)),i=(0,n.useRef)(e);return i.current!==e&&(r.current=(0,g.NF)(t),i.current=e),r.current}(i,"recharts-area-"),[y,b]=(0,n.useState)(!0),w=(0,n.useCallback)(()=>{"function"==typeof v&&v(),b(!1)},[v]),x=(0,n.useCallback)(()=>{"function"==typeof h&&h(),b(!0)},[h]),S=o.current,O=a.current;return n.createElement(ea.i,{begin:f,duration:d,isActive:s,easing:p,from:{t:0},to:{t:1},onAnimationEnd:w,onAnimationStart:x,key:m},e=>{var{t:s}=e;if(S){var f,d=S.length/l.length,p=1===s?l:l.map((e,t)=>{var r=Math.floor(t*d);if(S[r]){var n=S[r];return ef(ef({},e),{},{x:(0,g.GW)(n.x,e.x,s),y:(0,g.GW)(n.y,e.y,s)})}return e});return f=(0,g.Et)(u)?(0,g.GW)(O,u,s):(0,g.uy)(u)||(0,g.M8)(u)?(0,g.GW)(O,0,s):u.map((e,t)=>{var r=Math.floor(t*d);if(Array.isArray(O)&&O[r]){var n=O[r];return ef(ef({},e),{},{x:(0,g.GW)(n.x,e.x,s),y:(0,g.GW)(n.y,e.y,s)})}return e}),s>0&&(o.current=p,a.current=f),n.createElement(em,{points:p,baseLine:f,needClip:t,clipPathId:r,props:i,showLabels:!y})}return s>0&&(o.current=l,a.current=u),n.createElement(c.W,null,n.createElement("defs",null,n.createElement("clipPath",{id:"animationClipPath-".concat(r)},n.createElement(ew,{alpha:s,points:l,baseLine:u,layout:i.layout,strokeWidth:i.strokeWidth}))),n.createElement(c.W,{clipPath:"url(#animationClipPath-".concat(r,")")},n.createElement(em,{points:l,baseLine:u,needClip:t,clipPathId:r,props:i,showLabels:!0})))})}function eS(e){var{needClip:t,clipPathId:r,props:i}=e,{points:o,baseLine:a,isAnimationActive:l}=i,u=(0,n.useRef)(null),s=(0,n.useRef)(),c=u.current,f=s.current;return l&&o&&o.length&&(c!==o||f!==a)?n.createElement(ex,{needClip:t,clipPathId:r,props:i,previousPointsRef:u,previousBaselineRef:s}):n.createElement(em,{points:o,baseLine:a,needClip:t,clipPathId:r,props:i,showLabels:!0})}class eO extends n.PureComponent{render(){var e,{hide:t,dot:r,points:o,className:a,top:u,left:s,needClip:f,xAxisId:d,yAxisId:p,width:h,height:v,id:m,baseLine:y}=this.props;if(t)return null;var b=(0,i.$)("recharts-area",a),w=(0,g.uy)(m)?this.id:m,{r:x=3,strokeWidth:S=2}=null!=(e=(0,l.J9)(r,!1))?e:{r:3,strokeWidth:2},O=(0,l.y$)(r),E=2*x+S;return n.createElement(n.Fragment,null,n.createElement(c.W,{className:b},f&&n.createElement("defs",null,n.createElement(U,{clipPathId:w,xAxisId:d,yAxisId:p}),!O&&n.createElement("clipPath",{id:"clipPath-dots-".concat(w)},n.createElement("rect",{x:s-E/2,y:u-E/2,width:h+E,height:v+E}))),n.createElement(eS,{needClip:f,clipPathId:w,props:this.props})),n.createElement(A,{points:o,mainColor:eh(this.props.stroke,this.props.fill),itemDataKey:this.props.dataKey,activeDot:this.props.activeDot}),this.props.isRange&&Array.isArray(y)&&n.createElement(A,{points:y,mainColor:eh(this.props.stroke,this.props.fill),itemDataKey:this.props.dataKey,activeDot:this.props.activeDot}))}constructor(){super(...arguments),ed(this,"id",(0,g.NF)("recharts-area-"))}}var eE={activeDot:!0,animationBegin:0,animationDuration:1500,animationEasing:"ease",connectNulls:!1,dot:!1,fill:"#3182bd",fillOpacity:.6,hide:!1,isAnimationActive:!E.m.isSsr,legendType:"line",stroke:"#3182bd",xAxisId:0,yAxisId:0};function eC(e){var t,r=(0,ei.e)(e,eE),{activeDot:i,animationBegin:o,animationDuration:a,animationEasing:l,connectNulls:u,dot:s,fill:c,fillOpacity:f,hide:d,isAnimationActive:p,legendType:h,stroke:g,xAxisId:v,yAxisId:m}=r,y=es(r,eu),b=(0,G.WX)(),w=(0,ee.fW)(),{needClip:x}=V(v,m),S=(0,T.r)(),O=(0,n.useMemo)(()=>({baseValue:e.baseValue,stackId:e.stackId,connectNulls:u,data:e.data,dataKey:e.dataKey}),[e.baseValue,e.stackId,u,e.data,e.dataKey]),{points:E,isRange:M,baseLine:_}=null!=(t=(0,C.G)(e=>Q(e,v,m,S,O)))?t:{},{height:P,width:A,x:j,y:R}=(0,k.oM)();return"horizontal"!==b&&"vertical"!==b||"AreaChart"!==w&&"ComposedChart"!==w?null:n.createElement(eO,ep({},y,{activeDot:i,animationBegin:o,animationDuration:a,animationEasing:l,baseLine:_,connectNulls:u,dot:s,fill:c,fillOpacity:f,height:P,hide:d,layout:b,isAnimationActive:p,isRange:M,legendType:h,needClip:x,points:E,stroke:g,width:A,left:j,top:R,xAxisId:v,yAxisId:m}))}function eM(e){var t,{areaSettings:{connectNulls:r,baseValue:n,dataKey:i},stackedData:o,layout:a,chartBaseValue:l,xAxis:u,yAxis:s,displayedData:c,dataStartIndex:f,xAxisTicks:d,yAxisTicks:p,bandSize:v}=e,m=o&&o.length,y=((e,t,r,n,i)=>{var o=null!=r?r:t;if((0,g.Et)(o))return o;var a="horizontal"===e?i:n,l=a.scale.domain();if("number"===a.type){var u=Math.max(l[0],l[1]),s=Math.min(l[0],l[1]);return"dataMin"===o?s:"dataMax"===o||u<0?u:Math.max(Math.min(l[0],l[1]),0)}return"dataMin"===o?l[0]:"dataMax"===o?l[1]:l[0]})(a,l,n,u,s),b="horizontal"===a,w=!1,x=c.map((e,t)=>{m?n=o[f+t]:Array.isArray(n=(0,h.kr)(e,i))?w=!0:n=[y,n];var n,a=null==n[1]||m&&!r&&null==(0,h.kr)(e,i);return b?{x:(0,h.nb)({axis:u,ticks:d,bandSize:v,entry:e,index:t}),y:a?null:s.scale(n[1]),value:n,payload:e}:{x:a?null:u.scale(n[1]),y:(0,h.nb)({axis:s,ticks:p,bandSize:v,entry:e,index:t}),value:n,payload:e}});return t=m||w?x.map(e=>{var t=Array.isArray(e.value)?e.value[0]:null;return b?{x:e.x,y:null!=t&&null!=e.y?s.scale(t):null}:{x:null!=t?u.scale(t):null,y:e.y}}):b?s.scale(y):u.scale(y),{points:x,baseLine:t,isRange:w}}class ek extends n.PureComponent{render(){return n.createElement($,{type:"area",data:this.props.data,dataKey:this.props.dataKey,xAxisId:this.props.xAxisId,yAxisId:this.props.yAxisId,zAxisId:0,stackId:this.props.stackId,hide:this.props.hide,barSize:void 0},n.createElement(en,{legendPayload:(e=>{var{dataKey:t,name:r,stroke:n,fill:i,legendType:o,hide:a}=e;return[{inactive:a,dataKey:t,type:o,color:eh(n,i),value:(0,h.uM)(r,t),payload:e}]})(this.props)}),n.createElement(R,{fn:eg,args:this.props}),n.createElement(eC,this.props))}}ed(ek,"displayName","Area"),ed(ek,"defaultProps",eE)},7238:(e,t,r)=>{"use strict";r.d(t,{Kp:()=>h,W7:()=>c,WX:()=>v,fz:()=>g,rY:()=>d,sk:()=>u,yi:()=>f}),r(2115);var n=r(1971),i=r(6124),o=r(2589),a=r(1807),l=r(972),u=()=>{var e,t=(0,a.r)(),r=(0,n.G)(i.Ds),o=(0,n.G)(l.U),u=null==(e=(0,n.G)(l.C))?void 0:e.padding;return t&&o&&u?{width:o.width-u.left-u.right,height:o.height-u.top-u.bottom,x:u.left,y:u.top}:r},s={top:0,bottom:0,left:0,right:0,width:0,height:0,brushBottom:0},c=()=>{var e;return null!=(e=(0,n.G)(i.HZ))?e:s},f=()=>(0,n.G)(o.Lp),d=()=>(0,n.G)(o.A$),p={top:0,right:0,bottom:0,left:0},h=()=>{var e;return null!=(e=(0,n.G)(e=>e.layout.margin))?e:p},g=e=>e.layout.layoutType,v=()=>(0,n.G)(g)},7298:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(9738);t.cloneDeep=function(e){return n.cloneDeepWith(e)}},7328:(e,t,r)=>{"use strict";function n(e,t,r){if(!t.has(e))throw TypeError("attempted to "+r+" private field on non-instance");return t.get(e)}function i(e,t){var r=n(e,t,"get");return r.get?r.get.call(e):r.value}function o(e,t,r){var i=n(e,t,"set");if(i.set)i.set.call(e,r);else{if(!i.writable)throw TypeError("attempted to set read only private field");i.value=r}return r}r.d(t,{N:()=>d});var a,l=r(2115),u=r(6081),s=r(6101),c=r(9708),f=r(5155);function d(e){let t=e+"CollectionProvider",[r,n]=(0,u.A)(t),[i,o]=r(t,{collectionRef:{current:null},itemMap:new Map}),a=e=>{let{scope:t,children:r}=e,n=l.useRef(null),o=l.useRef(new Map).current;return(0,f.jsx)(i,{scope:t,itemMap:o,collectionRef:n,children:r})};a.displayName=t;let d=e+"CollectionSlot",p=(0,c.TL)(d),h=l.forwardRef((e,t)=>{let{scope:r,children:n}=e,i=o(d,r),a=(0,s.s)(t,i.collectionRef);return(0,f.jsx)(p,{ref:a,children:n})});h.displayName=d;let g=e+"CollectionItemSlot",v="data-radix-collection-item",m=(0,c.TL)(g),y=l.forwardRef((e,t)=>{let{scope:r,children:n,...i}=e,a=l.useRef(null),u=(0,s.s)(t,a),c=o(g,r);return l.useEffect(()=>(c.itemMap.set(a,{ref:a,...i}),()=>void c.itemMap.delete(a))),(0,f.jsx)(m,{...{[v]:""},ref:u,children:n})});return y.displayName=g,[{Provider:a,Slot:h,ItemSlot:y},function(t){let r=o(e+"CollectionConsumer",t);return l.useCallback(()=>{let e=r.collectionRef.current;if(!e)return[];let t=Array.from(e.querySelectorAll("[".concat(v,"]")));return Array.from(r.itemMap.values()).sort((e,r)=>t.indexOf(e.ref.current)-t.indexOf(r.ref.current))},[r.collectionRef,r.itemMap])},n]}var p=new WeakMap;function h(e,t){if("at"in Array.prototype)return Array.prototype.at.call(e,t);let r=function(e,t){let r=e.length,n=g(t),i=n>=0?n:r+n;return i<0||i>=r?-1:i}(e,t);return -1===r?void 0:e[r]}function g(e){return e!=e||0===e?0:Math.trunc(e)}a=new WeakMap,class e extends Map{set(e,t){return p.get(this)&&(this.has(e)?i(this,a)[i(this,a).indexOf(e)]=e:i(this,a).push(e)),super.set(e,t),this}insert(e,t,r){let n,o=this.has(t),l=i(this,a).length,u=g(e),s=u>=0?u:l+u,c=s<0||s>=l?-1:s;if(c===this.size||o&&c===this.size-1||-1===c)return this.set(t,r),this;let f=this.size+ +!o;u<0&&s++;let d=[...i(this,a)],p=!1;for(let e=s;e=this.size&&(n=this.size-1),this.at(n)}keyFrom(e,t){let r=this.indexOf(e);if(-1===r)return;let n=r+t;return n<0&&(n=0),n>=this.size&&(n=this.size-1),this.keyAt(n)}find(e,t){let r=0;for(let n of this){if(Reflect.apply(e,t,[n,r,this]))return n;r++}}findIndex(e,t){let r=0;for(let n of this){if(Reflect.apply(e,t,[n,r,this]))return r;r++}return -1}filter(t,r){let n=[],i=0;for(let e of this)Reflect.apply(t,r,[e,i,this])&&n.push(e),i++;return new e(n)}map(t,r){let n=[],i=0;for(let e of this)n.push([e[0],Reflect.apply(t,r,[e,i,this])]),i++;return new e(n)}reduce(){for(var e=arguments.length,t=Array(e),r=0;r=0;e--){let r=this.at(e);o=e===this.size-1&&1===t.length?r:Reflect.apply(n,this,[o,r,e,this])}return o}toSorted(t){return new e([...this.entries()].sort(t))}toReversed(){let t=new e;for(let e=this.size-1;e>=0;e--){let r=this.keyAt(e),n=this.get(r);t.set(r,n)}return t}toSpliced(){for(var t=arguments.length,r=Array(t),n=0;n0&&(i=r-1);for(let e=t;e<=i;e++){let t=this.keyAt(e),r=this.get(t);n.set(t,r)}return n}every(e,t){let r=0;for(let n of this){if(!Reflect.apply(e,t,[n,r,this]))return!1;r++}return!0}some(e,t){let r=0;for(let n of this){if(Reflect.apply(e,t,[n,r,this]))return!0;r++}return!1}constructor(e){super(e),function(e,t,r){if(t.has(e))throw TypeError("Cannot initialize the same private elements twice on an object");t.set(e,r)}(this,a,{writable:!0,value:void 0}),o(this,a,[...super.keys()]),p.set(this,!0)}}},7547:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(3676),i=r(2465),o=r(656),a=r(1571);t.uniqBy=function(e,t=i.identity){return o.isArrayLikeObject(e)?n.uniqBy(Array.from(e),a.iteratee(t)):[]}},7580:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]])},7863:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevron-up",[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]])},7918:(e,t,r)=>{"use strict";r.d(t,{V:()=>i});var n=r(2115);function i(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],[t,r]=(0,n.useState)({height:0,left:0,top:0,width:0}),i=(0,n.useCallback)(e=>{if(null!=e){var n=e.getBoundingClientRect(),i={height:n.height,left:n.left,top:n.top,width:n.width};(Math.abs(i.height-t.height)>1||Math.abs(i.left-t.left)>1||Math.abs(i.top-t.top)>1||Math.abs(i.width-t.width)>1)&&r({height:i.height,left:i.left,top:i.top,width:i.width})}},[t.width,t.height,t.top,t.left,...e]);return[t,i]}},8060:(e,t,r)=>{"use strict";r.d(t,{M:()=>o,t:()=>i});var n=r(2115),i=(0,n.createContext)(null),o=()=>(0,n.useContext)(i)},8080:(e,t,r)=>{e.exports=r(8359).last},8124:(e,t,r)=>{"use strict";r.d(t,{m:()=>ef});var n=r(2115),i=r(7650),o=r(241),a=r.n(o),l=r(2596),u=r(6377);function s(){return(s=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{separator:t=" : ",contentStyle:r={},itemStyle:i={},labelStyle:o={},payload:c,formatter:p,itemSorter:h,wrapperClassName:g,labelClassName:v,label:m,labelFormatter:y,accessibilityLayer:b=!1}=e,w=f({margin:0,padding:10,backgroundColor:"#fff",border:"1px solid #ccc",whiteSpace:"nowrap"},r),x=f({margin:0},o),S=!(0,u.uy)(m),O=S?m:"",E=(0,l.$)("recharts-default-tooltip",g),C=(0,l.$)("recharts-tooltip-label",v);return S&&y&&null!=c&&(O=y(m,c)),n.createElement("div",s({className:E,style:w},b?{role:"status","aria-live":"assertive"}:{}),n.createElement("p",{className:C,style:x},n.isValidElement(O)?O:"".concat(O)),(()=>{if(c&&c.length){var e=(h?a()(c,h):c).map((e,r)=>{if("none"===e.type)return null;var o=e.formatter||p||d,{value:a,name:l}=e,s=a,h=l;if(o){var g=o(a,l,e,r,c);if(Array.isArray(g))[s,h]=g;else{if(null==g)return null;s=g}}var v=f({display:"block",paddingTop:4,paddingBottom:4,color:e.color||"#000"},i);return n.createElement("li",{className:"recharts-tooltip-item",key:"tooltip-item-".concat(r),style:v},(0,u.vh)(h)?n.createElement("span",{className:"recharts-tooltip-item-name"},h):null,(0,u.vh)(h)?n.createElement("span",{className:"recharts-tooltip-item-separator"},t):null,n.createElement("span",{className:"recharts-tooltip-item-value"},s),n.createElement("span",{className:"recharts-tooltip-item-unit"},e.unit||""))});return n.createElement("ul",{className:"recharts-tooltip-item-list",style:{padding:0,margin:0}},e)}return null})())},h="recharts-tooltip-wrapper",g={visibility:"hidden"};function v(e){var{allowEscapeViewBox:t,coordinate:r,key:n,offsetTopLeft:i,position:o,reverseDirection:a,tooltipDimension:l,viewBox:s,viewBoxDimension:c}=e;if(o&&(0,u.Et)(o[n]))return o[n];var f=r[n]-l-(i>0?i:0),d=r[n]+i;if(t[n])return a[n]?f:d;var p=s[n];return null==p?0:a[n]?fp+c?Math.max(f,p):Math.max(d,p)}function m(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function y(e){for(var t=1;t0&&f.width>0&&o?function(e){var{translateX:t,translateY:r,useTranslate3d:n}=e;return{transform:n?"translate3d(".concat(t,"px, ").concat(r,"px, 0)"):"translate(".concat(t,"px, ").concat(r,"px)")}}({translateX:r=v({allowEscapeViewBox:i,coordinate:o,key:"x",offsetTopLeft:a,position:s,reverseDirection:c,tooltipDimension:f.width,viewBox:p,viewBoxDimension:p.width}),translateY:n=v({allowEscapeViewBox:i,coordinate:o,key:"y",offsetTopLeft:a,position:s,reverseDirection:c,tooltipDimension:f.height,viewBox:p,viewBoxDimension:p.height}),useTranslate3d:d}):g,cssClasses:function(e){var{coordinate:t,translateX:r,translateY:n}=e;return(0,l.$)(h,{["".concat(h,"-right")]:(0,u.Et)(r)&&t&&(0,u.Et)(t.x)&&r>=t.x,["".concat(h,"-left")]:(0,u.Et)(r)&&t&&(0,u.Et)(t.x)&&r=t.y,["".concat(h,"-top")]:(0,u.Et)(n)&&t&&(0,u.Et)(t.y)&&n{if("Escape"===e.key){var t,r,n,i;this.setState({dismissed:!0,dismissedAtCoordinate:{x:null!=(t=null==(r=this.props.coordinate)?void 0:r.x)?t:0,y:null!=(n=null==(i=this.props.coordinate)?void 0:i.y)?n:0}})}})}}var x=r(1643),S=r(2494),O=r(7238),E=r(6752),C=r(7918),M=r(688),k=r(788),_=["x","y","top","left","width","height","className"];function P(){return(P=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{x:t=0,y:r=0,top:i=0,left:o=0,width:a=0,height:s=0,className:c}=e,f=function(e){for(var t=1;t{var o,a=Math.min(Math.abs(r)/2,Math.abs(n)/2),l=n>=0?1:-1,u=r>=0?1:-1,s=+(n>=0&&r>=0||n<0&&r<0);if(a>0&&i instanceof Array){for(var c=[0,0,0,0],f=0;f<4;f++)c[f]=i[f]>a?a:i[f];o="M".concat(e,",").concat(t+l*c[0]),c[0]>0&&(o+="A ".concat(c[0],",").concat(c[0],",0,0,").concat(s,",").concat(e+u*c[0],",").concat(t)),o+="L ".concat(e+r-u*c[1],",").concat(t),c[1]>0&&(o+="A ".concat(c[1],",").concat(c[1],",0,0,").concat(s,",\n ").concat(e+r,",").concat(t+l*c[1])),o+="L ".concat(e+r,",").concat(t+n-l*c[2]),c[2]>0&&(o+="A ".concat(c[2],",").concat(c[2],",0,0,").concat(s,",\n ").concat(e+r-u*c[2],",").concat(t+n)),o+="L ".concat(e+u*c[3],",").concat(t+n),c[3]>0&&(o+="A ".concat(c[3],",").concat(c[3],",0,0,").concat(s,",\n ").concat(e,",").concat(t+n-l*c[3])),o+="Z"}else if(a>0&&i===+i&&i>0){var d=Math.min(a,i);o="M ".concat(e,",").concat(t+l*d,"\n A ").concat(d,",").concat(d,",0,0,").concat(s,",").concat(e+u*d,",").concat(t,"\n L ").concat(e+r-u*d,",").concat(t,"\n A ").concat(d,",").concat(d,",0,0,").concat(s,",").concat(e+r,",").concat(t+l*d,"\n L ").concat(e+r,",").concat(t+n-l*d,"\n A ").concat(d,",").concat(d,",0,0,").concat(s,",").concat(e+r-u*d,",").concat(t+n,"\n L ").concat(e+u*d,",").concat(t+n,"\n A ").concat(d,",").concat(d,",0,0,").concat(s,",").concat(e,",").concat(t+n-l*d," Z")}else o="M ".concat(e,",").concat(t," h ").concat(r," v ").concat(n," h ").concat(-r," Z");return o},I={x:0,y:0,width:0,height:0,radius:0,isAnimationActive:!1,isUpdateAnimationActive:!1,animationBegin:0,animationDuration:1500,animationEasing:"ease"},N=e=>{var t=(0,T.e)(e,I),r=(0,n.useRef)(null),[i,o]=(0,n.useState)(-1);(0,n.useEffect)(()=>{if(r.current&&r.current.getTotalLength)try{var e=r.current.getTotalLength();e&&o(e)}catch(e){}},[]);var{x:a,y:u,width:s,height:c,radius:f,className:d}=t,{animationEasing:p,animationDuration:h,animationBegin:g,isAnimationActive:v,isUpdateAnimationActive:m}=t;if(a!==+a||u!==+u||s!==+s||c!==+c||0===s||0===c)return null;var y=(0,l.$)("recharts-rectangle",d);return m?n.createElement(R.i,{canBegin:i>0,from:{width:s,height:c,x:a,y:u},to:{width:s,height:c,x:a,y:u},duration:h,animationEasing:p,isActive:m},e=>{var{width:o,height:a,x:l,y:u}=e;return n.createElement(R.i,{canBegin:i>0,from:"0px ".concat(-1===i?1:i,"px"),to:"".concat(i,"px 0px"),attributeName:"strokeDasharray",begin:g,duration:h,isActive:v,easing:p},n.createElement("path",z({},(0,k.J9)(t,!0),{className:y,d:D(l,u,o,a,f),ref:r})))}):n.createElement("path",z({},(0,k.J9)(t,!0),{className:y,d:D(a,u,s,c,f)}))},L=r(5641);function F(e){var{cx:t,cy:r,radius:n,startAngle:i,endAngle:o}=e;return{points:[(0,L.IZ)(t,r,n,i),(0,L.IZ)(t,r,n,o)],cx:t,cy:r,radius:n,startAngle:i,endAngle:o}}function $(){return($=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{cx:t,cy:r,radius:n,angle:i,sign:o,isExternal:a,cornerRadius:l,cornerIsExternal:u}=e,s=l*(a?1:-1)+n,c=Math.asin(l/s)/L.Kg,f=u?i:i+o*c,d=(0,L.IZ)(t,r,s,f);return{center:d,circleTangency:(0,L.IZ)(t,r,n,f),lineTangency:(0,L.IZ)(t,r,s*Math.cos(c*L.Kg),u?i-o*c:i),theta:c}},V=e=>{var{cx:t,cy:r,innerRadius:n,outerRadius:i,startAngle:o,endAngle:a}=e,l=((e,t)=>(0,u.sA)(t-e)*Math.min(Math.abs(t-e),359.999))(o,a),s=o+l,c=(0,L.IZ)(t,r,i,o),f=(0,L.IZ)(t,r,i,s),d="M ".concat(c.x,",").concat(c.y,"\n A ").concat(i,",").concat(i,",0,\n ").concat(+(Math.abs(l)>180),",").concat(+(o>s),",\n ").concat(f.x,",").concat(f.y,"\n ");if(n>0){var p=(0,L.IZ)(t,r,n,o),h=(0,L.IZ)(t,r,n,s);d+="L ".concat(h.x,",").concat(h.y,"\n A ").concat(n,",").concat(n,",0,\n ").concat(+(Math.abs(l)>180),",").concat(+(o<=s),",\n ").concat(p.x,",").concat(p.y," Z")}else d+="L ".concat(t,",").concat(r," Z");return d},U={cx:0,cy:0,innerRadius:0,outerRadius:0,startAngle:0,endAngle:0,cornerRadius:0,forceCornerRadius:!1,cornerIsExternal:!1},H=e=>{var t,r=(0,T.e)(e,U),{cx:i,cy:o,innerRadius:a,outerRadius:s,cornerRadius:c,forceCornerRadius:f,cornerIsExternal:d,startAngle:p,endAngle:h,className:g}=r;if(s0&&360>Math.abs(p-h)?(e=>{var{cx:t,cy:r,innerRadius:n,outerRadius:i,cornerRadius:o,forceCornerRadius:a,cornerIsExternal:l,startAngle:s,endAngle:c}=e,f=(0,u.sA)(c-s),{circleTangency:d,lineTangency:p,theta:h}=B({cx:t,cy:r,radius:i,angle:s,sign:f,cornerRadius:o,cornerIsExternal:l}),{circleTangency:g,lineTangency:v,theta:m}=B({cx:t,cy:r,radius:i,angle:c,sign:-f,cornerRadius:o,cornerIsExternal:l}),y=l?Math.abs(s-c):Math.abs(s-c)-h-m;if(y<0)return a?"M ".concat(p.x,",").concat(p.y,"\n a").concat(o,",").concat(o,",0,0,1,").concat(2*o,",0\n a").concat(o,",").concat(o,",0,0,1,").concat(-(2*o),",0\n "):V({cx:t,cy:r,innerRadius:n,outerRadius:i,startAngle:s,endAngle:c});var b="M ".concat(p.x,",").concat(p.y,"\n A").concat(o,",").concat(o,",0,0,").concat(+(f<0),",").concat(d.x,",").concat(d.y,"\n A").concat(i,",").concat(i,",0,").concat(+(y>180),",").concat(+(f<0),",").concat(g.x,",").concat(g.y,"\n A").concat(o,",").concat(o,",0,0,").concat(+(f<0),",").concat(v.x,",").concat(v.y,"\n ");if(n>0){var{circleTangency:w,lineTangency:x,theta:S}=B({cx:t,cy:r,radius:n,angle:s,sign:f,isExternal:!0,cornerRadius:o,cornerIsExternal:l}),{circleTangency:O,lineTangency:E,theta:C}=B({cx:t,cy:r,radius:n,angle:c,sign:-f,isExternal:!0,cornerRadius:o,cornerIsExternal:l}),M=l?Math.abs(s-c):Math.abs(s-c)-S-C;if(M<0&&0===o)return"".concat(b,"L").concat(t,",").concat(r,"Z");b+="L".concat(E.x,",").concat(E.y,"\n A").concat(o,",").concat(o,",0,0,").concat(+(f<0),",").concat(O.x,",").concat(O.y,"\n A").concat(n,",").concat(n,",0,").concat(+(M>180),",").concat(+(f>0),",").concat(w.x,",").concat(w.y,"\n A").concat(o,",").concat(o,",0,0,").concat(+(f<0),",").concat(x.x,",").concat(x.y,"Z")}else b+="L".concat(t,",").concat(r,"Z");return b})({cx:i,cy:o,innerRadius:a,outerRadius:s,cornerRadius:Math.min(y,m/2),forceCornerRadius:f,cornerIsExternal:d,startAngle:p,endAngle:h}):V({cx:i,cy:o,innerRadius:a,outerRadius:s,startAngle:p,endAngle:h}),n.createElement("path",$({},(0,k.J9)(r,!0),{className:v,d:t}))},G=r(1971),Z=r(9827),W=r(215);function K(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function q(e){for(var t=1;t{A((0,en.UF)({shared:x,trigger:M,axisId:P,active:o,defaultIndex:j}))},[A,x,M,P,o,j]);var R=(0,O.sk)(),z=(0,E.$)(),D=(0,eo.Td)(x),{activeIndex:I,isActive:N}=(0,G.G)(e=>(0,Y.yn)(e,D,M,j)),L=(0,G.G)(e=>(0,Y.u9)(e,D,M,j)),F=(0,G.G)(e=>(0,Y.BZ)(e,D,M,j)),$=(0,G.G)(e=>(0,Y.dS)(e,D,M,j)),B=(0,er.X)(),V=null!=o?o:N,[U,H]=(0,C.V)([L,V]),Z="axis"===D?F:void 0;(0,ei.m7)(D,M,$,Z,I,V);var W=null!=_?_:B;if(null==W)return null;var K=null!=L?L:es;V||(K=es),c&&K.length&&(K=(0,S.s)(L.filter(e=>null!=e.value&&(!0!==e.hide||r.includeHidden)),h,eu));var q=K.length>0,X=n.createElement(w,{allowEscapeViewBox:a,animationDuration:l,animationEasing:u,isAnimationActive:f,active:V,coordinate:$,hasPayload:q,offset:d,position:g,reverseDirection:v,useTranslate3d:m,viewBox:R,wrapperStyle:y,lastBoundingBox:U,innerRef:H,hasPortalFromProps:!!_},(t=el(el({},r),{},{payload:K,label:Z,active:V,coordinate:$,accessibilityLayer:z}),n.isValidElement(s)?n.cloneElement(s,t):"function"==typeof s?n.createElement(s,t):n.createElement(p,t)));return n.createElement(n.Fragment,null,(0,i.createPortal)(X,W),V&&n.createElement(et,{cursor:b,tooltipEventType:D,coordinate:$,payload:L,index:I}))}},8132:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(2744);t.isMatch=function(e,t){return n.isMatchWith(e,t,()=>void 0)}},8179:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(9452);t.isArrayLike=function(e){return null!=e&&"function"!=typeof e&&n.isLength(e.length)}},8190:(e,t,r)=>{"use strict";r.d(t,{I:()=>n});var n=(e,t)=>{if(e&&t)return null!=e&&e.reversed?[t[1],t[0]]:t}},8221:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.getSymbols=function(e){return Object.getOwnPropertySymbols(e).filter(t=>Object.prototype.propertyIsEnumerable.call(e,t))}},8234:(e,t,r)=>{"use strict";r.d(t,{EI:()=>f,oM:()=>c});var n=r(1971),i=r(215),o=r(8924),a=r(6124),l=(0,o.Mz)([a.HZ],e=>{if(e)return{top:e.top,bottom:e.bottom,left:e.left,right:e.right}}),u=r(2589),s=(0,o.Mz)([l,u.Lp,u.A$],(e,t,r)=>{if(e&&null!=t&&null!=r)return{x:e.left,y:e.top,width:Math.max(0,t-e.left-e.right),height:Math.max(0,r-e.top-e.bottom)}}),c=()=>(0,n.G)(s),f=()=>(0,n.G)(i.JG)},8247:(e,t,r)=>{"use strict";r.d(t,{Q:()=>tf});var n=r(2115),i=r(6641);r(1992);var o={notify(){},get:()=>[]},a="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,l="undefined"!=typeof navigator&&"ReactNative"===navigator.product,u=a||l?n.useLayoutEffect:n.useEffect;Object.getOwnPropertyNames,Object.getOwnPropertySymbols,Object.getOwnPropertyDescriptor,Object.getPrototypeOf,Object.prototype;var s=Symbol.for("react-redux-context"),c="undefined"!=typeof globalThis?globalThis:{},f=function(){if(!n.createContext)return{};let e=c[s]??=new Map,t=e.get(n.createContext);return t||(t=n.createContext(null),e.set(n.createContext,t)),t}(),d=function(e){let{children:t,context:r,serverState:i,store:a}=e,l=n.useMemo(()=>{let e=function(e,t){let r,n=o,i=0,a=!1;function l(){c.onStateChange&&c.onStateChange()}function u(){if(i++,!r){let t,i;r=e.subscribe(l),t=null,i=null,n={clear(){t=null,i=null},notify(){let e=t;for(;e;)e.callback(),e=e.next},get(){let e=[],r=t;for(;r;)e.push(r),r=r.next;return e},subscribe(e){let r=!0,n=i={callback:e,next:null,prev:i};return n.prev?n.prev.next=n:t=n,function(){r&&null!==t&&(r=!1,n.next?n.next.prev=n.prev:i=n.prev,n.prev?n.prev.next=n.next:t=n.next)}}}}}function s(){i--,r&&0===i&&(r(),r=void 0,n.clear(),n=o)}let c={addNestedSub:function(e){u();let t=n.subscribe(e),r=!1;return()=>{r||(r=!0,t(),s())}},notifyNestedSubs:function(){n.notify()},handleChangeWrapper:l,isSubscribed:function(){return a},trySubscribe:function(){a||(a=!0,u())},tryUnsubscribe:function(){a&&(a=!1,s())},getListeners:()=>n};return c}(a);return{store:a,subscription:e,getServerState:i?()=>i:void 0}},[a,i]),s=n.useMemo(()=>a.getState(),[a]);return u(()=>{let{subscription:e}=l;return e.onStateChange=e.notifyNestedSubs,e.trySubscribe(),s!==a.getState()&&e.notifyNestedSubs(),()=>{e.tryUnsubscribe(),e.onStateChange=void 0}},[l,s]),n.createElement((r||f).Provider,{value:l},t)},p=r(52),h=r(5710),g=r(4890),v=r(4487),m=(0,h.Z0)({name:"chartLayout",initialState:{layoutType:"horizontal",width:0,height:0,margin:{top:5,right:5,bottom:5,left:5},scale:1},reducers:{setLayout(e,t){e.layoutType=t.payload},setChartSize(e,t){e.width=t.payload.width,e.height=t.payload.height},setMargin(e,t){e.margin.top=t.payload.top,e.margin.right=t.payload.right,e.margin.bottom=t.payload.bottom,e.margin.left=t.payload.left},setScale(e,t){e.scale=t.payload}}}),{setMargin:y,setLayout:b,setChartSize:w,setScale:x}=m.actions,S=m.reducer,O=r(8924),E=r(7238),C=r(215),M=r(6124),k=r(4732),_=r(7062),P=(0,O.Mz)([(e,t)=>t,E.fz,_.D0,C.Re,C.gL,C.R4,k.r1,M.HZ],k.aX),A=r(6523),j=e=>{var t=e.currentTarget.getBoundingClientRect(),r=t.width/e.currentTarget.offsetWidth,n=t.height/e.currentTarget.offsetHeight;return{chartX:Math.round((e.clientX-t.left)/r),chartY:Math.round((e.clientY-t.top)/n)}},T=(0,h.VP)("mouseClick"),R=(0,h.Nc)();R.startListening({actionCreator:T,effect:(e,t)=>{var r=e.payload,n=P(t.getState(),j(r));(null==n?void 0:n.activeIndex)!=null&&t.dispatch((0,g.jF)({activeIndex:n.activeIndex,activeDataKey:void 0,activeCoordinate:n.activeCoordinate}))}});var z=(0,h.VP)("mouseMove"),D=(0,h.Nc)();function I(e,t){return t instanceof HTMLElement?"HTMLElement <".concat(t.tagName,' class="').concat(t.className,'">'):t===window?"global.window":t}D.startListening({actionCreator:z,effect:(e,t)=>{var r=e.payload,n=t.getState(),i=(0,A.au)(n,n.tooltip.settings.shared),o=P(n,j(r));"axis"===i&&((null==o?void 0:o.activeIndex)!=null?t.dispatch((0,g.Nt)({activeIndex:o.activeIndex,activeDataKey:void 0,activeCoordinate:o.activeCoordinate})):t.dispatch((0,g.xS)()))}});var N=r(4532);function L(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function F(e){for(var t=1;t{e.dots.push(t.payload)},removeDot:(e,t)=>{var r=(0,N.ss)(e).dots.findIndex(e=>e===t.payload);-1!==r&&e.dots.splice(r,1)},addArea:(e,t)=>{e.areas.push(t.payload)},removeArea:(e,t)=>{var r=(0,N.ss)(e).areas.findIndex(e=>e===t.payload);-1!==r&&e.areas.splice(r,1)},addLine:(e,t)=>{e.lines.push(t.payload)},removeLine:(e,t)=>{var r=(0,N.ss)(e).lines.findIndex(e=>e===t.payload);-1!==r&&e.lines.splice(r,1)}}}),{addDot:X,removeDot:J,addArea:Q,removeArea:ee,addLine:et,removeLine:er}=Y.actions,en=Y.reducer,ei={x:0,y:0,width:0,height:0,padding:{top:0,right:0,bottom:0,left:0}},eo=(0,h.Z0)({name:"brush",initialState:ei,reducers:{setBrushSettings:(e,t)=>null==t.payload?ei:t.payload}}),{setBrushSettings:ea}=eo.actions,el=eo.reducer,eu=r(2634),es={accessibilityLayer:!0,barCategoryGap:"10%",barGap:4,barSize:void 0,className:void 0,maxBarSize:void 0,stackOffset:"none",syncId:void 0,syncMethod:"index"},ec=(0,h.Z0)({name:"rootProps",initialState:es,reducers:{updateOptions:(e,t)=>{var r;e.accessibilityLayer=t.payload.accessibilityLayer,e.barCategoryGap=t.payload.barCategoryGap,e.barGap=null!=(r=t.payload.barGap)?r:es.barGap,e.barSize=t.payload.barSize,e.maxBarSize=t.payload.maxBarSize,e.stackOffset=t.payload.stackOffset,e.syncId=t.payload.syncId,e.syncMethod=t.payload.syncMethod,e.className=t.payload.className}}}),ef=ec.reducer,{updateOptions:ed}=ec.actions,ep=(0,h.Z0)({name:"polarAxis",initialState:{radiusAxis:{},angleAxis:{}},reducers:{addRadiusAxis(e,t){e.radiusAxis[t.payload.id]=(0,N.h4)(t.payload)},removeRadiusAxis(e,t){delete e.radiusAxis[t.payload.id]},addAngleAxis(e,t){e.angleAxis[t.payload.id]=(0,N.h4)(t.payload)},removeAngleAxis(e,t){delete e.angleAxis[t.payload.id]}}}),{addRadiusAxis:eh,removeRadiusAxis:eg,addAngleAxis:ev,removeAngleAxis:em}=ep.actions,ey=ep.reducer,eb=(0,h.Z0)({name:"polarOptions",initialState:null,reducers:{updatePolarOptions:(e,t)=>t.payload}}),{updatePolarOptions:ew}=eb.actions,ex=eb.reducer,eS=r(2183),eO=r(841),eE=(0,h.VP)("keyDown"),eC=(0,h.VP)("focus"),eM=(0,h.Nc)();eM.startListening({actionCreator:eE,effect:(e,t)=>{var r=t.getState();if(!1!==r.rootProps.accessibilityLayer){var{keyboardInteraction:n}=r.tooltip,i=e.payload;if("ArrowRight"===i||"ArrowLeft"===i||"Enter"===i){var o=Number((0,eO.P)(n,(0,C.n4)(r))),a=(0,C.R4)(r);if("Enter"===i){var l=(0,k.pg)(r,"axis","hover",String(n.index));t.dispatch((0,g.o4)({active:!n.active,activeIndex:n.index,activeDataKey:n.dataKey,activeCoordinate:l}));return}var u=o+("ArrowRight"===i?1:-1)*("left-to-right"===(0,eS._y)(r)?1:-1);if(null!=a&&!(u>=a.length)&&!(u<0)){var s=(0,k.pg)(r,"axis","hover",String(u));t.dispatch((0,g.o4)({active:!0,activeIndex:u.toString(),activeDataKey:void 0,activeCoordinate:s}))}}}}}),eM.startListening({actionCreator:eC,effect:(e,t)=>{var r=t.getState();if(!1!==r.rootProps.accessibilityLayer){var{keyboardInteraction:n}=r.tooltip;if(!n.active&&null==n.index){var i=(0,k.pg)(r,"axis","hover",String("0"));t.dispatch((0,g.o4)({activeDataKey:void 0,active:!0,activeIndex:"0",activeCoordinate:i}))}}}});var ek=(0,h.VP)("externalEvent"),e_=(0,h.Nc)();e_.startListening({actionCreator:ek,effect:(e,t)=>{if(null!=e.payload.handler){var r=t.getState(),n={activeCoordinate:(0,C.eE)(r),activeDataKey:(0,C.Xb)(r),activeIndex:(0,C.A2)(r),activeLabel:(0,C.BZ)(r),activeTooltipIndex:(0,C.A2)(r),isTooltipActive:(0,C.yn)(r)};e.payload.handler(n,e.payload.reactEvent)}}});var eP=r(4421),eA=r(6670),ej=r(5714),eT=(0,O.Mz)([ej.J],e=>e.tooltipItemPayloads),eR=(0,O.Mz)([eT,eA.x,(e,t,r)=>t,(e,t,r)=>r],(e,t,r,n)=>{var i=e.find(e=>e.settings.dataKey===n);if(null!=i){var{positions:o}=i;if(null!=o)return t(o,r)}}),ez=(0,h.VP)("touchMove"),eD=(0,h.Nc)();eD.startListening({actionCreator:ez,effect:(e,t)=>{var r=e.payload,n=t.getState(),i=(0,A.au)(n,n.tooltip.settings.shared);if("axis"===i){var o=P(n,j({clientX:r.touches[0].clientX,clientY:r.touches[0].clientY,currentTarget:r.currentTarget}));(null==o?void 0:o.activeIndex)!=null&&t.dispatch((0,g.Nt)({activeIndex:o.activeIndex,activeDataKey:void 0,activeCoordinate:o.activeCoordinate}))}else if("item"===i){var a,l=r.touches[0],u=document.elementFromPoint(l.clientX,l.clientY);if(!u||!u.getAttribute)return;var s=u.getAttribute(eP.F0),c=null!=(a=u.getAttribute(eP.um))?a:void 0,f=eR(t.getState(),s,c);t.dispatch((0,g.RD)({activeDataKey:c,activeIndex:s,activeCoordinate:f}))}}});var eI=(0,p.HY)({brush:el,cartesianAxis:K,chartData:v.LV,graphicalItems:q.iZ,layout:S,legend:eu.CU,options:i.lJ,polarAxis:ey,polarOptions:ex,referenceElements:en,rootProps:ef,tooltip:g.En}),eN=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"Chart";return(0,h.U1)({reducer:eI,preloadedState:e,middleware:e=>e({serializableCheck:!1}).concat([R.middleware,D.middleware,eM.middleware,e_.middleware,eD.middleware]),devTools:{serialize:{replacer:I},name:"recharts-".concat(t)}})},eL=r(1807),eF=r(5064);function e$(e){var{preloadedState:t,children:r,reduxStoreName:i}=e,o=(0,eL.r)(),a=(0,n.useRef)(null);if(o)return r;null==a.current&&(a.current=eN(t,i));var l=eF.E;return n.createElement(d,{context:l,store:a.current},r)}var eB=r(1971),eV=e=>{var{chartData:t}=e,r=(0,eB.j)(),i=(0,eL.r)();return(0,n.useEffect)(()=>i?()=>{}:(r((0,v.hq)(t)),()=>{r((0,v.hq)(void 0))}),[t,r,i]),null};function eU(e){var{layout:t,width:r,height:i,margin:o}=e,a=(0,eB.j)(),l=(0,eL.r)();return(0,n.useEffect)(()=>{l||(a(b(t)),a(w({width:r,height:i})),a(y(o)))},[a,l,t,r,i,o]),null}function eH(e){var t=(0,eB.j)();return(0,n.useEffect)(()=>{t(ed(e))},[t,e]),null}var eG=r(788),eZ=r(6752),eW=r(2790),eK=r(972),eq=r(8892),eY=["children"];function eX(){return(eX=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var r,i,o=(0,E.yi)(),a=(0,E.rY)(),l=(0,eZ.$)();if(!(0,eq.F)(o)||!(0,eq.F)(a))return null;var{children:u,otherAttributes:s,title:c,desc:f}=e;return r="number"==typeof s.tabIndex?s.tabIndex:l?0:void 0,i="string"==typeof s.role?s.role:l?"application":void 0,n.createElement(eW.u,eX({},s,{title:c,desc:f,role:i,tabIndex:r,width:o,height:a,style:eJ,ref:t}),u)}),e0=e=>{var{children:t}=e,r=(0,eB.G)(eK.U);if(!r)return null;var{width:i,height:o,y:a,x:l}=r;return n.createElement(eW.u,{width:i,height:o,x:l,y:a},t)},e1=(0,n.forwardRef)((e,t)=>{var{children:r}=e,i=function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{var{children:r,className:i,height:o,onClick:a,onContextMenu:l,onDoubleClick:u,onMouseDown:s,onMouseEnter:c,onMouseLeave:f,onMouseMove:d,onMouseUp:p,onTouchEnd:h,onTouchMove:v,onTouchStart:m,style:y,width:b}=e,w=(0,eB.j)(),[S,O]=(0,n.useState)(null),[E,C]=(0,n.useState)(null);(0,e5.l3)();var M=function(){var e=(0,eB.j)(),[t,r]=(0,n.useState)(null),i=(0,eB.G)(e6.et);return(0,n.useEffect)(()=>{if(null!=t){var r=t.getBoundingClientRect().width/t.offsetWidth;(0,eq.H)(r)&&r!==i&&e(x(r))}},[t,e,i]),r}(),k=(0,n.useCallback)(e=>{M(e),"function"==typeof t&&t(e),O(e),C(e)},[M,t,O,C]),_=(0,n.useCallback)(e=>{w(T(e)),w(ek({handler:a,reactEvent:e}))},[w,a]),P=(0,n.useCallback)(e=>{w(z(e)),w(ek({handler:c,reactEvent:e}))},[w,c]),A=(0,n.useCallback)(e=>{w((0,g.xS)()),w(ek({handler:f,reactEvent:e}))},[w,f]),j=(0,n.useCallback)(e=>{w(z(e)),w(ek({handler:d,reactEvent:e}))},[w,d]),R=(0,n.useCallback)(()=>{w(eC())},[w]),D=(0,n.useCallback)(e=>{w(eE(e.key))},[w]),I=(0,n.useCallback)(e=>{w(ek({handler:l,reactEvent:e}))},[w,l]),N=(0,n.useCallback)(e=>{w(ek({handler:u,reactEvent:e}))},[w,u]),L=(0,n.useCallback)(e=>{w(ek({handler:s,reactEvent:e}))},[w,s]),F=(0,n.useCallback)(e=>{w(ek({handler:p,reactEvent:e}))},[w,p]),$=(0,n.useCallback)(e=>{w(ek({handler:m,reactEvent:e}))},[w,m]),B=(0,n.useCallback)(e=>{w(ez(e)),w(ek({handler:v,reactEvent:e}))},[w,v]),V=(0,n.useCallback)(e=>{w(ek({handler:h,reactEvent:e}))},[w,h]);return n.createElement(e4.$.Provider,{value:S},n.createElement(e3.t.Provider,{value:E},n.createElement("div",{className:(0,e2.$)("recharts-wrapper",i),style:function(e){for(var t=1;t{var{children:t}=e,[r]=(0,n.useState)("".concat((0,e7.NF)("recharts"),"-clip")),i=(0,te.oM)();if(null==i)return null;var{x:o,y:a,width:l,height:u}=i;return n.createElement(tt.Provider,{value:r},n.createElement("defs",null,n.createElement("clipPath",{id:r},n.createElement("rect",{x:o,y:a,height:u,width:l}))),t)},tn=["children","className","width","height","style","compact","title","desc"],ti=(0,n.forwardRef)((e,t)=>{var{children:r,className:i,width:o,height:a,style:l,compact:u,title:s,desc:c}=e,f=function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;nn.createElement(ts,{chartName:"AreaChart",defaultTooltipEventType:"axis",validateTooltipEventTypes:tc,tooltipPayloadSearcher:i.uN,categoricalChartProps:e,ref:t}))},8266:(e,t,r)=>{"use strict";r.d(t,{$$:()=>g,Es:()=>p,KG:()=>m,Ks:()=>k,Ll:()=>l,Re:()=>E,Sw:()=>o,TW:()=>d,WQ:()=>O,YG:()=>x,YN:()=>v,ZC:()=>b,_q:()=>h,ag:()=>P,e_:()=>M,jn:()=>i,kx:()=>C,l6:()=>a,lk:()=>y,sb:()=>c,wz:()=>s,xZ:()=>f,zk:()=>u});var n=r(2115);function i(){for(var e=arguments.length,t=Array(e),r=0;re=>{t.forEach(t=>t(e))},t)}let o="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement;function a(e){let t=Object.prototype.toString.call(e);return"[object Window]"===t||"[object global]"===t}function l(e){return"nodeType"in e}function u(e){var t,r;return e?a(e)?e:l(e)&&null!=(t=null==(r=e.ownerDocument)?void 0:r.defaultView)?t:window:window}function s(e){let{Document:t}=u(e);return e instanceof t}function c(e){return!a(e)&&e instanceof u(e).HTMLElement}function f(e){return e instanceof u(e).SVGElement}function d(e){return e?a(e)?e.document:l(e)?s(e)?e:c(e)||f(e)?e.ownerDocument:document:document:document}let p=o?n.useLayoutEffect:n.useEffect;function h(e){let t=(0,n.useRef)(e);return p(()=>{t.current=e}),(0,n.useCallback)(function(){for(var e=arguments.length,r=Array(e),n=0;n{e.current=setInterval(t,r)},[]),(0,n.useCallback)(()=>{null!==e.current&&(clearInterval(e.current),e.current=null)},[])]}function v(e,t){void 0===t&&(t=[e]);let r=(0,n.useRef)(e);return p(()=>{r.current!==e&&(r.current=e)},t),r}function m(e,t){let r=(0,n.useRef)();return(0,n.useMemo)(()=>{let t=e(r.current);return r.current=t,t},[...t])}function y(e){let t=h(e),r=(0,n.useRef)(null),i=(0,n.useCallback)(e=>{e!==r.current&&(null==t||t(e,r.current)),r.current=e},[]);return[r,i]}function b(e){let t=(0,n.useRef)();return(0,n.useEffect)(()=>{t.current=e},[e]),t.current}let w={};function x(e,t){return(0,n.useMemo)(()=>{if(t)return t;let r=null==w[e]?0:w[e]+1;return w[e]=r,e+"-"+r},[e,t])}function S(e){return function(t){for(var r=arguments.length,n=Array(r>1?r-1:0),i=1;i{for(let[n,i]of Object.entries(r)){let r=t[n];null!=r&&(t[n]=r+e*i)}return t},{...t})}}let O=S(1),E=S(-1);function C(e){if(!e)return!1;let{KeyboardEvent:t}=u(e.target);return t&&e instanceof t}function M(e){if(function(e){if(!e)return!1;let{TouchEvent:t}=u(e.target);return t&&e instanceof t}(e)){if(e.touches&&e.touches.length){let{clientX:t,clientY:r}=e.touches[0];return{x:t,y:r}}else if(e.changedTouches&&e.changedTouches.length){let{clientX:t,clientY:r}=e.changedTouches[0];return{x:t,y:r}}}return"clientX"in e&&"clientY"in e?{x:e.clientX,y:e.clientY}:null}let k=Object.freeze({Translate:{toString(e){if(!e)return;let{x:t,y:r}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(r?Math.round(r):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;let{scaleX:t,scaleY:r}=e;return"scaleX("+t+") scaleY("+r+")"}},Transform:{toString(e){if(e)return[k.Translate.toString(e),k.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:r,easing:n}=e;return t+" "+r+"ms "+n}}}),_="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function P(e){return e.matches(_)?e:e.querySelector(_)}},8359:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(220),i=r(4986),o=r(8179);t.last=function(e){if(o.isArrayLike(e))return n.last(i.toArray(e))}},8412:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let r=/^(?:0|[1-9]\d*)$/;t.isIndex=function(e,t=Number.MAX_SAFE_INTEGER){switch(typeof e){case"number":return Number.isInteger(e)&&e>=0&&e{"use strict";r.d(t,{eC:()=>i,gY:()=>n,hX:()=>l,iO:()=>o,lZ:()=>a,pH:()=>u});var n=e=>e.rootProps.barCategoryGap,i=e=>e.rootProps.stackOffset,o=e=>e.options.chartName,a=e=>e.rootProps.syncId,l=e=>e.rootProps.syncMethod,u=e=>e.options.eventEmitter},8673:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.debounce=function(e,t=0,r={}){let n;"object"!=typeof r&&(r={});let i=null,o=null,a=null,l=0,u=null,{leading:s=!1,trailing:c=!0,maxWait:f}=r,d="maxWait"in r,p=d?Math.max(Number(f)||0,t):0,h=t=>(null!==i&&(n=e.apply(o,i)),i=o=null,l=t,n),g=e=>(u=null,c&&null!==i)?h(e):n,v=e=>{if(null===a)return!0;let r=e-a,n=d&&e-l>=p;return r>=t||r<0||n},m=()=>{let e=Date.now();if(v(e))return g(e);u=setTimeout(m,(e=>{let r=t-(null===a?0:e-a),n=p-(e-l);return d?Math.min(r,n):r})(e))},y=function(...e){let r=Date.now(),c=v(r);if(i=e,o=this,a=r,c){if(null===u)return(l=r,u=setTimeout(m,t),s&&null!==i)?h(r):n;if(d)return clearTimeout(u),u=setTimeout(m,t),h(r)}return null===u&&(u=setTimeout(m,t)),n};return y.cancel=()=>{null!==u&&clearTimeout(u),l=0,a=i=o=u=null},y.flush=()=>null===u?n:g(Date.now()),y}},8870:function(e,t,r){var n;!function(i){"use strict";var o,a={precision:20,rounding:4,toExpNeg:-7,toExpPos:21,LN10:"2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286"},l=!0,u="[DecimalError] ",s=u+"Invalid argument: ",c=u+"Exponent out of range: ",f=Math.floor,d=Math.pow,p=/^(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,h=f(1286742750677284.5),g={};function v(e,t){var r,n,i,o,a,u,s,c,f=e.constructor,d=f.precision;if(!e.s||!t.s)return t.s||(t=new f(e)),l?M(t,d):t;if(s=e.d,c=t.d,a=e.e,i=t.e,s=s.slice(),o=a-i){for(o<0?(n=s,o=-o,u=c.length):(n=c,i=a,u=s.length),o>(u=(a=Math.ceil(d/7))>u?a+1:u+1)&&(o=u,n.length=1),n.reverse();o--;)n.push(0);n.reverse()}for((u=s.length)-(o=c.length)<0&&(o=u,n=c,c=s,s=n),r=0;o;)r=(s[--o]=s[o]+c[o]+r)/1e7|0,s[o]%=1e7;for(r&&(s.unshift(r),++i),u=s.length;0==s[--u];)s.pop();return t.d=s,t.e=i,l?M(t,d):t}function m(e,t,r){if(e!==~~e||er)throw Error(s+e)}function y(e){var t,r,n,i=e.length-1,o="",a=e[0];if(i>0){for(o+=a,t=1;te.e^this.s<0?1:-1;for(t=0,r=(n=this.d.length)<(i=e.d.length)?n:i;te.d[t]^this.s<0?1:-1;return n===i?0:n>i^this.s<0?1:-1},g.decimalPlaces=g.dp=function(){var e=this.d.length-1,t=(e-this.e)*7;if(e=this.d[e])for(;e%10==0;e/=10)t--;return t<0?0:t},g.dividedBy=g.div=function(e){return b(this,new this.constructor(e))},g.dividedToIntegerBy=g.idiv=function(e){var t=this.constructor;return M(b(this,new t(e),0,1),t.precision)},g.equals=g.eq=function(e){return!this.cmp(e)},g.exponent=function(){return x(this)},g.greaterThan=g.gt=function(e){return this.cmp(e)>0},g.greaterThanOrEqualTo=g.gte=function(e){return this.cmp(e)>=0},g.isInteger=g.isint=function(){return this.e>this.d.length-2},g.isNegative=g.isneg=function(){return this.s<0},g.isPositive=g.ispos=function(){return this.s>0},g.isZero=function(){return 0===this.s},g.lessThan=g.lt=function(e){return 0>this.cmp(e)},g.lessThanOrEqualTo=g.lte=function(e){return 1>this.cmp(e)},g.logarithm=g.log=function(e){var t,r=this.constructor,n=r.precision,i=n+5;if(void 0===e)e=new r(10);else if((e=new r(e)).s<1||e.eq(o))throw Error(u+"NaN");if(this.s<1)throw Error(u+(this.s?"NaN":"-Infinity"));return this.eq(o)?new r(0):(l=!1,t=b(E(this,i),E(e,i),i),l=!0,M(t,n))},g.minus=g.sub=function(e){return e=new this.constructor(e),this.s==e.s?k(this,e):v(this,(e.s=-e.s,e))},g.modulo=g.mod=function(e){var t,r=this.constructor,n=r.precision;if(!(e=new r(e)).s)throw Error(u+"NaN");return this.s?(l=!1,t=b(this,e,0,1).times(e),l=!0,this.minus(t)):M(new r(this),n)},g.naturalExponential=g.exp=function(){return w(this)},g.naturalLogarithm=g.ln=function(){return E(this)},g.negated=g.neg=function(){var e=new this.constructor(this);return e.s=-e.s||0,e},g.plus=g.add=function(e){return e=new this.constructor(e),this.s==e.s?v(this,e):k(this,(e.s=-e.s,e))},g.precision=g.sd=function(e){var t,r,n;if(void 0!==e&&!!e!==e&&1!==e&&0!==e)throw Error(s+e);if(t=x(this)+1,r=7*(n=this.d.length-1)+1,n=this.d[n]){for(;n%10==0;n/=10)r--;for(n=this.d[0];n>=10;n/=10)r++}return e&&t>r?t:r},g.squareRoot=g.sqrt=function(){var e,t,r,n,i,o,a,s=this.constructor;if(this.s<1){if(!this.s)return new s(0);throw Error(u+"NaN")}for(e=x(this),l=!1,0==(i=Math.sqrt(+this))||i==1/0?(((t=y(this.d)).length+e)%2==0&&(t+="0"),i=Math.sqrt(t),e=f((e+1)/2)-(e<0||e%2),n=new s(t=i==1/0?"5e"+e:(t=i.toExponential()).slice(0,t.indexOf("e")+1)+e)):n=new s(i.toString()),i=a=(r=s.precision)+3;;)if(n=(o=n).plus(b(this,o,a+2)).times(.5),y(o.d).slice(0,a)===(t=y(n.d)).slice(0,a)){if(t=t.slice(a-3,a+1),i==a&&"4999"==t){if(M(o,r+1,0),o.times(o).eq(this)){n=o;break}}else if("9999"!=t)break;a+=4}return l=!0,M(n,r)},g.times=g.mul=function(e){var t,r,n,i,o,a,u,s,c,f=this.constructor,d=this.d,p=(e=new f(e)).d;if(!this.s||!e.s)return new f(0);for(e.s*=this.s,r=this.e+e.e,(s=d.length)<(c=p.length)&&(o=d,d=p,p=o,a=s,s=c,c=a),o=[],n=a=s+c;n--;)o.push(0);for(n=c;--n>=0;){for(t=0,i=s+n;i>n;)u=o[i]+p[n]*d[i-n-1]+t,o[i--]=u%1e7|0,t=u/1e7|0;o[i]=(o[i]+t)%1e7|0}for(;!o[--a];)o.pop();return t?++r:o.shift(),e.d=o,e.e=r,l?M(e,f.precision):e},g.toDecimalPlaces=g.todp=function(e,t){var r=this,n=r.constructor;return(r=new n(r),void 0===e)?r:(m(e,0,1e9),void 0===t?t=n.rounding:m(t,0,8),M(r,e+x(r)+1,t))},g.toExponential=function(e,t){var r,n=this,i=n.constructor;return void 0===e?r=_(n,!0):(m(e,0,1e9),void 0===t?t=i.rounding:m(t,0,8),r=_(n=M(new i(n),e+1,t),!0,e+1)),r},g.toFixed=function(e,t){var r,n,i=this.constructor;return void 0===e?_(this):(m(e,0,1e9),void 0===t?t=i.rounding:m(t,0,8),r=_((n=M(new i(this),e+x(this)+1,t)).abs(),!1,e+x(n)+1),this.isneg()&&!this.isZero()?"-"+r:r)},g.toInteger=g.toint=function(){var e=this.constructor;return M(new e(this),x(this)+1,e.rounding)},g.toNumber=function(){return+this},g.toPower=g.pow=function(e){var t,r,n,i,a,s,c=this,d=c.constructor,p=+(e=new d(e));if(!e.s)return new d(o);if(!(c=new d(c)).s){if(e.s<1)throw Error(u+"Infinity");return c}if(c.eq(o))return c;if(n=d.precision,e.eq(o))return M(c,n);if(s=(t=e.e)>=(r=e.d.length-1),a=c.s,s){if((r=p<0?-p:p)<=0x1fffffffffffff){for(i=new d(o),t=Math.ceil(n/7+4),l=!1;r%2&&P((i=i.times(c)).d,t),0!==(r=f(r/2));)P((c=c.times(c)).d,t);return l=!0,e.s<0?new d(o).div(i):M(i,n)}}else if(a<0)throw Error(u+"NaN");return a=a<0&&1&e.d[Math.max(t,r)]?-1:1,c.s=1,l=!1,i=e.times(E(c,n+12)),l=!0,(i=w(i)).s=a,i},g.toPrecision=function(e,t){var r,n,i=this,o=i.constructor;return void 0===e?(r=x(i),n=_(i,r<=o.toExpNeg||r>=o.toExpPos)):(m(e,1,1e9),void 0===t?t=o.rounding:m(t,0,8),r=x(i=M(new o(i),e,t)),n=_(i,e<=r||r<=o.toExpNeg,e)),n},g.toSignificantDigits=g.tosd=function(e,t){var r=this.constructor;return void 0===e?(e=r.precision,t=r.rounding):(m(e,1,1e9),void 0===t?t=r.rounding:m(t,0,8)),M(new r(this),e,t)},g.toString=g.valueOf=g.val=g.toJSON=function(){var e=x(this),t=this.constructor;return _(this,e<=t.toExpNeg||e>=t.toExpPos)};var b=function(){function e(e,t){var r,n=0,i=e.length;for(e=e.slice();i--;)r=e[i]*t+n,e[i]=r%1e7|0,n=r/1e7|0;return n&&e.unshift(n),e}function t(e,t,r,n){var i,o;if(r!=n)o=r>n?1:-1;else for(i=o=0;it[i]?1:-1;break}return o}function r(e,t,r){for(var n=0;r--;)e[r]-=n,n=+(e[r]1;)e.shift()}return function(n,i,o,a){var l,s,c,f,d,p,h,g,v,m,y,b,w,S,O,E,C,k,_=n.constructor,P=n.s==i.s?1:-1,A=n.d,j=i.d;if(!n.s)return new _(n);if(!i.s)throw Error(u+"Division by zero");for(c=0,s=n.e-i.e,C=j.length,O=A.length,g=(h=new _(P)).d=[];j[c]==(A[c]||0);)++c;if(j[c]>(A[c]||0)&&--s,(b=null==o?o=_.precision:a?o+(x(n)-x(i))+1:o)<0)return new _(0);if(b=b/7+2|0,c=0,1==C)for(f=0,j=j[0],b++;(c1&&(j=e(j,f),A=e(A,f),C=j.length,O=A.length),S=C,m=(v=A.slice(0,C)).length;m=1e7/2&&++E;do f=0,(l=t(j,v,C,m))<0?(y=v[0],C!=m&&(y=1e7*y+(v[1]||0)),(f=y/E|0)>1?(f>=1e7&&(f=1e7-1),p=(d=e(j,f)).length,m=v.length,1==(l=t(d,v,p,m))&&(f--,r(d,C16)throw Error(c+x(e));if(!e.s)return new p(o);for(null==t?(l=!1,u=h):u=t,a=new p(.03125);e.abs().gte(.1);)e=e.times(a),f+=5;for(u+=Math.log(d(2,f))/Math.LN10*2+5|0,r=n=i=new p(o),p.precision=u;;){if(n=M(n.times(e),u),r=r.times(++s),y((a=i.plus(b(n,r,u))).d).slice(0,u)===y(i.d).slice(0,u)){for(;f--;)i=M(i.times(i),u);return p.precision=h,null==t?(l=!0,M(i,h)):i}i=a}}function x(e){for(var t=7*e.e,r=e.d[0];r>=10;r/=10)t++;return t}function S(e,t,r){if(t>e.LN10.sd())throw l=!0,r&&(e.precision=r),Error(u+"LN10 precision limit exceeded");return M(new e(e.LN10),t)}function O(e){for(var t="";e--;)t+="0";return t}function E(e,t){var r,n,i,a,s,c,f,d,p,h=1,g=e,v=g.d,m=g.constructor,w=m.precision;if(g.s<1)throw Error(u+(g.s?"NaN":"-Infinity"));if(g.eq(o))return new m(0);if(null==t?(l=!1,d=w):d=t,g.eq(10))return null==t&&(l=!0),S(m,d);if(m.precision=d+=10,n=(r=y(v)).charAt(0),!(15e14>Math.abs(a=x(g))))return f=S(m,d+2,w).times(a+""),g=E(new m(n+"."+r.slice(1)),d-10).plus(f),m.precision=w,null==t?(l=!0,M(g,w)):g;for(;n<7&&1!=n||1==n&&r.charAt(1)>3;)n=(r=y((g=g.times(e)).d)).charAt(0),h++;for(a=x(g),n>1?(g=new m("0."+r),a++):g=new m(n+"."+r.slice(1)),c=s=g=b(g.minus(o),g.plus(o),d),p=M(g.times(g),d),i=3;;){if(s=M(s.times(p),d),y((f=c.plus(b(s,new m(i),d))).d).slice(0,d)===y(c.d).slice(0,d))return c=c.times(2),0!==a&&(c=c.plus(S(m,d+2,w).times(a+""))),c=b(c,new m(h),d),m.precision=w,null==t?(l=!0,M(c,w)):c;c=f,i+=2}}function C(e,t){var r,n,i;for((r=t.indexOf("."))>-1&&(t=t.replace(".","")),(n=t.search(/e/i))>0?(r<0&&(r=n),r+=+t.slice(n+1),t=t.substring(0,n)):r<0&&(r=t.length),n=0;48===t.charCodeAt(n);)++n;for(i=t.length;48===t.charCodeAt(i-1);)--i;if(t=t.slice(n,i)){if(i-=n,e.e=f((r=r-n-1)/7),e.d=[],n=(r+1)%7,r<0&&(n+=7),nh||e.e<-h))throw Error(c+r)}else e.s=0,e.e=0,e.d=[0];return e}function M(e,t,r){var n,i,o,a,u,s,p,g,v=e.d;for(a=1,o=v[0];o>=10;o/=10)a++;if((n=t-a)<0)n+=7,i=t,p=v[g=0];else{if((g=Math.ceil((n+1)/7))>=(o=v.length))return e;for(a=1,p=o=v[g];o>=10;o/=10)a++;n%=7,i=n-7+a}if(void 0!==r&&(u=p/(o=d(10,a-i-1))%10|0,s=t<0||void 0!==v[g+1]||p%o,s=r<4?(u||s)&&(0==r||r==(e.s<0?3:2)):u>5||5==u&&(4==r||s||6==r&&(n>0?i>0?p/d(10,a-i):0:v[g-1])%10&1||r==(e.s<0?8:7))),t<1||!v[0])return s?(o=x(e),v.length=1,t=t-o-1,v[0]=d(10,(7-t%7)%7),e.e=f(-t/7)||0):(v.length=1,v[0]=e.e=e.s=0),e;if(0==n?(v.length=g,o=1,g--):(v.length=g+1,o=d(10,7-n),v[g]=i>0?(p/d(10,a-i)%d(10,i)|0)*o:0),s)for(;;)if(0==g){1e7==(v[0]+=o)&&(v[0]=1,++e.e);break}else{if(v[g]+=o,1e7!=v[g])break;v[g--]=0,o=1}for(n=v.length;0===v[--n];)v.pop();if(l&&(e.e>h||e.e<-h))throw Error(c+x(e));return e}function k(e,t){var r,n,i,o,a,u,s,c,f,d,p=e.constructor,h=p.precision;if(!e.s||!t.s)return t.s?t.s=-t.s:t=new p(e),l?M(t,h):t;if(s=e.d,d=t.d,n=t.e,c=e.e,s=s.slice(),a=c-n){for((f=a<0)?(r=s,a=-a,u=d.length):(r=d,n=c,u=s.length),a>(i=Math.max(Math.ceil(h/7),u)+2)&&(a=i,r.length=1),r.reverse(),i=a;i--;)r.push(0);r.reverse()}else{for((f=(i=s.length)<(u=d.length))&&(u=i),i=0;i0;--i)s[u++]=0;for(i=d.length;i>a;){if(s[--i]0?o=o.charAt(0)+"."+o.slice(1)+O(n):a>1&&(o=o.charAt(0)+"."+o.slice(1)),o=o+(i<0?"e":"e+")+i):i<0?(o="0."+O(-i-1)+o,r&&(n=r-a)>0&&(o+=O(n))):i>=a?(o+=O(i+1-a),r&&(n=r-i-1)>0&&(o=o+"."+O(n))):((n=i+1)0&&(i+1===a&&(o+="."),o+=O(n))),e.s<0?"-"+o:o}function P(e,t){if(e.length>t)return e.length=t,!0}function A(e){if(!e||"object"!=typeof e)throw Error(u+"Object expected");var t,r,n,i=["precision",1,1e9,"rounding",0,8,"toExpNeg",-1/0,0,"toExpPos",0,1/0];for(t=0;t=i[t+1]&&n<=i[t+2])this[r]=n;else throw Error(s+r+": "+n);if(void 0!==(n=e[r="LN10"]))if(n==Math.LN10)this[r]=new this(n);else throw Error(s+r+": "+n);return this}(a=function e(t){var r,n,i;function o(e){if(!(this instanceof o))return new o(e);if(this.constructor=o,e instanceof o){this.s=e.s,this.e=e.e,this.d=(e=e.d)?e.slice():e;return}if("number"==typeof e){if(0*e!=0)throw Error(s+e);if(e>0)this.s=1;else if(e<0)e=-e,this.s=-1;else{this.s=0,this.e=0,this.d=[0];return}if(e===~~e&&e<1e7){this.e=0,this.d=[e];return}return C(this,e.toString())}if("string"!=typeof e)throw Error(s+e);if(45===e.charCodeAt(0)?(e=e.slice(1),this.s=-1):this.s=1,p.test(e))C(this,e);else throw Error(s+e)}if(o.prototype=g,o.ROUND_UP=0,o.ROUND_DOWN=1,o.ROUND_CEIL=2,o.ROUND_FLOOR=3,o.ROUND_HALF_UP=4,o.ROUND_HALF_DOWN=5,o.ROUND_HALF_EVEN=6,o.ROUND_HALF_CEIL=7,o.ROUND_HALF_FLOOR=8,o.clone=e,o.config=o.set=A,void 0===t&&(t={}),t)for(r=0,i=["precision","rounding","toExpNeg","toExpPos","LN10"];r{"use strict";function n(e){return Number.isFinite(e)}function i(e){return"number"==typeof e&&e>0&&Number.isFinite(e)}r.d(t,{F:()=>i,H:()=>n})},8924:(e,t,r)=>{"use strict";r.d(t,{Mz:()=>w});var n=e=>Array.isArray(e)?e:[e],i=0,o=class{revision=i;_value;_lastValue;_isEqual=a;constructor(e,t=a){this._value=this._lastValue=e,this._isEqual=t}get value(){return this._value}set value(e){this.value!==e&&(this._value=e,this.revision=++i)}};function a(e,t){return e===t}function l(e){return e instanceof o||console.warn("Not a valid cell! ",e),e.value}var u=(e,t)=>!1;function s(){return function(e,t=a){return new o(null,t)}(0,u)}var c=e=>{let t=e.collectionTag;null===t&&(t=e.collectionTag=s()),l(t)};Symbol();var f=0,d=Object.getPrototypeOf({}),p=class{constructor(e){this.value=e,this.value=e,this.tag.value=e}proxy=new Proxy(this,h);tag=s();tags={};children={};collectionTag=null;id=f++},h={get:(e,t)=>(function(){let{value:r}=e,n=Reflect.get(r,t);if("symbol"==typeof t||t in d)return n;if("object"==typeof n&&null!==n){var i;let r=e.children[t];return void 0===r&&(r=e.children[t]=Array.isArray(i=n)?new g(i):new p(i)),r.tag&&l(r.tag),r.proxy}{let r=e.tags[t];return void 0===r&&((r=e.tags[t]=s()).value=n),l(r),n}})(),ownKeys:e=>(c(e),Reflect.ownKeys(e.value)),getOwnPropertyDescriptor:(e,t)=>Reflect.getOwnPropertyDescriptor(e.value,t),has:(e,t)=>Reflect.has(e.value,t)},g=class{constructor(e){this.value=e,this.value=e,this.tag.value=e}proxy=new Proxy([this],v);tag=s();tags={};children={};collectionTag=null;id=f++},v={get:([e],t)=>("length"===t&&c(e),h.get(e,t)),ownKeys:([e])=>h.ownKeys(e),getOwnPropertyDescriptor:([e],t)=>h.getOwnPropertyDescriptor(e,t),has:([e],t)=>h.has(e,t)},m="undefined"!=typeof WeakRef?WeakRef:class{constructor(e){this.value=e}deref(){return this.value}};function y(){return{s:0,v:void 0,o:null,p:null}}function b(e,t={}){let r,n=y(),{resultEqualityCheck:i}=t,o=0;function a(){let t,a=n,{length:l}=arguments;for(let e=0;e{n=y(),a.resetResultsCount()},a.resultsCount=()=>o,a.resetResultsCount=()=>{o=0},a}var w=function(e,...t){let r="function"==typeof e?{memoize:e,memoizeOptions:t}:e,i=(...e)=>{let t,i=0,o=0,a={},l=e.pop();"object"==typeof l&&(a=l,l=e.pop()),function(e,t=`expected a function, instead received ${typeof e}`){if("function"!=typeof e)throw TypeError(t)}(l,`createSelector expects an output function after the inputs, but received: [${typeof l}]`);let{memoize:u,memoizeOptions:s=[],argsMemoize:c=b,argsMemoizeOptions:f=[],devModeChecks:d={}}={...r,...a},p=n(s),h=n(f),g=function(e){let t=Array.isArray(e[0])?e[0]:e;return!function(e,t="expected all items to be functions, instead received the following types: "){if(!e.every(e=>"function"==typeof e)){let r=e.map(e=>"function"==typeof e?`function ${e.name||"unnamed"}()`:typeof e).join(", ");throw TypeError(`${t}[${r}]`)}}(t,"createSelector expects all input-selectors to be functions, but received the following types: "),t}(e),v=u(function(){return i++,l.apply(null,arguments)},...p);return Object.assign(c(function(){o++;let e=function(e,t){let r=[],{length:n}=e;for(let i=0;io,resetDependencyRecomputations:()=>{o=0},lastResult:()=>t,recomputations:()=>i,resetRecomputations:()=>{i=0},memoize:u,argsMemoize:c})};return Object.assign(i,{withTypes:()=>i}),i}(b),x=Object.assign((e,t=w)=>{!function(e,t=`expected an object, instead received ${typeof e}`){if("object"!=typeof e)throw TypeError(t)}(e,`createStructuredSelector expects first argument to be an object where each property is a selector, instead received a ${typeof e}`);let r=Object.keys(e);return t(r.map(t=>e[t]),(...e)=>e.reduce((e,t,n)=>(e[r[n]]=t,e),{}))},{withTypes:()=>x})},9033:(e,t,r)=>{"use strict";r.d(t,{c:()=>i});var n=r(2115);function i(e){let t=n.useRef(e);return n.useEffect(()=>{t.current=e}),n.useMemo(()=>(...e)=>t.current?.(...e),[])}},9074:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("calendar",[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}]])},9095:(e,t,r)=>{"use strict";r.d(t,{E:()=>k});var n=r(2115),i=r(2596),o=r(6377),a=r(1643),l=r(788),u=r(6605),s=/(-?\d+(?:\.\d+)?[a-zA-Z%]*)([*/])(-?\d+(?:\.\d+)?[a-zA-Z%]*)/,c=/(-?\d+(?:\.\d+)?[a-zA-Z%]*)([+-])(-?\d+(?:\.\d+)?[a-zA-Z%]*)/,f=/^px|cm|vh|vw|em|rem|%|mm|in|pt|pc|ex|ch|vmin|vmax|Q$/,d=/(-?\d+(?:\.\d+)?)([a-zA-Z%]+)?/,p={cm:96/2.54,mm:96/25.4,pt:96/72,pc:16,in:96,Q:96/101.6,px:1},h=Object.keys(p);class g{static parse(e){var t,[,r,n]=null!=(t=d.exec(e))?t:[];return new g(parseFloat(r),null!=n?n:"")}add(e){return this.unit!==e.unit?new g(NaN,""):new g(this.num+e.num,this.unit)}subtract(e){return this.unit!==e.unit?new g(NaN,""):new g(this.num-e.num,this.unit)}multiply(e){return""!==this.unit&&""!==e.unit&&this.unit!==e.unit?new g(NaN,""):new g(this.num*e.num,this.unit||e.unit)}divide(e){return""!==this.unit&&""!==e.unit&&this.unit!==e.unit?new g(NaN,""):new g(this.num/e.num,this.unit||e.unit)}toString(){return"".concat(this.num).concat(this.unit)}isNaN(){return(0,o.M8)(this.num)}constructor(e,t){this.num=e,this.unit=t,this.num=e,this.unit=t,(0,o.M8)(e)&&(this.unit=""),""===t||f.test(t)||(this.num=NaN,this.unit=""),h.includes(t)&&(this.num=e*p[t],this.unit="px")}}function v(e){if(e.includes("NaN"))return"NaN";for(var t=e;t.includes("*")||t.includes("/");){var r,[,n,i,o]=null!=(r=s.exec(t))?r:[],a=g.parse(null!=n?n:""),l=g.parse(null!=o?o:""),u="*"===i?a.multiply(l):a.divide(l);if(u.isNaN())return"NaN";t=t.replace(s,u.toString())}for(;t.includes("+")||/.-\d+(?:\.\d+)?/.test(t);){var f,[,d,p,h]=null!=(f=c.exec(t))?f:[],v=g.parse(null!=d?d:""),m=g.parse(null!=h?h:""),y="+"===p?v.add(m):v.subtract(m);if(y.isNaN())return"NaN";t=t.replace(c,y.toString())}return t}var m=/\(([^()]*)\)/;function y(e){var t=function(e){try{var t;return t=e.replace(/\s+/g,""),t=function(e){for(var t,r=e;null!=(t=m.exec(r));){var[,n]=t;r=r.replace(m,v(n))}return r}(t),t=v(t)}catch(e){return"NaN"}}(e.slice(5,-1));return"NaN"===t?"":t}var b=["x","y","lineHeight","capHeight","scaleToFit","textAnchor","verticalAnchor","fill"],w=["dx","dy","angle","className","breakAll"];function x(){return(x=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:t,breakAll:r,style:n}=e;try{var i=[];(0,o.uy)(t)||(i=r?t.toString().split(""):t.toString().split(O));var a=i.map(e=>({word:e,width:(0,u.P)(e,n).width})),l=r?0:(0,u.P)("\xa0",n).width;return{wordsWithComputedWidth:a,spaceWidth:l}}catch(e){return null}},C=e=>[{words:(0,o.uy)(e)?[]:e.toString().split(O)}],M="#808080",k=(0,n.forwardRef)((e,t)=>{var r,{x:u=0,y:s=0,lineHeight:c="1em",capHeight:f="0.71em",scaleToFit:d=!1,textAnchor:p="start",verticalAnchor:h="end",fill:g=M}=e,v=S(e,b),m=(0,n.useMemo)(()=>(e=>{var{width:t,scaleToFit:r,children:n,style:i,breakAll:l,maxLines:u}=e;if((t||r)&&!a.m.isSsr){var s=E({breakAll:l,children:n,style:i});if(!s)return C(n);var{wordsWithComputedWidth:c,spaceWidth:f}=s;return((e,t,r,n,i)=>{var a,{maxLines:l,children:u,style:s,breakAll:c}=e,f=(0,o.Et)(l),d=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.reduce((e,t)=>{var{word:o,width:a}=t,l=e[e.length-1];return l&&(null==n||i||l.width+a+re.reduce((e,t)=>e.width>t.width?e:t);if(!f||i||!(p.length>l||h(p).width>Number(n)))return p;for(var g=e=>{var t=d(E({breakAll:c,style:s,children:u.slice(0,e)+"…"}).wordsWithComputedWidth);return[t.length>l||h(t).width>Number(n),t]},v=0,m=u.length-1,y=0;v<=m&&y<=u.length-1;){var b=Math.floor((v+m)/2),[w,x]=g(b-1),[S]=g(b);if(w||S||(v=b+1),w&&S&&(m=b-1),!w&&S){a=x;break}y++}return a||p})({breakAll:l,children:n,maxLines:u,style:i},c,f,t,r)}return C(n)})({breakAll:v.breakAll,children:v.children,maxLines:v.maxLines,scaleToFit:d,style:v.style,width:v.width}),[v.breakAll,v.children,v.maxLines,d,v.style,v.width]),{dx:O,dy:k,angle:_,className:P,breakAll:A}=v,j=S(v,w);if(!(0,o.vh)(u)||!(0,o.vh)(s))return null;var T=u+((0,o.Et)(O)?O:0),R=s+((0,o.Et)(k)?k:0);switch(h){case"start":r=y("calc(".concat(f,")"));break;case"middle":r=y("calc(".concat((m.length-1)/2," * -").concat(c," + (").concat(f," / 2))"));break;default:r=y("calc(".concat(m.length-1," * -").concat(c,")"))}var z=[];if(d){var D=m[0].width,{width:I}=v;z.push("scale(".concat((0,o.Et)(I)?I/D:1,")"))}return _&&z.push("rotate(".concat(_,", ").concat(T,", ").concat(R,")")),z.length&&(j.transform=z.join(" ")),n.createElement("text",x({},(0,l.J9)(j,!0),{ref:t,x:T,y:R,className:(0,i.$)("recharts-text",P),textAnchor:p,fill:g.includes("url")?M:g}),m.map((e,t)=>{var i=e.words.join(A?"":" ");return n.createElement("tspan",{x:T,dy:0===t?r:c,key:"".concat(i,"-").concat(t)},i)}))});k.displayName="Text"},9196:(e,t,r)=>{"use strict";r.d(t,{RG:()=>x,bL:()=>A,q7:()=>j});var n=r(2115),i=r(5185),o=r(7328),a=r(6101),l=r(6081),u=r(1285),s=r(3655),c=r(9033),f=r(5845),d=r(4315),p=r(5155),h="rovingFocusGroup.onEntryFocus",g={bubbles:!1,cancelable:!0},v="RovingFocusGroup",[m,y,b]=(0,o.N)(v),[w,x]=(0,l.A)(v,[b]),[S,O]=w(v),E=n.forwardRef((e,t)=>(0,p.jsx)(m.Provider,{scope:e.__scopeRovingFocusGroup,children:(0,p.jsx)(m.Slot,{scope:e.__scopeRovingFocusGroup,children:(0,p.jsx)(C,{...e,ref:t})})}));E.displayName=v;var C=n.forwardRef((e,t)=>{let{__scopeRovingFocusGroup:r,orientation:o,loop:l=!1,dir:u,currentTabStopId:m,defaultCurrentTabStopId:b,onCurrentTabStopIdChange:w,onEntryFocus:x,preventScrollOnEntryFocus:O=!1,...E}=e,C=n.useRef(null),M=(0,a.s)(t,C),k=(0,d.jH)(u),[_,A]=(0,f.i)({prop:m,defaultProp:null!=b?b:null,onChange:w,caller:v}),[j,T]=n.useState(!1),R=(0,c.c)(x),z=y(r),D=n.useRef(!1),[I,N]=n.useState(0);return n.useEffect(()=>{let e=C.current;if(e)return e.addEventListener(h,R),()=>e.removeEventListener(h,R)},[R]),(0,p.jsx)(S,{scope:r,orientation:o,dir:k,loop:l,currentTabStopId:_,onItemFocus:n.useCallback(e=>A(e),[A]),onItemShiftTab:n.useCallback(()=>T(!0),[]),onFocusableItemAdd:n.useCallback(()=>N(e=>e+1),[]),onFocusableItemRemove:n.useCallback(()=>N(e=>e-1),[]),children:(0,p.jsx)(s.sG.div,{tabIndex:j||0===I?-1:0,"data-orientation":o,...E,ref:M,style:{outline:"none",...e.style},onMouseDown:(0,i.m)(e.onMouseDown,()=>{D.current=!0}),onFocus:(0,i.m)(e.onFocus,e=>{let t=!D.current;if(e.target===e.currentTarget&&t&&!j){let t=new CustomEvent(h,g);if(e.currentTarget.dispatchEvent(t),!t.defaultPrevented){let e=z().filter(e=>e.focusable);P([e.find(e=>e.active),e.find(e=>e.id===_),...e].filter(Boolean).map(e=>e.ref.current),O)}}D.current=!1}),onBlur:(0,i.m)(e.onBlur,()=>T(!1))})})}),M="RovingFocusGroupItem",k=n.forwardRef((e,t)=>{let{__scopeRovingFocusGroup:r,focusable:o=!0,active:a=!1,tabStopId:l,children:c,...f}=e,d=(0,u.B)(),h=l||d,g=O(M,r),v=g.currentTabStopId===h,b=y(r),{onFocusableItemAdd:w,onFocusableItemRemove:x,currentTabStopId:S}=g;return n.useEffect(()=>{if(o)return w(),()=>x()},[o,w,x]),(0,p.jsx)(m.ItemSlot,{scope:r,id:h,focusable:o,active:a,children:(0,p.jsx)(s.sG.span,{tabIndex:v?0:-1,"data-orientation":g.orientation,...f,ref:t,onMouseDown:(0,i.m)(e.onMouseDown,e=>{o?g.onItemFocus(h):e.preventDefault()}),onFocus:(0,i.m)(e.onFocus,()=>g.onItemFocus(h)),onKeyDown:(0,i.m)(e.onKeyDown,e=>{if("Tab"===e.key&&e.shiftKey)return void g.onItemShiftTab();if(e.target!==e.currentTarget)return;let t=function(e,t,r){var n;let i=(n=e.key,"rtl"!==r?n:"ArrowLeft"===n?"ArrowRight":"ArrowRight"===n?"ArrowLeft":n);if(!("vertical"===t&&["ArrowLeft","ArrowRight"].includes(i))&&!("horizontal"===t&&["ArrowUp","ArrowDown"].includes(i)))return _[i]}(e,g.orientation,g.dir);if(void 0!==t){if(e.metaKey||e.ctrlKey||e.altKey||e.shiftKey)return;e.preventDefault();let r=b().filter(e=>e.focusable).map(e=>e.ref.current);if("last"===t)r.reverse();else if("prev"===t||"next"===t){"prev"===t&&r.reverse();let n=r.indexOf(e.currentTarget);r=g.loop?function(e,t){return e.map((r,n)=>e[(t+n)%e.length])}(r,n+1):r.slice(n+1)}setTimeout(()=>P(r))}}),children:"function"==typeof c?c({isCurrentTabStop:v,hasTabStop:null!=S}):c})})});k.displayName=M;var _={ArrowLeft:"prev",ArrowUp:"prev",ArrowRight:"next",ArrowDown:"next",PageUp:"first",Home:"first",PageDown:"last",End:"last"};function P(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=document.activeElement;for(let n of e)if(n===r||(n.focus({preventScroll:t}),document.activeElement!==r))return}var A=E,j=k},9279:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isObjectLike=function(e){return"object"==typeof e&&null!==e}},9452:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isLength=function(e){return Number.isSafeInteger(e)&&e>=0}},9641:e=>{!function(){var t={675:function(e,t){"use strict";t.byteLength=function(e){var t=u(e),r=t[0],n=t[1];return(r+n)*3/4-n},t.toByteArray=function(e){var t,r,o=u(e),a=o[0],l=o[1],s=new i((a+l)*3/4-l),c=0,f=l>0?a-4:a;for(r=0;r>16&255,s[c++]=t>>8&255,s[c++]=255&t;return 2===l&&(t=n[e.charCodeAt(r)]<<2|n[e.charCodeAt(r+1)]>>4,s[c++]=255&t),1===l&&(t=n[e.charCodeAt(r)]<<10|n[e.charCodeAt(r+1)]<<4|n[e.charCodeAt(r+2)]>>2,s[c++]=t>>8&255,s[c++]=255&t),s},t.fromByteArray=function(e){for(var t,n=e.length,i=n%3,o=[],a=0,l=n-i;a>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return o.join("")}(e,a,a+16383>l?l:a+16383));return 1===i?o.push(r[(t=e[n-1])>>2]+r[t<<4&63]+"=="):2===i&&o.push(r[(t=(e[n-2]<<8)+e[n-1])>>10]+r[t>>4&63]+r[t<<2&63]+"="),o.join("")};for(var r=[],n=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,l=o.length;a0)throw Error("Invalid string. Length must be a multiple of 4");var r=e.indexOf("=");-1===r&&(r=t);var n=r===t?0:4-r%4;return[r,n]}n[45]=62,n[95]=63},72:function(e,t,r){"use strict";var n=r(675),i=r(783),o="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;function a(e){if(e>0x7fffffff)throw RangeError('The value "'+e+'" is invalid for option "size"');var t=new Uint8Array(e);return Object.setPrototypeOf(t,l.prototype),t}function l(e,t,r){if("number"==typeof e){if("string"==typeof t)throw TypeError('The "string" argument must be of type string. Received type number');return c(e)}return u(e,t,r)}function u(e,t,r){if("string"==typeof e){var n=e,i=t;if(("string"!=typeof i||""===i)&&(i="utf8"),!l.isEncoding(i))throw TypeError("Unknown encoding: "+i);var o=0|p(n,i),u=a(o),s=u.write(n,i);return s!==o&&(u=u.slice(0,s)),u}if(ArrayBuffer.isView(e))return f(e);if(null==e)throw TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(P(e,ArrayBuffer)||e&&P(e.buffer,ArrayBuffer)||"undefined"!=typeof SharedArrayBuffer&&(P(e,SharedArrayBuffer)||e&&P(e.buffer,SharedArrayBuffer)))return function(e,t,r){var n;if(t<0||e.byteLength=0x7fffffff)throw RangeError("Attempt to allocate Buffer larger than maximum size: 0x7fffffff bytes");return 0|e}function p(e,t){if(l.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||P(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);var r=e.length,n=arguments.length>2&&!0===arguments[2];if(!n&&0===r)return 0;for(var i=!1;;)switch(t){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":return C(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return k(e).length;default:if(i)return n?-1:C(e).length;t=(""+t).toLowerCase(),i=!0}}function h(e,t,r){var i,o,a,l=!1;if((void 0===t||t<0)&&(t=0),t>this.length||((void 0===r||r>this.length)&&(r=this.length),r<=0||(r>>>=0)<=(t>>>=0)))return"";for(e||(e="utf8");;)switch(e){case"hex":return function(e,t,r){var n=e.length;(!t||t<0)&&(t=0),(!r||r<0||r>n)&&(r=n);for(var i="",o=t;o0x7fffffff?r=0x7fffffff:r<-0x80000000&&(r=-0x80000000),(o=r*=1)!=o&&(r=i?0:e.length-1),r<0&&(r=e.length+r),r>=e.length)if(i)return -1;else r=e.length-1;else if(r<0)if(!i)return -1;else r=0;if("string"==typeof t&&(t=l.from(t,n)),l.isBuffer(t))return 0===t.length?-1:m(e,t,r,n,i);if("number"==typeof t){if(t&=255,"function"==typeof Uint8Array.prototype.indexOf)if(i)return Uint8Array.prototype.indexOf.call(e,t,r);else return Uint8Array.prototype.lastIndexOf.call(e,t,r);return m(e,[t],r,n,i)}throw TypeError("val must be string, number or Buffer")}function m(e,t,r,n,i){var o,a=1,l=e.length,u=t.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(e.length<2||t.length<2)return -1;a=2,l/=2,u/=2,r/=2}function s(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(i){var c=-1;for(o=r;ol&&(r=l-u),o=r;o>=0;o--){for(var f=!0,d=0;dr&&(e+=" ... "),""},o&&(l.prototype[o]=l.prototype.inspect),l.prototype.compare=function(e,t,r,n,i){if(P(e,Uint8Array)&&(e=l.from(e,e.offset,e.byteLength)),!l.isBuffer(e))throw TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===n&&(n=0),void 0===i&&(i=this.length),t<0||r>e.length||n<0||i>this.length)throw RangeError("out of range index");if(n>=i&&t>=r)return 0;if(n>=i)return -1;if(t>=r)return 1;if(t>>>=0,r>>>=0,n>>>=0,i>>>=0,this===e)return 0;for(var o=i-n,a=r-t,u=Math.min(o,a),s=this.slice(n,i),c=e.slice(t,r),f=0;f239?4:s>223?3:s>191?2:1;if(i+f<=r)switch(f){case 1:s<128&&(c=s);break;case 2:(192&(o=e[i+1]))==128&&(u=(31&s)<<6|63&o)>127&&(c=u);break;case 3:o=e[i+1],a=e[i+2],(192&o)==128&&(192&a)==128&&(u=(15&s)<<12|(63&o)<<6|63&a)>2047&&(u<55296||u>57343)&&(c=u);break;case 4:o=e[i+1],a=e[i+2],l=e[i+3],(192&o)==128&&(192&a)==128&&(192&l)==128&&(u=(15&s)<<18|(63&o)<<12|(63&a)<<6|63&l)>65535&&u<1114112&&(c=u)}null===c?(c=65533,f=1):c>65535&&(c-=65536,n.push(c>>>10&1023|55296),c=56320|1023&c),n.push(c),i+=f}var d=n,p=d.length;if(p<=4096)return String.fromCharCode.apply(String,d);for(var h="",g=0;gr)throw RangeError("Trying to access beyond buffer length")}function w(e,t,r,n,i,o){if(!l.isBuffer(e))throw TypeError('"buffer" argument must be a Buffer instance');if(t>i||te.length)throw RangeError("Index out of range")}function x(e,t,r,n,i,o){if(r+n>e.length||r<0)throw RangeError("Index out of range")}function S(e,t,r,n,o){return t*=1,r>>>=0,o||x(e,t,r,4,34028234663852886e22,-34028234663852886e22),i.write(e,t,r,n,23,4),r+4}function O(e,t,r,n,o){return t*=1,r>>>=0,o||x(e,t,r,8,17976931348623157e292,-17976931348623157e292),i.write(e,t,r,n,52,8),r+8}l.prototype.write=function(e,t,r,n){if(void 0===t)n="utf8",r=this.length,t=0;else if(void 0===r&&"string"==typeof t)n=t,r=this.length,t=0;else if(isFinite(t))t>>>=0,isFinite(r)?(r>>>=0,void 0===n&&(n="utf8")):(n=r,r=void 0);else throw Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");var i,o,a,l,u,s,c,f,d=this.length-t;if((void 0===r||r>d)&&(r=d),e.length>0&&(r<0||t<0)||t>this.length)throw RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var p=!1;;)switch(n){case"hex":return function(e,t,r,n){r=Number(r)||0;var i=e.length-r;n?(n=Number(n))>i&&(n=i):n=i;var o=t.length;n>o/2&&(n=o/2);for(var a=0;a>8,i.push(r%256),i.push(n);return i}(e,this.length-c),this,c,f);default:if(p)throw TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),p=!0}},l.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}},l.prototype.slice=function(e,t){var r=this.length;e=~~e,t=void 0===t?r:~~t,e<0?(e+=r)<0&&(e=0):e>r&&(e=r),t<0?(t+=r)<0&&(t=0):t>r&&(t=r),t>>=0,t>>>=0,r||b(e,t,this.length);for(var n=this[e],i=1,o=0;++o>>=0,t>>>=0,r||b(e,t,this.length);for(var n=this[e+--t],i=1;t>0&&(i*=256);)n+=this[e+--t]*i;return n},l.prototype.readUInt8=function(e,t){return e>>>=0,t||b(e,1,this.length),this[e]},l.prototype.readUInt16LE=function(e,t){return e>>>=0,t||b(e,2,this.length),this[e]|this[e+1]<<8},l.prototype.readUInt16BE=function(e,t){return e>>>=0,t||b(e,2,this.length),this[e]<<8|this[e+1]},l.prototype.readUInt32LE=function(e,t){return e>>>=0,t||b(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+0x1000000*this[e+3]},l.prototype.readUInt32BE=function(e,t){return e>>>=0,t||b(e,4,this.length),0x1000000*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},l.prototype.readIntLE=function(e,t,r){e>>>=0,t>>>=0,r||b(e,t,this.length);for(var n=this[e],i=1,o=0;++o=(i*=128)&&(n-=Math.pow(2,8*t)),n},l.prototype.readIntBE=function(e,t,r){e>>>=0,t>>>=0,r||b(e,t,this.length);for(var n=t,i=1,o=this[e+--n];n>0&&(i*=256);)o+=this[e+--n]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*t)),o},l.prototype.readInt8=function(e,t){return(e>>>=0,t||b(e,1,this.length),128&this[e])?-((255-this[e]+1)*1):this[e]},l.prototype.readInt16LE=function(e,t){e>>>=0,t||b(e,2,this.length);var r=this[e]|this[e+1]<<8;return 32768&r?0xffff0000|r:r},l.prototype.readInt16BE=function(e,t){e>>>=0,t||b(e,2,this.length);var r=this[e+1]|this[e]<<8;return 32768&r?0xffff0000|r:r},l.prototype.readInt32LE=function(e,t){return e>>>=0,t||b(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},l.prototype.readInt32BE=function(e,t){return e>>>=0,t||b(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},l.prototype.readFloatLE=function(e,t){return e>>>=0,t||b(e,4,this.length),i.read(this,e,!0,23,4)},l.prototype.readFloatBE=function(e,t){return e>>>=0,t||b(e,4,this.length),i.read(this,e,!1,23,4)},l.prototype.readDoubleLE=function(e,t){return e>>>=0,t||b(e,8,this.length),i.read(this,e,!0,52,8)},l.prototype.readDoubleBE=function(e,t){return e>>>=0,t||b(e,8,this.length),i.read(this,e,!1,52,8)},l.prototype.writeUIntLE=function(e,t,r,n){if(e*=1,t>>>=0,r>>>=0,!n){var i=Math.pow(2,8*r)-1;w(this,e,t,r,i,0)}var o=1,a=0;for(this[t]=255&e;++a>>=0,r>>>=0,!n){var i=Math.pow(2,8*r)-1;w(this,e,t,r,i,0)}var o=r-1,a=1;for(this[t+o]=255&e;--o>=0&&(a*=256);)this[t+o]=e/a&255;return t+r},l.prototype.writeUInt8=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,1,255,0),this[t]=255&e,t+1},l.prototype.writeUInt16LE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeUInt16BE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeUInt32LE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,4,0xffffffff,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},l.prototype.writeUInt32BE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,4,0xffffffff,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeIntLE=function(e,t,r,n){if(e*=1,t>>>=0,!n){var i=Math.pow(2,8*r-1);w(this,e,t,r,i-1,-i)}var o=0,a=1,l=0;for(this[t]=255&e;++o>>=0,!n){var i=Math.pow(2,8*r-1);w(this,e,t,r,i-1,-i)}var o=r-1,a=1,l=0;for(this[t+o]=255&e;--o>=0&&(a*=256);)e<0&&0===l&&0!==this[t+o+1]&&(l=1),this[t+o]=(e/a|0)-l&255;return t+r},l.prototype.writeInt8=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},l.prototype.writeInt16LE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeInt16BE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeInt32LE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,4,0x7fffffff,-0x80000000),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},l.prototype.writeInt32BE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,4,0x7fffffff,-0x80000000),e<0&&(e=0xffffffff+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeFloatLE=function(e,t,r){return S(this,e,t,!0,r)},l.prototype.writeFloatBE=function(e,t,r){return S(this,e,t,!1,r)},l.prototype.writeDoubleLE=function(e,t,r){return O(this,e,t,!0,r)},l.prototype.writeDoubleBE=function(e,t,r){return O(this,e,t,!1,r)},l.prototype.copy=function(e,t,r,n){if(!l.isBuffer(e))throw TypeError("argument should be a Buffer");if(r||(r=0),n||0===n||(n=this.length),t>=e.length&&(t=e.length),t||(t=0),n>0&&n=this.length)throw RangeError("Index out of range");if(n<0)throw RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),e.length-t=0;--o)e[o+t]=this[o+r];else Uint8Array.prototype.set.call(e,this.subarray(r,n),t);return i},l.prototype.fill=function(e,t,r,n){if("string"==typeof e){if("string"==typeof t?(n=t,t=0,r=this.length):"string"==typeof r&&(n=r,r=this.length),void 0!==n&&"string"!=typeof n)throw TypeError("encoding must be a string");if("string"==typeof n&&!l.isEncoding(n))throw TypeError("Unknown encoding: "+n);if(1===e.length){var i,o=e.charCodeAt(0);("utf8"===n&&o<128||"latin1"===n)&&(e=o)}}else"number"==typeof e?e&=255:"boolean"==typeof e&&(e=Number(e));if(t<0||this.length>>=0,r=void 0===r?this.length:r>>>0,e||(e=0),"number"==typeof e)for(i=t;i55295&&r<57344){if(!i){if(r>56319||a+1===n){(t-=3)>-1&&o.push(239,191,189);continue}i=r;continue}if(r<56320){(t-=3)>-1&&o.push(239,191,189),i=r;continue}r=(i-55296<<10|r-56320)+65536}else i&&(t-=3)>-1&&o.push(239,191,189);if(i=null,r<128){if((t-=1)<0)break;o.push(r)}else if(r<2048){if((t-=2)<0)break;o.push(r>>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;o.push(r>>12|224,r>>6&63|128,63&r|128)}else if(r<1114112){if((t-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}else throw Error("Invalid code point")}return o}function M(e){for(var t=[],r=0;r=t.length)&&!(i>=e.length);++i)t[i+r]=e[i];return i}function P(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}var A=function(){for(var e="0123456789abcdef",t=Array(256),r=0;r<16;++r)for(var n=16*r,i=0;i<16;++i)t[n+i]=e[r]+e[i];return t}()},783:function(e,t){t.read=function(e,t,r,n,i){var o,a,l=8*i-n-1,u=(1<>1,c=-7,f=r?i-1:0,d=r?-1:1,p=e[t+f];for(f+=d,o=p&(1<<-c)-1,p>>=-c,c+=l;c>0;o=256*o+e[t+f],f+=d,c-=8);for(a=o&(1<<-c)-1,o>>=-c,c+=n;c>0;a=256*a+e[t+f],f+=d,c-=8);if(0===o)o=1-s;else{if(o===u)return a?NaN:1/0*(p?-1:1);a+=Math.pow(2,n),o-=s}return(p?-1:1)*a*Math.pow(2,o-n)},t.write=function(e,t,r,n,i,o){var a,l,u,s=8*o-i-1,c=(1<>1,d=5960464477539062e-23*(23===i),p=n?0:o-1,h=n?1:-1,g=+(t<0||0===t&&1/t<0);for(isNaN(t=Math.abs(t))||t===1/0?(l=+!!isNaN(t),a=c):(a=Math.floor(Math.log(t)/Math.LN2),t*(u=Math.pow(2,-a))<1&&(a--,u*=2),a+f>=1?t+=d/u:t+=d*Math.pow(2,1-f),t*u>=2&&(a++,u/=2),a+f>=c?(l=0,a=c):a+f>=1?(l=(t*u-1)*Math.pow(2,i),a+=f):(l=t*Math.pow(2,f-1)*Math.pow(2,i),a=0));i>=8;e[r+p]=255&l,p+=h,l/=256,i-=8);for(a=a<0;e[r+p]=255&a,p+=h,a/=256,s-=8);e[r+p-h]|=128*g}}},r={};function n(e){var i=r[e];if(void 0!==i)return i.exports;var o=r[e]={exports:{}},a=!0;try{t[e](o,o.exports,n),a=!1}finally{a&&delete r[e]}return o.exports}n.ab="//",e.exports=n(72)}()},9688:(e,t,r)=>{"use strict";r.d(t,{QP:()=>ee});let n=(e,t)=>{if(0===e.length)return t.classGroupId;let r=e[0],i=t.nextPart.get(r),o=i?n(e.slice(1),i):void 0;if(o)return o;if(0===t.validators.length)return;let a=e.join("-");return t.validators.find(({validator:e})=>e(a))?.classGroupId},i=/^\[(.+)\]$/,o=(e,t,r,n)=>{e.forEach(e=>{if("string"==typeof e){(""===e?t:a(t,e)).classGroupId=r;return}if("function"==typeof e)return l(e)?void o(e(n),t,r,n):void t.validators.push({validator:e,classGroupId:r});Object.entries(e).forEach(([e,i])=>{o(i,a(t,e),r,n)})})},a=(e,t)=>{let r=e;return t.split("-").forEach(e=>{r.nextPart.has(e)||r.nextPart.set(e,{nextPart:new Map,validators:[]}),r=r.nextPart.get(e)}),r},l=e=>e.isThemeGetter,u=/\s+/;function s(){let e,t,r=0,n="";for(;r{let t;if("string"==typeof e)return e;let r="";for(let n=0;n{let t=t=>t[e]||[];return t.isThemeGetter=!0,t},d=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,p=/^\((?:(\w[\w-]*):)?(.+)\)$/i,h=/^\d+\/\d+$/,g=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,v=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,m=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,y=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,b=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,w=e=>h.test(e),x=e=>!!e&&!Number.isNaN(Number(e)),S=e=>!!e&&Number.isInteger(Number(e)),O=e=>e.endsWith("%")&&x(e.slice(0,-1)),E=e=>g.test(e),C=()=>!0,M=e=>v.test(e)&&!m.test(e),k=()=>!1,_=e=>y.test(e),P=e=>b.test(e),A=e=>!T(e)&&!L(e),j=e=>G(e,q,k),T=e=>d.test(e),R=e=>G(e,Y,M),z=e=>G(e,X,x),D=e=>G(e,W,k),I=e=>G(e,K,P),N=e=>G(e,Q,_),L=e=>p.test(e),F=e=>Z(e,Y),$=e=>Z(e,J),B=e=>Z(e,W),V=e=>Z(e,q),U=e=>Z(e,K),H=e=>Z(e,Q,!0),G=(e,t,r)=>{let n=d.exec(e);return!!n&&(n[1]?t(n[1]):r(n[2]))},Z=(e,t,r=!1)=>{let n=p.exec(e);return!!n&&(n[1]?t(n[1]):r)},W=e=>"position"===e||"percentage"===e,K=e=>"image"===e||"url"===e,q=e=>"length"===e||"size"===e||"bg-size"===e,Y=e=>"length"===e,X=e=>"number"===e,J=e=>"family-name"===e,Q=e=>"shadow"===e;Symbol.toStringTag;let ee=function(e,...t){let r,a,l,c=function(u){let s;return a=(r={cache:(e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let t=0,r=new Map,n=new Map,i=(i,o)=>{r.set(i,o),++t>e&&(t=0,n=r,r=new Map)};return{get(e){let t=r.get(e);return void 0!==t?t:void 0!==(t=n.get(e))?(i(e,t),t):void 0},set(e,t){r.has(e)?r.set(e,t):i(e,t)}}})((s=t.reduce((e,t)=>t(e),e())).cacheSize),parseClassName:(e=>{let{prefix:t,experimentalParseClassName:r}=e,n=e=>{let t,r,n=[],i=0,o=0,a=0;for(let r=0;ra?t-a:void 0}};if(t){let e=t+":",r=n;n=t=>t.startsWith(e)?r(t.substring(e.length)):{isExternal:!0,modifiers:[],hasImportantModifier:!1,baseClassName:t,maybePostfixModifierPosition:void 0}}if(r){let e=n;n=t=>r({className:t,parseClassName:e})}return n})(s),sortModifiers:(e=>{let t=Object.fromEntries(e.orderSensitiveModifiers.map(e=>[e,!0]));return e=>{if(e.length<=1)return e;let r=[],n=[];return e.forEach(e=>{"["===e[0]||t[e]?(r.push(...n.sort(),e),n=[]):n.push(e)}),r.push(...n.sort()),r}})(s),...(e=>{let t=(e=>{let{theme:t,classGroups:r}=e,n={nextPart:new Map,validators:[]};for(let e in r)o(r[e],n,e,t);return n})(e),{conflictingClassGroups:r,conflictingClassGroupModifiers:a}=e;return{getClassGroupId:e=>{let r=e.split("-");return""===r[0]&&1!==r.length&&r.shift(),n(r,t)||(e=>{if(i.test(e)){let t=i.exec(e)[1],r=t?.substring(0,t.indexOf(":"));if(r)return"arbitrary.."+r}})(e)},getConflictingClassGroupIds:(e,t)=>{let n=r[e]||[];return t&&a[e]?[...n,...a[e]]:n}}})(s)}).cache.get,l=r.cache.set,c=f,f(u)};function f(e){let t=a(e);if(t)return t;let n=((e,t)=>{let{parseClassName:r,getClassGroupId:n,getConflictingClassGroupIds:i,sortModifiers:o}=t,a=[],l=e.trim().split(u),s="";for(let e=l.length-1;e>=0;e-=1){let t=l[e],{isExternal:u,modifiers:c,hasImportantModifier:f,baseClassName:d,maybePostfixModifierPosition:p}=r(t);if(u){s=t+(s.length>0?" "+s:s);continue}let h=!!p,g=n(h?d.substring(0,p):d);if(!g){if(!h||!(g=n(d))){s=t+(s.length>0?" "+s:s);continue}h=!1}let v=o(c).join(":"),m=f?v+"!":v,y=m+g;if(a.includes(y))continue;a.push(y);let b=i(g,h);for(let e=0;e0?" "+s:s)}return s})(e,r);return l(e,n),n}return function(){return c(s.apply(null,arguments))}}(()=>{let e=f("color"),t=f("font"),r=f("text"),n=f("font-weight"),i=f("tracking"),o=f("leading"),a=f("breakpoint"),l=f("container"),u=f("spacing"),s=f("radius"),c=f("shadow"),d=f("inset-shadow"),p=f("text-shadow"),h=f("drop-shadow"),g=f("blur"),v=f("perspective"),m=f("aspect"),y=f("ease"),b=f("animate"),M=()=>["auto","avoid","all","avoid-page","page","left","right","column"],k=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom"],_=()=>[...k(),L,T],P=()=>["auto","hidden","clip","visible","scroll"],G=()=>["auto","contain","none"],Z=()=>[L,T,u],W=()=>[w,"full","auto",...Z()],K=()=>[S,"none","subgrid",L,T],q=()=>["auto",{span:["full",S,L,T]},S,L,T],Y=()=>[S,"auto",L,T],X=()=>["auto","min","max","fr",L,T],J=()=>["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"],Q=()=>["start","end","center","stretch","center-safe","end-safe"],ee=()=>["auto",...Z()],et=()=>[w,"auto","full","dvw","dvh","lvw","lvh","svw","svh","min","max","fit",...Z()],er=()=>[e,L,T],en=()=>[...k(),B,D,{position:[L,T]}],ei=()=>["no-repeat",{repeat:["","x","y","space","round"]}],eo=()=>["auto","cover","contain",V,j,{size:[L,T]}],ea=()=>[O,F,R],el=()=>["","none","full",s,L,T],eu=()=>["",x,F,R],es=()=>["solid","dashed","dotted","double"],ec=()=>["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],ef=()=>[x,O,B,D],ed=()=>["","none",g,L,T],ep=()=>["none",x,L,T],eh=()=>["none",x,L,T],eg=()=>[x,L,T],ev=()=>[w,"full",...Z()];return{cacheSize:500,theme:{animate:["spin","ping","pulse","bounce"],aspect:["video"],blur:[E],breakpoint:[E],color:[C],container:[E],"drop-shadow":[E],ease:["in","out","in-out"],font:[A],"font-weight":["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"],"inset-shadow":[E],leading:["none","tight","snug","normal","relaxed","loose"],perspective:["dramatic","near","normal","midrange","distant","none"],radius:[E],shadow:[E],spacing:["px",x],text:[E],"text-shadow":[E],tracking:["tighter","tight","normal","wide","wider","widest"]},classGroups:{aspect:[{aspect:["auto","square",w,T,L,m]}],container:["container"],columns:[{columns:[x,T,L,l]}],"break-after":[{"break-after":M()}],"break-before":[{"break-before":M()}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"]}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{box:["border","content"]}],display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"],sr:["sr-only","not-sr-only"],float:[{float:["right","left","none","start","end"]}],clear:[{clear:["left","right","both","none","start","end"]}],isolation:["isolate","isolation-auto"],"object-fit":[{object:["contain","cover","fill","none","scale-down"]}],"object-position":[{object:_()}],overflow:[{overflow:P()}],"overflow-x":[{"overflow-x":P()}],"overflow-y":[{"overflow-y":P()}],overscroll:[{overscroll:G()}],"overscroll-x":[{"overscroll-x":G()}],"overscroll-y":[{"overscroll-y":G()}],position:["static","fixed","absolute","relative","sticky"],inset:[{inset:W()}],"inset-x":[{"inset-x":W()}],"inset-y":[{"inset-y":W()}],start:[{start:W()}],end:[{end:W()}],top:[{top:W()}],right:[{right:W()}],bottom:[{bottom:W()}],left:[{left:W()}],visibility:["visible","invisible","collapse"],z:[{z:[S,"auto",L,T]}],basis:[{basis:[w,"full","auto",l,...Z()]}],"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}],"flex-wrap":[{flex:["nowrap","wrap","wrap-reverse"]}],flex:[{flex:[x,w,"auto","initial","none",T]}],grow:[{grow:["",x,L,T]}],shrink:[{shrink:["",x,L,T]}],order:[{order:[S,"first","last","none",L,T]}],"grid-cols":[{"grid-cols":K()}],"col-start-end":[{col:q()}],"col-start":[{"col-start":Y()}],"col-end":[{"col-end":Y()}],"grid-rows":[{"grid-rows":K()}],"row-start-end":[{row:q()}],"row-start":[{"row-start":Y()}],"row-end":[{"row-end":Y()}],"grid-flow":[{"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{"auto-cols":X()}],"auto-rows":[{"auto-rows":X()}],gap:[{gap:Z()}],"gap-x":[{"gap-x":Z()}],"gap-y":[{"gap-y":Z()}],"justify-content":[{justify:[...J(),"normal"]}],"justify-items":[{"justify-items":[...Q(),"normal"]}],"justify-self":[{"justify-self":["auto",...Q()]}],"align-content":[{content:["normal",...J()]}],"align-items":[{items:[...Q(),{baseline:["","last"]}]}],"align-self":[{self:["auto",...Q(),{baseline:["","last"]}]}],"place-content":[{"place-content":J()}],"place-items":[{"place-items":[...Q(),"baseline"]}],"place-self":[{"place-self":["auto",...Q()]}],p:[{p:Z()}],px:[{px:Z()}],py:[{py:Z()}],ps:[{ps:Z()}],pe:[{pe:Z()}],pt:[{pt:Z()}],pr:[{pr:Z()}],pb:[{pb:Z()}],pl:[{pl:Z()}],m:[{m:ee()}],mx:[{mx:ee()}],my:[{my:ee()}],ms:[{ms:ee()}],me:[{me:ee()}],mt:[{mt:ee()}],mr:[{mr:ee()}],mb:[{mb:ee()}],ml:[{ml:ee()}],"space-x":[{"space-x":Z()}],"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":Z()}],"space-y-reverse":["space-y-reverse"],size:[{size:et()}],w:[{w:[l,"screen",...et()]}],"min-w":[{"min-w":[l,"screen","none",...et()]}],"max-w":[{"max-w":[l,"screen","none","prose",{screen:[a]},...et()]}],h:[{h:["screen","lh",...et()]}],"min-h":[{"min-h":["screen","lh","none",...et()]}],"max-h":[{"max-h":["screen","lh",...et()]}],"font-size":[{text:["base",r,F,R]}],"font-smoothing":["antialiased","subpixel-antialiased"],"font-style":["italic","not-italic"],"font-weight":[{font:[n,L,z]}],"font-stretch":[{"font-stretch":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded",O,T]}],"font-family":[{font:[$,T,t]}],"fvn-normal":["normal-nums"],"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"],"fvn-figure":["lining-nums","oldstyle-nums"],"fvn-spacing":["proportional-nums","tabular-nums"],"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{tracking:[i,L,T]}],"line-clamp":[{"line-clamp":[x,"none",L,z]}],leading:[{leading:[o,...Z()]}],"list-image":[{"list-image":["none",L,T]}],"list-style-position":[{list:["inside","outside"]}],"list-style-type":[{list:["disc","decimal","none",L,T]}],"text-alignment":[{text:["left","center","right","justify","start","end"]}],"placeholder-color":[{placeholder:er()}],"text-color":[{text:er()}],"text-decoration":["underline","overline","line-through","no-underline"],"text-decoration-style":[{decoration:[...es(),"wavy"]}],"text-decoration-thickness":[{decoration:[x,"from-font","auto",L,R]}],"text-decoration-color":[{decoration:er()}],"underline-offset":[{"underline-offset":[x,"auto",L,T]}],"text-transform":["uppercase","lowercase","capitalize","normal-case"],"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:Z()}],"vertical-align":[{align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",L,T]}],whitespace:[{whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}],break:[{break:["normal","words","all","keep"]}],wrap:[{wrap:["break-word","anywhere","normal"]}],hyphens:[{hyphens:["none","manual","auto"]}],content:[{content:["none",L,T]}],"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{"bg-clip":["border","padding","content","text"]}],"bg-origin":[{"bg-origin":["border","padding","content"]}],"bg-position":[{bg:en()}],"bg-repeat":[{bg:ei()}],"bg-size":[{bg:eo()}],"bg-image":[{bg:["none",{linear:[{to:["t","tr","r","br","b","bl","l","tl"]},S,L,T],radial:["",L,T],conic:[S,L,T]},U,I]}],"bg-color":[{bg:er()}],"gradient-from-pos":[{from:ea()}],"gradient-via-pos":[{via:ea()}],"gradient-to-pos":[{to:ea()}],"gradient-from":[{from:er()}],"gradient-via":[{via:er()}],"gradient-to":[{to:er()}],rounded:[{rounded:el()}],"rounded-s":[{"rounded-s":el()}],"rounded-e":[{"rounded-e":el()}],"rounded-t":[{"rounded-t":el()}],"rounded-r":[{"rounded-r":el()}],"rounded-b":[{"rounded-b":el()}],"rounded-l":[{"rounded-l":el()}],"rounded-ss":[{"rounded-ss":el()}],"rounded-se":[{"rounded-se":el()}],"rounded-ee":[{"rounded-ee":el()}],"rounded-es":[{"rounded-es":el()}],"rounded-tl":[{"rounded-tl":el()}],"rounded-tr":[{"rounded-tr":el()}],"rounded-br":[{"rounded-br":el()}],"rounded-bl":[{"rounded-bl":el()}],"border-w":[{border:eu()}],"border-w-x":[{"border-x":eu()}],"border-w-y":[{"border-y":eu()}],"border-w-s":[{"border-s":eu()}],"border-w-e":[{"border-e":eu()}],"border-w-t":[{"border-t":eu()}],"border-w-r":[{"border-r":eu()}],"border-w-b":[{"border-b":eu()}],"border-w-l":[{"border-l":eu()}],"divide-x":[{"divide-x":eu()}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{"divide-y":eu()}],"divide-y-reverse":["divide-y-reverse"],"border-style":[{border:[...es(),"hidden","none"]}],"divide-style":[{divide:[...es(),"hidden","none"]}],"border-color":[{border:er()}],"border-color-x":[{"border-x":er()}],"border-color-y":[{"border-y":er()}],"border-color-s":[{"border-s":er()}],"border-color-e":[{"border-e":er()}],"border-color-t":[{"border-t":er()}],"border-color-r":[{"border-r":er()}],"border-color-b":[{"border-b":er()}],"border-color-l":[{"border-l":er()}],"divide-color":[{divide:er()}],"outline-style":[{outline:[...es(),"none","hidden"]}],"outline-offset":[{"outline-offset":[x,L,T]}],"outline-w":[{outline:["",x,F,R]}],"outline-color":[{outline:er()}],shadow:[{shadow:["","none",c,H,N]}],"shadow-color":[{shadow:er()}],"inset-shadow":[{"inset-shadow":["none",d,H,N]}],"inset-shadow-color":[{"inset-shadow":er()}],"ring-w":[{ring:eu()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:er()}],"ring-offset-w":[{"ring-offset":[x,R]}],"ring-offset-color":[{"ring-offset":er()}],"inset-ring-w":[{"inset-ring":eu()}],"inset-ring-color":[{"inset-ring":er()}],"text-shadow":[{"text-shadow":["none",p,H,N]}],"text-shadow-color":[{"text-shadow":er()}],opacity:[{opacity:[x,L,T]}],"mix-blend":[{"mix-blend":[...ec(),"plus-darker","plus-lighter"]}],"bg-blend":[{"bg-blend":ec()}],"mask-clip":[{"mask-clip":["border","padding","content","fill","stroke","view"]},"mask-no-clip"],"mask-composite":[{mask:["add","subtract","intersect","exclude"]}],"mask-image-linear-pos":[{"mask-linear":[x]}],"mask-image-linear-from-pos":[{"mask-linear-from":ef()}],"mask-image-linear-to-pos":[{"mask-linear-to":ef()}],"mask-image-linear-from-color":[{"mask-linear-from":er()}],"mask-image-linear-to-color":[{"mask-linear-to":er()}],"mask-image-t-from-pos":[{"mask-t-from":ef()}],"mask-image-t-to-pos":[{"mask-t-to":ef()}],"mask-image-t-from-color":[{"mask-t-from":er()}],"mask-image-t-to-color":[{"mask-t-to":er()}],"mask-image-r-from-pos":[{"mask-r-from":ef()}],"mask-image-r-to-pos":[{"mask-r-to":ef()}],"mask-image-r-from-color":[{"mask-r-from":er()}],"mask-image-r-to-color":[{"mask-r-to":er()}],"mask-image-b-from-pos":[{"mask-b-from":ef()}],"mask-image-b-to-pos":[{"mask-b-to":ef()}],"mask-image-b-from-color":[{"mask-b-from":er()}],"mask-image-b-to-color":[{"mask-b-to":er()}],"mask-image-l-from-pos":[{"mask-l-from":ef()}],"mask-image-l-to-pos":[{"mask-l-to":ef()}],"mask-image-l-from-color":[{"mask-l-from":er()}],"mask-image-l-to-color":[{"mask-l-to":er()}],"mask-image-x-from-pos":[{"mask-x-from":ef()}],"mask-image-x-to-pos":[{"mask-x-to":ef()}],"mask-image-x-from-color":[{"mask-x-from":er()}],"mask-image-x-to-color":[{"mask-x-to":er()}],"mask-image-y-from-pos":[{"mask-y-from":ef()}],"mask-image-y-to-pos":[{"mask-y-to":ef()}],"mask-image-y-from-color":[{"mask-y-from":er()}],"mask-image-y-to-color":[{"mask-y-to":er()}],"mask-image-radial":[{"mask-radial":[L,T]}],"mask-image-radial-from-pos":[{"mask-radial-from":ef()}],"mask-image-radial-to-pos":[{"mask-radial-to":ef()}],"mask-image-radial-from-color":[{"mask-radial-from":er()}],"mask-image-radial-to-color":[{"mask-radial-to":er()}],"mask-image-radial-shape":[{"mask-radial":["circle","ellipse"]}],"mask-image-radial-size":[{"mask-radial":[{closest:["side","corner"],farthest:["side","corner"]}]}],"mask-image-radial-pos":[{"mask-radial-at":k()}],"mask-image-conic-pos":[{"mask-conic":[x]}],"mask-image-conic-from-pos":[{"mask-conic-from":ef()}],"mask-image-conic-to-pos":[{"mask-conic-to":ef()}],"mask-image-conic-from-color":[{"mask-conic-from":er()}],"mask-image-conic-to-color":[{"mask-conic-to":er()}],"mask-mode":[{mask:["alpha","luminance","match"]}],"mask-origin":[{"mask-origin":["border","padding","content","fill","stroke","view"]}],"mask-position":[{mask:en()}],"mask-repeat":[{mask:ei()}],"mask-size":[{mask:eo()}],"mask-type":[{"mask-type":["alpha","luminance"]}],"mask-image":[{mask:["none",L,T]}],filter:[{filter:["","none",L,T]}],blur:[{blur:ed()}],brightness:[{brightness:[x,L,T]}],contrast:[{contrast:[x,L,T]}],"drop-shadow":[{"drop-shadow":["","none",h,H,N]}],"drop-shadow-color":[{"drop-shadow":er()}],grayscale:[{grayscale:["",x,L,T]}],"hue-rotate":[{"hue-rotate":[x,L,T]}],invert:[{invert:["",x,L,T]}],saturate:[{saturate:[x,L,T]}],sepia:[{sepia:["",x,L,T]}],"backdrop-filter":[{"backdrop-filter":["","none",L,T]}],"backdrop-blur":[{"backdrop-blur":ed()}],"backdrop-brightness":[{"backdrop-brightness":[x,L,T]}],"backdrop-contrast":[{"backdrop-contrast":[x,L,T]}],"backdrop-grayscale":[{"backdrop-grayscale":["",x,L,T]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[x,L,T]}],"backdrop-invert":[{"backdrop-invert":["",x,L,T]}],"backdrop-opacity":[{"backdrop-opacity":[x,L,T]}],"backdrop-saturate":[{"backdrop-saturate":[x,L,T]}],"backdrop-sepia":[{"backdrop-sepia":["",x,L,T]}],"border-collapse":[{border:["collapse","separate"]}],"border-spacing":[{"border-spacing":Z()}],"border-spacing-x":[{"border-spacing-x":Z()}],"border-spacing-y":[{"border-spacing-y":Z()}],"table-layout":[{table:["auto","fixed"]}],caption:[{caption:["top","bottom"]}],transition:[{transition:["","all","colors","opacity","shadow","transform","none",L,T]}],"transition-behavior":[{transition:["normal","discrete"]}],duration:[{duration:[x,"initial",L,T]}],ease:[{ease:["linear","initial",y,L,T]}],delay:[{delay:[x,L,T]}],animate:[{animate:["none",b,L,T]}],backface:[{backface:["hidden","visible"]}],perspective:[{perspective:[v,L,T]}],"perspective-origin":[{"perspective-origin":_()}],rotate:[{rotate:ep()}],"rotate-x":[{"rotate-x":ep()}],"rotate-y":[{"rotate-y":ep()}],"rotate-z":[{"rotate-z":ep()}],scale:[{scale:eh()}],"scale-x":[{"scale-x":eh()}],"scale-y":[{"scale-y":eh()}],"scale-z":[{"scale-z":eh()}],"scale-3d":["scale-3d"],skew:[{skew:eg()}],"skew-x":[{"skew-x":eg()}],"skew-y":[{"skew-y":eg()}],transform:[{transform:[L,T,"","none","gpu","cpu"]}],"transform-origin":[{origin:_()}],"transform-style":[{transform:["3d","flat"]}],translate:[{translate:ev()}],"translate-x":[{"translate-x":ev()}],"translate-y":[{"translate-y":ev()}],"translate-z":[{"translate-z":ev()}],"translate-none":["translate-none"],accent:[{accent:er()}],appearance:[{appearance:["none","auto"]}],"caret-color":[{caret:er()}],"color-scheme":[{scheme:["normal","dark","light","light-dark","only-dark","only-light"]}],cursor:[{cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",L,T]}],"field-sizing":[{"field-sizing":["fixed","content"]}],"pointer-events":[{"pointer-events":["auto","none"]}],resize:[{resize:["none","","y","x"]}],"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":Z()}],"scroll-mx":[{"scroll-mx":Z()}],"scroll-my":[{"scroll-my":Z()}],"scroll-ms":[{"scroll-ms":Z()}],"scroll-me":[{"scroll-me":Z()}],"scroll-mt":[{"scroll-mt":Z()}],"scroll-mr":[{"scroll-mr":Z()}],"scroll-mb":[{"scroll-mb":Z()}],"scroll-ml":[{"scroll-ml":Z()}],"scroll-p":[{"scroll-p":Z()}],"scroll-px":[{"scroll-px":Z()}],"scroll-py":[{"scroll-py":Z()}],"scroll-ps":[{"scroll-ps":Z()}],"scroll-pe":[{"scroll-pe":Z()}],"scroll-pt":[{"scroll-pt":Z()}],"scroll-pr":[{"scroll-pr":Z()}],"scroll-pb":[{"scroll-pb":Z()}],"scroll-pl":[{"scroll-pl":Z()}],"snap-align":[{snap:["start","end","center","align-none"]}],"snap-stop":[{snap:["normal","always"]}],"snap-type":[{snap:["none","x","y","both"]}],"snap-strictness":[{snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}],"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{select:["none","text","all","auto"]}],"will-change":[{"will-change":["auto","scroll","contents","transform",L,T]}],fill:[{fill:["none",...er()]}],"stroke-w":[{stroke:[x,F,R,z]}],stroke:[{stroke:["none",...er()]}],"forced-color-adjust":[{"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"],inset:["inset-x","inset-y","start","end","top","right","bottom","left"],"inset-x":["right","left"],"inset-y":["top","bottom"],flex:["basis","grow","shrink"],gap:["gap-x","gap-y"],p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"],m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"],size:["w","h"],"font-size":["leading"],"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"],"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"],"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"],"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"],rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"],"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"],"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"],"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"],"border-spacing":["border-spacing-x","border-spacing-y"],"border-w":["border-w-x","border-w-y","border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"],"border-w-x":["border-w-r","border-w-l"],"border-w-y":["border-w-t","border-w-b"],"border-color":["border-color-x","border-color-y","border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"],"border-color-x":["border-color-r","border-color-l"],"border-color-y":["border-color-t","border-color-b"],translate:["translate-x","translate-y","translate-none"],"translate-none":["translate","translate-x","translate-y","translate-z"],"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"],"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"],"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"],"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"],touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"],"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]},orderSensitiveModifiers:["*","**","after","backdrop","before","details-content","file","first-letter","first-line","marker","placeholder","selection"]}})},9708:(e,t,r)=>{"use strict";r.d(t,{DX:()=>l,TL:()=>a});var n=r(2115),i=r(6101),o=r(5155);function a(e){let t=function(e){let t=n.forwardRef((e,t)=>{let{children:r,...o}=e;if(n.isValidElement(r)){var a;let e,l,u=(a=r,(l=(e=Object.getOwnPropertyDescriptor(a.props,"ref")?.get)&&"isReactWarning"in e&&e.isReactWarning)?a.ref:(l=(e=Object.getOwnPropertyDescriptor(a,"ref")?.get)&&"isReactWarning"in e&&e.isReactWarning)?a.props.ref:a.props.ref||a.ref),s=function(e,t){let r={...t};for(let n in t){let i=e[n],o=t[n];/^on[A-Z]/.test(n)?i&&o?r[n]=(...e)=>{let t=o(...e);return i(...e),t}:i&&(r[n]=i):"style"===n?r[n]={...i,...o}:"className"===n&&(r[n]=[i,o].filter(Boolean).join(" "))}return{...e,...r}}(o,r.props);return r.type!==n.Fragment&&(s.ref=t?(0,i.t)(t,u):u),n.cloneElement(r,s)}return n.Children.count(r)>1?n.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}(e),r=n.forwardRef((e,r)=>{let{children:i,...a}=e,l=n.Children.toArray(i),u=l.find(s);if(u){let e=u.props.children,i=l.map(t=>t!==u?t:n.Children.count(e)>1?n.Children.only(null):n.isValidElement(e)?e.props.children:null);return(0,o.jsx)(t,{...a,ref:r,children:n.isValidElement(e)?n.cloneElement(e,void 0,i):null})}return(0,o.jsx)(t,{...a,ref:r,children:i})});return r.displayName=`${e}.Slot`,r}var l=a("Slot"),u=Symbol("radix.slottable");function s(e){return n.isValidElement(e)&&"function"==typeof e.type&&"__radixId"in e.type&&e.type.__radixId===u}},9738:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(4117),i=r(2721);t.cloneDeepWith=function(e,t){return n.cloneDeepWith(e,(r,o,a,l)=>{let u=t?.(r,o,a,l);if(null!=u)return u;if("object"==typeof e)switch(Object.prototype.toString.call(e)){case i.numberTag:case i.stringTag:case i.booleanTag:{let t=new e.constructor(e?.valueOf());return n.copyProperties(t,e),t}case i.argumentsTag:{let t={};return n.copyProperties(t,e),t.length=e.length,t[Symbol.iterator]=e[Symbol.iterator],t}default:return}})}},9819:(e,t,r)=>{"use strict";function n(e){return"object"==typeof e&&"length"in e?e:Array.from(e)}r.d(t,{A:()=>n}),Array.prototype.slice},9827:(e,t,r)=>{"use strict";r.d(t,{qx:()=>A,IH:()=>P,s0:()=>b,gH:()=>y,SW:()=>I,YB:()=>O,bk:()=>D,Hj:()=>j,nb:()=>k,PW:()=>x,Mk:()=>_,$8:()=>M,yy:()=>C,Rh:()=>S,GF:()=>T,uM:()=>R,kr:()=>m,r4:()=>z,_L:()=>w});var n=r(241),i=r.n(n),o=r(5672),a=r.n(o);function l(e,t){if((i=e.length)>1)for(var r,n,i,o=1,a=e[t[0]],l=a.length;o=0;)r[t]=t;return r}function f(e,t){return e[t]}function d(e){let t=[];return t.key=e,t}var p=r(6377),h=r(5641);function g(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function v(e){for(var t=1;t{var o,a=-1,l=null!=(o=null==t?void 0:t.length)?o:0;if(l<=1||null==e)return 0;if("angleAxis"===n&&null!=i&&1e-6>=Math.abs(Math.abs(i[1]-i[0])-360))for(var u=0;u0?r[u-1].coordinate:r[l-1].coordinate,c=r[u].coordinate,f=u>=l-1?r[0].coordinate:r[u+1].coordinate,d=void 0;if((0,p.sA)(c-s)!==(0,p.sA)(f-c)){var h=[];if((0,p.sA)(f-c)===(0,p.sA)(i[1]-i[0])){d=f;var g=c+i[1]-i[0];h[0]=Math.min(g,(g+s)/2),h[1]=Math.max(g,(g+s)/2)}else{d=s;var v=f+i[1]-i[0];h[0]=Math.min(c,(v+c)/2),h[1]=Math.max(c,(v+c)/2)}var m=[Math.min(c,(d+c)/2),Math.max(c,(d+c)/2)];if(e>m[0]&&e<=m[1]||e>=h[0]&&e<=h[1]){({index:a}=r[u]);break}}else{var y=Math.min(s,f),b=Math.max(s,f);if(e>(y+c)/2&&e<=(b+c)/2){({index:a}=r[u]);break}}}else if(t){for(var w=0;w0&&w(t[w].coordinate+t[w-1].coordinate)/2&&e<=(t[w].coordinate+t[w+1].coordinate)/2||w===l-1&&e>(t[w].coordinate+t[w-1].coordinate)/2){({index:a}=t[w]);break}}return a},b=(e,t,r)=>{if(t&&r){var{width:n,height:i}=r,{align:o,verticalAlign:a,layout:l}=t;if(("vertical"===l||"horizontal"===l&&"middle"===a)&&"center"!==o&&(0,p.Et)(e[o]))return v(v({},e),{},{[o]:e[o]+(n||0)});if(("horizontal"===l||"vertical"===l&&"center"===o)&&"middle"!==a&&(0,p.Et)(e[a]))return v(v({},e),{},{[a]:e[a]+(i||0)})}return e},w=(e,t)=>"horizontal"===e&&"xAxis"===t||"vertical"===e&&"yAxis"===t||"centric"===e&&"angleAxis"===t||"radial"===e&&"radiusAxis"===t,x=(e,t,r,n)=>{if(n)return e.map(e=>e.coordinate);var i,o,a=e.map(e=>(e.coordinate===t&&(i=!0),e.coordinate===r&&(o=!0),e.coordinate));return i||a.push(t),o||a.push(r),a},S=(e,t,r)=>{if(!e)return null;var{duplicateDomain:n,type:i,range:o,scale:a,realScaleType:l,isCategorical:u,categoricalDomain:s,tickCount:c,ticks:f,niceTicks:d,axisType:h}=e;if(!a)return null;var g="scaleBand"===l&&a.bandwidth?a.bandwidth()/2:2,v=(t||r)&&"category"===i&&a.bandwidth?a.bandwidth()/g:0;return(v="angleAxis"===h&&o&&o.length>=2?2*(0,p.sA)(o[0]-o[1])*v:v,t&&(f||d))?(f||d||[]).map((e,t)=>({coordinate:a(n?n.indexOf(e):e)+v,value:e,offset:v,index:t})).filter(e=>!(0,p.M8)(e.coordinate)):u&&s?s.map((e,t)=>({coordinate:a(e)+v,value:e,index:t,offset:v})):a.ticks&&!r&&null!=c?a.ticks(c).map((e,t)=>({coordinate:a(e)+v,value:e,offset:v,index:t})):a.domain().map((e,t)=>({coordinate:a(e)+v,value:n?n[e]:e,index:t,offset:v}))},O=e=>{var t=e.domain();if(t&&!(t.length<=2)){var r=t.length,n=e.range(),i=Math.min(n[0],n[1])-1e-4,o=Math.max(n[0],n[1])+1e-4,a=e(t[0]),l=e(t[r-1]);(ao||lo)&&e.domain([t[0],t[r-1]])}},E={sign:e=>{var t=e.length;if(!(t<=0))for(var r=0,n=e[0].length;r=0?(e[a][r][0]=i,e[a][r][1]=i+l,i=e[a][r][1]):(e[a][r][0]=o,e[a][r][1]=o+l,o=e[a][r][1])}},expand:function(e,t){if((n=e.length)>0){for(var r,n,i,o=0,a=e[0].length;o0){for(var r,n=0,i=e[t[0]],o=i.length;n0&&(n=(r=e[t[0]]).length)>0){for(var r,n,i,o=0,a=1;a{var t=e.length;if(!(t<=0))for(var r=0,n=e[0].length;r=0?(e[o][r][0]=i,e[o][r][1]=i+a,i=e[o][r][1]):(e[o][r][0]=0,e[o][r][1]=0)}}},C=(e,t,r)=>{var n=E[r];return(function(){var e=(0,s.A)([]),t=c,r=l,n=f;function i(i){var o,a,l=Array.from(e.apply(this,arguments),d),s=l.length,c=-1;for(let e of i)for(o=0,++c;o+m(e,t,0)).order(c).offset(n)(e)};function M(e){return null==e?void 0:String(e)}function k(e){var{axis:t,ticks:r,bandSize:n,entry:i,index:o,dataKey:a}=e;if("category"===t.type){if(!t.allowDuplicatedCategory&&t.dataKey&&!(0,p.uy)(i[t.dataKey])){var l=(0,p.eP)(r,"value",i[t.dataKey]);if(l)return l.coordinate+n/2}return r[o]?r[o].coordinate+n/2:null}var u=m(i,(0,p.uy)(a)?t.dataKey:a);return(0,p.uy)(u)?null:t.scale(u)}var _=(e,t,r)=>{if(null!=e)return(e=>[e[0]===1/0?0:e[0],e[1]===-1/0?0:e[1]])(Object.keys(e).reduce((n,i)=>{var{stackedData:o}=e[i],a=o.reduce((e,n)=>{var i=(e=>{var t=e.flat(2).filter(p.Et);return[Math.min(...t),Math.max(...t)]})(n.slice(t,r+1));return[Math.min(e[0],i[0]),Math.max(e[1],i[1])]},[1/0,-1/0]);return[Math.min(a[0],n[0]),Math.max(a[1],n[1])]},[1/0,-1/0]))},P=/^dataMin[\s]*-[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,A=/^dataMax[\s]*\+[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,j=(e,t,r)=>{if(e&&e.scale&&e.scale.bandwidth){var n=e.scale.bandwidth();if(!r||n>0)return n}if(e&&t&&t.length>=2){for(var o=i()(t,e=>e.coordinate),a=1/0,l=1,u=o.length;l=i.left&&e<=i.left+i.width&&t>=i.top&&t<=i.top+i.height?{x:e,y:t}:null:n?(0,h.yy)({x:e,y:t},n):null}var D=(e,t,r,n)=>{var i=t.find(e=>e&&e.index===r);if(i){if("horizontal"===e)return{x:i.coordinate,y:n.y};if("vertical"===e)return{x:n.x,y:i.coordinate};if("centric"===e){var o=i.coordinate,{radius:a}=n;return v(v(v({},n),(0,h.IZ)(n.cx,n.cy,a,o)),{},{angle:o,radius:a})}var l=i.coordinate,{angle:u}=n;return v(v(v({},n),(0,h.IZ)(n.cx,n.cy,l,u)),{},{angle:u,radius:l})}return{x:0,y:0}},I=(e,t)=>"horizontal"===t?e.x:"vertical"===t?e.y:"centric"===t?e.angle:e.radius},9901:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(4373),i=r(4664);t.range=function(e,t,r){r&&"number"!=typeof r&&n.isIterateeCall(e,t,r)&&(t=r=void 0),e=i.toFinite(e),void 0===t?(t=e,e=0):t=i.toFinite(t),r=void 0===r?e{"use strict";r.d(t,{A:()=>u});var n=r(2115);let i=e=>{let t=e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,r)=>r?r.toUpperCase():t.toLowerCase());return t.charAt(0).toUpperCase()+t.slice(1)},o=function(){for(var e=arguments.length,t=Array(e),r=0;r!!e&&""!==e.trim()&&r.indexOf(e)===t).join(" ").trim()};var a={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};let l=(0,n.forwardRef)((e,t)=>{let{color:r="currentColor",size:i=24,strokeWidth:l=2,absoluteStrokeWidth:u,className:s="",children:c,iconNode:f,...d}=e;return(0,n.createElement)("svg",{ref:t,...a,width:i,height:i,stroke:r,strokeWidth:u?24*Number(l)/Number(i):l,className:o("lucide",s),...!c&&!(e=>{for(let t in e)if(t.startsWith("aria-")||"role"===t||"title"===t)return!0})(d)&&{"aria-hidden":"true"},...d},[...f.map(e=>{let[t,r]=e;return(0,n.createElement)(t,r)}),...Array.isArray(c)?c:[c]])}),u=(e,t)=>{let r=(0,n.forwardRef)((r,a)=>{let{className:u,...s}=r;return(0,n.createElement)(l,{ref:a,iconNode:t,className:o("lucide-".concat(i(e).replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()),"lucide-".concat(e),u),...s})});return r.displayName=i(e),r}}}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/964-69097a61540f27b4.js b/keploy/pkg/service/load/out/_next/static/chunks/964-69097a61540f27b4.js new file mode 100644 index 0000000..58ae106 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/964-69097a61540f27b4.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[964],{89:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"useRouterBFCache",{enumerable:!0,get:function(){return u}});let n=r(2115);function u(e,t){let[r,u]=(0,n.useState)(()=>({tree:e,stateKey:t,next:null}));if(r.tree===e)return r;let l={tree:e,stateKey:t,next:null},o=1,a=r,i=l;for(;null!==a&&o<1;){if(a.stateKey===t){i.next=a.next;break}{o++;let e={tree:a.tree,stateKey:a.stateKey,next:null};i.next=e,i=e}a=a.next}return u(l),l}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},214:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizePathTrailingSlash",{enumerable:!0,get:function(){return l}});let n=r(6361),u=r(427),l=e=>{if(!e.startsWith("/"))return e;let{pathname:t,query:r,hash:l}=(0,u.parsePath)(e);return/\.[^/]+\/?$/.test(t)?""+(0,n.removeTrailingSlash)(t)+r+l:t.endsWith("/")?""+t+r+l:t+"/"+r+l};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},427:(e,t)=>{"use strict";function r(e){let t=e.indexOf("#"),r=e.indexOf("?"),n=r>-1&&(t<0||r-1?{pathname:e.substring(0,n?r:t),query:n?e.substring(r,t>-1?t:void 0):"",hash:t>-1?e.slice(t):""}:{pathname:e,query:"",hash:""}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"parsePath",{enumerable:!0,get:function(){return r}})},589:(e,t)=>{"use strict";function r(e){return e.split("/").map(e=>encodeURIComponent(e)).join("/")}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"encodeURIPath",{enumerable:!0,get:function(){return r}})},666:e=>{!function(){var t={229:function(e){var t,r,n,u=e.exports={};function l(){throw Error("setTimeout has not been defined")}function o(){throw Error("clearTimeout has not been defined")}try{t="function"==typeof setTimeout?setTimeout:l}catch(e){t=l}try{r="function"==typeof clearTimeout?clearTimeout:o}catch(e){r=o}function a(e){if(t===setTimeout)return setTimeout(e,0);if((t===l||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(r){try{return t.call(null,e,0)}catch(r){return t.call(this,e,0)}}}var i=[],c=!1,s=-1;function f(){c&&n&&(c=!1,n.length?i=n.concat(i):s=-1,i.length&&d())}function d(){if(!c){var e=a(f);c=!0;for(var t=i.length;t;){for(n=i,i=[];++s1)for(var r=1;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{RedirectBoundary:function(){return f},RedirectErrorBoundary:function(){return s}});let n=r(6966),u=r(5155),l=n._(r(2115)),o=r(8999),a=r(6825),i=r(2210);function c(e){let{redirect:t,reset:r,redirectType:n}=e,u=(0,o.useRouter)();return(0,l.useEffect)(()=>{l.default.startTransition(()=>{n===i.RedirectType.push?u.push(t,{}):u.replace(t,{}),r()})},[t,n,r,u]),null}class s extends l.default.Component{static getDerivedStateFromError(e){if((0,i.isRedirectError)(e))return{redirect:(0,a.getURLFromRedirectError)(e),redirectType:(0,a.getRedirectTypeFromError)(e)};throw e}render(){let{redirect:e,redirectType:t}=this.state;return null!==e&&null!==t?(0,u.jsx)(c,{redirect:e,redirectType:t,reset:()=>this.setState({redirect:null})}):this.props.children}constructor(e){super(e),this.state={redirect:null,redirectType:null}}}function f(e){let{children:t}=e,r=(0,o.useRouter)();return(0,u.jsx)(s,{router:r,children:t})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},708:(e,t)=>{"use strict";function r(e){return Array.isArray(e)?e[1]:e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getSegmentValue",{enumerable:!0,get:function(){return r}}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},774:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{HTML_LIMITED_BOT_UA_RE:function(){return n.HTML_LIMITED_BOT_UA_RE},HTML_LIMITED_BOT_UA_RE_STRING:function(){return l},getBotType:function(){return i},isBot:function(){return a}});let n=r(5072),u=/google/i,l=n.HTML_LIMITED_BOT_UA_RE.source;function o(e){return n.HTML_LIMITED_BOT_UA_RE.test(e)}function a(e){return u.test(e)||o(e)}function i(e){return u.test(e)?"dom":o(e)?"html":void 0}},878:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"applyFlightData",{enumerable:!0,get:function(){return l}});let n=r(4758),u=r(3118);function l(e,t,r,l,o){let{tree:a,seedData:i,head:c,isRootRender:s}=l;if(null===i)return!1;if(s){let u=i[1];r.loading=i[3],r.rsc=u,r.prefetchRsc=null,(0,n.fillLazyItemsTillLeafWithHead)(e,r,t,a,i,c,o)}else r.rsc=t.rsc,r.prefetchRsc=t.prefetchRsc,r.parallelRoutes=new Map(t.parallelRoutes),r.loading=t.loading,(0,u.fillCacheWithNewSubTreeData)(e,r,t,l,o);return!0}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},886:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{PathParamsContext:function(){return o},PathnameContext:function(){return l},SearchParamsContext:function(){return u}});let n=r(2115),u=(0,n.createContext)(null),l=(0,n.createContext)(null),o=(0,n.createContext)(null)},894:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ClientPageRoot",{enumerable:!0,get:function(){return u}});let n=r(5155);function u(e){let{Component:t,searchParams:u,params:l,promises:o}=e;{let{createRenderSearchParamsFromClient:e}=r(7205),o=e(u),{createRenderParamsFromClient:a}=r(3558),i=a(l);return(0,n.jsx)(t,{params:i,searchParams:o})}}r(9837),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1027:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{dispatchAppRouterAction:function(){return o},useActionQueue:function(){return a}});let n=r(6966)._(r(2115)),u=r(5122),l=null;function o(e){if(null===l)throw Object.defineProperty(Error("Internal Next.js error: Router action dispatched before initialization."),"__NEXT_ERROR_CODE",{value:"E668",enumerable:!1,configurable:!0});l(e)}function a(e){let[t,r]=n.default.useState(e.state);return l=t=>e.dispatch(t,r),(0,u.isThenable)(t)?(0,n.use)(t):t}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1127:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"matchSegment",{enumerable:!0,get:function(){return r}});let r=(e,t)=>"string"==typeof e?"string"==typeof t&&e===t:"string"!=typeof t&&e[0]===t[0]&&e[1]===t[1];("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1139:(e,t)=>{"use strict";function r(e,t){return void 0===t&&(t=!0),e.pathname+e.search+(t?e.hash:"")}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createHrefFromUrl",{enumerable:!0,get:function(){return r}}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1295:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return a}});let n=r(6966),u=r(5155),l=n._(r(2115)),o=r(5227);function a(){let e=(0,l.useContext)(o.TemplateContext);return(0,u.jsx)(u.Fragment,{children:e})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1315:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"assignLocation",{enumerable:!0,get:function(){return u}});let n=r(5929);function u(e,t){if(e.startsWith(".")){let r=t.origin+t.pathname;return new URL((r.endsWith("/")?r:r+"/")+e)}return new URL((0,n.addBasePath)(e),t.href)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1408:(e,t,r)=>{"use strict";e.exports=r(9393)},1426:(e,t,r)=>{"use strict";var n=r(9509),u=Symbol.for("react.transitional.element"),l=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),c=Symbol.for("react.consumer"),s=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),d=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),h=Symbol.for("react.lazy"),_=Symbol.iterator,y={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},b=Object.assign,v={};function g(e,t,r){this.props=e,this.context=t,this.refs=v,this.updater=r||y}function m(){}function R(e,t,r){this.props=e,this.context=t,this.refs=v,this.updater=r||y}g.prototype.isReactComponent={},g.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},g.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},m.prototype=g.prototype;var E=R.prototype=new m;E.constructor=R,b(E,g.prototype),E.isPureReactComponent=!0;var O=Array.isArray;function P(){}var j={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function S(e,t,r,n,l,o){return{$$typeof:u,type:e,key:t,ref:void 0!==(r=o.ref)?r:null,props:o}}function M(e){return"object"==typeof e&&null!==e&&e.$$typeof===u}var w=/\/+/g;function x(e,t){var r,n;return"object"==typeof e&&null!==e&&null!=e.key?(r=""+e.key,n={"=":"=0",":":"=2"},"$"+r.replace(/[=:]/g,function(e){return n[e]})):t.toString(36)}function C(e,t,r){if(null==e)return e;var n=[],o=0;return!function e(t,r,n,o,a){var i,c,s,f=typeof t;("undefined"===f||"boolean"===f)&&(t=null);var d=!1;if(null===t)d=!0;else switch(f){case"bigint":case"string":case"number":d=!0;break;case"object":switch(t.$$typeof){case u:case l:d=!0;break;case h:return e((d=t._init)(t._payload),r,n,o,a)}}if(d)return a=a(t),d=""===o?"."+x(t,0):o,O(a)?(n="",null!=d&&(n=d.replace(w,"$&/")+"/"),e(a,r,n,"",function(e){return e})):null!=a&&(M(a)&&(i=a,c=n+(null==a.key||t&&t.key===a.key?"":(""+a.key).replace(w,"$&/")+"/")+d,a=S(i.type,c,void 0,void 0,void 0,i.props)),r.push(a)),1;d=0;var p=""===o?".":o+":";if(O(t))for(var y=0;y{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{DYNAMIC_STALETIME_MS:function(){return d},STATIC_STALETIME_MS:function(){return p},createSeededPrefetchCacheEntry:function(){return c},getOrCreatePrefetchCacheEntry:function(){return i},prunePrefetchCache:function(){return f}});let n=r(8586),u=r(9818),l=r(9154);function o(e,t,r){let n=e.pathname;return(t&&(n+=e.search),r)?""+r+"%"+n:n}function a(e,t,r){return o(e,t===u.PrefetchKind.FULL,r)}function i(e){let{url:t,nextUrl:r,tree:n,prefetchCache:l,kind:a,allowAliasing:i=!0}=e,c=function(e,t,r,n,l){for(let a of(void 0===t&&(t=u.PrefetchKind.TEMPORARY),[r,null])){let r=o(e,!0,a),i=o(e,!1,a),c=e.search?r:i,s=n.get(c);if(s&&l){if(s.url.pathname===e.pathname&&s.url.search!==e.search)return{...s,aliased:!0};return s}let f=n.get(i);if(l&&e.search&&t!==u.PrefetchKind.FULL&&f&&!f.key.includes("%"))return{...f,aliased:!0}}if(t!==u.PrefetchKind.FULL&&l){for(let t of n.values())if(t.url.pathname===e.pathname&&!t.key.includes("%"))return{...t,aliased:!0}}}(t,a,r,l,i);return c?(c.status=h(c),c.kind!==u.PrefetchKind.FULL&&a===u.PrefetchKind.FULL&&c.data.then(e=>{if(!(Array.isArray(e.flightData)&&e.flightData.some(e=>e.isRootRender&&null!==e.seedData)))return s({tree:n,url:t,nextUrl:r,prefetchCache:l,kind:null!=a?a:u.PrefetchKind.TEMPORARY})}),a&&c.kind===u.PrefetchKind.TEMPORARY&&(c.kind=a),c):s({tree:n,url:t,nextUrl:r,prefetchCache:l,kind:a||u.PrefetchKind.TEMPORARY})}function c(e){let{nextUrl:t,tree:r,prefetchCache:n,url:l,data:o,kind:i}=e,c=o.couldBeIntercepted?a(l,i,t):a(l,i),s={treeAtTimeOfPrefetch:r,data:Promise.resolve(o),kind:i,prefetchTime:Date.now(),lastUsedTime:Date.now(),staleTime:o.staleTime,key:c,status:u.PrefetchCacheEntryStatus.fresh,url:l};return n.set(c,s),s}function s(e){let{url:t,kind:r,tree:o,nextUrl:i,prefetchCache:c}=e,s=a(t,r),f=l.prefetchQueue.enqueue(()=>(0,n.fetchServerResponse)(t,{flightRouterState:o,nextUrl:i,prefetchKind:r}).then(e=>{let r;if(e.couldBeIntercepted&&(r=function(e){let{url:t,nextUrl:r,prefetchCache:n,existingCacheKey:u}=e,l=n.get(u);if(!l)return;let o=a(t,l.kind,r);return n.set(o,{...l,key:o}),n.delete(u),o}({url:t,existingCacheKey:s,nextUrl:i,prefetchCache:c})),e.prerendered){let t=c.get(null!=r?r:s);t&&(t.kind=u.PrefetchKind.FULL,-1!==e.staleTime&&(t.staleTime=e.staleTime))}return e})),d={treeAtTimeOfPrefetch:o,data:f,kind:r,prefetchTime:Date.now(),lastUsedTime:null,staleTime:-1,key:s,status:u.PrefetchCacheEntryStatus.fresh,url:t};return c.set(s,d),d}function f(e){for(let[t,r]of e)h(r)===u.PrefetchCacheEntryStatus.expired&&e.delete(t)}let d=1e3*Number("0"),p=1e3*Number("300");function h(e){let{kind:t,prefetchTime:r,lastUsedTime:n,staleTime:l}=e;return -1!==l?Date.now(){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"reportGlobalError",{enumerable:!0,get:function(){return r}});let r="function"==typeof reportError?reportError:e=>{globalThis.console.error(e)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1747:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"pathHasPrefix",{enumerable:!0,get:function(){return u}});let n=r(427);function u(e,t){if("string"!=typeof e)return!1;let{pathname:r}=(0,n.parsePath)(e);return r===t||r.startsWith(t+"/")}},1799:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HandleISRError",{enumerable:!0,get:function(){return n}});let r=void 0;function n(e){let{error:t}=e;if(r){let e=r.getStore();if((null==e?void 0:e.isRevalidate)||(null==e?void 0:e.isStaticGeneration))throw console.error(t),t}return null}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1818:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"findSourceMapURL",{enumerable:!0,get:function(){return r}});let r=void 0;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1822:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"unresolvedThenable",{enumerable:!0,get:function(){return r}});let r={then:()=>{}};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2004:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"invalidateCacheByRouterState",{enumerable:!0,get:function(){return u}});let n=r(5637);function u(e,t,r){for(let u in r[1]){let l=r[1][u][0],o=(0,n.createRouterCacheKey)(l),a=t.parallelRoutes.get(u);if(a){let t=new Map(a);t.delete(o),e.parallelRoutes.set(u,t)}}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2115:(e,t,r)=>{"use strict";e.exports=r(1426)},2210:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{REDIRECT_ERROR_CODE:function(){return u},RedirectType:function(){return l},isRedirectError:function(){return o}});let n=r(4420),u="NEXT_REDIRECT";var l=function(e){return e.push="push",e.replace="replace",e}({});function o(e){if("object"!=typeof e||null===e||!("digest"in e)||"string"!=typeof e.digest)return!1;let t=e.digest.split(";"),[r,l]=t,o=t.slice(2,-2).join(";"),a=Number(t.at(-2));return r===u&&("replace"===l||"push"===l)&&"string"==typeof o&&!isNaN(a)&&a in n.RedirectStatusCode}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2223:(e,t)=>{"use strict";function r(e,t){var r=e.length;for(e.push(t);0>>1,u=e[n];if(0>>1;nl(i,r))cl(s,i)?(e[n]=s,e[c]=r,n=c):(e[n]=i,e[a]=r,n=a);else if(cl(s,r))e[n]=s,e[c]=r,n=c;else break}}return t}function l(e,t){var r=e.sortIndex-t.sortIndex;return 0!==r?r:e.id-t.id}if(t.unstable_now=void 0,"object"==typeof performance&&"function"==typeof performance.now){var o,a=performance;t.unstable_now=function(){return a.now()}}else{var i=Date,c=i.now();t.unstable_now=function(){return i.now()-c}}var s=[],f=[],d=1,p=null,h=3,_=!1,y=!1,b=!1,v=!1,g="function"==typeof setTimeout?setTimeout:null,m="function"==typeof clearTimeout?clearTimeout:null,R="undefined"!=typeof setImmediate?setImmediate:null;function E(e){for(var t=n(f);null!==t;){if(null===t.callback)u(f);else if(t.startTime<=e)u(f),t.sortIndex=t.expirationTime,r(s,t);else break;t=n(f)}}function O(e){if(b=!1,E(e),!y)if(null!==n(s))y=!0,P||(P=!0,o());else{var t=n(f);null!==t&&A(O,t.startTime-e)}}var P=!1,j=-1,T=5,S=-1;function M(){return!!v||!(t.unstable_now()-Se&&M());){var a=p.callback;if("function"==typeof a){p.callback=null,h=p.priorityLevel;var i=a(p.expirationTime<=e);if(e=t.unstable_now(),"function"==typeof i){p.callback=i,E(e),r=!0;break t}p===n(s)&&u(s),E(e)}else u(s);p=n(s)}if(null!==p)r=!0;else{var c=n(f);null!==c&&A(O,c.startTime-e),r=!1}}break e}finally{p=null,h=l,_=!1}}}finally{r?o():P=!1}}}if("function"==typeof R)o=function(){R(w)};else if("undefined"!=typeof MessageChannel){var x=new MessageChannel,C=x.port2;x.port1.onmessage=w,o=function(){C.postMessage(null)}}else o=function(){g(w,0)};function A(e,r){j=g(function(){e(t.unstable_now())},r)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_forceFrameRate=function(e){0>e||125a?(e.sortIndex=l,r(f,e),null===n(s)&&e===n(f)&&(b?(m(j),j=-1):b=!0,A(O,l-a))):(e.sortIndex=i,r(s,e),y||_||(y=!0,P||(P=!0,o()))),e},t.unstable_shouldYield=M,t.unstable_wrapCallback=function(e){var t=h;return function(){var r=h;h=t;try{return e.apply(this,arguments)}finally{h=r}}}},2312:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"PromiseQueue",{enumerable:!0,get:function(){return c}});let n=r(5952),u=r(6420);var l=u._("_maxConcurrency"),o=u._("_runningCount"),a=u._("_queue"),i=u._("_processNext");class c{enqueue(e){let t,r,u=new Promise((e,n)=>{t=e,r=n}),l=async()=>{try{n._(this,o)[o]++;let r=await e();t(r)}catch(e){r(e)}finally{n._(this,o)[o]--,n._(this,i)[i]()}};return n._(this,a)[a].push({promiseFn:u,task:l}),n._(this,i)[i](),u}bump(e){let t=n._(this,a)[a].findIndex(t=>t.promiseFn===e);if(t>-1){let e=n._(this,a)[a].splice(t,1)[0];n._(this,a)[a].unshift(e),n._(this,i)[i](!0)}}constructor(e=5){Object.defineProperty(this,i,{value:s}),Object.defineProperty(this,l,{writable:!0,value:void 0}),Object.defineProperty(this,o,{writable:!0,value:void 0}),Object.defineProperty(this,a,{writable:!0,value:void 0}),n._(this,l)[l]=e,n._(this,o)[o]=0,n._(this,a)[a]=[]}}function s(e){if(void 0===e&&(e=!1),(n._(this,o)[o]0){var t;null==(t=n._(this,a)[a].shift())||t.task()}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2561:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getFlightDataPartsFromPath:function(){return u},getNextFlightSegmentPath:function(){return l},normalizeFlightData:function(){return o},prepareFlightRouterStateForRequest:function(){return a}});let n=r(8291);function u(e){var t;let[r,n,u,l]=e.slice(-4),o=e.slice(0,-4);return{pathToSegment:o.slice(0,-1),segmentPath:o,segment:null!=(t=o[o.length-1])?t:"",tree:r,seedData:n,head:u,isHeadPartial:l,isRootRender:4===e.length}}function l(e){return e.slice(2)}function o(e){return"string"==typeof e?e:e.map(u)}function a(e,t){return t?encodeURIComponent(JSON.stringify(e)):encodeURIComponent(JSON.stringify(function e(t){var r,u;let[l,o,a,i,c,s]=t,f="string"==typeof(r=l)&&r.startsWith(n.PAGE_SEGMENT_KEY+"?")?n.PAGE_SEGMENT_KEY:r,d={};for(let[t,r]of Object.entries(o))d[t]=e(r);let p=[f,d,null,(u=i)&&"refresh"!==u?i:null];return void 0!==c&&(p[4]=c),void 0!==s&&(p[5]=s),p}(e)))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2669:(e,t,r)=>{"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=r(9248)},2691:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"findHeadInCache",{enumerable:!0,get:function(){return l}});let n=r(8291),u=r(5637);function l(e,t){return function e(t,r,l){if(0===Object.keys(r).length)return[t,l];let o=Object.keys(r).filter(e=>"children"!==e);for(let a of("children"in r&&o.unshift("children"),o)){let[o,i]=r[a];if(o===n.DEFAULT_SEGMENT_KEY)continue;let c=t.parallelRoutes.get(a);if(!c)continue;let s=(0,u.createRouterCacheKey)(o),f=c.get(s);if(!f)continue;let d=e(f,i,l+"/"+s);if(d)return d}return null}(e,t,"")}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2816:(e,t)=>{"use strict";function r(e){let t=parseInt(e.slice(0,2),16),r=t>>1&63,n=Array(6);for(let e=0;e<6;e++){let t=r>>5-e&1;n[e]=1===t}return{type:1==(t>>7&1)?"use-cache":"server-action",usedArgs:n,hasRestArgs:1==(1&t)}}function n(e,t){let r=Array(e.length);for(let n=0;n=6&&t.hasRestArgs)&&(r[n]=e[n]);return r}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{extractInfoFromServerReferenceId:function(){return r},omitUnusedArgs:function(){return n}})},2830:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HeadManagerContext",{enumerable:!0,get:function(){return n}});let n=r(8229)._(r(2115)).default.createContext({})},2858:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isNextRouterError",{enumerable:!0,get:function(){return l}});let n=r(6494),u=r(2210);function l(e){return(0,u.isRedirectError)(e)||(0,n.isHTTPAccessFallbackError)(e)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3118:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{fillCacheWithNewSubTreeData:function(){return i},fillCacheWithNewSubTreeDataButOnlyLoading:function(){return c}});let n=r(2004),u=r(4758),l=r(5637),o=r(8291);function a(e,t,r,a,i,c){let{segmentPath:s,seedData:f,tree:d,head:p}=a,h=t,_=r;for(let t=0;t{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"warnOnce",{enumerable:!0,get:function(){return r}});let r=e=>{}},3269:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ACTION_HEADER:function(){return n},FLIGHT_HEADERS:function(){return f},NEXT_ACTION_NOT_FOUND_HEADER:function(){return v},NEXT_DID_POSTPONE_HEADER:function(){return h},NEXT_HMR_REFRESH_HASH_COOKIE:function(){return i},NEXT_HMR_REFRESH_HEADER:function(){return a},NEXT_IS_PRERENDER_HEADER:function(){return b},NEXT_REWRITTEN_PATH_HEADER:function(){return _},NEXT_REWRITTEN_QUERY_HEADER:function(){return y},NEXT_ROUTER_PREFETCH_HEADER:function(){return l},NEXT_ROUTER_SEGMENT_PREFETCH_HEADER:function(){return o},NEXT_ROUTER_STALE_TIME_HEADER:function(){return p},NEXT_ROUTER_STATE_TREE_HEADER:function(){return u},NEXT_RSC_UNION_QUERY:function(){return d},NEXT_URL:function(){return c},RSC_CONTENT_TYPE_HEADER:function(){return s},RSC_HEADER:function(){return r}});let r="RSC",n="Next-Action",u="Next-Router-State-Tree",l="Next-Router-Prefetch",o="Next-Router-Segment-Prefetch",a="Next-HMR-Refresh",i="__next_hmr_refresh_hash__",c="Next-Url",s="text/x-component",f=[r,u,l,a,o],d="_rsc",p="x-nextjs-stale-time",h="x-nextjs-postponed",_="x-nextjs-rewritten-path",y="x-nextjs-rewritten-query",b="x-nextjs-prerender",v="x-nextjs-action-not-found";("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3507:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"handleMutable",{enumerable:!0,get:function(){return l}});let n=r(8946);function u(e){return void 0!==e}function l(e,t){var r,l;let o=null==(r=t.shouldScroll)||r,a=e.nextUrl;if(u(t.patchedTree)){let r=(0,n.computeChangedPath)(e.tree,t.patchedTree);r?a=r:a||(a=e.canonicalUrl)}return{canonicalUrl:u(t.canonicalUrl)?t.canonicalUrl===e.canonicalUrl?e.canonicalUrl:t.canonicalUrl:e.canonicalUrl,pushRef:{pendingPush:u(t.pendingPush)?t.pendingPush:e.pushRef.pendingPush,mpaNavigation:u(t.mpaNavigation)?t.mpaNavigation:e.pushRef.mpaNavigation,preserveCustomHistoryState:u(t.preserveCustomHistoryState)?t.preserveCustomHistoryState:e.pushRef.preserveCustomHistoryState},focusAndScrollRef:{apply:!!o&&(!!u(null==t?void 0:t.scrollableSegments)||e.focusAndScrollRef.apply),onlyHashChange:t.onlyHashChange||!1,hashFragment:o?t.hashFragment&&""!==t.hashFragment?decodeURIComponent(t.hashFragment.slice(1)):e.focusAndScrollRef.hashFragment:null,segmentPaths:o?null!=(l=null==t?void 0:t.scrollableSegments)?l:e.focusAndScrollRef.segmentPaths:[]},cache:t.cache?t.cache:e.cache,prefetchCache:t.prefetchCache?t.prefetchCache:e.prefetchCache,tree:u(t.patchedTree)?t.patchedTree:e.tree,nextUrl:a}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3558:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createRenderParamsFromClient",{enumerable:!0,get:function(){return n}});let n=r(7829).createRenderParamsFromClient;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3567:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createInitialRouterState",{enumerable:!0,get:function(){return s}});let n=r(1139),u=r(4758),l=r(8946),o=r(1518),a=r(9818),i=r(4908),c=r(2561);function s(e){var t,r;let{navigatedAt:s,initialFlightData:f,initialCanonicalUrlParts:d,initialParallelRoutes:p,location:h,couldBeIntercepted:_,postponed:y,prerendered:b}=e,v=d.join("/"),g=(0,c.getFlightDataPartsFromPath)(f[0]),{tree:m,seedData:R,head:E}=g,O={lazyData:null,rsc:null==R?void 0:R[1],prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:p,loading:null!=(t=null==R?void 0:R[3])?t:null,navigatedAt:s},P=h?(0,n.createHrefFromUrl)(h):v;(0,i.addRefreshMarkerToActiveParallelSegments)(m,P);let j=new Map;(null===p||0===p.size)&&(0,u.fillLazyItemsTillLeafWithHead)(s,O,void 0,m,R,E,void 0);let T={tree:m,cache:O,prefetchCache:j,pushRef:{pendingPush:!1,mpaNavigation:!1,preserveCustomHistoryState:!0},focusAndScrollRef:{apply:!1,onlyHashChange:!1,hashFragment:null,segmentPaths:[]},canonicalUrl:P,nextUrl:null!=(r=(0,l.extractPathFromFlightRouterState)(m)||(null==h?void 0:h.pathname))?r:null};if(h){let e=new URL(""+h.pathname+h.search,h.origin);(0,o.createSeededPrefetchCacheEntry)({url:e,data:{flightData:[g],canonicalUrl:void 0,couldBeIntercepted:!!_,prerendered:b,postponed:y,staleTime:b&&1?o.STATIC_STALETIME_MS:-1},tree:T.tree,prefetchCache:T.prefetchCache,nextUrl:T.nextUrl,kind:b?a.PrefetchKind.FULL:a.PrefetchKind.AUTO})}return T}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3612:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hmrRefreshReducer",{enumerable:!0,get:function(){return n}}),r(8586),r(1139),r(7442),r(9234),r(3894),r(3507),r(878),r(6158),r(6375),r(4108);let n=function(e,t){return e};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3668:(e,t)=>{"use strict";function r(){return""}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getDeploymentIdQueryOrEmptyString",{enumerable:!0,get:function(){return r}})},3678:(e,t,r)=>{"use strict";function n(){throw Object.defineProperty(Error("`forbidden()` is experimental and only allowed to be enabled when `experimental.authInterrupts` is enabled."),"__NEXT_ERROR_CODE",{value:"E488",enumerable:!1,configurable:!0})}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"forbidden",{enumerable:!0,get:function(){return n}}),r(6494).HTTP_ERROR_FALLBACK_ERROR_CODE,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3806:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"callServer",{enumerable:!0,get:function(){return o}});let n=r(2115),u=r(9818),l=r(1027);async function o(e,t){return new Promise((r,o)=>{(0,n.startTransition)(()=>{(0,l.dispatchAppRouterAction)({type:u.ACTION_SERVER_ACTION,actionId:e,actionArgs:t,resolve:r,reject:o})})})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3838:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(6446),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3894:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{handleExternalUrl:function(){return g},navigateReducer:function(){return function e(t,r){let{url:R,isExternalUrl:E,navigateType:O,shouldScroll:P,allowAliasing:j}=r,T={},{hash:S}=R,M=(0,u.createHrefFromUrl)(R),w="push"===O;if((0,y.prunePrefetchCache)(t.prefetchCache),T.preserveCustomHistoryState=!1,T.pendingPush=w,E)return g(t,T,R.toString(),w);if(document.getElementById("__next-page-redirect"))return g(t,T,M,w);let x=(0,y.getOrCreatePrefetchCacheEntry)({url:R,nextUrl:t.nextUrl,tree:t.tree,prefetchCache:t.prefetchCache,allowAliasing:j}),{treeAtTimeOfPrefetch:C,data:A}=x;return d.prefetchQueue.bump(A),A.then(d=>{let{flightData:y,canonicalUrl:E,postponed:O}=d,j=Date.now(),A=!1;if(x.lastUsedTime||(x.lastUsedTime=j,A=!0),x.aliased){let n=new URL(R.href);E&&(n.pathname=E.pathname);let u=(0,v.handleAliasedPrefetchEntry)(j,t,y,n,T);return!1===u?e(t,{...r,allowAliasing:!1}):u}if("string"==typeof y)return g(t,T,y,w);let N=E?(0,u.createHrefFromUrl)(E):M;if(S&&t.canonicalUrl.split("#",1)[0]===N.split("#",1)[0])return T.onlyHashChange=!0,T.canonicalUrl=N,T.shouldScroll=P,T.hashFragment=S,T.scrollableSegments=[],(0,s.handleMutable)(t,T);let D=t.tree,U=t.cache,I=[];for(let e of y){let{pathToSegment:r,seedData:u,head:s,isHeadPartial:d,isRootRender:y}=e,v=e.tree,E=["",...r],P=(0,o.applyRouterStatePatchToTree)(E,D,v,M);if(null===P&&(P=(0,o.applyRouterStatePatchToTree)(E,C,v,M)),null!==P){if(u&&y&&O){let e=(0,_.startPPRNavigation)(j,U,D,v,u,s,d,!1,I);if(null!==e){if(null===e.route)return g(t,T,M,w);P=e.route;let r=e.node;null!==r&&(T.cache=r);let u=e.dynamicRequestTree;if(null!==u){let r=(0,n.fetchServerResponse)(new URL(N,R.origin),{flightRouterState:u,nextUrl:t.nextUrl});(0,_.listenForDynamicRequest)(e,r)}}else P=v}else{if((0,i.isNavigatingToNewRootLayout)(D,P))return g(t,T,M,w);let n=(0,p.createEmptyCacheNode)(),u=!1;for(let t of(x.status!==c.PrefetchCacheEntryStatus.stale||A?u=(0,f.applyFlightData)(j,U,n,e,x):(u=function(e,t,r,n){let u=!1;for(let l of(e.rsc=t.rsc,e.prefetchRsc=t.prefetchRsc,e.loading=t.loading,e.parallelRoutes=new Map(t.parallelRoutes),m(n).map(e=>[...r,...e])))(0,b.clearCacheNodeDataForSegmentPath)(e,t,l),u=!0;return u}(n,U,r,v),x.lastUsedTime=j),(0,a.shouldHardNavigate)(E,D)?(n.rsc=U.rsc,n.prefetchRsc=U.prefetchRsc,(0,l.invalidateCacheBelowFlightSegmentPath)(n,U,r),T.cache=n):u&&(T.cache=n,U=n),m(v))){let e=[...r,...t];e[e.length-1]!==h.DEFAULT_SEGMENT_KEY&&I.push(e)}}D=P}}return T.patchedTree=D,T.canonicalUrl=N,T.scrollableSegments=I,T.hashFragment=S,T.shouldScroll=P,(0,s.handleMutable)(t,T)},()=>t)}}});let n=r(8586),u=r(1139),l=r(4466),o=r(7442),a=r(5567),i=r(9234),c=r(9818),s=r(3507),f=r(878),d=r(9154),p=r(6158),h=r(8291),_=r(4150),y=r(1518),b=r(9880),v=r(5563);function g(e,t,r,n){return t.mpaNavigation=!0,t.canonicalUrl=r,t.pendingPush=n,t.scrollableSegments=void 0,(0,s.handleMutable)(e,t)}function m(e){let t=[],[r,n]=e;if(0===Object.keys(n).length)return[[r]];for(let[e,u]of Object.entries(n))for(let n of m(u))""===r?t.push([e,...n]):t.push([r,e,...n]);return t}r(6005),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3942:(e,t)=>{"use strict";function r(e){let t=5381;for(let r=0;r>>0}function n(e){return r(e).toString(36).slice(0,5)}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{djb2Hash:function(){return r},hexHash:function(){return n}})},4074:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addPathPrefix",{enumerable:!0,get:function(){return u}});let n=r(427);function u(e,t){if(!e.startsWith("/")||!t)return e;let{pathname:r,query:u,hash:l}=(0,n.parsePath)(e);return""+t+r+u+l}},4108:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hasInterceptionRouteInCurrentTree",{enumerable:!0,get:function(){return function e(t){let[r,u]=t;if(Array.isArray(r)&&("di"===r[2]||"ci"===r[2])||"string"==typeof r&&(0,n.isInterceptionRouteAppPath)(r))return!0;if(u){for(let t in u)if(e(u[t]))return!0}return!1}}});let n=r(7755);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4150:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{abortTask:function(){return h},listenForDynamicRequest:function(){return p},startPPRNavigation:function(){return c},updateCacheNodeOnPopstateRestoration:function(){return function e(t,r){let n=r[1],u=t.parallelRoutes,o=new Map(u);for(let t in n){let r=n[t],a=r[0],i=(0,l.createRouterCacheKey)(a),c=u.get(t);if(void 0!==c){let n=c.get(i);if(void 0!==n){let u=e(n,r),l=new Map(c);l.set(i,u),o.set(t,l)}}}let a=t.rsc,i=b(a)&&"pending"===a.status;return{lazyData:null,rsc:a,head:t.head,prefetchHead:i?t.prefetchHead:[null,null],prefetchRsc:i?t.prefetchRsc:null,loading:t.loading,parallelRoutes:o,navigatedAt:t.navigatedAt}}}});let n=r(8291),u=r(1127),l=r(5637),o=r(9234),a=r(1518),i={route:null,node:null,dynamicRequestTree:null,children:null};function c(e,t,r,o,a,c,d,p,h){return function e(t,r,o,a,c,d,p,h,_,y,b){let v=o[1],g=a[1],m=null!==d?d[2]:null;c||!0===a[4]&&(c=!0);let R=r.parallelRoutes,E=new Map(R),O={},P=null,j=!1,T={};for(let r in g){let o,a=g[r],f=v[r],d=R.get(r),S=null!==m?m[r]:null,M=a[0],w=y.concat([r,M]),x=(0,l.createRouterCacheKey)(M),C=void 0!==f?f[0]:void 0,A=void 0!==d?d.get(x):void 0;if(null!==(o=M===n.DEFAULT_SEGMENT_KEY?void 0!==f?{route:f,node:null,dynamicRequestTree:null,children:null}:s(t,f,a,A,c,void 0!==S?S:null,p,h,w,b):_&&0===Object.keys(a[1]).length?s(t,f,a,A,c,void 0!==S?S:null,p,h,w,b):void 0!==f&&void 0!==C&&(0,u.matchSegment)(M,C)&&void 0!==A&&void 0!==f?e(t,A,f,a,c,S,p,h,_,w,b):s(t,f,a,A,c,void 0!==S?S:null,p,h,w,b))){if(null===o.route)return i;null===P&&(P=new Map),P.set(r,o);let e=o.node;if(null!==e){let t=new Map(d);t.set(x,e),E.set(r,t)}let t=o.route;O[r]=t;let n=o.dynamicRequestTree;null!==n?(j=!0,T[r]=n):T[r]=t}else O[r]=a,T[r]=a}if(null===P)return null;let S={lazyData:null,rsc:r.rsc,prefetchRsc:r.prefetchRsc,head:r.head,prefetchHead:r.prefetchHead,loading:r.loading,parallelRoutes:E,navigatedAt:t};return{route:f(a,O),node:S,dynamicRequestTree:j?f(a,T):null,children:P}}(e,t,r,o,!1,a,c,d,p,[],h)}function s(e,t,r,n,u,c,s,p,h,_){return!u&&(void 0===t||(0,o.isNavigatingToNewRootLayout)(t,r))?i:function e(t,r,n,u,o,i,c,s){let p,h,_,y,b=r[1],v=0===Object.keys(b).length;if(void 0!==n&&n.navigatedAt+a.DYNAMIC_STALETIME_MS>t)p=n.rsc,h=n.loading,_=n.head,y=n.navigatedAt;else if(null===u)return d(t,r,null,o,i,c,s);else if(p=u[1],h=u[3],_=v?o:null,y=t,u[4]||i&&v)return d(t,r,u,o,i,c,s);let g=null!==u?u[2]:null,m=new Map,R=void 0!==n?n.parallelRoutes:null,E=new Map(R),O={},P=!1;if(v)s.push(c);else for(let r in b){let n=b[r],u=null!==g?g[r]:null,a=null!==R?R.get(r):void 0,f=n[0],d=c.concat([r,f]),p=(0,l.createRouterCacheKey)(f),h=e(t,n,void 0!==a?a.get(p):void 0,u,o,i,d,s);m.set(r,h);let _=h.dynamicRequestTree;null!==_?(P=!0,O[r]=_):O[r]=n;let y=h.node;if(null!==y){let e=new Map;e.set(p,y),E.set(r,e)}}return{route:r,node:{lazyData:null,rsc:p,prefetchRsc:null,head:_,prefetchHead:null,loading:h,parallelRoutes:E,navigatedAt:y},dynamicRequestTree:P?f(r,O):null,children:m}}(e,r,n,c,s,p,h,_)}function f(e,t){let r=[e[0],t];return 2 in e&&(r[2]=e[2]),3 in e&&(r[3]=e[3]),4 in e&&(r[4]=e[4]),r}function d(e,t,r,n,u,o,a){let i=f(t,t[1]);return i[3]="refetch",{route:t,node:function e(t,r,n,u,o,a,i){let c=r[1],s=null!==n?n[2]:null,f=new Map;for(let r in c){let n=c[r],d=null!==s?s[r]:null,p=n[0],h=a.concat([r,p]),_=(0,l.createRouterCacheKey)(p),y=e(t,n,void 0===d?null:d,u,o,h,i),b=new Map;b.set(_,y),f.set(r,b)}let d=0===f.size;d&&i.push(a);let p=null!==n?n[1]:null,h=null!==n?n[3]:null;return{lazyData:null,parallelRoutes:f,prefetchRsc:void 0!==p?p:null,prefetchHead:d?u:[null,null],loading:void 0!==h?h:null,rsc:v(),head:d?v():null,navigatedAt:t}}(e,t,r,n,u,o,a),dynamicRequestTree:i,children:null}}function p(e,t){t.then(t=>{let{flightData:r}=t;if("string"!=typeof r){for(let t of r){let{segmentPath:r,tree:n,seedData:o,head:a}=t;o&&function(e,t,r,n,o){let a=e;for(let e=0;e{h(e,t)})}function h(e,t){let r=e.node;if(null===r)return;let n=e.children;if(null===n)_(e.route,r,t);else for(let e of n.values())h(e,t);e.dynamicRequestTree=null}function _(e,t,r){let n=e[1],u=t.parallelRoutes;for(let e in n){let t=n[e],o=u.get(e);if(void 0===o)continue;let a=t[0],i=(0,l.createRouterCacheKey)(a),c=o.get(i);void 0!==c&&_(t,c,r)}let o=t.rsc;b(o)&&(null===r?o.resolve(null):o.reject(r));let a=t.head;b(a)&&a.resolve(null)}let y=Symbol();function b(e){return e&&e.tag===y}function v(){let e,t,r=new Promise((r,n)=>{e=r,t=n});return r.status="pending",r.resolve=t=>{"pending"===r.status&&(r.status="fulfilled",r.value=t,e(t))},r.reject=e=>{"pending"===r.status&&(r.status="rejected",r.reason=e,t(e))},r.tag=y,r}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4340:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{GracefulDegradeBoundary:function(){return l},default:function(){return o}});let n=r(5155),u=r(2115);class l extends u.Component{static getDerivedStateFromError(e){return{hasError:!0}}componentDidMount(){let e=this.htmlRef.current;this.state.hasError&&e&&Object.entries(this.htmlAttributes).forEach(t=>{let[r,n]=t;e.setAttribute(r,n)})}render(){let{hasError:e}=this.state;return(this.rootHtml||(this.rootHtml=document.documentElement.innerHTML,this.htmlAttributes=function(e){let t={};for(let r=0;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"RedirectStatusCode",{enumerable:!0,get:function(){return r}});var r=function(e){return e[e.SeeOther=303]="SeeOther",e[e.TemporaryRedirect=307]="TemporaryRedirect",e[e.PermanentRedirect=308]="PermanentRedirect",e}({});("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4466:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"invalidateCacheBelowFlightSegmentPath",{enumerable:!0,get:function(){return function e(t,r,l){let o=l.length<=2,[a,i]=l,c=(0,n.createRouterCacheKey)(i),s=r.parallelRoutes.get(a);if(!s)return;let f=t.parallelRoutes.get(a);if(f&&f!==s||(f=new Map(s),t.parallelRoutes.set(a,f)),o)return void f.delete(c);let d=s.get(c),p=f.get(c);p&&d&&(p===d&&(p={lazyData:p.lazyData,rsc:p.rsc,prefetchRsc:p.prefetchRsc,head:p.head,prefetchHead:p.prefetchHead,parallelRoutes:new Map(p.parallelRoutes)},f.set(c,p)),e(p,d,(0,u.getNextFlightSegmentPath)(l)))}}});let n=r(5637),u=r(2561);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4486:(e,t,r)=>{"use strict";let n,u;Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hydrate",{enumerable:!0,get:function(){return U}});let l=r(8229),o=r(6966),a=r(5155);r(3838);let i=l._(r(2669)),c=o._(r(2115)),s=r(7197),f=r(2830),d=r(6698),p=r(9155),h=r(3806),_=r(1818),y=r(6634),b=l._(r(6158)),v=r(3567);r(5227);let g=r(5624),m=r(774),R=s.createFromReadableStream,E=document,O=new TextEncoder,P=!1,j=!1,T=null;function S(e){if(0===e[0])n=[];else if(1===e[0]){if(!n)throw Object.defineProperty(Error("Unexpected server data: missing bootstrap script."),"__NEXT_ERROR_CODE",{value:"E18",enumerable:!1,configurable:!0});u?u.enqueue(O.encode(e[1])):n.push(e[1])}else if(2===e[0])T=e[1];else if(3===e[0]){if(!n)throw Object.defineProperty(Error("Unexpected server data: missing bootstrap script."),"__NEXT_ERROR_CODE",{value:"E18",enumerable:!1,configurable:!0});let r=atob(e[1]),l=new Uint8Array(r.length);for(var t=0;t{e.enqueue("string"==typeof t?O.encode(t):t)}),P&&!j)&&(null===e.desiredSize||e.desiredSize<0?e.error(Object.defineProperty(Error("The connection to the page was unexpectedly closed, possibly due to the stop button being clicked, loss of Wi-Fi, or an unstable internet connection."),"__NEXT_ERROR_CODE",{value:"E117",enumerable:!1,configurable:!0})):e.close(),j=!0,n=void 0),u=e}}),{callServer:h.callServer,findSourceMapURL:_.findSourceMapURL});function C(e){let{pendingActionQueue:t}=e,r=(0,c.use)(x),n=(0,c.use)(t);return(0,a.jsx)(b.default,{gracefullyDegrade:(0,m.isBot)(window.navigator.userAgent),actionQueue:n,globalErrorState:r.G,assetPrefix:r.p})}let A=c.default.StrictMode;function N(e){let{children:t}=e;return t}let D={onDefaultTransitionIndicator:function(){return()=>{}},onRecoverableError:d.onRecoverableError,onCaughtError:p.onCaughtError,onUncaughtError:p.onUncaughtError};function U(e){let t=new Promise((t,r)=>{x.then(r=>{(0,g.setAppBuildId)(r.b);let n=Date.now();t((0,y.createMutableActionQueue)((0,v.createInitialRouterState)({navigatedAt:n,initialFlightData:r.f,initialCanonicalUrlParts:r.c,initialParallelRoutes:new Map,location:window.location,couldBeIntercepted:r.i,postponed:r.s,prerendered:r.S}),e))},e=>r(e))}),r=(0,a.jsx)(A,{children:(0,a.jsx)(f.HeadManagerContext.Provider,{value:{appDir:!0},children:(0,a.jsx)(N,{children:(0,a.jsx)(C,{pendingActionQueue:t})})})});"__next_error__"===document.documentElement.id?i.default.createRoot(E,D).render(r):c.default.startTransition(()=>{i.default.hydrateRoot(E,r,{...D,formState:T})})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4758:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"fillLazyItemsTillLeafWithHead",{enumerable:!0,get:function(){return function e(t,r,l,o,a,i,c){if(0===Object.keys(o[1]).length){r.head=i;return}for(let s in o[1]){let f,d=o[1][s],p=d[0],h=(0,n.createRouterCacheKey)(p),_=null!==a&&void 0!==a[2][s]?a[2][s]:null;if(l){let n=l.parallelRoutes.get(s);if(n){let l,o=(null==c?void 0:c.kind)==="auto"&&c.status===u.PrefetchCacheEntryStatus.reusable,a=new Map(n),f=a.get(h);l=null!==_?{lazyData:null,rsc:_[1],prefetchRsc:null,head:null,prefetchHead:null,loading:_[3],parallelRoutes:new Map(null==f?void 0:f.parallelRoutes),navigatedAt:t}:o&&f?{lazyData:f.lazyData,rsc:f.rsc,prefetchRsc:f.prefetchRsc,head:f.head,prefetchHead:f.prefetchHead,parallelRoutes:new Map(f.parallelRoutes),loading:f.loading}:{lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map(null==f?void 0:f.parallelRoutes),loading:null,navigatedAt:t},a.set(h,l),e(t,l,f,d,_||null,i,c),r.parallelRoutes.set(s,a);continue}}if(null!==_){let e=_[1],r=_[3];f={lazyData:null,rsc:e,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:r,navigatedAt:t}}else f={lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:t};let y=r.parallelRoutes.get(s);y?y.set(h,f):r.parallelRoutes.set(s,new Map([[h,f]])),e(t,f,void 0,d,_,i,c)}}}});let n=r(5637),u=r(9818);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4819:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"restoreReducer",{enumerable:!0,get:function(){return l}});let n=r(1139),u=r(8946);function l(e,t){var r;let{url:l,tree:o}=t,a=(0,n.createHrefFromUrl)(l),i=o||e.tree,c=e.cache;return{canonicalUrl:a,pushRef:{pendingPush:!1,mpaNavigation:!1,preserveCustomHistoryState:!0},focusAndScrollRef:e.focusAndScrollRef,cache:c,prefetchCache:e.prefetchCache,tree:i,nextUrl:null!=(r=(0,u.extractPathFromFlightRouterState)(i))?r:l.pathname}}r(4150),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4882:(e,t,r)=>{"use strict";function n(e){return e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeBasePath",{enumerable:!0,get:function(){return n}}),r(7102),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4908:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{addRefreshMarkerToActiveParallelSegments:function(){return function e(t,r){let[n,u,,o]=t;for(let a in n.includes(l.PAGE_SEGMENT_KEY)&&"refresh"!==o&&(t[2]=r,t[3]="refresh"),u)e(u[a],r)}},refreshInactiveParallelSegments:function(){return o}});let n=r(878),u=r(8586),l=r(8291);async function o(e){let t=new Set;await a({...e,rootTree:e.updatedTree,fetchedSegments:t})}async function a(e){let{navigatedAt:t,state:r,updatedTree:l,updatedCache:o,includeNextUrl:i,fetchedSegments:c,rootTree:s=l,canonicalUrl:f}=e,[,d,p,h]=l,_=[];if(p&&p!==f&&"refresh"===h&&!c.has(p)){c.add(p);let e=(0,u.fetchServerResponse)(new URL(p,location.origin),{flightRouterState:[s[0],s[1],s[2],"refetch"],nextUrl:i?r.nextUrl:null}).then(e=>{let{flightData:r}=e;if("string"!=typeof r)for(let e of r)(0,n.applyFlightData)(t,o,o,e)});_.push(e)}for(let e in d){let n=a({navigatedAt:t,state:r,updatedTree:d[e],updatedCache:o,includeNextUrl:i,fetchedSegments:c,rootTree:s,canonicalUrl:f});_.push(n)}await Promise.all(_)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4911:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"AsyncMetadataOutlet",{enumerable:!0,get:function(){return o}});let n=r(5155),u=r(2115);function l(e){let{promise:t}=e,{error:r,digest:n}=(0,u.use)(t);if(r)throw n&&(r.digest=n),r;return null}function o(e){let{promise:t}=e;return(0,n.jsx)(u.Suspense,{fallback:null,children:(0,n.jsx)(l,{promise:t})})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4930:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{IDLE_LINK_STATUS:function(){return c},PENDING_LINK_STATUS:function(){return i},mountFormInstance:function(){return v},mountLinkInstance:function(){return b},onLinkVisibilityChanged:function(){return m},onNavigationIntent:function(){return R},pingVisibleLinks:function(){return O},setLinkForCurrentNavigation:function(){return s},unmountLinkForCurrentNavigation:function(){return f},unmountPrefetchableInstance:function(){return g}}),r(6634);let n=r(6158),u=r(9818),l=r(6005),o=r(2115),a=null,i={pending:!0},c={pending:!1};function s(e){(0,o.startTransition)(()=>{null==a||a.setOptimisticLinkStatus(c),null==e||e.setOptimisticLinkStatus(i),a=e})}function f(e){a===e&&(a=null)}let d="function"==typeof WeakMap?new WeakMap:new Map,p=new Set,h="function"==typeof IntersectionObserver?new IntersectionObserver(function(e){for(let t of e){let e=t.intersectionRatio>0;m(t.target,e)}},{rootMargin:"200px"}):null;function _(e,t){void 0!==d.get(e)&&g(e),d.set(e,t),null!==h&&h.observe(e)}function y(e){try{return(0,n.createPrefetchURL)(e)}catch(t){return("function"==typeof reportError?reportError:console.error)("Cannot prefetch '"+e+"' because it cannot be converted to a URL."),null}}function b(e,t,r,n,u,l){if(u){let u=y(t);if(null!==u){let t={router:r,kind:n,isVisible:!1,prefetchTask:null,prefetchHref:u.href,setOptimisticLinkStatus:l};return _(e,t),t}}return{router:r,kind:n,isVisible:!1,prefetchTask:null,prefetchHref:null,setOptimisticLinkStatus:l}}function v(e,t,r,n){let u=y(t);null!==u&&_(e,{router:r,kind:n,isVisible:!1,prefetchTask:null,prefetchHref:u.href,setOptimisticLinkStatus:null})}function g(e){let t=d.get(e);if(void 0!==t){d.delete(e),p.delete(t);let r=t.prefetchTask;null!==r&&(0,l.cancelPrefetchTask)(r)}null!==h&&h.unobserve(e)}function m(e,t){let r=d.get(e);void 0!==r&&(r.isVisible=t,t?p.add(r):p.delete(r),E(r,l.PrefetchPriority.Default))}function R(e,t){let r=d.get(e);void 0!==r&&void 0!==r&&E(r,l.PrefetchPriority.Intent)}function E(e,t){var r;let n=e.prefetchTask;if(!e.isVisible){null!==n&&(0,l.cancelPrefetchTask)(n);return}r=e,(async()=>r.router.prefetch(r.prefetchHref,{kind:r.kind}))().catch(e=>{})}function O(e,t){for(let r of p){let n=r.prefetchTask;if(null!==n&&!(0,l.isPrefetchTaskDirty)(n,e,t))continue;null!==n&&(0,l.cancelPrefetchTask)(n);let o=(0,l.createCacheKey)(r.prefetchHref,e);r.prefetchTask=(0,l.schedulePrefetchTask)(o,t,r.kind===u.PrefetchKind.FULL,l.PrefetchPriority.Default,null)}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4970:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ClientSegmentRoot",{enumerable:!0,get:function(){return u}});let n=r(5155);function u(e){let{Component:t,slots:u,params:l,promise:o}=e;{let{createRenderParamsFromClient:e}=r(3558),o=e(l);return(0,n.jsx)(t,{...u,params:o})}}r(9837),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5072:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTML_LIMITED_BOT_UA_RE",{enumerable:!0,get:function(){return r}});let r=/Mediapartners-Google|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti/i},5122:(e,t)=>{"use strict";function r(e){return null!==e&&"object"==typeof e&&"then"in e&&"function"==typeof e.then}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isThenable",{enumerable:!0,get:function(){return r}})},5155:(e,t,r)=>{"use strict";e.exports=r(6897)},5209:(e,t)=>{"use strict";function r(e){return Object.prototype.toString.call(e)}function n(e){if("[object Object]"!==r(e))return!1;let t=Object.getPrototypeOf(e);return null===t||t.hasOwnProperty("isPrototypeOf")}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getObjectClassLabel:function(){return r},isPlainObject:function(){return n}})},5227:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{AppRouterContext:function(){return u},GlobalLayoutRouterContext:function(){return o},LayoutRouterContext:function(){return l},MissingSlotContext:function(){return i},TemplateContext:function(){return a}});let n=r(8229)._(r(2115)),u=n.default.createContext(null),l=n.default.createContext(null),o=n.default.createContext(null),a=n.default.createContext(null),i=n.default.createContext(new Set)},5262:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{BailoutToCSRError:function(){return n},isBailoutToCSRError:function(){return u}});let r="BAILOUT_TO_CLIENT_SIDE_RENDERING";class n extends Error{constructor(e){super("Bail out to client-side rendering: "+e),this.reason=e,this.digest=r}}function u(e){return"object"==typeof e&&null!==e&&"digest"in e&&e.digest===r}},5415:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(5449);let n=r(6188),u=r(1408);(0,n.appBootstrap)(()=>{let{hydrate:e}=r(4486);r(6158),r(7555),e(u)}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5449:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(3668);let n=r(589);{let e=r.u;r.u=function(){for(var t=arguments.length,r=Array(t),u=0;u{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"refreshReducer",{enumerable:!0,get:function(){return h}});let n=r(8586),u=r(1139),l=r(7442),o=r(9234),a=r(3894),i=r(3507),c=r(4758),s=r(6158),f=r(6375),d=r(4108),p=r(4908);function h(e,t){let{origin:r}=t,h={},_=e.canonicalUrl,y=e.tree;h.preserveCustomHistoryState=!1;let b=(0,s.createEmptyCacheNode)(),v=(0,d.hasInterceptionRouteInCurrentTree)(e.tree);b.lazyData=(0,n.fetchServerResponse)(new URL(_,r),{flightRouterState:[y[0],y[1],y[2],"refetch"],nextUrl:v?e.nextUrl:null});let g=Date.now();return b.lazyData.then(async r=>{let{flightData:n,canonicalUrl:s}=r;if("string"==typeof n)return(0,a.handleExternalUrl)(e,h,n,e.pushRef.pendingPush);for(let r of(b.lazyData=null,n)){let{tree:n,seedData:i,head:d,isRootRender:m}=r;if(!m)return console.log("REFRESH FAILED"),e;let R=(0,l.applyRouterStatePatchToTree)([""],y,n,e.canonicalUrl);if(null===R)return(0,f.handleSegmentMismatch)(e,t,n);if((0,o.isNavigatingToNewRootLayout)(y,R))return(0,a.handleExternalUrl)(e,h,_,e.pushRef.pendingPush);let E=s?(0,u.createHrefFromUrl)(s):void 0;if(s&&(h.canonicalUrl=E),null!==i){let e=i[1],t=i[3];b.rsc=e,b.prefetchRsc=null,b.loading=t,(0,c.fillLazyItemsTillLeafWithHead)(g,b,void 0,n,i,d,void 0),h.prefetchCache=new Map}await (0,p.refreshInactiveParallelSegments)({navigatedAt:g,state:e,updatedTree:R,updatedCache:b,includeNextUrl:v,canonicalUrl:h.canonicalUrl||e.canonicalUrl}),h.cache=b,h.patchedTree=R,y=R}return(0,i.handleMutable)(e,h)},()=>e)}r(6005),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5563:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{addSearchParamsToPageSegments:function(){return f},handleAliasedPrefetchEntry:function(){return s}});let n=r(8291),u=r(6158),l=r(7442),o=r(1139),a=r(5637),i=r(3118),c=r(3507);function s(e,t,r,s,d){let p,h=t.tree,_=t.cache,y=(0,o.createHrefFromUrl)(s);if("string"==typeof r)return!1;for(let t of r){if(!function e(t){if(!t)return!1;let r=t[2];if(t[3])return!0;for(let t in r)if(e(r[t]))return!0;return!1}(t.seedData))continue;let r=t.tree;r=f(r,Object.fromEntries(s.searchParams));let{seedData:o,isRootRender:c,pathToSegment:d}=t,b=["",...d];r=f(r,Object.fromEntries(s.searchParams));let v=(0,l.applyRouterStatePatchToTree)(b,h,r,y),g=(0,u.createEmptyCacheNode)();if(c&&o){let t=o[1];g.loading=o[3],g.rsc=t,function e(t,r,u,l,o){if(0!==Object.keys(l[1]).length)for(let i in l[1]){let c,s=l[1][i],f=s[0],d=(0,a.createRouterCacheKey)(f),p=null!==o&&void 0!==o[2][i]?o[2][i]:null;if(null!==p){let e=p[1],r=p[3];c={lazyData:null,rsc:f.includes(n.PAGE_SEGMENT_KEY)?null:e,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:r,navigatedAt:t}}else c={lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:-1};let h=r.parallelRoutes.get(i);h?h.set(d,c):r.parallelRoutes.set(i,new Map([[d,c]])),e(t,c,u,s,p)}}(e,g,_,r,o)}else g.rsc=_.rsc,g.prefetchRsc=_.prefetchRsc,g.loading=_.loading,g.parallelRoutes=new Map(_.parallelRoutes),(0,i.fillCacheWithNewSubTreeDataButOnlyLoading)(e,g,_,t);v&&(h=v,_=g,p=!0)}return!!p&&(d.patchedTree=h,d.cache=_,d.canonicalUrl=y,d.hashFragment=s.hash,(0,c.handleMutable)(t,d))}function f(e,t){let[r,u,...l]=e;if(r.includes(n.PAGE_SEGMENT_KEY))return[(0,n.addSearchParamsIfPageSegment)(r,t),u,...l];let o={};for(let[e,r]of Object.entries(u))o[e]=f(r,t);return[r,o,...l]}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5567:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"shouldHardNavigate",{enumerable:!0,get:function(){return function e(t,r){let[l,o]=r,[a,i]=t;return(0,u.matchSegment)(a,l)?!(t.length<=2)&&e((0,n.getNextFlightSegmentPath)(t),o[i]):!!Array.isArray(a)}}});let n=r(2561),u=r(1127);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5618:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ReadonlyURLSearchParams:function(){return s},RedirectType:function(){return u.RedirectType},forbidden:function(){return o.forbidden},notFound:function(){return l.notFound},permanentRedirect:function(){return n.permanentRedirect},redirect:function(){return n.redirect},unauthorized:function(){return a.unauthorized},unstable_rethrow:function(){return i.unstable_rethrow}});let n=r(6825),u=r(2210),l=r(8527),o=r(3678),a=r(9187),i=r(7599);class c extends Error{constructor(){super("Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams")}}class s extends URLSearchParams{append(){throw new c}delete(){throw new c}set(){throw new c}sort(){throw new c}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5624:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getAppBuildId:function(){return u},setAppBuildId:function(){return n}});let r="";function n(e){r=e}function u(){return r}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5637:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createRouterCacheKey",{enumerable:!0,get:function(){return u}});let n=r(8291);function u(e,t){return(void 0===t&&(t=!1),Array.isArray(e))?e[0]+"|"+e[1]+"|"+e[2]:t&&e.startsWith(n.PAGE_SEGMENT_KEY)?n.PAGE_SEGMENT_KEY:e}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5807:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return u},getProperError:function(){return l}});let n=r(5209);function u(e){return"object"==typeof e&&null!==e&&"name"in e&&"message"in e}function l(e){return u(e)?e:Object.defineProperty(Error((0,n.isPlainObject)(e)?function(e){let t=new WeakSet;return JSON.stringify(e,(e,r)=>{if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r})}(e):e+""),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0})}},5929:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addBasePath",{enumerable:!0,get:function(){return l}});let n=r(4074),u=r(214);function l(e,t){return(0,u.normalizePathTrailingSlash)((0,n.addPathPrefix)(e,""))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5952:(e,t,r)=>{"use strict";function n(e,t){if(!Object.prototype.hasOwnProperty.call(e,t))throw TypeError("attempted to use private field on non-instance");return e}r.r(t),r.d(t,{_:()=>n})},6005:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{NavigationResultTag:function(){return d},PrefetchPriority:function(){return p},cancelPrefetchTask:function(){return i},createCacheKey:function(){return f},getCurrentCacheVersion:function(){return o},isPrefetchTaskDirty:function(){return s},navigate:function(){return u},prefetch:function(){return n},reschedulePrefetchTask:function(){return c},revalidateEntireCache:function(){return l},schedulePrefetchTask:function(){return a}});let r=()=>{throw Object.defineProperty(Error("Segment Cache experiment is not enabled. This is a bug in Next.js."),"__NEXT_ERROR_CODE",{value:"E654",enumerable:!1,configurable:!0})},n=r,u=r,l=r,o=r,a=r,i=r,c=r,s=r,f=r;var d=function(e){return e[e.MPA=0]="MPA",e[e.Success=1]="Success",e[e.NoOp=2]="NoOp",e[e.Async=3]="Async",e}({}),p=function(e){return e[e.Intent=2]="Intent",e[e.Default=1]="Default",e[e.Background=0]="Background",e}({});("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6158:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{createEmptyCacheNode:function(){return A},createPrefetchURL:function(){return x},default:function(){return I},isExternalURL:function(){return w}});let n=r(8229),u=r(6966),l=r(5155),o=u._(r(2115)),a=r(5227),i=r(9818),c=r(1139),s=r(886),f=r(1027),d=r(6614),p=n._(r(8393)),h=r(774),_=r(5929),y=r(7760),b=r(686),v=r(2691),g=r(1822),m=r(4882),R=r(7102),E=r(8946),O=r(8836),P=r(6634),j=r(6825),T=r(2210);r(4930);let S=n._(r(4340)),M={};function w(e){return e.origin!==window.location.origin}function x(e){let t;if((0,h.isBot)(window.navigator.userAgent))return null;try{t=new URL((0,_.addBasePath)(e),window.location.href)}catch(t){throw Object.defineProperty(Error("Cannot prefetch '"+e+"' because it cannot be converted to a URL."),"__NEXT_ERROR_CODE",{value:"E234",enumerable:!1,configurable:!0})}return w(t)?null:t}function C(e){let{appRouterState:t}=e;return(0,o.useInsertionEffect)(()=>{let{tree:e,pushRef:r,canonicalUrl:n}=t,u={...r.preserveCustomHistoryState?window.history.state:{},__NA:!0,__PRIVATE_NEXTJS_INTERNALS_TREE:e};r.pendingPush&&(0,c.createHrefFromUrl)(new URL(window.location.href))!==n?(r.pendingPush=!1,window.history.pushState(u,"",n)):window.history.replaceState(u,"",n)},[t]),(0,o.useEffect)(()=>{},[t.nextUrl,t.tree]),null}function A(){return{lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:-1}}function N(e){null==e&&(e={});let t=window.history.state,r=null==t?void 0:t.__NA;r&&(e.__NA=r);let n=null==t?void 0:t.__PRIVATE_NEXTJS_INTERNALS_TREE;return n&&(e.__PRIVATE_NEXTJS_INTERNALS_TREE=n),e}function D(e){let{headCacheNode:t}=e,r=null!==t?t.head:null,n=null!==t?t.prefetchHead:null,u=null!==n?n:r;return(0,o.useDeferredValue)(r,u)}function U(e){let t,{actionQueue:r,assetPrefix:n,globalError:u,gracefullyDegrade:c}=e,p=(0,f.useActionQueue)(r),{canonicalUrl:h}=p,{searchParams:_,pathname:O}=(0,o.useMemo)(()=>{let e=new URL(h,window.location.href);return{searchParams:e.searchParams,pathname:(0,R.hasBasePath)(e.pathname)?(0,m.removeBasePath)(e.pathname):e.pathname}},[h]);(0,o.useEffect)(()=>{function e(e){var t;e.persisted&&(null==(t=window.history.state)?void 0:t.__PRIVATE_NEXTJS_INTERNALS_TREE)&&(M.pendingMpaPath=void 0,(0,f.dispatchAppRouterAction)({type:i.ACTION_RESTORE,url:new URL(window.location.href),tree:window.history.state.__PRIVATE_NEXTJS_INTERNALS_TREE}))}return window.addEventListener("pageshow",e),()=>{window.removeEventListener("pageshow",e)}},[]),(0,o.useEffect)(()=>{function e(e){let t="reason"in e?e.reason:e.error;if((0,T.isRedirectError)(t)){e.preventDefault();let r=(0,j.getURLFromRedirectError)(t);(0,j.getRedirectTypeFromError)(t)===T.RedirectType.push?P.publicAppRouterInstance.push(r,{}):P.publicAppRouterInstance.replace(r,{})}}return window.addEventListener("error",e),window.addEventListener("unhandledrejection",e),()=>{window.removeEventListener("error",e),window.removeEventListener("unhandledrejection",e)}},[]);let{pushRef:w}=p;if(w.mpaNavigation){if(M.pendingMpaPath!==h){let e=window.location;w.pendingPush?e.assign(h):e.replace(h),M.pendingMpaPath=h}throw g.unresolvedThenable}(0,o.useEffect)(()=>{let e=window.history.pushState.bind(window.history),t=window.history.replaceState.bind(window.history),r=e=>{var t;let r=window.location.href,n=null==(t=window.history.state)?void 0:t.__PRIVATE_NEXTJS_INTERNALS_TREE;(0,o.startTransition)(()=>{(0,f.dispatchAppRouterAction)({type:i.ACTION_RESTORE,url:new URL(null!=e?e:r,r),tree:n})})};window.history.pushState=function(t,n,u){return(null==t?void 0:t.__NA)||(null==t?void 0:t._N)||(t=N(t),u&&r(u)),e(t,n,u)},window.history.replaceState=function(e,n,u){return(null==e?void 0:e.__NA)||(null==e?void 0:e._N)||(e=N(e),u&&r(u)),t(e,n,u)};let n=e=>{if(e.state){if(!e.state.__NA)return void window.location.reload();(0,o.startTransition)(()=>{(0,P.dispatchTraverseAction)(window.location.href,e.state.__PRIVATE_NEXTJS_INTERNALS_TREE)})}};return window.addEventListener("popstate",n),()=>{window.history.pushState=e,window.history.replaceState=t,window.removeEventListener("popstate",n)}},[]);let{cache:x,tree:A,nextUrl:U,focusAndScrollRef:I}=p,L=(0,o.useMemo)(()=>(0,v.findHeadInCache)(x,A[1]),[x,A]),k=(0,o.useMemo)(()=>(0,E.getSelectedParams)(A),[A]),F=(0,o.useMemo)(()=>({parentTree:A,parentCacheNode:x,parentSegmentPath:null,url:h}),[A,x,h]),B=(0,o.useMemo)(()=>({tree:A,focusAndScrollRef:I,nextUrl:U}),[A,I,U]);if(null!==L){let[e,r]=L;t=(0,l.jsx)(D,{headCacheNode:e},r)}else t=null;let K=(0,l.jsxs)(b.RedirectBoundary,{children:[t,x.rsc,(0,l.jsx)(y.AppRouterAnnouncer,{tree:A})]});return K=c?(0,l.jsx)(S.default,{children:K}):(0,l.jsx)(d.ErrorBoundary,{errorComponent:u[0],errorStyles:u[1],children:K}),(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(C,{appRouterState:p}),(0,l.jsx)(H,{}),(0,l.jsx)(s.PathParamsContext.Provider,{value:k,children:(0,l.jsx)(s.PathnameContext.Provider,{value:O,children:(0,l.jsx)(s.SearchParamsContext.Provider,{value:_,children:(0,l.jsx)(a.GlobalLayoutRouterContext.Provider,{value:B,children:(0,l.jsx)(a.AppRouterContext.Provider,{value:P.publicAppRouterInstance,children:(0,l.jsx)(a.LayoutRouterContext.Provider,{value:F,children:K})})})})})})]})}function I(e){let{actionQueue:t,globalErrorState:r,assetPrefix:n,gracefullyDegrade:u}=e;(0,O.useNavFailureHandler)();let o=(0,l.jsx)(U,{actionQueue:t,assetPrefix:n,globalError:r,gracefullyDegrade:u});return u?o:(0,l.jsx)(d.ErrorBoundary,{errorComponent:p.default,children:o})}let L=new Set,k=new Set;function H(){let[,e]=o.default.useState(0),t=L.size;return(0,o.useEffect)(()=>{let r=()=>e(e=>e+1);return k.add(r),t!==L.size&&r(),()=>{k.delete(r)}},[t,e]),[...L].map((e,t)=>(0,l.jsx)("link",{rel:"stylesheet",href:""+e,precedence:"next"},t))}globalThis._N_E_STYLE_LOAD=function(e){let t=L.size;return L.add(e),L.size!==t&&k.forEach(e=>e()),Promise.resolve()},("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6188:(e,t)=>{"use strict";function r(e){var t,r;t=self.__next_s,r=()=>{e()},t&&t.length?t.reduce((e,t)=>{let[r,n]=t;return e.then(()=>new Promise((e,t)=>{let u=document.createElement("script");if(n)for(let e in n)"children"!==e&&u.setAttribute(e,n[e]);r?(u.src=r,u.onload=()=>e(),u.onerror=t):n&&(u.innerHTML=n.children,setTimeout(e)),document.head.appendChild(u)}))},Promise.resolve()).catch(e=>{console.error(e)}).then(()=>{r()}):r()}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"appBootstrap",{enumerable:!0,get:function(){return r}}),window.next={version:"15.4.2",appDir:!0},("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6206:(e,t,r)=>{"use strict";e.exports=r(2223)},6361:(e,t)=>{"use strict";function r(e){return e.replace(/\/$/,"")||"/"}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeTrailingSlash",{enumerable:!0,get:function(){return r}})},6375:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"handleSegmentMismatch",{enumerable:!0,get:function(){return u}});let n=r(3894);function u(e,t,r){return(0,n.handleExternalUrl)(e,{},e.canonicalUrl,!0)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6420:(e,t,r)=>{"use strict";r.r(t),r.d(t,{_:()=>u});var n=0;function u(e){return"__private_"+n+++"_"+e}},6446:()=>{"trimStart"in String.prototype||(String.prototype.trimStart=String.prototype.trimLeft),"trimEnd"in String.prototype||(String.prototype.trimEnd=String.prototype.trimRight),"description"in Symbol.prototype||Object.defineProperty(Symbol.prototype,"description",{configurable:!0,get:function(){var e=/\((.*)\)/.exec(this.toString());return e?e[1]:void 0}}),Array.prototype.flat||(Array.prototype.flat=function(e,t){return t=this.concat.apply([],this),e>1&&t.some(Array.isArray)?t.flat(e-1):t},Array.prototype.flatMap=function(e,t){return this.map(e,t).flat()}),Promise.prototype.finally||(Promise.prototype.finally=function(e){if("function"!=typeof e)return this.then(e,e);var t=this.constructor||Promise;return this.then(function(r){return t.resolve(e()).then(function(){return r})},function(r){return t.resolve(e()).then(function(){throw r})})}),Object.fromEntries||(Object.fromEntries=function(e){return Array.from(e).reduce(function(e,t){return e[t[0]]=t[1],e},{})}),Array.prototype.at||(Array.prototype.at=function(e){var t=Math.trunc(e)||0;if(t<0&&(t+=this.length),!(t<0||t>=this.length))return this[t]}),Object.hasOwn||(Object.hasOwn=function(e,t){if(null==e)throw TypeError("Cannot convert undefined or null to object");return Object.prototype.hasOwnProperty.call(Object(e),t)}),"canParse"in URL||(URL.canParse=function(e,t){try{return new URL(e,t),!0}catch(e){return!1}})},6494:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{HTTPAccessErrorStatus:function(){return r},HTTP_ERROR_FALLBACK_ERROR_CODE:function(){return u},getAccessFallbackErrorTypeByStatus:function(){return a},getAccessFallbackHTTPStatus:function(){return o},isHTTPAccessFallbackError:function(){return l}});let r={NOT_FOUND:404,FORBIDDEN:403,UNAUTHORIZED:401},n=new Set(Object.values(r)),u="NEXT_HTTP_ERROR_FALLBACK";function l(e){if("object"!=typeof e||null===e||!("digest"in e)||"string"!=typeof e.digest)return!1;let[t,r]=e.digest.split(";");return t===u&&n.has(Number(r))}function o(e){return Number(e.digest.split(";")[1])}function a(e){switch(e){case 401:return"unauthorized";case 403:return"forbidden";case 404:return"not-found";default:return}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6539:(e,t,r)=>{"use strict";function n(e,t){if(void 0===t&&(t={}),t.onlyHashChange)return void e();let r=document.documentElement;r.dataset.scrollBehavior;let n=r.style.scrollBehavior;r.style.scrollBehavior="auto",t.dontForceLayout||r.getClientRects(),e(),r.style.scrollBehavior=n}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"disableSmoothScrollDuringRouteTransition",{enumerable:!0,get:function(){return n}}),r(3230)},6614:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ErrorBoundary:function(){return s},ErrorBoundaryHandler:function(){return c}});let n=r(8229),u=r(5155),l=n._(r(2115)),o=r(9921),a=r(2858);r(8836);let i=r(1799);class c extends l.default.Component{static getDerivedStateFromError(e){if((0,a.isNextRouterError)(e))throw e;return{error:e}}static getDerivedStateFromProps(e,t){let{error:r}=t;return e.pathname!==t.previousPathname&&t.error?{error:null,previousPathname:e.pathname}:{error:t.error,previousPathname:e.pathname}}render(){return this.state.error?(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(i.HandleISRError,{error:this.state.error}),this.props.errorStyles,this.props.errorScripts,(0,u.jsx)(this.props.errorComponent,{error:this.state.error,reset:this.reset})]}):this.props.children}constructor(e){super(e),this.reset=()=>{this.setState({error:null})},this.state={error:null,previousPathname:this.props.pathname}}}function s(e){let{errorComponent:t,errorStyles:r,errorScripts:n,children:l}=e,a=(0,o.useUntrackedPathname)();return t?(0,u.jsx)(c,{pathname:a,errorComponent:t,errorStyles:r,errorScripts:n,children:l}):(0,u.jsx)(u.Fragment,{children:l})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6634:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{createMutableActionQueue:function(){return _},dispatchNavigateAction:function(){return v},dispatchTraverseAction:function(){return g},getCurrentAppRouterState:function(){return y},publicAppRouterInstance:function(){return m}});let n=r(9818),u=r(9726),l=r(2115),o=r(5122);r(6005);let a=r(1027),i=r(5929),c=r(6158),s=r(9154),f=r(4930);function d(e,t){null!==e.pending&&(e.pending=e.pending.next,null!==e.pending?p({actionQueue:e,action:e.pending,setState:t}):e.needsRefresh&&(e.needsRefresh=!1,e.dispatch({type:n.ACTION_REFRESH,origin:window.location.origin},t)))}async function p(e){let{actionQueue:t,action:r,setState:n}=e,u=t.state;t.pending=r;let l=r.payload,a=t.action(u,l);function i(e){r.discarded||(t.state=e,d(t,n),r.resolve(e))}(0,o.isThenable)(a)?a.then(i,e=>{d(t,n),r.reject(e)}):i(a)}let h=null;function _(e,t){let r={state:e,dispatch:(e,t)=>(function(e,t,r){let u={resolve:r,reject:()=>{}};if(t.type!==n.ACTION_RESTORE){let e=new Promise((e,t)=>{u={resolve:e,reject:t}});(0,l.startTransition)(()=>{r(e)})}let o={payload:t,next:null,resolve:u.resolve,reject:u.reject};null===e.pending?(e.last=o,p({actionQueue:e,action:o,setState:r})):t.type===n.ACTION_NAVIGATE||t.type===n.ACTION_RESTORE?(e.pending.discarded=!0,o.next=e.pending.next,e.pending.payload.type===n.ACTION_SERVER_ACTION&&(e.needsRefresh=!0),p({actionQueue:e,action:o,setState:r})):(null!==e.last&&(e.last.next=o),e.last=o)})(r,e,t),action:async(e,t)=>(0,u.reducer)(e,t),pending:null,last:null,onRouterTransitionStart:null!==t&&"function"==typeof t.onRouterTransitionStart?t.onRouterTransitionStart:null};if(null!==h)throw Object.defineProperty(Error("Internal Next.js Error: createMutableActionQueue was called more than once"),"__NEXT_ERROR_CODE",{value:"E624",enumerable:!1,configurable:!0});return h=r,r}function y(){return null!==h?h.state:null}function b(){return null!==h?h.onRouterTransitionStart:null}function v(e,t,r,u){let l=new URL((0,i.addBasePath)(e),location.href);(0,f.setLinkForCurrentNavigation)(u);let o=b();null!==o&&o(e,t),(0,a.dispatchAppRouterAction)({type:n.ACTION_NAVIGATE,url:l,isExternalUrl:(0,c.isExternalURL)(l),locationSearch:location.search,shouldScroll:r,navigateType:t,allowAliasing:!0})}function g(e,t){let r=b();null!==r&&r(e,"traverse"),(0,a.dispatchAppRouterAction)({type:n.ACTION_RESTORE,url:new URL(e),tree:t})}let m={back:()=>window.history.back(),forward:()=>window.history.forward(),prefetch:(e,t)=>{let r=function(){if(null===h)throw Object.defineProperty(Error("Internal Next.js error: Router action dispatched before initialization."),"__NEXT_ERROR_CODE",{value:"E668",enumerable:!1,configurable:!0});return h}(),u=(0,c.createPrefetchURL)(e);if(null!==u){var l;(0,s.prefetchReducer)(r.state,{type:n.ACTION_PREFETCH,url:u,kind:null!=(l=null==t?void 0:t.kind)?l:n.PrefetchKind.FULL})}},replace:(e,t)=>{(0,l.startTransition)(()=>{var r;v(e,"replace",null==(r=null==t?void 0:t.scroll)||r,null)})},push:(e,t)=>{(0,l.startTransition)(()=>{var r;v(e,"push",null==(r=null==t?void 0:t.scroll)||r,null)})},refresh:()=>{(0,l.startTransition)(()=>{(0,a.dispatchAppRouterAction)({type:n.ACTION_REFRESH,origin:window.location.origin})})},hmrRefresh:()=>{throw Object.defineProperty(Error("hmrRefresh can only be used in development mode. Please use refresh instead."),"__NEXT_ERROR_CODE",{value:"E485",enumerable:!1,configurable:!0})}};window.next&&(window.next.router=m),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6698:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{isRecoverableError:function(){return i},onRecoverableError:function(){return c}});let n=r(8229),u=r(5262),l=n._(r(5807)),o=r(1646),a=new WeakSet;function i(e){return a.has(e)}let c=(e,t)=>{let r=(0,l.default)(e)&&"cause"in e?e.cause:e;(0,u.isBailoutToCSRError)(r)||(0,o.reportGlobalError)(r)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6825:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getRedirectError:function(){return o},getRedirectStatusCodeFromError:function(){return f},getRedirectTypeFromError:function(){return s},getURLFromRedirectError:function(){return c},permanentRedirect:function(){return i},redirect:function(){return a}});let n=r(4420),u=r(2210),l=void 0;function o(e,t,r){void 0===r&&(r=n.RedirectStatusCode.TemporaryRedirect);let l=Object.defineProperty(Error(u.REDIRECT_ERROR_CODE),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});return l.digest=u.REDIRECT_ERROR_CODE+";"+t+";"+e+";"+r+";",l}function a(e,t){var r;throw null!=t||(t=(null==l||null==(r=l.getStore())?void 0:r.isAction)?u.RedirectType.push:u.RedirectType.replace),o(e,t,n.RedirectStatusCode.TemporaryRedirect)}function i(e,t){throw void 0===t&&(t=u.RedirectType.replace),o(e,t,n.RedirectStatusCode.PermanentRedirect)}function c(e){return(0,u.isRedirectError)(e)?e.digest.split(";").slice(2,-2).join(";"):null}function s(e){if(!(0,u.isRedirectError)(e))throw Object.defineProperty(Error("Not a redirect error"),"__NEXT_ERROR_CODE",{value:"E260",enumerable:!1,configurable:!0});return e.digest.split(";",2)[1]}function f(e){if(!(0,u.isRedirectError)(e))throw Object.defineProperty(Error("Not a redirect error"),"__NEXT_ERROR_CODE",{value:"E260",enumerable:!1,configurable:!0});return Number(e.digest.split(";").at(-2))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6897:(e,t)=>{"use strict";var r=Symbol.for("react.transitional.element");function n(e,t,n){var u=null;if(void 0!==n&&(u=""+n),void 0!==t.key&&(u=""+t.key),"key"in t)for(var l in n={},t)"key"!==l&&(n[l]=t[l]);else n=t;return{$$typeof:r,type:e,key:u,ref:void 0!==(t=n.ref)?t:null,props:n}}t.Fragment=Symbol.for("react.fragment"),t.jsx=n,t.jsxs=n},6966:(e,t,r)=>{"use strict";function n(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(n=function(e){return e?r:t})(e)}function u(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=n(t);if(r&&r.has(e))return r.get(e);var u={__proto__:null},l=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if("default"!==o&&Object.prototype.hasOwnProperty.call(e,o)){var a=l?Object.getOwnPropertyDescriptor(e,o):null;a&&(a.get||a.set)?Object.defineProperty(u,o,a):u[o]=e[o]}return u.default=e,r&&r.set(e,u),u}r.r(t),r.d(t,{_:()=>u})},6975:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTTPAccessFallbackBoundary",{enumerable:!0,get:function(){return s}});let n=r(6966),u=r(5155),l=n._(r(2115)),o=r(9921),a=r(6494);r(3230);let i=r(5227);class c extends l.default.Component{componentDidCatch(){}static getDerivedStateFromError(e){if((0,a.isHTTPAccessFallbackError)(e))return{triggeredStatus:(0,a.getAccessFallbackHTTPStatus)(e)};throw e}static getDerivedStateFromProps(e,t){return e.pathname!==t.previousPathname&&t.triggeredStatus?{triggeredStatus:void 0,previousPathname:e.pathname}:{triggeredStatus:t.triggeredStatus,previousPathname:e.pathname}}render(){let{notFound:e,forbidden:t,unauthorized:r,children:n}=this.props,{triggeredStatus:l}=this.state,o={[a.HTTPAccessErrorStatus.NOT_FOUND]:e,[a.HTTPAccessErrorStatus.FORBIDDEN]:t,[a.HTTPAccessErrorStatus.UNAUTHORIZED]:r};if(l){let i=l===a.HTTPAccessErrorStatus.NOT_FOUND&&e,c=l===a.HTTPAccessErrorStatus.FORBIDDEN&&t,s=l===a.HTTPAccessErrorStatus.UNAUTHORIZED&&r;return i||c||s?(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)("meta",{name:"robots",content:"noindex"}),!1,o[l]]}):n}return n}constructor(e){super(e),this.state={triggeredStatus:void 0,previousPathname:e.pathname}}}function s(e){let{notFound:t,forbidden:r,unauthorized:n,children:a}=e,s=(0,o.useUntrackedPathname)(),f=(0,l.useContext)(i.MissingSlotContext);return t||r||n?(0,u.jsx)(c,{pathname:s,notFound:t,forbidden:r,unauthorized:n,missingSlots:f,children:a}):(0,u.jsx)(u.Fragment,{children:a})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7102:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hasBasePath",{enumerable:!0,get:function(){return u}});let n=r(1747);function u(e){return(0,n.pathHasPrefix)(e,"")}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7197:(e,t,r)=>{"use strict";e.exports=r(9062)},7205:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createRenderSearchParamsFromClient",{enumerable:!0,get:function(){return n}});let n=r(8324).createRenderSearchParamsFromClient;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7276:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{normalizeAppPath:function(){return l},normalizeRscURL:function(){return o}});let n=r(9133),u=r(8291);function l(e){return(0,n.ensureLeadingSlash)(e.split("/").reduce((e,t,r,n)=>!t||(0,u.isGroupSegment)(t)||"@"===t[0]||("page"===t||"route"===t)&&r===n.length-1?e:e+"/"+t,""))}function o(e){return e.replace(/\.rsc($|\?)/,"$1")}},7442:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"applyRouterStatePatchToTree",{enumerable:!0,get:function(){return function e(t,r,n,i){let c,[s,f,d,p,h]=r;if(1===t.length){let e=a(r,n);return(0,o.addRefreshMarkerToActiveParallelSegments)(e,i),e}let[_,y]=t;if(!(0,l.matchSegment)(_,s))return null;if(2===t.length)c=a(f[y],n);else if(null===(c=e((0,u.getNextFlightSegmentPath)(t),f[y],n,i)))return null;let b=[t[0],{...f,[y]:c},d,p];return h&&(b[4]=!0),(0,o.addRefreshMarkerToActiveParallelSegments)(b,i),b}}});let n=r(8291),u=r(2561),l=r(1127),o=r(4908);function a(e,t){let[r,u]=e,[o,i]=t;if(o===n.DEFAULT_SEGMENT_KEY&&r!==n.DEFAULT_SEGMENT_KEY)return e;if((0,l.matchSegment)(r,o)){let t={};for(let e in u)void 0!==i[e]?t[e]=a(u[e],i[e]):t[e]=u[e];for(let e in i)t[e]||(t[e]=i[e]);let n=[r,t];return e[2]&&(n[2]=e[2]),e[3]&&(n[3]=e[3]),e[4]&&(n[4]=e[4]),n}return t}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7541:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{describeHasCheckingStringProperty:function(){return u},describeStringPropertyAccess:function(){return n},wellKnownProperties:function(){return l}});let r=/^[A-Za-z_$][A-Za-z0-9_$]*$/;function n(e,t){return r.test(t)?"`"+e+"."+t+"`":"`"+e+"["+JSON.stringify(t)+"]`"}function u(e,t){let r=JSON.stringify(t);return"`Reflect.has("+e+", "+r+")`, `"+r+" in "+e+"`, or similar"}let l=new Set(["hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toString","valueOf","toLocaleString","then","catch","finally","status","displayName","_debugInfo","toJSON","$$typeof","__esModule"])},7555:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return w}});let n=r(8229),u=r(6966),l=r(5155),o=r(9818),a=u._(r(2115)),i=n._(r(7650)),c=r(5227),s=r(8586),f=r(1822),d=r(6614),p=r(1127),h=r(6539),_=r(686),y=r(6975),b=r(5637),v=r(4108),g=r(1027),m=r(89);r(7276);let R=i.default.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,E=["bottom","height","left","right","top","width","x","y"];function O(e,t){let r=e.getBoundingClientRect();return r.top>=0&&r.top<=t}class P extends a.default.Component{componentDidMount(){this.handlePotentialScroll()}componentDidUpdate(){this.props.focusAndScrollRef.apply&&this.handlePotentialScroll()}render(){return this.props.children}constructor(...e){super(...e),this.handlePotentialScroll=()=>{let{focusAndScrollRef:e,segmentPath:t}=this.props;if(e.apply){if(0!==e.segmentPaths.length&&!e.segmentPaths.some(e=>t.every((t,r)=>(0,p.matchSegment)(t,e[r]))))return;let r=null,n=e.hashFragment;if(n&&(r=function(e){var t;return"top"===e?document.body:null!=(t=document.getElementById(e))?t:document.getElementsByName(e)[0]}(n)),r||(r=(0,R.findDOMNode)(this)),!(r instanceof Element))return;for(;!(r instanceof HTMLElement)||function(e){if(["sticky","fixed"].includes(getComputedStyle(e).position))return!0;let t=e.getBoundingClientRect();return E.every(e=>0===t[e])}(r);){if(null===r.nextElementSibling)return;r=r.nextElementSibling}e.apply=!1,e.hashFragment=null,e.segmentPaths=[],(0,h.disableSmoothScrollDuringRouteTransition)(()=>{if(n)return void r.scrollIntoView();let e=document.documentElement,t=e.clientHeight;!O(r,t)&&(e.scrollTop=0,O(r,t)||r.scrollIntoView())},{dontForceLayout:!0,onlyHashChange:e.onlyHashChange}),e.onlyHashChange=!1,r.focus()}}}}function j(e){let{segmentPath:t,children:r}=e,n=(0,a.useContext)(c.GlobalLayoutRouterContext);if(!n)throw Object.defineProperty(Error("invariant global layout router not mounted"),"__NEXT_ERROR_CODE",{value:"E473",enumerable:!1,configurable:!0});return(0,l.jsx)(P,{segmentPath:t,focusAndScrollRef:n.focusAndScrollRef,children:r})}function T(e){let{tree:t,segmentPath:r,cacheNode:n,url:u}=e,i=(0,a.useContext)(c.GlobalLayoutRouterContext);if(!i)throw Object.defineProperty(Error("invariant global layout router not mounted"),"__NEXT_ERROR_CODE",{value:"E473",enumerable:!1,configurable:!0});let{tree:d}=i,h=null!==n.prefetchRsc?n.prefetchRsc:n.rsc,_=(0,a.useDeferredValue)(n.rsc,h),y="object"==typeof _&&null!==_&&"function"==typeof _.then?(0,a.use)(_):_;if(!y){let e=n.lazyData;if(null===e){let t=function e(t,r){if(t){let[n,u]=t,l=2===t.length;if((0,p.matchSegment)(r[0],n)&&r[1].hasOwnProperty(u)){if(l){let t=e(void 0,r[1][u]);return[r[0],{...r[1],[u]:[t[0],t[1],t[2],"refetch"]}]}return[r[0],{...r[1],[u]:e(t.slice(2),r[1][u])}]}}return r}(["",...r],d),l=(0,v.hasInterceptionRouteInCurrentTree)(d),c=Date.now();n.lazyData=e=(0,s.fetchServerResponse)(new URL(u,location.origin),{flightRouterState:t,nextUrl:l?i.nextUrl:null}).then(e=>((0,a.startTransition)(()=>{(0,g.dispatchAppRouterAction)({type:o.ACTION_SERVER_PATCH,previousTree:d,serverResponse:e,navigatedAt:c})}),e)),(0,a.use)(e)}(0,a.use)(f.unresolvedThenable)}return(0,l.jsx)(c.LayoutRouterContext.Provider,{value:{parentTree:t,parentCacheNode:n,parentSegmentPath:r,url:u},children:y})}function S(e){let t,{loading:r,children:n}=e;if(t="object"==typeof r&&null!==r&&"function"==typeof r.then?(0,a.use)(r):r){let e=t[0],r=t[1],u=t[2];return(0,l.jsx)(a.Suspense,{fallback:(0,l.jsxs)(l.Fragment,{children:[r,u,e]}),children:n})}return(0,l.jsx)(l.Fragment,{children:n})}function M(e){let{children:t}=e;return(0,l.jsx)(l.Fragment,{children:t})}function w(e){let{parallelRouterKey:t,error:r,errorStyles:n,errorScripts:u,templateStyles:o,templateScripts:i,template:s,notFound:f,forbidden:p,unauthorized:h,gracefullyDegrade:v,segmentViewBoundaries:g}=e,R=(0,a.useContext)(c.LayoutRouterContext);if(!R)throw Object.defineProperty(Error("invariant expected layout router to be mounted"),"__NEXT_ERROR_CODE",{value:"E56",enumerable:!1,configurable:!0});let{parentTree:E,parentCacheNode:O,parentSegmentPath:P,url:w}=R,x=O.parallelRoutes,C=x.get(t);C||(C=new Map,x.set(t,C));let A=E[0],N=null===P?[t]:P.concat([A,t]),D=E[1][t],U=D[0],I=(0,b.createRouterCacheKey)(U,!0),L=(0,m.useRouterBFCache)(D,I),k=[];do{let e=L.tree,t=L.stateKey,a=e[0],g=(0,b.createRouterCacheKey)(a),m=C.get(g);if(void 0===m){let e={lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:-1};m=e,C.set(g,e)}let R=v?M:d.ErrorBoundary,E=O.loading,P=(0,l.jsxs)(c.TemplateContext.Provider,{value:(0,l.jsxs)(j,{segmentPath:N,children:[(0,l.jsx)(R,{errorComponent:r,errorStyles:n,errorScripts:u,children:(0,l.jsx)(S,{loading:E,children:(0,l.jsx)(y.HTTPAccessFallbackBoundary,{notFound:f,forbidden:p,unauthorized:h,children:(0,l.jsxs)(_.RedirectBoundary,{children:[(0,l.jsx)(T,{url:w,tree:e,cacheNode:m,segmentPath:N}),null]})})})}),null]}),children:[o,i,s]},t);k.push(P),L=L.next}while(null!==L);return k}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7568:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ServerInsertedHTMLContext:function(){return u},useServerInsertedHTML:function(){return l}});let n=r(6966)._(r(2115)),u=n.default.createContext(null);function l(e){let t=(0,n.useContext)(u);t&&t(e)}},7599:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"unstable_rethrow",{enumerable:!0,get:function(){return n}});let n=r(7865).unstable_rethrow;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7650:(e,t,r)=>{"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=r(8730)},7755:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{INTERCEPTION_ROUTE_MARKERS:function(){return u},extractInterceptionRouteInformation:function(){return o},isInterceptionRouteAppPath:function(){return l}});let n=r(7276),u=["(..)(..)","(.)","(..)","(...)"];function l(e){return void 0!==e.split("/").find(e=>u.find(t=>e.startsWith(t)))}function o(e){let t,r,l;for(let n of e.split("/"))if(r=u.find(e=>n.startsWith(e))){[t,l]=e.split(r,2);break}if(!t||!r||!l)throw Object.defineProperty(Error("Invalid interception route: "+e+". Must be in the format //(..|...|..)(..)/"),"__NEXT_ERROR_CODE",{value:"E269",enumerable:!1,configurable:!0});switch(t=(0,n.normalizeAppPath)(t),r){case"(.)":l="/"===t?"/"+l:t+"/"+l;break;case"(..)":if("/"===t)throw Object.defineProperty(Error("Invalid interception route: "+e+". Cannot use (..) marker at the root level, use (.) instead."),"__NEXT_ERROR_CODE",{value:"E207",enumerable:!1,configurable:!0});l=t.split("/").slice(0,-1).concat(l).join("/");break;case"(...)":l="/"+l;break;case"(..)(..)":let o=t.split("/");if(o.length<=2)throw Object.defineProperty(Error("Invalid interception route: "+e+". Cannot use (..)(..) marker at the root level or one level up."),"__NEXT_ERROR_CODE",{value:"E486",enumerable:!1,configurable:!0});l=o.slice(0,-2).concat(l).join("/");break;default:throw Object.defineProperty(Error("Invariant: unexpected marker"),"__NEXT_ERROR_CODE",{value:"E112",enumerable:!1,configurable:!0})}return{interceptingRoute:t,interceptedRoute:l}}},7760:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"AppRouterAnnouncer",{enumerable:!0,get:function(){return o}});let n=r(2115),u=r(7650),l="next-route-announcer";function o(e){let{tree:t}=e,[r,o]=(0,n.useState)(null);(0,n.useEffect)(()=>(o(function(){var e;let t=document.getElementsByName(l)[0];if(null==t||null==(e=t.shadowRoot)?void 0:e.childNodes[0])return t.shadowRoot.childNodes[0];{let e=document.createElement(l);e.style.cssText="position:absolute";let t=document.createElement("div");return t.ariaLive="assertive",t.id="__next-route-announcer__",t.role="alert",t.style.cssText="position:absolute;border:0;height:1px;margin:-1px;padding:0;width:1px;clip:rect(0 0 0 0);overflow:hidden;white-space:nowrap;word-wrap:normal",e.attachShadow({mode:"open"}).appendChild(t),document.body.appendChild(e),t}}()),()=>{let e=document.getElementsByTagName(l)[0];(null==e?void 0:e.isConnected)&&document.body.removeChild(e)}),[]);let[a,i]=(0,n.useState)(""),c=(0,n.useRef)(void 0);return(0,n.useEffect)(()=>{let e="";if(document.title)e=document.title;else{let t=document.querySelector("h1");t&&(e=t.innerText||t.textContent||"")}void 0!==c.current&&c.current!==e&&i(e),c.current=e},[t]),r?(0,u.createPortal)(a,r):null}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7801:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"serverPatchReducer",{enumerable:!0,get:function(){return s}});let n=r(1139),u=r(7442),l=r(9234),o=r(3894),a=r(878),i=r(3507),c=r(6158);function s(e,t){let{serverResponse:{flightData:r,canonicalUrl:s},navigatedAt:f}=t,d={};if(d.preserveCustomHistoryState=!1,"string"==typeof r)return(0,o.handleExternalUrl)(e,d,r,e.pushRef.pendingPush);let p=e.tree,h=e.cache;for(let t of r){let{segmentPath:r,tree:i}=t,_=(0,u.applyRouterStatePatchToTree)(["",...r],p,i,e.canonicalUrl);if(null===_)return e;if((0,l.isNavigatingToNewRootLayout)(p,_))return(0,o.handleExternalUrl)(e,d,e.canonicalUrl,e.pushRef.pendingPush);let y=s?(0,n.createHrefFromUrl)(s):void 0;y&&(d.canonicalUrl=y);let b=(0,c.createEmptyCacheNode)();(0,a.applyFlightData)(f,h,b,t),d.patchedTree=_,d.cache=b,h=b,p=_}return(0,i.handleMutable)(e,d)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7829:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createRenderParamsFromClient",{enumerable:!0,get:function(){return l}});let n=r(7541),u=new WeakMap;function l(e){let t=u.get(e);if(t)return t;let r=Promise.resolve(e);return u.set(e,r),Object.keys(e).forEach(t=>{n.wellKnownProperties.has(t)||(r[t]=e[t])}),r}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7865:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"unstable_rethrow",{enumerable:!0,get:function(){return function e(t){if((0,u.isNextRouterError)(t)||(0,n.isBailoutToCSRError)(t))throw t;t instanceof Error&&"cause"in t&&e(t.cause)}}});let n=r(5262),u=r(2858);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8175:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"IconMark",{enumerable:!0,get:function(){return n}}),r(5155);let n=()=>null},8229:(e,t,r)=>{"use strict";function n(e){return e&&e.__esModule?e:{default:e}}r.r(t),r.d(t,{_:()=>n})},8287:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{METADATA_BOUNDARY_NAME:function(){return r},OUTLET_BOUNDARY_NAME:function(){return u},VIEWPORT_BOUNDARY_NAME:function(){return n}});let r="__next_metadata_boundary__",n="__next_viewport_boundary__",u="__next_outlet_boundary__"},8291:(e,t)=>{"use strict";function r(e){return"("===e[0]&&e.endsWith(")")}function n(e){return e.startsWith("@")&&"@children"!==e}function u(e,t){if(e.includes(l)){let e=JSON.stringify(t);return"{}"!==e?l+"?"+e:l}return e}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{DEFAULT_SEGMENT_KEY:function(){return o},PAGE_SEGMENT_KEY:function(){return l},addSearchParamsIfPageSegment:function(){return u},isGroupSegment:function(){return r},isParallelRouteSegment:function(){return n}});let l="__PAGE__",o="__DEFAULT__"},8324:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createRenderSearchParamsFromClient",{enumerable:!0,get:function(){return l}});let n=r(7541),u=new WeakMap;function l(e){let t=u.get(e);if(t)return t;let r=Promise.resolve(e);return u.set(e,r),Object.keys(e).forEach(t=>{n.wellKnownProperties.has(t)||(r[t]=e[t])}),r}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8393:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return o}});let n=r(5155),u=r(1799),l={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},text:{fontSize:"14px",fontWeight:400,lineHeight:"28px",margin:"0 8px"}},o=function(e){let{error:t}=e,r=null==t?void 0:t.digest;return(0,n.jsxs)("html",{id:"__next_error__",children:[(0,n.jsx)("head",{}),(0,n.jsxs)("body",{children:[(0,n.jsx)(u.HandleISRError,{error:t}),(0,n.jsx)("div",{style:l.error,children:(0,n.jsxs)("div",{children:[(0,n.jsxs)("h2",{style:l.text,children:["Application error: a ",r?"server":"client","-side exception has occurred while loading ",window.location.hostname," (see the"," ",r?"server logs":"browser console"," for more information)."]}),r?(0,n.jsx)("p",{style:l.text,children:"Digest: "+r}):null]})})]})]})};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8527:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"notFound",{enumerable:!0,get:function(){return u}});let n=""+r(6494).HTTP_ERROR_FALLBACK_ERROR_CODE+";404";function u(){let e=Object.defineProperty(Error(n),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});throw e.digest=n,e}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8586:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{createFetch:function(){return y},createFromNextReadableStream:function(){return b},fetchServerResponse:function(){return _},urlToUrlWithoutFlightMarker:function(){return d}});let n=r(7197),u=r(3269),l=r(3806),o=r(1818),a=r(9818),i=r(2561),c=r(5624),s=r(8969),f=n.createFromReadableStream;function d(e){let t=new URL(e,location.origin);if(t.searchParams.delete(u.NEXT_RSC_UNION_QUERY),t.pathname.endsWith(".txt")){let{pathname:e}=t,r=e.endsWith("/index.txt")?10:4;t.pathname=e.slice(0,-r)}return t}function p(e){return{flightData:d(e).toString(),canonicalUrl:void 0,couldBeIntercepted:!1,prerendered:!1,postponed:!1,staleTime:-1}}let h=new AbortController;async function _(e,t){let{flightRouterState:r,nextUrl:n,prefetchKind:l}=t,o={[u.RSC_HEADER]:"1",[u.NEXT_ROUTER_STATE_TREE_HEADER]:(0,i.prepareFlightRouterStateForRequest)(r,t.isHmrRefresh)};l===a.PrefetchKind.AUTO&&(o[u.NEXT_ROUTER_PREFETCH_HEADER]="1"),n&&(o[u.NEXT_URL]=n);try{var s;let t=l?l===a.PrefetchKind.TEMPORARY?"high":"low":"auto";(e=new URL(e)).pathname.endsWith("/")?e.pathname+="index.txt":e.pathname+=".txt";let r=await y(e,o,t,h.signal),n=d(r.url),f=r.redirected?n:void 0,_=r.headers.get("content-type")||"",v=!!(null==(s=r.headers.get("vary"))?void 0:s.includes(u.NEXT_URL)),g=!!r.headers.get(u.NEXT_DID_POSTPONE_HEADER),m=r.headers.get(u.NEXT_ROUTER_STALE_TIME_HEADER),R=null!==m?1e3*parseInt(m,10):-1,E=_.startsWith(u.RSC_CONTENT_TYPE_HEADER);if(E||(E=_.startsWith("text/plain")),!E||!r.ok||!r.body)return e.hash&&(n.hash=e.hash),p(n.toString());let O=g?function(e){let t=e.getReader();return new ReadableStream({async pull(e){for(;;){let{done:r,value:n}=await t.read();if(!r){e.enqueue(n);continue}return}}})}(r.body):r.body,P=await b(O);if((0,c.getAppBuildId)()!==P.b)return p(r.url);return{flightData:(0,i.normalizeFlightData)(P.f),canonicalUrl:f,couldBeIntercepted:v,prerendered:P.S,postponed:g,staleTime:R}}catch(t){return h.signal.aborted||console.error("Failed to fetch RSC payload for "+e+". Falling back to browser navigation.",t),{flightData:e.toString(),canonicalUrl:void 0,couldBeIntercepted:!1,prerendered:!1,postponed:!1,staleTime:-1}}}async function y(e,t,r,n){let l=new URL(e);(0,s.setCacheBustingSearchParam)(l,t);let o=await fetch(l,{credentials:"same-origin",headers:t,priority:r||void 0,signal:n}),a=o.redirected,i=new URL(o.url,l);return i.searchParams.delete(u.NEXT_RSC_UNION_QUERY),{url:i.href,redirected:a,ok:o.ok,headers:o.headers,body:o.body,status:o.status}}function b(e){return f(e,{callServer:l.callServer,findSourceMapURL:o.findSourceMapURL})}window.addEventListener("pagehide",()=>{h.abort()}),window.addEventListener("pageshow",()=>{h=new AbortController}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8709:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"serverActionReducer",{enumerable:!0,get:function(){return w}});let n=r(3806),u=r(1818),l=r(3269),o=r(7197),a=r(9818),i=r(1315),c=r(1139),s=r(3894),f=r(7442),d=r(9234),p=r(3507),h=r(4758),_=r(6158),y=r(4108),b=r(6375),v=r(4908),g=r(2561),m=r(6825),R=r(2210),E=r(1518),O=r(4882),P=r(7102),j=r(2816);r(6005);let T=o.createFromFetch;async function S(e,t,r){let a,c,s,f,{actionId:d,actionArgs:p}=r,h=(0,o.createTemporaryReferenceSet)(),_=(0,j.extractInfoFromServerReferenceId)(d),y="use-cache"===_.type?(0,j.omitUnusedArgs)(p,_):p,b=await (0,o.encodeReply)(y,{temporaryReferences:h}),v=await fetch(e.canonicalUrl,{method:"POST",headers:{Accept:l.RSC_CONTENT_TYPE_HEADER,[l.ACTION_HEADER]:d,[l.NEXT_ROUTER_STATE_TREE_HEADER]:(0,g.prepareFlightRouterStateForRequest)(e.tree),...{},...t?{[l.NEXT_URL]:t}:{}},body:b});if("1"===v.headers.get(l.NEXT_ACTION_NOT_FOUND_HEADER))throw Object.defineProperty(Error('Server Action "'+d+'" was not found on the server. \nRead more: https://nextjs.org/docs/messages/failed-to-find-server-action'),"__NEXT_ERROR_CODE",{value:"E715",enumerable:!1,configurable:!0});let m=v.headers.get("x-action-redirect"),[E,O]=(null==m?void 0:m.split(";"))||[];switch(O){case"push":a=R.RedirectType.push;break;case"replace":a=R.RedirectType.replace;break;default:a=void 0}let P=!!v.headers.get(l.NEXT_IS_PRERENDER_HEADER);try{let e=JSON.parse(v.headers.get("x-action-revalidated")||"[[],0,0]");c={paths:e[0]||[],tag:!!e[1],cookie:e[2]}}catch(e){c=M}let S=E?(0,i.assignLocation)(E,new URL(e.canonicalUrl,window.location.href)):void 0,w=v.headers.get("content-type"),x=!!(w&&w.startsWith(l.RSC_CONTENT_TYPE_HEADER));if(!x&&!S)throw Object.defineProperty(Error(v.status>=400&&"text/plain"===w?await v.text():"An unexpected response was received from the server."),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});if(x){let e=await T(Promise.resolve(v),{callServer:n.callServer,findSourceMapURL:u.findSourceMapURL,temporaryReferences:h});s=S?void 0:e.a,f=(0,g.normalizeFlightData)(e.f)}else s=void 0,f=void 0;return{actionResult:s,actionFlightData:f,redirectLocation:S,redirectType:a,revalidatedParts:c,isPrerender:P}}let M={paths:[],tag:!1,cookie:!1};function w(e,t){let{resolve:r,reject:n}=t,u={},l=e.tree;u.preserveCustomHistoryState=!1;let o=e.nextUrl&&(0,y.hasInterceptionRouteInCurrentTree)(e.tree)?e.nextUrl:null,i=Date.now();return S(e,o,t).then(async y=>{let g,{actionResult:j,actionFlightData:T,redirectLocation:S,redirectType:M,isPrerender:w,revalidatedParts:x}=y;if(S&&(M===R.RedirectType.replace?(e.pushRef.pendingPush=!1,u.pendingPush=!1):(e.pushRef.pendingPush=!0,u.pendingPush=!0),u.canonicalUrl=g=(0,c.createHrefFromUrl)(S,!1)),!T)return(r(j),S)?(0,s.handleExternalUrl)(e,u,S.href,e.pushRef.pendingPush):e;if("string"==typeof T)return r(j),(0,s.handleExternalUrl)(e,u,T,e.pushRef.pendingPush);let C=x.paths.length>0||x.tag||x.cookie;for(let n of T){let{tree:a,seedData:c,head:p,isRootRender:y}=n;if(!y)return console.log("SERVER ACTION APPLY FAILED"),r(j),e;let m=(0,f.applyRouterStatePatchToTree)([""],l,a,g||e.canonicalUrl);if(null===m)return r(j),(0,b.handleSegmentMismatch)(e,t,a);if((0,d.isNavigatingToNewRootLayout)(l,m))return r(j),(0,s.handleExternalUrl)(e,u,g||e.canonicalUrl,e.pushRef.pendingPush);if(null!==c){let t=c[1],r=(0,_.createEmptyCacheNode)();r.rsc=t,r.prefetchRsc=null,r.loading=c[3],(0,h.fillLazyItemsTillLeafWithHead)(i,r,void 0,a,c,p,void 0),u.cache=r,u.prefetchCache=new Map,C&&await (0,v.refreshInactiveParallelSegments)({navigatedAt:i,state:e,updatedTree:m,updatedCache:r,includeNextUrl:!!o,canonicalUrl:u.canonicalUrl||e.canonicalUrl})}u.patchedTree=m,l=m}return S&&g?(C||((0,E.createSeededPrefetchCacheEntry)({url:S,data:{flightData:T,canonicalUrl:void 0,couldBeIntercepted:!1,prerendered:!1,postponed:!1,staleTime:-1},tree:e.tree,prefetchCache:e.prefetchCache,nextUrl:e.nextUrl,kind:w?a.PrefetchKind.FULL:a.PrefetchKind.AUTO}),u.prefetchCache=e.prefetchCache),n((0,m.getRedirectError)((0,P.hasBasePath)(g)?(0,O.removeBasePath)(g):g,M||R.RedirectType.push))):r(j),(0,p.handleMutable)(e,u)},t=>(n(t),e))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8726:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"computeCacheBustingSearchParam",{enumerable:!0,get:function(){return u}});let n=r(3942);function u(e,t,r,u){return void 0===e&&void 0===t&&void 0===r&&void 0===u?"":(0,n.hexHash)([e||"0",t||"0",r||"0",u||"0"].join(","))}},8730:(e,t,r)=>{"use strict";var n=r(2115);function u(e){var t="https://react.dev/errors/"+e;if(1{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{handleHardNavError:function(){return u},useNavFailureHandler:function(){return l}}),r(2115);let n=r(1139);function u(e){return!!e&&!!window.next.__pendingUrl&&(0,n.createHrefFromUrl)(new URL(window.location.href))!==(0,n.createHrefFromUrl)(window.next.__pendingUrl)&&(console.error("Error occurred during navigation, falling back to hard navigation",e),window.location.href=window.next.__pendingUrl.toString(),!0)}function l(){}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8946:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{computeChangedPath:function(){return c},extractPathFromFlightRouterState:function(){return i},getSelectedParams:function(){return function e(t,r){for(let n of(void 0===r&&(r={}),Object.values(t[1]))){let t=n[0],l=Array.isArray(t),o=l?t[1]:t;!o||o.startsWith(u.PAGE_SEGMENT_KEY)||(l&&("c"===t[2]||"oc"===t[2])?r[t[0]]=t[1].split("/"):l&&(r[t[0]]=t[1]),r=e(n,r))}return r}}});let n=r(7755),u=r(8291),l=r(1127),o=e=>"string"==typeof e?"children"===e?"":e:e[1];function a(e){return e.reduce((e,t)=>{let r;return""===(t="/"===(r=t)[0]?r.slice(1):r)||(0,u.isGroupSegment)(t)?e:e+"/"+t},"")||"/"}function i(e){var t;let r=Array.isArray(e[0])?e[0][1]:e[0];if(r===u.DEFAULT_SEGMENT_KEY||n.INTERCEPTION_ROUTE_MARKERS.some(e=>r.startsWith(e)))return;if(r.startsWith(u.PAGE_SEGMENT_KEY))return"";let l=[o(r)],c=null!=(t=e[1])?t:{},s=c.children?i(c.children):void 0;if(void 0!==s)l.push(s);else for(let[e,t]of Object.entries(c)){if("children"===e)continue;let r=i(t);void 0!==r&&l.push(r)}return a(l)}function c(e,t){let r=function e(t,r){let[u,a]=t,[c,s]=r,f=o(u),d=o(c);if(n.INTERCEPTION_ROUTE_MARKERS.some(e=>f.startsWith(e)||d.startsWith(e)))return"";if(!(0,l.matchSegment)(u,c)){var p;return null!=(p=i(r))?p:""}for(let t in a)if(s[t]){let r=e(a[t],s[t]);if(null!==r)return o(c)+"/"+r}return null}(e,t);return null==r||"/"===r?r:a(r.split("/"))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8969:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{setCacheBustingSearchParam:function(){return l},setCacheBustingSearchParamWithHash:function(){return o}});let n=r(8726),u=r(3269),l=(e,t)=>{o(e,(0,n.computeCacheBustingSearchParam)(t[u.NEXT_ROUTER_PREFETCH_HEADER],t[u.NEXT_ROUTER_SEGMENT_PREFETCH_HEADER],t[u.NEXT_ROUTER_STATE_TREE_HEADER],t[u.NEXT_URL]))},o=(e,t)=>{let r=e.search,n=(r.startsWith("?")?r.slice(1):r).split("&").filter(e=>e&&!e.startsWith(""+u.NEXT_RSC_UNION_QUERY+"="));t.length>0?n.push(u.NEXT_RSC_UNION_QUERY+"="+t):n.push(""+u.NEXT_RSC_UNION_QUERY),e.search=n.length?"?"+n.join("&"):""};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8999:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ReadonlyURLSearchParams:function(){return i.ReadonlyURLSearchParams},RedirectType:function(){return i.RedirectType},ServerInsertedHTMLContext:function(){return c.ServerInsertedHTMLContext},forbidden:function(){return i.forbidden},notFound:function(){return i.notFound},permanentRedirect:function(){return i.permanentRedirect},redirect:function(){return i.redirect},unauthorized:function(){return i.unauthorized},unstable_rethrow:function(){return i.unstable_rethrow},useParams:function(){return h},usePathname:function(){return d},useRouter:function(){return p},useSearchParams:function(){return f},useSelectedLayoutSegment:function(){return y},useSelectedLayoutSegments:function(){return _},useServerInsertedHTML:function(){return c.useServerInsertedHTML}});let n=r(2115),u=r(5227),l=r(886),o=r(708),a=r(8291),i=r(5618),c=r(7568),s=void 0;function f(){let e=(0,n.useContext)(l.SearchParamsContext);return(0,n.useMemo)(()=>e?new i.ReadonlyURLSearchParams(e):null,[e])}function d(){return null==s||s("usePathname()"),(0,n.useContext)(l.PathnameContext)}function p(){let e=(0,n.useContext)(u.AppRouterContext);if(null===e)throw Object.defineProperty(Error("invariant expected app router to be mounted"),"__NEXT_ERROR_CODE",{value:"E238",enumerable:!1,configurable:!0});return e}function h(){return null==s||s("useParams()"),(0,n.useContext)(l.PathParamsContext)}function _(e){void 0===e&&(e="children"),null==s||s("useSelectedLayoutSegments()");let t=(0,n.useContext)(u.LayoutRouterContext);return t?function e(t,r,n,u){let l;if(void 0===n&&(n=!0),void 0===u&&(u=[]),n)l=t[1][r];else{var i;let e=t[1];l=null!=(i=e.children)?i:Object.values(e)[0]}if(!l)return u;let c=l[0],s=(0,o.getSegmentValue)(c);return!s||s.startsWith(a.PAGE_SEGMENT_KEY)?u:(u.push(s),e(l,r,!1,u))}(t.parentTree,e):null}function y(e){void 0===e&&(e="children"),null==s||s("useSelectedLayoutSegment()");let t=_(e);if(!t||0===t.length)return null;let r="children"===e?t[0]:t[t.length-1];return r===a.DEFAULT_SEGMENT_KEY?null:r}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9062:(e,t,r)=>{"use strict";var n=r(7650),u={stream:!0},l=new Map;function o(e){var t=r(e);return"function"!=typeof t.then||"fulfilled"===t.status?null:(t.then(function(e){t.status="fulfilled",t.value=e},function(e){t.status="rejected",t.reason=e}),t)}function a(){}function i(e){for(var t=e[1],n=[],u=0;uf||35===f||114===f||120===f?(p=f,f=3,s++):(p=0,f=3);continue;case 2:44===(b=c[s++])?f=4:h=h<<4|(96c.length&&(b=-1)}var v=c.byteOffset+s;if(-1{"use strict";function r(e){return e.startsWith("/")?e:"/"+e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ensureLeadingSlash",{enumerable:!0,get:function(){return r}})},9154:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{prefetchQueue:function(){return l},prefetchReducer:function(){return o}});let n=r(2312),u=r(1518),l=new n.PromiseQueue(5),o=function(e,t){(0,u.prunePrefetchCache)(e.prefetchCache);let{url:r}=t;return(0,u.getOrCreatePrefetchCacheEntry)({url:r,nextUrl:e.nextUrl,prefetchCache:e.prefetchCache,kind:t.kind,tree:e.tree,allowAliasing:!0}),e};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9155:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{onCaughtError:function(){return s},onUncaughtError:function(){return f}});let n=r(8229),u=r(2858),l=r(5262),o=r(1646),a=r(6614),i=n._(r(8393)),c={decorateDevError:e=>e,handleClientError:()=>{},originConsoleError:console.error.bind(console)};function s(e,t){var r;let n,o=null==(r=t.errorBoundary)?void 0:r.constructor;if(n=n||o===a.ErrorBoundaryHandler&&t.errorBoundary.props.errorComponent===i.default)return f(e,t);(0,l.isBailoutToCSRError)(e)||(0,u.isNextRouterError)(e)||c.originConsoleError(e)}function f(e,t){(0,l.isBailoutToCSRError)(e)||(0,u.isNextRouterError)(e)||(0,o.reportGlobalError)(e)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9187:(e,t,r)=>{"use strict";function n(){throw Object.defineProperty(Error("`unauthorized()` is experimental and only allowed to be used when `experimental.authInterrupts` is enabled."),"__NEXT_ERROR_CODE",{value:"E411",enumerable:!1,configurable:!0})}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"unauthorized",{enumerable:!0,get:function(){return n}}),r(6494).HTTP_ERROR_FALLBACK_ERROR_CODE,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9234:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isNavigatingToNewRootLayout",{enumerable:!0,get:function(){return function e(t,r){let n=t[0],u=r[0];if(Array.isArray(n)&&Array.isArray(u)){if(n[0]!==u[0]||n[2]!==u[2])return!0}else if(n!==u)return!0;if(t[4])return!r[4];if(r[4])return!0;let l=Object.values(t[1])[0],o=Object.values(r[1])[0];return!l||!o||e(l,o)}}}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9509:(e,t,r)=>{"use strict";var n,u;e.exports=(null==(n=r.g.process)?void 0:n.env)&&"object"==typeof(null==(u=r.g.process)?void 0:u.env)?r.g.process:r(666)},9665:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{MetadataBoundary:function(){return l},OutletBoundary:function(){return a},ViewportBoundary:function(){return o}});let n=r(8287),u={[n.METADATA_BOUNDARY_NAME]:function(e){let{children:t}=e;return t},[n.VIEWPORT_BOUNDARY_NAME]:function(e){let{children:t}=e;return t},[n.OUTLET_BOUNDARY_NAME]:function(e){let{children:t}=e;return t}},l=u[n.METADATA_BOUNDARY_NAME.slice(0)],o=u[n.VIEWPORT_BOUNDARY_NAME.slice(0)],a=u[n.OUTLET_BOUNDARY_NAME.slice(0)];("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9726:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"reducer",{enumerable:!0,get:function(){return f}});let n=r(9818),u=r(3894),l=r(7801),o=r(4819),a=r(5542),i=r(9154),c=r(3612),s=r(8709),f=function(e,t){switch(t.type){case n.ACTION_NAVIGATE:return(0,u.navigateReducer)(e,t);case n.ACTION_SERVER_PATCH:return(0,l.serverPatchReducer)(e,t);case n.ACTION_RESTORE:return(0,o.restoreReducer)(e,t);case n.ACTION_REFRESH:return(0,a.refreshReducer)(e,t);case n.ACTION_HMR_REFRESH:return(0,c.hmrRefreshReducer)(e,t);case n.ACTION_PREFETCH:return(0,i.prefetchReducer)(e,t);case n.ACTION_SERVER_ACTION:return(0,s.serverActionReducer)(e,t);default:throw Object.defineProperty(Error("Unknown action"),"__NEXT_ERROR_CODE",{value:"E295",enumerable:!1,configurable:!0})}};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9818:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ACTION_HMR_REFRESH:function(){return a},ACTION_NAVIGATE:function(){return n},ACTION_PREFETCH:function(){return o},ACTION_REFRESH:function(){return r},ACTION_RESTORE:function(){return u},ACTION_SERVER_ACTION:function(){return i},ACTION_SERVER_PATCH:function(){return l},PrefetchCacheEntryStatus:function(){return s},PrefetchKind:function(){return c}});let r="refresh",n="navigate",u="restore",l="server-patch",o="prefetch",a="hmr-refresh",i="server-action";var c=function(e){return e.AUTO="auto",e.FULL="full",e.TEMPORARY="temporary",e}({}),s=function(e){return e.fresh="fresh",e.reusable="reusable",e.expired="expired",e.stale="stale",e}({});("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9837:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"InvariantError",{enumerable:!0,get:function(){return r}});class r extends Error{constructor(e,t){super("Invariant: "+(e.endsWith(".")?e:e+".")+" This is a bug in Next.js.",t),this.name="InvariantError"}}},9880:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"clearCacheNodeDataForSegmentPath",{enumerable:!0,get:function(){return function e(t,r,l){let o=l.length<=2,[a,i]=l,c=(0,u.createRouterCacheKey)(i),s=r.parallelRoutes.get(a),f=t.parallelRoutes.get(a);f&&f!==s||(f=new Map(s),t.parallelRoutes.set(a,f));let d=null==s?void 0:s.get(c),p=f.get(c);if(o){p&&p.lazyData&&p!==d||f.set(c,{lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:-1});return}if(!p||!d){p||f.set(c,{lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:-1});return}return p===d&&(p={lazyData:p.lazyData,rsc:p.rsc,prefetchRsc:p.prefetchRsc,head:p.head,prefetchHead:p.prefetchHead,parallelRoutes:new Map(p.parallelRoutes),loading:p.loading},f.set(c,p)),e(p,d,(0,n.getNextFlightSegmentPath)(l))}}});let n=r(2561),u=r(5637);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9921:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"useUntrackedPathname",{enumerable:!0,get:function(){return l}});let n=r(2115),u=r(886);function l(){return(0,n.useContext)(u.PathnameContext)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/app/_not-found/page-7c3ecdf160dc3360.js b/keploy/pkg/service/load/out/_next/static/chunks/app/_not-found/page-7c3ecdf160dc3360.js new file mode 100644 index 0000000..132ec28 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/app/_not-found/page-7c3ecdf160dc3360.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[492],{1137:(e,t,l)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found/page",function(){return l(3303)}])},3303:(e,t,l)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return o}});let r=l(5155),n=l(6395),o=function(){return(0,r.jsx)("html",{children:(0,r.jsx)("body",{children:(0,r.jsx)(n.HTTPAccessErrorFallback,{status:404,message:"This page could not be found."})})})};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4502:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"styles",{enumerable:!0,get:function(){return l}});let l={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{display:"inline-block"},h1:{display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},h2:{fontSize:14,fontWeight:400,lineHeight:"49px",margin:0}};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6395:(e,t,l)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTTPAccessErrorFallback",{enumerable:!0,get:function(){return o}});let r=l(5155),n=l(4502);function o(e){let{status:t,message:l}=e;return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)("title",{children:t+": "+l}),(0,r.jsx)("div",{style:n.styles.error,children:(0,r.jsxs)("div",{children:[(0,r.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,r.jsx)("h1",{className:"next-error-h1",style:n.styles.h1,children:t}),(0,r.jsx)("div",{style:n.styles.desc,children:(0,r.jsx)("h2",{style:n.styles.h2,children:l})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{e.O(0,[441,964,358],()=>e(e.s=1137)),_N_E=e.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/app/layout-bc503d5738af696e.js b/keploy/pkg/service/load/out/_next/static/chunks/app/layout-bc503d5738af696e.js new file mode 100644 index 0000000..0c818e6 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/app/layout-bc503d5738af696e.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[177],{347:()=>{},1012:(e,r,t)=>{Promise.resolve().then(t.t.bind(t,8346,23)),Promise.resolve().then(t.t.bind(t,347,23)),Promise.resolve().then(t.bind(t,3313)),Promise.resolve().then(t.bind(t,2597))},2597:(e,r,t)=>{"use strict";t.d(r,{default:()=>i});var o=t(2115);let a=async()=>{if("serviceWorker"in navigator)try{let e=await navigator.serviceWorker.register("/sw.js",{scope:"/"});console.log("Service Worker registered successfully:",e),e.addEventListener("updatefound",()=>{let r=e.installing;r&&r.addEventListener("statechange",()=>{"installed"===r.state&&navigator.serviceWorker.controller&&confirm("A new version of the app is available. Would you like to refresh?")&&(r.postMessage({type:"SKIP_WAITING"}),window.location.reload())})}),navigator.serviceWorker.addEventListener("controllerchange",()=>{window.location.reload()})}catch(e){console.error("Service Worker registration failed:",e)}},n=async()=>{if("serviceWorker"in navigator)try{let e=await navigator.serviceWorker.getRegistration();e&&(await e.unregister(),sessionStorage.setItem("sw-unregistered",Date.now().toString()),console.log("Service Worker unregistered"),await s())}catch(e){console.error("Service Worker unregistration failed:",e)}},s=async()=>{if("caches"in window)try{let e=await caches.keys();await Promise.all(e.map(e=>(console.log("Clearing cache:",e),caches.delete(e)))),console.log("All caches cleared")}catch(e){console.error("Failed to clear caches:",e)}};function i(){return(0,o.useEffect)(()=>{if("serviceWorker"in navigator){let e=sessionStorage.getItem("sw-unregistered");e&&Date.now()-parseInt(e)>1e4&&sessionStorage.removeItem("sw-unregistered");let r=new URLSearchParams(window.location.search);if("true"===r.get("disable-sw")||"true"===localStorage.getItem("disable-sw")){console.log("\uD83D\uDEAB Service Worker disabled via parameter/localStorage"),n();return}{let e=r.get("dashboard");setTimeout(()=>{"true"!==localStorage.getItem("disable-sw")&&(console.log("✅ Registering Service Worker for offline support"),a())},!(e&&(()=>{try{let r=localStorage.getItem("klt-dashboards");if(r)return JSON.parse(r).some(r=>r.id===e)}catch(e){console.error("Failed to check stored dashboards:",e)}return!1})())&&e?2e3:100)}}},[]),null}},3313:(e,r,t)=>{"use strict";t.d(r,{default:()=>n});var o=t(5155),a=t(2115);function n(){let[e,r]=(0,a.useState)(!0);return((0,a.useEffect)(()=>{let e=()=>{r(navigator.onLine)};return r(navigator.onLine),window.addEventListener("online",e),window.addEventListener("offline",e),()=>{window.removeEventListener("online",e),window.removeEventListener("offline",e)}},[]),e)?null:(0,o.jsx)("div",{className:"fixed top-0 left-0 right-0 bg-yellow-500 text-black text-center py-2 px-4 z-50",children:(0,o.jsx)("span",{className:"text-sm font-medium",children:"\uD83D\uDCE1 You're currently offline. The app is running from cache."})})}},8346:e=>{e.exports={style:{fontFamily:"'Inter', 'Inter Fallback'",fontStyle:"normal"},className:"__className_e8ce0c"}}},e=>{e.O(0,[838,441,964,358],()=>e(e.s=1012)),_N_E=e.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/app/page-a3cd1a73fe99bbf3.js b/keploy/pkg/service/load/out/_next/static/chunks/app/page-a3cd1a73fe99bbf3.js new file mode 100644 index 0000000..1ed22c0 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/app/page-a3cd1a73fe99bbf3.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[974],{2769:(e,t,a)=>{Promise.resolve().then(a.bind(a,7704))},7704:(e,t,a)=>{"use strict";a.r(t),a.d(t,{default:()=>td});var s=a(5155),r=a(2115),n=a(5695),l=a(9708),i=a(2085),o=a(2596),d=a(9688);function c(){for(var e=arguments.length,t=Array(e),a=0;asvg]:px-3",sm:"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9"}},defaultVariants:{variant:"default",size:"default"}});function m(e){let{className:t,variant:a,size:r,asChild:n=!1,...i}=e,o=n?l.DX:"button";return(0,s.jsx)(o,{"data-slot":"button",className:c(u({variant:a,size:r,className:t})),...i})}let h=r.createContext(null);function x(){let e=r.useContext(h);if(!e)throw Error("useSidebar must be used within a SidebarProvider");return e}function g(e){let{children:t}=e,[a,n]=r.useState(!0);return(0,s.jsx)(h.Provider,{value:{isOpen:a,toggle:()=>n(!a)},children:(0,s.jsx)("div",{className:"flex h-screen",children:t})})}function p(e){let{children:t,className:a}=e,{isOpen:r}=x();return(0,s.jsx)("div",{"data-sidebar":!0,className:c("bg-sidebar border-r border-sidebar-border transition-all duration-300",r?"w-64":"w-16",a),children:t})}function f(e){let{children:t,className:a}=e;return(0,s.jsx)("div",{className:c("p-4 border-b border-sidebar-border",a),children:t})}function v(e){let{children:t,className:a}=e;return(0,s.jsx)("div",{className:c("flex-1 overflow-y-auto p-4",a),children:t})}function b(e){let{className:t}=e,{toggle:a}=x();return(0,s.jsx)(m,{onClick:a,variant:"ghost",size:"sm",className:c("p-1 cursor-pointer",t),children:(0,s.jsx)("svg",{className:"h-4 w-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 6h16M4 12h16M4 18h16"})})})}function j(e){let{children:t,className:a}=e;return(0,s.jsx)("main",{className:c("flex-1 overflow-auto",a),children:t})}var y=a(5604);let N=y.Ik({step_name:y.Yj(),step_count:y.ai().int().nonnegative(),step_failure:y.ai().int().nonnegative(),step_response_time:y.YO(y.ai().nonnegative()),step_bytes_in:y.ai().int().nonnegative(),step_bytes_out:y.ai().int().nonnegative()}),w=y.Ik({vu_id:y.ai().int().min(0),ts_exec_count:y.ai().int().nonnegative(),ts_exec_failure:y.ai().int().nonnegative(),ts_exec_time:y.YO(y.ai().nonnegative()),steps:y.YO(N)}),_=y.Ik({Target:y.ai().int().positive(),Duration:y.Yj()}),S=y.Ik({Metric:y.Yj(),Condition:y.Yj(),Severity:y.Yj(),Value:y.ai()}),k=y.Ik({Profile:y.Yj(),VUs:y.ai().int().positive(),Duration:y.Yj(),RPS:y.ai().int().positive(),Stages:y.YO(_).optional(),Thresholds:y.YO(S).optional()}),A=y.Ik({check_id:y.Yj(),check_name:y.Yj(),status:y.k5(["passed","failed","warning"]),severity:y.Yj(),description:y.Yj(),details:y.Yj().optional(),recommendation:y.Yj().optional(),step_name:y.Yj(),step_method:y.Yj(),step_url:y.Yj(),status_code:y.ai().int().optional(),target:y.k5(["request","response"])}),C=y.Ik({step_name:y.Yj(),step_method:y.Yj(),step_url:y.Yj(),results:y.YO(A),passed:y.ai().int().nonnegative(),failed:y.ai().int().nonnegative(),warnings:y.ai().int().nonnegative()}),I=y.Ik({test_suite:y.Yj(),timestamp:y.Yj(),total_checks:y.ai().int().nonnegative(),passed:y.ai().int().nonnegative(),failed:y.ai().int().nonnegative(),warnings:y.ai().int().nonnegative(),steps:y.YO(C),summary:y.g1(y.Yj(),y.ai().int().nonnegative())}),P=y.Ik({id:y.Yj().min(1),url:y.Yj().url(),title:y.Yj().min(1),created_at:y.Yj(),description:y.Yj(),load_options:k,security_report:I.optional(),end_at:y.Yj().optional()}),T=y.Ik({timestamp:y.ai().int().positive(),value:y.ai().nonnegative()}),E=y.Ik({timestamp:y.ai().int().positive(),avg_latency:y.ai().nonnegative()}),z=y.Ik({overall:y.YO(E),perStep:y.g1(y.Yj(),y.YO(T)),perVU:y.g1(y.Yj(),y.YO(T))});y.YO(P);let O=y.YO(w);y.Ik({dashboardData:y.g1(y.Yj(),O),chartHistories:y.g1(y.Yj(),z),dashboardStopTimes:y.g1(y.Yj(),y.Yj()),dashboardCloseTimes:y.g1(y.Yj(),y.Yj()).optional(),dashboardOpenedStatus:y.g1(y.Yj(),y.zM()).optional()});var D=a(464),Y=a(574),M=a(5525);function V(e){let{selectedDashboard:t,currentView:a="dashboard",onViewChange:r}=e,{isOpen:n}=x(),l=e=>{null==r||r(e)},i=null==t?void 0:t.security_report;return(0,s.jsxs)(p,{children:[(0,s.jsx)(f,{children:(0,s.jsx)("div",{className:"flex items-center gap-2 w-full",children:(0,s.jsxs)("div",{className:"p-2 text-orange-900 h-full w-full flex items-center ".concat(n?"ml-4":"justify-center"),children:[(0,s.jsx)(D.A,{className:"h-4 w-4"}),n&&(0,s.jsx)("h2",{className:"font-semibold text-lg ml-2",children:"KLT Dashboard"})]})})}),(0,s.jsx)(v,{children:(0,s.jsxs)("div",{className:"space-y-2",children:[(0,s.jsxs)(m,{onClick:()=>l("dashboard"),variant:"dashboard"===a?"default":"ghost",className:"w-full ".concat(n?"justify-start":"justify-center"," gap-2 ").concat("dashboard"===a?"bg-orange-100 text-orange-900 hover:bg-orange-200":"text-orange-900 hover:bg-orange-100"),children:[(0,s.jsx)(Y.A,{className:"h-4 w-4"}),n&&"Dashboard"]}),(0,s.jsxs)(m,{onClick:()=>l("security"),disabled:!i,variant:"security"===a?"default":"ghost",className:"w-full ".concat(n?"justify-start":"justify-center"," gap-2 ").concat("security"===a?"bg-orange-100 text-orange-900 hover:bg-orange-200":i?"text-orange-900 hover:bg-orange-100":"text-orange-400 cursor-not-allowed"),children:[(0,s.jsx)(M.A,{className:"h-4 w-4"}),n&&(0,s.jsxs)("span",{children:["Security Report",!i&&(0,s.jsx)("span",{className:"text-xs ml-1",children:"(N/A)"})]})]}),n&&i&&(null==t?void 0:t.security_report)&&(0,s.jsxs)("div",{className:"mt-4 p-3 bg-orange-50 rounded-md",children:[(0,s.jsx)("div",{className:"text-xs font-medium text-orange-900 mb-2",children:"Security Summary"}),(0,s.jsxs)("div",{className:"space-y-1 text-xs text-orange-700",children:[(0,s.jsxs)("div",{className:"flex justify-between",children:[(0,s.jsx)("span",{children:"Total Checks:"}),(0,s.jsx)("span",{className:"font-medium",children:t.security_report.total_checks})]}),(0,s.jsxs)("div",{className:"flex justify-between",children:[(0,s.jsx)("span",{children:"Passed:"}),(0,s.jsx)("span",{className:"font-medium text-green-600",children:t.security_report.passed})]}),(0,s.jsxs)("div",{className:"flex justify-between",children:[(0,s.jsx)("span",{children:"Failed:"}),(0,s.jsx)("span",{className:"font-medium text-red-600",children:t.security_report.failed})]}),(0,s.jsxs)("div",{className:"flex justify-between",children:[(0,s.jsx)("span",{children:"Warnings:"}),(0,s.jsx)("span",{className:"font-medium text-yellow-600",children:t.security_report.warnings})]})]})]})]})})]})}let R=(0,r.createContext)(void 0);function U(e){let{children:t}=e,[a,n]=(0,r.useState)(null);return(0,s.jsx)(R.Provider,{value:{selectedDashboard:a,setSelectedDashboard:n},children:t})}function L(){let e=(0,r.useContext)(R);if(void 0===e)throw Error("useDashboard must be used within a DashboardProvider");return e}function K(e){let{lttoken:t,currentVUCount:a=0,stopped:n=!1,stopTime:l}=e,i=function(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],a=arguments.length>2?arguments[2]:void 0,[s,n]=(0,r.useState)("0s");return(0,r.useEffect)(()=>{if(!e)return;if(t&&a)return void n(a);if(t)return void n("0s");let s=()=>{let t=new Date(e).getTime(),a=Math.max(0,Math.floor((Date.now()-t)/1e3)),s=Math.floor(a/3600),r=Math.floor(a%3600/60),l=a%60;s>0?n("".concat(s,"h ").concat(r,"m ").concat(l,"s")):r>0?n("".concat(r,"m ").concat(l,"s")):n("".concat(l,"s"))};s();let r=setInterval(s,1e3);return()=>clearInterval(r)},[e,t,a]),s}(null==t?void 0:t.created_at,n,l);if(!t)return(0,s.jsx)("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6",children:(0,s.jsx)(F,{children:(0,s.jsxs)(J,{className:"text-center",children:[(0,s.jsx)("div",{className:"text-2xl font-bold text-gray-400",children:"--"}),(0,s.jsx)("div",{className:"text-sm text-gray-500",children:"Select a dashboard"})]})})});let o=[{label:"Profile",value:t.load_options.Profile,color:"text-gray-600"},{label:"Active VUs",value:"".concat(a," / ").concat(t.load_options.VUs),color:"text-gray-600"},{label:"Target Duration",value:t.load_options.Duration,color:"text-gray-600"},{label:n?"Final Runtime":"Elapsed Time",value:n?l||"Stopped":i,color:n?"text-red-600":"text-blue-600"},{label:"Target RPS",value:t.load_options.RPS.toLocaleString(),color:"text-gray-600"},{label:"Status",value:n?"Completed":"Running",color:n?"text-red-600":"text-green-600"}];return(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsx)("div",{className:"bg-orange-50 border border-orange-200 rounded-lg p-4",children:(0,s.jsxs)("div",{className:"flex justify-between items-start",children:[(0,s.jsxs)("div",{className:"flex items-center gap-3",children:[(0,s.jsx)(Y.A,{className:"h-10 w-10 text-orange-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-xl font-semibold text-orange-900",children:t.title}),(0,s.jsx)("p",{className:"text-orange-700 text-sm mt-1",children:t.description}),(0,s.jsxs)("p",{className:"text-orange-600 text-xs mt-2",children:["ID: ",t.id]})]})]}),(0,s.jsx)("span",{className:"px-3 py-1 rounded-full text-xs font-medium ".concat(n?"bg-red-100 text-red-800":"bg-green-100 text-green-800"),children:n?"Completed":"Running"})]})}),(0,s.jsx)("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-4",children:o.map(e=>(0,s.jsx)(F,{children:(0,s.jsxs)(J,{className:"text-center",children:[(0,s.jsx)("div",{className:"text-2xl font-bold ".concat(e.color),children:e.value}),(0,s.jsx)("div",{className:"text-sm text-orange-600",children:e.label})]})},e.label))})]})}function F(e){let{children:t,className:a=""}=e;return(0,s.jsx)("div",{className:"bg-orange-50 border border-orange-200 rounded-lg shadow-sm ".concat(a),children:t})}function J(e){let{children:t,className:a=""}=e;return(0,s.jsx)("div",{className:"p-4 ".concat(a),children:t})}var G=a(6268),H=a(1032);function q(e){let{data:t,columns:a,enableRowSelection:s=!0,defaultPageIndex:n,defaultPageSize:l,getRowId:i}=e,[o,d]=r.useState({}),[c,u]=r.useState({}),[m,h]=r.useState([]),[x,g]=r.useState([]),[p,f]=r.useState({pageIndex:null!=n?n:0,pageSize:null!=l?l:10});return(0,G.N4)({data:t,columns:a,state:{sorting:x,columnVisibility:c,rowSelection:o,columnFilters:m,pagination:p},enableRowSelection:s,getRowId:null!=i?i:e=>e.id.toString(),onRowSelectionChange:d,onSortingChange:g,onColumnFiltersChange:h,onColumnVisibilityChange:u,onPaginationChange:f,getCoreRowModel:(0,H.HT)(),getFilteredRowModel:(0,H.hM)(),getPaginationRowModel:(0,H.kW)(),getSortedRowModel:(0,H.h5)(),getFacetedRowModel:(0,H.kQ)(),getFacetedUniqueValues:(0,H.oS)()})}var B=a(17);function W(e){let{className:t,...a}=e;return(0,s.jsx)(B.bL,{"data-slot":"tabs",className:c("flex flex-col gap-2",t),...a})}function Q(e){let{className:t,...a}=e;return(0,s.jsx)(B.B8,{"data-slot":"tabs-list",className:c("bg-orange-200/60 text-orange-700 inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",t),...a})}function X(e){let{className:t,...a}=e;return(0,s.jsx)(B.l9,{"data-slot":"tabs-trigger",className:c("data-[state=active]:bg-orange-50 data-[state=active]:text-orange-900 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring data-[state=active]:border-orange-300 data-[state=active]:bg-orange-100 text-orange-800 hover:bg-orange-100/50 hover:text-orange-900 inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",t),...a})}var Z=a(5143),$=a(1206),ee=a(402);function et(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"table-container",className:"relative w-full overflow-x-auto",children:(0,s.jsx)("table",{"data-slot":"table",className:c("w-full caption-bottom text-sm",t),...a})})}function ea(e){let{className:t,...a}=e;return(0,s.jsx)("thead",{"data-slot":"table-header",className:c("[&_tr]:border-b",t),...a})}function es(e){let{className:t,...a}=e;return(0,s.jsx)("tbody",{"data-slot":"table-body",className:c("[&_tr:last-child]:border-0",t),...a})}function er(e){let{className:t,...a}=e;return(0,s.jsx)("tr",{"data-slot":"table-row",className:c("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",t),...a})}function en(e){let{className:t,...a}=e;return(0,s.jsx)("th",{"data-slot":"table-head",className:c("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",t),...a})}function el(e){let{className:t,...a}=e;return(0,s.jsx)("td",{"data-slot":"table-cell",className:c("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",t),...a})}var ei=a(8266);function eo(e){let{row:t}=e,{transform:a,transition:r,setNodeRef:n,isDragging:l}=(0,ee.gl)({id:t.original.id});return(0,s.jsx)(er,{"data-state":t.getIsSelected()&&"selected","data-dragging":l,ref:n,className:"relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80",style:{transform:ei.Ks.Transform.toString(a),transition:r},children:t.getVisibleCells().map(e=>(0,s.jsx)(el,{children:(0,G.Kv)(e.column.columnDef.cell,e.getContext())},e.id))})}function ed(e){let{table:t,columns:a,dndEnabled:n=!1,onReorder:l}=e,i=t.getRowModel().rows.map(e=>Number(e.id)),o=r.useId(),d=(0,Z.FR)((0,Z.MS)(Z.cA,{}),(0,Z.MS)(Z.IG,{}),(0,Z.MS)(Z.uN,{})),c=(0,s.jsxs)(et,{children:[(0,s.jsx)(ea,{className:"bg-orange-100 sticky top-0 z-10",children:t.getHeaderGroups().map(e=>(0,s.jsx)(er,{children:e.headers.map(e=>(0,s.jsx)(en,{colSpan:e.colSpan,children:e.isPlaceholder?null:(0,G.Kv)(e.column.columnDef.header,e.getContext())},e.id))},e.id))}),(0,s.jsx)(es,{className:"**:data-[slot=table-cell]:first:w-8",children:function(e){let{table:t,columns:a,dndEnabled:r,dataIds:n}=e;return t.getRowModel().rows.length?r?(0,s.jsx)(ee.gB,{items:n,strategy:ee._G,children:t.getRowModel().rows.map(e=>(0,s.jsx)(eo,{row:e},e.id))}):t.getRowModel().rows.map(e=>(0,s.jsx)(er,{"data-state":e.getIsSelected()&&"selected",children:e.getVisibleCells().map(e=>(0,s.jsx)(el,{children:(0,G.Kv)(e.column.columnDef.cell,e.getContext())},e.id))},e.id)):(0,s.jsx)(er,{children:(0,s.jsx)(el,{colSpan:a.length,className:"h-24 text-center",children:"No results."})})}({table:t,columns:a,dndEnabled:n,dataIds:i})})]});return n?(0,s.jsx)(Z.Mp,{collisionDetection:Z.fp,modifiers:[$.FN],onDragEnd:function(e){let{active:a,over:s}=e;if(a&&s&&a.id!==s.id&&l){let e=i.indexOf(a.id),r=i.indexOf(s.id);l((0,ee.be)(t.options.data,e,r))}},sensors:d,id:o,children:c}):c}var ec=a(2278),eu=a(2355),em=a(3052),eh=a(2767),ex=a(968);function eg(e){let{className:t,...a}=e;return(0,s.jsx)(ex.b,{"data-slot":"label",className:c("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",t),...a})}var ep=a(1918),ef=a(6474),ev=a(5196),eb=a(7863);function ej(e){let{...t}=e;return(0,s.jsx)(ep.bL,{"data-slot":"select",...t})}function ey(e){let{...t}=e;return(0,s.jsx)(ep.WT,{"data-slot":"select-value",...t})}function eN(e){let{className:t,size:a="default",children:r,...n}=e;return(0,s.jsxs)(ep.l9,{"data-slot":"select-trigger","data-size":a,className:c("border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",t),...n,children:[r,(0,s.jsx)(ep.In,{asChild:!0,children:(0,s.jsx)(ef.A,{className:"size-4 opacity-50"})})]})}function ew(e){let{className:t,children:a,position:r="popper",...n}=e;return(0,s.jsx)(ep.ZL,{children:(0,s.jsxs)(ep.UC,{"data-slot":"select-content",className:c("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md","popper"===r&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",t),position:r,...n,children:[(0,s.jsx)(eS,{}),(0,s.jsx)(ep.LM,{className:c("p-1","popper"===r&&"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"),children:a}),(0,s.jsx)(ek,{})]})})}function e_(e){let{className:t,children:a,...r}=e;return(0,s.jsxs)(ep.q7,{"data-slot":"select-item",className:c("focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",t),...r,children:[(0,s.jsx)("span",{className:"absolute right-2 flex size-3.5 items-center justify-center",children:(0,s.jsx)(ep.VF,{children:(0,s.jsx)(ev.A,{className:"size-4"})})}),(0,s.jsx)(ep.p4,{children:a})]})}function eS(e){let{className:t,...a}=e;return(0,s.jsx)(ep.PP,{"data-slot":"select-scroll-up-button",className:c("flex cursor-default items-center justify-center py-1",t),...a,children:(0,s.jsx)(eb.A,{className:"size-4"})})}function ek(e){let{className:t,...a}=e;return(0,s.jsx)(ep.wn,{"data-slot":"select-scroll-down-button",className:c("flex cursor-default items-center justify-center py-1",t),...a,children:(0,s.jsx)(ef.A,{className:"size-4"})})}function eA(e){let{table:t}=e;return(0,s.jsx)("div",{className:"flex items-center justify-between px-4",children:(0,s.jsxs)("div",{className:"flex w-full items-center gap-8 lg:w-fit",children:[(0,s.jsxs)("div",{className:"hidden items-center gap-2 lg:flex",children:[(0,s.jsx)(eg,{htmlFor:"rows-per-page",className:"text-sm font-medium",children:"Rows per page"}),(0,s.jsxs)(ej,{value:"".concat(t.getState().pagination.pageSize),onValueChange:e=>t.setPageSize(Number(e)),children:[(0,s.jsx)(eN,{className:"h-8 w-[70px] bg-orange-50 border border-orange-200 hover:bg-orange-100",id:"rows-per-page",children:(0,s.jsx)(ey,{placeholder:t.getState().pagination.pageSize})}),(0,s.jsx)(ew,{side:"top",className:"bg-orange-50 border border-orange-200 shadow-lg",children:[10,20,30,40,50].map(e=>(0,s.jsx)(e_,{value:"".concat(e),className:"hover:bg-orange-100",children:e},e))})]})]}),(0,s.jsxs)("div",{className:"flex w-fit items-center justify-center text-sm font-medium",children:["Page ",t.getState().pagination.pageIndex+1," of ",t.getPageCount()]}),(0,s.jsxs)("div",{className:"ml-auto flex items-center gap-2 lg:ml-0",children:[(0,s.jsxs)(m,{variant:"outline",className:"hidden h-8 w-8 p-0 lg:flex",onClick:()=>t.setPageIndex(0),disabled:!t.getCanPreviousPage(),children:[(0,s.jsx)("span",{className:"sr-only",children:"Go to first page"}),(0,s.jsx)(ec.A,{})]}),(0,s.jsxs)(m,{variant:"outline",className:"size-8",size:"icon",onClick:()=>t.previousPage(),disabled:!t.getCanPreviousPage(),children:[(0,s.jsx)("span",{className:"sr-only",children:"Go to previous page"}),(0,s.jsx)(eu.A,{})]}),(0,s.jsxs)(m,{variant:"outline",className:"size-8",size:"icon",onClick:()=>t.nextPage(),disabled:!t.getCanNextPage(),children:[(0,s.jsx)("span",{className:"sr-only",children:"Go to next page"}),(0,s.jsx)(em.A,{})]}),(0,s.jsxs)(m,{variant:"outline",className:"hidden size-8 lg:flex",size:"icon",onClick:()=>t.setPageIndex(t.getPageCount()-1),disabled:!t.getCanNextPage(),children:[(0,s.jsx)("span",{className:"sr-only",children:"Go to last page"}),(0,s.jsx)(eh.A,{})]})]})]})})}function eC(e){if(!Array.isArray(e)||0===e.length)return"-";let t=[...e].sort((e,t)=>e-t),a=Math.floor(.95*(t.length-1));return(t[a]/1e6).toFixed(2)}function eI(e){if(!Array.isArray(e)||0===e.length)return"-";let t=[...e].sort((e,t)=>e-t),a=Math.floor(.9*(t.length-1));return(t[a]/1e6).toFixed(2)}function eP(e){if(!Array.isArray(e)||0===e.length)return"-";let t=[...e].sort((e,t)=>e-t),a=Math.floor(.99*(t.length-1));return(t[a]/1e6).toFixed(2)}function eT(e){return"number"!=typeof e||isNaN(e)?"-":(e/1048576).toFixed(2)}function eE(e){let{data:t,isCompleted:a=!1}=e,[n,l]=r.useState(()=>null!=t?t:[]),[i,o]=r.useState("virtual-users"),[d,c]=r.useState(()=>null!=t?t:[]),[u,m]=r.useState(()=>(null!=t?t:[]).flatMap(e=>Array.isArray(e.steps)?e.steps.map(t=>({...t,vu_id:e.vu_id})):[]));r.useEffect(()=>{console.log("DataTable updating with data:",(null==t?void 0:t.length)||0,"records"),l(null!=t?t:[]),c(null!=t?t:[]),m((null!=t?t:[]).flatMap(e=>Array.isArray(e.steps)?e.steps.map(t=>({...t,vu_id:e.vu_id})):[]))},[t]),r.useEffect(()=>{"virtual-users"===i?c(n):"steps"===i&&m(n.flatMap(e=>Array.isArray(e.steps)?e.steps.map(t=>({...t,vu_id:e.vu_id})):[]))},[i,n]);let h=r.useMemo(()=>{let e={},t={};return n.forEach(a=>{Array.isArray(a.steps)&&a.steps.forEach(a=>{let s=a.step_name;if(s&&(e[s]||(e[s]={step_name:s,step_count:0,step_failure:0,step_response_time:[],step_bytes_in:0,step_bytes_out:0,step_response_time_averages:[]},t[s]=[]),e[s].step_count+="number"==typeof a.step_count?a.step_count:0,e[s].step_failure+="number"==typeof a.step_failure?a.step_failure:0,e[s].step_bytes_in+="number"==typeof a.step_bytes_in?a.step_bytes_in:0,e[s].step_bytes_out+="number"==typeof a.step_bytes_out?a.step_bytes_out:0,Array.isArray(a.step_response_time)&&a.step_response_time.length>0)){e[s].step_response_time.push(...a.step_response_time);let r=a.step_response_time.reduce((e,t)=>e+t,0)/a.step_response_time.length;t[s].push(r)}})}),Object.keys(e).forEach(a=>{e[a].step_response_time_averages=t[a]}),Object.values(e)},[n]);r.useEffect(()=>{"virtual-users"===i?c(n):"steps"===i&&m(h)},[i,h]);let x=[{accessorKey:"vu_id",header:"ID"},{accessorKey:"ts_exec_count",header:"TSExecCount"},{accessorKey:"ts_exec_failure",header:"TSExecFailure"},{accessorKey:"steps",header:"StepExecCount",cell:e=>{let{row:t}=e;return Array.isArray(t.original.steps)?t.original.steps.reduce((e,t)=>e+("number"==typeof t.step_count?t.step_count:0),0):"-"}},{accessorKey:"ts_exec_time",header:"AvgTSExecTime(ms)",cell:e=>{let{row:t}=e;var a=t.original.ts_exec_time;return Array.isArray(a)&&0!==a.length?(a.reduce((e,t)=>e+t,0)/a.length/1e6).toFixed(2):"-"}},{accessorKey:"ts_exec_time_p90",header:"P90TSExecTime(ms)",cell:e=>{let{row:t}=e;return eI(t.original.ts_exec_time)}},{accessorKey:"ts_exec_time_p95",header:"P95TSExecTime(ms)",cell:e=>{let{row:t}=e;return eC(t.original.ts_exec_time)}},{accessorKey:"ts_exec_time_p99",header:"P99TSExecTime(ms)",cell:e=>{let{row:t}=e;return eP(t.original.ts_exec_time)}}],g=[{accessorKey:"step_name",header:"Step Name"},{accessorKey:"step_count",header:"Count(All VUs)",cell:e=>{var t;let{row:a}=e;return null!=(t=a.original.step_count)?t:"-"}},{accessorKey:"step_failure",header:"Failure(All VUs)",cell:e=>{var t;let{row:a}=e;return null!=(t=a.original.step_failure)?t:"-"}},{accessorKey:"step_bytes_in",header:"BytesIn(MB)",cell:e=>{let{row:t}=e;return eT(t.original.step_bytes_in)}},{accessorKey:"step_bytes_out",header:"BytesOut (MB)",cell:e=>{let{row:t}=e;return eT(t.original.step_bytes_out)}},{accessorKey:"step_response_time",header:"AvgResponseTime(ms)",cell:e=>{let{row:t}=e,a=t.original.step_response_time_averages;return Array.isArray(a)&&0!==a.length?(a.reduce((e,t)=>e+t,0)/a.length/1e6).toFixed(2):"-"}},{accessorKey:"p90",header:"P90(ms)",cell:e=>{let{row:t}=e;return eI(t.original.step_response_time)}},{accessorKey:"p95",header:"P95(ms)",cell:e=>{let{row:t}=e;return eC(t.original.step_response_time)}},{accessorKey:"p99",header:"P99(ms)",cell:e=>{let{row:t}=e;return eP(t.original.step_response_time)}}],p=q({data:d,columns:x,getRowId:(e,t)=>null!=e.vu_id?e.vu_id.toString():"row-".concat(t)}),f=q({data:u,columns:g,getRowId:(e,t)=>null!=e.vu_id&&null!=e.step_name?"".concat(e.vu_id,"-").concat(e.step_name):e.step_name?e.step_name+"-"+t:"row-"+t});return t&&0!==t.length?(0,s.jsxs)("div",{className:"w-full bg-orange-50 border border-orange-200 rounded-lg overflow-hidden",children:[(0,s.jsx)("div",{className:"p-4 border-b border-orange-200 bg-orange-100",children:(0,s.jsxs)("div",{className:"flex items-center justify-between",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("h3",{className:"text-lg font-semibold text-orange-900",children:"Load Test Data"}),(0,s.jsx)("p",{className:"text-sm text-orange-700",children:"virtual-users"===i?"".concat(d.length," Virtual Users"):"".concat(h.length," Unique Steps")})]}),(0,s.jsx)(W,{value:i,onValueChange:e=>o(e),className:"w-fit",children:(0,s.jsxs)(Q,{children:[(0,s.jsx)(X,{value:"virtual-users",children:"Virtual Users"}),(0,s.jsx)(X,{value:"steps",children:"Steps"})]})})]})}),(0,s.jsxs)("div",{className:"relative flex flex-col",children:[(0,s.jsx)("div",{className:"overflow-hidden px-2",children:"virtual-users"===i?(0,s.jsx)(ed,{dndEnabled:!0,table:p,columns:x,onReorder:c}):(0,s.jsx)(ed,{dndEnabled:!0,table:f,columns:g,onReorder:m})}),(0,s.jsx)("div",{className:"p-4 border-t border-orange-200 bg-orange-100",children:"virtual-users"===i?(0,s.jsx)(eA,{table:p}):(0,s.jsx)(eA,{table:f})})]})]}):(0,s.jsx)("div",{className:"w-full bg-orange-50 border border-orange-200 rounded-lg p-8 text-center text-orange-600",children:"No VU data available"})}var ez=a(8247),eO=a(3406),eD=a(7156);function eY(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"card",className:c("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",t),...a})}function eM(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"card-header",className:c("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",t),...a})}function eV(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"card-title",className:c("leading-none font-semibold",t),...a})}function eR(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"card-description",className:c("text-muted-foreground text-sm",t),...a})}function eU(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"card-content",className:c("px-6",t),...a})}var eL=a(3540),eK=a(8124),eF=a(2319);let eJ={light:"",dark:".dark"},eG=r.createContext(null);function eH(e){let{id:t,className:a,children:n,config:l,...i}=e,o=r.useId(),d="chart-".concat(t||o.replace(/:/g,""));return(0,s.jsx)(eG.Provider,{value:{config:l},children:(0,s.jsxs)("div",{"data-slot":"chart","data-chart":d,className:c("[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",a),...i,children:[(0,s.jsx)(eq,{id:d,config:l}),(0,s.jsx)(eL.u,{children:n})]})})}let eq=e=>{let{id:t,config:a}=e,r=Object.entries(a).filter(e=>{let[,t]=e;return t.theme||t.color});return r.length?(0,s.jsx)("style",{dangerouslySetInnerHTML:{__html:Object.entries(eJ).map(e=>{let[a,s]=e;return"\n".concat(s," [data-chart=").concat(t,"] {\n").concat(r.map(e=>{var t;let[s,r]=e,n=(null==(t=r.theme)?void 0:t[a])||r.color;return n?" --color-".concat(s,": ").concat(n,";"):null}).join("\n"),"\n}\n")}).join("\n")}}):null},eB=eK.m;function eW(e){let{active:t,payload:a,className:n,indicator:l="dot",hideLabel:i=!1,hideIndicator:o=!1,label:d,labelFormatter:u,labelClassName:m,formatter:h,color:x,nameKey:g,labelKey:p}=e,{config:f}=function(){let e=r.useContext(eG);if(!e)throw Error("useChart must be used within a ");return e}(),v=r.useMemo(()=>{var e;if(i||!(null==a?void 0:a.length))return null;let[t]=a,r="".concat(p||(null==t?void 0:t.dataKey)||(null==t?void 0:t.name)||"value"),n=eQ(f,t,r),l=p||"string"!=typeof d?null==n?void 0:n.label:(null==(e=f[d])?void 0:e.label)||d;return u?(0,s.jsx)("div",{className:c("font-medium",m),children:u(l,a)}):l?(0,s.jsx)("div",{className:c("font-medium",m),children:l}):null},[d,u,a,i,m,f,p]);if(!t||!(null==a?void 0:a.length))return null;let b=1===a.length&&"dot"!==l;return(0,s.jsxs)("div",{className:c("border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",n),children:[b?null:v,(0,s.jsx)("div",{className:"grid gap-1.5",children:a.map((e,t)=>{let a="".concat(g||e.name||e.dataKey||"value"),r=eQ(f,e,a),n=x||(null==r?void 0:r.color)||e.color||e.stroke||e.fill||"hsl(var(--chart-".concat(t%5+1,"))");return(0,s.jsx)("div",{className:c("[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5","dot"===l&&"items-center"),children:h&&(null==e?void 0:e.value)!==void 0&&e.name?h(e.value,e.name,e,t,e.payload):(0,s.jsxs)(s.Fragment,{children:[(null==r?void 0:r.icon)?(0,s.jsx)(r.icon,{}):!o&&(0,s.jsx)("div",{className:c("shrink-0 rounded-[2px]",{"h-2.5 w-2.5":"dot"===l,"w-1":"line"===l,"w-0 border-[1.5px] border-dashed bg-transparent":"dashed"===l,"my-0.5":b&&"dashed"===l}),style:{backgroundColor:"dashed"!==l?n:"transparent",borderColor:n,..."dashed"===l&&{borderStyle:"dashed"}}}),(0,s.jsxs)("div",{className:c("flex flex-1 justify-between leading-none",b?"items-end":"items-center"),children:[(0,s.jsxs)("div",{className:"grid gap-1.5",children:[b?v:null,(0,s.jsx)("span",{className:"text-muted-foreground",children:(null==r?void 0:r.label)||e.name})]}),e.value&&(0,s.jsx)("span",{className:"text-foreground font-mono font-medium tabular-nums",children:"number"==typeof e.value?e.value.toLocaleString():e.value})]})]})},e.dataKey||t)})})]})}function eQ(e,t,a){if("object"!=typeof t||null===t)return;let s="payload"in t&&"object"==typeof t.payload&&null!==t.payload?t.payload:void 0,r=a;return a in t&&"string"==typeof t[a]?r=t[a]:s&&a in s&&"string"==typeof s[a]&&(r=s[a]),r in e?e[r]:e[a]}eF.s;var eX=a(2493);let eZ=(0,i.F)("inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",{variants:{variant:{default:"bg-transparent",outline:"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"},size:{default:"h-9 px-2 min-w-9",sm:"h-8 px-1.5 min-w-8",lg:"h-10 px-2.5 min-w-10"}},defaultVariants:{variant:"default",size:"default"}}),e$=r.createContext({size:"default",variant:"default"});function e0(e){let{className:t,variant:a,size:r,children:n,...l}=e;return(0,s.jsx)(eX.bL,{"data-slot":"toggle-group","data-variant":a,"data-size":r,className:c("group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",t),...l,children:(0,s.jsx)(e$.Provider,{value:{variant:a,size:r},children:n})})}function e1(e){let{className:t,children:a,variant:n,size:l,...i}=e,o=r.useContext(e$);return(0,s.jsx)(eX.q7,{"data-slot":"toggle-group-item","data-variant":o.variant||n,"data-size":o.size||l,className:c(eZ({variant:o.variant||n,size:o.size||l}),"min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",t),...i,children:a})}function e2(e){return Array.isArray(e)&&0!==e.length?e.reduce((e,t)=>e+t,0)/e.length:0}function e4(e){let{vuData:t=[],chartHistory:a,setChartHistory:n,isCompleted:l=!1}=e,[i,o]=r.useState("overall"),[d,c]=r.useState(!1),u=r.useRef("");r.useEffect(()=>{if(!Array.isArray(t)||0===t.length)return;if(l&&a.overall.length>0)return void console.log("Dashboard is completed and chart history exists, skipping chart updates");let e=JSON.stringify(t.map(e=>{var t;return{id:e.vu_id,stepCount:(null==(t=e.steps)?void 0:t.length)||0,timestamp:Date.now()}}));if(e===u.current)return;u.current=e;let s=Date.now(),r=[];t.forEach(e=>{var t;null==(t=e.steps)||t.forEach(e=>{Array.isArray(e.step_response_time)&&r.push(...e.step_response_time)})});let i=e2(r)/1e6,o=Array.from(new Set(t.flatMap(e=>{var t;return(null==(t=e.steps)?void 0:t.map(e=>e.step_name))||[]}))).filter(Boolean);n(e=>{let a={...e.perStep};o.forEach(e=>{let r=[];t.forEach(t=>{var a;null==(a=t.steps)||a.forEach(t=>{t.step_name===e&&Array.isArray(t.step_response_time)&&r.push(...t.step_response_time)})});let n=e2(r)/1e6;a[e]||(a[e]=[]),a[e]=[...a[e],{timestamp:s,value:n}]});let r=Array.from(new Set(t.map(e=>e.vu_id.toString()))),n={...e.perVU};return r.forEach(e=>{let a=[];t.forEach(t=>{if(t.vu_id.toString()===e){var s;null==(s=t.steps)||s.forEach(e=>{Array.isArray(e.step_response_time)&&a.push(...e.step_response_time)})}});let r=e2(a)/1e6;n[e]||(n[e]=[]),n[e]=[...n[e],{timestamp:s,value:r}]}),{overall:[...e.overall,{timestamp:s,avg_latency:i}],perStep:a,perVU:n}})},[t,l]);let h=r.useMemo(()=>{if("overall"===i)return a.overall.map(e=>({timestamp:e.timestamp,avg_latency:e.avg_latency}));if("perStep"===i){let e=Object.keys(a.perStep);return Array.from(new Set(e.flatMap(e=>(a.perStep[e]||[]).map(e=>e.timestamp)))).sort((e,t)=>e-t).map(t=>{let s={timestamp:t};return e.forEach(e=>{let r=(a.perStep[e]||[]).find(e=>e.timestamp===t);r&&(s[e]=r.value)}),s})}if("perVU"===i){let e=Object.keys(a.perVU);return Array.from(new Set(e.flatMap(e=>(a.perVU[e]||[]).map(e=>e.timestamp)))).sort((e,t)=>e-t).map(t=>{let s={timestamp:t};return e.forEach(e=>{let r=(a.perVU[e]||[]).find(e=>e.timestamp===t);r&&(s[e]=r.value)}),s})}return[]},[a,i]),x=r.useMemo(()=>{if("overall"===i)return{avg_latency:{label:"Avg Latency (ms)",color:"var(--chart-1)"}};if("perStep"===i){let e=Object.keys(a.perStep),t=["var(--chart-1)","var(--chart-2)","var(--chart-3)","var(--chart-4)","var(--chart-5)"];return Object.fromEntries(e.map((e,a)=>[e,{label:e,color:t[a%t.length]}]))}if("perVU"===i){let e=Object.keys(a.perVU),t=["var(--chart-1)","var(--chart-2)","var(--chart-3)","var(--chart-4)","var(--chart-5)"];return Object.fromEntries(e.map((e,a)=>[e,{label:"VU ".concat(e),color:t[a%t.length]}]))}return{}},[i,a]),g=r.useMemo(()=>"perStep"===i?Object.entries(a.perStep).map(e=>{let[t,a]=e;return{key:t,label:t,data:a.map(e=>({timestamp:e.timestamp,value:e.value}))}}):"perVU"===i?Object.entries(a.perVU).map(e=>{let[t,a]=e;return{key:t,label:"VU ".concat(t),data:a.map(e=>({timestamp:e.timestamp,value:e.value}))}}):[],[i,a]),p=d?g:g.slice(0,4);return t&&0!==t.length?(0,s.jsxs)(eY,{className:"@container/card",children:[(0,s.jsx)(eM,{children:(0,s.jsxs)("div",{className:"flex items-center justify-between",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(eV,{children:"Average Latency"}),(0,s.jsxs)(eR,{children:[(0,s.jsxs)("span",{className:"hidden @[540px]/card:block",children:["Latency by"," ","overall"===i?"All Steps":"perStep"===i?"Step":"VU"]}),(0,s.jsx)("span",{className:"@[540px]/card:hidden",children:"Latency"})]})]}),(0,s.jsxs)(e0,{type:"single",value:i,onValueChange:e=>e&&o(e),variant:"outline",className:"gap-2",children:[(0,s.jsx)(e1,{value:"overall",className:"text-xs transition-all ".concat("overall"===i?"bg-orange-100 text-orange-900 border-orange-300 shadow-sm":"hover:bg-oragne-50"),children:"Overall"}),(0,s.jsx)(e1,{value:"perStep",className:"text-xs transition-all ".concat("perStep"===i?"bg-orange-100 text-orange-900 border-orange-300 shadow-sm":"hover:bg-orange-50"),children:"Per Step"}),(0,s.jsx)(e1,{value:"perVU",className:"text-xs transition-all ".concat("perVU"===i?"bg-orange-100 text-orange-900 border-orange-300 shadow-sm":"hover:bg-orange-50"),children:"Per VU"})]})]})}),(0,s.jsx)(eU,{className:"px-2 pt-4 sm:px-6 sm:pt-6",children:(0,s.jsx)(eH,{config:x,className:"aspect-auto h-[250px] w-full",children:(0,s.jsxs)(ez.Q,{data:h,children:[(0,s.jsx)(eO.d,{vertical:!1}),(0,s.jsx)(eB,{cursor:!1,content:(0,s.jsx)(eW,{indicator:"line",hideLabel:!0})}),Object.keys(x).map(e=>(0,s.jsx)(eD.Gk,{dataKey:e,type:"natural",fill:x[e].color,fillOpacity:.3,stroke:x[e].color,strokeWidth:2,name:x[e].label||e},e))]})})}),("perStep"===i||"perVU"===i)&&g.length>0&&(0,s.jsxs)("div",{className:"px-4",children:[g.length>4&&(0,s.jsxs)("div",{className:"mb-4 flex justify-between items-center",children:[(0,s.jsxs)("h3",{className:"text-sm font-medium text-gray-700",children:["Individual ","perStep"===i?"Step":"VU"," Charts",(0,s.jsxs)("span",{className:"text-gray-500 font-normal ml-1",children:["(",d?g.length:Math.min(4,g.length)," ","of ",g.length,")"]})]}),(0,s.jsx)(m,{variant:"outline",size:"sm",onClick:()=>c(!d),className:"text-xs cursor-pointer",children:d?"Show Less (4 of ".concat(g.length,")"):"Show All ".concat("perStep"===i?"Steps":"VUs"," (").concat(g.length,")")})]}),g.length<=4&&(0,s.jsx)("div",{className:"mb-4",children:(0,s.jsxs)("h3",{className:"text-sm font-medium text-gray-700",children:["Individual ","perStep"===i?"Step":"VU"," Charts",(0,s.jsxs)("span",{className:"text-gray-500 font-normal ml-1",children:["(",g.length,")"]})]})}),(0,s.jsx)("div",{className:"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",children:p.map((e,t)=>{let{key:a,label:r,data:n}=e,l=["var(--chart-1)","var(--chart-2)","var(--chart-3)","var(--chart-4)","var(--chart-5)"],i=l[t%l.length];return(0,s.jsxs)(eY,{className:"h-[180px] overflow-hidden",children:[(0,s.jsx)(eM,{children:(0,s.jsx)(eV,{className:"font-medium truncate leading-tight -m-2",title:r,children:r})}),(0,s.jsx)(eU,{className:"px-3",children:(0,s.jsx)("div",{className:"h-[130px] w-full",children:(0,s.jsx)(eH,{config:{value:{label:r,color:i}},className:"h-full w-full",children:(0,s.jsxs)(ez.Q,{data:n,margin:{top:20,right:2,left:2,bottom:20},children:[(0,s.jsx)(eB,{cursor:!1,content:(0,s.jsx)(eW,{indicator:"dot"})}),(0,s.jsx)(eD.Gk,{type:"natural",dataKey:"value",fill:i,fillOpacity:.4,stroke:i,strokeWidth:1.5,name:x[a].label||a})]})})})})]},a)})}),g.length>4&&(0,s.jsx)("div",{className:"mt-4 flex justify-center",children:(0,s.jsx)(m,{variant:"outline",size:"sm",onClick:()=>c(!d),className:"text-xs cursor-pointer",children:d?"Show Less (4 of ".concat(g.length,")"):"Show All ".concat("perStep"===i?"Steps":"VUs"," (").concat(g.length,")")})})]})]}):(0,s.jsxs)(eY,{className:"w-full",children:[(0,s.jsxs)(eM,{children:[(0,s.jsx)(eV,{children:"Average Latency"}),(0,s.jsx)(eR,{children:"No data available"})]}),(0,s.jsx)(eU,{className:"h-[250px] flex items-center justify-center text-gray-500",children:"No data is available"})]})}let e5=(0,i.F)("inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",secondary:"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",destructive:"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",outline:"text-foreground"}},defaultVariants:{variant:"default"}});function e6(e){let{className:t,variant:a,...r}=e;return(0,s.jsx)("div",{className:c(e5({variant:a}),t),...r})}var e3=a(646),e9=a(4861),e8=a(1243),e7=a(6785),te=a(4186);function tt(e){let{securityReport:t}=e,[a,n]=(0,r.useState)(new Set),l=e=>{switch(e.toLowerCase()){case"critical":return"bg-red-600 text-white";case"high":return"bg-red-500 text-white";case"medium":return"bg-yellow-500 text-white";case"low":return"bg-blue-500 text-white";case"info":return"bg-gray-500 text-white";default:return"bg-gray-400 text-white"}};return(0,s.jsxs)("div",{className:"space-y-6",children:[(0,s.jsxs)("div",{className:"flex items-center gap-3",children:[(0,s.jsx)(M.A,{className:"w-10 h-10 text-orange-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-2xl font-bold text-orange-900",children:"Security Report"}),(0,s.jsxs)("p",{className:"text-orange-700",children:["Test Suite: ",t.test_suite]})]})]}),(0,s.jsxs)("div",{className:"grid grid-cols-2 md:grid-cols-4 gap-4",children:[(0,s.jsx)(eY,{className:"p-4 border-orange-200",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e7.A,{className:"w-5 h-5 text-orange-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-2xl font-bold text-orange-900",children:t.total_checks}),(0,s.jsx)("div",{className:"text-sm text-orange-600",children:"Total Checks"})]})]})}),(0,s.jsx)(eY,{className:"p-4 border-green-200",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e3.A,{className:"w-5 h-5 text-green-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-2xl font-bold text-green-700",children:t.passed}),(0,s.jsx)("div",{className:"text-sm text-green-600",children:"Passed"})]})]})}),(0,s.jsx)(eY,{className:"p-4 border-red-200",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e9.A,{className:"w-5 h-5 text-red-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-2xl font-bold text-red-700",children:t.failed}),(0,s.jsx)("div",{className:"text-sm text-red-600",children:"Failed"})]})]})}),(0,s.jsx)(eY,{className:"p-4 border-yellow-200",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e8.A,{className:"w-5 h-5 text-yellow-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-2xl font-bold text-yellow-700",children:t.warnings}),(0,s.jsx)("div",{className:"text-sm text-yellow-600",children:"Warnings"})]})]})})]}),(0,s.jsx)(eY,{className:"p-4 border-orange-200",children:(0,s.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-6",children:[(0,s.jsxs)("div",{children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 mb-2",children:[(0,s.jsx)(te.A,{className:"w-4 h-4 text-orange-600"}),(0,s.jsx)("span",{className:"text-sm font-medium text-orange-900",children:"Report Generated"})]}),(0,s.jsx)("p",{className:"text-orange-700",children:new Date(t.timestamp).toLocaleString()})]}),Object.keys(t.summary).length>0&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h4",{className:"text-sm font-medium text-orange-900 mb-3",children:"Severity Summary"}),(0,s.jsx)("div",{className:"flex flex-wrap gap-2",children:Object.entries(t.summary).map(e=>{let[t,a]=e;return(0,s.jsxs)(e6,{className:l(t),children:[t,": ",a]},t)})})]})]})}),(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsx)("h3",{className:"text-lg font-semibold text-orange-900",children:"Security Check Results by Step"}),t.steps.map((e,t)=>{let r="".concat(e.step_method,"-").concat(e.step_name),i=a.has(r);return(0,s.jsxs)(eY,{className:"border-l-4 ".concat(e.failed>0?"border-l-red-500 border-red-200":e.warnings>0?"border-l-yellow-500 border-yellow-200":"border-l-green-500 border-green-200"),children:[(0,s.jsx)("div",{className:"p-4 cursor-pointer",onClick:()=>{n(e=>{let t=new Set(e);return t.has(r)?t.delete(r):t.add(r),t})},children:(0,s.jsxs)("div",{className:"flex items-center justify-between",children:[(0,s.jsxs)("div",{className:"flex items-center gap-3",children:[i?(0,s.jsx)(ef.A,{className:"w-5 h-5 text-orange-600"}):(0,s.jsx)(em.A,{className:"w-5 h-5 text-orange-600"}),(0,s.jsxs)("div",{children:[(0,s.jsxs)("h4",{className:"font-semibold text-orange-900",children:[e.step_method," - ",e.step_name]}),(0,s.jsx)("p",{className:"text-sm text-orange-700 break-all",children:e.step_url})]})]}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsxs)(e6,{className:"bg-green-100 text-green-800 border-green-200",children:["Passed: ",e.passed]}),(0,s.jsxs)(e6,{className:"bg-red-100 text-red-800 border-red-200",children:["Failed: ",e.failed]}),(0,s.jsxs)(e6,{className:"bg-yellow-100 text-yellow-800 border-yellow-200",children:["Warnings: ",e.warnings]})]})]})}),i&&(0,s.jsx)("div",{className:"border-t border-orange-200 p-4 space-y-4",children:e.results.map((e,t)=>(0,s.jsxs)("div",{className:"bg-orange-50 p-4 rounded-md space-y-3",children:[(0,s.jsxs)("div",{className:"flex items-start justify-between",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(e=>{switch(e){case"passed":return(0,s.jsx)(e3.A,{className:"w-4 h-4 text-green-600"});case"failed":return(0,s.jsx)(e9.A,{className:"w-4 h-4 text-red-600"});case"warning":return(0,s.jsx)(e8.A,{className:"w-4 h-4 text-yellow-600"});default:return(0,s.jsx)(M.A,{className:"w-4 h-4 text-gray-600"})}})(e.status),(0,s.jsx)("h5",{className:"font-semibold text-orange-900",children:e.check_name})]}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsx)(e6,{className:(e=>{switch(e){case"passed":return"bg-green-100 text-green-800 border-green-200";case"failed":return"bg-red-100 text-red-800 border-red-200";case"warning":return"bg-yellow-100 text-yellow-800 border-yellow-200";default:return"bg-gray-100 text-gray-800 border-gray-200"}})(e.status),children:e.status}),(0,s.jsx)(e6,{className:l(e.severity),children:e.severity})]})]}),(0,s.jsx)("p",{className:"text-orange-700",children:e.description}),(0,s.jsx)("div",{className:"bg-white p-3 rounded-md",children:(0,s.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-2 text-sm",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"font-medium text-orange-900",children:"Check ID:"})," ",(0,s.jsx)("span",{className:"text-orange-700",children:e.check_id})]}),e.status_code&&(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"font-medium text-orange-900",children:"Status Code:"})," ",(0,s.jsx)("span",{className:"text-orange-700",children:e.status_code})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"font-medium text-orange-900",children:"Target:"})," ",(0,s.jsx)("span",{className:"text-orange-700 capitalize",children:e.target})]})]})}),e.details&&(0,s.jsxs)("div",{className:"bg-gray-50 p-3 rounded-md",children:[(0,s.jsx)("h6",{className:"font-medium text-orange-900 mb-1",children:"Details"}),(0,s.jsx)("p",{className:"text-sm text-orange-700",children:e.details})]}),e.recommendation&&(0,s.jsxs)("div",{className:"bg-blue-50 p-3 rounded-md",children:[(0,s.jsx)("h6",{className:"font-medium text-orange-900 mb-1",children:"Recommendation"}),(0,s.jsx)("p",{className:"text-sm text-orange-700",children:e.recommendation})]})]},t))})]},t)})]})]})}var ta=a(3786),ts=a(2525),tr=a(9074),tn=a(7580);function tl(){let e=(0,n.useRouter)(),[t,a]=(0,r.useState)([]);return(0,r.useEffect)(()=>{let e=localStorage.getItem("klt-dashboards");if(e)try{let t=JSON.parse(e);a(t)}catch(e){console.error("Failed to parse saved dashboards:",e)}},[]),(0,s.jsx)("div",{className:"min-h-screen p-6",children:(0,s.jsxs)("div",{className:"max-w-7xl mx-auto",children:[(0,s.jsxs)("div",{className:"mb-8",children:[(0,s.jsx)("h1",{className:"text-4xl font-bold text-orange-900 mb-2",children:"KLT Dashboard"}),(0,s.jsx)("p",{className:"text-orange-700 text-lg",children:"Manage and monitor your load testing dashboards"})]}),(0,s.jsx)("div",{className:"mb-6",children:(0,s.jsxs)("div",{className:"text-sm text-orange-600",children:[t.length," dashboard",1!==t.length?"s":""," available"]})}),0===t.length?(0,s.jsxs)(eY,{className:"p-12 text-center border-orange-200",children:[(0,s.jsx)("div",{className:"text-orange-400 mb-4",children:(0,s.jsx)(Y.A,{className:"w-16 h-16 mx-auto mb-4"})}),(0,s.jsx)("h3",{className:"text-xl font-semibold text-orange-800 mb-2",children:"No dashboards available"}),(0,s.jsx)("p",{className:"text-orange-600 mb-6",children:"Dashboards will appear here automatically when load tests are running"})]}):(0,s.jsx)("div",{className:"grid gap-6 md:grid-cols-2 lg:grid-cols-3",children:t.map(t=>(0,s.jsxs)(eY,{className:"p-6 hover:shadow-lg transition-shadow border-orange-200 hover:border-orange-300",children:[(0,s.jsxs)("div",{className:"flex justify-between items-start mb-4",children:[(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsx)("h3",{className:"text-xl font-semibold text-orange-900 truncate",children:t.title}),(0,s.jsx)("p",{className:"text-sm text-orange-500 mt-1",children:t.id})]}),(0,s.jsxs)("div",{className:"flex gap-2 ml-2",children:[(0,s.jsx)(m,{variant:"ghost",size:"sm",onClick:()=>(e=>{let t=new URL(window.location.origin);t.searchParams.set("dashboard",e),window.open(t.toString(),"_blank")})(t.id),className:"p-2 text-orange-600 hover:text-orange-700 hover:bg-orange-50",children:(0,s.jsx)(ta.A,{className:"w-4 h-4"})}),(0,s.jsx)(m,{variant:"ghost",size:"sm",onClick:()=>(e=>{a(t=>{let a=t.filter(t=>t.id!==e);return localStorage.setItem("klt-dashboards",JSON.stringify(a)),a});try{localStorage.removeItem("dashboard-storage-dashboard-".concat(e))}catch(e){console.error("Failed to clean up dashboard data:",e)}})(t.id),className:"p-2 text-red-600 hover:text-red-700 hover:bg-red-50",children:(0,s.jsx)(ts.A,{className:"w-4 h-4"})})]})]}),(0,s.jsx)("p",{className:"text-orange-700 text-sm mb-4 line-clamp-2",children:t.description}),(0,s.jsxs)("div",{className:"space-y-2 mb-4 text-sm text-orange-600",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(tr.A,{className:"w-4 h-4"}),(0,s.jsx)("span",{children:new Date(t.created_at).toLocaleString()})]}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(tn.A,{className:"w-4 h-4"}),(0,s.jsxs)("span",{children:[t.load_options.VUs," VUs"]})]}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(te.A,{className:"w-4 h-4"}),(0,s.jsx)("span",{children:t.load_options.Duration})]}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e7.A,{className:"w-4 h-4"}),(0,s.jsxs)("span",{children:[t.load_options.RPS," RPS"]})]})]}),(0,s.jsx)("div",{className:"space-y-2",children:(0,s.jsx)(m,{onClick:()=>{var a;return a=t.id,void e.push("/?dashboard=".concat(a))},className:"w-full bg-orange-600 hover:bg-orange-700 text-white",children:"Open Dashboard"})})]},t.id))})]})})}function ti(){let{selectedDashboard:e,setSelectedDashboard:t}=L(),[a,n]=(0,r.useState)("dashboard"),[l,i]=(0,r.useState)({}),[o,d]=(0,r.useState)({}),[c,u]=(0,r.useState)({}),[m,h]=(0,r.useState)({}),[x,p]=(0,r.useState)({}),[f,v]=(0,r.useState)(!1),y=(null==e?void 0:e.id)||"",N=l[y]||[],_=o[y]||{overall:[],perStep:{},perVU:{}},S=(0,r.useRef)(l),k=(0,r.useRef)(o),A=(0,r.useRef)(c),C=(0,r.useRef)(m),I=(0,r.useRef)(x);(0,r.useEffect)(()=>{S.current=l,k.current=o,A.current=c,C.current=m,I.current=x},[l,o,c,m,x]);let P=(e,t)=>"".concat(e,"-dashboard-").concat(t),T=e=>{let t=new Date(e).getTime();var a=Math.max(0,Math.floor((Date.now()-t)/1e3));let s=Math.floor(a/3600),r=Math.floor(a%3600/60),n=a%60;return s>0?"".concat(s,"h ").concat(r,"m ").concat(n,"s"):r>0?"".concat(r,"m ").concat(n,"s"):"".concat(n,"s")},E=(e,t)=>{u(a=>({...a,[e]:t}))};(0,r.useEffect)(()=>{(()=>{if(!(null==e?void 0:e.id))return v(!1);try{let t=P("dashboard-storage",e.id),a=localStorage.getItem(t);if(a){let e=JSON.parse(a);i(t=>({...t,...e.dashboardData})),d(t=>({...t,...e.chartHistories})),u(t=>({...t,...e.dashboardStopTimes})),h(t=>({...t,...e.dashboardCloseTimes})),p(t=>({...t,...e.dashboardOpenedStatus}))}}catch(e){}finally{v(!0)}})()},[null==e?void 0:e.id]),(0,r.useEffect)(()=>{if(!(null==e?void 0:e.id)||!f)return;let t=e.id;x[t]||(p(e=>({...e,[t]:!0})),z(e.url).then(e=>{if(e)h(e=>{let a={...e};return delete a[t],a});else{let e=m[t];e?E(t,e):E(t,"0s"),h(e=>{let a={...e};return delete a[t],a})}}))},[null==e?void 0:e.id,x,null==e?void 0:e.url,f]);let z=async function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;for(let a=0;a<=t;a++)try{if(0===a){let e=sessionStorage.getItem("sw-unregistered");e&&Date.now()-parseInt(e)<3e3&&await new Promise(e=>setTimeout(e,300))}let t=await fetch(e,{cache:"no-store",signal:AbortSignal.timeout(2e3+500*a)});if(t.ok)return!0;if(t.status>=400&&t.status<500)break}catch(e){if(a===t)return!1;await new Promise(e=>setTimeout(e,500*(a+1)))}return!1};(0,r.useEffect)(()=>{if(!(null==e?void 0:e.id)||!(null==e?void 0:e.created_at)||!(null==e?void 0:e.url)||!f)return;let t=e.id;if(!c[t]){if(m[t]&&x[t])(async()=>{if(!await z(e.url)){let e=m[t];E(t,e),h(e=>{let a={...e};return delete a[t],a})}})();else if(x[t]){let a=setInterval(async()=>{if(!await z(e.url)&&!c[t]){let a=(null==e?void 0:e.id)===t,s=m[t];E(t,a?T(e.created_at):s||T(e.created_at)),s&&!a&&h(e=>{let a={...e};return delete a[t],a})}},1e4);return()=>clearInterval(a)}}},[e,c,m,x,f]);let O=(0,r.useRef)(null);(0,r.useEffect)(()=>{let t=O.current;if((null==t?void 0:t.id)&&t.id!==(null==e?void 0:e.id)&&f){let e=t.id;if(!A.current[e])try{let a=T(t.created_at);h(t=>({...t,[e]:a}));let s=P("dashboard-storage",e),r=localStorage.getItem(s);if(r){let t=JSON.parse(r);t.dashboardCloseTimes={...t.dashboardCloseTimes,[e]:a},localStorage.setItem(s,JSON.stringify(t))}}catch(e){}}O.current=e},[null==e?void 0:e.id,f]),(0,r.useEffect)(()=>{if(!f)return;let t=Object.entries(m);if(0===t.length)return;let a=async()=>{for(let[a,s]of t){if(c[a]||(null==e?void 0:e.id)===a||!x[a])continue;let t=JSON.parse(localStorage.getItem("klt-dashboards")||"[]").find(e=>e.id===a);(null==t?void 0:t.url)&&(await z(t.url)||(E(a,s),h(e=>{let t={...e};return delete t[a],t})))}};a();let s=setInterval(a,15e3);return()=>clearInterval(s)},[m,c,null==e?void 0:e.id,f]),(0,r.useEffect)(()=>{if(null==e?void 0:e.id)try{let t=P("dashboard-storage",e.id);localStorage.setItem(t,JSON.stringify({dashboardData:l,chartHistories:o,dashboardStopTimes:c,dashboardCloseTimes:m,dashboardOpenedStatus:x}))}catch(e){}},[l,o,c,m,x,null==e?void 0:e.id]),(0,r.useEffect)(()=>{if(!(null==e?void 0:e.id)||!(null==e?void 0:e.created_at))return;let t=()=>{if(!A.current[e.id])try{let t=T(e.created_at);h(a=>({...a,[e.id]:t}));let a=P("dashboard-storage",e.id),s=localStorage.getItem(a);if(s){let r=JSON.parse(s);r.dashboardCloseTimes={...r.dashboardCloseTimes,[e.id]:t},r.dashboardOpenedStatus={...r.dashboardOpenedStatus,[e.id]:!0},localStorage.setItem(a,JSON.stringify(r))}}catch(e){}};return window.addEventListener("beforeunload",t),()=>window.removeEventListener("beforeunload",t)},[null==e?void 0:e.id,null==e?void 0:e.created_at]);let D=async function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;if(!e)return[];for(let a=0;a<=t;a++)try{if(0===a){let e=sessionStorage.getItem("sw-unregistered");e&&Date.now()-parseInt(e)<3e3&&await new Promise(e=>setTimeout(e,200))}let t=await fetch(e,{cache:"no-store",signal:AbortSignal.timeout(3e3+500*a)});if(!t.ok)throw Error("HTTP ".concat(t.status,": ").concat(t.statusText));let s=await t.json();if(!Array.isArray(s))return[];let r=[],n=[];return s.forEach((e,t)=>{if(!e||"number"!=typeof e.vu_id)return void n.push({index:t,item:e,reason:"Missing or invalid vu_id"});let a=w.safeParse(e);a.success?r.push(a.data):n.push({index:t,item:{vu_id:e.vu_id},reason:"Schema validation failed",errors:a.error.issues})}),r}catch(e){if(a===t)return[];await new Promise(e=>setTimeout(e,500*(a+1)))}return[]};(0,r.useEffect)(()=>{var t;let a;if(!(null==e?void 0:e.url)||!f)return;let s=!!c[y],r=!!A.current[y],n=!!(null==(t=l[y])?void 0:t.length)&&(s||r);if(s||r||n)return;let o=!0,d=0,u=async()=>{var t;let s=A.current[y]||c[y];if(s||(null==(t=S.current[y])?void 0:t.length)>0&&s)return void clearInterval(a);try{let t=await D(e.url);if(A.current[y]||c[y])return void clearInterval(a);o&&(Array.isArray(t)&&t.length>0?(i(e=>({...e,[y]:t})),d=0):d++)}catch(e){d++}if(d>=3&&a&&(clearInterval(a),!A.current[y])){let t=I.current[y],a=C.current[y],s=(null==e?void 0:e.id)===y;E(y,s&&(null==e?void 0:e.created_at)?T(e.created_at):a&&t?a:t&&(null==e?void 0:e.created_at)?T(e.created_at):"0s"),a&&t&&!s&&h(e=>{let t={...e};return delete t[y],t})}};return u(),a=setInterval(u,1e3),()=>{o=!1,a&&clearInterval(a)}},[e,y,c,f]);let Y=N.length,M=!!c[y];return(0,s.jsxs)(g,{children:[(0,s.jsx)(V,{selectedDashboard:e,onSelectDashboard:t,onDashboardDeleted:a=>{i(e=>{let t={...e};return delete t[a],t}),d(e=>{let t={...e};return delete t[a],t}),u(e=>{let t={...e};return delete t[a],t}),h(e=>{let t={...e};return delete t[a],t}),p(e=>{let t={...e};return delete t[a],t}),(null==e?void 0:e.id)===a&&t(null)},currentView:a,onViewChange:n}),(0,s.jsxs)(j,{children:[(0,s.jsxs)("header",{className:"flex h-12 items-center gap-2 border-b px-4",children:[(0,s.jsx)(b,{className:"text-orange-900"}),(0,s.jsxs)("h1",{className:"text-lg font-semibold text-orange-900",children:["KLT Dashboard","security"===a&&" - Security Report"]}),e&&(0,s.jsxs)("span",{className:"text-xs text-orange-500 ml-auto",children:["Dashboard: ",e.id]})]}),(0,s.jsx)("div",{className:"p-6",children:"dashboard"===a?(0,s.jsxs)("div",{className:"@container/main flex flex-col gap-4 md:gap-6",children:[(0,s.jsx)(K,{lttoken:null!=e?e:void 0,stopped:M,stopTime:c[y],currentVUCount:Y}),(0,s.jsx)(e4,{vuData:N,chartHistory:_,setChartHistory:e=>{d(t=>({...t,[y]:e(t[y]||{overall:[],perStep:{},perVU:{}})}))},isCompleted:M}),(0,s.jsx)(eE,{data:N,isCompleted:M})]}):"security"===a&&(null==e?void 0:e.security_report)?(0,s.jsx)(tt,{securityReport:e.security_report}):(0,s.jsx)("div",{className:"flex items-center justify-center h-64",children:(0,s.jsx)("div",{className:"text-center text-orange-600",children:(0,s.jsx)("p",{children:"No security report available for this dashboard."})})})})]})]})}function to(){let{selectedDashboard:e,setSelectedDashboard:t}=L(),a=(0,n.useSearchParams)(),[l,i]=(0,r.useState)(!1),[o,d]=(0,r.useState)(null),c=()=>{try{let e=localStorage.getItem("klt-dashboards");if(e)return JSON.parse(e)}catch(e){}return[]},u=e=>{try{let t=c(),a=t.findIndex(t=>t.id===e.id);a>=0?t[a]=e:t.unshift(e),localStorage.setItem("klt-dashboards",JSON.stringify(t))}catch(e){}},m=e=>{let a=c().find(t=>t.id===e);return!!a&&(t(a),!0)};(0,r.useEffect)(()=>{if(l){let t=setTimeout(()=>{i(!1),e||o||d("Request timed out. Using cached data if available.")},1e4);return()=>clearTimeout(t)}},[l,e,o]);let h=async function(e){let a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;i(!0),d(null);try{for(let s=0;s<=a;s++)try{if(0===s){let e=sessionStorage.getItem("sw-unregistered");e&&Date.now()-parseInt(e)<3e3&&await new Promise(e=>setTimeout(e,500))}let a=localStorage.getItem("disable-sw");localStorage.setItem("disable-sw","true");let r=await fetch("http://localhost:2345/dashboards",{cache:"no-store",signal:AbortSignal.timeout(3e3+1e3*s)});if(!r.ok)throw Error("HTTP error! status: ".concat(r.status));let n=await r.json();if(n&&n.id&&(u(n),n.id===e&&t(n)),null===a?localStorage.removeItem("disable-sw"):localStorage.setItem("disable-sw",a),n.id===e)return;throw Error("Dashboard ID mismatch: expected ".concat(e,", got ").concat(n.id))}catch(t){let e=localStorage.getItem("disable-sw");if("true"===e&&localStorage.removeItem("disable-sw"),s===a)throw t;await new Promise(e=>setTimeout(e,500*(s+1)))}}catch(t){d(t instanceof Error?t.message:"Failed to fetch dashboard"),m(e)?d(null):d("Dashboard token unavailable. The server may have closed after serving the initial request.")}finally{i(!1)}};return((0,r.useEffect)(()=>{let s=null==a?void 0:a.get("dashboard");if(s){if((null==e?void 0:e.id)===s)return;m(s)?setTimeout(()=>{h(s).catch(()=>{})},100):h(s)}else t(null)},[null==a?void 0:a.get("dashboard")]),null==a?void 0:a.get("dashboard"))?l?(0,s.jsx)("div",{className:"flex h-screen items-center justify-center",children:(0,s.jsx)("div",{className:"text-orange-900",children:"Loading dashboard..."})}):o&&!e?(0,s.jsx)("div",{className:"flex h-screen items-center justify-center",children:(0,s.jsxs)("div",{className:"text-center max-w-md",children:[(0,s.jsx)("div",{className:"text-red-600 mb-2",children:"Failed to load dashboard"}),(0,s.jsx)("div",{className:"text-sm text-gray-600 mb-4",children:o}),o.includes("Dashboard token unavailable")&&(0,s.jsxs)("div",{className:"text-xs text-orange-600 bg-orange-50 p-3 rounded",children:[(0,s.jsx)("strong",{children:"Note:"})," The dashboard server only serves one request per session. If you've unregistered the service worker, the token may no longer be available. Try restarting the load test to generate a new token."]})]})}):(0,s.jsx)(ti,{}):(0,s.jsx)(tl,{})}function td(){return(0,s.jsx)(U,{children:(0,s.jsx)(r.Suspense,{fallback:(0,s.jsx)("div",{className:"flex h-screen items-center justify-center",children:(0,s.jsx)("div",{className:"text-orange-900",children:"Loading dashboard..."})}),children:(0,s.jsx)(to,{})})})}}},e=>{e.O(0,[547,441,964,358],()=>e(e.s=2769)),_N_E=e.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/framework-7c95b8e5103c9e90.js b/keploy/pkg/service/load/out/_next/static/chunks/framework-7c95b8e5103c9e90.js new file mode 100644 index 0000000..458bbbf --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/framework-7c95b8e5103c9e90.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[593],{2167:(e,t,n)=>{var r=n(5364),l=Symbol.for("react.transitional.element"),a=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),i=Symbol.for("react.strict_mode"),u=Symbol.for("react.profiler"),s=Symbol.for("react.consumer"),c=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),d=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),h=Symbol.iterator,g={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,v={};function b(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}function k(){}function w(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}b.prototype.isReactComponent={},b.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},b.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},k.prototype=b.prototype;var S=w.prototype=new k;S.constructor=w,y(S,b.prototype),S.isPureReactComponent=!0;var x=Array.isArray,E={H:null,A:null,T:null,S:null,V:null},C=Object.prototype.hasOwnProperty;function _(e,t,n,r,a,o){return{$$typeof:l,type:e,key:t,ref:void 0!==(n=o.ref)?n:null,props:o}}function P(e){return"object"==typeof e&&null!==e&&e.$$typeof===l}var z=/\/+/g;function N(e,t){var n,r;return"object"==typeof e&&null!==e&&null!=e.key?(n=""+e.key,r={"=":"=0",":":"=2"},"$"+n.replace(/[=:]/g,function(e){return r[e]})):t.toString(36)}function T(){}function L(e,t,n){if(null==e)return e;var r=[],o=0;return!function e(t,n,r,o,i){var u,s,c,f=typeof t;("undefined"===f||"boolean"===f)&&(t=null);var d=!1;if(null===t)d=!0;else switch(f){case"bigint":case"string":case"number":d=!0;break;case"object":switch(t.$$typeof){case l:case a:d=!0;break;case m:return e((d=t._init)(t._payload),n,r,o,i)}}if(d)return i=i(t),d=""===o?"."+N(t,0):o,x(i)?(r="",null!=d&&(r=d.replace(z,"$&/")+"/"),e(i,n,r,"",function(e){return e})):null!=i&&(P(i)&&(u=i,s=r+(null==i.key||t&&t.key===i.key?"":(""+i.key).replace(z,"$&/")+"/")+d,i=_(u.type,s,void 0,void 0,void 0,u.props)),n.push(i)),1;d=0;var p=""===o?".":o+":";if(x(t))for(var g=0;g{e.exports=n(5919)},4232:(e,t,n)=>{e.exports=n(2167)},4279:(e,t,n)=>{var r,l=n(5364),a=n(2786),o=n(4232),i=n(8477);function u(e){var t="https://react.dev/errors/"+e;if(1I||(e.current=M[I],M[I]=null,I--)}function H(e,t){M[++I]=e.current,e.current=t}var $=U(null),V=U(null),B=U(null),Q=U(null);function W(e,t){switch(H(B,t),H(V,e),H($,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?si(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)e=su(t=si(t),e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}j($),H($,e)}function q(){j($),j(V),j(B)}function K(e){null!==e.memoizedState&&H(Q,e);var t=$.current,n=su(t,e.type);t!==n&&(H(V,e),H($,n))}function Y(e){V.current===e&&(j($),j(V)),Q.current===e&&(j(Q),sX._currentValue=F)}var G=Object.prototype.hasOwnProperty,X=a.unstable_scheduleCallback,Z=a.unstable_cancelCallback,J=a.unstable_shouldYield,ee=a.unstable_requestPaint,et=a.unstable_now,en=a.unstable_getCurrentPriorityLevel,er=a.unstable_ImmediatePriority,el=a.unstable_UserBlockingPriority,ea=a.unstable_NormalPriority,eo=a.unstable_LowPriority,ei=a.unstable_IdlePriority,eu=a.log,es=a.unstable_setDisableYieldValue,ec=null,ef=null;function ed(e){if("function"==typeof eu&&es(e),ef&&"function"==typeof ef.setStrictMode)try{ef.setStrictMode(ec,e)}catch(e){}}var ep=Math.clz32?Math.clz32:function(e){return 0==(e>>>=0)?32:31-(em(e)/eh|0)|0},em=Math.log,eh=Math.LN2,eg=256,ey=4194304;function ev(e){var t=42&e;if(0!==t)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return 4194048&e;case 4194304:case 8388608:case 0x1000000:case 0x2000000:return 0x3c00000&e;case 0x4000000:return 0x4000000;case 0x8000000:return 0x8000000;case 0x10000000:return 0x10000000;case 0x20000000:return 0x20000000;case 0x40000000:return 0;default:return e}}function eb(e,t,n){var r=e.pendingLanes;if(0===r)return 0;var l=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var i=0x7ffffff&r;return 0!==i?0!=(r=i&~a)?l=ev(r):0!=(o&=i)?l=ev(o):n||0!=(n=i&~e)&&(l=ev(n)):0!=(i=r&~a)?l=ev(i):0!==o?l=ev(o):n||0!=(n=r&~e)&&(l=ev(n)),0===l?0:0!==t&&t!==l&&0==(t&a)&&((a=l&-l)>=(n=t&-t)||32===a&&0!=(4194048&n))?t:l}function ek(e,t){return 0==(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)}function ew(){var e=eg;return 0==(4194048&(eg<<=1))&&(eg=256),e}function eS(){var e=ey;return 0==(0x3c00000&(ey<<=1))&&(ey=4194304),e}function ex(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function eE(e,t){e.pendingLanes|=t,0x10000000!==t&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function eC(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-ep(t);e.entangledLanes|=t,e.entanglements[r]=0x40000000|e.entanglements[r]|4194090&n}function e_(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-ep(n),l=1<)":-1l||u[r]!==s[l]){var c="\n"+u[r].replace(" at new "," at ");return e.displayName&&c.includes("")&&(c=c.replace("",e.displayName)),c}while(1<=r&&0<=l);break}}}finally{e2=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:"")?e1(n):""}function e4(e){try{var t="";do t+=function(e){switch(e.tag){case 26:case 27:case 5:return e1(e.type);case 16:return e1("Lazy");case 13:return e1("Suspense");case 19:return e1("SuspenseList");case 0:case 15:return e3(e.type,!1);case 11:return e3(e.type.render,!1);case 1:return e3(e.type,!0);case 31:return e1("Activity");default:return""}}(e),e=e.return;while(e);return t}catch(e){return"\nError generating stack: "+e.message+"\n"+e.stack}}function e8(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":case"object":return e;default:return""}}function e6(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===t||"radio"===t)}function e5(e){e._valueTracker||(e._valueTracker=function(e){var t=e6(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&void 0!==n&&"function"==typeof n.get&&"function"==typeof n.set){var l=n.get,a=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(e){r=""+e,a.call(this,e)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(e){r=""+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e))}function e9(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=e6(e)?e.checked?"true":"false":e.value),(e=r)!==n&&(t.setValue(e),!0)}function e7(e){if(void 0===(e=e||("undefined"!=typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}var te=/[\n"\\]/g;function tt(e){return e.replace(te,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function tn(e,t,n,r,l,a,o,i){e.name="",null!=o&&"function"!=typeof o&&"symbol"!=typeof o&&"boolean"!=typeof o?e.type=o:e.removeAttribute("type"),null!=t?"number"===o?(0===t&&""===e.value||e.value!=t)&&(e.value=""+e8(t)):e.value!==""+e8(t)&&(e.value=""+e8(t)):"submit"!==o&&"reset"!==o||e.removeAttribute("value"),null!=t?tl(e,o,e8(t)):null!=n?tl(e,o,e8(n)):null!=r&&e.removeAttribute("value"),null==l&&null!=a&&(e.defaultChecked=!!a),null!=l&&(e.checked=l&&"function"!=typeof l&&"symbol"!=typeof l),null!=i&&"function"!=typeof i&&"symbol"!=typeof i&&"boolean"!=typeof i?e.name=""+e8(i):e.removeAttribute("name")}function tr(e,t,n,r,l,a,o,i){if(null!=a&&"function"!=typeof a&&"symbol"!=typeof a&&"boolean"!=typeof a&&(e.type=a),null!=t||null!=n){if(("submit"===a||"reset"===a)&&null==t)return;n=null!=n?""+e8(n):"",t=null!=t?""+e8(t):n,i||t===e.value||(e.value=t),e.defaultValue=t}r="function"!=typeof(r=null!=r?r:l)&&"symbol"!=typeof r&&!!r,e.checked=i?e.checked:!!r,e.defaultChecked=!!r,null!=o&&"function"!=typeof o&&"symbol"!=typeof o&&"boolean"!=typeof o&&(e.name=o)}function tl(e,t,n){"number"===t&&e7(e.ownerDocument)===e||e.defaultValue===""+n||(e.defaultValue=""+n)}function ta(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l=ne),nr=!1;function nl(e,t){switch(e){case"keyup":return -1!==t9.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function na(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var no=!1,ni={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function nu(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!ni[e.type]:"textarea"===t}function ns(e,t,n,r){tv?tb?tb.push(r):tb=[r]:tv=r,0<(t=u3(t,"onChange")).length&&(n=new tH("onChange","change",null,n,r),e.push({event:n,listeners:t}))}var nc=null,nf=null;function nd(e){uY(e,0)}function np(e){if(e9(e$(e)))return e}function nm(e,t){if("change"===e)return t}var nh=!1;if(tE){if(tE){var ng="oninput"in document;if(!ng){var ny=document.createElement("div");ny.setAttribute("oninput","return;"),ng="function"==typeof ny.oninput}r=ng}else r=!1;nh=r&&(!document.documentMode||9=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=n_(r)}}function nz(e){e=null!=e&&null!=e.ownerDocument&&null!=e.ownerDocument.defaultView?e.ownerDocument.defaultView:window;for(var t=e7(e.document);t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(e){n=!1}if(n)e=t.contentWindow;else break;t=e7(e.document)}return t}function nN(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var nT=tE&&"documentMode"in document&&11>=document.documentMode,nL=null,nO=null,nR=null,nD=!1;function nA(e,t,n){var r=n.window===n?n.document:9===n.nodeType?n:n.ownerDocument;nD||null==nL||nL!==e7(r)||(r="selectionStart"in(r=nL)&&nN(r)?{start:r.selectionStart,end:r.selectionEnd}:{anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},nR&&nC(nR,r)||(nR=r,0<(r=u3(nO,"onSelect")).length&&(t=new tH("onSelect","select",null,t,n),e.push({event:t,listeners:r}),t.target=nL)))}function nF(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var nM={animationend:nF("Animation","AnimationEnd"),animationiteration:nF("Animation","AnimationIteration"),animationstart:nF("Animation","AnimationStart"),transitionrun:nF("Transition","TransitionRun"),transitionstart:nF("Transition","TransitionStart"),transitioncancel:nF("Transition","TransitionCancel"),transitionend:nF("Transition","TransitionEnd")},nI={},nU={};function nj(e){if(nI[e])return nI[e];if(!nM[e])return e;var t,n=nM[e];for(t in n)if(n.hasOwnProperty(t)&&t in nU)return nI[e]=n[t];return e}tE&&(nU=document.createElement("div").style,"AnimationEvent"in window||(delete nM.animationend.animation,delete nM.animationiteration.animation,delete nM.animationstart.animation),"TransitionEvent"in window||delete nM.transitionend.transition);var nH=nj("animationend"),n$=nj("animationiteration"),nV=nj("animationstart"),nB=nj("transitionrun"),nQ=nj("transitionstart"),nW=nj("transitioncancel"),nq=nj("transitionend"),nK=new Map,nY="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function nG(e,t){nK.set(e,t),eq(t,[e])}nY.push("scrollEnd");var nX=new WeakMap;function nZ(e,t){if("object"==typeof e&&null!==e){var n=nX.get(e);return void 0!==n?n:(t={value:e,source:t,stack:e4(t)},nX.set(e,t),t)}return{value:e,source:t,stack:e4(t)}}var nJ=[],n0=0,n1=0;function n2(){for(var e=n0,t=n1=n0=0;t>=o,l-=o,rh=1<<32-ep(t)+l|n<a?a:8;var o=D.T,i={};D.T=i,aj(e,!1,t,n);try{var u=l(),s=D.S;if(null!==s&&s(i,u),null!==u&&"object"==typeof u&&"function"==typeof u.then){var c,f,d=(c=[],f={status:"pending",value:null,reason:null,then:function(e){c.push(e)}},u.then(function(){f.status="fulfilled",f.value=r;for(var e=0;eh?(g=f,f=null):g=f.sibling;var y=p(l,f,i[h],u);if(null===y){null===f&&(f=g);break}e&&f&&null===y.alternate&&t(l,f),o=a(y,o,h),null===c?s=y:c.sibling=y,c=y,f=g}if(h===i.length)return n(l,f),rx&&ry(l,h),s;if(null===f){for(;hg?(y=h,h=null):y=h.sibling;var b=p(l,h,v.value,s);if(null===b){null===h&&(h=y);break}e&&h&&null===b.alternate&&t(l,h),o=a(b,o,g),null===f?c=b:f.sibling=b,f=b,h=y}if(v.done)return n(l,h),rx&&ry(l,g),c;if(null===h){for(;!v.done;g++,v=i.next())null!==(v=d(l,v.value,s))&&(o=a(v,o,g),null===f?c=v:f.sibling=v,f=v);return rx&&ry(l,g),c}for(h=r(h);!v.done;g++,v=i.next())null!==(v=m(h,l,g,v.value,s))&&(e&&null!==v.alternate&&h.delete(null===v.key?g:v.key),o=a(v,o,g),null===f?c=v:f.sibling=v,f=v);return e&&h.forEach(function(e){return t(l,e)}),rx&&ry(l,g),c}(s,c,f=b.call(f),v)}if("function"==typeof f.then)return i(s,c,aG(f),v);if(f.$$typeof===S)return i(s,c,rQ(s,f),v);aZ(s,f)}return"string"==typeof f&&""!==f||"number"==typeof f||"bigint"==typeof f?(f=""+f,null!==c&&6===c.tag?(n(s,c.sibling),(v=l(c,f)).return=s):(n(s,c),(v=ro(f,s.mode,v)).return=s),o(s=v)):n(s,c)}(i,s,c,f);return aK=null,v}catch(e){if(e===r7||e===lt)throw e;var b=re(29,e,null,i.mode);return b.lanes=f,b.return=i,b}finally{}}}var a1=a0(!0),a2=a0(!1),a3=U(null),a4=null;function a8(e){var t=e.alternate;H(a7,1&a7.current),H(a3,e),null===a4&&(null===t||null!==lw.current?a4=e:null!==t.memoizedState&&(a4=e))}function a6(e){if(22===e.tag){if(H(a7,a7.current),H(a3,e),null===a4){var t=e.alternate;null!==t&&null!==t.memoizedState&&(a4=e)}}else a5(e)}function a5(){H(a7,a7.current),H(a3,a3.current)}function a9(e){j(a3),a4===e&&(a4=null),j(a7)}var a7=U(0);function oe(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||"$?"===n.data||sb(n)))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(0!=(128&t.flags))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function ot(e,t,n,r){n=null==(n=n(r,t=e.memoizedState))?t:p({},t,n),e.memoizedState=n,0===e.lanes&&(e.updateQueue.baseState=n)}var on={enqueueSetState:function(e,t,n){e=e._reactInternals;var r=i6(),l=ld(r);l.payload=t,null!=n&&(l.callback=n),null!==(t=lp(e,l,r))&&(i9(t,e,r),lm(t,e,r))},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var r=i6(),l=ld(r);l.tag=1,l.payload=t,null!=n&&(l.callback=n),null!==(t=lp(e,l,r))&&(i9(t,e,r),lm(t,e,r))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=i6(),r=ld(n);r.tag=2,null!=t&&(r.callback=t),null!==(t=lp(e,r,n))&&(i9(t,e,n),lm(t,e,n))}};function or(e,t,n,r,l,a,o){return"function"==typeof(e=e.stateNode).shouldComponentUpdate?e.shouldComponentUpdate(r,a,o):!t.prototype||!t.prototype.isPureReactComponent||!nC(n,r)||!nC(l,a)}function ol(e,t,n,r){e=t.state,"function"==typeof t.componentWillReceiveProps&&t.componentWillReceiveProps(n,r),"function"==typeof t.UNSAFE_componentWillReceiveProps&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&on.enqueueReplaceState(t,t.state,null)}function oa(e,t){var n=t;if("ref"in t)for(var r in n={},t)"ref"!==r&&(n[r]=t[r]);if(e=e.defaultProps)for(var l in n===t&&(n=p({},n)),e)void 0===n[l]&&(n[l]=e[l]);return n}var oo="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof l&&"function"==typeof l.emit)return void l.emit("uncaughtException",e);console.error(e)};function oi(e){oo(e)}function ou(e){console.error(e)}function os(e){oo(e)}function oc(e,t){try{(0,e.onUncaughtError)(t.value,{componentStack:t.stack})}catch(e){setTimeout(function(){throw e})}}function of(e,t,n){try{(0,e.onCaughtError)(n.value,{componentStack:n.stack,errorBoundary:1===t.tag?t.stateNode:null})}catch(e){setTimeout(function(){throw e})}}function od(e,t,n){return(n=ld(n)).tag=3,n.payload={element:null},n.callback=function(){oc(e,t)},n}function op(e){return(e=ld(e)).tag=3,e}function om(e,t,n,r){var l=n.type.getDerivedStateFromError;if("function"==typeof l){var a=r.value;e.payload=function(){return l(a)},e.callback=function(){of(t,n,r)}}var o=n.stateNode;null!==o&&"function"==typeof o.componentDidCatch&&(e.callback=function(){of(t,n,r),"function"!=typeof l&&(null===iG?iG=new Set([this]):iG.add(this));var e=r.stack;this.componentDidCatch(r.value,{componentStack:null!==e?e:""})})}var oh=Error(u(461)),og=!1;function oy(e,t,n,r){t.child=null===e?a2(t,null,n,r):a1(t,e.child,n,r)}function ov(e,t,n,r,l){n=n.render;var a=t.ref;if("ref"in r){var o={};for(var i in r)"ref"!==i&&(o[i]=r[i])}else o=r;return(rV(t),r=lU(e,t,n,o,a,l),i=lV(),null===e||og)?(rx&&i&&rb(t),t.flags|=1,oy(e,t,r,l),t.child):(lB(e,t,l),oI(e,t,l))}function ob(e,t,n,r,l){if(null===e){var a=n.type;return"function"!=typeof a||rt(a)||void 0!==a.defaultProps||null!==n.compare?((e=rl(n.type,null,r,t,t.mode,l)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=a,ok(e,t,a,r,l))}if(a=e.child,!oU(e,l)){var o=a.memoizedProps;if((n=null!==(n=n.compare)?n:nC)(o,r)&&e.ref===t.ref)return oI(e,t,l)}return t.flags|=1,(e=rn(a,r)).ref=t.ref,e.return=t,t.child=e}function ok(e,t,n,r,l){if(null!==e){var a=e.memoizedProps;if(nC(a,r)&&e.ref===t.ref)if(og=!1,t.pendingProps=r=a,!oU(e,l))return t.lanes=e.lanes,oI(e,t,l);else 0!=(131072&e.flags)&&(og=!0)}return oE(e,t,n,r,l)}function ow(e,t,n){var r=t.pendingProps,l=r.children,a=null!==e?e.memoizedState:null;if("hidden"===r.mode){if(0!=(128&t.flags)){if(r=null!==a?a.baseLanes|n:n,null!==e){for(a=0,l=t.child=e.child;null!==l;)a=a|l.lanes|l.childLanes,l=l.sibling;t.childLanes=a&~r}else t.childLanes=0,t.child=null;return oS(e,t,r,n)}if(0==(0x20000000&n))return t.lanes=t.childLanes=0x20000000,oS(e,t,null!==a?a.baseLanes|n:n,n);t.memoizedState={baseLanes:0,cachePool:null},null!==e&&r5(t,null!==a?a.cachePool:null),null!==a?lx(t,a):lE(),a6(t)}else null!==a?(r5(t,a.cachePool),lx(t,a),a5(t),t.memoizedState=null):(null!==e&&r5(t,null),lE(),a5(t));return oy(e,t,l,n),t.child}function oS(e,t,n,r){var l=r6();return t.memoizedState={baseLanes:n,cachePool:l=null===l?null:{parent:rG._currentValue,pool:l}},null!==e&&r5(t,null),lE(),a6(t),null!==e&&rH(e,t,r,!0),null}function ox(e,t){var n=t.ref;if(null===n)null!==e&&null!==e.ref&&(t.flags|=4194816);else{if("function"!=typeof n&&"object"!=typeof n)throw Error(u(284));(null===e||e.ref!==n)&&(t.flags|=4194816)}}function oE(e,t,n,r,l){return(rV(t),n=lU(e,t,n,r,void 0,l),r=lV(),null===e||og)?(rx&&r&&rb(t),t.flags|=1,oy(e,t,n,l),t.child):(lB(e,t,l),oI(e,t,l))}function oC(e,t,n,r,l,a){return(rV(t),t.updateQueue=null,n=lH(t,r,n,l),lj(e),r=lV(),null===e||og)?(rx&&r&&rb(t),t.flags|=1,oy(e,t,n,a),t.child):(lB(e,t,a),oI(e,t,a))}function o_(e,t,n,r,l){if(rV(t),null===t.stateNode){var a=n9,o=n.contextType;"object"==typeof o&&null!==o&&(a=rB(o)),t.memoizedState=null!==(a=new n(r,a)).state&&void 0!==a.state?a.state:null,a.updater=on,t.stateNode=a,a._reactInternals=t,(a=t.stateNode).props=r,a.state=t.memoizedState,a.refs={},lc(t),o=n.contextType,a.context="object"==typeof o&&null!==o?rB(o):n9,a.state=t.memoizedState,"function"==typeof(o=n.getDerivedStateFromProps)&&(ot(t,n,o,r),a.state=t.memoizedState),"function"==typeof n.getDerivedStateFromProps||"function"==typeof a.getSnapshotBeforeUpdate||"function"!=typeof a.UNSAFE_componentWillMount&&"function"!=typeof a.componentWillMount||(o=a.state,"function"==typeof a.componentWillMount&&a.componentWillMount(),"function"==typeof a.UNSAFE_componentWillMount&&a.UNSAFE_componentWillMount(),o!==a.state&&on.enqueueReplaceState(a,a.state,null),lv(t,r,a,l),ly(),a.state=t.memoizedState),"function"==typeof a.componentDidMount&&(t.flags|=4194308),r=!0}else if(null===e){a=t.stateNode;var i=t.memoizedProps,u=oa(n,i);a.props=u;var s=a.context,c=n.contextType;o=n9,"object"==typeof c&&null!==c&&(o=rB(c));var f=n.getDerivedStateFromProps;c="function"==typeof f||"function"==typeof a.getSnapshotBeforeUpdate,i=t.pendingProps!==i,c||"function"!=typeof a.UNSAFE_componentWillReceiveProps&&"function"!=typeof a.componentWillReceiveProps||(i||s!==o)&&ol(t,a,r,o),ls=!1;var d=t.memoizedState;a.state=d,lv(t,r,a,l),ly(),s=t.memoizedState,i||d!==s||ls?("function"==typeof f&&(ot(t,n,f,r),s=t.memoizedState),(u=ls||or(t,n,u,r,d,s,o))?(c||"function"!=typeof a.UNSAFE_componentWillMount&&"function"!=typeof a.componentWillMount||("function"==typeof a.componentWillMount&&a.componentWillMount(),"function"==typeof a.UNSAFE_componentWillMount&&a.UNSAFE_componentWillMount()),"function"==typeof a.componentDidMount&&(t.flags|=4194308)):("function"==typeof a.componentDidMount&&(t.flags|=4194308),t.memoizedProps=r,t.memoizedState=s),a.props=r,a.state=s,a.context=o,r=u):("function"==typeof a.componentDidMount&&(t.flags|=4194308),r=!1)}else{a=t.stateNode,lf(e,t),c=oa(n,o=t.memoizedProps),a.props=c,f=t.pendingProps,d=a.context,s=n.contextType,u=n9,"object"==typeof s&&null!==s&&(u=rB(s)),(s="function"==typeof(i=n.getDerivedStateFromProps)||"function"==typeof a.getSnapshotBeforeUpdate)||"function"!=typeof a.UNSAFE_componentWillReceiveProps&&"function"!=typeof a.componentWillReceiveProps||(o!==f||d!==u)&&ol(t,a,r,u),ls=!1,d=t.memoizedState,a.state=d,lv(t,r,a,l),ly();var p=t.memoizedState;o!==f||d!==p||ls||null!==e&&null!==e.dependencies&&r$(e.dependencies)?("function"==typeof i&&(ot(t,n,i,r),p=t.memoizedState),(c=ls||or(t,n,c,r,d,p,u)||null!==e&&null!==e.dependencies&&r$(e.dependencies))?(s||"function"!=typeof a.UNSAFE_componentWillUpdate&&"function"!=typeof a.componentWillUpdate||("function"==typeof a.componentWillUpdate&&a.componentWillUpdate(r,p,u),"function"==typeof a.UNSAFE_componentWillUpdate&&a.UNSAFE_componentWillUpdate(r,p,u)),"function"==typeof a.componentDidUpdate&&(t.flags|=4),"function"==typeof a.getSnapshotBeforeUpdate&&(t.flags|=1024)):("function"!=typeof a.componentDidUpdate||o===e.memoizedProps&&d===e.memoizedState||(t.flags|=4),"function"!=typeof a.getSnapshotBeforeUpdate||o===e.memoizedProps&&d===e.memoizedState||(t.flags|=1024),t.memoizedProps=r,t.memoizedState=p),a.props=r,a.state=p,a.context=u,r=c):("function"!=typeof a.componentDidUpdate||o===e.memoizedProps&&d===e.memoizedState||(t.flags|=4),"function"!=typeof a.getSnapshotBeforeUpdate||o===e.memoizedProps&&d===e.memoizedState||(t.flags|=1024),r=!1)}return a=r,ox(e,t),r=0!=(128&t.flags),a||r?(a=t.stateNode,n=r&&"function"!=typeof n.getDerivedStateFromError?null:a.render(),t.flags|=1,null!==e&&r?(t.child=a1(t,e.child,null,l),t.child=a1(t,null,n,l)):oy(e,t,n,l),t.memoizedState=a.state,e=t.child):e=oI(e,t,l),e}function oP(e,t,n,r){return rL(),t.flags|=256,oy(e,t,n,r),t.child}var oz={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function oN(e){return{baseLanes:e,cachePool:r9()}}function oT(e,t,n){return e=null!==e?e.childLanes&~n:0,t&&(e|=i$),e}function oL(e,t,n){var r,l=t.pendingProps,a=!1,o=0!=(128&t.flags);if((r=o)||(r=(null===e||null!==e.memoizedState)&&0!=(2&a7.current)),r&&(a=!0,t.flags&=-129),r=0!=(32&t.flags),t.flags&=-33,null===e){if(rx){if(a?a8(t):a5(t),rx){var i,s=rS;if(i=s){n:{for(i=s,s=rC;8!==i.nodeType;)if(!s||null===(i=sk(i.nextSibling))){s=null;break n}s=i}null!==s?(t.memoizedState={dehydrated:s,treeContext:null!==rm?{id:rh,overflow:rg}:null,retryLane:0x20000000,hydrationErrors:null},(i=re(18,null,null,0)).stateNode=s,i.return=t,t.child=i,rw=t,rS=null,i=!0):i=!1}i||rP(t)}if(null!==(s=t.memoizedState)&&null!==(s=s.dehydrated))return sb(s)?t.lanes=32:t.lanes=0x20000000,null;a9(t)}return(s=l.children,l=l.fallback,a)?(a5(t),s=oR({mode:"hidden",children:s},a=t.mode),l=ra(l,a,n,null),s.return=t,l.return=t,s.sibling=l,t.child=s,(a=t.child).memoizedState=oN(n),a.childLanes=oT(e,r,n),t.memoizedState=oz,l):(a8(t),oO(t,s))}if(null!==(i=e.memoizedState)&&null!==(s=i.dehydrated)){if(o)256&t.flags?(a8(t),t.flags&=-257,t=oD(e,t,n)):null!==t.memoizedState?(a5(t),t.child=e.child,t.flags|=128,t=null):(a5(t),a=l.fallback,s=t.mode,l=oR({mode:"visible",children:l.children},s),a=ra(a,s,n,null),a.flags|=2,l.return=t,a.return=t,l.sibling=a,t.child=l,a1(t,e.child,null,n),(l=t.child).memoizedState=oN(n),l.childLanes=oT(e,r,n),t.memoizedState=oz,t=a);else if(a8(t),sb(s)){if(r=s.nextSibling&&s.nextSibling.dataset)var c=r.dgst;r=c,(l=Error(u(419))).stack="",l.digest=r,rR({value:l,source:null,stack:null}),t=oD(e,t,n)}else if(og||rH(e,t,n,!1),r=0!=(n&e.childLanes),og||r){if(null!==(r=iN)&&0!==(l=0!=((l=0!=(42&(l=n&-n))?1:eP(l))&(r.suspendedLanes|n))?0:l)&&l!==i.retryLane)throw i.retryLane=l,n8(e,l),i9(r,e,l),oh;"$?"===s.data||uu(),t=oD(e,t,n)}else"$?"===s.data?(t.flags|=192,t.child=e.child,t=null):(e=i.treeContext,rS=sk(s.nextSibling),rw=t,rx=!0,rE=null,rC=!1,null!==e&&(rd[rp++]=rh,rd[rp++]=rg,rd[rp++]=rm,rh=e.id,rg=e.overflow,rm=t),t=oO(t,l.children),t.flags|=4096);return t}return a?(a5(t),a=l.fallback,s=t.mode,c=(i=e.child).sibling,(l=rn(i,{mode:"hidden",children:l.children})).subtreeFlags=0x3e00000&i.subtreeFlags,null!==c?a=rn(c,a):(a=ra(a,s,n,null),a.flags|=2),a.return=t,l.return=t,l.sibling=a,t.child=l,l=a,a=t.child,null===(s=e.child.memoizedState)?s=oN(n):(null!==(i=s.cachePool)?(c=rG._currentValue,i=i.parent!==c?{parent:c,pool:c}:i):i=r9(),s={baseLanes:s.baseLanes|n,cachePool:i}),a.memoizedState=s,a.childLanes=oT(e,r,n),t.memoizedState=oz,l):(a8(t),e=(n=e.child).sibling,(n=rn(n,{mode:"visible",children:l.children})).return=t,n.sibling=null,null!==e&&(null===(r=t.deletions)?(t.deletions=[e],t.flags|=16):r.push(e)),t.child=n,t.memoizedState=null,n)}function oO(e,t){return(t=oR({mode:"visible",children:t},e.mode)).return=e,e.child=t}function oR(e,t){return(e=re(22,e,null,t)).lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function oD(e,t,n){return a1(t,e.child,null,n),e=oO(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function oA(e,t,n){e.lanes|=t;var r=e.alternate;null!==r&&(r.lanes|=t),rU(e.return,t,n)}function oF(e,t,n,r,l){var a=e.memoizedState;null===a?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:l}:(a.isBackwards=t,a.rendering=null,a.renderingStartTime=0,a.last=r,a.tail=n,a.tailMode=l)}function oM(e,t,n){var r=t.pendingProps,l=r.revealOrder,a=r.tail;if(oy(e,t,r.children,n),0!=(2&(r=a7.current)))r=1&r|2,t.flags|=128;else{if(null!==e&&0!=(128&e.flags))e:for(e=t.child;null!==e;){if(13===e.tag)null!==e.memoizedState&&oA(e,n,t);else if(19===e.tag)oA(e,n,t);else if(null!==e.child){e.child.return=e,e=e.child;continue}if(e===t)break;for(;null===e.sibling;){if(null===e.return||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}switch(H(a7,r),l){case"forwards":for(l=null,n=t.child;null!==n;)null!==(e=n.alternate)&&null===oe(e)&&(l=n),n=n.sibling;null===(n=l)?(l=t.child,t.child=null):(l=n.sibling,n.sibling=null),oF(t,!1,l,n,a);break;case"backwards":for(n=null,l=t.child,t.child=null;null!==l;){if(null!==(e=l.alternate)&&null===oe(e)){t.child=l;break}e=l.sibling,l.sibling=n,n=l,l=e}oF(t,!0,n,null,a);break;case"together":oF(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function oI(e,t,n){if(null!==e&&(t.dependencies=e.dependencies),iU|=t.lanes,0==(n&t.childLanes)){if(null===e)return null;else if(rH(e,t,n,!1),0==(n&t.childLanes))return null}if(null!==e&&t.child!==e.child)throw Error(u(153));if(null!==t.child){for(n=rn(e=t.child,e.pendingProps),t.child=n,n.return=t;null!==e.sibling;)e=e.sibling,(n=n.sibling=rn(e,e.pendingProps)).return=t;n.sibling=null}return t.child}function oU(e,t){return 0!=(e.lanes&t)||!!(null!==(e=e.dependencies)&&r$(e))}function oj(e,t,n){if(null!==e)if(e.memoizedProps!==t.pendingProps)og=!0;else{if(!oU(e,n)&&0==(128&t.flags))return og=!1,function(e,t,n){switch(t.tag){case 3:W(t,t.stateNode.containerInfo),rM(t,rG,e.memoizedState.cache),rL();break;case 27:case 5:K(t);break;case 4:W(t,t.stateNode.containerInfo);break;case 10:rM(t,t.type,t.memoizedProps.value);break;case 13:var r=t.memoizedState;if(null!==r){if(null!==r.dehydrated)return a8(t),t.flags|=128,null;if(0!=(n&t.child.childLanes))return oL(e,t,n);return a8(t),null!==(e=oI(e,t,n))?e.sibling:null}a8(t);break;case 19:var l=0!=(128&e.flags);if((r=0!=(n&t.childLanes))||(rH(e,t,n,!1),r=0!=(n&t.childLanes)),l){if(r)return oM(e,t,n);t.flags|=128}if(null!==(l=t.memoizedState)&&(l.rendering=null,l.tail=null,l.lastEffect=null),H(a7,a7.current),!r)return null;break;case 22:case 23:return t.lanes=0,ow(e,t,n);case 24:rM(t,rG,e.memoizedState.cache)}return oI(e,t,n)}(e,t,n);og=0!=(131072&e.flags)}else og=!1,rx&&0!=(1048576&t.flags)&&rv(t,rf,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var r=t.elementType,l=r._init;if(r=l(r._payload),t.type=r,"function"==typeof r)rt(r)?(e=oa(r,e),t.tag=1,t=o_(null,t,r,e,n)):(t.tag=0,t=oE(null,t,r,e,n));else{if(null!=r){if((l=r.$$typeof)===x){t.tag=11,t=ov(null,t,r,e,n);break e}else if(l===_){t.tag=14,t=ob(null,t,r,e,n);break e}}throw Error(u(306,t=function e(t){if(null==t)return null;if("function"==typeof t)return t.$$typeof===O?null:t.displayName||t.name||null;if("string"==typeof t)return t;switch(t){case y:return"Fragment";case b:return"Profiler";case v:return"StrictMode";case E:return"Suspense";case C:return"SuspenseList";case z:return"Activity"}if("object"==typeof t)switch(t.$$typeof){case g:return"Portal";case S:return(t.displayName||"Context")+".Provider";case w:return(t._context.displayName||"Context")+".Consumer";case x:var n=t.render;return(t=t.displayName)||(t=""!==(t=n.displayName||n.name||"")?"ForwardRef("+t+")":"ForwardRef"),t;case _:return null!==(n=t.displayName||null)?n:e(t.type)||"Memo";case P:n=t._payload,t=t._init;try{return e(t(n))}catch(e){}}return null}(r)||r,""))}}return t;case 0:return oE(e,t,t.type,t.pendingProps,n);case 1:return l=oa(r=t.type,t.pendingProps),o_(e,t,r,l,n);case 3:e:{if(W(t,t.stateNode.containerInfo),null===e)throw Error(u(387));r=t.pendingProps;var a=t.memoizedState;l=a.element,lf(e,t),lv(t,r,null,n);var o=t.memoizedState;if(rM(t,rG,r=o.cache),r!==a.cache&&rj(t,[rG],n,!0),ly(),r=o.element,a.isDehydrated)if(a={element:r,isDehydrated:!1,cache:o.cache},t.updateQueue.baseState=a,t.memoizedState=a,256&t.flags){t=oP(e,t,r,n);break e}else if(r!==l){rR(l=nZ(Error(u(424)),t)),t=oP(e,t,r,n);break e}else for(rS=sk((e=9===(e=t.stateNode.containerInfo).nodeType?e.body:"HTML"===e.nodeName?e.ownerDocument.body:e).firstChild),rw=t,rx=!0,rE=null,rC=!0,n=a2(t,null,r,n),t.child=n;n;)n.flags=-3&n.flags|4096,n=n.sibling;else{if(rL(),r===l){t=oI(e,t,n);break e}oy(e,t,r,n)}t=t.child}return t;case 26:return ox(e,t),null===e?(n=sL(t.type,null,t.pendingProps,null))?t.memoizedState=n:rx||(n=t.type,e=t.pendingProps,(r=so(B.current).createElement(n))[eL]=t,r[eO]=e,sr(r,n,e),eB(r),t.stateNode=r):t.memoizedState=sL(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return K(t),null===e&&rx&&(r=t.stateNode=sx(t.type,t.pendingProps,B.current),rw=t,rC=!0,l=rS,sg(t.type)?(sw=l,rS=sk(r.firstChild)):rS=l),oy(e,t,t.pendingProps.children,n),ox(e,t),null===e&&(t.flags|=4194304),t.child;case 5:return null===e&&rx&&((l=r=rS)&&(null!==(r=function(e,t,n,r){for(;1===e.nodeType;){if(e.nodeName.toLowerCase()!==t.toLowerCase()){if(!r&&("INPUT"!==e.nodeName||"hidden"!==e.type))break}else if(r){if(!e[eI])switch(t){case"meta":if(!e.hasAttribute("itemprop"))break;return e;case"link":if("stylesheet"===(l=e.getAttribute("rel"))&&e.hasAttribute("data-precedence")||l!==n.rel||e.getAttribute("href")!==(null==n.href||""===n.href?null:n.href)||e.getAttribute("crossorigin")!==(null==n.crossOrigin?null:n.crossOrigin)||e.getAttribute("title")!==(null==n.title?null:n.title))break;return e;case"style":if(e.hasAttribute("data-precedence"))break;return e;case"script":if(((l=e.getAttribute("src"))!==(null==n.src?null:n.src)||e.getAttribute("type")!==(null==n.type?null:n.type)||e.getAttribute("crossorigin")!==(null==n.crossOrigin?null:n.crossOrigin))&&l&&e.hasAttribute("async")&&!e.hasAttribute("itemprop"))break;return e;default:return e}}else{if("input"!==t||"hidden"!==e.type)return e;var l=null==n.name?null:""+n.name;if("hidden"===n.type&&e.getAttribute("name")===l)return e}if(null===(e=sk(e.nextSibling)))break}return null}(r,t.type,t.pendingProps,rC))?(t.stateNode=r,rw=t,rS=sk(r.firstChild),rC=!1,l=!0):l=!1),l||rP(t)),K(t),l=t.type,a=t.pendingProps,o=null!==e?e.memoizedProps:null,r=a.children,ss(l,a)?r=null:null!==o&&ss(l,o)&&(t.flags|=32),null!==t.memoizedState&&(sX._currentValue=l=lU(e,t,l$,null,null,n)),ox(e,t),oy(e,t,r,n),t.child;case 6:return null===e&&rx&&((e=n=rS)&&(null!==(n=function(e,t,n){if(""===t)return null;for(;3!==e.nodeType;)if((1!==e.nodeType||"INPUT"!==e.nodeName||"hidden"!==e.type)&&!n||null===(e=sk(e.nextSibling)))return null;return e}(n,t.pendingProps,rC))?(t.stateNode=n,rw=t,rS=null,e=!0):e=!1),e||rP(t)),null;case 13:return oL(e,t,n);case 4:return W(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=a1(t,null,r,n):oy(e,t,r,n),t.child;case 11:return ov(e,t,t.type,t.pendingProps,n);case 7:return oy(e,t,t.pendingProps,n),t.child;case 8:case 12:return oy(e,t,t.pendingProps.children,n),t.child;case 10:return r=t.pendingProps,rM(t,t.type,r.value),oy(e,t,r.children,n),t.child;case 9:return l=t.type._context,r=t.pendingProps.children,rV(t),r=r(l=rB(l)),t.flags|=1,oy(e,t,r,n),t.child;case 14:return ob(e,t,t.type,t.pendingProps,n);case 15:return ok(e,t,t.type,t.pendingProps,n);case 19:return oM(e,t,n);case 31:return r=t.pendingProps,n=t.mode,r={mode:r.mode,children:r.children},null===e?(n=oR(r,n)).ref=t.ref:(n=rn(e.child,r)).ref=t.ref,t.child=n,n.return=t,t=n;case 22:return ow(e,t,n);case 24:return rV(t),r=rB(rG),null===e?(null===(l=r6())&&(l=iN,a=rX(),l.pooledCache=a,a.refCount++,null!==a&&(l.pooledCacheLanes|=n),l=a),t.memoizedState={parent:r,cache:l},lc(t),rM(t,rG,l)):(0!=(e.lanes&n)&&(lf(e,t),lv(t,null,null,n),ly()),l=e.memoizedState,a=t.memoizedState,l.parent!==r?(l={parent:r,cache:r},t.memoizedState=l,0===t.lanes&&(t.memoizedState=t.updateQueue.baseState=l),rM(t,rG,r)):(rM(t,rG,r=a.cache),r!==l.cache&&rj(t,[rG],n,!0))),oy(e,t,t.pendingProps.children,n),t.child;case 29:throw t.pendingProps}throw Error(u(156,t.tag))}function oH(e){e.flags|=4}function o$(e,t){if("stylesheet"!==t.type||0!=(4&t.state.loading))e.flags&=-0x1000001;else if(e.flags|=0x1000000,!sB(t)){if(null!==(t=a3.current)&&((4194048&iL)===iL?null!==a4:(0x3c00000&iL)!==iL&&0==(0x20000000&iL)||t!==a4))throw lo=ln,le;e.flags|=8192}}function oV(e,t){null!==t&&(e.flags|=4),16384&e.flags&&(t=22!==e.tag?eS():0x20000000,e.lanes|=t,iV|=t)}function oB(e,t){if(!rx)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;null!==t;)null!==t.alternate&&(n=t),t=t.sibling;null===n?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;null!==n;)null!==n.alternate&&(r=n),n=n.sibling;null===r?t||null===e.tail?e.tail=null:e.tail.sibling=null:r.sibling=null}}function oQ(e){var t=null!==e.alternate&&e.alternate.child===e.child,n=0,r=0;if(t)for(var l=e.child;null!==l;)n|=l.lanes|l.childLanes,r|=0x3e00000&l.subtreeFlags,r|=0x3e00000&l.flags,l.return=e,l=l.sibling;else for(l=e.child;null!==l;)n|=l.lanes|l.childLanes,r|=l.subtreeFlags,r|=l.flags,l.return=e,l=l.sibling;return e.subtreeFlags|=r,e.childLanes=n,t}function oW(e,t){switch(rk(t),t.tag){case 3:rI(rG),q();break;case 26:case 27:case 5:Y(t);break;case 4:q();break;case 13:a9(t);break;case 19:j(a7);break;case 10:rI(t.type);break;case 22:case 23:a9(t),lC(),null!==e&&j(r8);break;case 24:rI(rG)}}function oq(e,t){try{var n=t.updateQueue,r=null!==n?n.lastEffect:null;if(null!==r){var l=r.next;n=l;do{if((n.tag&e)===e){r=void 0;var a=n.create;n.inst.destroy=r=a()}n=n.next}while(n!==l)}}catch(e){ux(t,t.return,e)}}function oK(e,t,n){try{var r=t.updateQueue,l=null!==r?r.lastEffect:null;if(null!==l){var a=l.next;r=a;do{if((r.tag&e)===e){var o=r.inst,i=o.destroy;if(void 0!==i){o.destroy=void 0,l=t;try{i()}catch(e){ux(l,n,e)}}}r=r.next}while(r!==a)}}catch(e){ux(t,t.return,e)}}function oY(e){var t=e.updateQueue;if(null!==t){var n=e.stateNode;try{lk(t,n)}catch(t){ux(e,e.return,t)}}}function oG(e,t,n){n.props=oa(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){ux(e,t,n)}}function oX(e,t){try{var n=e.ref;if(null!==n){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;default:r=e.stateNode}"function"==typeof n?e.refCleanup=n(r):n.current=r}}catch(n){ux(e,t,n)}}function oZ(e,t){var n=e.ref,r=e.refCleanup;if(null!==n)if("function"==typeof r)try{r()}catch(n){ux(e,t,n)}finally{e.refCleanup=null,null!=(e=e.alternate)&&(e.refCleanup=null)}else if("function"==typeof n)try{n(null)}catch(n){ux(e,t,n)}else n.current=null}function oJ(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{switch(t){case"button":case"input":case"select":case"textarea":n.autoFocus&&r.focus();break;case"img":n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){ux(e,e.return,t)}}function o0(e,t,n){try{var r=e.stateNode;(function(e,t,n,r){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"input":var l=null,a=null,o=null,i=null,s=null,c=null,f=null;for(m in n){var d=n[m];if(n.hasOwnProperty(m)&&null!=d)switch(m){case"checked":case"value":break;case"defaultValue":s=d;default:r.hasOwnProperty(m)||st(e,t,m,null,r,d)}}for(var p in r){var m=r[p];if(d=n[p],r.hasOwnProperty(p)&&(null!=m||null!=d))switch(p){case"type":a=m;break;case"name":l=m;break;case"checked":c=m;break;case"defaultChecked":f=m;break;case"value":o=m;break;case"defaultValue":i=m;break;case"children":case"dangerouslySetInnerHTML":if(null!=m)throw Error(u(137,t));break;default:m!==d&&st(e,t,p,m,r,d)}}tn(e,o,i,s,c,f,a,l);return;case"select":for(a in m=o=i=p=null,n)if(s=n[a],n.hasOwnProperty(a)&&null!=s)switch(a){case"value":break;case"multiple":m=s;default:r.hasOwnProperty(a)||st(e,t,a,null,r,s)}for(l in r)if(a=r[l],s=n[l],r.hasOwnProperty(l)&&(null!=a||null!=s))switch(l){case"value":p=a;break;case"defaultValue":i=a;break;case"multiple":o=a;default:a!==s&&st(e,t,l,a,r,s)}t=i,n=o,r=m,null!=p?ta(e,!!n,p,!1):!!r!=!!n&&(null!=t?ta(e,!!n,t,!0):ta(e,!!n,n?[]:"",!1));return;case"textarea":for(i in m=p=null,n)if(l=n[i],n.hasOwnProperty(i)&&null!=l&&!r.hasOwnProperty(i))switch(i){case"value":case"children":break;default:st(e,t,i,null,r,l)}for(o in r)if(l=r[o],a=n[o],r.hasOwnProperty(o)&&(null!=l||null!=a))switch(o){case"value":p=l;break;case"defaultValue":m=l;break;case"children":break;case"dangerouslySetInnerHTML":if(null!=l)throw Error(u(91));break;default:l!==a&&st(e,t,o,l,r,a)}to(e,p,m);return;case"option":for(var h in n)p=n[h],n.hasOwnProperty(h)&&null!=p&&!r.hasOwnProperty(h)&&("selected"===h?e.selected=!1:st(e,t,h,null,r,p));for(s in r)p=r[s],m=n[s],r.hasOwnProperty(s)&&p!==m&&(null!=p||null!=m)&&("selected"===s?e.selected=p&&"function"!=typeof p&&"symbol"!=typeof p:st(e,t,s,p,r,m));return;case"img":case"link":case"area":case"base":case"br":case"col":case"embed":case"hr":case"keygen":case"meta":case"param":case"source":case"track":case"wbr":case"menuitem":for(var g in n)p=n[g],n.hasOwnProperty(g)&&null!=p&&!r.hasOwnProperty(g)&&st(e,t,g,null,r,p);for(c in r)if(p=r[c],m=n[c],r.hasOwnProperty(c)&&p!==m&&(null!=p||null!=m))switch(c){case"children":case"dangerouslySetInnerHTML":if(null!=p)throw Error(u(137,t));break;default:st(e,t,c,p,r,m)}return;default:if(td(t)){for(var y in n)p=n[y],n.hasOwnProperty(y)&&void 0!==p&&!r.hasOwnProperty(y)&&sn(e,t,y,void 0,r,p);for(f in r)p=r[f],m=n[f],r.hasOwnProperty(f)&&p!==m&&(void 0!==p||void 0!==m)&&sn(e,t,f,p,r,m);return}}for(var v in n)p=n[v],n.hasOwnProperty(v)&&null!=p&&!r.hasOwnProperty(v)&&st(e,t,v,null,r,p);for(d in r)p=r[d],m=n[d],r.hasOwnProperty(d)&&p!==m&&(null!=p||null!=m)&&st(e,t,d,p,r,m)})(r,e.type,n,t),r[eO]=t}catch(t){ux(e,e.return,t)}}function o1(e){return 5===e.tag||3===e.tag||26===e.tag||27===e.tag&&sg(e.type)||4===e.tag}function o2(e){e:for(;;){for(;null===e.sibling;){if(null===e.return||o1(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;5!==e.tag&&6!==e.tag&&18!==e.tag;){if(27===e.tag&&sg(e.type)||2&e.flags||null===e.child||4===e.tag)continue e;e.child.return=e,e=e.child}if(!(2&e.flags))return e.stateNode}}function o3(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(4!==r&&(27===r&&sg(e.type)&&(n=e.stateNode),null!==(e=e.child)))for(o3(e,t,n),e=e.sibling;null!==e;)o3(e,t,n),e=e.sibling}function o4(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,l=t.attributes;l.length;)t.removeAttributeNode(l[0]);sr(t,r,n),t[eL]=e,t[eO]=n}catch(t){ux(e,e.return,t)}}var o8=!1,o6=!1,o5=!1,o9="function"==typeof WeakSet?WeakSet:Set,o7=null;function ie(e,t,n){var r=n.flags;switch(n.tag){case 0:case 11:case 15:ip(e,n),4&r&&oq(5,n);break;case 1:if(ip(e,n),4&r)if(e=n.stateNode,null===t)try{e.componentDidMount()}catch(e){ux(n,n.return,e)}else{var l=oa(n.type,t.memoizedProps);t=t.memoizedState;try{e.componentDidUpdate(l,t,e.__reactInternalSnapshotBeforeUpdate)}catch(e){ux(n,n.return,e)}}64&r&&oY(n),512&r&&oX(n,n.return);break;case 3:if(ip(e,n),64&r&&null!==(e=n.updateQueue)){if(t=null,null!==n.child)switch(n.child.tag){case 27:case 5:case 1:t=n.child.stateNode}try{lk(e,t)}catch(e){ux(n,n.return,e)}}break;case 27:null===t&&4&r&&o4(n);case 26:case 5:ip(e,n),null===t&&4&r&&oJ(n),512&r&&oX(n,n.return);break;case 12:default:ip(e,n);break;case 13:ip(e,n),4&r&&io(e,n),64&r&&null!==(e=n.memoizedState)&&null!==(e=e.dehydrated)&&function(e,t){var n=e.ownerDocument;if("$?"!==e.data||"complete"===n.readyState)t();else{var r=function(){t(),n.removeEventListener("DOMContentLoaded",r)};n.addEventListener("DOMContentLoaded",r),e._reactRetry=r}}(e,n=uP.bind(null,n));break;case 22:if(!(r=null!==n.memoizedState||o8)){t=null!==t&&null!==t.memoizedState||o6,l=o8;var a=o6;o8=r,(o6=t)&&!a?function e(t,n,r){for(r=r&&0!=(8772&n.subtreeFlags),n=n.child;null!==n;){var l=n.alternate,a=t,o=n,i=o.flags;switch(o.tag){case 0:case 11:case 15:e(a,o,r),oq(4,o);break;case 1:if(e(a,o,r),"function"==typeof(a=(l=o).stateNode).componentDidMount)try{a.componentDidMount()}catch(e){ux(l,l.return,e)}if(null!==(a=(l=o).updateQueue)){var u=l.stateNode;try{var s=a.shared.hiddenCallbacks;if(null!==s)for(a.shared.hiddenCallbacks=null,a=0;a title"))),sr(a,r,n),a[eL]=e,eB(a),r=a;break e;case"link":var o=s$("link","href",l).get(r+(n.href||""));if(o){for(var i=0;i<\/script>",e=e.removeChild(e.firstChild);break;case"select":e="string"==typeof r.is?l.createElement("select",{is:r.is}):l.createElement("select"),r.multiple?e.multiple=!0:r.size&&(e.size=r.size);break;default:e="string"==typeof r.is?l.createElement(n,{is:r.is}):l.createElement(n)}}e[eL]=t,e[eO]=r;e:for(l=t.child;null!==l;){if(5===l.tag||6===l.tag)e.appendChild(l.stateNode);else if(4!==l.tag&&27!==l.tag&&null!==l.child){l.child.return=l,l=l.child;continue}if(l===t)break;for(;null===l.sibling;){if(null===l.return||l.return===t)break e;l=l.return}l.sibling.return=l.return,l=l.sibling}switch(t.stateNode=e,sr(e,n,r),n){case"button":case"input":case"select":case"textarea":e=!!r.autoFocus;break;case"img":e=!0;break;default:e=!1}e&&oH(t)}}return oQ(t),t.flags&=-0x1000001,null;case 6:if(e&&null!=t.stateNode)e.memoizedProps!==r&&oH(t);else{if("string"!=typeof r&&null===t.stateNode)throw Error(u(166));if(e=B.current,rT(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,null!==(l=rw))switch(l.tag){case 27:case 5:r=l.memoizedProps}e[eL]=t,(e=!!(e.nodeValue===n||null!==r&&!0===r.suppressHydrationWarning||u7(e.nodeValue,n)))||rP(t)}else(e=so(e).createTextNode(r))[eL]=t,t.stateNode=e}return oQ(t),null;case 13:if(r=t.memoizedState,null===e||null!==e.memoizedState&&null!==e.memoizedState.dehydrated){if(l=rT(t),null!==r&&null!==r.dehydrated){if(null===e){if(!l)throw Error(u(318));if(!(l=null!==(l=t.memoizedState)?l.dehydrated:null))throw Error(u(317));l[eL]=t}else rL(),0==(128&t.flags)&&(t.memoizedState=null),t.flags|=4;oQ(t),l=!1}else l=rO(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=l),l=!0;if(!l){if(256&t.flags)return a9(t),t;return a9(t),null}}if(a9(t),0!=(128&t.flags))return t.lanes=n,t;if(n=null!==r,e=null!==e&&null!==e.memoizedState,n){r=t.child,l=null,null!==r.alternate&&null!==r.alternate.memoizedState&&null!==r.alternate.memoizedState.cachePool&&(l=r.alternate.memoizedState.cachePool.pool);var a=null;null!==r.memoizedState&&null!==r.memoizedState.cachePool&&(a=r.memoizedState.cachePool.pool),a!==l&&(r.flags|=2048)}return n!==e&&n&&(t.child.flags|=8192),oV(t,t.updateQueue),oQ(t),null;case 4:return q(),null===e&&uJ(t.stateNode.containerInfo),oQ(t),null;case 10:return rI(t.type),oQ(t),null;case 19:if(j(a7),null===(l=t.memoizedState))return oQ(t),null;if(r=0!=(128&t.flags),null===(a=l.rendering))if(r)oB(l,!1);else{if(0!==iI||null!==e&&0!=(128&e.flags))for(e=t.child;null!==e;){if(null!==(a=oe(e))){for(t.flags|=128,oB(l,!1),e=a.updateQueue,t.updateQueue=e,oV(t,e),t.subtreeFlags=0,e=n,n=t.child;null!==n;)rr(n,e),n=n.sibling;return H(a7,1&a7.current|2),t.child}e=e.sibling}null!==l.tail&&et()>iK&&(t.flags|=128,r=!0,oB(l,!1),t.lanes=4194304)}else{if(!r)if(null!==(e=oe(a))){if(t.flags|=128,r=!0,e=e.updateQueue,t.updateQueue=e,oV(t,e),oB(l,!0),null===l.tail&&"hidden"===l.tailMode&&!a.alternate&&!rx)return oQ(t),null}else 2*et()-l.renderingStartTime>iK&&0x20000000!==n&&(t.flags|=128,r=!0,oB(l,!1),t.lanes=4194304);l.isBackwards?(a.sibling=t.child,t.child=a):(null!==(e=l.last)?e.sibling=a:t.child=a,l.last=a)}if(null!==l.tail)return t=l.tail,l.rendering=t,l.tail=t.sibling,l.renderingStartTime=et(),t.sibling=null,e=a7.current,H(a7,r?1&e|2:1&e),t;return oQ(t),null;case 22:case 23:return a9(t),lC(),r=null!==t.memoizedState,null!==e?null!==e.memoizedState!==r&&(t.flags|=8192):r&&(t.flags|=8192),r?0!=(0x20000000&n)&&0==(128&t.flags)&&(oQ(t),6&t.subtreeFlags&&(t.flags|=8192)):oQ(t),null!==(n=t.updateQueue)&&oV(t,n.retryQueue),n=null,null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(n=e.memoizedState.cachePool.pool),r=null,null!==t.memoizedState&&null!==t.memoizedState.cachePool&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),null!==e&&j(r8),null;case 24:return n=null,null!==e&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),rI(rG),oQ(t),null;case 25:case 30:return null}throw Error(u(156,t.tag))}(t.alternate,t,iM);if(null!==n){iT=n;return}if(null!==(t=t.sibling)){iT=t;return}iT=t=e}while(null!==t);0===iI&&(iI=5)}function um(e,t){do{var n=function(e,t){switch(rk(t),t.tag){case 1:return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 3:return rI(rG),q(),0!=(65536&(e=t.flags))&&0==(128&e)?(t.flags=-65537&e|128,t):null;case 26:case 27:case 5:return Y(t),null;case 13:if(a9(t),null!==(e=t.memoizedState)&&null!==e.dehydrated){if(null===t.alternate)throw Error(u(340));rL()}return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 19:return j(a7),null;case 4:return q(),null;case 10:return rI(t.type),null;case 22:case 23:return a9(t),lC(),null!==e&&j(r8),65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 24:return rI(rG),null;default:return null}}(e.alternate,e);if(null!==n){n.flags&=32767,iT=n;return}if(null!==(n=e.return)&&(n.flags|=32768,n.subtreeFlags=0,n.deletions=null),!t&&null!==(e=e.sibling)){iT=e;return}iT=e=n}while(null!==e);iI=6,iT=null}function uh(e,t,n,r,l,a,o,i,s){e.cancelPendingCommit=null;do uk();while(0!==iX);if(0!=(6&iz))throw Error(u(327));if(null!==t){if(t===e.current)throw Error(u(177));if(!function(e,t,n,r,l,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var i=e.entanglements,u=e.expirationTimes,s=e.hiddenUpdates;for(n=o&~n;0g&&(o=g,g=h,h=o);var y=nP(i,h),v=nP(i,g);if(y&&v&&(1!==p.rangeCount||p.anchorNode!==y.node||p.anchorOffset!==y.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var b=f.createRange();b.setStart(y.node,y.offset),p.removeAllRanges(),h>g?(p.addRange(b),p.extend(v.node,v.offset)):(b.setEnd(v.node,v.offset),p.addRange(b))}}}}for(f=[],p=i;p=p.parentNode;)1===p.nodeType&&f.push({element:p,left:p.scrollLeft,top:p.scrollTop});for("function"==typeof i.focus&&i.focus(),i=0;in?32:n,D.T=null,n=i2,i2=null;var a=iZ,o=i0;if(iX=0,iJ=iZ=null,i0=0,0!=(6&iz))throw Error(u(331));var i=iz;if(iz|=4,iE(a.current),iy(a,a.current,o,n),iz=i,uF(0,!1),ef&&"function"==typeof ef.onPostCommitFiberRoot)try{ef.onPostCommitFiberRoot(ec,a)}catch(e){}return!0}finally{A.p=l,D.T=r,ub(e,t)}}function uS(e,t,n){t=nZ(n,t),t=od(e.stateNode,t,2),null!==(e=lp(e,t,2))&&(eE(e,2),uA(e))}function ux(e,t,n){if(3===e.tag)uS(e,e,n);else for(;null!==t;){if(3===t.tag){uS(t,e,n);break}if(1===t.tag){var r=t.stateNode;if("function"==typeof t.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===iG||!iG.has(r))){e=nZ(n,e),null!==(r=lp(t,n=op(2),2))&&(om(n,r,t,e),eE(r,2),uA(r));break}}t=t.return}}function uE(e,t,n){var r=e.pingCache;if(null===r){r=e.pingCache=new iP;var l=new Set;r.set(t,l)}else void 0===(l=r.get(t))&&(l=new Set,r.set(t,l));l.has(n)||(iF=!0,l.add(n),e=uC.bind(null,e,t,n),t.then(e,e))}function uC(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,iN===e&&(iL&n)===n&&(4===iI||3===iI&&(0x3c00000&iL)===iL&&300>et()-iq?0==(2&iz)&&ul(e,0):iH|=n,iV===iL&&(iV=0)),uA(e)}function u_(e,t){0===t&&(t=eS()),null!==(e=n8(e,t))&&(eE(e,t),uA(e))}function uP(e){var t=e.memoizedState,n=0;null!==t&&(n=t.retryLane),u_(e,n)}function uz(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;null!==l&&(n=l.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(u(314))}null!==r&&r.delete(t),u_(e,n)}var uN=null,uT=null,uL=!1,uO=!1,uR=!1,uD=0;function uA(e){e!==uT&&null===e.next&&(null===uT?uN=uT=e:uT=uT.next=e),uO=!0,uL||(uL=!0,sm(function(){0!=(6&iz)?X(er,uM):uI()}))}function uF(e,t){if(!uR&&uO){uR=!0;do for(var n=!1,r=uN;null!==r;){if(!t)if(0!==e){var l=r.pendingLanes;if(0===l)var a=0;else{var o=r.suspendedLanes,i=r.pingedLanes;a=0xc000095&(a=(1<<31-ep(42|e)+1)-1&(l&~(o&~i)))?0xc000095&a|1:a?2|a:0}0!==a&&(n=!0,uH(r,a))}else a=iL,0==(3&(a=eb(r,r===iN?a:0,null!==r.cancelPendingCommit||-1!==r.timeoutHandle)))||ek(r,a)||(n=!0,uH(r,a));r=r.next}while(n);uR=!1}}function uM(){uI()}function uI(){uO=uL=!1;var e,t=0;0!==uD&&(((e=window.event)&&"popstate"===e.type?e===sc||(sc=e,0):(sc=null,1))||(t=uD),uD=0);for(var n=et(),r=null,l=uN;null!==l;){var a=l.next,o=uU(l,n);0===o?(l.next=null,null===r?uN=a:r.next=a,null===a&&(uT=r)):(r=l,(0!==t||0!=(3&o))&&(uO=!0)),l=a}uF(t,!1)}function uU(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,l=e.expirationTimes,a=-0x3c00001&e.pendingLanes;0r){n=r;var o=e.ownerDocument;if(1&n&&sE(o.documentElement),2&n&&sE(o.body),4&n)for(sE(n=o.head),o=n.firstChild;o;){var i=o.nextSibling,u=o.nodeName;o[eI]||"SCRIPT"===u||"STYLE"===u||"LINK"===u&&"stylesheet"===o.rel.toLowerCase()||n.removeChild(o),o=i}}if(0===l){e.removeChild(a),cb(t);return}l--}else"$"===n||"$?"===n||"$!"===n?l++:r=n.charCodeAt(0)-48;else r=0;n=a}while(n);cb(t)}function sv(e){var t=e.firstChild;for(t&&10===t.nodeType&&(t=t.nextSibling);t;){var n=t;switch(t=t.nextSibling,n.nodeName){case"HTML":case"HEAD":case"BODY":sv(n),eU(n);continue;case"SCRIPT":case"STYLE":continue;case"LINK":if("stylesheet"===n.rel.toLowerCase())continue}e.removeChild(n)}}function sb(e){return"$!"===e.data||"$?"===e.data&&"complete"===e.ownerDocument.readyState}function sk(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break;if(8===t){if("$"===(t=e.data)||"$!"===t||"$?"===t||"F!"===t||"F"===t)break;if("/$"===t)return null}}return e}var sw=null;function sS(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("$"===n||"$!"===n||"$?"===n){if(0===t)return e;t--}else"/$"===n&&t++}e=e.previousSibling}return null}function sx(e,t,n){switch(t=so(n),e){case"html":if(!(e=t.documentElement))throw Error(u(452));return e;case"head":if(!(e=t.head))throw Error(u(453));return e;case"body":if(!(e=t.body))throw Error(u(454));return e;default:throw Error(u(451))}}function sE(e){for(var t=e.attributes;t.length;)e.removeAttributeNode(t[0]);eU(e)}var sC=new Map,s_=new Set;function sP(e){return"function"==typeof e.getRootNode?e.getRootNode():9===e.nodeType?e:e.ownerDocument}var sz=A.d;A.d={f:function(){var e=sz.f(),t=un();return e||t},r:function(e){var t=eH(e);null!==t&&5===t.tag&&"form"===t.type?aO(t):sz.r(e)},D:function(e){sz.D(e),sT("dns-prefetch",e,null)},C:function(e,t){sz.C(e,t),sT("preconnect",e,t)},L:function(e,t,n){if(sz.L(e,t,n),sN&&e&&t){var r='link[rel="preload"][as="'+tt(t)+'"]';"image"===t&&n&&n.imageSrcSet?(r+='[imagesrcset="'+tt(n.imageSrcSet)+'"]',"string"==typeof n.imageSizes&&(r+='[imagesizes="'+tt(n.imageSizes)+'"]')):r+='[href="'+tt(e)+'"]';var l=r;switch(t){case"style":l=sO(e);break;case"script":l=sA(e)}sC.has(l)||(e=p({rel:"preload",href:"image"===t&&n&&n.imageSrcSet?void 0:e,as:t},n),sC.set(l,e),null!==sN.querySelector(r)||"style"===t&&sN.querySelector(sR(l))||"script"===t&&sN.querySelector(sF(l))||(sr(t=sN.createElement("link"),"link",e),eB(t),sN.head.appendChild(t)))}},m:function(e,t){if(sz.m(e,t),sN&&e){var n=t&&"string"==typeof t.as?t.as:"script",r='link[rel="modulepreload"][as="'+tt(n)+'"][href="'+tt(e)+'"]',l=r;switch(n){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":l=sA(e)}if(!sC.has(l)&&(e=p({rel:"modulepreload",href:e},t),sC.set(l,e),null===sN.querySelector(r))){switch(n){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(sN.querySelector(sF(l)))return}sr(n=sN.createElement("link"),"link",e),eB(n),sN.head.appendChild(n)}}},X:function(e,t){if(sz.X(e,t),sN&&e){var n=eV(sN).hoistableScripts,r=sA(e),l=n.get(r);l||((l=sN.querySelector(sF(r)))||(e=p({src:e,async:!0},t),(t=sC.get(r))&&sj(e,t),eB(l=sN.createElement("script")),sr(l,"link",e),sN.head.appendChild(l)),l={type:"script",instance:l,count:1,state:null},n.set(r,l))}},S:function(e,t,n){if(sz.S(e,t,n),sN&&e){var r=eV(sN).hoistableStyles,l=sO(e);t=t||"default";var a=r.get(l);if(!a){var o={loading:0,preload:null};if(a=sN.querySelector(sR(l)))o.loading=5;else{e=p({rel:"stylesheet",href:e,"data-precedence":t},n),(n=sC.get(l))&&sU(e,n);var i=a=sN.createElement("link");eB(i),sr(i,"link",e),i._p=new Promise(function(e,t){i.onload=e,i.onerror=t}),i.addEventListener("load",function(){o.loading|=1}),i.addEventListener("error",function(){o.loading|=2}),o.loading|=4,sI(a,t,sN)}a={type:"stylesheet",instance:a,count:1,state:o},r.set(l,a)}}},M:function(e,t){if(sz.M(e,t),sN&&e){var n=eV(sN).hoistableScripts,r=sA(e),l=n.get(r);l||((l=sN.querySelector(sF(r)))||(e=p({src:e,async:!0,type:"module"},t),(t=sC.get(r))&&sj(e,t),eB(l=sN.createElement("script")),sr(l,"link",e),sN.head.appendChild(l)),l={type:"script",instance:l,count:1,state:null},n.set(r,l))}}};var sN="undefined"==typeof document?null:document;function sT(e,t,n){if(sN&&"string"==typeof t&&t){var r=tt(t);r='link[rel="'+e+'"][href="'+r+'"]',"string"==typeof n&&(r+='[crossorigin="'+n+'"]'),s_.has(r)||(s_.add(r),e={rel:e,crossOrigin:n,href:t},null===sN.querySelector(r)&&(sr(t=sN.createElement("link"),"link",e),eB(t),sN.head.appendChild(t)))}}function sL(e,t,n,r){var l=(l=B.current)?sP(l):null;if(!l)throw Error(u(446));switch(e){case"meta":case"title":return null;case"style":return"string"==typeof n.precedence&&"string"==typeof n.href?(t=sO(n.href),(r=(n=eV(l).hoistableStyles).get(t))||(r={type:"style",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};case"link":if("stylesheet"===n.rel&&"string"==typeof n.href&&"string"==typeof n.precedence){e=sO(n.href);var a,o,i,s,c=eV(l).hoistableStyles,f=c.get(e);if(f||(l=l.ownerDocument||l,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},c.set(e,f),(c=l.querySelector(sR(e)))&&!c._p&&(f.instance=c,f.state.loading=5),sC.has(e)||(n={rel:"preload",as:"style",href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},sC.set(e,n),c||(a=l,o=e,i=n,s=f.state,a.querySelector('link[rel="preload"][as="style"]['+o+"]")?s.loading=1:(s.preload=o=a.createElement("link"),o.addEventListener("load",function(){return s.loading|=1}),o.addEventListener("error",function(){return s.loading|=2}),sr(o,"link",i),eB(o),a.head.appendChild(o))))),t&&null===r)throw Error(u(528,""));return f}if(t&&null!==r)throw Error(u(529,""));return null;case"script":return t=n.async,"string"==typeof(n=n.src)&&t&&"function"!=typeof t&&"symbol"!=typeof t?(t=sA(n),(r=(n=eV(l).hoistableScripts).get(t))||(r={type:"script",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};default:throw Error(u(444,e))}}function sO(e){return'href="'+tt(e)+'"'}function sR(e){return'link[rel="stylesheet"]['+e+"]"}function sD(e){return p({},e,{"data-precedence":e.precedence,precedence:null})}function sA(e){return'[src="'+tt(e)+'"]'}function sF(e){return"script[async]"+e}function sM(e,t,n){if(t.count++,null===t.instance)switch(t.type){case"style":var r=e.querySelector('style[data-href~="'+tt(n.href)+'"]');if(r)return t.instance=r,eB(r),r;var l=p({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return eB(r=(e.ownerDocument||e).createElement("style")),sr(r,"style",l),sI(r,n.precedence,e),t.instance=r;case"stylesheet":l=sO(n.href);var a=e.querySelector(sR(l));if(a)return t.state.loading|=4,t.instance=a,eB(a),a;r=sD(n),(l=sC.get(l))&&sU(r,l),eB(a=(e.ownerDocument||e).createElement("link"));var o=a;return o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),sr(a,"link",r),t.state.loading|=4,sI(a,n.precedence,e),t.instance=a;case"script":if(a=sA(n.src),l=e.querySelector(sF(a)))return t.instance=l,eB(l),l;return r=n,(l=sC.get(a))&&sj(r=p({},n),l),eB(l=(e=e.ownerDocument||e).createElement("script")),sr(l,"link",r),e.head.appendChild(l),t.instance=l;case"void":return null;default:throw Error(u(443,t.type))}return"stylesheet"===t.type&&0==(4&t.state.loading)&&(r=t.instance,t.state.loading|=4,sI(r,n.precedence,e)),t.instance}function sI(e,t,n){for(var r=n.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),l=r.length?r[r.length-1]:null,a=l,o=0;o title"):null)}function sB(e){return"stylesheet"!==e.type||0!=(3&e.state.loading)}var sQ=null;function sW(){}function sq(){if(this.count--,0===this.count){if(this.stylesheets)sY(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var sK=null;function sY(e,t){e.stylesheets=null,null!==e.unsuspend&&(e.count++,sK=new Map,t.forEach(sG,e),sK=null,sq.call(e))}function sG(e,t){if(!(4&t.state.loading)){var n=sK.get(e);if(n)var r=n.get(null);else{n=new Map,sK.set(e,n);for(var l=e.querySelectorAll("link[data-precedence],style[data-precedence]"),a=0;a{var r=n(4232);function l(e){var t="https://react.dev/errors/"+e;if(1{function n(e,t){var n=e.length;for(e.push(t);0>>1,l=e[r];if(0>>1;ra(u,n))sa(c,u)?(e[r]=c,e[s]=n,r=s):(e[r]=u,e[i]=n,r=i);else if(sa(c,n))e[r]=c,e[s]=n,r=s;else break}}return t}function a(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}if(t.unstable_now=void 0,"object"==typeof performance&&"function"==typeof performance.now){var o,i=performance;t.unstable_now=function(){return i.now()}}else{var u=Date,s=u.now();t.unstable_now=function(){return u.now()-s}}var c=[],f=[],d=1,p=null,m=3,h=!1,g=!1,y=!1,v=!1,b="function"==typeof setTimeout?setTimeout:null,k="function"==typeof clearTimeout?clearTimeout:null,w="undefined"!=typeof setImmediate?setImmediate:null;function S(e){for(var t=r(f);null!==t;){if(null===t.callback)l(f);else if(t.startTime<=e)l(f),t.sortIndex=t.expirationTime,n(c,t);else break;t=r(f)}}function x(e){if(y=!1,S(e),!g)if(null!==r(c))g=!0,E||(E=!0,o());else{var t=r(f);null!==t&&O(x,t.startTime-e)}}var E=!1,C=-1,_=5,P=-1;function z(){return!!v||!(t.unstable_now()-P<_)}function N(){if(v=!1,E){var e=t.unstable_now();P=e;var n=!0;try{e:{g=!1,y&&(y=!1,k(C),C=-1),h=!0;var a=m;try{t:{for(S(e),p=r(c);null!==p&&!(p.expirationTime>e&&z());){var i=p.callback;if("function"==typeof i){p.callback=null,m=p.priorityLevel;var u=i(p.expirationTime<=e);if(e=t.unstable_now(),"function"==typeof u){p.callback=u,S(e),n=!0;break t}p===r(c)&&l(c),S(e)}else l(c);p=r(c)}if(null!==p)n=!0;else{var s=r(f);null!==s&&O(x,s.startTime-e),n=!1}}break e}finally{p=null,m=a,h=!1}}}finally{n?o():E=!1}}}if("function"==typeof w)o=function(){w(N)};else if("undefined"!=typeof MessageChannel){var T=new MessageChannel,L=T.port2;T.port1.onmessage=N,o=function(){L.postMessage(null)}}else o=function(){b(N,0)};function O(e,n){C=b(function(){e(t.unstable_now())},n)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_forceFrameRate=function(e){0>e||125i?(e.sortIndex=a,n(f,e),null===r(c)&&e===r(f)&&(y?(k(C),C=-1):y=!0,O(x,a-i))):(e.sortIndex=u,n(c,e),g||h||(g=!0,E||(E=!0,o()))),e},t.unstable_shouldYield=z,t.unstable_wrapCallback=function(e){var t=m;return function(){var n=m;m=t;try{return e.apply(this,arguments)}finally{m=n}}}},7876:(e,t,n)=>{e.exports=n(8228)},8228:(e,t)=>{var n=Symbol.for("react.transitional.element");function r(e,t,r){var l=null;if(void 0!==r&&(l=""+r),void 0!==t.key&&(l=""+t.key),"key"in t)for(var a in r={},t)"key"!==a&&(r[a]=t[a]);else r=t;return{$$typeof:n,type:e,key:l,ref:void 0!==(t=r.ref)?t:null,props:r}}t.Fragment=Symbol.for("react.fragment"),t.jsx=r,t.jsxs=r},8477:(e,t,n)=>{!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=n(4655)},8944:(e,t,n)=>{!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=n(4279)}}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/main-a5da5fd7e32dc553.js b/keploy/pkg/service/load/out/_next/static/chunks/main-a5da5fd7e32dc553.js new file mode 100644 index 0000000..5d27872 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/main-a5da5fd7e32dc553.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[792],{303:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"AmpStateContext",{enumerable:!0,get:function(){return n}});let n=r(4252)._(r(4232)).default.createContext({})},472:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return l}});let n=r(4252),o=r(7876),a=n._(r(4232)),i=r(2746);async function u(e){let{Component:t,ctx:r}=e;return{pageProps:await (0,i.loadGetInitialProps)(t,r)}}class l extends a.default.Component{render(){let{Component:e,pageProps:t}=this.props;return(0,o.jsx)(e,{...t})}}l.origGetInitialProps=u,l.getInitialProps=u,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},536:(e,t)=>{"use strict";function r(e){return e.split("/").map(e=>encodeURIComponent(e)).join("/")}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"encodeURIPath",{enumerable:!0,get:function(){return r}})},541:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"parseRelativeUrl",{enumerable:!0,get:function(){return a}});let n=r(2746),o=r(8040);function a(e,t,r){void 0===r&&(r=!0);let a=new URL((0,n.getLocationOrigin)()),i=t?new URL(t,a):e.startsWith(".")?new URL(window.location.href):a,{pathname:u,searchParams:l,search:s,hash:c,href:f,origin:d}=new URL(e,i);if(d!==a.origin)throw Object.defineProperty(Error("invariant: invalid relative URL, router received "+e),"__NEXT_ERROR_CODE",{value:"E159",enumerable:!1,configurable:!0});return{pathname:u,query:r?(0,o.searchParamsToUrlQuery)(l):void 0,search:s,hash:c,href:f.slice(d.length),slashes:void 0}}},938:(e,t)=>{"use strict";function r(e){return e.startsWith("/")?e:"/"+e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ensureLeadingSlash",{enumerable:!0,get:function(){return r}})},990:(e,t)=>{"use strict";function r(e,t){let r={};return Object.keys(e).forEach(n=>{t.includes(n)||(r[n]=e[n])}),r}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"omit",{enumerable:!0,get:function(){return r}})},1017:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"RedirectStatusCode",{enumerable:!0,get:function(){return r}});var r=function(e){return e[e.SeeOther=303]="SeeOther",e[e.TemporaryRedirect=307]="TemporaryRedirect",e[e.PermanentRedirect=308]="PermanentRedirect",e}({});("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1025:(e,t,r)=>{"use strict";function n(e){return e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeBasePath",{enumerable:!0,get:function(){return n}}),r(6023),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1291:()=>{"trimStart"in String.prototype||(String.prototype.trimStart=String.prototype.trimLeft),"trimEnd"in String.prototype||(String.prototype.trimEnd=String.prototype.trimRight),"description"in Symbol.prototype||Object.defineProperty(Symbol.prototype,"description",{configurable:!0,get:function(){var e=/\((.*)\)/.exec(this.toString());return e?e[1]:void 0}}),Array.prototype.flat||(Array.prototype.flat=function(e,t){return t=this.concat.apply([],this),e>1&&t.some(Array.isArray)?t.flat(e-1):t},Array.prototype.flatMap=function(e,t){return this.map(e,t).flat()}),Promise.prototype.finally||(Promise.prototype.finally=function(e){if("function"!=typeof e)return this.then(e,e);var t=this.constructor||Promise;return this.then(function(r){return t.resolve(e()).then(function(){return r})},function(r){return t.resolve(e()).then(function(){throw r})})}),Object.fromEntries||(Object.fromEntries=function(e){return Array.from(e).reduce(function(e,t){return e[t[0]]=t[1],e},{})}),Array.prototype.at||(Array.prototype.at=function(e){var t=Math.trunc(e)||0;if(t<0&&(t+=this.length),!(t<0||t>=this.length))return this[t]}),Object.hasOwn||(Object.hasOwn=function(e,t){if(null==e)throw TypeError("Cannot convert undefined or null to object");return Object.prototype.hasOwnProperty.call(Object(e),t)}),"canParse"in URL||(URL.canParse=function(e,t){try{return new URL(e,t),!0}catch(e){return!1}})},1318:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{RouteAnnouncer:function(){return l},default:function(){return s}});let n=r(4252),o=r(7876),a=n._(r(4232)),i=r(4294),u={border:0,clip:"rect(0 0 0 0)",height:"1px",margin:"-1px",overflow:"hidden",padding:0,position:"absolute",top:0,width:"1px",whiteSpace:"nowrap",wordWrap:"normal"},l=()=>{let{asPath:e}=(0,i.useRouter)(),[t,r]=a.default.useState(""),n=a.default.useRef(e);return a.default.useEffect(()=>{if(n.current!==e)if(n.current=e,document.title)r(document.title);else{var t;let n=document.querySelector("h1");r((null!=(t=null==n?void 0:n.innerText)?t:null==n?void 0:n.textContent)||e)}},[e]),(0,o.jsx)("p",{"aria-live":"assertive",id:"__next-route-announcer__",role:"alert",style:u,children:t})},s=l;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1533:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isLocalURL",{enumerable:!0,get:function(){return a}});let n=r(2746),o=r(6023);function a(e){if(!(0,n.isAbsoluteUrl)(e))return!0;try{let t=(0,n.getLocationOrigin)(),r=new URL(e,t);return r.origin===t&&(0,o.hasBasePath)(r.pathname)}catch(e){return!1}}},1827:(e,t)=>{"use strict";function r(e,t){return void 0===t&&(t=""),("/"===e?"/index":/^\/index(\/|$)/.test(e)?"/index"+e:e)+t}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return r}})},1862:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizeLocalePath",{enumerable:!0,get:function(){return n}});let r=new WeakMap;function n(e,t){let n;if(!t)return{pathname:e};let o=r.get(t);o||(o=t.map(e=>e.toLowerCase()),r.set(t,o));let a=e.split("/",2);if(!a[1])return{pathname:e};let i=a[1].toLowerCase(),u=o.indexOf(i);return u<0?{pathname:e}:(n=t[u],{pathname:e=e.slice(n.length+1)||"/",detectedLocale:n})}},1921:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"resolveHref",{enumerable:!0,get:function(){return f}});let n=r(8040),o=r(8480),a=r(990),i=r(2746),u=r(8205),l=r(1533),s=r(3069),c=r(8069);function f(e,t,r){let f,d="string"==typeof t?t:(0,o.formatWithValidation)(t),p=d.match(/^[a-z][a-z0-9+.-]*:\/\//i),h=p?d.slice(p[0].length):d;if((h.split("?",1)[0]||"").match(/(\/\/|\\)/)){console.error("Invalid href '"+d+"' passed to next/router in page: '"+e.pathname+"'. Repeated forward-slashes (//) or backslashes \\ are not valid in the href.");let t=(0,i.normalizeRepeatedSlashes)(h);d=(p?p[0]:"")+t}if(!(0,l.isLocalURL)(d))return r?[d]:d;try{f=new URL(d.startsWith("#")?e.asPath:e.pathname,"http://n")}catch(e){f=new URL("/","http://n")}try{let e=new URL(d,f);e.pathname=(0,u.normalizePathTrailingSlash)(e.pathname);let t="";if((0,s.isDynamicRoute)(e.pathname)&&e.searchParams&&r){let r=(0,n.searchParamsToUrlQuery)(e.searchParams),{result:i,params:u}=(0,c.interpolateAs)(e.pathname,e.pathname,r);i&&(t=(0,o.formatWithValidation)({pathname:i,hash:e.hash,query:(0,a.omit)(r,u)}))}let i=e.origin===f.origin?e.href.slice(e.origin.length):e.href;return r?[i,t||i]:i}catch(e){return r?[d]:d}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1924:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"escapeStringRegexp",{enumerable:!0,get:function(){return o}});let r=/[|\\{}()[\]^$+*?.-]/,n=/[|\\{}()[\]^$+*?.-]/g;function o(e){return r.test(e)?e.replace(n,"\\$&"):e}},2092:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addBasePath",{enumerable:!0,get:function(){return a}});let n=r(2889),o=r(8205);function a(e,t){return(0,o.normalizePathTrailingSlash)((0,n.addPathPrefix)(e,""))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2326:(e,t)=>{"use strict";function r(e){return"/api"===e||!!(null==e?void 0:e.startsWith("/api/"))}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isAPIRoute",{enumerable:!0,get:function(){return r}})},2455:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTML_LIMITED_BOT_UA_RE",{enumerable:!0,get:function(){return r}});let r=/Mediapartners-Google|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti/i},2591:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{REDIRECT_ERROR_CODE:function(){return o},RedirectType:function(){return a},isRedirectError:function(){return i}});let n=r(1017),o="NEXT_REDIRECT";var a=function(e){return e.push="push",e.replace="replace",e}({});function i(e){if("object"!=typeof e||null===e||!("digest"in e)||"string"!=typeof e.digest)return!1;let t=e.digest.split(";"),[r,a]=t,i=t.slice(2,-2).join(";"),u=Number(t.at(-2));return r===o&&("replace"===a||"push"===a)&&"string"==typeof i&&!isNaN(u)&&u in n.RedirectStatusCode}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2616:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"detectDomainLocale",{enumerable:!0,get:function(){return r}});let r=function(){for(var e=arguments.length,t=Array(e),r=0;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{DecodeError:function(){return h},MiddlewareNotFoundError:function(){return b},MissingStaticPage:function(){return g},NormalizeError:function(){return _},PageNotFoundError:function(){return m},SP:function(){return d},ST:function(){return p},WEB_VITALS:function(){return r},execOnce:function(){return n},getDisplayName:function(){return l},getLocationOrigin:function(){return i},getURL:function(){return u},isAbsoluteUrl:function(){return a},isResSent:function(){return s},loadGetInitialProps:function(){return f},normalizeRepeatedSlashes:function(){return c},stringifyError:function(){return y}});let r=["CLS","FCP","FID","INP","LCP","TTFB"];function n(e){let t,r=!1;return function(){for(var n=arguments.length,o=Array(n),a=0;ao.test(e);function i(){let{protocol:e,hostname:t,port:r}=window.location;return e+"//"+t+(r?":"+r:"")}function u(){let{href:e}=window.location,t=i();return e.substring(t.length)}function l(e){return"string"==typeof e?e:e.displayName||e.name||"Unknown"}function s(e){return e.finished||e.headersSent}function c(e){let t=e.split("?");return t[0].replace(/\\/g,"/").replace(/\/\/+/g,"/")+(t[1]?"?"+t.slice(1).join("?"):"")}async function f(e,t){let r=t.res||t.ctx&&t.ctx.res;if(!e.getInitialProps)return t.ctx&&t.Component?{pageProps:await f(t.Component,t.ctx)}:{};let n=await e.getInitialProps(t);if(r&&s(r))return n;if(!n)throw Object.defineProperty(Error('"'+l(e)+'.getInitialProps()" should resolve to an object. But found "'+n+'" instead.'),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});return n}let d="undefined"!=typeof performance,p=d&&["mark","measure","getEntriesByName"].every(e=>"function"==typeof performance[e]);class h extends Error{}class _ extends Error{}class m extends Error{constructor(e){super(),this.code="ENOENT",this.name="PageNotFoundError",this.message="Cannot find module for page: "+e}}class g extends Error{constructor(e,t){super(),this.message="Failed to load static file for page: "+e+" "+t}}class b extends Error{constructor(){super(),this.code="ENOENT",this.message="Cannot find the middleware module"}}function y(e){return JSON.stringify({message:e.message,stack:e.stack})}},2792:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return d}});let n=r(4252),o=r(2092),a=r(8069),i=n._(r(1827)),u=r(4591),l=r(9163),s=r(541),c=r(4902),f=r(7176);r(3802);class d{getPageList(){return(0,f.getClientBuildManifest)().then(e=>e.sortedPages)}getMiddleware(){return window.__MIDDLEWARE_MATCHERS=[],window.__MIDDLEWARE_MATCHERS}getDataHref(e){let{asPath:t,href:r,locale:n}=e,{pathname:f,query:d,search:p}=(0,s.parseRelativeUrl)(r),{pathname:h}=(0,s.parseRelativeUrl)(t),_=(0,c.removeTrailingSlash)(f);if("/"!==_[0])throw Object.defineProperty(Error('Route name should start with a "/", got "'+_+'"'),"__NEXT_ERROR_CODE",{value:"E303",enumerable:!1,configurable:!0});var m=e.skipInterpolation?h:(0,l.isDynamicRoute)(_)?(0,a.interpolateAs)(f,h,d).result:_;let g=(0,i.default)((0,c.removeTrailingSlash)((0,u.addLocale)(m,n)),".json");return(0,o.addBasePath)("/_next/data/"+this.buildId+g+p,!0)}_isSsg(e){return this.promisedSsgManifest.then(t=>t.has(e))}loadPage(e){return this.routeLoader.loadRoute(e).then(e=>{if("component"in e)return{page:e.component,mod:e.exports,styleSheets:e.styles.map(e=>({href:e.href,text:e.content}))};throw e.error})}prefetch(e){return this.routeLoader.prefetch(e)}constructor(e,t){this.routeLoader=(0,f.createRouteLoader)(t),this.buildId=e,this.assetPrefix=t,this.promisedSsgManifest=new Promise(e=>{window.__SSG_MANIFEST?e(window.__SSG_MANIFEST):window.__SSG_MANIFEST_CB=()=>{e(window.__SSG_MANIFEST)}})}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2850:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{AppRouterContext:function(){return o},GlobalLayoutRouterContext:function(){return i},LayoutRouterContext:function(){return a},MissingSlotContext:function(){return l},TemplateContext:function(){return u}});let n=r(4252)._(r(4232)),o=n.default.createContext(null),a=n.default.createContext(null),i=n.default.createContext(null),u=n.default.createContext(null),l=n.default.createContext(new Set)},2889:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addPathPrefix",{enumerable:!0,get:function(){return o}});let n=r(3670);function o(e,t){if(!e.startsWith("/")||!t)return e;let{pathname:r,query:o,hash:a}=(0,n.parsePath)(e);return""+t+r+o+a}},2917:(e,t)=>{"use strict";let r;Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return n},setConfig:function(){return o}});let n=()=>r;function o(e){r=e}},2959:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{normalizeAppPath:function(){return a},normalizeRscURL:function(){return i}});let n=r(938),o=r(8714);function a(e){return(0,n.ensureLeadingSlash)(e.split("/").reduce((e,t,r,n)=>!t||(0,o.isGroupSegment)(t)||"@"===t[0]||("page"===t||"route"===t)&&r===n.length-1?e:e+"/"+t,""))}function i(e){return e.replace(/\.rsc($|\?)/,"$1")}},3069:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getSortedRouteObjects:function(){return n.getSortedRouteObjects},getSortedRoutes:function(){return n.getSortedRoutes},isDynamicRoute:function(){return o.isDynamicRoute}});let n=r(3703),o=r(9163)},3090:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"Portal",{enumerable:!0,get:function(){return a}});let n=r(4232),o=r(8477),a=e=>{let{children:t,type:r}=e,[a,i]=(0,n.useState)(null);return(0,n.useEffect)(()=>{let e=document.createElement(r);return document.body.appendChild(e),i(e),()=>{document.body.removeChild(e)}},[r]),a?(0,o.createPortal)(t,a):null};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3123:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{BailoutToCSRError:function(){return n},isBailoutToCSRError:function(){return o}});let r="BAILOUT_TO_CLIENT_SIDE_RENDERING";class n extends Error{constructor(e){super("Bail out to client-side rendering: "+e),this.reason=e,this.digest=r}}function o(e){return"object"==typeof e&&null!==e&&"digest"in e&&e.digest===r}},3407:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getNextPathnameInfo",{enumerable:!0,get:function(){return i}});let n=r(1862),o=r(6292),a=r(3716);function i(e,t){var r,i;let{basePath:u,i18n:l,trailingSlash:s}=null!=(r=t.nextConfig)?r:{},c={pathname:e,trailingSlash:"/"!==e?e.endsWith("/"):s};u&&(0,a.pathHasPrefix)(c.pathname,u)&&(c.pathname=(0,o.removePathPrefix)(c.pathname,u),c.basePath=u);let f=c.pathname;if(c.pathname.startsWith("/_next/data/")&&c.pathname.endsWith(".json")){let e=c.pathname.replace(/^\/_next\/data\//,"").replace(/\.json$/,"").split("/");c.buildId=e[0],f="index"!==e[1]?"/"+e.slice(1).join("/"):"/",!0===t.parseData&&(c.pathname=f)}if(l){let e=t.i18nProvider?t.i18nProvider.analyze(c.pathname):(0,n.normalizeLocalePath)(c.pathname,l.locales);c.locale=e.detectedLocale,c.pathname=null!=(i=e.pathname)?i:c.pathname,!e.detectedLocale&&c.buildId&&(e=t.i18nProvider?t.i18nProvider.analyze(f):(0,n.normalizeLocalePath)(f,l.locales)).detectedLocale&&(c.locale=e.detectedLocale)}return c}},3670:(e,t)=>{"use strict";function r(e){let t=e.indexOf("#"),r=e.indexOf("?"),n=r>-1&&(t<0||r-1?{pathname:e.substring(0,n?r:t),query:n?e.substring(r,t>-1?t:void 0):"",hash:t>-1?e.slice(t):""}:{pathname:e,query:"",hash:""}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"parsePath",{enumerable:!0,get:function(){return r}})},3703:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getSortedRouteObjects:function(){return o},getSortedRoutes:function(){return n}});class r{insert(e){this._insert(e.split("/").filter(Boolean),[],!1)}smoosh(){return this._smoosh()}_smoosh(e){void 0===e&&(e="/");let t=[...this.children.keys()].sort();null!==this.slugName&&t.splice(t.indexOf("[]"),1),null!==this.restSlugName&&t.splice(t.indexOf("[...]"),1),null!==this.optionalRestSlugName&&t.splice(t.indexOf("[[...]]"),1);let r=t.map(t=>this.children.get(t)._smoosh(""+e+t+"/")).reduce((e,t)=>[...e,...t],[]);if(null!==this.slugName&&r.push(...this.children.get("[]")._smoosh(e+"["+this.slugName+"]/")),!this.placeholder){let t="/"===e?"/":e.slice(0,-1);if(null!=this.optionalRestSlugName)throw Object.defineProperty(Error('You cannot define a route with the same specificity as a optional catch-all route ("'+t+'" and "'+t+"[[..."+this.optionalRestSlugName+']]").'),"__NEXT_ERROR_CODE",{value:"E458",enumerable:!1,configurable:!0});r.unshift(t)}return null!==this.restSlugName&&r.push(...this.children.get("[...]")._smoosh(e+"[..."+this.restSlugName+"]/")),null!==this.optionalRestSlugName&&r.push(...this.children.get("[[...]]")._smoosh(e+"[[..."+this.optionalRestSlugName+"]]/")),r}_insert(e,t,n){if(0===e.length){this.placeholder=!1;return}if(n)throw Object.defineProperty(Error("Catch-all must be the last part of the URL."),"__NEXT_ERROR_CODE",{value:"E392",enumerable:!1,configurable:!0});let o=e[0];if(o.startsWith("[")&&o.endsWith("]")){let r=o.slice(1,-1),i=!1;if(r.startsWith("[")&&r.endsWith("]")&&(r=r.slice(1,-1),i=!0),r.startsWith("…"))throw Object.defineProperty(Error("Detected a three-dot character ('…') at ('"+r+"'). Did you mean ('...')?"),"__NEXT_ERROR_CODE",{value:"E147",enumerable:!1,configurable:!0});if(r.startsWith("...")&&(r=r.substring(3),n=!0),r.startsWith("[")||r.endsWith("]"))throw Object.defineProperty(Error("Segment names may not start or end with extra brackets ('"+r+"')."),"__NEXT_ERROR_CODE",{value:"E421",enumerable:!1,configurable:!0});if(r.startsWith("."))throw Object.defineProperty(Error("Segment names may not start with erroneous periods ('"+r+"')."),"__NEXT_ERROR_CODE",{value:"E288",enumerable:!1,configurable:!0});function a(e,r){if(null!==e&&e!==r)throw Object.defineProperty(Error("You cannot use different slug names for the same dynamic path ('"+e+"' !== '"+r+"')."),"__NEXT_ERROR_CODE",{value:"E337",enumerable:!1,configurable:!0});t.forEach(e=>{if(e===r)throw Object.defineProperty(Error('You cannot have the same slug name "'+r+'" repeat within a single dynamic path'),"__NEXT_ERROR_CODE",{value:"E247",enumerable:!1,configurable:!0});if(e.replace(/\W/g,"")===o.replace(/\W/g,""))throw Object.defineProperty(Error('You cannot have the slug names "'+e+'" and "'+r+'" differ only by non-word symbols within a single dynamic path'),"__NEXT_ERROR_CODE",{value:"E499",enumerable:!1,configurable:!0})}),t.push(r)}if(n)if(i){if(null!=this.restSlugName)throw Object.defineProperty(Error('You cannot use both an required and optional catch-all route at the same level ("[...'+this.restSlugName+']" and "'+e[0]+'" ).'),"__NEXT_ERROR_CODE",{value:"E299",enumerable:!1,configurable:!0});a(this.optionalRestSlugName,r),this.optionalRestSlugName=r,o="[[...]]"}else{if(null!=this.optionalRestSlugName)throw Object.defineProperty(Error('You cannot use both an optional and required catch-all route at the same level ("[[...'+this.optionalRestSlugName+']]" and "'+e[0]+'").'),"__NEXT_ERROR_CODE",{value:"E300",enumerable:!1,configurable:!0});a(this.restSlugName,r),this.restSlugName=r,o="[...]"}else{if(i)throw Object.defineProperty(Error('Optional route parameters are not yet supported ("'+e[0]+'").'),"__NEXT_ERROR_CODE",{value:"E435",enumerable:!1,configurable:!0});a(this.slugName,r),this.slugName=r,o="[]"}}this.children.has(o)||this.children.set(o,new r),this.children.get(o)._insert(e.slice(1),t,n)}constructor(){this.placeholder=!0,this.children=new Map,this.slugName=null,this.restSlugName=null,this.optionalRestSlugName=null}}function n(e){let t=new r;return e.forEach(e=>t.insert(e)),t.smoosh()}function o(e,t){let r={},o=[];for(let n=0;ne[r[t]])}},3716:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"pathHasPrefix",{enumerable:!0,get:function(){return o}});let n=r(3670);function o(e,t){if("string"!=typeof e)return!1;let{pathname:r}=(0,n.parsePath)(e);return r===t||r.startsWith(t+"/")}},3718:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(8757),self.__next_set_public_path__=e=>{r.p=e},("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3776:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}});let n=r(4232),o=n.useLayoutEffect,a=n.useEffect;function i(e){let{headManager:t,reduceComponentsToState:r}=e;function i(){if(t&&t.mountedInstances){let o=n.Children.toArray(Array.from(t.mountedInstances).filter(Boolean));t.updateHead(r(o,e))}}return o(()=>{var r;return null==t||null==(r=t.mountedInstances)||r.add(e.children),()=>{var r;null==t||null==(r=t.mountedInstances)||r.delete(e.children)}}),o(()=>(t&&(t._pendingUpdate=i),()=>{t&&(t._pendingUpdate=i)})),a(()=>(t&&t._pendingUpdate&&(t._pendingUpdate(),t._pendingUpdate=null),()=>{t&&t._pendingUpdate&&(t._pendingUpdate(),t._pendingUpdate=null)})),null}},3802:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{APP_BUILD_MANIFEST:function(){return y},APP_CLIENT_INTERNALS:function(){return Q},APP_PATHS_MANIFEST:function(){return m},APP_PATH_ROUTES_MANIFEST:function(){return g},BARREL_OPTIMIZATION_PREFIX:function(){return X},BLOCKED_PAGES:function(){return U},BUILD_ID_FILE:function(){return D},BUILD_MANIFEST:function(){return b},CLIENT_PUBLIC_FILES_PATH:function(){return F},CLIENT_REFERENCE_MANIFEST:function(){return W},CLIENT_STATIC_FILES_PATH:function(){return k},CLIENT_STATIC_FILES_RUNTIME_AMP:function(){return Z},CLIENT_STATIC_FILES_RUNTIME_MAIN:function(){return K},CLIENT_STATIC_FILES_RUNTIME_MAIN_APP:function(){return $},CLIENT_STATIC_FILES_RUNTIME_POLYFILLS:function(){return et},CLIENT_STATIC_FILES_RUNTIME_POLYFILLS_SYMBOL:function(){return er},CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH:function(){return J},CLIENT_STATIC_FILES_RUNTIME_WEBPACK:function(){return ee},COMPILER_INDEXES:function(){return a},COMPILER_NAMES:function(){return o},CONFIG_FILES:function(){return L},DEFAULT_RUNTIME_WEBPACK:function(){return en},DEFAULT_SANS_SERIF_FONT:function(){return el},DEFAULT_SERIF_FONT:function(){return eu},DEV_CLIENT_MIDDLEWARE_MANIFEST:function(){return x},DEV_CLIENT_PAGES_MANIFEST:function(){return w},DYNAMIC_CSS_MANIFEST:function(){return Y},EDGE_RUNTIME_WEBPACK:function(){return eo},EDGE_UNSUPPORTED_NODE_APIS:function(){return ep},EXPORT_DETAIL:function(){return O},EXPORT_MARKER:function(){return R},FUNCTIONS_CONFIG_MANIFEST:function(){return E},IMAGES_MANIFEST:function(){return j},INTERCEPTION_ROUTE_REWRITE_MANIFEST:function(){return z},MIDDLEWARE_BUILD_MANIFEST:function(){return q},MIDDLEWARE_MANIFEST:function(){return C},MIDDLEWARE_REACT_LOADABLE_MANIFEST:function(){return V},MODERN_BROWSERSLIST_TARGET:function(){return n.default},NEXT_BUILTIN_DOCUMENT:function(){return H},NEXT_FONT_MANIFEST:function(){return v},PAGES_MANIFEST:function(){return h},PHASE_DEVELOPMENT_SERVER:function(){return f},PHASE_EXPORT:function(){return l},PHASE_INFO:function(){return p},PHASE_PRODUCTION_BUILD:function(){return s},PHASE_PRODUCTION_SERVER:function(){return c},PHASE_TEST:function(){return d},PRERENDER_MANIFEST:function(){return S},REACT_LOADABLE_MANIFEST:function(){return N},ROUTES_MANIFEST:function(){return T},RSC_MODULE_TYPES:function(){return ed},SERVER_DIRECTORY:function(){return M},SERVER_FILES_MANIFEST:function(){return A},SERVER_PROPS_ID:function(){return ei},SERVER_REFERENCE_MANIFEST:function(){return G},STATIC_PROPS_ID:function(){return ea},STATIC_STATUS_PAGES:function(){return es},STRING_LITERAL_DROP_BUNDLE:function(){return B},SUBRESOURCE_INTEGRITY_MANIFEST:function(){return P},SYSTEM_ENTRYPOINTS:function(){return eh},TRACE_OUTPUT_VERSION:function(){return ec},TURBOPACK_CLIENT_MIDDLEWARE_MANIFEST:function(){return I},TURBO_TRACE_DEFAULT_MEMORY_LIMIT:function(){return ef},UNDERSCORE_NOT_FOUND_ROUTE:function(){return i},UNDERSCORE_NOT_FOUND_ROUTE_ENTRY:function(){return u},WEBPACK_STATS:function(){return _}});let n=r(4252)._(r(6582)),o={client:"client",server:"server",edgeServer:"edge-server"},a={[o.client]:0,[o.server]:1,[o.edgeServer]:2},i="/_not-found",u=""+i+"/page",l="phase-export",s="phase-production-build",c="phase-production-server",f="phase-development-server",d="phase-test",p="phase-info",h="pages-manifest.json",_="webpack-stats.json",m="app-paths-manifest.json",g="app-path-routes-manifest.json",b="build-manifest.json",y="app-build-manifest.json",E="functions-config-manifest.json",P="subresource-integrity-manifest",v="next-font-manifest",R="export-marker.json",O="export-detail.json",S="prerender-manifest.json",T="routes-manifest.json",j="images-manifest.json",A="required-server-files.json",w="_devPagesManifest.json",C="middleware-manifest.json",I="_clientMiddlewareManifest.json",x="_devMiddlewareManifest.json",N="react-loadable-manifest.json",M="server",L=["next.config.js","next.config.mjs","next.config.ts"],D="BUILD_ID",U=["/_document","/_app","/_error"],F="public",k="static",B="__NEXT_DROP_CLIENT_FILE__",H="__NEXT_BUILTIN_DOCUMENT__",X="__barrel_optimize__",W="client-reference-manifest",G="server-reference-manifest",q="middleware-build-manifest",V="middleware-react-loadable-manifest",z="interception-route-rewrite-manifest",Y="dynamic-css-manifest",K="main",$=""+K+"-app",Q="app-pages-internals",J="react-refresh",Z="amp",ee="webpack",et="polyfills",er=Symbol(et),en="webpack-runtime",eo="edge-runtime-webpack",ea="__N_SSG",ei="__N_SSP",eu={name:"Times New Roman",xAvgCharWidth:821,azAvgWidth:854.3953488372093,unitsPerEm:2048},el={name:"Arial",xAvgCharWidth:904,azAvgWidth:934.5116279069767,unitsPerEm:2048},es=["/500"],ec=1,ef=6e3,ed={client:"client",server:"server"},ep=["clearImmediate","setImmediate","BroadcastChannel","ByteLengthQueuingStrategy","CompressionStream","CountQueuingStrategy","DecompressionStream","DomException","MessageChannel","MessageEvent","MessagePort","ReadableByteStreamController","ReadableStreamBYOBRequest","ReadableStreamDefaultController","TransformStreamDefaultController","WritableStreamDefaultController"],eh=new Set([K,J,Z,$]);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3836:(e,t,r)=>{"use strict";function n(e,t){return e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeLocale",{enumerable:!0,get:function(){return n}}),r(3670),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3980:(e,t,r)=>{"use strict";function n(e,t){if(void 0===t&&(t={}),t.onlyHashChange)return void e();let r=document.documentElement;r.dataset.scrollBehavior;let n=r.style.scrollBehavior;r.style.scrollBehavior="auto",t.dontForceLayout||r.getClientRects(),e(),r.style.scrollBehavior=n}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"disableSmoothScrollDuringRouteTransition",{enumerable:!0,get:function(){return n}}),r(6079)},3996:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return g},handleClientScriptLoad:function(){return h},initScriptLoader:function(){return _}});let n=r(4252),o=r(8365),a=r(7876),i=n._(r(8477)),u=o._(r(4232)),l=r(8831),s=r(9611),c=r(6959),f=new Map,d=new Set,p=e=>{let{src:t,id:r,onLoad:n=()=>{},onReady:o=null,dangerouslySetInnerHTML:a,children:u="",strategy:l="afterInteractive",onError:c,stylesheets:p}=e,h=r||t;if(h&&d.has(h))return;if(f.has(t)){d.add(h),f.get(t).then(n,c);return}let _=()=>{o&&o(),d.add(h)},m=document.createElement("script"),g=new Promise((e,t)=>{m.addEventListener("load",function(t){e(),n&&n.call(this,t),_()}),m.addEventListener("error",function(e){t(e)})}).catch(function(e){c&&c(e)});a?(m.innerHTML=a.__html||"",_()):u?(m.textContent="string"==typeof u?u:Array.isArray(u)?u.join(""):"",_()):t&&(m.src=t,f.set(t,g)),(0,s.setAttributesFromProps)(m,e),"worker"===l&&m.setAttribute("type","text/partytown"),m.setAttribute("data-nscript",l),p&&(e=>{if(i.default.preinit)return e.forEach(e=>{i.default.preinit(e,{as:"style"})});{let t=document.head;e.forEach(e=>{let r=document.createElement("link");r.type="text/css",r.rel="stylesheet",r.href=e,t.appendChild(r)})}})(p),document.body.appendChild(m)};function h(e){let{strategy:t="afterInteractive"}=e;"lazyOnload"===t?window.addEventListener("load",()=>{(0,c.requestIdleCallback)(()=>p(e))}):p(e)}function _(e){e.forEach(h),[...document.querySelectorAll('[data-nscript="beforeInteractive"]'),...document.querySelectorAll('[data-nscript="beforePageRender"]')].forEach(e=>{let t=e.id||e.getAttribute("src");d.add(t)})}function m(e){let{id:t,src:r="",onLoad:n=()=>{},onReady:o=null,strategy:s="afterInteractive",onError:f,stylesheets:h,..._}=e,{updateScripts:m,scripts:g,getIsSsr:b,appDir:y,nonce:E}=(0,u.useContext)(l.HeadManagerContext);E=_.nonce||E;let P=(0,u.useRef)(!1);(0,u.useEffect)(()=>{let e=t||r;P.current||(o&&e&&d.has(e)&&o(),P.current=!0)},[o,t,r]);let v=(0,u.useRef)(!1);if((0,u.useEffect)(()=>{if(!v.current){if("afterInteractive"===s)p(e);else"lazyOnload"===s&&("complete"===document.readyState?(0,c.requestIdleCallback)(()=>p(e)):window.addEventListener("load",()=>{(0,c.requestIdleCallback)(()=>p(e))}));v.current=!0}},[e,s]),("beforeInteractive"===s||"worker"===s)&&(m?(g[s]=(g[s]||[]).concat([{id:t,src:r,onLoad:n,onReady:o,onError:f,..._,nonce:E}]),m(g)):b&&b()?d.add(t||r):b&&!b()&&p({...e,nonce:E})),y){if(h&&h.forEach(e=>{i.default.preinit(e,{as:"style"})}),"beforeInteractive"===s)if(!r)return _.dangerouslySetInnerHTML&&(_.children=_.dangerouslySetInnerHTML.__html,delete _.dangerouslySetInnerHTML),(0,a.jsx)("script",{nonce:E,dangerouslySetInnerHTML:{__html:"(self.__next_s=self.__next_s||[]).push("+JSON.stringify([0,{..._,id:t}])+")"}});else return i.default.preload(r,_.integrity?{as:"script",integrity:_.integrity,nonce:E,crossOrigin:_.crossOrigin}:{as:"script",nonce:E,crossOrigin:_.crossOrigin}),(0,a.jsx)("script",{nonce:E,dangerouslySetInnerHTML:{__html:"(self.__next_s=self.__next_s||[]).push("+JSON.stringify([r,{..._,id:t}])+")"}});"afterInteractive"===s&&r&&i.default.preload(r,_.integrity?{as:"script",integrity:_.integrity,nonce:E,crossOrigin:_.crossOrigin}:{as:"script",nonce:E,crossOrigin:_.crossOrigin})}return null}Object.defineProperty(m,"__nextScript",{value:!0});let g=m;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4069:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"BloomFilter",{enumerable:!0,get:function(){return r}});class r{static from(e,t){void 0===t&&(t=1e-4);let n=new r(e.length,t);for(let t of e)n.add(t);return n}export(){return{numItems:this.numItems,errorRate:this.errorRate,numBits:this.numBits,numHashes:this.numHashes,bitArray:this.bitArray}}import(e){this.numItems=e.numItems,this.errorRate=e.errorRate,this.numBits=e.numBits,this.numHashes=e.numHashes,this.bitArray=e.bitArray}add(e){this.getHashValues(e).forEach(e=>{this.bitArray[e]=1})}contains(e){return this.getHashValues(e).every(e=>this.bitArray[e])}getHashValues(e){let t=[];for(let r=1;r<=this.numHashes;r++){let n=function(e){let t=0;for(let r=0;r>>13,t=Math.imul(t,0x5bd1e995);return t>>>0}(""+e+r)%this.numBits;t.push(n)}return t}constructor(e,t=1e-4){this.numItems=e,this.errorRate=t,this.numBits=Math.ceil(-(e*Math.log(t))/(Math.log(2)*Math.log(2))),this.numHashes=Math.ceil(this.numBits/e*Math.log(2)),this.bitArray=Array(this.numBits).fill(0)}}},4181:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{HTTPAccessErrorStatus:function(){return r},HTTP_ERROR_FALLBACK_ERROR_CODE:function(){return o},getAccessFallbackErrorTypeByStatus:function(){return u},getAccessFallbackHTTPStatus:function(){return i},isHTTPAccessFallbackError:function(){return a}});let r={NOT_FOUND:404,FORBIDDEN:403,UNAUTHORIZED:401},n=new Set(Object.values(r)),o="NEXT_HTTP_ERROR_FALLBACK";function a(e){if("object"!=typeof e||null===e||!("digest"in e)||"string"!=typeof e.digest)return!1;let[t,r]=e.digest.split(";");return t===o&&n.has(Number(r))}function i(e){return Number(e.digest.split(";")[1])}function u(e){switch(e){case 401:return"unauthorized";case 403:return"forbidden";case 404:return"not-found";default:return}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4252:(e,t,r)=>{"use strict";function n(e){return e&&e.__esModule?e:{default:e}}r.r(t),r.d(t,{_:()=>n})},4294:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{Router:function(){return a.default},createRouter:function(){return _},default:function(){return p},makePublicRouterInstance:function(){return m},useRouter:function(){return h},withRouter:function(){return l.default}});let n=r(4252),o=n._(r(4232)),a=n._(r(8276)),i=r(9948),u=n._(r(6240)),l=n._(r(8147)),s={router:null,readyCallbacks:[],ready(e){if(this.router)return e();this.readyCallbacks.push(e)}},c=["pathname","route","query","asPath","components","isFallback","basePath","locale","locales","defaultLocale","isReady","isPreview","isLocaleDomain","domainLocales"],f=["push","replace","reload","back","prefetch","beforePopState"];function d(){if(!s.router)throw Object.defineProperty(Error('No router instance found.\nYou should only use "next/router" on the client side of your app.\n'),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});return s.router}Object.defineProperty(s,"events",{get:()=>a.default.events}),c.forEach(e=>{Object.defineProperty(s,e,{get:()=>d()[e]})}),f.forEach(e=>{s[e]=function(){for(var t=arguments.length,r=Array(t),n=0;n{s.ready(()=>{a.default.events.on(e,function(){for(var t=arguments.length,r=Array(t),n=0;ne()),s.readyCallbacks=[],s.router}function m(e){let t={};for(let r of c){if("object"==typeof e[r]){t[r]=Object.assign(Array.isArray(e[r])?[]:{},e[r]);continue}t[r]=e[r]}return t.events=a.default.events,f.forEach(r=>{t[r]=function(){for(var t=arguments.length,n=Array(t),o=0;o{"use strict";function r(e,t){let r=Object.keys(e);if(r.length!==Object.keys(t).length)return!1;for(let n=r.length;n--;){let o=r[n];if("query"===o){let r=Object.keys(e.query);if(r.length!==Object.keys(t.query).length)return!1;for(let n=r.length;n--;){let o=r[n];if(!t.query.hasOwnProperty(o)||e.query[o]!==t.query[o])return!1}}else if(!t.hasOwnProperty(o)||e[o]!==t[o])return!1}return!0}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"compareRouterStates",{enumerable:!0,get:function(){return r}})},4547:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return a},isEqualNode:function(){return o}});let n=r(9611);function o(e,t){if(e instanceof HTMLElement&&t instanceof HTMLElement){let r=t.getAttribute("nonce");if(r&&!e.getAttribute("nonce")){let n=t.cloneNode(!0);return n.setAttribute("nonce",""),n.nonce=r,r===e.nonce&&e.isEqualNode(n)}}return e.isEqualNode(t)}function a(){return{mountedInstances:new Set,updateHead:e=>{let t={};e.forEach(e=>{if("link"===e.type&&e.props["data-optimized-fonts"])if(document.querySelector('style[data-href="'+e.props["data-href"]+'"]'))return;else e.props.href=e.props["data-href"],e.props["data-href"]=void 0;let r=t[e.type]||[];r.push(e),t[e.type]=r});let r=t.title?t.title[0]:null,a="";if(r){let{children:e}=r.props;a="string"==typeof e?e:Array.isArray(e)?e.join(""):""}a!==document.title&&(document.title=a),["meta","base","link","style","script"].forEach(e=>{((e,t)=>{let r=document.querySelector("head");if(!r)return;let a=new Set(r.querySelectorAll(""+e+"[data-next-head]"));if("meta"===e){let e=r.querySelector("meta[charset]");null!==e&&a.add(e)}let i=[];for(let e=0;e{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"reportGlobalError",{enumerable:!0,get:function(){return r}});let r="function"==typeof reportError?reportError:e=>{globalThis.console.error(e)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4591:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addLocale",{enumerable:!0,get:function(){return n}}),r(8205);let n=function(e){for(var t=arguments.length,r=Array(t>1?t-1:0),n=1;n{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}});let n=r(4252)._(r(9871));class o{end(e){if("ended"===this.state.state)throw Object.defineProperty(Error("Span has already ended"),"__NEXT_ERROR_CODE",{value:"E17",enumerable:!1,configurable:!0});this.state={state:"ended",endTime:null!=e?e:Date.now()},this.onSpanEnd(this)}constructor(e,t,r){var n,o;this.name=e,this.attributes=null!=(n=t.attributes)?n:{},this.startTime=null!=(o=t.startTime)?o:Date.now(),this.onSpanEnd=r,this.state={state:"inprogress"}}}class a{startSpan(e,t){return new o(e,t,this.handleSpanEnd)}onSpanEnd(e){return this._emitter.on("spanend",e),()=>{this._emitter.off("spanend",e)}}constructor(){this._emitter=(0,n.default)(),this.handleSpanEnd=e=>{this._emitter.emit("spanend",e)}}}let i=new a;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4902:(e,t)=>{"use strict";function r(e){return e.replace(/\/$/,"")||"/"}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeTrailingSlash",{enumerable:!0,get:function(){return r}})},4980:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"formatNextPathnameInfo",{enumerable:!0,get:function(){return u}});let n=r(4902),o=r(2889),a=r(7952),i=r(6711);function u(e){let t=(0,i.addLocale)(e.pathname,e.locale,e.buildId?void 0:e.defaultLocale,e.ignorePrefix);return(e.buildId||!e.trailingSlash)&&(t=(0,n.removeTrailingSlash)(t)),e.buildId&&(t=(0,a.addPathSuffix)((0,o.addPathPrefix)(t,"/_next/data/"+e.buildId),"/"===e.pathname?"index.json":".json")),t=(0,o.addPathPrefix)(t,e.basePath),!e.buildId&&e.trailingSlash?t.endsWith("/")?t:(0,a.addPathSuffix)(t,"/"):(0,n.removeTrailingSlash)(t)}},5195:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"denormalizePagePath",{enumerable:!0,get:function(){return a}});let n=r(3069),o=r(5419);function a(e){let t=(0,o.normalizePathSep)(e);return t.startsWith("/index/")&&!(0,n.isDynamicRoute)(t)?t.slice(6):"/index"!==t?t:"/"}},5214:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getNamedMiddlewareRegex:function(){return _},getNamedRouteRegex:function(){return h},getRouteRegex:function(){return f},parseParameter:function(){return l}});let n=r(9308),o=r(7188),a=r(1924),i=r(4902),u=/^([^[]*)\[((?:\[[^\]]*\])|[^\]]+)\](.*)$/;function l(e){let t=e.match(u);return t?s(t[2]):s(e)}function s(e){let t=e.startsWith("[")&&e.endsWith("]");t&&(e=e.slice(1,-1));let r=e.startsWith("...");return r&&(e=e.slice(3)),{key:e,repeat:r,optional:t}}function c(e,t,r){let n={},l=1,c=[];for(let f of(0,i.removeTrailingSlash)(e).slice(1).split("/")){let e=o.INTERCEPTION_ROUTE_MARKERS.find(e=>f.startsWith(e)),i=f.match(u);if(e&&i&&i[2]){let{key:t,optional:r,repeat:o}=s(i[2]);n[t]={pos:l++,repeat:o,optional:r},c.push("/"+(0,a.escapeStringRegexp)(e)+"([^/]+?)")}else if(i&&i[2]){let{key:e,repeat:t,optional:o}=s(i[2]);n[e]={pos:l++,repeat:t,optional:o},r&&i[1]&&c.push("/"+(0,a.escapeStringRegexp)(i[1]));let u=t?o?"(?:/(.+?))?":"/(.+?)":"/([^/]+?)";r&&i[1]&&(u=u.substring(1)),c.push(u)}else c.push("/"+(0,a.escapeStringRegexp)(f));t&&i&&i[3]&&c.push((0,a.escapeStringRegexp)(i[3]))}return{parameterizedRoute:c.join(""),groups:n}}function f(e,t){let{includeSuffix:r=!1,includePrefix:n=!1,excludeOptionalTrailingSlash:o=!1}=void 0===t?{}:t,{parameterizedRoute:a,groups:i}=c(e,r,n),u=a;return o||(u+="(?:/)?"),{re:RegExp("^"+u+"$"),groups:i}}function d(e){let t,{interceptionMarker:r,getSafeRouteKey:n,segment:o,routeKeys:i,keyPrefix:u,backreferenceDuplicateKeys:l}=e,{key:c,optional:f,repeat:d}=s(o),p=c.replace(/\W/g,"");u&&(p=""+u+p);let h=!1;(0===p.length||p.length>30)&&(h=!0),isNaN(parseInt(p.slice(0,1)))||(h=!0),h&&(p=n());let _=p in i;u?i[p]=""+u+c:i[p]=c;let m=r?(0,a.escapeStringRegexp)(r):"";return t=_&&l?"\\k<"+p+">":d?"(?<"+p+">.+?)":"(?<"+p+">[^/]+?)",f?"(?:/"+m+t+")?":"/"+m+t}function p(e,t,r,l,s){let c,f=(c=0,()=>{let e="",t=++c;for(;t>0;)e+=String.fromCharCode(97+(t-1)%26),t=Math.floor((t-1)/26);return e}),p={},h=[];for(let c of(0,i.removeTrailingSlash)(e).slice(1).split("/")){let e=o.INTERCEPTION_ROUTE_MARKERS.some(e=>c.startsWith(e)),i=c.match(u);if(e&&i&&i[2])h.push(d({getSafeRouteKey:f,interceptionMarker:i[1],segment:i[2],routeKeys:p,keyPrefix:t?n.NEXT_INTERCEPTION_MARKER_PREFIX:void 0,backreferenceDuplicateKeys:s}));else if(i&&i[2]){l&&i[1]&&h.push("/"+(0,a.escapeStringRegexp)(i[1]));let e=d({getSafeRouteKey:f,segment:i[2],routeKeys:p,keyPrefix:t?n.NEXT_QUERY_PARAM_PREFIX:void 0,backreferenceDuplicateKeys:s});l&&i[1]&&(e=e.substring(1)),h.push(e)}else h.push("/"+(0,a.escapeStringRegexp)(c));r&&i&&i[3]&&h.push((0,a.escapeStringRegexp)(i[3]))}return{namedParameterizedRoute:h.join(""),routeKeys:p}}function h(e,t){var r,n,o;let a=p(e,t.prefixRouteKeys,null!=(r=t.includeSuffix)&&r,null!=(n=t.includePrefix)&&n,null!=(o=t.backreferenceDuplicateKeys)&&o),i=a.namedParameterizedRoute;return t.excludeOptionalTrailingSlash||(i+="(?:/)?"),{...f(e,t),namedRegex:"^"+i+"$",routeKeys:a.routeKeys}}function _(e,t){let{parameterizedRoute:r}=c(e,!1,!1),{catchAll:n=!0}=t;if("/"===r)return{namedRegex:"^/"+(n?".*":"")+"$"};let{namedParameterizedRoute:o}=p(e,!1,!1,!1,!1);return{namedRegex:"^"+o+(n?"(?:(/.*)?)":"")+"$"}}},5364:(e,t,r)=>{"use strict";var n,o;e.exports=(null==(n=r.g.process)?void 0:n.env)&&"object"==typeof(null==(o=r.g.process)?void 0:o.env)?r.g.process:r(5861)},5419:(e,t)=>{"use strict";function r(e){return e.replace(/\\/g,"/")}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizePathSep",{enumerable:!0,get:function(){return r}})},5519:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getRouteMatcher",{enumerable:!0,get:function(){return o}});let n=r(2746);function o(e){let{re:t,groups:r}=e;return e=>{let o=t.exec(e);if(!o)return!1;let a=e=>{try{return decodeURIComponent(e)}catch(e){throw Object.defineProperty(new n.DecodeError("failed to decode param"),"__NEXT_ERROR_CODE",{value:"E528",enumerable:!1,configurable:!0})}},i={};for(let[e,t]of Object.entries(r)){let r=o[t.pos];void 0!==r&&(t.repeat?i[e]=r.split("/").map(e=>a(e)):i[e]=a(r))}return i}}},5679:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return _},defaultHead:function(){return f}});let n=r(4252),o=r(8365),a=r(7876),i=o._(r(4232)),u=n._(r(3776)),l=r(303),s=r(8831),c=r(6807);function f(e){void 0===e&&(e=!1);let t=[(0,a.jsx)("meta",{charSet:"utf-8"},"charset")];return e||t.push((0,a.jsx)("meta",{name:"viewport",content:"width=device-width"},"viewport")),t}function d(e,t){return"string"==typeof t||"number"==typeof t?e:t.type===i.default.Fragment?e.concat(i.default.Children.toArray(t.props.children).reduce((e,t)=>"string"==typeof t||"number"==typeof t?e:e.concat(t),[])):e.concat(t)}r(6079);let p=["name","httpEquiv","charSet","itemProp"];function h(e,t){let{inAmpMode:r}=t;return e.reduce(d,[]).reverse().concat(f(r).reverse()).filter(function(){let e=new Set,t=new Set,r=new Set,n={};return o=>{let a=!0,i=!1;if(o.key&&"number"!=typeof o.key&&o.key.indexOf("$")>0){i=!0;let t=o.key.slice(o.key.indexOf("$")+1);e.has(t)?a=!1:e.add(t)}switch(o.type){case"title":case"base":t.has(o.type)?a=!1:t.add(o.type);break;case"meta":for(let e=0,t=p.length;e{let r=e.key||t;return i.default.cloneElement(e,{key:r})})}let _=function(e){let{children:t}=e,r=(0,i.useContext)(l.AmpStateContext),n=(0,i.useContext)(s.HeadManagerContext);return(0,a.jsx)(u.default,{reduceComponentsToState:h,headManager:n,inAmpMode:(0,c.isInAmpMode)(r),children:t})};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5842:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(3718),r(7647);let n=r(9525);window.next={version:n.version,get router(){return n.router},emitter:n.emitter},(0,n.initialize)({}).then(()=>(0,n.hydrate)()).catch(console.error),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5861:e=>{!function(){var t={229:function(e){var t,r,n,o=e.exports={};function a(){throw Error("setTimeout has not been defined")}function i(){throw Error("clearTimeout has not been defined")}try{t="function"==typeof setTimeout?setTimeout:a}catch(e){t=a}try{r="function"==typeof clearTimeout?clearTimeout:i}catch(e){r=i}function u(e){if(t===setTimeout)return setTimeout(e,0);if((t===a||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(r){try{return t.call(null,e,0)}catch(r){return t.call(this,e,0)}}}var l=[],s=!1,c=-1;function f(){s&&n&&(s=!1,n.length?l=n.concat(l):c=-1,l.length&&d())}function d(){if(!s){var e=u(f);s=!0;for(var t=l.length;t;){for(n=l,l=[];++c1)for(var r=1;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{PathParamsContext:function(){return i},PathnameContext:function(){return a},SearchParamsContext:function(){return o}});let n=r(4232),o=(0,n.createContext)(null),a=(0,n.createContext)(null),i=(0,n.createContext)(null)},6023:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hasBasePath",{enumerable:!0,get:function(){return o}});let n=r(3716);function o(e){return(0,n.pathHasPrefix)(e,"")}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6079:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"warnOnce",{enumerable:!0,get:function(){return r}});let r=e=>{}},6240:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return o},getProperError:function(){return a}});let n=r(8096);function o(e){return"object"==typeof e&&null!==e&&"name"in e&&"message"in e}function a(e){return o(e)?e:Object.defineProperty(Error((0,n.isPlainObject)(e)?function(e){let t=new WeakSet;return JSON.stringify(e,(e,r)=>{if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r})}(e):e+""),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0})}},6292:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removePathPrefix",{enumerable:!0,get:function(){return o}});let n=r(3716);function o(e,t){if(!(0,n.pathHasPrefix)(e,t))return e;let r=e.slice(t.length);return r.startsWith("/")?r:"/"+r}},6582:e=>{"use strict";e.exports=["chrome 64","edge 79","firefox 67","opera 51","safari 12"]},6711:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addLocale",{enumerable:!0,get:function(){return a}});let n=r(2889),o=r(3716);function a(e,t,r,a){if(!t||t===r)return e;let i=e.toLowerCase();return!a&&((0,o.pathHasPrefix)(i,"/api")||(0,o.pathHasPrefix)(i,"/"+t.toLowerCase()))?e:(0,n.addPathPrefix)(e,"/"+t)}},6807:(e,t)=>{"use strict";function r(e){let{ampFirst:t=!1,hybrid:r=!1,hasQuery:n=!1}=void 0===e?{}:e;return t||r&&n}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isInAmpMode",{enumerable:!0,get:function(){return r}})},6818:(e,t)=>{"use strict";let r;function n(e){var t;return(null==(t=function(){if(void 0===r){var e;r=(null==(e=window.trustedTypes)?void 0:e.createPolicy("nextjs",{createHTML:e=>e,createScript:e=>e,createScriptURL:e=>e}))||null}return r}())?void 0:t.createScriptURL(e))||e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"__unsafeCreateTrustedScriptURL",{enumerable:!0,get:function(){return n}}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6959:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{cancelIdleCallback:function(){return n},requestIdleCallback:function(){return r}});let r="undefined"!=typeof self&&self.requestIdleCallback&&self.requestIdleCallback.bind(window)||function(e){let t=Date.now();return self.setTimeout(function(){e({didTimeout:!1,timeRemaining:function(){return Math.max(0,50-(Date.now()-t))}})},1)},n="undefined"!=typeof self&&self.cancelIdleCallback&&self.cancelIdleCallback.bind(window)||function(e){return clearTimeout(e)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6999:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isNextRouterError",{enumerable:!0,get:function(){return a}});let n=r(4181),o=r(2591);function a(e){return(0,o.isRedirectError)(e)||(0,n.isHTTPAccessFallbackError)(e)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7176:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{createRouteLoader:function(){return m},getClientBuildManifest:function(){return h},isAssetError:function(){return c},markAssetError:function(){return s}}),r(4252),r(1827);let n=r(6818),o=r(6959),a=r(8757),i=r(536);function u(e,t,r){let n,o=t.get(e);if(o)return"future"in o?o.future:Promise.resolve(o);let a=new Promise(e=>{n=e});return t.set(e,{resolve:n,future:a}),r?r().then(e=>(n(e),e)).catch(r=>{throw t.delete(e),r}):a}let l=Symbol("ASSET_LOAD_ERROR");function s(e){return Object.defineProperty(e,l,{})}function c(e){return e&&l in e}let f=function(e){try{return e=document.createElement("link"),!!window.MSInputMethodContext&&!!document.documentMode||e.relList.supports("prefetch")}catch(e){return!1}}(),d=()=>(0,a.getDeploymentIdQueryOrEmptyString)();function p(e,t,r){return new Promise((n,a)=>{let i=!1;e.then(e=>{i=!0,n(e)}).catch(a),(0,o.requestIdleCallback)(()=>setTimeout(()=>{i||a(r)},t))})}function h(){return self.__BUILD_MANIFEST?Promise.resolve(self.__BUILD_MANIFEST):p(new Promise(e=>{let t=self.__BUILD_MANIFEST_CB;self.__BUILD_MANIFEST_CB=()=>{e(self.__BUILD_MANIFEST),t&&t()}}),3800,s(Object.defineProperty(Error("Failed to load client build manifest"),"__NEXT_ERROR_CODE",{value:"E273",enumerable:!1,configurable:!0})))}function _(e,t){return h().then(r=>{if(!(t in r))throw s(Object.defineProperty(Error("Failed to lookup route: "+t),"__NEXT_ERROR_CODE",{value:"E446",enumerable:!1,configurable:!0}));let o=r[t].map(t=>e+"/_next/"+(0,i.encodeURIPath)(t));return{scripts:o.filter(e=>e.endsWith(".js")).map(e=>(0,n.__unsafeCreateTrustedScriptURL)(e)+d()),css:o.filter(e=>e.endsWith(".css")).map(e=>e+d())}})}function m(e){let t=new Map,r=new Map,n=new Map,a=new Map;function i(e){{var t;let n=r.get(e.toString());return n?n:document.querySelector('script[src^="'+e+'"]')?Promise.resolve():(r.set(e.toString(),n=new Promise((r,n)=>{(t=document.createElement("script")).onload=r,t.onerror=()=>n(s(Object.defineProperty(Error("Failed to load script: "+e),"__NEXT_ERROR_CODE",{value:"E74",enumerable:!1,configurable:!0}))),t.crossOrigin=void 0,t.src=e,document.body.appendChild(t)})),n)}}function l(e){let t=n.get(e);return t||n.set(e,t=fetch(e,{credentials:"same-origin"}).then(t=>{if(!t.ok)throw Object.defineProperty(Error("Failed to load stylesheet: "+e),"__NEXT_ERROR_CODE",{value:"E189",enumerable:!1,configurable:!0});return t.text().then(t=>({href:e,content:t}))}).catch(e=>{throw s(e)})),t}return{whenEntrypoint:e=>u(e,t),onEntrypoint(e,r){(r?Promise.resolve().then(()=>r()).then(e=>({component:e&&e.default||e,exports:e}),e=>({error:e})):Promise.resolve(void 0)).then(r=>{let n=t.get(e);n&&"resolve"in n?r&&(t.set(e,r),n.resolve(r)):(r?t.set(e,r):t.delete(e),a.delete(e))})},loadRoute(r,n){return u(r,a,()=>{let o;return p(_(e,r).then(e=>{let{scripts:n,css:o}=e;return Promise.all([t.has(r)?[]:Promise.all(n.map(i)),Promise.all(o.map(l))])}).then(e=>this.whenEntrypoint(r).then(t=>({entrypoint:t,styles:e[1]}))),3800,s(Object.defineProperty(Error("Route did not complete loading: "+r),"__NEXT_ERROR_CODE",{value:"E12",enumerable:!1,configurable:!0}))).then(e=>{let{entrypoint:t,styles:r}=e,n=Object.assign({styles:r},t);return"error"in t?t:n}).catch(e=>{if(n)throw e;return{error:e}}).finally(()=>null==o?void 0:o())})},prefetch(t){let r;return(r=navigator.connection)&&(r.saveData||/2g/.test(r.effectiveType))?Promise.resolve():_(e,t).then(e=>Promise.all(f?e.scripts.map(e=>{var t,r,n;return t=e.toString(),r="script",new Promise((e,o)=>{let a='\n link[rel="prefetch"][href^="'+t+'"],\n link[rel="preload"][href^="'+t+'"],\n script[src^="'+t+'"]';if(document.querySelector(a))return e();n=document.createElement("link"),r&&(n.as=r),n.rel="prefetch",n.crossOrigin=void 0,n.onload=e,n.onerror=()=>o(s(Object.defineProperty(Error("Failed to prefetch: "+t),"__NEXT_ERROR_CODE",{value:"E268",enumerable:!1,configurable:!0}))),n.href=t,document.head.appendChild(n)})}):[])).then(()=>{(0,o.requestIdleCallback)(()=>this.loadRoute(t,!0).catch(()=>{}))}).catch(()=>{})}}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7188:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{INTERCEPTION_ROUTE_MARKERS:function(){return o},extractInterceptionRouteInformation:function(){return i},isInterceptionRouteAppPath:function(){return a}});let n=r(2959),o=["(..)(..)","(.)","(..)","(...)"];function a(e){return void 0!==e.split("/").find(e=>o.find(t=>e.startsWith(t)))}function i(e){let t,r,a;for(let n of e.split("/"))if(r=o.find(e=>n.startsWith(e))){[t,a]=e.split(r,2);break}if(!t||!r||!a)throw Object.defineProperty(Error("Invalid interception route: "+e+". Must be in the format //(..|...|..)(..)/"),"__NEXT_ERROR_CODE",{value:"E269",enumerable:!1,configurable:!0});switch(t=(0,n.normalizeAppPath)(t),r){case"(.)":a="/"===t?"/"+a:t+"/"+a;break;case"(..)":if("/"===t)throw Object.defineProperty(Error("Invalid interception route: "+e+". Cannot use (..) marker at the root level, use (.) instead."),"__NEXT_ERROR_CODE",{value:"E207",enumerable:!1,configurable:!0});a=t.split("/").slice(0,-1).concat(a).join("/");break;case"(...)":a="/"+a;break;case"(..)(..)":let i=t.split("/");if(i.length<=2)throw Object.defineProperty(Error("Invalid interception route: "+e+". Cannot use (..)(..) marker at the root level or one level up."),"__NEXT_ERROR_CODE",{value:"E486",enumerable:!1,configurable:!0});a=i.slice(0,-2).concat(a).join("/");break;default:throw Object.defineProperty(Error("Invariant: unexpected marker"),"__NEXT_ERROR_CODE",{value:"E112",enumerable:!1,configurable:!0})}return{interceptingRoute:t,interceptedRoute:a}}},7207:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{isRecoverableError:function(){return l},onRecoverableError:function(){return s}});let n=r(4252),o=r(3123),a=n._(r(6240)),i=r(4569),u=new WeakSet;function l(e){return u.has(e)}let s=(e,t)=>{let r=(0,a.default)(e)&&"cause"in e?e.cause:e;(0,o.isBailoutToCSRError)(r)||(0,i.reportGlobalError)(r)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7407:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{HTML_LIMITED_BOT_UA_RE:function(){return n.HTML_LIMITED_BOT_UA_RE},HTML_LIMITED_BOT_UA_RE_STRING:function(){return a},getBotType:function(){return l},isBot:function(){return u}});let n=r(2455),o=/google/i,a=n.HTML_LIMITED_BOT_UA_RE.source;function i(e){return n.HTML_LIMITED_BOT_UA_RE.test(e)}function u(e){return o.test(e)||i(e)}function l(e){return o.test(e)?"dom":i(e)?"html":void 0}},7539:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{VALID_LOADERS:function(){return r},imageConfigDefault:function(){return n}});let r=["default","imgix","cloudinary","akamai","custom"],n={deviceSizes:[640,750,828,1080,1200,1920,2048,3840],imageSizes:[16,32,48,64,96,128,256,384],path:"/_next/image",loader:"default",loaderFile:"",domains:[],disableStaticImages:!1,minimumCacheTTL:60,formats:["image/webp"],dangerouslyAllowSVG:!1,contentSecurityPolicy:"script-src 'none'; frame-src 'none'; sandbox;",contentDispositionType:"attachment",localPatterns:void 0,remotePatterns:[],qualities:void 0,unoptimized:!1}},7647:(e,t,r)=>{"use strict";e.exports=r(9393)},7952:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addPathSuffix",{enumerable:!0,get:function(){return o}});let n=r(3670);function o(e,t){if(!e.startsWith("/")||!t)return e;let{pathname:r,query:o,hash:a}=(0,n.parsePath)(e);return""+r+t+o+a}},8040:(e,t)=>{"use strict";function r(e){let t={};for(let[r,n]of e.entries()){let e=t[r];void 0===e?t[r]=n:Array.isArray(e)?e.push(n):t[r]=[e,n]}return t}function n(e){return"string"==typeof e?e:("number"!=typeof e||isNaN(e))&&"boolean"!=typeof e?"":String(e)}function o(e){let t=new URLSearchParams;for(let[r,o]of Object.entries(e))if(Array.isArray(o))for(let e of o)t.append(r,n(e));else t.set(r,n(o));return t}function a(e){for(var t=arguments.length,r=Array(t>1?t-1:0),n=1;n{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"interpolateAs",{enumerable:!0,get:function(){return a}});let n=r(5519),o=r(5214);function a(e,t,r){let a="",i=(0,o.getRouteRegex)(e),u=i.groups,l=(t!==e?(0,n.getRouteMatcher)(i)(t):"")||r;a=e;let s=Object.keys(u);return s.every(e=>{let t=l[e]||"",{repeat:r,optional:n}=u[e],o="["+(r?"...":"")+e+"]";return n&&(o=(t?"":"/")+"["+o+"]"),r&&!Array.isArray(t)&&(t=[t]),(n||e in l)&&(a=a.replace(o,r?t.map(e=>encodeURIComponent(e)).join("/"):encodeURIComponent(t))||"/")})||(a=""),{params:s,result:a}}},8096:(e,t)=>{"use strict";function r(e){return Object.prototype.toString.call(e)}function n(e){if("[object Object]"!==r(e))return!1;let t=Object.getPrototypeOf(e);return null===t||t.hasOwnProperty("isPrototypeOf")}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getObjectClassLabel:function(){return r},isPlainObject:function(){return n}})},8147:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return a}}),r(4252);let n=r(7876);r(4232);let o=r(4294);function a(e){function t(t){return(0,n.jsx)(e,{router:(0,o.useRouter)(),...t})}return t.getInitialProps=e.getInitialProps,t.origGetInitialProps=e.origGetInitialProps,t}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8205:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizePathTrailingSlash",{enumerable:!0,get:function(){return a}});let n=r(4902),o=r(3670),a=e=>{if(!e.startsWith("/"))return e;let{pathname:t,query:r,hash:a}=(0,o.parsePath)(e);return/\.[^/]+\/?$/.test(t)?""+(0,n.removeTrailingSlash)(t)+r+a:t.endsWith("/")?""+t+r+a:t+"/"+r+a};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8213:(e,t)=>{"use strict";function r(e){return new URL(e,"http://n").searchParams}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"asPathToSearchParams",{enumerable:!0,get:function(){return r}})},8276:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{createKey:function(){return G},default:function(){return z},matchesMiddleware:function(){return D}});let n=r(4252),o=r(8365),a=r(4902),i=r(7176),u=r(3996),l=o._(r(6240)),s=r(5195),c=r(1862),f=n._(r(9871)),d=r(2746),p=r(9163),h=r(541),_=r(5519),m=r(5214),g=r(8480);r(2616);let b=r(3670),y=r(4591),E=r(3836),P=r(1025),v=r(2092),R=r(6023),O=r(1921),S=r(2326),T=r(3407),j=r(4980),A=r(4359),w=r(1533),C=r(7407),I=r(990),x=r(8069),N=r(3980),M=r(9308);function L(){return Object.assign(Object.defineProperty(Error("Route Cancelled"),"__NEXT_ERROR_CODE",{value:"E315",enumerable:!1,configurable:!0}),{cancelled:!0})}async function D(e){let t=await Promise.resolve(e.router.pageLoader.getMiddleware());if(!t)return!1;let{pathname:r}=(0,b.parsePath)(e.asPath),n=(0,R.hasBasePath)(r)?(0,P.removeBasePath)(r):r,o=(0,v.addBasePath)((0,y.addLocale)(n,e.locale));return t.some(e=>new RegExp(e.regexp).test(o))}function U(e){let t=(0,d.getLocationOrigin)();return e.startsWith(t)?e.substring(t.length):e}function F(e,t,r){let[n,o]=(0,O.resolveHref)(e,t,!0),a=(0,d.getLocationOrigin)(),i=n.startsWith(a),u=o&&o.startsWith(a);n=U(n),o=o?U(o):o;let l=i?n:(0,v.addBasePath)(n),s=r?U((0,O.resolveHref)(e,r)):o||n;return{url:l,as:u?s:(0,v.addBasePath)(s)}}function k(e,t){let r=(0,a.removeTrailingSlash)((0,s.denormalizePagePath)(e));return"/404"===r||"/_error"===r?e:(t.includes(r)||t.some(t=>{if((0,p.isDynamicRoute)(t)&&(0,m.getRouteRegex)(t).re.test(r))return e=t,!0}),(0,a.removeTrailingSlash)(e))}async function B(e){if(!await D(e)||!e.fetchData)return null;let t=await e.fetchData(),r=await function(e,t,r){let n={basePath:r.router.basePath,i18n:{locales:r.router.locales},trailingSlash:!0},o=t.headers.get("x-nextjs-rewrite"),u=o||t.headers.get("x-nextjs-matched-path"),l=t.headers.get(M.MATCHED_PATH_HEADER);if(!l||u||l.includes("__next_data_catchall")||l.includes("/_error")||l.includes("/404")||(u=l),u){if(u.startsWith("/")){let t=(0,h.parseRelativeUrl)(u),l=(0,T.getNextPathnameInfo)(t.pathname,{nextConfig:n,parseData:!0}),s=(0,a.removeTrailingSlash)(l.pathname);return Promise.all([r.router.pageLoader.getPageList(),(0,i.getClientBuildManifest)()]).then(a=>{let[i,{__rewrites:u}]=a,f=(0,y.addLocale)(l.pathname,l.locale);if((0,p.isDynamicRoute)(f)||!o&&i.includes((0,c.normalizeLocalePath)((0,P.removeBasePath)(f),r.router.locales).pathname)){let r=(0,T.getNextPathnameInfo)((0,h.parseRelativeUrl)(e).pathname,{nextConfig:n,parseData:!0});t.pathname=f=(0,v.addBasePath)(r.pathname)}if(!i.includes(s)){let e=k(s,i);e!==s&&(s=e)}let d=i.includes(s)?s:k((0,c.normalizeLocalePath)((0,P.removeBasePath)(t.pathname),r.router.locales).pathname,i);if((0,p.isDynamicRoute)(d)){let e=(0,_.getRouteMatcher)((0,m.getRouteRegex)(d))(f);Object.assign(t.query,e||{})}return{type:"rewrite",parsedAs:t,resolvedHref:d}})}let t=(0,b.parsePath)(e);return Promise.resolve({type:"redirect-external",destination:""+(0,j.formatNextPathnameInfo)({...(0,T.getNextPathnameInfo)(t.pathname,{nextConfig:n,parseData:!0}),defaultLocale:r.router.defaultLocale,buildId:""})+t.query+t.hash})}let s=t.headers.get("x-nextjs-redirect");if(s){if(s.startsWith("/")){let e=(0,b.parsePath)(s),t=(0,j.formatNextPathnameInfo)({...(0,T.getNextPathnameInfo)(e.pathname,{nextConfig:n,parseData:!0}),defaultLocale:r.router.defaultLocale,buildId:""});return Promise.resolve({type:"redirect-internal",newAs:""+t+e.query+e.hash,newUrl:""+t+e.query+e.hash})}return Promise.resolve({type:"redirect-external",destination:s})}return Promise.resolve({type:"next"})}(t.dataHref,t.response,e);return{dataHref:t.dataHref,json:t.json,response:t.response,text:t.text,cacheKey:t.cacheKey,effect:r}}let H=Symbol("SSG_DATA_NOT_FOUND");function X(e){try{return JSON.parse(e)}catch(e){return null}}function W(e){let{dataHref:t,inflightCache:r,isPrefetch:n,hasMiddleware:o,isServerRender:a,parseJSON:u,persistCache:l,isBackground:s,unstable_skipClientCache:c}=e,{href:f}=new URL(t,window.location.href),d=e=>{var s;return(function e(t,r,n){return fetch(t,{credentials:"same-origin",method:n.method||"GET",headers:Object.assign({},n.headers,{"x-nextjs-data":"1"})}).then(o=>!o.ok&&r>1&&o.status>=500?e(t,r-1,n):o)})(t,a?3:1,{headers:Object.assign({},n?{purpose:"prefetch"}:{},n&&o?{"x-middleware-prefetch":"1"}:{},{}),method:null!=(s=null==e?void 0:e.method)?s:"GET"}).then(r=>r.ok&&(null==e?void 0:e.method)==="HEAD"?{dataHref:t,response:r,text:"",json:{},cacheKey:f}:r.text().then(e=>{if(!r.ok){if(o&&[301,302,307,308].includes(r.status))return{dataHref:t,response:r,text:e,json:{},cacheKey:f};if(404===r.status){var n;if(null==(n=X(e))?void 0:n.notFound)return{dataHref:t,json:{notFound:H},response:r,text:e,cacheKey:f}}let u=Object.defineProperty(Error("Failed to load static props"),"__NEXT_ERROR_CODE",{value:"E124",enumerable:!1,configurable:!0});throw a||(0,i.markAssetError)(u),u}return{dataHref:t,json:u?X(e):null,response:r,text:e,cacheKey:f}})).then(e=>(l&&"no-cache"!==e.response.headers.get("x-middleware-cache")||delete r[f],e)).catch(e=>{throw c||delete r[f],("Failed to fetch"===e.message||"NetworkError when attempting to fetch resource."===e.message||"Load failed"===e.message)&&(0,i.markAssetError)(e),e})};return c&&l?d({}).then(e=>("no-cache"!==e.response.headers.get("x-middleware-cache")&&(r[f]=Promise.resolve(e)),e)):void 0!==r[f]?r[f]:r[f]=d(s?{method:"HEAD"}:{})}function G(){return Math.random().toString(36).slice(2,10)}function q(e){let{url:t,router:r}=e;if(t===(0,v.addBasePath)((0,y.addLocale)(r.asPath,r.locale)))throw Object.defineProperty(Error("Invariant: attempted to hard navigate to the same URL "+t+" "+location.href),"__NEXT_ERROR_CODE",{value:"E282",enumerable:!1,configurable:!0});window.location.href=t}let V=e=>{let{route:t,router:r}=e,n=!1,o=r.clc=()=>{n=!0};return()=>{if(n){let e=Object.defineProperty(Error('Abort fetching component for route: "'+t+'"'),"__NEXT_ERROR_CODE",{value:"E483",enumerable:!1,configurable:!0});throw e.cancelled=!0,e}o===r.clc&&(r.clc=null)}};class z{reload(){window.location.reload()}back(){window.history.back()}forward(){window.history.forward()}push(e,t,r){return void 0===r&&(r={}),{url:e,as:t}=F(this,e,t),this.change("pushState",e,t,r)}replace(e,t,r){return void 0===r&&(r={}),{url:e,as:t}=F(this,e,t),this.change("replaceState",e,t,r)}async _bfl(e,t,n,o){{if(!this._bfl_s&&!this._bfl_d){let t,a,{BloomFilter:u}=r(4069);try{({__routerFilterStatic:t,__routerFilterDynamic:a}=await (0,i.getClientBuildManifest)())}catch(t){if(console.error(t),o)return!0;return q({url:(0,v.addBasePath)((0,y.addLocale)(e,n||this.locale,this.defaultLocale)),router:this}),new Promise(()=>{})}(null==t?void 0:t.numHashes)&&(this._bfl_s=new u(t.numItems,t.errorRate),this._bfl_s.import(t)),(null==a?void 0:a.numHashes)&&(this._bfl_d=new u(a.numItems,a.errorRate),this._bfl_d.import(a))}let c=!1,f=!1;for(let{as:r,allowMatchCurrent:i}of[{as:e},{as:t}])if(r){let t=(0,a.removeTrailingSlash)(new URL(r,"http://n").pathname),d=(0,v.addBasePath)((0,y.addLocale)(t,n||this.locale));if(i||t!==(0,a.removeTrailingSlash)(new URL(this.asPath,"http://n").pathname)){var u,l,s;for(let e of(c=c||!!(null==(u=this._bfl_s)?void 0:u.contains(t))||!!(null==(l=this._bfl_s)?void 0:l.contains(d)),[t,d])){let t=e.split("/");for(let e=0;!f&&e{})}}}}return!1}async change(e,t,r,n,o){var s,c,f,O,S,T,j,C,N;let M,U;if(!(0,w.isLocalURL)(t))return q({url:t,router:this}),!1;let B=1===n._h;B||n.shallow||await this._bfl(r,void 0,n.locale);let X=B||n._shouldResolveHref||(0,b.parsePath)(t).pathname===(0,b.parsePath)(r).pathname,W={...this.state},G=!0!==this.isReady;this.isReady=!0;let V=this.isSsr;if(B||(this.isSsr=!1),B&&this.clc)return!1;let Y=W.locale;d.ST&&performance.mark("routeChange");let{shallow:K=!1,scroll:$=!0}=n,Q={shallow:K};this._inFlightRoute&&this.clc&&(V||z.events.emit("routeChangeError",L(),this._inFlightRoute,Q),this.clc(),this.clc=null),r=(0,v.addBasePath)((0,y.addLocale)((0,R.hasBasePath)(r)?(0,P.removeBasePath)(r):r,n.locale,this.defaultLocale));let J=(0,E.removeLocale)((0,R.hasBasePath)(r)?(0,P.removeBasePath)(r):r,W.locale);this._inFlightRoute=r;let Z=Y!==W.locale;if(!B&&this.onlyAHashChange(J)&&!Z){W.asPath=J,z.events.emit("hashChangeStart",r,Q),this.changeState(e,t,r,{...n,scroll:!1}),$&&this.scrollToHash(J);try{await this.set(W,this.components[W.route],null)}catch(e){throw(0,l.default)(e)&&e.cancelled&&z.events.emit("routeChangeError",e,J,Q),e}return z.events.emit("hashChangeComplete",r,Q),!0}let ee=(0,h.parseRelativeUrl)(t),{pathname:et,query:er}=ee;try{[M,{__rewrites:U}]=await Promise.all([this.pageLoader.getPageList(),(0,i.getClientBuildManifest)(),this.pageLoader.getMiddleware()])}catch(e){return q({url:r,router:this}),!1}this.urlIsNew(J)||Z||(e="replaceState");let en=r;et=et?(0,a.removeTrailingSlash)((0,P.removeBasePath)(et)):et;let eo=(0,a.removeTrailingSlash)(et),ea=r.startsWith("/")&&(0,h.parseRelativeUrl)(r).pathname;if(null==(s=this.components[et])?void 0:s.__appRouter)return q({url:r,router:this}),new Promise(()=>{});let ei=!!(ea&&eo!==ea&&(!(0,p.isDynamicRoute)(eo)||!(0,_.getRouteMatcher)((0,m.getRouteRegex)(eo))(ea))),eu=!n.shallow&&await D({asPath:r,locale:W.locale,router:this});if(B&&eu&&(X=!1),X&&"/_error"!==et&&(n._shouldResolveHref=!0,ee.pathname=k(et,M),ee.pathname!==et&&(et=ee.pathname,ee.pathname=(0,v.addBasePath)(et),eu||(t=(0,g.formatWithValidation)(ee)))),!(0,w.isLocalURL)(r))return q({url:r,router:this}),!1;en=(0,E.removeLocale)((0,P.removeBasePath)(en),W.locale),eo=(0,a.removeTrailingSlash)(et);let el=!1;if((0,p.isDynamicRoute)(eo)){let e=(0,h.parseRelativeUrl)(en),n=e.pathname,o=(0,m.getRouteRegex)(eo);el=(0,_.getRouteMatcher)(o)(n);let a=eo===n,i=a?(0,x.interpolateAs)(eo,n,er):{};if(el&&(!a||i.result))a?r=(0,g.formatWithValidation)(Object.assign({},e,{pathname:i.result,query:(0,I.omit)(er,i.params)})):Object.assign(er,el);else{let e=Object.keys(o.groups).filter(e=>!er[e]&&!o.groups[e].optional);if(e.length>0&&!eu)throw Object.defineProperty(Error((a?"The provided `href` ("+t+") value is missing query values ("+e.join(", ")+") to be interpolated properly. ":"The provided `as` value ("+n+") is incompatible with the `href` value ("+eo+"). ")+"Read more: https://nextjs.org/docs/messages/"+(a?"href-interpolation-failed":"incompatible-href-as")),"__NEXT_ERROR_CODE",{value:"E344",enumerable:!1,configurable:!0})}}B||z.events.emit("routeChangeStart",r,Q);let es="/404"===this.pathname||"/_error"===this.pathname;try{let a=await this.getRouteInfo({route:eo,pathname:et,query:er,as:r,resolvedAs:en,routeProps:Q,locale:W.locale,isPreview:W.isPreview,hasMiddleware:eu,unstable_skipClientCache:n.unstable_skipClientCache,isQueryUpdating:B&&!this.isFallback,isMiddlewareRewrite:ei});if(B||n.shallow||await this._bfl(r,"resolvedAs"in a?a.resolvedAs:void 0,W.locale),"route"in a&&eu){eo=et=a.route||eo,Q.shallow||(er=Object.assign({},a.query||{},er));let e=(0,R.hasBasePath)(ee.pathname)?(0,P.removeBasePath)(ee.pathname):ee.pathname;if(el&&et!==e&&Object.keys(el).forEach(e=>{el&&er[e]===el[e]&&delete er[e]}),(0,p.isDynamicRoute)(et)){let e=!Q.shallow&&a.resolvedAs?a.resolvedAs:(0,v.addBasePath)((0,y.addLocale)(new URL(r,location.href).pathname,W.locale),!0);(0,R.hasBasePath)(e)&&(e=(0,P.removeBasePath)(e));let t=(0,m.getRouteRegex)(et),n=(0,_.getRouteMatcher)(t)(new URL(e,location.href).pathname);n&&Object.assign(er,n)}}if("type"in a)if("redirect-internal"===a.type)return this.change(e,a.newUrl,a.newAs,n);else return q({url:a.destination,router:this}),new Promise(()=>{});let i=a.Component;if(i&&i.unstable_scriptLoader&&[].concat(i.unstable_scriptLoader()).forEach(e=>{(0,u.handleClientScriptLoad)(e.props)}),(a.__N_SSG||a.__N_SSP)&&a.props){if(a.props.pageProps&&a.props.pageProps.__N_REDIRECT){n.locale=!1;let t=a.props.pageProps.__N_REDIRECT;if(t.startsWith("/")&&!1!==a.props.pageProps.__N_REDIRECT_BASE_PATH){let r=(0,h.parseRelativeUrl)(t);r.pathname=k(r.pathname,M);let{url:o,as:a}=F(this,t,t);return this.change(e,o,a,n)}return q({url:t,router:this}),new Promise(()=>{})}if(W.isPreview=!!a.props.__N_PREVIEW,a.props.notFound===H){let e;try{await this.fetchComponent("/404"),e="/404"}catch(t){e="/_error"}if(a=await this.getRouteInfo({route:e,pathname:e,query:er,as:r,resolvedAs:en,routeProps:{shallow:!1},locale:W.locale,isPreview:W.isPreview,isNotFound:!0}),"type"in a)throw Object.defineProperty(Error("Unexpected middleware effect on /404"),"__NEXT_ERROR_CODE",{value:"E158",enumerable:!1,configurable:!0})}}B&&"/_error"===this.pathname&&(null==(f=self.__NEXT_DATA__.props)||null==(c=f.pageProps)?void 0:c.statusCode)===500&&(null==(O=a.props)?void 0:O.pageProps)&&(a.props.pageProps.statusCode=500);let s=n.shallow&&W.route===(null!=(S=a.route)?S:eo),d=null!=(T=n.scroll)?T:!B&&!s,g=null!=o?o:d?{x:0,y:0}:null,b={...W,route:eo,pathname:et,query:er,asPath:J,isFallback:!1};if(B&&es){if(a=await this.getRouteInfo({route:this.pathname,pathname:this.pathname,query:er,as:r,resolvedAs:en,routeProps:{shallow:!1},locale:W.locale,isPreview:W.isPreview,isQueryUpdating:B&&!this.isFallback}),"type"in a)throw Object.defineProperty(Error("Unexpected middleware effect on "+this.pathname),"__NEXT_ERROR_CODE",{value:"E225",enumerable:!1,configurable:!0});"/_error"===this.pathname&&(null==(C=self.__NEXT_DATA__.props)||null==(j=C.pageProps)?void 0:j.statusCode)===500&&(null==(N=a.props)?void 0:N.pageProps)&&(a.props.pageProps.statusCode=500);try{await this.set(b,a,g)}catch(e){throw(0,l.default)(e)&&e.cancelled&&z.events.emit("routeChangeError",e,J,Q),e}return!0}if(z.events.emit("beforeHistoryChange",r,Q),this.changeState(e,t,r,n),!(B&&!g&&!G&&!Z&&(0,A.compareRouterStates)(b,this.state))){try{await this.set(b,a,g)}catch(e){if(e.cancelled)a.error=a.error||e;else throw e}if(a.error)throw B||z.events.emit("routeChangeError",a.error,J,Q),a.error;B||z.events.emit("routeChangeComplete",r,Q),d&&/#.+$/.test(r)&&this.scrollToHash(r)}return!0}catch(e){if((0,l.default)(e)&&e.cancelled)return!1;throw e}}changeState(e,t,r,n){void 0===n&&(n={}),("pushState"!==e||(0,d.getURL)()!==r)&&(this._shallow=n.shallow,window.history[e]({url:t,as:r,options:n,__N:!0,key:this._key="pushState"!==e?this._key:G()},"",r))}async handleRouteInfoError(e,t,r,n,o,a){if(e.cancelled)throw e;if((0,i.isAssetError)(e)||a)throw z.events.emit("routeChangeError",e,n,o),q({url:n,router:this}),L();console.error(e);try{let n,{page:o,styleSheets:a}=await this.fetchComponent("/_error"),i={props:n,Component:o,styleSheets:a,err:e,error:e};if(!i.props)try{i.props=await this.getInitialProps(o,{err:e,pathname:t,query:r})}catch(e){console.error("Error in error page `getInitialProps`: ",e),i.props={}}return i}catch(e){return this.handleRouteInfoError((0,l.default)(e)?e:Object.defineProperty(Error(e+""),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0}),t,r,n,o,!0)}}async getRouteInfo(e){let{route:t,pathname:r,query:n,as:o,resolvedAs:i,routeProps:u,locale:s,hasMiddleware:f,isPreview:d,unstable_skipClientCache:p,isQueryUpdating:h,isMiddlewareRewrite:_,isNotFound:m}=e,b=t;try{var y,E,v,R;let e=this.components[b];if(u.shallow&&e&&this.route===b)return e;let t=V({route:b,router:this});f&&(e=void 0);let l=!e||"initial"in e?void 0:e,O={dataHref:this.pageLoader.getDataHref({href:(0,g.formatWithValidation)({pathname:r,query:n}),skipInterpolation:!0,asPath:m?"/404":i,locale:s}),hasMiddleware:!0,isServerRender:this.isSsr,parseJSON:!0,inflightCache:h?this.sbc:this.sdc,persistCache:!d,isPrefetch:!1,unstable_skipClientCache:p,isBackground:h},T=h&&!_?null:await B({fetchData:()=>W(O),asPath:m?"/404":i,locale:s,router:this}).catch(e=>{if(h)return null;throw e});if(T&&("/_error"===r||"/404"===r)&&(T.effect=void 0),h&&(T?T.json=self.__NEXT_DATA__.props:T={json:self.__NEXT_DATA__.props}),t(),(null==T||null==(y=T.effect)?void 0:y.type)==="redirect-internal"||(null==T||null==(E=T.effect)?void 0:E.type)==="redirect-external")return T.effect;if((null==T||null==(v=T.effect)?void 0:v.type)==="rewrite"){let t=(0,a.removeTrailingSlash)(T.effect.resolvedHref),o=await this.pageLoader.getPageList();if((!h||o.includes(t))&&(b=t,r=T.effect.resolvedHref,n={...n,...T.effect.parsedAs.query},i=(0,P.removeBasePath)((0,c.normalizeLocalePath)(T.effect.parsedAs.pathname,this.locales).pathname),e=this.components[b],u.shallow&&e&&this.route===b&&!f))return{...e,route:b}}if((0,S.isAPIRoute)(b))return q({url:o,router:this}),new Promise(()=>{});let j=l||await this.fetchComponent(b).then(e=>({Component:e.page,styleSheets:e.styleSheets,__N_SSG:e.mod.__N_SSG,__N_SSP:e.mod.__N_SSP})),A=null==T||null==(R=T.response)?void 0:R.headers.get("x-middleware-skip"),w=j.__N_SSG||j.__N_SSP;A&&(null==T?void 0:T.dataHref)&&delete this.sdc[T.dataHref];let{props:C,cacheKey:I}=await this._getData(async()=>{if(w){if((null==T?void 0:T.json)&&!A)return{cacheKey:T.cacheKey,props:T.json};let e=(null==T?void 0:T.dataHref)?T.dataHref:this.pageLoader.getDataHref({href:(0,g.formatWithValidation)({pathname:r,query:n}),asPath:i,locale:s}),t=await W({dataHref:e,isServerRender:this.isSsr,parseJSON:!0,inflightCache:A?{}:this.sdc,persistCache:!d,isPrefetch:!1,unstable_skipClientCache:p});return{cacheKey:t.cacheKey,props:t.json||{}}}return{headers:{},props:await this.getInitialProps(j.Component,{pathname:r,query:n,asPath:o,locale:s,locales:this.locales,defaultLocale:this.defaultLocale})}});return j.__N_SSP&&O.dataHref&&I&&delete this.sdc[I],this.isPreview||!j.__N_SSG||h||W(Object.assign({},O,{isBackground:!0,persistCache:!1,inflightCache:this.sbc})).catch(()=>{}),C.pageProps=Object.assign({},C.pageProps),j.props=C,j.route=b,j.query=n,j.resolvedAs=i,this.components[b]=j,j}catch(e){return this.handleRouteInfoError((0,l.getProperError)(e),r,n,o,u)}}set(e,t,r){return this.state=e,this.sub(t,this.components["/_app"].Component,r)}beforePopState(e){this._bps=e}onlyAHashChange(e){if(!this.asPath)return!1;let[t,r]=this.asPath.split("#",2),[n,o]=e.split("#",2);return!!o&&t===n&&r===o||t===n&&r!==o}scrollToHash(e){let[,t=""]=e.split("#",2);(0,N.disableSmoothScrollDuringRouteTransition)(()=>{if(""===t||"top"===t)return void window.scrollTo(0,0);let e=decodeURIComponent(t),r=document.getElementById(e);if(r)return void r.scrollIntoView();let n=document.getElementsByName(e)[0];n&&n.scrollIntoView()},{onlyHashChange:this.onlyAHashChange(e)})}urlIsNew(e){return this.asPath!==e}async prefetch(e,t,r){if(void 0===t&&(t=e),void 0===r&&(r={}),(0,C.isBot)(window.navigator.userAgent))return;let n=(0,h.parseRelativeUrl)(e),o=n.pathname,{pathname:i,query:u}=n,l=i,s=await this.pageLoader.getPageList(),c=t,f=void 0!==r.locale?r.locale||void 0:this.locale,d=await D({asPath:t,locale:f,router:this});n.pathname=k(n.pathname,s),(0,p.isDynamicRoute)(n.pathname)&&(i=n.pathname,n.pathname=i,Object.assign(u,(0,_.getRouteMatcher)((0,m.getRouteRegex)(n.pathname))((0,b.parsePath)(t).pathname)||{}),d||(e=(0,g.formatWithValidation)(n)));let y=await B({fetchData:()=>W({dataHref:this.pageLoader.getDataHref({href:(0,g.formatWithValidation)({pathname:l,query:u}),skipInterpolation:!0,asPath:c,locale:f}),hasMiddleware:!0,isServerRender:!1,parseJSON:!0,inflightCache:this.sdc,persistCache:!this.isPreview,isPrefetch:!0}),asPath:t,locale:f,router:this});if((null==y?void 0:y.effect.type)==="rewrite"&&(n.pathname=y.effect.resolvedHref,i=y.effect.resolvedHref,u={...u,...y.effect.parsedAs.query},c=y.effect.parsedAs.pathname,e=(0,g.formatWithValidation)(n)),(null==y?void 0:y.effect.type)==="redirect-external")return;let E=(0,a.removeTrailingSlash)(i);await this._bfl(t,c,r.locale,!0)&&(this.components[o]={__appRouter:!0}),await Promise.all([this.pageLoader._isSsg(E).then(t=>!!t&&W({dataHref:(null==y?void 0:y.json)?null==y?void 0:y.dataHref:this.pageLoader.getDataHref({href:e,asPath:c,locale:f}),isServerRender:!1,parseJSON:!0,inflightCache:this.sdc,persistCache:!this.isPreview,isPrefetch:!0,unstable_skipClientCache:r.unstable_skipClientCache||r.priority&&!0}).then(()=>!1).catch(()=>!1)),this.pageLoader[r.priority?"loadPage":"prefetch"](E)])}async fetchComponent(e){let t=V({route:e,router:this});try{let r=await this.pageLoader.loadPage(e);return t(),r}catch(e){throw t(),e}}_getData(e){let t=!1,r=()=>{t=!0};return this.clc=r,e().then(e=>{if(r===this.clc&&(this.clc=null),t){let e=Object.defineProperty(Error("Loading initial props cancelled"),"__NEXT_ERROR_CODE",{value:"E405",enumerable:!1,configurable:!0});throw e.cancelled=!0,e}return e})}getInitialProps(e,t){let{Component:r}=this.components["/_app"],n=this._wrapApp(r);return t.AppTree=n,(0,d.loadGetInitialProps)(r,{AppTree:n,Component:e,router:this,ctx:t})}get route(){return this.state.route}get pathname(){return this.state.pathname}get query(){return this.state.query}get asPath(){return this.state.asPath}get locale(){return this.state.locale}get isFallback(){return this.state.isFallback}get isPreview(){return this.state.isPreview}constructor(e,t,r,{initialProps:n,pageLoader:o,App:i,wrapApp:u,Component:l,err:s,subscription:c,isFallback:f,locale:_,locales:m,defaultLocale:b,domainLocales:y,isPreview:E}){this.sdc={},this.sbc={},this.isFirstPopStateEvent=!0,this._key=G(),this.onPopState=e=>{let t,{isFirstPopStateEvent:r}=this;this.isFirstPopStateEvent=!1;let n=e.state;if(!n){let{pathname:e,query:t}=this;this.changeState("replaceState",(0,g.formatWithValidation)({pathname:(0,v.addBasePath)(e),query:t}),(0,d.getURL)());return}if(n.__NA)return void window.location.reload();if(!n.__N||r&&this.locale===n.options.locale&&n.as===this.asPath)return;let{url:o,as:a,options:i,key:u}=n;this._key=u;let{pathname:l}=(0,h.parseRelativeUrl)(o);(!this.isSsr||a!==(0,v.addBasePath)(this.asPath)||l!==(0,v.addBasePath)(this.pathname))&&(!this._bps||this._bps(n))&&this.change("replaceState",o,a,Object.assign({},i,{shallow:i.shallow&&this._shallow,locale:i.locale||this.defaultLocale,_h:0}),t)};let P=(0,a.removeTrailingSlash)(e);this.components={},"/_error"!==e&&(this.components[P]={Component:l,initial:!0,props:n,err:s,__N_SSG:n&&n.__N_SSG,__N_SSP:n&&n.__N_SSP}),this.components["/_app"]={Component:i,styleSheets:[]},this.events=z.events,this.pageLoader=o;let R=(0,p.isDynamicRoute)(e)&&self.__NEXT_DATA__.autoExport;if(this.basePath="",this.sub=c,this.clc=null,this._wrapApp=u,this.isSsr=!0,this.isLocaleDomain=!1,this.isReady=!!(self.__NEXT_DATA__.gssp||self.__NEXT_DATA__.gip||self.__NEXT_DATA__.isExperimentalCompile||self.__NEXT_DATA__.appGip&&!self.__NEXT_DATA__.gsp||!R&&!self.location.search),this.state={route:P,pathname:e,query:t,asPath:R?e:r,isPreview:!!E,locale:void 0,isFallback:f},this._initialMatchesMiddlewarePromise=Promise.resolve(!1),!r.startsWith("//")){let n={locale:_},o=(0,d.getURL)();this._initialMatchesMiddlewarePromise=D({router:this,locale:_,asPath:o}).then(a=>(n._shouldResolveHref=r!==e,this.changeState("replaceState",a?o:(0,g.formatWithValidation)({pathname:(0,v.addBasePath)(e),query:t}),o,n),a))}window.addEventListener("popstate",this.onPopState)}}z.events=(0,f.default)()},8365:(e,t,r)=>{"use strict";function n(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(n=function(e){return e?r:t})(e)}function o(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=n(t);if(r&&r.has(e))return r.get(e);var o={__proto__:null},a=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if("default"!==i&&Object.prototype.hasOwnProperty.call(e,i)){var u=a?Object.getOwnPropertyDescriptor(e,i):null;u&&(u.get||u.set)?Object.defineProperty(o,i,u):o[i]=e[i]}return o.default=e,r&&r.set(e,o),o}r.r(t),r.d(t,{_:()=>o})},8480:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{formatUrl:function(){return a},formatWithValidation:function(){return u},urlObjectKeys:function(){return i}});let n=r(8365)._(r(8040)),o=/https?|ftp|gopher|file/;function a(e){let{auth:t,hostname:r}=e,a=e.protocol||"",i=e.pathname||"",u=e.hash||"",l=e.query||"",s=!1;t=t?encodeURIComponent(t).replace(/%3A/i,":")+"@":"",e.host?s=t+e.host:r&&(s=t+(~r.indexOf(":")?"["+r+"]":r),e.port&&(s+=":"+e.port)),l&&"object"==typeof l&&(l=String(n.urlQueryToSearchParams(l)));let c=e.search||l&&"?"+l||"";return a&&!a.endsWith(":")&&(a+=":"),e.slashes||(!a||o.test(a))&&!1!==s?(s="//"+(s||""),i&&"/"!==i[0]&&(i="/"+i)):s||(s=""),u&&"#"!==u[0]&&(u="#"+u),c&&"?"!==c[0]&&(c="?"+c),""+a+s+(i=i.replace(/[?#]/g,encodeURIComponent))+(c=c.replace("#","%23"))+u}let i=["auth","hash","host","hostname","href","path","pathname","port","protocol","query","search","slashes"];function u(e){return a(e)}},8677:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ImageConfigContext",{enumerable:!0,get:function(){return a}});let n=r(4252)._(r(4232)),o=r(7539),a=n.default.createContext(o.imageConfigDefault)},8714:(e,t)=>{"use strict";function r(e){return"("===e[0]&&e.endsWith(")")}function n(e){return e.startsWith("@")&&"@children"!==e}function o(e,t){if(e.includes(a)){let e=JSON.stringify(t);return"{}"!==e?a+"?"+e:a}return e}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{DEFAULT_SEGMENT_KEY:function(){return i},PAGE_SEGMENT_KEY:function(){return a},addSearchParamsIfPageSegment:function(){return o},isGroupSegment:function(){return r},isParallelRouteSegment:function(){return n}});let a="__PAGE__",i="__DEFAULT__"},8757:(e,t)=>{"use strict";function r(){return""}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getDeploymentIdQueryOrEmptyString",{enumerable:!0,get:function(){return r}})},8831:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HeadManagerContext",{enumerable:!0,get:function(){return n}});let n=r(4252)._(r(4232)).default.createContext({})},9163:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isDynamicRoute",{enumerable:!0,get:function(){return i}});let n=r(7188),o=/\/[^/]*\[[^/]+\][^/]*(?=\/|$)/,a=/\/\[[^/]+\](?=\/|$)/;function i(e,t){return(void 0===t&&(t=!0),(0,n.isInterceptionRouteAppPath)(e)&&(e=(0,n.extractInterceptionRouteInformation)(e).interceptedRoute),t)?a.test(e):o.test(e)}},9308:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ACTION_SUFFIX:function(){return f},APP_DIR_ALIAS:function(){return I},CACHE_ONE_YEAR:function(){return R},DOT_NEXT_ALIAS:function(){return w},ESLINT_DEFAULT_DIRS:function(){return Q},GSP_NO_RETURNED_VALUE:function(){return q},GSSP_COMPONENT_MEMBER_ERROR:function(){return Y},GSSP_NO_RETURNED_VALUE:function(){return V},INFINITE_CACHE:function(){return O},INSTRUMENTATION_HOOK_FILENAME:function(){return j},MATCHED_PATH_HEADER:function(){return o},MIDDLEWARE_FILENAME:function(){return S},MIDDLEWARE_LOCATION_REGEXP:function(){return T},NEXT_BODY_SUFFIX:function(){return h},NEXT_CACHE_IMPLICIT_TAG_ID:function(){return v},NEXT_CACHE_REVALIDATED_TAGS_HEADER:function(){return m},NEXT_CACHE_REVALIDATE_TAG_TOKEN_HEADER:function(){return g},NEXT_CACHE_SOFT_TAG_MAX_LENGTH:function(){return P},NEXT_CACHE_TAGS_HEADER:function(){return _},NEXT_CACHE_TAG_MAX_ITEMS:function(){return y},NEXT_CACHE_TAG_MAX_LENGTH:function(){return E},NEXT_DATA_SUFFIX:function(){return d},NEXT_INTERCEPTION_MARKER_PREFIX:function(){return n},NEXT_META_SUFFIX:function(){return p},NEXT_QUERY_PARAM_PREFIX:function(){return r},NEXT_RESUME_HEADER:function(){return b},NON_STANDARD_NODE_ENV:function(){return K},PAGES_DIR_ALIAS:function(){return A},PRERENDER_REVALIDATE_HEADER:function(){return a},PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER:function(){return i},PUBLIC_DIR_MIDDLEWARE_CONFLICT:function(){return k},ROOT_DIR_ALIAS:function(){return C},RSC_ACTION_CLIENT_WRAPPER_ALIAS:function(){return F},RSC_ACTION_ENCRYPTION_ALIAS:function(){return U},RSC_ACTION_PROXY_ALIAS:function(){return M},RSC_ACTION_VALIDATE_ALIAS:function(){return N},RSC_CACHE_WRAPPER_ALIAS:function(){return L},RSC_DYNAMIC_IMPORT_WRAPPER_ALIAS:function(){return D},RSC_MOD_REF_PROXY_ALIAS:function(){return x},RSC_PREFETCH_SUFFIX:function(){return u},RSC_SEGMENTS_DIR_SUFFIX:function(){return l},RSC_SEGMENT_SUFFIX:function(){return s},RSC_SUFFIX:function(){return c},SERVER_PROPS_EXPORT_ERROR:function(){return G},SERVER_PROPS_GET_INIT_PROPS_CONFLICT:function(){return H},SERVER_PROPS_SSG_CONFLICT:function(){return X},SERVER_RUNTIME:function(){return J},SSG_FALLBACK_EXPORT_ERROR:function(){return $},SSG_GET_INITIAL_PROPS_CONFLICT:function(){return B},STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR:function(){return W},UNSTABLE_REVALIDATE_RENAME_ERROR:function(){return z},WEBPACK_LAYERS:function(){return ee},WEBPACK_RESOURCE_QUERIES:function(){return et}});let r="nxtP",n="nxtI",o="x-matched-path",a="x-prerender-revalidate",i="x-prerender-revalidate-if-generated",u=".prefetch.rsc",l=".segments",s=".segment.rsc",c=".rsc",f=".action",d=".json",p=".meta",h=".body",_="x-next-cache-tags",m="x-next-revalidated-tags",g="x-next-revalidate-tag-token",b="next-resume",y=128,E=256,P=1024,v="_N_T_",R=31536e3,O=0xfffffffe,S="middleware",T=`(?:src/)?${S}`,j="instrumentation",A="private-next-pages",w="private-dot-next",C="private-next-root-dir",I="private-next-app-dir",x="private-next-rsc-mod-ref-proxy",N="private-next-rsc-action-validate",M="private-next-rsc-server-reference",L="private-next-rsc-cache-wrapper",D="private-next-rsc-track-dynamic-import",U="private-next-rsc-action-encryption",F="private-next-rsc-action-client-wrapper",k="You can not have a '_next' folder inside of your public folder. This conflicts with the internal '/_next' route. https://nextjs.org/docs/messages/public-next-folder-conflict",B="You can not use getInitialProps with getStaticProps. To use SSG, please remove your getInitialProps",H="You can not use getInitialProps with getServerSideProps. Please remove getInitialProps.",X="You can not use getStaticProps or getStaticPaths with getServerSideProps. To use SSG, please remove getServerSideProps",W="can not have getInitialProps/getServerSideProps, https://nextjs.org/docs/messages/404-get-initial-props",G="pages with `getServerSideProps` can not be exported. See more info here: https://nextjs.org/docs/messages/gssp-export",q="Your `getStaticProps` function did not return an object. Did you forget to add a `return`?",V="Your `getServerSideProps` function did not return an object. Did you forget to add a `return`?",z="The `unstable_revalidate` property is available for general use.\nPlease use `revalidate` instead.",Y="can not be attached to a page's component and must be exported from the page. See more info here: https://nextjs.org/docs/messages/gssp-component-member",K='You are using a non-standard "NODE_ENV" value in your environment. This creates inconsistencies in the project and is strongly advised against. Read more: https://nextjs.org/docs/messages/non-standard-node-env',$="Pages with `fallback` enabled in `getStaticPaths` can not be exported. See more info here: https://nextjs.org/docs/messages/ssg-fallback-true-export",Q=["app","pages","components","lib","src"],J={edge:"edge",experimentalEdge:"experimental-edge",nodejs:"nodejs"},Z={shared:"shared",reactServerComponents:"rsc",serverSideRendering:"ssr",actionBrowser:"action-browser",apiNode:"api-node",apiEdge:"api-edge",middleware:"middleware",instrument:"instrument",edgeAsset:"edge-asset",appPagesBrowser:"app-pages-browser",pagesDirBrowser:"pages-dir-browser",pagesDirEdge:"pages-dir-edge",pagesDirNode:"pages-dir-node"},ee={...Z,GROUP:{builtinReact:[Z.reactServerComponents,Z.actionBrowser],serverOnly:[Z.reactServerComponents,Z.actionBrowser,Z.instrument,Z.middleware],neutralTarget:[Z.apiNode,Z.apiEdge],clientOnly:[Z.serverSideRendering,Z.appPagesBrowser],bundled:[Z.reactServerComponents,Z.actionBrowser,Z.serverSideRendering,Z.appPagesBrowser,Z.shared,Z.instrument,Z.middleware],appPages:[Z.reactServerComponents,Z.serverSideRendering,Z.appPagesBrowser,Z.actionBrowser]}},et={edgeSSREntry:"__next_edge_ssr_entry__",metadata:"__next_metadata__",metadataRoute:"__next_metadata_route__",metadataImageMeta:"__next_metadata_image_meta__"}},9341:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return c}});let n=r(4252),o=r(7876),a=n._(r(4232)),i=n._(r(5679)),u={400:"Bad Request",404:"This page could not be found",405:"Method Not Allowed",500:"Internal Server Error"};function l(e){let{req:t,res:r,err:n}=e;return{statusCode:r&&r.statusCode?r.statusCode:n?n.statusCode:404,hostname:window.location.hostname}}let s={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{lineHeight:"48px"},h1:{display:"inline-block",margin:"0 20px 0 0",paddingRight:23,fontSize:24,fontWeight:500,verticalAlign:"top"},h2:{fontSize:14,fontWeight:400,lineHeight:"28px"},wrap:{display:"inline-block"}};class c extends a.default.Component{render(){let{statusCode:e,withDarkMode:t=!0}=this.props,r=this.props.title||u[e]||"An unexpected error has occurred";return(0,o.jsxs)("div",{style:s.error,children:[(0,o.jsx)(i.default,{children:(0,o.jsx)("title",{children:e?e+": "+r:"Application error: a client-side exception has occurred"})}),(0,o.jsxs)("div",{style:s.desc,children:[(0,o.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}"+(t?"@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}":"")}}),e?(0,o.jsx)("h1",{className:"next-error-h1",style:s.h1,children:e}):null,(0,o.jsx)("div",{style:s.wrap,children:(0,o.jsxs)("h2",{style:s.h2,children:[this.props.title||e?r:(0,o.jsxs)(o.Fragment,{children:["Application error: a client-side exception has occurred"," ",!!this.props.hostname&&(0,o.jsxs)(o.Fragment,{children:["while loading ",this.props.hostname]})," ","(see the browser console for more information)"]}),"."]})})]})]})}}c.displayName="ErrorPage",c.getInitialProps=l,c.origGetInitialProps=l,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9393:()=>{},9525:(e,t,r)=>{"use strict";let n,o,a,i,u,l,s,c,f,d,p,h;Object.defineProperty(t,"__esModule",{value:!0});let _=r(8365);Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{emitter:function(){return X},hydrate:function(){return el},initialize:function(){return V},router:function(){return n},version:function(){return H}});let m=r(4252),g=r(7876);r(1291);let b=m._(r(4232)),y=m._(r(8944)),E=r(8831),P=m._(r(9871)),v=r(9948),R=r(3980),O=r(9163),S=r(8040),T=r(2917),j=r(2746),A=r(3090),w=m._(r(4547)),C=m._(r(2792)),I=r(1318),x=r(4294),N=r(6240),M=r(8677),L=r(1025),D=r(6023),U=r(2850),F=r(9609),k=r(5931),B=r(7207);r(4609),r(6999);let H="15.4.2",X=(0,P.default)(),W=e=>[].slice.call(e),G=!1;class q extends b.default.Component{componentDidCatch(e,t){this.props.fn(e,t)}componentDidMount(){this.scrollToHash(),n.isSsr&&(o.isFallback||o.nextExport&&((0,O.isDynamicRoute)(n.pathname)||location.search||G)||o.props&&o.props.__N_SSG&&(location.search||G))&&n.replace(n.pathname+"?"+String((0,S.assign)((0,S.urlQueryToSearchParams)(n.query),new URLSearchParams(location.search))),a,{_h:1,shallow:!o.isFallback&&!G}).catch(e=>{if(!e.cancelled)throw e})}componentDidUpdate(){this.scrollToHash()}scrollToHash(){let{hash:e}=location;if(!(e=e&&e.substring(1)))return;let t=document.getElementById(e);t&&setTimeout(()=>t.scrollIntoView(),0)}render(){return this.props.children}}async function V(e){void 0===e&&(e={}),o=JSON.parse(document.getElementById("__NEXT_DATA__").textContent),window.__NEXT_DATA__=o,h=o.defaultLocale;let t=o.assetPrefix||"";if(self.__next_set_public_path__(""+t+"/_next/"),(0,T.setConfig)({serverRuntimeConfig:{},publicRuntimeConfig:o.runtimeConfig||{}}),a=(0,j.getURL)(),(0,D.hasBasePath)(a)&&(a=(0,L.removeBasePath)(a)),o.scriptLoader){let{initScriptLoader:e}=r(3996);e(o.scriptLoader)}i=new C.default(o.buildId,t);let s=e=>{let[t,r]=e;return i.routeLoader.onEntrypoint(t,r)};return window.__NEXT_P&&window.__NEXT_P.map(e=>setTimeout(()=>s(e),0)),window.__NEXT_P=[],window.__NEXT_P.push=s,(l=(0,w.default)()).getIsSsr=()=>n.isSsr,u=document.getElementById("__next"),{assetPrefix:t}}function z(e,t){return(0,g.jsx)(e,{...t})}function Y(e){var t;let{children:r}=e,o=b.default.useMemo(()=>(0,F.adaptForAppRouterInstance)(n),[]);return(0,g.jsx)(q,{fn:e=>$({App:f,err:e}).catch(e=>console.error("Error rendering page: ",e)),children:(0,g.jsx)(U.AppRouterContext.Provider,{value:o,children:(0,g.jsx)(k.SearchParamsContext.Provider,{value:(0,F.adaptForSearchParams)(n),children:(0,g.jsx)(F.PathnameContextProviderAdapter,{router:n,isAutoExport:null!=(t=self.__NEXT_DATA__.autoExport)&&t,children:(0,g.jsx)(k.PathParamsContext.Provider,{value:(0,F.adaptForPathParams)(n),children:(0,g.jsx)(v.RouterContext.Provider,{value:(0,x.makePublicRouterInstance)(n),children:(0,g.jsx)(E.HeadManagerContext.Provider,{value:l,children:(0,g.jsx)(M.ImageConfigContext.Provider,{value:{deviceSizes:[640,750,828,1080,1200,1920,2048,3840],imageSizes:[16,32,48,64,96,128,256,384],path:"/_next/image/",loader:"default",dangerouslyAllowSVG:!1,unoptimized:!0},children:r})})})})})})})})}let K=e=>t=>{let r={...t,Component:p,err:o.err,router:n};return(0,g.jsx)(Y,{children:z(e,r)})};function $(e){let{App:t,err:u}=e;return console.error(u),console.error("A client-side exception has occurred, see here for more info: https://nextjs.org/docs/messages/client-side-exception-occurred"),i.loadPage("/_error").then(n=>{let{page:o,styleSheets:a}=n;return(null==s?void 0:s.Component)===o?Promise.resolve().then(()=>_._(r(9341))).then(n=>Promise.resolve().then(()=>_._(r(472))).then(r=>(e.App=t=r.default,n))).then(e=>({ErrorComponent:e.default,styleSheets:[]})):{ErrorComponent:o,styleSheets:a}}).then(r=>{var i;let{ErrorComponent:l,styleSheets:s}=r,c=K(t),f={Component:l,AppTree:c,router:n,ctx:{err:u,pathname:o.page,query:o.query,asPath:a,AppTree:c}};return Promise.resolve((null==(i=e.props)?void 0:i.err)?e.props:(0,j.loadGetInitialProps)(t,f)).then(t=>ei({...e,err:u,Component:l,styleSheets:s,props:t}))})}function Q(e){let{callback:t}=e;return b.default.useLayoutEffect(()=>t(),[t]),null}let J={navigationStart:"navigationStart",beforeRender:"beforeRender",afterRender:"afterRender",afterHydrate:"afterHydrate",routeChange:"routeChange"},Z={hydration:"Next.js-hydration",beforeHydration:"Next.js-before-hydration",routeChangeToRender:"Next.js-route-change-to-render",render:"Next.js-render"},ee=null,et=!0;function er(){[J.beforeRender,J.afterHydrate,J.afterRender,J.routeChange].forEach(e=>performance.clearMarks(e))}function en(){j.ST&&(performance.mark(J.afterHydrate),performance.getEntriesByName(J.beforeRender,"mark").length&&(performance.measure(Z.beforeHydration,J.navigationStart,J.beforeRender),performance.measure(Z.hydration,J.beforeRender,J.afterHydrate)),d&&performance.getEntriesByName(Z.hydration).forEach(d),er())}function eo(){if(!j.ST)return;performance.mark(J.afterRender);let e=performance.getEntriesByName(J.routeChange,"mark");e.length&&(performance.getEntriesByName(J.beforeRender,"mark").length&&(performance.measure(Z.routeChangeToRender,e[0].name,J.beforeRender),performance.measure(Z.render,J.beforeRender,J.afterRender),d&&(performance.getEntriesByName(Z.render).forEach(d),performance.getEntriesByName(Z.routeChangeToRender).forEach(d))),er(),[Z.routeChangeToRender,Z.render].forEach(e=>performance.clearMeasures(e)))}function ea(e){let{callbacks:t,children:r}=e;return b.default.useLayoutEffect(()=>t.forEach(e=>e()),[t]),r}function ei(e){let t,r,{App:o,Component:a,props:i,err:l}=e,f="initial"in e?void 0:e.styleSheets;a=a||s.Component;let d={...i=i||s.props,Component:a,err:l,router:n};s=d;let p=!1,h=new Promise((e,t)=>{c&&c(),r=()=>{c=null,e()},c=()=>{p=!0,c=null;let e=Object.defineProperty(Error("Cancel rendering route"),"__NEXT_ERROR_CODE",{value:"E503",enumerable:!1,configurable:!0});e.cancelled=!0,t(e)}});function _(){r()}!function(){if(!f)return;let e=new Set(W(document.querySelectorAll("style[data-n-href]")).map(e=>e.getAttribute("data-n-href"))),t=document.querySelector("noscript[data-n-css]"),r=null==t?void 0:t.getAttribute("data-n-css");f.forEach(t=>{let{href:n,text:o}=t;if(!e.has(n)){let e=document.createElement("style");e.setAttribute("data-n-href",n),e.setAttribute("media","x"),r&&e.setAttribute("nonce",r),document.head.appendChild(e),e.appendChild(document.createTextNode(o))}})}();let m=(0,g.jsxs)(g.Fragment,{children:[(0,g.jsx)(Q,{callback:function(){if(f&&!p){let e=new Set(f.map(e=>e.href)),t=W(document.querySelectorAll("style[data-n-href]")),r=t.map(e=>e.getAttribute("data-n-href"));for(let n=0;n{let{href:t}=e,r=document.querySelector('style[data-n-href="'+t+'"]');r&&(n.parentNode.insertBefore(r,n.nextSibling),n=r)}),W(document.querySelectorAll("link[data-n-p]")).forEach(e=>{e.parentNode.removeChild(e)})}if(e.scroll){let{x:t,y:r}=e.scroll;(0,R.disableSmoothScrollDuringRouteTransition)(()=>{window.scrollTo(t,r)})}}}),(0,g.jsxs)(Y,{children:[z(o,d),(0,g.jsx)(A.Portal,{type:"next-route-announcer",children:(0,g.jsx)(I.RouteAnnouncer,{})})]})]});var E=u;j.ST&&performance.mark(J.beforeRender);let P=(t=et?en:eo,(0,g.jsx)(ea,{callbacks:[t,_],children:(0,g.jsx)(b.default.StrictMode,{children:m})}));return ee?(0,b.default.startTransition)(()=>{ee.render(P)}):(ee=y.default.hydrateRoot(E,P,{onRecoverableError:B.onRecoverableError}),et=!1),h}async function eu(e){if(e.err&&(void 0===e.Component||!e.isHydratePass))return void await $(e);try{await ei(e)}catch(r){let t=(0,N.getProperError)(r);if(t.cancelled)throw t;await $({...e,err:t})}}async function el(e){let t=o.err;try{let e=await i.routeLoader.whenEntrypoint("/_app");if("error"in e)throw e.error;let{component:t,exports:r}=e;f=t,r&&r.reportWebVitals&&(d=e=>{let t,{id:n,name:o,startTime:a,value:i,duration:u,entryType:l,entries:s,attribution:c}=e,f=Date.now()+"-"+(Math.floor(Math.random()*(9e12-1))+1e12);s&&s.length&&(t=s[0].startTime);let d={id:n||f,name:o,startTime:a||t,value:null==i?u:i,label:"mark"===l||"measure"===l?"custom":"web-vital"};c&&(d.attribution=c),r.reportWebVitals(d)});let n=await i.routeLoader.whenEntrypoint(o.page);if("error"in n)throw n.error;p=n.component}catch(e){t=(0,N.getProperError)(e)}window.__NEXT_PRELOADREADY&&await window.__NEXT_PRELOADREADY(o.dynamicIds),n=(0,x.createRouter)(o.page,o.query,a,{initialProps:o.props,pageLoader:i,App:f,Component:p,wrapApp:K,err:t,isFallback:!!o.isFallback,subscription:(e,t,r)=>eu(Object.assign({},e,{App:t,scroll:r})),locale:o.locale,locales:o.locales,defaultLocale:h,domainLocales:o.domainLocales,isPreview:o.isPreview}),G=await n._initialMatchesMiddlewarePromise;let r={App:f,initial:!0,Component:p,props:o.props,err:t,isHydratePass:!0};(null==e?void 0:e.beforeRender)&&await e.beforeRender(),eu(r)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9609:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{PathnameContextProviderAdapter:function(){return p},adaptForAppRouterInstance:function(){return c},adaptForPathParams:function(){return d},adaptForSearchParams:function(){return f}});let n=r(8365),o=r(7876),a=n._(r(4232)),i=r(5931),u=r(3069),l=r(8213),s=r(5214);function c(e){return{back(){e.back()},forward(){e.forward()},refresh(){e.reload()},hmrRefresh(){},push(t,r){let{scroll:n}=void 0===r?{}:r;e.push(t,void 0,{scroll:n})},replace(t,r){let{scroll:n}=void 0===r?{}:r;e.replace(t,void 0,{scroll:n})},prefetch(t){e.prefetch(t)}}}function f(e){return e.isReady&&e.query?(0,l.asPathToSearchParams)(e.asPath):new URLSearchParams}function d(e){if(!e.isReady||!e.query)return null;let t={};for(let r of Object.keys((0,s.getRouteRegex)(e.pathname).groups))t[r]=e.query[r];return t}function p(e){let{children:t,router:r,...n}=e,l=(0,a.useRef)(n.isAutoExport),s=(0,a.useMemo)(()=>{let e,t=l.current;if(t&&(l.current=!1),(0,u.isDynamicRoute)(r.pathname)&&(r.isFallback||t&&!r.isReady))return null;try{e=new URL(r.asPath,"http://f")}catch(e){return"/"}return e.pathname},[r.asPath,r.isFallback,r.isReady,r.pathname]);return(0,o.jsx)(i.PathnameContext.Provider,{value:s,children:t})}},9611:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"setAttributesFromProps",{enumerable:!0,get:function(){return a}});let r={acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv",noModule:"noModule"},n=["onLoad","onReady","dangerouslySetInnerHTML","children","onError","strategy","stylesheets"];function o(e){return["async","defer","noModule"].includes(e)}function a(e,t){for(let[a,i]of Object.entries(t)){if(!t.hasOwnProperty(a)||n.includes(a)||void 0===i)continue;let u=r[a]||a.toLowerCase();"SCRIPT"===e.tagName&&o(u)?e[u]=!!i:e.setAttribute(u,String(i)),(!1===i||"SCRIPT"===e.tagName&&o(u)&&(!i||"false"===i))&&(e.setAttribute(u,""),e.removeAttribute(u))}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9871:(e,t)=>{"use strict";function r(){let e=Object.create(null);return{on(t,r){(e[t]||(e[t]=[])).push(r)},off(t,r){e[t]&&e[t].splice(e[t].indexOf(r)>>>0,1)},emit(t){for(var r=arguments.length,n=Array(r>1?r-1:0),o=1;o{e(...n)})}}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return r}})},9948:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"RouterContext",{enumerable:!0,get:function(){return n}});let n=r(4252)._(r(4232)).default.createContext(null)}},e=>{e.O(0,[593],()=>e(e.s=5842)),_N_E=e.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/main-app-b3d2ebcb2857645a.js b/keploy/pkg/service/load/out/_next/static/chunks/main-app-b3d2ebcb2857645a.js new file mode 100644 index 0000000..d0b6218 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/main-app-b3d2ebcb2857645a.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[358],{5972:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,8393,23)),Promise.resolve().then(n.t.bind(n,894,23)),Promise.resolve().then(n.t.bind(n,4970,23)),Promise.resolve().then(n.t.bind(n,6975,23)),Promise.resolve().then(n.t.bind(n,7555,23)),Promise.resolve().then(n.t.bind(n,4911,23)),Promise.resolve().then(n.t.bind(n,9665,23)),Promise.resolve().then(n.t.bind(n,1295,23)),Promise.resolve().then(n.bind(n,8175))},9393:()=>{}},e=>{var s=s=>e(e.s=s);e.O(0,[441,964],()=>(s(5415),s(5972))),_N_E=e.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/pages/_app-0a0020ddd67f79cf.js b/keploy/pkg/service/load/out/_next/static/chunks/pages/_app-0a0020ddd67f79cf.js new file mode 100644 index 0000000..5c39d7b --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/pages/_app-0a0020ddd67f79cf.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[636],{326:(_,n,p)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return p(472)}])}},_=>{var n=n=>_(_.s=n);_.O(0,[593,792],()=>(n(326),n(4294))),_N_E=_.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/pages/_error-03529f2c21436739.js b/keploy/pkg/service/load/out/_next/static/chunks/pages/_error-03529f2c21436739.js new file mode 100644 index 0000000..f9fbf98 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/pages/_error-03529f2c21436739.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[731],{2164:(_,n,e)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return e(9341)}])}},_=>{_.O(0,[636,593,792],()=>_(_.s=2164)),_N_E=_.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/polyfills-42372ed130431b0a.js b/keploy/pkg/service/load/out/_next/static/chunks/polyfills-42372ed130431b0a.js new file mode 100644 index 0000000..ab422b9 --- /dev/null +++ b/keploy/pkg/service/load/out/_next/static/chunks/polyfills-42372ed130431b0a.js @@ -0,0 +1 @@ +!function(){var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function e(t){var e={exports:{}};return t(e,e.exports),e.exports}var r,n,o=function(t){return t&&t.Math===Math&&t},i=o("object"==typeof globalThis&&globalThis)||o("object"==typeof window&&window)||o("object"==typeof self&&self)||o("object"==typeof t&&t)||o("object"==typeof t&&t)||function(){return this}()||Function("return this")(),a=function(t){try{return!!t()}catch(t){return!0}},u=!a(function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]}),s=!a(function(){var t=function(){}.bind();return"function"!=typeof t||t.hasOwnProperty("prototype")}),c=Function.prototype.call,f=s?c.bind(c):function(){return c.apply(c,arguments)},l={}.propertyIsEnumerable,h=Object.getOwnPropertyDescriptor,p=h&&!l.call({1:2},1)?function(t){var e=h(this,t);return!!e&&e.enumerable}:l,v={f:p},d=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},g=Function.prototype,y=g.call,m=s&&g.bind.bind(y,y),b=s?m:function(t){return function(){return y.apply(t,arguments)}},w=b({}.toString),S=b("".slice),E=function(t){return S(w(t),8,-1)},O=Object,x=b("".split),R=a(function(){return!O("z").propertyIsEnumerable(0)})?function(t){return"String"===E(t)?x(t,""):O(t)}:O,P=function(t){return null==t},A=TypeError,j=function(t){if(P(t))throw new A("Can't call method on "+t);return t},k=function(t){return R(j(t))},I="object"==typeof document&&document.all,T=void 0===I&&void 0!==I?function(t){return"function"==typeof t||t===I}:function(t){return"function"==typeof t},M=function(t){return"object"==typeof t?null!==t:T(t)},L=function(t,e){return arguments.length<2?T(r=i[t])?r:void 0:i[t]&&i[t][e];var r},U=b({}.isPrototypeOf),N=i.navigator,C=N&&N.userAgent,_=C?String(C):"",F=i.process,B=i.Deno,D=F&&F.versions||B&&B.version,z=D&&D.v8;z&&(n=(r=z.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!n&&_&&(!(r=_.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=_.match(/Chrome\/(\d+)/))&&(n=+r[1]);var W=n,q=i.String,H=!!Object.getOwnPropertySymbols&&!a(function(){var t=Symbol("symbol detection");return!q(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&W&&W<41}),$=H&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,K=Object,G=$?function(t){return"symbol"==typeof t}:function(t){var e=L("Symbol");return T(e)&&U(e.prototype,K(t))},V=String,Y=function(t){try{return V(t)}catch(t){return"Object"}},X=TypeError,J=function(t){if(T(t))return t;throw new X(Y(t)+" is not a function")},Q=function(t,e){var r=t[e];return P(r)?void 0:J(r)},Z=TypeError,tt=Object.defineProperty,et=function(t,e){try{tt(i,t,{value:e,configurable:!0,writable:!0})}catch(r){i[t]=e}return e},rt=e(function(t){var e="__core-js_shared__",r=t.exports=i[e]||et(e,{});(r.versions||(r.versions=[])).push({version:"3.38.1",mode:"global",copyright:"© 2014-2024 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.38.1/LICENSE",source:"https://github.com/zloirock/core-js"})}),nt=function(t,e){return rt[t]||(rt[t]=e||{})},ot=Object,it=function(t){return ot(j(t))},at=b({}.hasOwnProperty),ut=Object.hasOwn||function(t,e){return at(it(t),e)},st=0,ct=Math.random(),ft=b(1..toString),lt=function(t){return"Symbol("+(void 0===t?"":t)+")_"+ft(++st+ct,36)},ht=i.Symbol,pt=nt("wks"),vt=$?ht.for||ht:ht&&ht.withoutSetter||lt,dt=function(t){return ut(pt,t)||(pt[t]=H&&ut(ht,t)?ht[t]:vt("Symbol."+t)),pt[t]},gt=TypeError,yt=dt("toPrimitive"),mt=function(t,e){if(!M(t)||G(t))return t;var r,n=Q(t,yt);if(n){if(void 0===e&&(e="default"),r=f(n,t,e),!M(r)||G(r))return r;throw new gt("Can't convert object to primitive value")}return void 0===e&&(e="number"),function(t,e){var r,n;if("string"===e&&T(r=t.toString)&&!M(n=f(r,t)))return n;if(T(r=t.valueOf)&&!M(n=f(r,t)))return n;if("string"!==e&&T(r=t.toString)&&!M(n=f(r,t)))return n;throw new Z("Can't convert object to primitive value")}(t,e)},bt=function(t){var e=mt(t,"string");return G(e)?e:e+""},wt=i.document,St=M(wt)&&M(wt.createElement),Et=function(t){return St?wt.createElement(t):{}},Ot=!u&&!a(function(){return 7!==Object.defineProperty(Et("div"),"a",{get:function(){return 7}}).a}),xt=Object.getOwnPropertyDescriptor,Rt={f:u?xt:function(t,e){if(t=k(t),e=bt(e),Ot)try{return xt(t,e)}catch(t){}if(ut(t,e))return d(!f(v.f,t,e),t[e])}},Pt=u&&a(function(){return 42!==Object.defineProperty(function(){},"prototype",{value:42,writable:!1}).prototype}),At=String,jt=TypeError,kt=function(t){if(M(t))return t;throw new jt(At(t)+" is not an object")},It=TypeError,Tt=Object.defineProperty,Mt=Object.getOwnPropertyDescriptor,Lt="enumerable",Ut="configurable",Nt="writable",Ct={f:u?Pt?function(t,e,r){if(kt(t),e=bt(e),kt(r),"function"==typeof t&&"prototype"===e&&"value"in r&&Nt in r&&!r[Nt]){var n=Mt(t,e);n&&n[Nt]&&(t[e]=r.value,r={configurable:Ut in r?r[Ut]:n[Ut],enumerable:Lt in r?r[Lt]:n[Lt],writable:!1})}return Tt(t,e,r)}:Tt:function(t,e,r){if(kt(t),e=bt(e),kt(r),Ot)try{return Tt(t,e,r)}catch(t){}if("get"in r||"set"in r)throw new It("Accessors not supported");return"value"in r&&(t[e]=r.value),t}},_t=u?function(t,e,r){return Ct.f(t,e,d(1,r))}:function(t,e,r){return t[e]=r,t},Ft=Function.prototype,Bt=u&&Object.getOwnPropertyDescriptor,Dt=ut(Ft,"name"),zt={EXISTS:Dt,PROPER:Dt&&"something"===function(){}.name,CONFIGURABLE:Dt&&(!u||u&&Bt(Ft,"name").configurable)},Wt=b(Function.toString);T(rt.inspectSource)||(rt.inspectSource=function(t){return Wt(t)});var qt,Ht,$t,Kt=rt.inspectSource,Gt=i.WeakMap,Vt=T(Gt)&&/native code/.test(String(Gt)),Yt=nt("keys"),Xt=function(t){return Yt[t]||(Yt[t]=lt(t))},Jt={},Qt="Object already initialized",Zt=i.TypeError;if(Vt||rt.state){var te=rt.state||(rt.state=new(0,i.WeakMap));te.get=te.get,te.has=te.has,te.set=te.set,qt=function(t,e){if(te.has(t))throw new Zt(Qt);return e.facade=t,te.set(t,e),e},Ht=function(t){return te.get(t)||{}},$t=function(t){return te.has(t)}}else{var ee=Xt("state");Jt[ee]=!0,qt=function(t,e){if(ut(t,ee))throw new Zt(Qt);return e.facade=t,_t(t,ee,e),e},Ht=function(t){return ut(t,ee)?t[ee]:{}},$t=function(t){return ut(t,ee)}}var re,ne={set:qt,get:Ht,has:$t,enforce:function(t){return $t(t)?Ht(t):qt(t,{})},getterFor:function(t){return function(e){var r;if(!M(e)||(r=Ht(e)).type!==t)throw new Zt("Incompatible receiver, "+t+" required");return r}}},oe=e(function(t){var e=zt.CONFIGURABLE,r=ne.enforce,n=ne.get,o=String,i=Object.defineProperty,s=b("".slice),c=b("".replace),f=b([].join),l=u&&!a(function(){return 8!==i(function(){},"length",{value:8}).length}),h=String(String).split("String"),p=t.exports=function(t,n,a){"Symbol("===s(o(n),0,7)&&(n="["+c(o(n),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),a&&a.getter&&(n="get "+n),a&&a.setter&&(n="set "+n),(!ut(t,"name")||e&&t.name!==n)&&(u?i(t,"name",{value:n,configurable:!0}):t.name=n),l&&a&&ut(a,"arity")&&t.length!==a.arity&&i(t,"length",{value:a.arity});try{a&&ut(a,"constructor")&&a.constructor?u&&i(t,"prototype",{writable:!1}):t.prototype&&(t.prototype=void 0)}catch(t){}var p=r(t);return ut(p,"source")||(p.source=f(h,"string"==typeof n?n:"")),t};Function.prototype.toString=p(function(){return T(this)&&n(this).source||Kt(this)},"toString")}),ie=function(t,e,r,n){n||(n={});var o=n.enumerable,i=void 0!==n.name?n.name:e;if(T(r)&&oe(r,i,n),n.global)o?t[e]=r:et(e,r);else{try{n.unsafe?t[e]&&(o=!0):delete t[e]}catch(t){}o?t[e]=r:Ct.f(t,e,{value:r,enumerable:!1,configurable:!n.nonConfigurable,writable:!n.nonWritable})}return t},ae=Math.ceil,ue=Math.floor,se=Math.trunc||function(t){var e=+t;return(e>0?ue:ae)(e)},ce=function(t){var e=+t;return e!=e||0===e?0:se(e)},fe=Math.max,le=Math.min,he=function(t,e){var r=ce(t);return r<0?fe(r+e,0):le(r,e)},pe=Math.min,ve=function(t){var e=ce(t);return e>0?pe(e,9007199254740991):0},de=function(t){return ve(t.length)},ge=function(t){return function(e,r,n){var o=k(e),i=de(o);if(0===i)return!t&&-1;var a,u=he(n,i);if(t&&r!=r){for(;i>u;)if((a=o[u++])!=a)return!0}else for(;i>u;u++)if((t||u in o)&&o[u]===r)return t||u||0;return!t&&-1}},ye={includes:ge(!0),indexOf:ge(!1)},me=ye.indexOf,be=b([].push),we=function(t,e){var r,n=k(t),o=0,i=[];for(r in n)!ut(Jt,r)&&ut(n,r)&&be(i,r);for(;e.length>o;)ut(n,r=e[o++])&&(~me(i,r)||be(i,r));return i},Se=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],Ee=Se.concat("length","prototype"),Oe={f:Object.getOwnPropertyNames||function(t){return we(t,Ee)}},xe={f:Object.getOwnPropertySymbols},Re=b([].concat),Pe=L("Reflect","ownKeys")||function(t){var e=Oe.f(kt(t)),r=xe.f;return r?Re(e,r(t)):e},Ae=function(t,e,r){for(var n=Pe(e),o=Ct.f,i=Rt.f,a=0;aa;)Ct.f(t,r=o[a++],n[r]);return t},Be={f:Fe},De=L("document","documentElement"),ze="prototype",We="script",qe=Xt("IE_PROTO"),He=function(){},$e=function(t){return"<"+We+">"+t+""},Ke=function(t){t.write($e("")),t.close();var e=t.parentWindow.Object;return t=null,e},Ge=function(){try{re=new ActiveXObject("htmlfile")}catch(t){}var t,e,r;Ge="undefined"!=typeof document?document.domain&&re?Ke(re):(e=Et("iframe"),r="java"+We+":",e.style.display="none",De.appendChild(e),e.src=String(r),(t=e.contentWindow.document).open(),t.write($e("document.F=Object")),t.close(),t.F):Ke(re);for(var n=Se.length;n--;)delete Ge[ze][Se[n]];return Ge()};Jt[qe]=!0;var Ve=Object.create||function(t,e){var r;return null!==t?(He[ze]=kt(t),r=new He,He[ze]=null,r[qe]=t):r=Ge(),void 0===e?r:Be.f(r,e)},Ye=Ct.f,Xe=dt("unscopables"),Je=Array.prototype;void 0===Je[Xe]&&Ye(Je,Xe,{configurable:!0,value:Ve(null)});var Qe=function(t){Je[Xe][t]=!0};Ce({target:"Array",proto:!0},{at:function(t){var e=it(this),r=de(e),n=ce(t),o=n>=0?n:r+n;return o<0||o>=r?void 0:e[o]}}),Qe("at");var Ze=function(t,e){return b(i[t].prototype[e])},tr=(Ze("Array","at"),TypeError),er=function(t,e){if(!delete t[e])throw new tr("Cannot delete property "+Y(e)+" of "+Y(t))},rr=Math.min,nr=[].copyWithin||function(t,e){var r=it(this),n=de(r),o=he(t,n),i=he(e,n),a=arguments.length>2?arguments[2]:void 0,u=rr((void 0===a?n:he(a,n))-i,n-o),s=1;for(i0;)i in r?r[o]=r[i]:er(r,o),o+=s,i+=s;return r};Ce({target:"Array",proto:!0},{copyWithin:nr}),Qe("copyWithin"),Ze("Array","copyWithin"),Ce({target:"Array",proto:!0},{fill:function(t){for(var e=it(this),r=de(e),n=arguments.length,o=he(n>1?arguments[1]:void 0,r),i=n>2?arguments[2]:void 0,a=void 0===i?r:he(i,r);a>o;)e[o++]=t;return e}}),Qe("fill"),Ze("Array","fill");var or=function(t){if("Function"===E(t))return b(t)},ir=or(or.bind),ar=function(t,e){return J(t),void 0===e?t:s?ir(t,e):function(){return t.apply(e,arguments)}},ur=Array.isArray||function(t){return"Array"===E(t)},sr={};sr[dt("toStringTag")]="z";var cr="[object z]"===String(sr),fr=dt("toStringTag"),lr=Object,hr="Arguments"===E(function(){return arguments}()),pr=cr?E:function(t){var e,r,n;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(r=function(t,e){try{return t[e]}catch(t){}}(e=lr(t),fr))?r:hr?E(e):"Object"===(n=E(e))&&T(e.callee)?"Arguments":n},vr=function(){},dr=L("Reflect","construct"),gr=/^\s*(?:class|function)\b/,yr=b(gr.exec),mr=!gr.test(vr),br=function(t){if(!T(t))return!1;try{return dr(vr,[],t),!0}catch(t){return!1}},wr=function(t){if(!T(t))return!1;switch(pr(t)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return mr||!!yr(gr,Kt(t))}catch(t){return!0}};wr.sham=!0;var Sr=!dr||a(function(){var t;return br(br.call)||!br(Object)||!br(function(){t=!0})||t})?wr:br,Er=dt("species"),Or=Array,xr=function(t,e){return new(function(t){var e;return ur(t)&&(Sr(e=t.constructor)&&(e===Or||ur(e.prototype))||M(e)&&null===(e=e[Er]))&&(e=void 0),void 0===e?Or:e}(t))(0===e?0:e)},Rr=b([].push),Pr=function(t){var e=1===t,r=2===t,n=3===t,o=4===t,i=6===t,a=7===t,u=5===t||i;return function(s,c,f,l){for(var h,p,v=it(s),d=R(v),g=de(d),y=ar(c,f),m=0,b=l||xr,w=e?b(s,g):r||a?b(s,0):void 0;g>m;m++)if((u||m in d)&&(p=y(h=d[m],m,v),t))if(e)w[m]=p;else if(p)switch(t){case 3:return!0;case 5:return h;case 6:return m;case 2:Rr(w,h)}else switch(t){case 4:return!1;case 7:Rr(w,h)}return i?-1:n||o?o:w}},Ar={forEach:Pr(0),map:Pr(1),filter:Pr(2),some:Pr(3),every:Pr(4),find:Pr(5),findIndex:Pr(6),filterReject:Pr(7)},jr=Ar.find,kr="find",Ir=!0;kr in[]&&Array(1)[kr](function(){Ir=!1}),Ce({target:"Array",proto:!0,forced:Ir},{find:function(t){return jr(this,t,arguments.length>1?arguments[1]:void 0)}}),Qe(kr),Ze("Array","find");var Tr=Ar.findIndex,Mr="findIndex",Lr=!0;Mr in[]&&Array(1)[Mr](function(){Lr=!1}),Ce({target:"Array",proto:!0,forced:Lr},{findIndex:function(t){return Tr(this,t,arguments.length>1?arguments[1]:void 0)}}),Qe(Mr),Ze("Array","findIndex");var Ur=TypeError,Nr=function(t){if(t>9007199254740991)throw Ur("Maximum allowed index exceeded");return t},Cr=function(t,e,r,n,o,i,a,u){for(var s,c,f=o,l=0,h=!!a&&ar(a,u);l0&&ur(s)?(c=de(s),f=Cr(t,e,s,c,f,i-1)-1):(Nr(f+1),t[f]=s),f++),l++;return f},_r=Cr;Ce({target:"Array",proto:!0},{flatMap:function(t){var e,r=it(this),n=de(r);return J(t),(e=xr(r,0)).length=_r(e,r,r,n,0,1,t,arguments.length>1?arguments[1]:void 0),e}}),Qe("flatMap"),Ze("Array","flatMap"),Ce({target:"Array",proto:!0},{flat:function(){var t=arguments.length?arguments[0]:void 0,e=it(this),r=de(e),n=xr(e,0);return n.length=_r(n,e,e,r,0,void 0===t?1:ce(t)),n}}),Qe("flat"),Ze("Array","flat");var Fr,Br,Dr,zr=String,Wr=function(t){if("Symbol"===pr(t))throw new TypeError("Cannot convert a Symbol value to a string");return zr(t)},qr=b("".charAt),Hr=b("".charCodeAt),$r=b("".slice),Kr=function(t){return function(e,r){var n,o,i=Wr(j(e)),a=ce(r),u=i.length;return a<0||a>=u?t?"":void 0:(n=Hr(i,a))<55296||n>56319||a+1===u||(o=Hr(i,a+1))<56320||o>57343?t?qr(i,a):n:t?$r(i,a,a+2):o-56320+(n-55296<<10)+65536}},Gr={codeAt:Kr(!1),charAt:Kr(!0)},Vr=!a(function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}),Yr=Xt("IE_PROTO"),Xr=Object,Jr=Xr.prototype,Qr=Vr?Xr.getPrototypeOf:function(t){var e=it(t);if(ut(e,Yr))return e[Yr];var r=e.constructor;return T(r)&&e instanceof r?r.prototype:e instanceof Xr?Jr:null},Zr=dt("iterator"),tn=!1;[].keys&&("next"in(Dr=[].keys())?(Br=Qr(Qr(Dr)))!==Object.prototype&&(Fr=Br):tn=!0);var en=!M(Fr)||a(function(){var t={};return Fr[Zr].call(t)!==t});en&&(Fr={}),T(Fr[Zr])||ie(Fr,Zr,function(){return this});var rn={IteratorPrototype:Fr,BUGGY_SAFARI_ITERATORS:tn},nn=Ct.f,on=dt("toStringTag"),an=function(t,e,r){t&&!r&&(t=t.prototype),t&&!ut(t,on)&&nn(t,on,{configurable:!0,value:e})},un={},sn=rn.IteratorPrototype,cn=function(){return this},fn=function(t,e,r,n){var o=e+" Iterator";return t.prototype=Ve(sn,{next:d(+!n,r)}),an(t,o,!1),un[o]=cn,t},ln=function(t,e,r){try{return b(J(Object.getOwnPropertyDescriptor(t,e)[r]))}catch(t){}},hn=String,pn=TypeError,vn=function(t){if(function(t){return M(t)||null===t}(t))return t;throw new pn("Can't set "+hn(t)+" as a prototype")},dn=Object.setPrototypeOf||("__proto__"in{}?function(){var t,e=!1,r={};try{(t=ln(Object.prototype,"__proto__","set"))(r,[]),e=r instanceof Array}catch(t){}return function(r,n){return j(r),vn(n),M(r)?(e?t(r,n):r.__proto__=n,r):r}}():void 0),gn=zt.PROPER,yn=zt.CONFIGURABLE,mn=rn.IteratorPrototype,bn=rn.BUGGY_SAFARI_ITERATORS,wn=dt("iterator"),Sn="keys",En="values",On="entries",xn=function(){return this},Rn=function(t,e,r,n,o,i,a){fn(r,e,n);var u,s,c,l=function(t){if(t===o&&g)return g;if(!bn&&t&&t in v)return v[t];switch(t){case Sn:case En:case On:return function(){return new r(this,t)}}return function(){return new r(this)}},h=e+" Iterator",p=!1,v=t.prototype,d=v[wn]||v["@@iterator"]||o&&v[o],g=!bn&&d||l(o),y="Array"===e&&v.entries||d;if(y&&(u=Qr(y.call(new t)))!==Object.prototype&&u.next&&(Qr(u)!==mn&&(dn?dn(u,mn):T(u[wn])||ie(u,wn,xn)),an(u,h,!0)),gn&&o===En&&d&&d.name!==En&&(yn?_t(v,"name",En):(p=!0,g=function(){return f(d,this)})),o)if(s={values:l(En),keys:i?g:l(Sn),entries:l(On)},a)for(c in s)(bn||p||!(c in v))&&ie(v,c,s[c]);else Ce({target:e,proto:!0,forced:bn||p},s);return v[wn]!==g&&ie(v,wn,g,{name:o}),un[e]=g,s},Pn=function(t,e){return{value:t,done:e}},An=Gr.charAt,jn="String Iterator",kn=ne.set,In=ne.getterFor(jn);Rn(String,"String",function(t){kn(this,{type:jn,string:Wr(t),index:0})},function(){var t,e=In(this),r=e.string,n=e.index;return n>=r.length?Pn(void 0,!0):(t=An(r,n),e.index+=t.length,Pn(t,!1))});var Tn=function(t,e,r){var n,o;kt(t);try{if(!(n=Q(t,"return"))){if("throw"===e)throw r;return r}n=f(n,t)}catch(t){o=!0,n=t}if("throw"===e)throw r;if(o)throw n;return kt(n),r},Mn=function(t,e,r,n){try{return n?e(kt(r)[0],r[1]):e(r)}catch(e){Tn(t,"throw",e)}},Ln=dt("iterator"),Un=Array.prototype,Nn=function(t){return void 0!==t&&(un.Array===t||Un[Ln]===t)},Cn=function(t,e,r){u?Ct.f(t,e,d(0,r)):t[e]=r},_n=dt("iterator"),Fn=function(t){if(!P(t))return Q(t,_n)||Q(t,"@@iterator")||un[pr(t)]},Bn=TypeError,Dn=function(t,e){var r=arguments.length<2?Fn(t):e;if(J(r))return kt(f(r,t));throw new Bn(Y(t)+" is not iterable")},zn=Array,Wn=function(t){var e=it(t),r=Sr(this),n=arguments.length,o=n>1?arguments[1]:void 0,i=void 0!==o;i&&(o=ar(o,n>2?arguments[2]:void 0));var a,u,s,c,l,h,p=Fn(e),v=0;if(!p||this===zn&&Nn(p))for(a=de(e),u=r?new this(a):zn(a);a>v;v++)h=i?o(e[v],v):e[v],Cn(u,v,h);else for(u=r?new this:[],l=(c=Dn(e,p)).next;!(s=f(l,c)).done;v++)h=i?Mn(c,o,[s.value,v],!0):s.value,Cn(u,v,h);return u.length=v,u},qn=dt("iterator"),Hn=!1;try{var $n=0,Kn={next:function(){return{done:!!$n++}},return:function(){Hn=!0}};Kn[qn]=function(){return this},Array.from(Kn,function(){throw 2})}catch(t){}var Gn=function(t,e){try{if(!e&&!Hn)return!1}catch(t){return!1}var r=!1;try{var n={};n[qn]=function(){return{next:function(){return{done:r=!0}}}},t(n)}catch(t){}return r},Vn=!Gn(function(t){Array.from(t)});Ce({target:"Array",stat:!0,forced:Vn},{from:Wn});var Yn=i,Xn=ye.includes,Jn=a(function(){return!Array(1).includes()});Ce({target:"Array",proto:!0,forced:Jn},{includes:function(t){return Xn(this,t,arguments.length>1?arguments[1]:void 0)}}),Qe("includes"),Ze("Array","includes");var Qn=Ct.f,Zn="Array Iterator",to=ne.set,eo=ne.getterFor(Zn),ro=Rn(Array,"Array",function(t,e){to(this,{type:Zn,target:k(t),index:0,kind:e})},function(){var t=eo(this),e=t.target,r=t.index++;if(!e||r>=e.length)return t.target=null,Pn(void 0,!0);switch(t.kind){case"keys":return Pn(r,!1);case"values":return Pn(e[r],!1)}return Pn([r,e[r]],!1)},"values"),no=un.Arguments=un.Array;if(Qe("keys"),Qe("values"),Qe("entries"),u&&"values"!==no.name)try{Qn(no,"name",{value:"values"})}catch(t){}cr||ie(Object.prototype,"toString",cr?{}.toString:function(){return"[object "+pr(this)+"]"},{unsafe:!0}),Ze("Array","values");var oo=Array,io=a(function(){function t(){}return!(oo.of.call(t)instanceof t)});Ce({target:"Array",stat:!0,forced:io},{of:function(){for(var t=0,e=arguments.length,r=new(Sr(this)?this:oo)(e);e>t;)Cn(r,t,arguments[t++]);return r.length=e,r}});var ao=dt("hasInstance"),uo=Function.prototype;ao in uo||Ct.f(uo,ao,{value:oe(function(t){if(!T(this)||!M(t))return!1;var e=this.prototype;return M(e)?U(e,t):t instanceof this},ao)}),dt("hasInstance");var so=function(t,e,r){return r.get&&oe(r.get,e,{getter:!0}),r.set&&oe(r.set,e,{setter:!0}),Ct.f(t,e,r)},co=zt.EXISTS,fo=Function.prototype,lo=b(fo.toString),ho=/function\b(?:\s|\/\*[\S\s]*?\*\/|\/\/[^\n\r]*[\n\r]+)*([^\s(/]*)/,po=b(ho.exec);u&&!co&&so(fo,"name",{configurable:!0,get:function(){try{return po(ho,lo(this))[1]}catch(t){return""}}});var vo=b([].slice),go=Oe.f,yo="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],mo={f:function(t){return yo&&"Window"===E(t)?function(t){try{return go(t)}catch(t){return vo(yo)}}(t):go(k(t))}},bo=a(function(){if("function"==typeof ArrayBuffer){var t=new ArrayBuffer(8);Object.isExtensible(t)&&Object.defineProperty(t,"a",{value:8})}}),wo=Object.isExtensible,So=a(function(){wo(1)})||bo?function(t){return!!M(t)&&(!bo||"ArrayBuffer"!==E(t))&&(!wo||wo(t))}:wo,Eo=!a(function(){return Object.isExtensible(Object.preventExtensions({}))}),Oo=e(function(t){var e=Ct.f,r=!1,n=lt("meta"),o=0,i=function(t){e(t,n,{value:{objectID:"O"+o++,weakData:{}}})},a=t.exports={enable:function(){a.enable=function(){},r=!0;var t=Oe.f,e=b([].splice),o={};o[n]=1,t(o).length&&(Oe.f=function(r){for(var o=t(r),i=0,a=o.length;ii;i++)if((u=y(t[i]))&&U(Po,u))return u;return new Ro(!1)}n=Dn(t,o)}for(s=h?t.next:n.next;!(c=f(s,n)).done;){try{u=y(c.value)}catch(t){Tn(n,"throw",t)}if("object"==typeof u&&u&&U(Po,u))return u}return new Ro(!1)},jo=TypeError,ko=function(t,e){if(U(e,t))return t;throw new jo("Incorrect invocation")},Io=function(t,e,r){var n,o;return dn&&T(n=e.constructor)&&n!==r&&M(o=n.prototype)&&o!==r.prototype&&dn(t,o),t},To=function(t,e,r){var n=-1!==t.indexOf("Map"),o=-1!==t.indexOf("Weak"),u=n?"set":"add",s=i[t],c=s&&s.prototype,f=s,l={},h=function(t){var e=b(c[t]);ie(c,t,"add"===t?function(t){return e(this,0===t?0:t),this}:"delete"===t?function(t){return!(o&&!M(t))&&e(this,0===t?0:t)}:"get"===t?function(t){return o&&!M(t)?void 0:e(this,0===t?0:t)}:"has"===t?function(t){return!(o&&!M(t))&&e(this,0===t?0:t)}:function(t,r){return e(this,0===t?0:t,r),this})};if(Ue(t,!T(s)||!(o||c.forEach&&!a(function(){(new s).entries().next()}))))f=r.getConstructor(e,t,n,u),Oo.enable();else if(Ue(t,!0)){var p=new f,v=p[u](o?{}:-0,1)!==p,d=a(function(){p.has(1)}),g=Gn(function(t){new s(t)}),y=!o&&a(function(){for(var t=new s,e=5;e--;)t[u](e,e);return!t.has(-0)});g||((f=e(function(t,e){ko(t,c);var r=Io(new s,t,f);return P(e)||Ao(e,r[u],{that:r,AS_ENTRIES:n}),r})).prototype=c,c.constructor=f),(d||y)&&(h("delete"),h("has"),n&&h("get")),(y||v)&&h(u),o&&c.clear&&delete c.clear}return l[t]=f,Ce({global:!0,constructor:!0,forced:f!==s},l),an(f,t),o||r.setStrong(f,t,n),f},Mo=function(t,e,r){for(var n in e)ie(t,n,e[n],r);return t},Lo=dt("species"),Uo=function(t){var e=L(t);u&&e&&!e[Lo]&&so(e,Lo,{configurable:!0,get:function(){return this}})},No=Oo.fastKey,Co=ne.set,_o=ne.getterFor,Fo={getConstructor:function(t,e,r,n){var o=t(function(t,o){ko(t,i),Co(t,{type:e,index:Ve(null),first:null,last:null,size:0}),u||(t.size=0),P(o)||Ao(o,t[n],{that:t,AS_ENTRIES:r})}),i=o.prototype,a=_o(e),s=function(t,e,r){var n,o,i=a(t),s=c(t,e);return s?s.value=r:(i.last=s={index:o=No(e,!0),key:e,value:r,previous:n=i.last,next:null,removed:!1},i.first||(i.first=s),n&&(n.next=s),u?i.size++:t.size++,"F"!==o&&(i.index[o]=s)),t},c=function(t,e){var r,n=a(t),o=No(e);if("F"!==o)return n.index[o];for(r=n.first;r;r=r.next)if(r.key===e)return r};return Mo(i,{clear:function(){for(var t=a(this),e=t.first;e;)e.removed=!0,e.previous&&(e.previous=e.previous.next=null),e=e.next;t.first=t.last=null,t.index=Ve(null),u?t.size=0:this.size=0},delete:function(t){var e=this,r=a(e),n=c(e,t);if(n){var o=n.next,i=n.previous;delete r.index[n.index],n.removed=!0,i&&(i.next=o),o&&(o.previous=i),r.first===n&&(r.first=o),r.last===n&&(r.last=i),u?r.size--:e.size--}return!!n},forEach:function(t){for(var e,r=a(this),n=ar(t,arguments.length>1?arguments[1]:void 0);e=e?e.next:r.first;)for(n(e.value,e.key,this);e&&e.removed;)e=e.previous},has:function(t){return!!c(this,t)}}),Mo(i,r?{get:function(t){var e=c(this,t);return e&&e.value},set:function(t,e){return s(this,0===t?0:t,e)}}:{add:function(t){return s(this,t=0===t?0:t,t)}}),u&&so(i,"size",{configurable:!0,get:function(){return a(this).size}}),o},setStrong:function(t,e,r){var n=e+" Iterator",o=_o(e),i=_o(n);Rn(t,e,function(t,e){Co(this,{type:n,target:t,state:o(t),kind:e,last:null})},function(){for(var t=i(this),e=t.kind,r=t.last;r&&r.removed;)r=r.previous;return t.target&&(t.last=r=r?r.next:t.state.first)?Pn("keys"===e?r.key:"values"===e?r.value:[r.key,r.value],!1):(t.target=null,Pn(void 0,!0))},r?"entries":"values",!r,!0),Uo(e)}};To("Map",function(t){return function(){return t(this,arguments.length?arguments[0]:void 0)}},Fo);var Bo=Map.prototype,Do={Map:Map,set:b(Bo.set),get:b(Bo.get),has:b(Bo.has),remove:b(Bo.delete),proto:Bo},zo=Do.Map,Wo=Do.has,qo=Do.get,Ho=Do.set,$o=b([].push),Ko=a(function(){return 1!==zo.groupBy("ab",function(t){return t}).get("a").length});Ce({target:"Map",stat:!0,forced:Ko},{groupBy:function(t,e){j(t),J(e);var r=new zo,n=0;return Ao(t,function(t){var o=e(t,n++);Wo(r,o)?$o(qo(r,o),t):Ho(r,o,[t])}),r}});var Go={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0},Vo=Et("span").classList,Yo=Vo&&Vo.constructor&&Vo.constructor.prototype,Xo=Yo===Object.prototype?void 0:Yo,Jo=dt("iterator"),Qo=ro.values,Zo=function(t,e){if(t){if(t[Jo]!==Qo)try{_t(t,Jo,Qo)}catch(e){t[Jo]=Qo}if(an(t,e,!0),Go[e])for(var r in ro)if(t[r]!==ro[r])try{_t(t,r,ro[r])}catch(e){t[r]=ro[r]}}};for(var ti in Go)Zo(i[ti]&&i[ti].prototype,ti);Zo(Xo,"DOMTokenList");var ei=function(t,e,r){return function(n){var o=it(n),i=arguments.length,a=i>1?arguments[1]:void 0,u=void 0!==a,s=u?ar(a,i>2?arguments[2]:void 0):void 0,c=new t,f=0;return Ao(o,function(t){var n=u?s(t,f++):t;r?e(c,kt(n)[0],n[1]):e(c,n)}),c}};Ce({target:"Map",stat:!0,forced:!0},{from:ei(Do.Map,Do.set,!0)});var ri=function(t,e,r){return function(){for(var n=new t,o=arguments.length,i=0;i1?arguments[1]:void 0);return!1!==di(e,function(t,n){if(!r(t,n,e))return!1},!0)}});var gi=Do.Map,yi=Do.set;Ce({target:"Map",proto:!0,real:!0,forced:!0},{filter:function(t){var e=oi(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=new gi;return di(e,function(t,o){r(t,o,e)&&yi(n,o,t)}),n}}),Ce({target:"Map",proto:!0,real:!0,forced:!0},{find:function(t){var e=oi(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=di(e,function(t,n){if(r(t,n,e))return{value:t}},!0);return n&&n.value}}),Ce({target:"Map",proto:!0,real:!0,forced:!0},{findKey:function(t){var e=oi(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=di(e,function(t,n){if(r(t,n,e))return{key:n}},!0);return n&&n.key}}),Ce({target:"Map",proto:!0,real:!0,forced:!0},{includes:function(t){return!0===di(oi(this),function(e){if((r=e)===(n=t)||r!=r&&n!=n)return!0;var r,n},!0)}});var mi=Do.Map;Ce({target:"Map",stat:!0,forced:!0},{keyBy:function(t,e){var r=new(T(this)?this:mi);J(e);var n=J(r.set);return Ao(t,function(t){f(n,r,e(t),t)}),r}}),Ce({target:"Map",proto:!0,real:!0,forced:!0},{keyOf:function(t){var e=di(oi(this),function(e,r){if(e===t)return{key:r}},!0);return e&&e.key}});var bi=Do.Map,wi=Do.set;Ce({target:"Map",proto:!0,real:!0,forced:!0},{mapKeys:function(t){var e=oi(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=new bi;return di(e,function(t,o){wi(n,r(t,o,e),t)}),n}});var Si=Do.Map,Ei=Do.set;Ce({target:"Map",proto:!0,real:!0,forced:!0},{mapValues:function(t){var e=oi(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=new Si;return di(e,function(t,o){Ei(n,o,r(t,o,e))}),n}});var Oi=Do.set;Ce({target:"Map",proto:!0,real:!0,arity:1,forced:!0},{merge:function(t){for(var e=oi(this),r=arguments.length,n=0;n1?arguments[1]:void 0);return!0===di(e,function(t,n){if(r(t,n,e))return!0},!0)}});var Ri=TypeError,Pi=Do.get,Ai=Do.has,ji=Do.set;Ce({target:"Map",proto:!0,real:!0,forced:!0},{update:function(t,e){var r=oi(this),n=arguments.length;J(e);var o=Ai(r,t);if(!o&&n<3)throw new Ri("Updating absent value");var i=o?Pi(r,t):J(n>2?arguments[2]:void 0)(t,r);return ji(r,t,e(i,t,r)),r}});var ki=TypeError,Ii=function(t,e){var r,n=kt(this),o=J(n.get),i=J(n.has),a=J(n.set),u=arguments.length>2?arguments[2]:void 0;if(!T(e)&&!T(u))throw new ki("At least one callback required");return f(i,n,t)?(r=f(o,n,t),T(e)&&(r=e(r),f(a,n,t,r))):T(u)&&(r=u(),f(a,n,t,r)),r};Ce({target:"Map",proto:!0,real:!0,forced:!0},{upsert:Ii}),Ce({target:"Map",proto:!0,real:!0,name:"upsert",forced:!0},{updateOrInsert:Ii});var Ti=b(1..valueOf),Mi="\t\n\v\f\r                 \u2028\u2029\ufeff",Li=b("".replace),Ui=RegExp("^["+Mi+"]+"),Ni=RegExp("(^|[^"+Mi+"])["+Mi+"]+$"),Ci=function(t){return function(e){var r=Wr(j(e));return 1&t&&(r=Li(r,Ui,"")),2&t&&(r=Li(r,Ni,"$1")),r}},_i={start:Ci(1),end:Ci(2),trim:Ci(3)},Fi=Oe.f,Bi=Rt.f,Di=Ct.f,zi=_i.trim,Wi="Number",qi=i[Wi],Hi=qi.prototype,$i=i.TypeError,Ki=b("".slice),Gi=b("".charCodeAt),Vi=Ue(Wi,!qi(" 0o1")||!qi("0b1")||qi("+0x1")),Yi=function(t){var e,r=arguments.length<1?0:qi(function(t){var e=mt(t,"number");return"bigint"==typeof e?e:function(t){var e,r,n,o,i,a,u,s,c=mt(t,"number");if(G(c))throw new $i("Cannot convert a Symbol value to a number");if("string"==typeof c&&c.length>2)if(c=zi(c),43===(e=Gi(c,0))||45===e){if(88===(r=Gi(c,2))||120===r)return NaN}else if(48===e){switch(Gi(c,1)){case 66:case 98:n=2,o=49;break;case 79:case 111:n=8,o=55;break;default:return+c}for(a=(i=Ki(c,2)).length,u=0;uo)return NaN;return parseInt(i,n)}return+c}(e)}(t));return U(Hi,e=this)&&a(function(){Ti(e)})?Io(Object(r),this,Yi):r};Yi.prototype=Hi,Vi&&(Hi.constructor=Yi),Ce({global:!0,constructor:!0,wrap:!0,forced:Vi},{Number:Yi}),Vi&&function(t,e){for(var r,n=u?Fi(e):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,isFinite,isInteger,isNaN,isSafeInteger,parseFloat,parseInt,fromString,range".split(","),o=0;n.length>o;o++)ut(e,r=n[o])&&!ut(t,r)&&Di(t,r,Bi(e,r))}(Yn[Wi],qi),Ce({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{EPSILON:Math.pow(2,-52)});var Xi=i.isFinite;Ce({target:"Number",stat:!0},{isFinite:Number.isFinite||function(t){return"number"==typeof t&&Xi(t)}});var Ji=Math.floor,Qi=Number.isInteger||function(t){return!M(t)&&isFinite(t)&&Ji(t)===t};Ce({target:"Number",stat:!0},{isInteger:Qi}),Ce({target:"Number",stat:!0},{isNaN:function(t){return t!=t}});var Zi=Math.abs;Ce({target:"Number",stat:!0},{isSafeInteger:function(t){return Qi(t)&&Zi(t)<=9007199254740991}}),Ce({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{MAX_SAFE_INTEGER:9007199254740991}),Ce({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{MIN_SAFE_INTEGER:-9007199254740991});var ta=_i.trim,ea=b("".charAt),ra=i.parseFloat,na=i.Symbol,oa=na&&na.iterator,ia=1/ra(Mi+"-0")!=-Infinity||oa&&!a(function(){ra(Object(oa))})?function(t){var e=ta(Wr(t)),r=ra(e);return 0===r&&"-"===ea(e,0)?-0:r}:ra;Ce({target:"Number",stat:!0,forced:Number.parseFloat!==ia},{parseFloat:ia});var aa=_i.trim,ua=i.parseInt,sa=i.Symbol,ca=sa&&sa.iterator,fa=/^[+-]?0x/i,la=b(fa.exec),ha=8!==ua(Mi+"08")||22!==ua(Mi+"0x16")||ca&&!a(function(){ua(Object(ca))})?function(t,e){var r=aa(Wr(t));return ua(r,e>>>0||(la(fa,r)?16:10))}:ua;Ce({target:"Number",stat:!0,forced:Number.parseInt!==ha},{parseInt:ha});var pa=b(v.f),va=b([].push),da=u&&a(function(){var t=Object.create(null);return t[2]=2,!pa(t,2)}),ga=function(t){return function(e){for(var r,n=k(e),o=_e(n),i=da&&null===Qr(n),a=o.length,s=0,c=[];a>s;)r=o[s++],u&&!(i?r in n:pa(n,r))||va(c,t?[r,n[r]]:n[r]);return c}},ya={entries:ga(!0),values:ga(!1)},ma=ya.entries;Ce({target:"Object",stat:!0},{entries:function(t){return ma(t)}}),Ce({target:"Object",stat:!0,sham:!u},{getOwnPropertyDescriptors:function(t){for(var e,r,n=k(t),o=Rt.f,i=Pe(n),a={},u=0;i.length>u;)void 0!==(r=o(n,e=i[u++]))&&Cn(a,e,r);return a}});var ba=a(function(){_e(1)});Ce({target:"Object",stat:!0,forced:ba},{keys:function(t){return _e(it(t))}});var wa=Object.is||function(t,e){return t===e?0!==t||1/t==1/e:t!=t&&e!=e};Ce({target:"Object",stat:!0},{is:wa});var Sa=ya.values;Ce({target:"Object",stat:!0},{values:function(t){return Sa(t)}}),Ce({target:"Object",stat:!0},{hasOwn:ut});var Ea=Function.prototype,Oa=Ea.apply,xa=Ea.call,Ra="object"==typeof Reflect&&Reflect.apply||(s?xa.bind(Oa):function(){return xa.apply(Oa,arguments)}),Pa=!a(function(){Reflect.apply(function(){})});Ce({target:"Reflect",stat:!0,forced:Pa},{apply:function(t,e,r){return Ra(J(t),e,kt(r))}});var Aa=Function,ja=b([].concat),ka=b([].join),Ia={},Ta=s?Aa.bind:function(t){var e=J(this),r=e.prototype,n=vo(arguments,1),o=function(){var r=ja(n,vo(arguments));return this instanceof o?function(t,e,r){if(!ut(Ia,e)){for(var n=[],o=0;ob)","g");return"b"!==t.exec("b").groups.a||"bc"!=="b".replace(t,"$c")}),gs=Oe.f,ys=ne.enforce,ms=dt("match"),bs=i.RegExp,ws=bs.prototype,Ss=i.SyntaxError,Es=b(ws.exec),Os=b("".charAt),xs=b("".replace),Rs=b("".indexOf),Ps=b("".slice),As=/^\?<[^\s\d!#%&*+<=>@^][^\s!#%&*+<=>@^]*>/,js=/a/g,ks=/a/g,Is=new bs(js)!==js,Ts=cs.MISSED_STICKY,Ms=cs.UNSUPPORTED_Y,Ls=u&&(!Is||Ts||ps||ds||a(function(){return ks[ms]=!1,bs(js)!==js||bs(ks)===ks||"/a/i"!==String(bs(js,"i"))}));if(Ue("RegExp",Ls)){for(var Us=function(t,e){var r,n,o,i,a,u,s=U(ws,this),c=es(t),f=void 0===e,l=[],h=t;if(!s&&c&&f&&t.constructor===Us)return t;if((c||U(ws,t))&&(t=t.source,f&&(e=os(h))),t=void 0===t?"":Wr(t),e=void 0===e?"":Wr(e),h=t,ps&&"dotAll"in js&&(n=!!e&&Rs(e,"s")>-1)&&(e=xs(e,/s/g,"")),r=e,Ts&&"sticky"in js&&(o=!!e&&Rs(e,"y")>-1)&&Ms&&(e=xs(e,/y/g,"")),ds&&(i=function(t){for(var e,r=t.length,n=0,o="",i=[],a=Ve(null),u=!1,s=!1,c=0,f="";n<=r;n++){if("\\"===(e=Os(t,n)))e+=Os(t,++n);else if("]"===e)u=!1;else if(!u)switch(!0){case"["===e:u=!0;break;case"("===e:if(o+=e,"?:"===Ps(t,n+1,n+3))continue;Es(As,Ps(t,n+1))&&(n+=2,s=!0),c++;continue;case">"===e&&s:if(""===f||ut(a,f))throw new Ss("Invalid capture group name");a[f]=!0,i[i.length]=[f,c],s=!1,f="";continue}s?f+=e:o+=e}return[o,i]}(t),t=i[0],l=i[1]),a=Io(bs(t,e),s?this:ws,Us),(n||o||l.length)&&(u=ys(a),n&&(u.dotAll=!0,u.raw=Us(function(t){for(var e,r=t.length,n=0,o="",i=!1;n<=r;n++)"\\"!==(e=Os(t,n))?i||"."!==e?("["===e?i=!0:"]"===e&&(i=!1),o+=e):o+="[\\s\\S]":o+=e+Os(t,++n);return o}(t),r)),o&&(u.sticky=!0),l.length&&(u.groups=l)),t!==h)try{_t(a,"source",""===h?"(?:)":h)}catch(t){}return a},Ns=gs(bs),Cs=0;Ns.length>Cs;)ls(Us,bs,Ns[Cs++]);ws.constructor=Us,Us.prototype=ws,ie(i,"RegExp",Us,{constructor:!0})}Uo("RegExp");var _s=zt.PROPER,Fs="toString",Bs=RegExp.prototype,Ds=Bs[Fs];(a(function(){return"/a/b"!==Ds.call({source:"a",flags:"b"})})||_s&&Ds.name!==Fs)&&ie(Bs,Fs,function(){var t=kt(this);return"/"+Wr(t.source)+"/"+Wr(os(t))},{unsafe:!0});var zs=ne.get,Ws=RegExp.prototype,qs=TypeError;u&&ps&&so(Ws,"dotAll",{configurable:!0,get:function(){if(this!==Ws){if("RegExp"===E(this))return!!zs(this).dotAll;throw new qs("Incompatible receiver, RegExp required")}}});var Hs=ne.get,$s=nt("native-string-replace",String.prototype.replace),Ks=RegExp.prototype.exec,Gs=Ks,Vs=b("".charAt),Ys=b("".indexOf),Xs=b("".replace),Js=b("".slice),Qs=function(){var t=/a/,e=/b*/g;return f(Ks,t,"a"),f(Ks,e,"a"),0!==t.lastIndex||0!==e.lastIndex}(),Zs=cs.BROKEN_CARET,tc=void 0!==/()??/.exec("")[1];(Qs||tc||Zs||ps||ds)&&(Gs=function(t){var e,r,n,o,i,a,u,s=this,c=Hs(s),l=Wr(t),h=c.raw;if(h)return h.lastIndex=s.lastIndex,e=f(Gs,h,l),s.lastIndex=h.lastIndex,e;var p=c.groups,v=Zs&&s.sticky,d=f(rs,s),g=s.source,y=0,m=l;if(v&&(d=Xs(d,"y",""),-1===Ys(d,"g")&&(d+="g"),m=Js(l,s.lastIndex),s.lastIndex>0&&(!s.multiline||s.multiline&&"\n"!==Vs(l,s.lastIndex-1))&&(g="(?: "+g+")",m=" "+m,y++),r=new RegExp("^(?:"+g+")",d)),tc&&(r=new RegExp("^"+g+"$(?!\\s)",d)),Qs&&(n=s.lastIndex),o=f(Ks,v?r:s,m),v?o?(o.input=Js(o.input,y),o[0]=Js(o[0],y),o.index=s.lastIndex,s.lastIndex+=o[0].length):s.lastIndex=0:Qs&&o&&(s.lastIndex=s.global?o.index+o[0].length:n),tc&&o&&o.length>1&&f($s,o[0],r,function(){for(i=1;i]*>)/g,Oc=/\$([$&'`]|\d{1,2})/g,xc=function(t,e,r,n,o,i){var a=r+t.length,u=n.length,s=Oc;return void 0!==o&&(o=it(o),s=Ec),wc(i,s,function(i,s){var c;switch(bc(s,0)){case"$":return"$";case"&":return t;case"`":return Sc(e,0,r);case"'":return Sc(e,a);case"<":c=o[Sc(s,1,-1)];break;default:var f=+s;if(0===f)return i;if(f>u){var l=mc(f/10);return 0===l?i:l<=u?void 0===n[l-1]?bc(s,1):n[l-1]+bc(s,1):i}c=n[f-1]}return void 0===c?"":c})},Rc=dt("replace"),Pc=Math.max,Ac=Math.min,jc=b([].concat),kc=b([].push),Ic=b("".indexOf),Tc=b("".slice),Mc="$0"==="a".replace(/./,"$0"),Lc=!!/./[Rc]&&""===/./[Rc]("a","$0"),Uc=!a(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")});pc("replace",function(t,e,r){var n=Lc?"$":"$0";return[function(t,r){var n=j(this),o=P(t)?void 0:Q(t,Rc);return o?f(o,t,n,r):f(e,Wr(n),t,r)},function(t,o){var i=kt(this),a=Wr(t);if("string"==typeof o&&-1===Ic(o,n)&&-1===Ic(o,"$<")){var u=r(e,i,a,o);if(u.done)return u.value}var s=T(o);s||(o=Wr(o));var c,f=i.global;f&&(c=i.unicode,i.lastIndex=0);for(var l,h=[];null!==(l=yc(i,a))&&(kc(h,l),f);)""===Wr(l[0])&&(i.lastIndex=dc(a,ve(i.lastIndex),c));for(var p,v="",d=0,g=0;g=d&&(v+=Tc(a,d,b)+y,d=b+m.length)}return v+Tc(a,d)}]},!Uc||!Mc||Lc),pc("search",function(t,e,r){return[function(e){var r=j(this),n=P(e)?void 0:Q(e,t);return n?f(n,e,r):new RegExp(e)[t](Wr(r))},function(t){var n=kt(this),o=Wr(t),i=r(e,n,o);if(i.done)return i.value;var a=n.lastIndex;wa(a,0)||(n.lastIndex=0);var u=yc(n,o);return wa(n.lastIndex,a)||(n.lastIndex=a),null===u?-1:u.index}]});var Nc=dt("species"),Cc=function(t,e){var r,n=kt(t).constructor;return void 0===n||P(r=kt(n)[Nc])?e:La(r)},_c=cs.UNSUPPORTED_Y,Fc=Math.min,Bc=b([].push),Dc=b("".slice),zc=!a(function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var r="ab".split(t);return 2!==r.length||"a"!==r[0]||"b"!==r[1]}),Wc="c"==="abbc".split(/(b)*/)[1]||4!=="test".split(/(?:)/,-1).length||2!=="ab".split(/(?:ab)*/).length||4!==".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length;pc("split",function(t,e,r){var n="0".split(void 0,0).length?function(t,r){return void 0===t&&0===r?[]:f(e,this,t,r)}:e;return[function(e,r){var o=j(this),i=P(e)?void 0:Q(e,t);return i?f(i,e,o,r):f(n,Wr(o),e,r)},function(t,o){var i=kt(this),a=Wr(t);if(!Wc){var u=r(n,i,a,o,n!==e);if(u.done)return u.value}var s=Cc(i,RegExp),c=i.unicode,f=new s(_c?"^(?:"+i.source+")":i,(i.ignoreCase?"i":"")+(i.multiline?"m":"")+(i.unicode?"u":"")+(_c?"g":"y")),l=void 0===o?4294967295:o>>>0;if(0===l)return[];if(0===a.length)return null===yc(f,a)?[a]:[];for(var h=0,p=0,v=[];p0;(n>>>=1)&&(e+=e))1&n&&(r+=e);return r},Kc=b($c),Gc=b("".slice),Vc=Math.ceil,Yc=function(t){return function(e,r,n){var o,i,a=Wr(j(e)),u=ve(r),s=a.length,c=void 0===n?" ":Wr(n);return u<=s||""===c?a:((i=Kc(c,Vc((o=u-s)/c.length))).length>o&&(i=Gc(i,0,o)),t?a+i:i+a)}},Xc={start:Yc(!1),end:Yc(!0)},Jc=Xc.start,Qc=Array,Zc=RegExp.escape,tf=b("".charAt),ef=b("".charCodeAt),rf=b(1.1.toString),nf=b([].join),of=/^[0-9a-z]/i,af=/^[$()*+./?[\\\]^{|}]/,uf=RegExp("^[!\"#%&',\\-:;<=>@`~"+Mi+"]"),sf=b(of.exec),cf={"\t":"t","\n":"n","\v":"v","\f":"f","\r":"r"},ff=function(t){var e=rf(ef(t,0),16);return e.length<3?"\\x"+Jc(e,2,"0"):"\\u"+Jc(e,4,"0")},lf=!Zc||"\\x61b"!==Zc("ab");Ce({target:"RegExp",stat:!0,forced:lf},{escape:function(t){!function(t){if("string"==typeof t)return t;throw new qc("Argument is not a string")}(t);for(var e=t.length,r=Qc(e),n=0;n=56320||n+1>=e||56320!=(64512&ef(t,n+1))?r[n]=ff(o):(r[n]=o,r[++n]=tf(t,n))}}return nf(r,"")}}),To("Set",function(t){return function(){return t(this,arguments.length?arguments[0]:void 0)}},Fo);var hf=Set.prototype,pf={Set:Set,add:b(hf.add),has:b(hf.has),remove:b(hf.delete),proto:hf},vf=pf.has,df=function(t){return vf(t),t},gf=pf.Set,yf=pf.proto,mf=b(yf.forEach),bf=b(yf.keys),wf=bf(new gf).next,Sf=function(t,e,r){return r?ci({iterator:bf(t),next:wf},e):mf(t,e)},Ef=pf.Set,Of=pf.add,xf=function(t){var e=new Ef;return Sf(t,function(t){Of(e,t)}),e},Rf=ln(pf.proto,"size","get")||function(t){return t.size},Pf="Invalid size",Af=RangeError,jf=TypeError,kf=Math.max,If=function(t,e){this.set=t,this.size=kf(e,0),this.has=J(t.has),this.keys=J(t.keys)};If.prototype={getIterator:function(){return{iterator:t=kt(f(this.keys,this.set)),next:t.next,done:!1};var t},includes:function(t){return f(this.has,this.set,t)}};var Tf=function(t){kt(t);var e=+t.size;if(e!=e)throw new jf(Pf);var r=ce(e);if(r<0)throw new Af(Pf);return new If(t,r)},Mf=pf.has,Lf=pf.remove,Uf=function(t){var e=df(this),r=Tf(t),n=xf(e);return Rf(e)<=r.size?Sf(e,function(t){r.includes(t)&&Lf(n,t)}):ci(r.getIterator(),function(t){Mf(e,t)&&Lf(n,t)}),n},Nf=function(t){return{size:t,has:function(){return!1},keys:function(){return{next:function(){return{done:!0}}}}}},Cf=function(t){var e=L("Set");try{(new e)[t](Nf(0));try{return(new e)[t](Nf(-1)),!1}catch(t){return!0}}catch(t){return!1}};Ce({target:"Set",proto:!0,real:!0,forced:!Cf("difference")},{difference:Uf});var _f=pf.Set,Ff=pf.add,Bf=pf.has,Df=function(t){var e=df(this),r=Tf(t),n=new _f;return Rf(e)>r.size?ci(r.getIterator(),function(t){Bf(e,t)&&Ff(n,t)}):Sf(e,function(t){r.includes(t)&&Ff(n,t)}),n},zf=!Cf("intersection")||a(function(){return"3,2"!==String(Array.from(new Set([1,2,3]).intersection(new Set([3,2]))))});Ce({target:"Set",proto:!0,real:!0,forced:zf},{intersection:Df});var Wf=pf.has,qf=function(t){var e=df(this),r=Tf(t);if(Rf(e)<=r.size)return!1!==Sf(e,function(t){if(r.includes(t))return!1},!0);var n=r.getIterator();return!1!==ci(n,function(t){if(Wf(e,t))return Tn(n,"normal",!1)})};Ce({target:"Set",proto:!0,real:!0,forced:!Cf("isDisjointFrom")},{isDisjointFrom:qf});var Hf=function(t){var e=df(this),r=Tf(t);return!(Rf(e)>r.size)&&!1!==Sf(e,function(t){if(!r.includes(t))return!1},!0)};Ce({target:"Set",proto:!0,real:!0,forced:!Cf("isSubsetOf")},{isSubsetOf:Hf});var $f=pf.has,Kf=function(t){var e=df(this),r=Tf(t);if(Rf(e)1?arguments[1]:void 0);return!1!==Sf(e,function(t){if(!r(t,t,e))return!1},!0)}});var el=dt("iterator"),rl=Object,nl=L("Set"),ol=function(t){return function(t){return M(t)&&"number"==typeof t.size&&T(t.has)&&T(t.keys)}(t)?t:function(t){if(P(t))return!1;var e=rl(t);return void 0!==e[el]||"@@iterator"in e||ut(un,pr(e))}(t)?new nl(t):t};Ce({target:"Set",proto:!0,real:!0,forced:!0},{difference:function(t){return f(Uf,this,ol(t))}});var il=pf.Set,al=pf.add;Ce({target:"Set",proto:!0,real:!0,forced:!0},{filter:function(t){var e=df(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=new il;return Sf(e,function(t){r(t,t,e)&&al(n,t)}),n}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{find:function(t){var e=df(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=Sf(e,function(t){if(r(t,t,e))return{value:t}},!0);return n&&n.value}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{intersection:function(t){return f(Df,this,ol(t))}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{isDisjointFrom:function(t){return f(qf,this,ol(t))}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{isSubsetOf:function(t){return f(Hf,this,ol(t))}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{isSupersetOf:function(t){return f(Kf,this,ol(t))}});var ul=b([].join),sl=b([].push);Ce({target:"Set",proto:!0,real:!0,forced:!0},{join:function(t){var e=df(this),r=void 0===t?",":Wr(t),n=[];return Sf(e,function(t){sl(n,t)}),ul(n,r)}});var cl=pf.Set,fl=pf.add;Ce({target:"Set",proto:!0,real:!0,forced:!0},{map:function(t){var e=df(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=new cl;return Sf(e,function(t){fl(n,r(t,t,e))}),n}});var ll=TypeError;Ce({target:"Set",proto:!0,real:!0,forced:!0},{reduce:function(t){var e=df(this),r=arguments.length<2,n=r?void 0:arguments[1];if(J(t),Sf(e,function(o){r?(r=!1,n=o):n=t(n,o,o,e)}),r)throw new ll("Reduce of empty set with no initial value");return n}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{some:function(t){var e=df(this),r=ar(t,arguments.length>1?arguments[1]:void 0);return!0===Sf(e,function(t){if(r(t,t,e))return!0},!0)}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{symmetricDifference:function(t){return f(Xf,this,ol(t))}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{union:function(t){return f(Qf,this,ol(t))}});var hl=dt("species"),pl=dt("isConcatSpreadable"),vl=W>=51||!a(function(){var t=[];return t[pl]=!1,t.concat()[0]!==t}),dl=function(t){if(!M(t))return!1;var e=t[pl];return void 0!==e?!!e:ur(t)},gl=!(vl&&(W>=51||!a(function(){var t=[];return(t.constructor={})[hl]=function(){return{foo:1}},1!==t.concat(Boolean).foo})));Ce({target:"Array",proto:!0,arity:1,forced:gl},{concat:function(t){var e,r,n,o,i,a=it(this),u=xr(a,0),s=0;for(e=-1,n=arguments.length;e1?arguments[1]:void 0,n=e.length,o=void 0===r?n:ip(ve(r),n),i=Wr(t);return op(e,o-i.length,o)===i}}),Ze("String","endsWith");var sp=RangeError,cp=String.fromCharCode,fp=String.fromCodePoint,lp=b([].join);Ce({target:"String",stat:!0,arity:1,forced:!!fp&&1!==fp.length},{fromCodePoint:function(t){for(var e,r=[],n=arguments.length,o=0;n>o;){if(e=+arguments[o++],he(e,1114111)!==e)throw new sp(e+" is not a valid code point");r[o]=e<65536?cp(e):cp(55296+((e-=65536)>>10),e%1024+56320)}return lp(r,"")}});var hp=b("".indexOf);Ce({target:"String",proto:!0,forced:!rp("includes")},{includes:function(t){return!!~hp(Wr(j(this)),Wr(tp(t)),arguments.length>1?arguments[1]:void 0)}}),Ze("String","includes"),b(un.String);var pp=/Version\/10(?:\.\d+){1,2}(?: [\w./]+)?(?: Mobile\/\w+)? Safari\//.test(_),vp=Xc.start;Ce({target:"String",proto:!0,forced:pp},{padStart:function(t){return vp(this,t,arguments.length>1?arguments[1]:void 0)}}),Ze("String","padStart");var dp=Xc.end;Ce({target:"String",proto:!0,forced:pp},{padEnd:function(t){return dp(this,t,arguments.length>1?arguments[1]:void 0)}}),Ze("String","padEnd");var gp=b([].push),yp=b([].join);Ce({target:"String",stat:!0},{raw:function(t){var e=k(it(t).raw),r=de(e);if(!r)return"";for(var n=arguments.length,o=[],i=0;;){if(gp(o,Wr(e[i++])),i===r)return yp(o,"");i1?arguments[1]:void 0,e.length)),n=Wr(t);return bp(e,r,r+n.length)===n}}),Ze("String","startsWith");var Op=zt.PROPER,xp=function(t){return a(function(){return!!Mi[t]()||"​…᠎"!=="​…᠎"[t]()||Op&&Mi[t].name!==t})},Rp=_i.start,Pp=xp("trimStart")?function(){return Rp(this)}:"".trimStart;Ce({target:"String",proto:!0,name:"trimStart",forced:"".trimLeft!==Pp},{trimLeft:Pp}),Ce({target:"String",proto:!0,name:"trimStart",forced:"".trimStart!==Pp},{trimStart:Pp}),Ze("String","trimLeft");var Ap=_i.end,jp=xp("trimEnd")?function(){return Ap(this)}:"".trimEnd;Ce({target:"String",proto:!0,name:"trimEnd",forced:"".trimRight!==jp},{trimRight:jp}),Ce({target:"String",proto:!0,name:"trimEnd",forced:"".trimEnd!==jp},{trimEnd:jp}),Ze("String","trimRight");var kp=Object.getOwnPropertyDescriptor,Ip=function(t){if(!u)return i[t];var e=kp(i,t);return e&&e.value},Tp=dt("iterator"),Mp=!a(function(){var t=new URL("b?a=1&b=2&c=3","https://a"),e=t.searchParams,r=new URLSearchParams("a=1&a=2&b=3"),n="";return t.pathname="c%20d",e.forEach(function(t,r){e.delete("b"),n+=r+t}),r.delete("a",2),r.delete("b",void 0),!e.size&&!u||!e.sort||"https://a/c%20d?a=1&c=3"!==t.href||"3"!==e.get("c")||"a=1"!==String(new URLSearchParams("?a=1"))||!e[Tp]||"a"!==new URL("https://a@b").username||"b"!==new URLSearchParams(new URLSearchParams("a=b")).get("a")||"xn--e1aybc"!==new URL("https://тест").host||"#%D0%B1"!==new URL("https://a#б").hash||"a1c3"!==n||"x"!==new URL("https://x",void 0).host}),Lp=TypeError,Up=function(t,e){if(t0;)t[o]=t[--o];o!==i++&&(t[o]=n)}else for(var a=Np(r/2),u=Cp(vo(t,0,a),e),s=Cp(vo(t,a),e),c=u.length,f=s.length,l=0,h=0;l0&&0!=(t&r);r>>=1)e++;return e},pv=function(t){var e=null;switch(t.length){case 1:e=t[0];break;case 2:e=(31&t[0])<<6|63&t[1];break;case 3:e=(15&t[0])<<12|(63&t[1])<<6|63&t[2];break;case 4:e=(7&t[0])<<18|(63&t[1])<<12|(63&t[2])<<6|63&t[3]}return e>1114111?null:e},vv=function(t){for(var e=(t=nv(t,cv," ")).length,r="",n=0;ne){r+="%",n++;continue}var i=lv(t,n+1);if(i!=i){r+=o,n++;continue}n+=2;var a=hv(i);if(0===a)o=Jp(i);else{if(1===a||a>4){r+="�",n++;continue}for(var u=[i],s=1;se||"%"!==tv(t,n));){var c=lv(t,n+1);if(c!=c){n+=3;break}if(c>191||c<128)break;rv(u,c),n+=2,s++}if(u.length!==a){r+="�";continue}var f=pv(u);null===f?r+="�":o=Qp(f)}}r+=o,n++}return r},dv=/[!'()~]|%20/g,gv={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"},yv=function(t){return gv[t]},mv=function(t){return nv(Xp(t),dv,yv)},bv=fn(function(t,e){zp(this,{type:Dp,target:Wp(t).entries,index:0,kind:e})},Bp,function(){var t=qp(this),e=t.target,r=t.index++;if(!e||r>=e.length)return t.target=null,Pn(void 0,!0);var n=e[r];switch(t.kind){case"keys":return Pn(n.key,!1);case"values":return Pn(n.value,!1)}return Pn([n.key,n.value],!1)},!0),wv=function(t){this.entries=[],this.url=null,void 0!==t&&(M(t)?this.parseObject(t):this.parseQuery("string"==typeof t?"?"===tv(t,0)?uv(t,1):t:Wr(t)))};wv.prototype={type:Bp,bindURL:function(t){this.url=t,this.update()},parseObject:function(t){var e,r,n,o,i,a,u,s=this.entries,c=Fn(t);if(c)for(r=(e=Dn(t,c)).next;!(n=f(r,e)).done;){if(o=Dn(kt(n.value)),(a=f(i=o.next,o)).done||(u=f(i,o)).done||!f(i,o).done)throw new Yp("Expected sequence with length 2");rv(s,{key:Wr(a.value),value:Wr(u.value)})}else for(var l in t)ut(t,l)&&rv(s,{key:l,value:Wr(t[l])})},parseQuery:function(t){if(t)for(var e,r,n=this.entries,o=av(t,"&"),i=0;i0?arguments[0]:void 0));u||(this.size=t.entries.length)},Ev=Sv.prototype;if(Mo(Ev,{append:function(t,e){var r=Wp(this);Up(arguments.length,2),rv(r.entries,{key:Wr(t),value:Wr(e)}),u||this.length++,r.updateURL()},delete:function(t){for(var e=Wp(this),r=Up(arguments.length,1),n=e.entries,o=Wr(t),i=r<2?void 0:arguments[1],a=void 0===i?i:Wr(i),s=0;se.key?1:-1}),t.updateURL()},forEach:function(t){for(var e,r=Wp(this).entries,n=ar(t,arguments.length>1?arguments[1]:void 0),o=0;o1?Rv(arguments[1]):{})}}),T($p)){var Pv=function(t){return ko(this,Gp),new $p(t,arguments.length>1?Rv(arguments[1]):{})};Gp.constructor=Pv,Pv.prototype=Gp,Ce({global:!0,constructor:!0,dontCallGetSet:!0,forced:!0},{Request:Pv})}}var Av={URLSearchParams:Sv,getState:Wp},jv=URLSearchParams,kv=jv.prototype,Iv=b(kv.append),Tv=b(kv.delete),Mv=b(kv.forEach),Lv=b([].push),Uv=new jv("a=1&a=2&b=3");Uv.delete("a",1),Uv.delete("b",void 0),Uv+""!="a=2"&&ie(kv,"delete",function(t){var e=arguments.length,r=e<2?void 0:arguments[1];if(e&&void 0===r)return Tv(this,t);var n=[];Mv(this,function(t,e){Lv(n,{key:e,value:t})}),Up(e,1);for(var o,i=Wr(t),a=Wr(r),u=0,s=0,c=!1,f=n.length;uo;)for(var s,c=R(arguments[o++]),l=i?$v(_e(c),i(c)):_e(c),h=l.length,p=0;h>p;)s=l[p++],u&&!f(a,c,s)||(r[s]=c[s]);return r}:qv,Gv=2147483647,Vv=/[^\0-\u007E]/,Yv=/[.\u3002\uFF0E\uFF61]/g,Xv="Overflow: input needs wider integers to process",Jv=RangeError,Qv=b(Yv.exec),Zv=Math.floor,td=String.fromCharCode,ed=b("".charCodeAt),rd=b([].join),nd=b([].push),od=b("".replace),id=b("".split),ad=b("".toLowerCase),ud=function(t){return t+22+75*(t<26)},sd=function(t,e,r){var n=0;for(t=r?Zv(t/700):t>>1,t+=Zv(t/e);t>455;)t=Zv(t/35),n+=36;return Zv(n+36*t/(t+38))},cd=function(t){var e=[];t=function(t){for(var e=[],r=0,n=t.length;r=55296&&o<=56319&&r=i&&nZv((Gv-a)/l))throw new Jv(Xv);for(a+=(f-i)*l,i=f,r=0;rGv)throw new Jv(Xv);if(n===i){for(var h=a,p=36;;){var v=p<=u?1:p>=u+26?26:p-u;if(h?@[\\\]^|]/,qd=/[\0\t\n\r #/:<>?@[\\\]^|]/,Hd=/^[\u0000-\u0020]+/,$d=/(^|[^\u0000-\u0020])[\u0000-\u0020]+$/,Kd=/[\t\n\r]/g,Gd=function(t){var e,r,n,o;if("number"==typeof t){for(e=[],r=0;r<4;r++)Td(e,t%256),t=md(t/256);return Ed(e,".")}if("object"==typeof t){for(e="",n=function(t){for(var e=null,r=1,n=null,o=0,i=0;i<8;i++)0!==t[i]?(o>r&&(e=n,r=o),n=null,o=0):(null===n&&(n=i),++o);return o>r?n:e}(t),r=0;r<8;r++)o&&0===t[r]||(o&&(o=!1),n===r?(e+=r?":":"::",o=!0):(e+=Od(t[r],16),r<7&&(e+=":")));return"["+e+"]"}return t},Vd={},Yd=Kv({},Vd,{" ":1,'"':1,"<":1,">":1,"`":1}),Xd=Kv({},Yd,{"#":1,"?":1,"{":1,"}":1}),Jd=Kv({},Xd,{"/":1,":":1,";":1,"=":1,"@":1,"[":1,"\\":1,"]":1,"^":1,"|":1}),Qd=function(t,e){var r=fd(t,0);return r>32&&r<127&&!ut(e,t)?t:encodeURIComponent(t)},Zd={ftp:21,file:null,http:80,https:443,ws:80,wss:443},tg=function(t,e){var r;return 2===t.length&&Sd(Nd,wd(t,0))&&(":"===(r=wd(t,1))||!e&&"|"===r)},eg=function(t){var e;return t.length>1&&tg(kd(t,0,2))&&(2===t.length||"/"===(e=wd(t,2))||"\\"===e||"?"===e||"#"===e)},rg=function(t){return"."===t||"%2e"===Id(t)},ng={},og={},ig={},ag={},ug={},sg={},cg={},fg={},lg={},hg={},pg={},vg={},dg={},gg={},yg={},mg={},bg={},wg={},Sg={},Eg={},Og={},xg=function(t,e,r){var n,o,i,a=Wr(t);if(e){if(o=this.parse(a))throw new gd(o);this.searchParams=null}else{if(void 0!==r&&(n=new xg(r,!0)),o=this.parse(a,null,n))throw new gd(o);(i=vd(new pd)).bindURL(this),this.searchParams=i}};xg.prototype={type:"URL",parse:function(t,e,r){var n,o,i,a,u,s=this,c=e||ng,f=0,l="",h=!1,p=!1,v=!1;for(t=Wr(t),e||(s.scheme="",s.username="",s.password="",s.host=null,s.port=null,s.path=[],s.query=null,s.fragment=null,s.cannotBeABaseURL=!1,t=Pd(t,Hd,""),t=Pd(t,$d,"$1")),t=Pd(t,Kd,""),n=Wn(t);f<=n.length;){switch(o=n[f],c){case ng:if(!o||!Sd(Nd,o)){if(e)return Md;c=ig;continue}l+=Id(o),c=og;break;case og:if(o&&(Sd(Cd,o)||"+"===o||"-"===o||"."===o))l+=Id(o);else{if(":"!==o){if(e)return Md;l="",c=ig,f=0;continue}if(e&&(s.isSpecial()!==ut(Zd,l)||"file"===l&&(s.includesCredentials()||null!==s.port)||"file"===s.scheme&&!s.host))return;if(s.scheme=l,e)return void(s.isSpecial()&&Zd[s.scheme]===s.port&&(s.port=null));l="","file"===s.scheme?c=gg:s.isSpecial()&&r&&r.scheme===s.scheme?c=ag:s.isSpecial()?c=fg:"/"===n[f+1]?(c=ug,f++):(s.cannotBeABaseURL=!0,Rd(s.path,""),c=Sg)}break;case ig:if(!r||r.cannotBeABaseURL&&"#"!==o)return Md;if(r.cannotBeABaseURL&&"#"===o){s.scheme=r.scheme,s.path=vo(r.path),s.query=r.query,s.fragment="",s.cannotBeABaseURL=!0,c=Og;break}c="file"===r.scheme?gg:sg;continue;case ag:if("/"!==o||"/"!==n[f+1]){c=sg;continue}c=lg,f++;break;case ug:if("/"===o){c=hg;break}c=wg;continue;case sg:if(s.scheme=r.scheme,o===Wv)s.username=r.username,s.password=r.password,s.host=r.host,s.port=r.port,s.path=vo(r.path),s.query=r.query;else if("/"===o||"\\"===o&&s.isSpecial())c=cg;else if("?"===o)s.username=r.username,s.password=r.password,s.host=r.host,s.port=r.port,s.path=vo(r.path),s.query="",c=Eg;else{if("#"!==o){s.username=r.username,s.password=r.password,s.host=r.host,s.port=r.port,s.path=vo(r.path),s.path.length--,c=wg;continue}s.username=r.username,s.password=r.password,s.host=r.host,s.port=r.port,s.path=vo(r.path),s.query=r.query,s.fragment="",c=Og}break;case cg:if(!s.isSpecial()||"/"!==o&&"\\"!==o){if("/"!==o){s.username=r.username,s.password=r.password,s.host=r.host,s.port=r.port,c=wg;continue}c=hg}else c=lg;break;case fg:if(c=lg,"/"!==o||"/"!==wd(l,f+1))continue;f++;break;case lg:if("/"!==o&&"\\"!==o){c=hg;continue}break;case hg:if("@"===o){h&&(l="%40"+l),h=!0,i=Wn(l);for(var d=0;d65535)return Ud;s.port=s.isSpecial()&&m===Zd[s.scheme]?null:m,l=""}if(e)return;c=bg;continue}return Ud}l+=o;break;case gg:if(s.scheme="file","/"===o||"\\"===o)c=yg;else{if(!r||"file"!==r.scheme){c=wg;continue}switch(o){case Wv:s.host=r.host,s.path=vo(r.path),s.query=r.query;break;case"?":s.host=r.host,s.path=vo(r.path),s.query="",c=Eg;break;case"#":s.host=r.host,s.path=vo(r.path),s.query=r.query,s.fragment="",c=Og;break;default:eg(Ed(vo(n,f),""))||(s.host=r.host,s.path=vo(r.path),s.shortenPath()),c=wg;continue}}break;case yg:if("/"===o||"\\"===o){c=mg;break}r&&"file"===r.scheme&&!eg(Ed(vo(n,f),""))&&(tg(r.path[0],!0)?Rd(s.path,r.path[0]):s.host=r.host),c=wg;continue;case mg:if(o===Wv||"/"===o||"\\"===o||"?"===o||"#"===o){if(!e&&tg(l))c=wg;else if(""===l){if(s.host="",e)return;c=bg}else{if(a=s.parseHost(l))return a;if("localhost"===s.host&&(s.host=""),e)return;l="",c=bg}continue}l+=o;break;case bg:if(s.isSpecial()){if(c=wg,"/"!==o&&"\\"!==o)continue}else if(e||"?"!==o)if(e||"#"!==o){if(o!==Wv&&(c=wg,"/"!==o))continue}else s.fragment="",c=Og;else s.query="",c=Eg;break;case wg:if(o===Wv||"/"===o||"\\"===o&&s.isSpecial()||!e&&("?"===o||"#"===o)){if(".."===(u=Id(u=l))||"%2e."===u||".%2e"===u||"%2e%2e"===u?(s.shortenPath(),"/"===o||"\\"===o&&s.isSpecial()||Rd(s.path,"")):rg(l)?"/"===o||"\\"===o&&s.isSpecial()||Rd(s.path,""):("file"===s.scheme&&!s.path.length&&tg(l)&&(s.host&&(s.host=""),l=wd(l,0)+":"),Rd(s.path,l)),l="","file"===s.scheme&&(o===Wv||"?"===o||"#"===o))for(;s.path.length>1&&""===s.path[0];)Ad(s.path);"?"===o?(s.query="",c=Eg):"#"===o&&(s.fragment="",c=Og)}else l+=Qd(o,Xd);break;case Sg:"?"===o?(s.query="",c=Eg):"#"===o?(s.fragment="",c=Og):o!==Wv&&(s.path[0]+=Qd(o,Vd));break;case Eg:e||"#"!==o?o!==Wv&&("'"===o&&s.isSpecial()?s.query+="%27":s.query+="#"===o?"%23":Qd(o,Vd)):(s.fragment="",c=Og);break;case Og:o!==Wv&&(s.fragment+=Qd(o,Yd))}f++}},parseHost:function(t){var e,r,n;if("["===wd(t,0)){if("]"!==wd(t,t.length-1))return Ld;if(e=function(t){var e,r,n,o,i,a,u,s=[0,0,0,0,0,0,0,0],c=0,f=null,l=0,h=function(){return wd(t,l)};if(":"===h()){if(":"!==wd(t,1))return;l+=2,f=++c}for(;h();){if(8===c)return;if(":"!==h()){for(e=r=0;r<4&&Sd(zd,h());)e=16*e+yd(h(),16),l++,r++;if("."===h()){if(0===r)return;if(l-=r,c>6)return;for(n=0;h();){if(o=null,n>0){if(!("."===h()&&n<4))return;l++}if(!Sd(_d,h()))return;for(;Sd(_d,h());){if(i=yd(h(),10),null===o)o=i;else{if(0===o)return;o=10*o+i}if(o>255)return;l++}s[c]=256*s[c]+o,2!=++n&&4!==n||c++}if(4!==n)return;break}if(":"===h()){if(l++,!h())return}else if(h())return;s[c++]=e}else{if(null!==f)return;l++,f=++c}}if(null!==f)for(a=c-f,c=7;0!==c&&a>0;)u=s[c],s[c--]=s[f+a-1],s[f+--a]=u;else if(8!==c)return;return s}(kd(t,1,-1)),!e)return Ld;this.host=e}else if(this.isSpecial()){if(t=function(t){var e,r,n=[],o=id(od(ad(t),Yv,"."),".");for(e=0;e4)return t;for(r=[],n=0;n1&&"0"===wd(o,0)&&(i=Sd(Fd,o)?16:8,o=kd(o,8===i?1:2)),""===o)a=0;else{if(!Sd(10===i?Dd:8===i?Bd:zd,o))return t;a=yd(o,i)}Rd(r,a)}for(n=0;n=bd(256,5-e))return null}else if(a>255)return null;for(u=xd(r),n=0;n1?arguments[1]:void 0,n=ld(e,new xg(t,!1,r));u||(e.href=n.serialize(),e.origin=n.getOrigin(),e.protocol=n.getProtocol(),e.username=n.getUsername(),e.password=n.getPassword(),e.host=n.getHost(),e.hostname=n.getHostname(),e.port=n.getPort(),e.pathname=n.getPathname(),e.search=n.getSearch(),e.searchParams=n.getSearchParams(),e.hash=n.getHash())},Pg=Rg.prototype,Ag=function(t,e){return{get:function(){return hd(this)[t]()},set:e&&function(t){return hd(this)[e](t)},configurable:!0,enumerable:!0}};if(u&&(so(Pg,"href",Ag("serialize","setHref")),so(Pg,"origin",Ag("getOrigin")),so(Pg,"protocol",Ag("getProtocol","setProtocol")),so(Pg,"username",Ag("getUsername","setUsername")),so(Pg,"password",Ag("getPassword","setPassword")),so(Pg,"host",Ag("getHost","setHost")),so(Pg,"hostname",Ag("getHostname","setHostname")),so(Pg,"port",Ag("getPort","setPort")),so(Pg,"pathname",Ag("getPathname","setPathname")),so(Pg,"search",Ag("getSearch","setSearch")),so(Pg,"searchParams",Ag("getSearchParams")),so(Pg,"hash",Ag("getHash","setHash"))),ie(Pg,"toJSON",function(){return hd(this).serialize()},{enumerable:!0}),ie(Pg,"toString",function(){return hd(this).serialize()},{enumerable:!0}),dd){var jg=dd.createObjectURL,kg=dd.revokeObjectURL;jg&&ie(Rg,"createObjectURL",ar(jg,dd)),kg&&ie(Rg,"revokeObjectURL",ar(kg,dd))}an(Rg,"URL"),Ce({global:!0,constructor:!0,forced:!Mp,sham:!u},{URL:Rg});var Ig=L("URL"),Tg=Mp&&a(function(){Ig.canParse()}),Mg=a(function(){return 1!==Ig.canParse.length});Ce({target:"URL",stat:!0,forced:!Tg||Mg},{canParse:function(t){var e=Up(arguments.length,1),r=Wr(t),n=e<2||void 0===arguments[1]?void 0:Wr(arguments[1]);try{return!!new Ig(r,n)}catch(t){return!1}}});var Lg=L("URL");Ce({target:"URL",stat:!0,forced:!Mp},{parse:function(t){var e=Up(arguments.length,1),r=Wr(t),n=e<2||void 0===arguments[1]?void 0:Wr(arguments[1]);try{return new Lg(r,n)}catch(t){return null}}}),Ce({target:"URL",proto:!0,enumerable:!0},{toJSON:function(){return f(URL.prototype.toString,this)}});var Ug=WeakMap.prototype,Ng={WeakMap:WeakMap,set:b(Ug.set),get:b(Ug.get),has:b(Ug.has),remove:b(Ug.delete)},Cg=Ng.has,_g=function(t){return Cg(t),t},Fg=Ng.get,Bg=Ng.has,Dg=Ng.set;Ce({target:"WeakMap",proto:!0,real:!0,forced:!0},{emplace:function(t,e){var r,n,o=_g(this);return Bg(o,t)?(r=Fg(o,t),"update"in e&&(r=e.update(r,t,o),Dg(o,t,r)),r):(n=e.insert(t,o),Dg(o,t,n),n)}}),Ce({target:"WeakMap",stat:!0,forced:!0},{from:ei(Ng.WeakMap,Ng.set,!0)}),Ce({target:"WeakMap",stat:!0,forced:!0},{of:ri(Ng.WeakMap,Ng.set,!0)});var zg=Ng.remove;Ce({target:"WeakMap",proto:!0,real:!0,forced:!0},{deleteAll:function(){for(var t,e=_g(this),r=!0,n=0,o=arguments.length;n2&&(n=r,M(o=arguments[2])&&"cause"in o&&_t(n,"cause",o.cause));var s=[];return Ao(t,ny,{that:s}),_t(r,"errors",s),r};dn?dn(oy,ry):Ae(oy,ry,{name:!0});var iy=oy.prototype=Ve(ry.prototype,{constructor:d(1,oy),message:d(1,""),name:d(1,"AggregateError")});Ce({global:!0,constructor:!0,arity:2},{AggregateError:oy});var ay,uy,sy,cy,fy=function(t){return _.slice(0,t.length)===t},ly=fy("Bun/")?"BUN":fy("Cloudflare-Workers")?"CLOUDFLARE":fy("Deno/")?"DENO":fy("Node.js/")?"NODE":i.Bun&&"string"==typeof Bun.version?"BUN":i.Deno&&"object"==typeof Deno.version?"DENO":"process"===E(i.process)?"NODE":i.window&&i.document?"BROWSER":"REST",hy="NODE"===ly,py=/(?:ipad|iphone|ipod).*applewebkit/i.test(_),vy=i.setImmediate,dy=i.clearImmediate,gy=i.process,yy=i.Dispatch,my=i.Function,by=i.MessageChannel,wy=i.String,Sy=0,Ey={},Oy="onreadystatechange";a(function(){ay=i.location});var xy=function(t){if(ut(Ey,t)){var e=Ey[t];delete Ey[t],e()}},Ry=function(t){return function(){xy(t)}},Py=function(t){xy(t.data)},Ay=function(t){i.postMessage(wy(t),ay.protocol+"//"+ay.host)};vy&&dy||(vy=function(t){Up(arguments.length,1);var e=T(t)?t:my(t),r=vo(arguments,1);return Ey[++Sy]=function(){Ra(e,void 0,r)},uy(Sy),Sy},dy=function(t){delete Ey[t]},hy?uy=function(t){gy.nextTick(Ry(t))}:yy&&yy.now?uy=function(t){yy.now(Ry(t))}:by&&!py?(cy=(sy=new by).port2,sy.port1.onmessage=Py,uy=ar(cy.postMessage,cy)):i.addEventListener&&T(i.postMessage)&&!i.importScripts&&ay&&"file:"!==ay.protocol&&!a(Ay)?(uy=Ay,i.addEventListener("message",Py,!1)):uy=Oy in Et("script")?function(t){De.appendChild(Et("script"))[Oy]=function(){De.removeChild(this),xy(t)}}:function(t){setTimeout(Ry(t),0)});var jy={set:vy,clear:dy},ky=function(){this.head=null,this.tail=null};ky.prototype={add:function(t){var e={item:t,next:null},r=this.tail;r?r.next=e:this.head=e,this.tail=e},get:function(){var t=this.head;if(t)return null===(this.head=t.next)&&(this.tail=null),t.item}};var Iy,Ty,My,Ly,Uy,Ny=ky,Cy=/ipad|iphone|ipod/i.test(_)&&"undefined"!=typeof Pebble,_y=/web0s(?!.*chrome)/i.test(_),Fy=jy.set,By=i.MutationObserver||i.WebKitMutationObserver,Dy=i.document,zy=i.process,Wy=i.Promise,qy=Ip("queueMicrotask");if(!qy){var Hy=new Ny,$y=function(){var t,e;for(hy&&(t=zy.domain)&&t.exit();e=Hy.get();)try{e()}catch(t){throw Hy.head&&Iy(),t}t&&t.enter()};py||hy||_y||!By||!Dy?!Cy&&Wy&&Wy.resolve?((Ly=Wy.resolve(void 0)).constructor=Wy,Uy=ar(Ly.then,Ly),Iy=function(){Uy($y)}):hy?Iy=function(){zy.nextTick($y)}:(Fy=ar(Fy,i),Iy=function(){Fy($y)}):(Ty=!0,My=Dy.createTextNode(""),new By($y).observe(My,{characterData:!0}),Iy=function(){My.data=Ty=!Ty}),qy=function(t){Hy.head||Iy(),Hy.add(t)}}var Ky,Gy,Vy,Yy=qy,Xy=function(t){try{return{error:!1,value:t()}}catch(t){return{error:!0,value:t}}},Jy=i.Promise,Qy=dt("species"),Zy=!1,tm=T(i.PromiseRejectionEvent),em=Ue("Promise",function(){var t=Kt(Jy),e=t!==String(Jy);if(!e&&66===W)return!0;if(!W||W<51||!/native code/.test(t)){var r=new Jy(function(t){t(1)}),n=function(t){t(function(){},function(){})};if((r.constructor={})[Qy]=n,!(Zy=r.then(function(){})instanceof n))return!0}return!(e||"BROWSER"!==ly&&"DENO"!==ly||tm)}),rm={CONSTRUCTOR:em,REJECTION_EVENT:tm,SUBCLASSING:Zy},nm=TypeError,om=function(t){var e,r;this.promise=new t(function(t,n){if(void 0!==e||void 0!==r)throw new nm("Bad Promise constructor");e=t,r=n}),this.resolve=J(e),this.reject=J(r)},im={f:function(t){return new om(t)}},am=jy.set,um="Promise",sm=rm.CONSTRUCTOR,cm=rm.REJECTION_EVENT,fm=rm.SUBCLASSING,lm=ne.getterFor(um),hm=ne.set,pm=Jy&&Jy.prototype,vm=Jy,dm=pm,gm=i.TypeError,ym=i.document,mm=i.process,bm=im.f,wm=bm,Sm=!!(ym&&ym.createEvent&&i.dispatchEvent),Em="unhandledrejection",Om=function(t){var e;return!(!M(t)||!T(e=t.then))&&e},xm=function(t,e){var r,n,o,i=e.value,a=1===e.state,u=a?t.ok:t.fail,s=t.resolve,c=t.reject,l=t.domain;try{u?(a||(2===e.rejection&&km(e),e.rejection=1),!0===u?r=i:(l&&l.enter(),r=u(i),l&&(l.exit(),o=!0)),r===t.promise?c(new gm("Promise-chain cycle")):(n=Om(r))?f(n,r,s,c):s(r)):c(i)}catch(t){l&&!o&&l.exit(),c(t)}},Rm=function(t,e){t.notified||(t.notified=!0,Yy(function(){for(var r,n=t.reactions;r=n.get();)xm(r,t);t.notified=!1,e&&!t.rejection&&Am(t)}))},Pm=function(t,e,r){var n,o;Sm?((n=ym.createEvent("Event")).promise=e,n.reason=r,n.initEvent(t,!1,!0),i.dispatchEvent(n)):n={promise:e,reason:r},!cm&&(o=i["on"+t])?o(n):t===Em&&function(t,e){try{1===arguments.length?console.error(t):console.error(t,e)}catch(t){}}("Unhandled promise rejection",r)},Am=function(t){f(am,i,function(){var e,r=t.facade,n=t.value;if(jm(t)&&(e=Xy(function(){hy?mm.emit("unhandledRejection",n,r):Pm(Em,r,n)}),t.rejection=hy||jm(t)?2:1,e.error))throw e.value})},jm=function(t){return 1!==t.rejection&&!t.parent},km=function(t){f(am,i,function(){var e=t.facade;hy?mm.emit("rejectionHandled",e):Pm("rejectionhandled",e,t.value)})},Im=function(t,e,r){return function(n){t(e,n,r)}},Tm=function(t,e,r){t.done||(t.done=!0,r&&(t=r),t.value=e,t.state=2,Rm(t,!0))},Mm=function(t,e,r){if(!t.done){t.done=!0,r&&(t=r);try{if(t.facade===e)throw new gm("Promise can't be resolved itself");var n=Om(e);n?Yy(function(){var r={done:!1};try{f(n,e,Im(Mm,r,t),Im(Tm,r,t))}catch(e){Tm(r,e,t)}}):(t.value=e,t.state=1,Rm(t,!1))}catch(e){Tm({done:!1},e,t)}}};if(sm&&(vm=function(t){ko(this,dm),J(t),f(Ky,this);var e=lm(this);try{t(Im(Mm,e),Im(Tm,e))}catch(t){Tm(e,t)}},(Ky=function(t){hm(this,{type:um,done:!1,notified:!1,parent:!1,reactions:new Ny,rejection:!1,state:0,value:null})}).prototype=ie(dm=vm.prototype,"then",function(t,e){var r=lm(this),n=bm(Cc(this,vm));return r.parent=!0,n.ok=!T(t)||t,n.fail=T(e)&&e,n.domain=hy?mm.domain:void 0,0===r.state?r.reactions.add(n):Yy(function(){xm(n,r)}),n.promise}),Gy=function(){var t=new Ky,e=lm(t);this.promise=t,this.resolve=Im(Mm,e),this.reject=Im(Tm,e)},im.f=bm=function(t){return t===vm||void 0===t?new Gy(t):wm(t)},T(Jy)&&pm!==Object.prototype)){Vy=pm.then,fm||ie(pm,"then",function(t,e){var r=this;return new vm(function(t,e){f(Vy,r,t,e)}).then(t,e)},{unsafe:!0});try{delete pm.constructor}catch(t){}dn&&dn(pm,dm)}Ce({global:!0,constructor:!0,wrap:!0,forced:sm},{Promise:vm}),an(vm,um,!1),Uo(um);var Lm=rm.CONSTRUCTOR||!Gn(function(t){Jy.all(t).then(void 0,function(){})});Ce({target:"Promise",stat:!0,forced:Lm},{all:function(t){var e=this,r=im.f(e),n=r.resolve,o=r.reject,i=Xy(function(){var r=J(e.resolve),i=[],a=0,u=1;Ao(t,function(t){var s=a++,c=!1;u++,f(r,e,t).then(function(t){c||(c=!0,i[s]=t,--u||n(i))},o)}),--u||n(i)});return i.error&&o(i.value),r.promise}});var Um=Jy&&Jy.prototype;if(Ce({target:"Promise",proto:!0,forced:rm.CONSTRUCTOR,real:!0},{catch:function(t){return this.then(void 0,t)}}),T(Jy)){var Nm=L("Promise").prototype.catch;Um.catch!==Nm&&ie(Um,"catch",Nm,{unsafe:!0})}Ce({target:"Promise",stat:!0,forced:Lm},{race:function(t){var e=this,r=im.f(e),n=r.reject,o=Xy(function(){var o=J(e.resolve);Ao(t,function(t){f(o,e,t).then(r.resolve,n)})});return o.error&&n(o.value),r.promise}}),Ce({target:"Promise",stat:!0,forced:rm.CONSTRUCTOR},{reject:function(t){var e=im.f(this);return(0,e.reject)(t),e.promise}});var Cm=function(t,e){if(kt(t),M(e)&&e.constructor===t)return e;var r=im.f(t);return(0,r.resolve)(e),r.promise};Ce({target:"Promise",stat:!0,forced:rm.CONSTRUCTOR},{resolve:function(t){return Cm(this,t)}}),Ce({target:"Promise",stat:!0,forced:Lm},{allSettled:function(t){var e=this,r=im.f(e),n=r.resolve,o=r.reject,i=Xy(function(){var r=J(e.resolve),o=[],i=0,a=1;Ao(t,function(t){var u=i++,s=!1;a++,f(r,e,t).then(function(t){s||(s=!0,o[u]={status:"fulfilled",value:t},--a||n(o))},function(t){s||(s=!0,o[u]={status:"rejected",reason:t},--a||n(o))})}),--a||n(o)});return i.error&&o(i.value),r.promise}});var _m="No one promise resolved";Ce({target:"Promise",stat:!0,forced:Lm},{any:function(t){var e=this,r=L("AggregateError"),n=im.f(e),o=n.resolve,i=n.reject,a=Xy(function(){var n=J(e.resolve),a=[],u=0,s=1,c=!1;Ao(t,function(t){var l=u++,h=!1;s++,f(n,e,t).then(function(t){h||c||(c=!0,o(t))},function(t){h||c||(h=!0,a[l]=t,--s||i(new r(a,_m)))})}),--s||i(new r(a,_m))});return a.error&&i(a.value),n.promise}}),Ce({target:"Promise",stat:!0},{withResolvers:function(){var t=im.f(this);return{promise:t.promise,resolve:t.resolve,reject:t.reject}}});var Fm=Jy&&Jy.prototype,Bm=!!Jy&&a(function(){Fm.finally.call({then:function(){}},function(){})});if(Ce({target:"Promise",proto:!0,real:!0,forced:Bm},{finally:function(t){var e=Cc(this,L("Promise")),r=T(t);return this.then(r?function(r){return Cm(e,t()).then(function(){return r})}:t,r?function(r){return Cm(e,t()).then(function(){throw r})}:t)}}),T(Jy)){var Dm=L("Promise").prototype.finally;Fm.finally!==Dm&&ie(Fm,"finally",Dm,{unsafe:!0})}var zm=i.Promise,Wm=!1,qm=!zm||!zm.try||Xy(function(){zm.try(function(t){Wm=8===t},8)}).error||!Wm;Ce({target:"Promise",stat:!0,forced:qm},{try:function(t){var e=arguments.length>1?vo(arguments,1):[],r=im.f(this),n=Xy(function(){return Ra(J(t),void 0,e)});return(n.error?r.reject:r.resolve)(n.value),r.promise}}),Ze("Promise","finally");var Hm="URLSearchParams"in self,$m="Symbol"in self&&"iterator"in Symbol,Km="FileReader"in self&&"Blob"in self&&function(){try{return new Blob,!0}catch(t){return!1}}(),Gm="FormData"in self,Vm="ArrayBuffer"in self;if(Vm)var Ym=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],Xm=ArrayBuffer.isView||function(t){return t&&Ym.indexOf(Object.prototype.toString.call(t))>-1};function Jm(t){if("string"!=typeof t&&(t=String(t)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(t))throw new TypeError("Invalid character in header field name");return t.toLowerCase()}function Qm(t){return"string"!=typeof t&&(t=String(t)),t}function Zm(t){var e={next:function(){var e=t.shift();return{done:void 0===e,value:e}}};return $m&&(e[Symbol.iterator]=function(){return e}),e}function tb(t){this.map={},t instanceof tb?t.forEach(function(t,e){this.append(e,t)},this):Array.isArray(t)?t.forEach(function(t){this.append(t[0],t[1])},this):t&&Object.getOwnPropertyNames(t).forEach(function(e){this.append(e,t[e])},this)}function eb(t){if(t.bodyUsed)return Promise.reject(new TypeError("Already read"));t.bodyUsed=!0}function rb(t){return new Promise(function(e,r){t.onload=function(){e(t.result)},t.onerror=function(){r(t.error)}})}function nb(t){var e=new FileReader,r=rb(e);return e.readAsArrayBuffer(t),r}function ob(t){if(t.slice)return t.slice(0);var e=new Uint8Array(t.byteLength);return e.set(new Uint8Array(t)),e.buffer}function ib(){return this.bodyUsed=!1,this._initBody=function(t){var e;this._bodyInit=t,t?"string"==typeof t?this._bodyText=t:Km&&Blob.prototype.isPrototypeOf(t)?this._bodyBlob=t:Gm&&FormData.prototype.isPrototypeOf(t)?this._bodyFormData=t:Hm&&URLSearchParams.prototype.isPrototypeOf(t)?this._bodyText=t.toString():Vm&&Km&&(e=t)&&DataView.prototype.isPrototypeOf(e)?(this._bodyArrayBuffer=ob(t.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):Vm&&(ArrayBuffer.prototype.isPrototypeOf(t)||Xm(t))?this._bodyArrayBuffer=ob(t):this._bodyText=t=Object.prototype.toString.call(t):this._bodyText="",this.headers.get("content-type")||("string"==typeof t?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):Hm&&URLSearchParams.prototype.isPrototypeOf(t)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},Km&&(this.blob=function(){var t=eb(this);if(t)return t;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?eb(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(nb)}),this.text=function(){var t=eb(this);if(t)return t;if(this._bodyBlob)return function(t){var e=new FileReader,r=rb(e);return e.readAsText(t),r}(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(function(t){for(var e=new Uint8Array(t),r=new Array(e.length),n=0;n-1?e:t}(e.method||this.method||"GET"),this.mode=e.mode||this.mode||null,this.signal=e.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&r)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(r)}function sb(t){var e=new FormData;return t.trim().split("&").forEach(function(t){if(t){var r=t.split("="),n=r.shift().replace(/\+/g," "),o=r.join("=").replace(/\+/g," ");e.append(decodeURIComponent(n),decodeURIComponent(o))}}),e}function cb(t,e){e||(e={}),this.type="default",this.status=void 0===e.status?200:e.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in e?e.statusText:"OK",this.headers=new tb(e.headers),this.url=e.url||"",this._initBody(t)}ub.prototype.clone=function(){return new ub(this,{body:this._bodyInit})},ib.call(ub.prototype),ib.call(cb.prototype),cb.prototype.clone=function(){return new cb(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new tb(this.headers),url:this.url})},cb.error=function(){var t=new cb(null,{status:0,statusText:""});return t.type="error",t};var fb=[301,302,303,307,308];cb.redirect=function(t,e){if(-1===fb.indexOf(e))throw new RangeError("Invalid status code");return new cb(null,{status:e,headers:{location:t}})};var lb=self.DOMException;try{new lb}catch(t){(lb=function(t,e){this.message=t,this.name=e;var r=Error(t);this.stack=r.stack}).prototype=Object.create(Error.prototype),lb.prototype.constructor=lb}function hb(t,e){return new Promise(function(r,n){var o=new ub(t,e);if(o.signal&&o.signal.aborted)return n(new lb("Aborted","AbortError"));var i=new XMLHttpRequest;function a(){i.abort()}i.onload=function(){var t,e,n={status:i.status,statusText:i.statusText,headers:(t=i.getAllResponseHeaders()||"",e=new tb,t.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach(function(t){var r=t.split(":"),n=r.shift().trim();if(n){var o=r.join(":").trim();e.append(n,o)}}),e)};n.url="responseURL"in i?i.responseURL:n.headers.get("X-Request-URL"),r(new cb("response"in i?i.response:i.responseText,n))},i.onerror=function(){n(new TypeError("Network request failed"))},i.ontimeout=function(){n(new TypeError("Network request failed"))},i.onabort=function(){n(new lb("Aborted","AbortError"))},i.open(o.method,o.url,!0),"include"===o.credentials?i.withCredentials=!0:"omit"===o.credentials&&(i.withCredentials=!1),"responseType"in i&&Km&&(i.responseType="blob"),o.headers.forEach(function(t,e){i.setRequestHeader(e,t)}),o.signal&&(o.signal.addEventListener("abort",a),i.onreadystatechange=function(){4===i.readyState&&o.signal.removeEventListener("abort",a)}),i.send(void 0===o._bodyInit?null:o._bodyInit)})}hb.polyfill=!0,self.fetch||(self.fetch=hb,self.Headers=tb,self.Request=ub,self.Response=cb);var pb=Object.getOwnPropertySymbols,vb=Object.prototype.hasOwnProperty,db=Object.prototype.propertyIsEnumerable,gb=function(){try{if(!Object.assign)return!1;var t=new String("abc");if(t[5]="de","5"===Object.getOwnPropertyNames(t)[0])return!1;for(var e={},r=0;r<10;r++)e["_"+String.fromCharCode(r)]=r;if("0123456789"!==Object.getOwnPropertyNames(e).map(function(t){return e[t]}).join(""))return!1;var n={};return"abcdefghijklmnopqrst".split("").forEach(function(t){n[t]=t}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},n)).join("")}catch(t){return!1}}()?Object.assign:function(t,e){for(var r,n,o=function(t){if(null==t)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(t)}(t),i=1;i{"use strict";var e={},t={};function r(o){var n=t[o];if(void 0!==n)return n.exports;var a=t[o]={exports:{}},i=!0;try{e[o].call(a.exports,a,a.exports,r),i=!1}finally{i&&delete t[o]}return a.exports}r.m=e,(()=>{var e=[];r.O=(t,o,n,a)=>{if(o){a=a||0;for(var i=e.length;i>0&&e[i-1][2]>a;i--)e[i]=e[i-1];e[i]=[o,n,a];return}for(var u=1/0,i=0;i=a)&&Object.keys(r.O).every(e=>r.O[e](o[c]))?o.splice(c--,1):(l=!1,a{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},(()=>{var e,t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;r.t=function(o,n){if(1&n&&(o=this(o)),8&n||"object"==typeof o&&o&&(4&n&&o.__esModule||16&n&&"function"==typeof o.then))return o;var a=Object.create(null);r.r(a);var i={};e=e||[null,t({}),t([]),t(t)];for(var u=2&n&&o;"object"==typeof u&&!~e.indexOf(u);u=t(u))Object.getOwnPropertyNames(u).forEach(e=>i[e]=()=>o[e]);return i.default=()=>o,r.d(a,i),a}})(),r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,o)=>(r.f[o](e,t),t),[])),r.u=e=>{},r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={},t="_N_E:";r.l=(o,n,a,i)=>{if(e[o])return void e[o].push(n);if(void 0!==a)for(var u,l,c=document.getElementsByTagName("script"),d=0;d{u.onerror=u.onload=null,clearTimeout(p);var n=e[o];if(delete e[o],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(e=>e(r)),t)return t(r)},p=setTimeout(f.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=f.bind(null,u.onerror),u.onload=f.bind(null,u.onload),l&&document.head.appendChild(u)}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:e=>e},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("nextjs#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="/_next/",(()=>{var e={68:0,838:0};r.f.j=(t,o)=>{var n=r.o(e,t)?e[t]:void 0;if(0!==n)if(n)o.push(n[2]);else if(/^(6|83)8$/.test(t))e[t]=0;else{var a=new Promise((r,o)=>n=e[t]=[r,o]);o.push(n[2]=a);var i=r.p+r.u(t),u=Error();r.l(i,o=>{if(r.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var a=o&&("load"===o.type?"missing":o.type),i=o&&o.target&&o.target.src;u.message="Loading chunk "+t+" failed.\n("+a+": "+i+")",u.name="ChunkLoadError",u.type=a,u.request=i,n[1](u)}},"chunk-"+t,t)}},r.O.j=t=>0===e[t];var t=(t,o)=>{var n,a,[i,u,l]=o,c=0;if(i.some(t=>0!==e[t])){for(n in u)r.o(u,n)&&(r.m[n]=u[n]);if(l)var d=l(r)}for(t&&t(o);c:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.self-start{align-self:flex-start}.justify-self-end{justify-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[2px\]{border-radius:2px}.rounded-full{border-radius:3.40282e+38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-\[1\.5px\]{border-style:var(--tw-border-style);border-width:1.5px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-gray-200{border-color:var(--color-gray-200)}.border-green-200{border-color:var(--color-green-200)}.border-orange-200{border-color:var(--color-orange-200)}.border-orange-300{border-color:var(--color-orange-300)}.border-red-200{border-color:var(--color-red-200)}.border-transparent{border-color:#0000}.border-yellow-200{border-color:var(--color-yellow-200)}.border-l-green-500{border-left-color:var(--color-green-500)}.border-l-red-500{border-left-color:var(--color-red-500)}.border-l-yellow-500{border-left-color:var(--color-yellow-500)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-green-100{background-color:var(--color-green-100)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-orange-200\/60{background-color:#ffd7a899}@supports (color:color-mix(in lab,red,red)){.bg-orange-200\/60{background-color:color-mix(in oklab,var(--color-orange-200)60%,transparent)}}.bg-orange-600{background-color:var(--color-orange-600)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-600{background-color:var(--color-red-600)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-500{background-color:var(--color-yellow-500)}.fill-current{fill:currentColor}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-12{padding:calc(var(--spacing)*12)}.p-\[3px\]{padding:3px}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-6{padding-block:calc(var(--spacing)*6)}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-8{padding-left:calc(var(--spacing)*8)}.text-center{text-align:center}.text-left{text-align:left}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.text-black{color:var(--color-black)}.text-blue-600{color:var(--color-blue-600)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-orange-400{color:var(--color-orange-400)}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-orange-700{color:var(--color-orange-700)}.text-orange-800{color:var(--color-orange-800)}.text-orange-900{color:var(--color-orange-900)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.capitalize{text-transform:capitalize}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.underline-offset-4{text-underline-offset:4px}.opacity-50{opacity:.5}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.shadow,.shadow-lg{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a)}.shadow-md,.shadow-none{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.shadow-sm,.shadow-xl{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,box-shadow\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.running{animation-play-state:running}.group-data-\[disabled\=true\]\:pointer-events-none:is(:where(.group)[data-disabled=true] *){pointer-events:none}.group-data-\[disabled\=true\]\:opacity-50:is(:where(.group)[data-disabled=true] *){opacity:.5}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.first\:rounded-l-md:first-child{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.last\:rounded-r-md:last-child{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}@media (hover:hover){.hover\:border-orange-300:hover{border-color:var(--color-orange-300)}.hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.hover\:bg-orange-100:hover{background-color:var(--color-orange-100)}.hover\:bg-orange-100\/50:hover{background-color:#ffedd580}@supports (color:color-mix(in lab,red,red)){.hover\:bg-orange-100\/50:hover{background-color:color-mix(in oklab,var(--color-orange-100)50%,transparent)}}.hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-transparent:hover{background-color:#0000}.hover\:text-orange-700:hover{color:var(--color-orange-700)}.hover\:text-orange-900:hover{color:var(--color-orange-900)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:z-10:focus{z-index:10}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:z-10:focus-visible{z-index:10}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:outline-1:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.has-data-\[slot\=card-action\]\:grid-cols-\[1fr_auto\]:has([data-slot=card-action]){grid-template-columns:1fr auto}.has-\[\>svg\]\:px-2\.5:has(>svg){padding-inline:calc(var(--spacing)*2.5)}.has-\[\>svg\]\:px-3:has(>svg){padding-inline:calc(var(--spacing)*3)}.has-\[\>svg\]\:px-4:has(>svg){padding-inline:calc(var(--spacing)*4)}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[dragging\=true\]\:z-10[data-dragging=true]{z-index:10}.data-\[dragging\=true\]\:opacity-80[data-dragging=true]{opacity:.8}.data-\[inset\]\:pl-8[data-inset]{padding-left:calc(var(--spacing)*8)}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.data-\[size\=default\]\:h-9[data-size=default]{height:calc(var(--spacing)*9)}.data-\[size\=sm\]\:h-8[data-size=sm]{height:calc(var(--spacing)*8)}:is(.\*\:data-\[slot\=select-value\]\:line-clamp-1>*)[data-slot=select-value]{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.\*\:data-\[slot\=select-value\]\:flex>*)[data-slot=select-value]{display:flex}:is(.\*\:data-\[slot\=select-value\]\:items-center>*)[data-slot=select-value]{align-items:center}:is(.\*\:data-\[slot\=select-value\]\:gap-2>*)[data-slot=select-value]{gap:calc(var(--spacing)*2)}:is(.\*\*\:data-\[slot\=table-cell\]\:first\:w-8 *)[data-slot=table-cell]:first-child{width:calc(var(--spacing)*8)}.data-\[state\=active\]\:border-orange-300[data-state=active]{border-color:var(--color-orange-300)}.data-\[state\=active\]\:bg-orange-50[data-state=active]{background-color:var(--color-orange-50)}.data-\[state\=active\]\:bg-orange-100[data-state=active]{background-color:var(--color-orange-100)}.data-\[state\=active\]\:text-orange-900[data-state=active]{color:var(--color-orange-900)}.data-\[state\=active\]\:shadow-sm[data-state=active]{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=open\]\:animate-in[data-state=open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[variant\=outline\]\:border-l-0[data-variant=outline]{border-left-style:var(--tw-border-style);border-left-width:0}.data-\[variant\=outline\]\:shadow-xs[data-variant=outline]{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[variant\=outline\]\:first\:border-l[data-variant=outline]:first-child{border-left-style:var(--tw-border-style);border-left-width:1px}@media (min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:pt-6{padding-top:calc(var(--spacing)*6)}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:gap-6{gap:calc(var(--spacing)*6)}}@media (min-width:64rem){.lg\:ml-0{margin-left:calc(var(--spacing)*0)}.lg\:flex{display:flex}.lg\:w-fit{width:fit-content}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:80rem){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}@container card (min-width:540px){.\@\[540px\]\/card\:block{display:block}.\@\[540px\]\/card\:hidden{display:none}}.\[\&_\.recharts-dot\[stroke\=\'\#fff\'\]\]\:stroke-transparent .recharts-dot[stroke=\#fff]{stroke:#0000}.\[\&_\.recharts-layer\]\:outline-hidden .recharts-layer{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.\[\&_\.recharts-layer\]\:outline-hidden .recharts-layer{outline-offset:2px;outline:2px solid #0000}}.\[\&_\.recharts-sector\]\:outline-hidden .recharts-sector{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.\[\&_\.recharts-sector\]\:outline-hidden .recharts-sector{outline-offset:2px;outline:2px solid #0000}}.\[\&_\.recharts-sector\[stroke\=\'\#fff\'\]\]\:stroke-transparent .recharts-sector[stroke=\#fff]{stroke:#0000}.\[\&_\.recharts-surface\]\:outline-hidden .recharts-surface{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.\[\&_\.recharts-surface\]\:outline-hidden .recharts-surface{outline-offset:2px;outline:2px solid #0000}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_tr\]\:border-b tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-style:var(--tw-border-style);border-width:0}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:calc(var(--spacing)*0)}.\[\.border-b\]\:pb-6.border-b{padding-bottom:calc(var(--spacing)*6)}.\[\.border-t\]\:pt-6.border-t{padding-top:calc(var(--spacing)*6)}:is(.\*\:\[span\]\:last\:flex>*):is(span):last-child{display:flex}:is(.\*\:\[span\]\:last\:items-center>*):is(span):last-child{align-items:center}:is(.\*\:\[span\]\:last\:gap-2>*):is(span):last-child{gap:calc(var(--spacing)*2)}.\[\&\>\[role\=checkbox\]\]\:translate-y-\[2px\]>[role=checkbox]{--tw-translate-y:2px;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&\>svg\]\:h-2\.5>svg{height:calc(var(--spacing)*2.5)}.\[\&\>svg\]\:h-3>svg{height:calc(var(--spacing)*3)}.\[\&\>svg\]\:w-2\.5>svg{width:calc(var(--spacing)*2.5)}.\[\&\>svg\]\:w-3>svg{width:calc(var(--spacing)*3)}.\[\&\>tr\]\:last\:border-b-0>tr:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--radius:.625rem;--color-background:var(--background);--color-foreground:var(--foreground);--color-sidebar-ring:var(--sidebar-ring);--color-sidebar-border:var(--sidebar-border);--color-sidebar-accent-foreground:var(--sidebar-accent-foreground);--color-sidebar-accent:var(--sidebar-accent);--color-sidebar-primary-foreground:var(--sidebar-primary-foreground);--color-sidebar-primary:var(--sidebar-primary);--color-sidebar-foreground:var(--sidebar-foreground);--color-sidebar:var(--sidebar);--color-chart-5:var(--chart-5);--color-chart-4:var(--chart-4);--color-chart-3:var(--chart-3);--color-chart-2:var(--chart-2);--color-chart-1:var(--chart-1);--color-ring:var(--ring);--color-input:var(--input);--color-border:var(--border);--color-destructive:var(--destructive);--color-accent-foreground:var(--accent-foreground);--color-accent:var(--accent);--color-muted-foreground:var(--muted-foreground);--color-muted:var(--muted);--color-secondary-foreground:var(--secondary-foreground);--color-secondary:var(--secondary);--color-primary-foreground:var(--primary-foreground);--color-primary:var(--primary);--color-popover-foreground:var(--popover-foreground);--color-popover:var(--popover);--color-card-foreground:var(--card-foreground);--color-card:var(--card);--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);--card:oklch(99.5% .01 25);--card-foreground:oklch(15% .01 25);--popover:oklch(99.5% .01 25);--popover-foreground:oklch(15% .01 25);--primary:oklch(65% .21 25);--primary-foreground:oklch(100% 0 0);--secondary:oklch(97% .015 28);--secondary-foreground:oklch(20% .02 25);--muted:oklch(96% .015 30);--muted-foreground:oklch(45% .02 25);--accent:oklch(94% .02 28);--accent-foreground:oklch(20% .02 25);--destructive:oklch(64% .21 25.33);--border:oklch(90% .015 28);--input:oklch(97% .015 28);--ring:oklch(65% .21 25);--chart-1:oklch(70% .21 35);--chart-2:oklch(77% .09 34.19);--chart-3:oklch(58% .08 254.16);--chart-4:oklch(50% .08 259.49);--chart-5:oklch(42% .1 264.03);--sidebar:oklch(18% .04 35);--sidebar-foreground:oklch(15% .01 25);--sidebar-primary:oklch(65% .21 25);--sidebar-primary-foreground:oklch(100% 0 0);--sidebar-accent:oklch(95% .025 30);--sidebar-accent-foreground:oklch(20% .02 25);--sidebar-border:oklch(92% .02 28);--sidebar-ring:oklch(65% .21 25);--background:oklch(99% .02 25);--foreground:oklch(15% .01 25)}.dark{--background:oklch(15% .04 35);--foreground:oklch(95% .01 35);--card:oklch(18% .03 35);--card-foreground:oklch(92% 0 0);--popover:oklch(16% .03 35);--popover-foreground:oklch(92% 0 0);--primary:oklch(70% .21 35);--primary-foreground:oklch(100% 0 0);--secondary:oklch(20% .04 35);--secondary-foreground:oklch(92% 0 0);--muted:oklch(20% .04 35);--muted-foreground:oklch(72% 0 0);--accent:oklch(25% .06 35);--accent-foreground:oklch(88% .06 254.13);--destructive:oklch(64% .21 25.33);--border:oklch(25% .04 35);--input:oklch(25% .04 35);--ring:oklch(70% .21 35);--chart-1:oklch(70% .21 35);--chart-2:oklch(77% .09 34.19);--chart-3:oklch(58% .08 254.16);--chart-4:oklch(50% .08 259.49);--chart-5:oklch(42% .1 264.03);--sidebar:oklch(18% .04 35);--sidebar-foreground:oklch(92% 0 0);--sidebar-primary:oklch(70% .21 35);--sidebar-primary-foreground:oklch(100% 0 0);--sidebar-accent:oklch(25% .06 35);--sidebar-accent-foreground:oklch(88% .06 254.13);--sidebar-border:oklch(25% .04 35);--sidebar-ring:oklch(70% .21 35)}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-duration{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0))}} \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/media/26a46d62cd723877-s.woff2 b/keploy/pkg/service/load/out/_next/static/media/26a46d62cd723877-s.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..3a27e632eb14fffc6fce03483049986ba13f6802 GIT binary patch literal 18820 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

uw6FG`p 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 literal 0 HcmV?d00001 diff --git a/keploy/pkg/service/load/out/_next/static/media/e4af272ccee01ff0-s.p.woff2 b/keploy/pkg/service/load/out/_next/static/media/e4af272ccee01ff0-s.p.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..91dc3e8529921cabc74fb1dacf5253260b9c8cca GIT binary patch literal 48432 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 Tp~gD%=1~4;zp%;UV+#NP+I>lo literal 0 HcmV?d00001 diff --git a/keploy/pkg/service/load/out/favicon.ico b/keploy/pkg/service/load/out/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..30bd11d1eb8219ac5610f55be65714d1dcc645f9 GIT binary patch literal 243774 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 literal 0 HcmV?d00001 diff --git a/keploy/pkg/service/load/out/index.html b/keploy/pkg/service/load/out/index.html new file mode 100644 index 0000000..95ad288 --- /dev/null +++ b/keploy/pkg/service/load/out/index.html @@ -0,0 +1 @@ +KLT Dashboard

\ No newline at end of file diff --git a/keploy/pkg/service/load/out/index.txt b/keploy/pkg/service/load/out/index.txt new file mode 100644 index 0000000..6d3f186 --- /dev/null +++ b/keploy/pkg/service/load/out/index.txt @@ -0,0 +1,22 @@ +1:"$Sreact.fragment" +2:I[2597,["177","static/chunks/app/layout-bc503d5738af696e.js"],"default"] +3:I[3313,["177","static/chunks/app/layout-bc503d5738af696e.js"],"default"] +4:I[7555,[],""] +5:I[1295,[],""] +6:I[894,[],"ClientPageRoot"] +7:I[7704,["547","static/chunks/547-586c3cf76649ec2c.js","974","static/chunks/app/page-a3cd1a73fe99bbf3.js"],"default"] +a:I[9665,[],"OutletBoundary"] +c:I[4911,[],"AsyncMetadataOutlet"] +e:I[9665,[],"ViewportBoundary"] +10:I[9665,[],"MetadataBoundary"] +11:"$Sreact.suspense" +13:I[8393,[],""] +:HL["/_next/static/media/e4af272ccee01ff0-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}] +:HL["/_next/static/css/bc1768f92951bee0.css","style"] +0:{"P":null,"b":"S6A95ZgpgxS8RHL4D2qnv","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/bc1768f92951bee0.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":[["$","head",null,{"children":[["$","link",null,{"rel":"icon","href":"/favicon.ico"}],["$","link",null,{"rel":"manifest","href":"/manifest.json"}],["$","meta",null,{"name":"theme-color","content":"#000000"}],["$","meta",null,{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","body",null,{"className":"__className_e8ce0c","children":[["$","$L2",null,{}],["$","$L3",null,{}],["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]]}]]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L6",null,{"Component":"$7","searchParams":{},"params":{},"promises":["$@8","$@9"]}],null,["$","$La",null,{"children":["$Lb",["$","$Lc",null,{"promise":"$@d"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Le",null,{"children":"$Lf"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L10",null,{"children":["$","div",null,{"hidden":true,"children":["$","$11",null,{"fallback":null,"children":"$L12"}]}]}]]}],false]],"m":"$undefined","G":["$13",[]],"s":false,"S":true} +8:{} +9:"$0:f:0:1:2:children:1:props:children:0:props:params" +f:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] +b:null +d:{"metadata":[["$","title","0",{"children":"KLT Dashboard"}],["$","meta","1",{"name":"description","content":"Keploy's Load Testing Dashboard"}]],"error":null,"digest":"$undefined"} +12:"$d:metadata" diff --git a/keploy/pkg/service/load/out/manifest.json b/keploy/pkg/service/load/out/manifest.json new file mode 100644 index 0000000..3671e62 --- /dev/null +++ b/keploy/pkg/service/load/out/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "KLT Dashboard", + "short_name": "KLT Dashboard", + "description": "Keploy's Load Testing Dashboard", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#000000", + "orientation": "portrait-primary", + "icons": [ + { + "src": "/favicon.ico", + "sizes": "any", + "type": "image/x-icon" + } + ] +} diff --git a/keploy/pkg/service/load/out/sw.js b/keploy/pkg/service/load/out/sw.js new file mode 100644 index 0000000..eb1f49b --- /dev/null +++ b/keploy/pkg/service/load/out/sw.js @@ -0,0 +1,188 @@ +// Service Worker for offline caching +const CACHE_NAME = 'klt-dashboard-v4'; +const STATIC_CACHE_URLS = [ + '/', + '/index.html', + '/favicon.ico', + '/manifest.json' +]; + +// Install event - cache static resources +self.addEventListener('install', (event) => { + console.log('Service Worker installing...'); + event.waitUntil( + caches.open(CACHE_NAME) + .then((cache) => { + console.log('Caching static resources'); + // Try to cache the basic URLs, but don't fail if some are missing + return Promise.all( + STATIC_CACHE_URLS.map(url => { + return cache.add(url).catch(err => { + console.warn('Failed to cache:', url, err); + }); + }) + ); + }) + .then(() => { + console.log('Service Worker installation complete'); + // Force the waiting service worker to become the active service worker + return self.skipWaiting(); + }) + .catch(err => { + console.error('Service Worker installation failed:', err); + }) + ); +}); + +// Activate event - clean up old caches +self.addEventListener('activate', (event) => { + console.log('Service Worker activating...'); + event.waitUntil( + caches.keys() + .then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== CACHE_NAME) { + console.log('Deleting old cache:', cacheName); + return caches.delete(cacheName); + } + }) + ); + }) + .then(() => { + console.log('Service Worker activation complete'); + // Ensure the new service worker takes control immediately + return self.clients.claim(); + }) + ); +}); + +// Fetch event - Selective caching to avoid interfering with app functionality +self.addEventListener('fetch', (event) => { + // Skip cross-origin requests + if (!event.request.url.startsWith(self.location.origin)) { + return; + } + + // Skip chrome-extension and other non-http requests + if (!event.request.url.startsWith('http')) { + return; + } + + // Skip requests that shouldn't be cached (to avoid interfering with app logic) + const url = new URL(event.request.url); + + // Skip API calls, WebSocket connections, and other dynamic requests + if ( + url.pathname.startsWith('/metrics/') || + event.request.headers.get('cache-control') === 'no-cache' || + event.request.headers.get('cache-control') === 'no-store' + ) { + console.log('🚫 Skipping service worker for:', event.request.url); + return; // Let the browser handle these normally + } + + // Only cache static assets and navigation requests + if (shouldCacheRequest(event.request)) { + event.respondWith(networkFirstStrategy(event.request)); + } +}); + +// Determine if a request should be cached +function shouldCacheRequest(request) { + const url = new URL(request.url); + + // Cache navigation requests (HTML pages) + if (request.destination === 'document') { + return true; + } + + // Cache static assets + if ( + request.destination === 'script' || + request.destination === 'style' || + request.destination === 'image' || + request.destination === 'font' || + url.pathname.includes('/_next/static/') || + url.pathname.endsWith('.css') || + url.pathname.endsWith('.js') || + url.pathname.endsWith('.ico') || + url.pathname.endsWith('.png') || + url.pathname.endsWith('.jpg') || + url.pathname.endsWith('.svg') || + url.pathname.endsWith('.woff') || + url.pathname.endsWith('.woff2') + ) { + return true; + } + + // Cache manifest and service worker files + if ( + url.pathname === '/manifest.json' || + url.pathname === '/sw.js' + ) { + return true; + } + + return false; +} + +// Network first strategy - Always try network first, fallback to cache only when server is down +async function networkFirstStrategy(request) { + try { + const response = await fetch(request); + + if (response && response.status === 200) { + // Only cache successful responses for static content + if (shouldCacheResponse(request, response)) { + const cache = await caches.open(CACHE_NAME); + cache.put(request, response.clone()); + console.log('📦 Cached fresh content:', request.url); + } + + return response; + } else { + // Bad response, try cache + const cachedResponse = await caches.match(request); + return cachedResponse || response; + } + } catch (error) { + // Network failed (server down), use cache + const cachedResponse = await caches.match(request); + + if (cachedResponse) { + console.log('📱 Serving offline content:', request.url); + return cachedResponse; + } + + // If it's a navigation request and no cache, try to serve the main page + if (request.destination === 'document') { + const mainPage = await caches.match('/'); + if (mainPage) { + console.log('🏠 Serving main page as fallback'); + return mainPage; + } + } + + throw error; + } +} + +// Determine if a response should be cached +function shouldCacheResponse(request, response) { + // Don't cache if response has cache-control: no-store + const cacheControl = response.headers.get('cache-control'); + if (cacheControl && cacheControl.includes('no-store')) { + return false; + } + + // Cache static assets and navigation requests + return shouldCacheRequest(request); +} + +// Listen for messages from the main thread +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); diff --git a/keploy/pkg/service/load/scheduler.go b/keploy/pkg/service/load/scheduler.go new file mode 100644 index 0000000..8db9053 --- /dev/null +++ b/keploy/pkg/service/load/scheduler.go @@ -0,0 +1,138 @@ +package load + +import ( + "context" + "fmt" + "sync" + "time" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/service/testsuite" + "go.uber.org/zap" + "golang.org/x/time/rate" +) + +type Scheduler struct { + config *config.Config + logger *zap.Logger + loadOptions *testsuite.LoadOptions + ts *testsuite.TestSuite + collector *MetricsCollector + limiter *rate.Limiter + cancelAll context.CancelFunc + wg sync.WaitGroup + vuCownter int +} + +func NewScheduler(logger *zap.Logger, config *config.Config, loadOptions *testsuite.LoadOptions, ts *testsuite.TestSuite, collector *MetricsCollector) *Scheduler { + // setting the rate limiter based on the RPS specified in loadOptions will be passed later to the TSExecutor execute function. + var lim *rate.Limiter + if loadOptions.RPS > 0 { + lim = rate.NewLimiter(rate.Limit(loadOptions.RPS), loadOptions.RPS) + } + + return &Scheduler{ + loadOptions: loadOptions, + ts: ts, + collector: collector, + limiter: lim, + logger: logger, + config: config, + vuCownter: 0, + } +} + +func (s *Scheduler) Run(parent context.Context, exporter *Exporter, dashboardExposer *DashboardExposer) error { + // setting the context with a timeout based on the duration specified in loadOptions. + // will be given to the VUWorker goroutines along with the waitgroup to synchronize. + duration, err := time.ParseDuration(s.loadOptions.Duration) + if err != nil { + s.logger.Error("Failed to parse duration", zap.String("duration", s.loadOptions.Duration), zap.Error(err)) + return err + } + ctx, cancel := context.WithTimeout(parent, duration) + s.cancelAll = cancel + defer cancel() + + // check if the loadOptions has a valid profile set, if not return an error. + switch s.loadOptions.Profile { + case "constant_vus": + return s.runConstant(ctx, s.ts, exporter, dashboardExposer) + case "ramping_vus": + return s.runRamping(ctx, s.ts, exporter, dashboardExposer) + default: + return fmt.Errorf("unknown load profile %q", s.loadOptions.Profile) + } +} + +func (s *Scheduler) runConstant(ctx context.Context, ts *testsuite.TestSuite, exporter *Exporter, dashboardExposer *DashboardExposer) error { + exporter.ltToken.CreatedAt = time.Now() + exporter.StartServer(ctx) + dashboardExposer.Expose(ctx) + err := s.spawnVUGoroutines(ctx, ts, s.loadOptions.VUs, exporter) + if err != nil { + s.logger.Error("Failed to spawn VU goroutines", zap.Int("vus", s.loadOptions.VUs), zap.Error(err)) + return err + } + + // if context is done it waits for all VU goroutines to finish reporting back to the MetricCollector. + <-ctx.Done() + s.wg.Wait() + + return nil +} + +func (s *Scheduler) runRamping(ctx context.Context, ts *testsuite.TestSuite, exporter *Exporter, dashboardExposer *DashboardExposer) error { + exporter.ltToken.CreatedAt = time.Now() + exporter.StartServer(ctx) + dashboardExposer.Expose(ctx) + start := time.Now() + current := 0 + cumulative := start + for _, stg := range s.loadOptions.Stages { + // spawning VU goroutines based on the target specified in the stage. + target := stg.Target + delta := target - current + if delta > 0 { + if err := s.spawnVUGoroutines(ctx, ts, delta, exporter); err != nil { + return err + } + } + + stageDuration, err := time.ParseDuration(stg.Duration) + if err != nil { + return err + } + cumulative = cumulative.Add(stageDuration) + select { + case <-ctx.Done(): + s.wg.Wait() + return nil + case <-time.After(time.Until(cumulative)): + } + current = target + } + + // if context is done it waits for all VU goroutines to finish reporting back to the MetricCollector. + + <-ctx.Done() + s.wg.Wait() + + return nil +} + +func (s *Scheduler) spawnVUGoroutines(ctx context.Context, ts *testsuite.TestSuite, n int, exporter *Exporter) error { + startID := s.vuCownter + endID := s.vuCownter + n + for id := startID; id < endID; id++ { + s.wg.Add(1) + // spawning VUWorker goroutines with the context, test suite, metrics collector, rate limiter and waitgroup. + // the VUWorker will execute the test suite steps and report the results back to the MetricsCollector. + go func(id int) { + vuWorker := NewVUWorker(s.config, s.logger, id, ts, s.collector, s.limiter, &s.wg, exporter) + vuWorker.vuWorker(ctx) + }(id) + } + s.vuCownter += n + return nil +} diff --git a/keploy/pkg/service/load/service.go b/keploy/pkg/service/load/service.go new file mode 100644 index 0000000..d8dd2d3 --- /dev/null +++ b/keploy/pkg/service/load/service.go @@ -0,0 +1,9 @@ +package load + +import ( + "context" +) + +type Service interface { + Start(ctx context.Context) error +} diff --git a/keploy/pkg/service/load/threshold_evaluator.go b/keploy/pkg/service/load/threshold_evaluator.go new file mode 100644 index 0000000..72f2511 --- /dev/null +++ b/keploy/pkg/service/load/threshold_evaluator.go @@ -0,0 +1,299 @@ +package load + +import ( + "fmt" + "math" + "sort" + "strings" + "time" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/service/testsuite" + "go.uber.org/zap" +) + +type ThresholdReport struct { + Metric string `json:"metric"` + Condition string `json:"condition"` + Severity string `json:"severity"` + Comment string `json:"comment,omitempty"` + Actual interface{} `json:"actual"` + Pass bool `json:"pass"` +} + +type StepThresholdReport struct { + StepName string `json:"step_name"` + TotalRequests int `json:"total_requests"` + TotalFailures int `json:"total_failures"` + TotalBytesIn int64 `json:"total_bytes_in"` + TotalBytesOut int64 `json:"total_bytes_out"` + P95Latency time.Duration `json:"p95_latency"` + Thresholds []ThresholdReport `json:"thresholds"` +} + +type ThresholdEvaluator struct { + config *config.Config + logger *zap.Logger + ts *testsuite.TestSuite +} + +func NewThresholdEvaluator(cfg *config.Config, logger *zap.Logger, ts *testsuite.TestSuite) *ThresholdEvaluator { + return &ThresholdEvaluator{ + config: cfg, + logger: logger, + ts: ts, + } +} + +func (te *ThresholdEvaluator) Evaluate(steps []StepMetrics) []StepThresholdReport { + thresholds := te.ts.Spec.Load.Thresholds + if len(thresholds) == 0 { + te.logger.Info("No thresholds defined in TestSuite, skipping evaluation") + return nil + } + + var reports []StepThresholdReport + + for _, step := range steps { + var allResponseTimes = step.StepResponseTime + totalRequests := step.StepCount + totalFailures := step.StepFailure + totalBytesIn := step.StepBytesIn + totalBytesOut := step.StepBytesOut + + // Calculate P95 latency + // getting the index and value of 95th percentile from the response times. + var p95Idx int + var p95 time.Duration + if len(allResponseTimes) > 0 { + sort.Slice(allResponseTimes, func(i, j int) bool { return allResponseTimes[i] < allResponseTimes[j] }) + idx := int(math.Ceil(float64(len(allResponseTimes))*0.95)) - 1 + if idx < 0 { + idx = 0 + } + p95Idx = idx + p95 = allResponseTimes[p95Idx] + } + + // Calculate failed rate as a percentage + // If totalRequests is 0, we avoid division by zero by setting failedRate to 0. + var failedRate float64 + if totalRequests > 0 { + failedRate = (float64(totalFailures) / float64(totalRequests)) * 100 + } + + // Convert bytes to MB for reporting + // 1 MB = 1024 * 1024 bytes + dataReceivedMB := float64(totalBytesIn) / (1024 * 1024) + dataSentMB := float64(totalBytesOut) / (1024 * 1024) + + var stepReport StepThresholdReport + stepReport.StepName = step.StepName + stepReport.TotalRequests = totalRequests + stepReport.TotalFailures = totalFailures + stepReport.TotalBytesIn = totalBytesIn + stepReport.TotalBytesOut = totalBytesOut + stepReport.P95Latency = p95 + stepReport.Thresholds = make([]ThresholdReport, 0, len(thresholds)) + + for _, th := range thresholds { + switch th.Metric { + case "http_req_duration_p95": + pass := compareDuration(p95, th.Condition) + te.logger.Debug("Threshold check", + zap.String("step", step.StepName), + zap.String("metric", th.Metric), + zap.String("condition", th.Condition), + zap.String("actual", p95.String()), + zap.Bool("pass", pass), + zap.String("severity", th.Severity), + zap.String("comment", th.Comment), + ) + if !pass { + te.logger.Debug(fmt.Sprintf("Threshold failed: %s %s (actual: %s) for step %s", th.Metric, th.Condition, p95, step.StepName)) + } + stepReport.Thresholds = append(stepReport.Thresholds, ThresholdReport{ + Metric: th.Metric, + Condition: th.Condition, + Actual: p95.String(), + Pass: pass, + Severity: th.Severity, + Comment: th.Comment, + }) + case "http_req_failed_rate": + pass := compareFloat(failedRate, th.Condition) + te.logger.Debug("Threshold check", + zap.String("step", step.StepName), + zap.String("metric", th.Metric), + zap.String("condition", th.Condition), + zap.Float64("actual", failedRate), + zap.Bool("pass", pass), + zap.String("severity", th.Severity), + zap.String("comment", th.Comment), + ) + if !pass { + te.logger.Debug(fmt.Sprintf("Threshold failed: %s %s (actual: %.2f%%) for step %s", th.Metric, th.Condition, failedRate, step.StepName)) + } + stepReport.Thresholds = append(stepReport.Thresholds, ThresholdReport{ + Metric: th.Metric, + Condition: th.Condition, + Actual: failedRate, + Pass: pass, + Severity: th.Severity, + Comment: th.Comment, + }) + case "data_received": + pass := compareFloat(dataReceivedMB, th.Condition) + te.logger.Debug("Threshold check", + zap.String("step", step.StepName), + zap.String("metric", th.Metric), + zap.String("condition", th.Condition), + zap.Float64("actual", dataReceivedMB), + zap.Bool("pass", pass), + zap.String("severity", th.Severity), + zap.String("comment", th.Comment), + ) + if !pass { + te.logger.Debug(fmt.Sprintf("Threshold failed: %s %s (actual: %.2f MB) for step %s", th.Metric, th.Condition, dataReceivedMB, step.StepName)) + } + stepReport.Thresholds = append(stepReport.Thresholds, ThresholdReport{ + Metric: th.Metric, + Condition: th.Condition, + Actual: dataReceivedMB, + Pass: pass, + Severity: th.Severity, + Comment: th.Comment, + }) + case "data_sent": + pass := compareFloat(dataSentMB, th.Condition) + te.logger.Debug("Threshold check", + zap.String("step", step.StepName), + zap.String("metric", th.Metric), + zap.String("condition", th.Condition), + zap.Float64("actual", dataSentMB), + zap.Bool("pass", pass), + zap.String("severity", th.Severity), + zap.String("comment", th.Comment), + ) + if !pass { + te.logger.Debug(fmt.Sprintf("Threshold failed: %s %s (actual: %.2f MB) for step %s", th.Metric, th.Condition, dataSentMB, step.StepName)) + } + stepReport.Thresholds = append(stepReport.Thresholds, ThresholdReport{ + Metric: th.Metric, + Condition: th.Condition, + Actual: dataSentMB, + Pass: pass, + Severity: th.Severity, + Comment: th.Comment, + }) + default: + te.logger.Warn("Unknown threshold metric", zap.String("metric", th.Metric)) + } + } + reports = append(reports, stepReport) + } + return reports +} + +// compareDuration compares a time.Duration value with a condition string. +// The condition string can be in the format of "<", "<=", ">", ">=", "=", +// and can include a duration value like "500ms", "1s", etc. +// Returns true if the condition is met, false otherwise. +func compareDuration(val time.Duration, cond string) bool { + cond = strings.TrimSpace(cond) + if cond == "" { + return true + } + // seperate the given string into operator and value + // e.g. "<= 500ms" -> op = "<=", cmpStr = "500ms" + var op string + var cmpStr string + if strings.HasPrefix(cond, "<=") { + op = "<=" + cmpStr = strings.TrimSpace(cond[2:]) + } else if strings.HasPrefix(cond, "<") { + op = "<" + cmpStr = strings.TrimSpace(cond[1:]) + } else if strings.HasPrefix(cond, ">=") { + op = ">=" + cmpStr = strings.TrimSpace(cond[2:]) + } else if strings.HasPrefix(cond, ">") { + op = ">" + cmpStr = strings.TrimSpace(cond[1:]) + } else if strings.HasPrefix(cond, "=") { + op = "=" + cmpStr = strings.TrimSpace(cond[1:]) + } else { + return false + } + cmpDur, err := time.ParseDuration(cmpStr) + if err != nil { + return false + } + switch op { + case "<": + return val < cmpDur + case "<=": + return val <= cmpDur + case ">": + return val > cmpDur + case ">=": + return val >= cmpDur + case "=": + return val == cmpDur + } + return false +} + +// compareFloat compares a float64 value with a condition string. +// The condition string can be in the format of "<", "<=", ">", ">=", "=", +// and can include a value like "50%", "100MB", etc. +// Returns true if the condition is met, false otherwise. +func compareFloat(val float64, cond string) bool { + cond = strings.TrimSpace(cond) + if cond == "" { + return true + } + // separate the given string into operator and value + // e.g. "<= 50%" -> op = "<=", cmpStr = "50%" + // e.g. "> 100MB" -> op = ">", cmpStr = "100MB" + var op string + var cmpStr string + if strings.HasPrefix(cond, "<=") { + op = "<=" + cmpStr = strings.TrimSpace(cond[2:]) + } else if strings.HasPrefix(cond, "<") { + op = "<" + cmpStr = strings.TrimSpace(cond[1:]) + } else if strings.HasPrefix(cond, ">=") { + op = ">=" + cmpStr = strings.TrimSpace(cond[2:]) + } else if strings.HasPrefix(cond, ">") { + op = ">" + cmpStr = strings.TrimSpace(cond[1:]) + } else if strings.HasPrefix(cond, "=") { + op = "=" + cmpStr = strings.TrimSpace(cond[1:]) + } else { + return false + } + // Remove % or MB if present + cmpStr = strings.TrimSuffix(cmpStr, "%") + cmpStr = strings.TrimSuffix(cmpStr, "MB") + cmpStr = strings.TrimSpace(cmpStr) + cmpVal := 0.0 + fmt.Sscanf(cmpStr, "%f", &cmpVal) + switch op { + case "<": + return val < cmpVal + case "<=": + return val <= cmpVal + case ">": + return val > cmpVal + case ">=": + return val >= cmpVal + case "=": + return val == cmpVal + } + return false +} diff --git a/keploy/pkg/service/load/utils.go b/keploy/pkg/service/load/utils.go new file mode 100644 index 0000000..a4ac056 --- /dev/null +++ b/keploy/pkg/service/load/utils.go @@ -0,0 +1 @@ +package load diff --git a/keploy/pkg/service/load/vu_worker.go b/keploy/pkg/service/load/vu_worker.go new file mode 100644 index 0000000..cc63ab3 --- /dev/null +++ b/keploy/pkg/service/load/vu_worker.go @@ -0,0 +1,118 @@ +package load + +import ( + "context" + "sync" + "time" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/service/testsuite" + "go.uber.org/zap" + "golang.org/x/time/rate" +) + +type VUWorker struct { + config *config.Config + logger *zap.Logger + VUID int + ts *testsuite.TestSuite + mc *MetricsCollector + limiter *rate.Limiter + waitG *sync.WaitGroup + exporter *Exporter +} + +type VUReport struct { + VUID int `json:"vu_id"` + TSExecCount int `json:"ts_exec_count"` + TSExecFailure int `json:"ts_exec_failure"` + TSExecTime []time.Duration `json:"ts_exec_time"` + Steps []StepReport `json:"steps"` +} + +type StepReport struct { + StepName string `json:"step_name"` + StepCount int `json:"step_count"` + StepFailure int `json:"step_failure"` + StepResponseTime []time.Duration `json:"step_response_time"` + StepBytesIn int64 `json:"step_bytes_in"` + StepBytesOut int64 `json:"step_bytes_out"` +} + +func NewVUWorker(cfg *config.Config, logger *zap.Logger, id int, ts *testsuite.TestSuite, col *MetricsCollector, lim *rate.Limiter, wg *sync.WaitGroup, exp *Exporter) *VUWorker { + return &VUWorker{ + config: cfg, + logger: logger, + VUID: id, + ts: ts, + mc: col, + limiter: lim, + waitG: wg, + exporter: exp, + } +} + +func (w *VUWorker) vuWorker(ctx context.Context) { + VUReport := VUReport{ + VUID: w.VUID, + TSExecCount: 0, + TSExecFailure: 0, + TSExecTime: make([]time.Duration, 0), + Steps: make([]StepReport, len(w.ts.Spec.Steps)), + } + w.logger.Debug("Running virtual user", zap.Int("vuID", w.VUID)) + + for i, step := range w.ts.Spec.Steps { + VUReport.Steps[i] = StepReport{ + StepName: step.Name, + StepCount: 0, + StepResponseTime: make([]time.Duration, 0), + } + } + + tsExec, err := testsuite.NewTSExecutor(w.config, w.logger, true) + if err != nil { + w.logger.Error("Failed to create TestSuite executor", zap.Int("vuID", w.VUID), zap.Error(err)) + } + + // Set the TestSuite for the executor manually after skipping the parsing. + tsExec.Testsuite = w.ts + + for { + select { + case <-ctx.Done(): + // if the context duration is done, waits for reporting to the MetricsCollector. + w.mc.CollectVUReport(&VUReport) + w.logger.Debug("Virtual user context done", zap.Int("vuID", w.VUID)) + w.waitG.Done() + return + default: + execReport, err := tsExec.Execute(ctx, w.limiter) + w.exporter.mu.Lock() + if err != nil { // an execution failure occurs if any parameters needed is missing like the base-url, not on the step failure. + w.logger.Error("Failed to execute TestSuite", zap.Int("vuID", w.VUID), zap.Error(err)) + VUReport.TSExecCount++ + VUReport.TSExecFailure++ + // TODO: stop or continue + // return + } else { + w.logger.Debug("Virtual user executed TestSuite", zap.Int("vuID", w.VUID)) + VUReport.TSExecCount++ + VUReport.TSExecTime = append(VUReport.TSExecTime, execReport.ExecutionTime) + + // collecting per step results. + for i, step := range execReport.StepsResult { + if step.Status == "failed" { + VUReport.Steps[i].StepFailure++ + } + VUReport.Steps[i].StepCount++ + VUReport.Steps[i].StepResponseTime = append(VUReport.Steps[i].StepResponseTime, step.ResponseTime) + VUReport.Steps[i].StepBytesIn += step.ReqBytes + VUReport.Steps[i].StepBytesOut += step.ResBytes + } + } + w.exporter.mu.Unlock() + w.exporter.GetMetrics(VUReport) + } + } +} diff --git a/keploy/pkg/service/orchestrator/orchestrator.go b/keploy/pkg/service/orchestrator/orchestrator.go new file mode 100644 index 0000000..b130018 --- /dev/null +++ b/keploy/pkg/service/orchestrator/orchestrator.go @@ -0,0 +1,31 @@ +//go:build linux + +// Package orchestrator acts as a main brain for both the record and replay services +package orchestrator + +import ( + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/service/record" + "go.keploy.io/server/v2/pkg/service/replay" + "go.keploy.io/server/v2/pkg/service/tools" + + "go.uber.org/zap" +) + +type Orchestrator struct { + logger *zap.Logger + record record.Service + replay replay.Service + tools tools.Service + config *config.Config +} + +func New(logger *zap.Logger, record record.Service, tools tools.Service, replay replay.Service, config *config.Config) *Orchestrator { + return &Orchestrator{ + logger: logger, + record: record, + replay: replay, + tools: tools, + config: config, + } +} diff --git a/keploy/pkg/service/orchestrator/rerecord.go b/keploy/pkg/service/orchestrator/rerecord.go new file mode 100644 index 0000000..a3ff909 --- /dev/null +++ b/keploy/pkg/service/orchestrator/rerecord.go @@ -0,0 +1,334 @@ +//go:build linux + +package orchestrator + +import ( + "bufio" + "context" + "fmt" + "os" + "sort" + "strconv" + "time" + + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" +) + +func (o *Orchestrator) ReRecord(ctx context.Context) error { + // creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller + var stopReason string + var err error + + defer func() { + select { + case <-ctx.Done(): + default: + err := utils.Stop(o.logger, stopReason) + if err != nil { + utils.LogError(o.logger, err, "failed to stop recording") + } + } + }() + + // Get all the testsets + testSets, err := o.replay.GetAllTestSetIDs(ctx) + if err != nil { + errMsg := "Failed to get all testset IDs" + utils.LogError(o.logger, err, errMsg) + return err + } + + // Check for templates + o.checkForTemplates(ctx, testSets) + // Sort the testsets to ensure that the testcases are re-recorded in the same order + sort.SliceStable(testSets, func(i, j int) bool { + return testSets[i] < testSets[j] + }) + + var SelectedTests []string + + for _, testSet := range testSets { + if ctx.Err() != nil { + break + } + + if _, ok := o.config.Test.SelectedTests[testSet]; !ok && len(o.config.Test.SelectedTests) != 0 { + continue + } + + SelectedTests = append(SelectedTests, testSet) + + o.logger.Info("Re-recording testcases for the given testset", zap.String("testset", testSet)) + // Note: Here we've used child context without cancel to avoid the cancellation of the parent context. + // When we use errgroup and get an error from any of the go routines spawned by errgroup, it cancels the parent context. + // We don't want to stop the execution if there is an error in any of the test-set recording sessions, it should just skip that test-set and continue with the next one. + errGrp, _ := errgroup.WithContext(ctx) + recordCtx := context.WithoutCancel(ctx) + recordCtx, recordCtxCancel := context.WithCancel(recordCtx) + + var errCh = make(chan error, 1) + var replayErrCh = make(chan error, 1) + + //Keeping two back-to-back selects is used to not do blocking operation if parent ctx is done + + select { + case <-ctx.Done(): + default: + errGrp.Go(func() error { + defer utils.Recover(o.logger) + err := o.record.Start(recordCtx, true) + errCh <- err + return nil + }) + } + + select { + case <-ctx.Done(): + default: + errGrp.Go(func() error { + defer utils.Recover(o.logger) + allRecorded, err := o.replayTests(recordCtx, testSet) + + if allRecorded && err == nil { + o.logger.Info("Re-recorded testcases successfully for the given testset", zap.String("testset", testSet)) + } + if !allRecorded { + o.logger.Warn("Failed to re-record some testcases", zap.String("testset", testSet)) + stopReason = "failed to re-record some testcases" + } + + replayErrCh <- err + return nil + }) + } + + var err error + select { + case err = <-errCh: + if err != nil { + stopReason = "error while starting the recording" + utils.LogError(o.logger, err, stopReason, zap.String("testset", testSet)) + } + case err = <-replayErrCh: + if err != nil { + stopReason = "error while replaying the testcases" + utils.LogError(o.logger, err, stopReason, zap.String("testset", testSet)) + } + case <-ctx.Done(): + } + + if err == nil || ctx.Err() == nil { + // Sleep for 3 seconds to ensure that the recording has completed + time.Sleep(3 * time.Second) + } + + recordCtxCancel() + + // Wait for the recording to stop + err = errGrp.Wait() + if err != nil { + utils.LogError(o.logger, err, "failed to stop re-recording") + } + + // Check if the global context is done after each iteration + if ctx.Err() != nil { + break + } + } + + if stopReason != "" { + utils.LogError(o.logger, err, stopReason) + return fmt.Errorf("%s", stopReason) + } + + if ctx.Err() != nil { + stopReason = "context cancelled" + o.logger.Warn("Re-record was cancelled, keploy might have not recorded few test cases") + return nil + } + + stopReason = "Re-recorded all the selected testsets successfully" + if !o.config.InCi { + o.logger.Info("Re-record was successfull. Do you want to remove the older testsets? (y/n)", zap.Any("testsets", SelectedTests)) + reader := bufio.NewReader(os.Stdin) + input, err := reader.ReadString('\n') + if err != nil { + o.logger.Warn("Failed to read input. The older testsets will be kept.") + return nil + } + + if len(input) == 0 { + o.logger.Warn("Empty input. The older testsets will be kept.") + return nil + } + // Trimming the newline character for cleaner switch statement + input = input[:len(input)-1] + switch input { + case "y", "Y": + for _, testSet := range SelectedTests { + err := o.replay.DeleteTestSet(ctx, testSet) + if err != nil { + o.logger.Warn("Failed to delete the testset", zap.String("testset", testSet)) + } + } + o.logger.Info("Deleted the older testsets successfully") + case "n", "N": + o.logger.Info("skipping the deletion of older testsets") + default: + o.logger.Warn("Invalid input. The older testsets will be kept.") + } + } + return nil +} + +func (o *Orchestrator) replayTests(ctx context.Context, testSet string) (bool, error) { + //replay the recorded testcases + tcs, err := o.replay.GetTestCases(ctx, testSet) + if err != nil { + errMsg := "failed to get all testcases" + utils.LogError(o.logger, err, errMsg, zap.String("testset", testSet)) + return false, fmt.Errorf("%s", errMsg) + } + + if len(tcs) == 0 { + o.logger.Warn("No testcases found for the given testset", zap.String("testset", testSet)) + return false, nil + } + + host, port, err := pkg.ExtractHostAndPort(tcs[0].Curl) + if err != nil { + errMsg := "failed to extract host and port" + utils.LogError(o.logger, err, "") + o.logger.Debug("", zap.String("curl", tcs[0].Curl)) + return false, fmt.Errorf("%s", errMsg) + } + cmdType := utils.CmdType(o.config.CommandType) + var userIP string + delay := o.config.Test.Delay + time.Sleep(time.Duration(delay) * time.Second) + if utils.IsDockerCmd(cmdType) { + host = o.config.ContainerName + userIP, err = o.record.GetContainerIP(ctx, o.config.AppID) + if err != nil { + utils.LogError(o.logger, err, "failed to get the app ip") + return false, err + } + } + timeout := time.Duration(120+delay) * time.Second + + o.logger.Debug("", zap.String("host", host), zap.String("port", port), zap.Any("WaitTimeout", timeout), zap.Any("CommandType", cmdType)) + + if err := pkg.WaitForPort(ctx, host, port, timeout); err != nil { + utils.LogError(o.logger, err, "Waiting for port failed", zap.String("host", host), zap.String("port", port)) + return false, err + } + + // Read the template and secret values once per test set + testSetConf, err := o.replay.GetTestSetConf(ctx, testSet) + if err != nil { + o.logger.Debug("failed to read template values") + } + + utils.TemplatizedValues = map[string]interface{}{} + utils.SecretValues = map[string]interface{}{} + + if testSetConf != nil { + if testSetConf.Template != nil { + utils.TemplatizedValues = testSetConf.Template + } + + if testSetConf.Secret != nil { + utils.SecretValues = testSetConf.Secret + } + } + + allTcRecorded := true + var simErr bool + for _, tc := range tcs { + if ctx.Err() != nil { + return false, ctx.Err() + } + if utils.IsDockerCmd(cmdType) { + tc.HTTPReq.URL, err = utils.ReplaceHost(tc.HTTPReq.URL, userIP) + if err != nil { + utils.LogError(o.logger, err, "failed to replace host to docker container's IP") + break + } + o.logger.Debug("", zap.Any("replaced URL in case of docker env", tc.HTTPReq.URL)) + } + + if o.config.ReRecord.Host != "" { + tc.HTTPReq.URL, err = utils.ReplaceHost(tc.HTTPReq.URL, o.config.ReRecord.Host) + if err != nil { + utils.LogError(o.logger, err, "failed to replace host to provided host by the user") + break + } + } + + if o.config.ReRecord.Port != 0 { + tc.HTTPReq.URL, err = utils.ReplacePort(tc.HTTPReq.URL, strconv.Itoa(int(o.config.ReRecord.Port))) + if err != nil { + utils.LogError(o.logger, err, "failed to replace port to provided port by the user") + break + } + } + resp, err := pkg.SimulateHTTP(ctx, tc, testSet, o.logger, o.config.Test.APITimeout) + if err != nil { + utils.LogError(o.logger, err, "failed to simulate HTTP request") + if resp == nil { + allTcRecorded = false + } + simErr = true + continue // Proceed with the next command + } + + o.logger.Info("Re-recorded the testcase successfully", zap.String("testcase", tc.Name), zap.String("of testset", testSet)) + } + + if simErr { + return allTcRecorded, fmt.Errorf("got error while simulating HTTP request. Please make sure the related services are up and running") + } + + return allTcRecorded, nil +} + +// checkForTemplates checks if the testcases are already templatized. If not, it asks the user if they want to templatize the testcases before re-recording +func (o *Orchestrator) checkForTemplates(ctx context.Context, testSets []string) { + // Check if the testcases are already templatized. + var nonTemplatized []string + for _, testSet := range testSets { + if _, ok := o.config.Test.SelectedTests[testSet]; !ok && len(o.config.Test.SelectedTests) != 0 { + continue + } + + conf, err := o.replay.GetTestSetConf(ctx, testSet) + if err != nil || conf == nil || conf.Template == nil { + nonTemplatized = append(nonTemplatized, testSet) + } + } + + if len(nonTemplatized) == 0 { + return + } + + o.config.Templatize.TestSets = nonTemplatized + o.logger.Warn("The following testSets are not templatized. Do you want to templatize them to handle noisy fields?(y/n)", zap.Any("testSets:", nonTemplatized)) + reader := bufio.NewReader(os.Stdin) + input, err := reader.ReadString('\n') + if err != nil { + o.logger.Warn("failed to read input. Skipping templatization") + } + if input == "n\n" || input == "N\n" { + o.logger.Info("skipping templatization") + return + } + + if input == "y\n" || input == "Y\n" { + if err := o.tools.Templatize(ctx); err != nil { + utils.LogError(o.logger, err, "failed to templatize test cases, skipping templatization") + } + } +} diff --git a/keploy/pkg/service/orchestrator/service.go b/keploy/pkg/service/orchestrator/service.go new file mode 100644 index 0000000..01fc9fb --- /dev/null +++ b/keploy/pkg/service/orchestrator/service.go @@ -0,0 +1,7 @@ +package orchestrator + +import "context" + +type Service interface { + ReRecord(ctx context.Context) error +} diff --git a/keploy/pkg/service/record/record.go b/keploy/pkg/service/record/record.go new file mode 100755 index 0000000..bb9f429 --- /dev/null +++ b/keploy/pkg/service/record/record.go @@ -0,0 +1,390 @@ +// Package record provides functionality for recording and managing test cases and mocks. +package record + +import ( + "context" + "errors" + "fmt" + "strconv" + "strings" + + "time" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" + + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" +) + +type Recorder struct { + logger *zap.Logger + testDB TestDB + mockDB MockDB + telemetry Telemetry + instrumentation Instrumentation + testSetConf TestSetConfig + config *config.Config +} + +func New(logger *zap.Logger, testDB TestDB, mockDB MockDB, telemetry Telemetry, instrumentation Instrumentation, testSetConf TestSetConfig, config *config.Config) Service { + return &Recorder{ + logger: logger, + testDB: testDB, + mockDB: mockDB, + telemetry: telemetry, + instrumentation: instrumentation, + testSetConf: testSetConf, + config: config, + } +} + +func (r *Recorder) Start(ctx context.Context, reRecord bool) error { + + // creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller + errGrp, _ := errgroup.WithContext(ctx) + ctx = context.WithValue(ctx, models.ErrGroupKey, errGrp) + + runAppErrGrp, _ := errgroup.WithContext(ctx) + runAppCtx := context.WithoutCancel(ctx) + runAppCtx, runAppCtxCancel := context.WithCancel(runAppCtx) + + hookErrGrp, _ := errgroup.WithContext(ctx) + hookCtx := context.WithoutCancel(ctx) + hookCtx, hookCtxCancel := context.WithCancel(hookCtx) + hookCtx = context.WithValue(hookCtx, models.ErrGroupKey, hookErrGrp) + // reRecordCtx, reRecordCancel := context.WithCancel(ctx) + // defer reRecordCancel() // Cancel the context when the function returns + + var stopReason string + + // defining all the channels and variables required for the record + var runAppError models.AppError + var appErrChan = make(chan models.AppError, 1) + var insertTestErrChan = make(chan error, 10) + var insertMockErrChan = make(chan error, 10) + var appID uint64 + var newTestSetID string + var testCount = 0 + var mockCountMap = make(map[string]int) + + // defering the stop function to stop keploy in case of any error in record or in case of context cancellation + defer func() { + select { + case <-ctx.Done(): + default: + if !reRecord { + err := utils.Stop(r.logger, stopReason) + if err != nil { + utils.LogError(r.logger, err, "failed to stop recording") + } + } + } + runAppCtxCancel() + err := runAppErrGrp.Wait() + if err != nil { + utils.LogError(r.logger, err, "failed to stop application") + } + hookCtxCancel() + err = hookErrGrp.Wait() + if err != nil { + utils.LogError(r.logger, err, "failed to stop hooks") + } + err = errGrp.Wait() + if err != nil { + utils.LogError(r.logger, err, "failed to stop recording") + } + r.telemetry.RecordedTestSuite(newTestSetID, testCount, mockCountMap) + }() + + defer close(appErrChan) + defer close(insertTestErrChan) + defer close(insertMockErrChan) + + newTestSetID, err := r.GetNextTestSetID(ctx) + if err != nil { + stopReason = "failed to get new test-set id" + utils.LogError(r.logger, err, stopReason) + return fmt.Errorf("%s", stopReason) + } + + // Create config.yaml if metadata is provided + if r.config.Record.Metadata != "" { + r.createConfigWithMetadata(ctx, newTestSetID) + } + + //checking for context cancellation as we don't want to start the instrumentation if the context is cancelled + select { + case <-ctx.Done(): + return nil + default: + } + + // Instrument will setup the environment and start the hooks and proxy + appID, err = r.Instrument(hookCtx) + if err != nil { + stopReason = "failed to instrument the application" + utils.LogError(r.logger, err, stopReason) + return fmt.Errorf("%s", stopReason) + } + + r.config.AppID = appID + + // fetching test cases and mocks from the application and inserting them into the database + frames, err := r.GetTestAndMockChans(ctx, appID) + if err != nil { + stopReason = "failed to get data frames" + utils.LogError(r.logger, err, stopReason) + if ctx.Err() == context.Canceled { + return err + } + return fmt.Errorf("%s", stopReason) + } + + errGrp.Go(func() error { + for testCase := range frames.Incoming { + err := r.testDB.InsertTestCase(ctx, testCase, newTestSetID, true) + if err != nil { + if ctx.Err() == context.Canceled { + continue + } + insertTestErrChan <- err + } else { + testCount++ + r.telemetry.RecordedTestAndMocks() + } + } + return nil + }) + + errGrp.Go(func() error { + for mock := range frames.Outgoing { + err := r.mockDB.InsertMock(ctx, mock, newTestSetID) + if err != nil { + if ctx.Err() == context.Canceled { + continue + } + insertMockErrChan <- err + } else { + mockCountMap[mock.GetKind()]++ + r.telemetry.RecordedTestCaseMock(mock.GetKind()) + } + } + return nil + }) + + if !r.config.E2E { + runAppErrGrp.Go(func() error { + runAppError = r.instrumentation.Run(runAppCtx, appID, models.RunOptions{}) + if runAppError.AppErrorType == models.ErrCtxCanceled { + return nil + } + appErrChan <- runAppError + return nil + }) + } + + // setting a timer for recording + if r.config.Record.RecordTimer != 0 { + errGrp.Go(func() error { + r.logger.Info("Setting a timer of " + r.config.Record.RecordTimer.String() + " for recording") + timer := time.After(r.config.Record.RecordTimer) + select { + case <-timer: + r.logger.Warn("Time up! Stopping keploy") + err := utils.Stop(r.logger, "Time up! Stopping keploy") + if err != nil { + utils.LogError(r.logger, err, "failed to stop recording") + return errors.New("failed to stop recording") + } + case <-ctx.Done(): + return nil + } + return nil + }) + } + + // Waiting for the error to occur in any of the go routines + select { + case appErr := <-appErrChan: + switch appErr.AppErrorType { + case models.ErrCommandError: + stopReason = "error in running the user application, hence stopping keploy" + case models.ErrUnExpected: + stopReason = "user application terminated unexpectedly hence stopping keploy, please check application logs if this behaviour is not expected" + case models.ErrInternal: + stopReason = "internal error occured while hooking into the application, hence stopping keploy" + case models.ErrAppStopped: + stopReason = "user application terminated unexpectedly hence stopping keploy, please check application logs if this behaviour is not expected" + r.logger.Warn(stopReason, zap.Error(appErr)) + return nil + case models.ErrCtxCanceled: + return nil + case models.ErrTestBinStopped: + stopReason = "keploy test mode binary stopped, hence stopping keploy" + return nil + default: + stopReason = "unknown error received from application, hence stopping keploy" + } + + case err = <-insertTestErrChan: + stopReason = "error while inserting test case into db, hence stopping keploy" + case err = <-insertMockErrChan: + stopReason = "error while inserting mock into db, hence stopping keploy" + case <-ctx.Done(): + return nil + } + utils.LogError(r.logger, err, stopReason) + return fmt.Errorf("%s", stopReason) +} + +func (r *Recorder) Instrument(ctx context.Context) (uint64, error) { + var stopReason string + // setting up the environment for recording + appID, err := r.instrumentation.Setup(ctx, r.config.Command, models.SetupOptions{Container: r.config.ContainerName, DockerNetwork: r.config.NetworkName, DockerDelay: r.config.BuildDelay}) + if err != nil { + stopReason = "failed setting up the environment" + utils.LogError(r.logger, err, stopReason) + return 0, fmt.Errorf("%s", stopReason) + } + r.config.AppID = appID + + // checking for context cancellation as we don't want to start the hooks and proxy if the context is cancelled + select { + case <-ctx.Done(): + return appID, nil + default: + // Starting the hooks and proxy + hooks := models.HookOptions{ + Mode: models.MODE_RECORD, + EnableTesting: r.config.EnableTesting, + Rules: r.config.BypassRules, + E2E: r.config.E2E, + Port: r.config.Port, + } + err = r.instrumentation.Hook(ctx, appID, hooks) + if err != nil { + stopReason = "failed to start the hooks and proxy" + utils.LogError(r.logger, err, stopReason) + if ctx.Err() == context.Canceled { + return appID, err + } + return appID, fmt.Errorf("%s", stopReason) + } + } + return appID, nil +} + +func (r *Recorder) GetTestAndMockChans(ctx context.Context, appID uint64) (FrameChan, error) { + incomingOpts := models.IncomingOptions{ + Filters: r.config.Record.Filters, + BasePath: r.config.Record.BasePath, + } + incomingChan, err := r.instrumentation.GetIncoming(ctx, appID, incomingOpts) + if err != nil { + return FrameChan{}, fmt.Errorf("failed to get incoming test cases: %w", err) + } + + outgoingOpts := models.OutgoingOptions{ + Rules: r.config.BypassRules, + MongoPassword: r.config.Test.MongoPassword, + FallBackOnMiss: r.config.Test.FallBackOnMiss, + Backdate: time.Now(), + } + + outgoingChan, err := r.instrumentation.GetOutgoing(ctx, appID, outgoingOpts) + if err != nil { + return FrameChan{}, fmt.Errorf("failed to get outgoing mocks: %w", err) + } + + return FrameChan{ + Incoming: incomingChan, + Outgoing: outgoingChan, + }, nil +} + +func (r *Recorder) RunApplication(ctx context.Context, appID uint64, opts models.RunOptions) models.AppError { + return r.instrumentation.Run(ctx, appID, opts) +} + +func (r *Recorder) GetNextTestSetID(ctx context.Context) (string, error) { + testSetIDs, err := r.testDB.GetAllTestSetIDs(ctx) + if err != nil { + return "", fmt.Errorf("failed to get test set IDs: %w", err) + } + + if r.config.Record.Metadata == "" { + return pkg.NextID(testSetIDs, models.TestSetPattern), nil + } + r.config.Record.Metadata = utils.TrimSpaces(r.config.Record.Metadata) + meta, err := utils.ParseMetadata(r.config.Record.Metadata) + if err != nil || meta == nil { + return pkg.NextID(testSetIDs, models.TestSetPattern), nil + } + + nameVal, ok := meta["name"] + requestedName, isStr := nameVal.(string) + if !ok || !isStr || requestedName == "" { + return pkg.NextID(testSetIDs, models.TestSetPattern), nil + } + + existingIDs := make(map[string]struct{}, len(testSetIDs)) + for _, id := range testSetIDs { + existingIDs[id] = struct{}{} + } + + if _, occupied := existingIDs[requestedName]; !occupied { + return requestedName, nil + } + + var highestSuffix int + namePrefix := requestedName + "-" + for id := range existingIDs { + if !strings.HasPrefix(id, namePrefix) { + continue + } + suffixPart := id[len(namePrefix):] + if n, err := strconv.Atoi(suffixPart); err == nil && n > highestSuffix { + highestSuffix = n + } + } + + newSuffix := highestSuffix + 1 + assignedName := fmt.Sprintf("%s-%d", requestedName, newSuffix) + + r.logger.Warn(fmt.Sprintf( + "Test set name '%s' already exists, using '%s' instead. You can change this name if you want.", + requestedName, assignedName, + )) + + return assignedName, nil +} + +func (r *Recorder) GetContainerIP(ctx context.Context, id uint64) (string, error) { + return r.instrumentation.GetContainerIP(ctx, id) +} + +func (r *Recorder) createConfigWithMetadata(ctx context.Context, testSetID string) { + // Parse metadata from the config + metadata, err := utils.ParseMetadata(r.config.Record.Metadata) + if err != nil { + utils.LogError(r.logger, err, "failed to parse metadata", zap.String("metadata", r.config.Record.Metadata)) + return + } + testSet := &models.TestSet{ + PreScript: "", + PostScript: "", + Template: make(map[string]interface{}), + Metadata: metadata, + } + + err = r.testSetConf.Write(ctx, testSetID, testSet) + if err != nil { + utils.LogError(r.logger, err, "Failed to create test-set config file with metadata", zap.String("testSet", testSetID)) + return + } + + r.logger.Info("Created test-set config file with metadata") +} diff --git a/keploy/pkg/service/record/service.go b/keploy/pkg/service/record/service.go new file mode 100755 index 0000000..243ffb6 --- /dev/null +++ b/keploy/pkg/service/record/service.go @@ -0,0 +1,51 @@ +package record + +import ( + "context" + + "go.keploy.io/server/v2/pkg/models" +) + +type Instrumentation interface { + //Setup prepares the environment for the recording + Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) + //Hook will load hooks and start the proxy server. + Hook(ctx context.Context, id uint64, opts models.HookOptions) error + GetIncoming(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) + GetOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) (<-chan *models.Mock, error) + // Run is blocking call and will execute until error + Run(ctx context.Context, id uint64, opts models.RunOptions) models.AppError + GetContainerIP(ctx context.Context, id uint64) (string, error) +} + +type Service interface { + Start(ctx context.Context, reRecord bool) error + GetContainerIP(ctx context.Context, id uint64) (string, error) +} + +type TestDB interface { + GetAllTestSetIDs(ctx context.Context) ([]string, error) + InsertTestCase(ctx context.Context, tc *models.TestCase, testSetID string, enableLog bool) error + // GetTestCases(ctx context.Context, testID string) ([]*models.TestCase, error) +} + +type MockDB interface { + InsertMock(ctx context.Context, mock *models.Mock, testSetID string) error +} + +type TestSetConfig interface { + Read(ctx context.Context, testSetID string) (*models.TestSet, error) + Write(ctx context.Context, testSetID string, testSet *models.TestSet) error +} + +type Telemetry interface { + RecordedTestSuite(testSet string, testsTotal int, mockTotal map[string]int) + RecordedTestCaseMock(mockType string) + RecordedMocks(mockTotal map[string]int) + RecordedTestAndMocks() +} + +type FrameChan struct { + Incoming <-chan *models.TestCase + Outgoing <-chan *models.Mock +} diff --git a/keploy/pkg/service/record/utils.go b/keploy/pkg/service/record/utils.go new file mode 100644 index 0000000..aa96e8b --- /dev/null +++ b/keploy/pkg/service/record/utils.go @@ -0,0 +1,3 @@ +//go:build linux + +package record diff --git a/keploy/pkg/service/replay/hooks.go b/keploy/pkg/service/replay/hooks.go new file mode 100644 index 0000000..af60b20 --- /dev/null +++ b/keploy/pkg/service/replay/hooks.go @@ -0,0 +1,282 @@ +// Package replay provides the hooks for the replay service +package replay + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/golang-jwt/jwt/v4" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/service" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type Hooks struct { + logger *zap.Logger + cfg *config.Config + tsConfigDB TestSetConfig + storage Storage + auth service.Auth + instrumentation Instrumentation + mock *mock +} + +func NewHooks(logger *zap.Logger, cfg *config.Config, tsConfigDB TestSetConfig, storage Storage, auth service.Auth, instrumentation Instrumentation, mock *mock) TestHooks { + return &Hooks{ + cfg: cfg, + logger: logger, + tsConfigDB: tsConfigDB, + storage: storage, + auth: auth, + instrumentation: instrumentation, + mock: mock, + } +} + +func (h *Hooks) SimulateRequest(ctx context.Context, _ uint64, tc *models.TestCase, testSetID string) (interface{}, error) { + switch tc.Kind { + case models.HTTP: + h.logger.Debug("Simulating HTTP request", zap.Any("Test case", tc)) + return pkg.SimulateHTTP(ctx, tc, testSetID, h.logger, h.cfg.Test.APITimeout) + + case models.GRPC_EXPORT: + h.logger.Debug("Simulating gRPC request", zap.Any("Test case", tc)) + return pkg.SimulateGRPC(ctx, tc, testSetID, h.logger) + + default: + return nil, fmt.Errorf("unsupported test case kind: %s", tc.Kind) + } +} + +func (h *Hooks) BeforeTestRun(ctx context.Context, testRunID string) error { + h.logger.Debug("BeforeTestRun hook executed", zap.String("testRunID", testRunID)) + return nil +} + +func (h *Hooks) AfterTestSetRun(ctx context.Context, testSetID string, status bool) error { + + if h.cfg.Test.DisableMockUpload { + return nil + } + + if h.cfg.Test.BasePath != "" { + h.logger.Debug("Mocking is disabled when basePath is given", zap.String("testSetID", testSetID), zap.String("basePath", h.cfg.Test.BasePath)) + return nil + } + + if !status { + return nil + } + + token, err := h.auth.GetToken(ctx) + if err != nil || token == "" { + h.logger.Error("Failed to Authenticate user, skipping mock upload", zap.Error(err)) + return nil + } + h.mock.setToken(token) + + err = h.mock.upload(ctx, testSetID) + if err != nil { + h.logger.Warn("Failed to upload mock, hence skipping", zap.String("testSetID", testSetID), zap.Error(err)) + } + + return nil +} + +func (h *Hooks) BeforeTestSetRun(ctx context.Context, testSetID string) error { + + if h.cfg.Test.BasePath != "" { + h.logger.Debug("Mocking is disabled when basePath is given", zap.String("testSetID", testSetID), zap.String("basePath", h.cfg.Test.BasePath)) + return nil + } + + if h.cfg.Test.UseLocalMock { + h.logger.Debug("Using local mock file, as UseLocalMock is selected", zap.String("testSetID", testSetID)) + return nil + } + + token, err := h.auth.GetToken(ctx) + if err != nil { + h.logger.Warn("Failed to Authenticate user, continuing with local mock if present", zap.Error(err)) + return nil + } + h.mock.setToken(token) + + // Check if test-set config is present + tsConfig, err := h.tsConfigDB.Read(ctx, testSetID) + if err != nil || tsConfig == nil || tsConfig.MockRegistry == nil { + h.logger.Debug("test set config for upload mock not found, continuing with local mock", zap.String("testSetID", testSetID), zap.Error(err)) + return nil + } + + if tsConfig.MockRegistry.Mock == "" { + h.logger.Warn("Mock is empty in test-set config, continuing with local mock if present", zap.String("testSetID", testSetID)) + return nil + } + + if tsConfig.MockRegistry.App == "" { + h.logger.Warn("App name is empty in test-set config, continuing with local mock if present", zap.String("testSetID", testSetID)) + return nil + } + + // Check if mock file is already downloaded by previous test runs + localMockPath := filepath.Join(h.cfg.Path, testSetID, "mocks.yaml") + mockContent, err := os.ReadFile(localMockPath) + if err == nil { + if tsConfig.MockRegistry.Mock == utils.Hash(mockContent) { + h.logger.Debug("Mock file already exists, downloading from cloud is not necessary", zap.String("testSetID", testSetID), zap.String("mockPath", localMockPath)) + return nil + } + } + + if tsConfig.MockRegistry.App != h.cfg.AppName { + h.logger.Warn("App name in the keploy.yml does not match with the app name in the config.yml in the test-set", zap.String("test-set-config-AppName", tsConfig.MockRegistry.App), zap.String("global-config-Appname", h.cfg.AppName)) + h.logger.Warn("Using app name from the test-set's config.yml for mock retrieval", zap.String("appName", tsConfig.MockRegistry.App)) + } + + h.logger.Info("Downloading mock file from cloud...", zap.String("testSetID", testSetID)) + cloudFile, err := h.storage.Download(ctx, tsConfig.MockRegistry.Mock, tsConfig.MockRegistry.App, tsConfig.MockRegistry.User, token) + if err != nil { + h.logger.Error("Failed to download mock file", zap.Error(err)) + return err + } + + // Save the downloaded mock file to local + file, err := os.Create(localMockPath) + if err != nil { + h.logger.Error("Failed to create local file", zap.String("path", localMockPath), zap.Error(err)) + return err + } + defer func() { + err := file.Close() + if err != nil { + utils.LogError(h.logger, err, "failed to close the http response body") + } + }() + + done := make(chan struct{}) + + // Spinner goroutine + go func() { + spinnerChars := []rune{'|', '/', '-', '\\'} + i := 0 + for { + select { + case <-done: + fmt.Print("\r") // Clear spinner line after done + return + default: + fmt.Printf("\rDownloading... %c", spinnerChars[i%len(spinnerChars)]) + i++ + time.Sleep(100 * time.Millisecond) + } + } + }() + + _, err = io.Copy(file, cloudFile) + if err != nil { + close(done) + return err + } + close(done) + h.logger.Info("Mock file downloaded successfully") + + err = utils.AddToGitIgnore(h.logger, h.cfg.Path, "/*/mocks.yaml") + if err != nil { + utils.LogError(h.logger, err, "failed to add /*/mocks.yaml to .gitignore file") + } + + return nil +} + +func (h *Hooks) AfterTestRun(_ context.Context, testRunID string, testSetIDs []string, coverage models.TestCoverage) error { + h.logger.Debug("AfterTestRun hook executed", zap.String("testRunID", testRunID), zap.Any("testSetIDs", testSetIDs), zap.Any("coverage", coverage)) + return nil +} + +func (h *Hooks) GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) { + consumedMocks, err := h.instrumentation.GetConsumedMocks(ctx, id) + if err != nil { + h.logger.Error("failed to get consumed mocks", zap.Error(err)) + return nil, err + } + return consumedMocks, nil +} + +// Function to parse and extract claims from a JWT token without verification +func extractClaimsWithoutVerification(tokenString string) (jwt.MapClaims, error) { + token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) + if err != nil { + return nil, err + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok { + return claims, nil + } + return nil, fmt.Errorf("unable to parse claims") +} + +type getPlanRes struct { + Plan models.Plan `json:"plan"` + Error string `json:"error"` +} + +func getLatestPlan(ctx context.Context, logger *zap.Logger, serverUrl, token string) (string, error) { + logger.Debug("Getting the latest plan", zap.String("serverUrl", serverUrl), zap.String("token", token)) + + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/subscription/plan", serverUrl), nil) + if err != nil { + logger.Error("failed to create request", zap.Error(err)) + return "", fmt.Errorf("failed to get plan") + } + req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + logger.Error("http request failed", zap.Error(err)) + return "", fmt.Errorf("failed to get plan") + } + defer func() { + if cerr := resp.Body.Close(); cerr != nil { + logger.Error("failed to close response body", zap.Error(cerr)) + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + logger.Error("failed to read response body", zap.Error(err)) + return "", fmt.Errorf("failed to get plan") + } + + var res getPlanRes + if err := json.Unmarshal(body, &res); err != nil { + logger.Error("failed to unmarshal response", zap.Error(err)) + return "", fmt.Errorf("failed to get plan") + } + + if resp.StatusCode != http.StatusOK { + logger.Error("non-200 response from subscription/plan", zap.Int("status", resp.StatusCode), zap.String("api_error", res.Error)) + if res.Error != "" { + return "", fmt.Errorf("%s", res.Error) + } + return "", fmt.Errorf("failed to get plan") + } + + if res.Plan.Type == "" { + logger.Error("plan type missing in successful response", zap.Any("plan", res.Plan)) + return "", fmt.Errorf("plan not found") + } + + return res.Plan.Type, nil +} diff --git a/keploy/pkg/service/replay/integration_test.go b/keploy/pkg/service/replay/integration_test.go new file mode 100644 index 0000000..0d812a9 --- /dev/null +++ b/keploy/pkg/service/replay/integration_test.go @@ -0,0 +1,272 @@ +package replay + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +// TestChannelVsDelayComparison demonstrates the improvement of using channels over delays +func TestChannelVsDelayComparison(t *testing.T) { + // This test demonstrates the performance improvement of channel-based notification + // over the previous delay-based approach + + mockInstr := NewMockInstrumentation() + appID := uint64(12345) + + // Test 1: Channel-based approach (current implementation) + channelStartTime := time.Now() + + unloadCh := mockInstr.GetHookUnloadDone(appID) + inst := &InstrumentState{ + AppID: appID, + HookCancel: func() {}, + UnloadDone: unloadCh, + } + + channelComplete := make(chan bool, 1) + go func() { + <-inst.UnloadDone + channelComplete <- true + }() + + // Simulate unload happening quickly + go func() { + time.Sleep(10 * time.Millisecond) // Simulate quick unload + mockInstr.CloseUnloadChannel(appID) + }() + + select { + case <-channelComplete: + channelDuration := time.Since(channelStartTime) + t.Logf("Channel-based approach completed in: %v", channelDuration) + + // Should complete quickly (within reasonable time) + assert.Less(t, channelDuration, 100*time.Millisecond, "Channel approach should be fast") + + case <-time.After(200 * time.Millisecond): + t.Error("Channel approach should not timeout") + } + + // Test 2: Simulate old delay-based approach for comparison + delayStartTime := time.Now() + + // Previous implementation used fixed delay (2000ms as seen in commit) + oldDelay := 2000 * time.Millisecond + + // Simulate the old delay + time.Sleep(oldDelay) + delayDuration := time.Since(delayStartTime) + + t.Logf("Old delay-based approach would take: %v", delayDuration) + + // The channel approach should be significantly faster + // (This test just demonstrates the concept; in practice, the improvement depends on actual unload time) + assert.Greater(t, delayDuration, 100*time.Millisecond, "Old delay approach was slow") +} + +// TestHookReloadSequence demonstrates the complete hook reload sequence with proper signaling +func TestHookReloadSequence(t *testing.T) { + // This test simulates the exact sequence that happens in replay.go + // when reloading hooks between test sets + + mockInstr := NewMockInstrumentation() + + // Simulate multiple test sets + testSets := []struct { + name string + appID uint64 + }{ + {"test-set-1", 12345}, + {"test-set-2", 12346}, + {"test-set-3", 12347}, + } + + var currentInst *InstrumentState + totalReloadTime := time.Duration(0) + + for i, testSet := range testSets { + t.Logf("Processing %s", testSet.name) + + // For test sets after the first one, reload hooks + if i > 0 && currentInst != nil { + reloadStartTime := time.Now() + + // Step 1: Cancel current hooks (simulated) + t.Logf("Canceling hooks for previous test set") + + // Step 2: Wait for unload completion using channel + t.Logf("Waiting for hooks to be completely unloaded") + + unloadComplete := make(chan bool, 1) + go func(inst *InstrumentState) { + <-inst.UnloadDone + unloadComplete <- true + }(currentInst) + + // Simulate the unload happening + go func() { + time.Sleep(20 * time.Millisecond) // Simulate unload time + mockInstr.CloseUnloadChannel(currentInst.AppID) + }() + + // Wait for unload completion + select { + case <-unloadComplete: + t.Logf("Hooks unload completed") + case <-time.After(500 * time.Millisecond): + t.Errorf("Unload should complete for %s", testSet.name) + continue + } + + reloadDuration := time.Since(reloadStartTime) + totalReloadTime += reloadDuration + t.Logf("Hook reload for %s completed in: %v", testSet.name, reloadDuration) + } + + // Step 3: Create new instrument state for new test set + unloadCh := mockInstr.GetHookUnloadDone(testSet.appID) + currentInst = &InstrumentState{ + AppID: testSet.appID, + HookCancel: func() {}, + UnloadDone: unloadCh, + } + + t.Logf("New instrument state created for %s with AppID: %d", testSet.name, testSet.appID) + + // Verify new channel is not closed + select { + case <-currentInst.UnloadDone: + t.Errorf("New channel for %s should not be closed", testSet.name) + default: + // Expected behavior + } + } + + t.Logf("Total time for all hook reloads: %v", totalReloadTime) + + // With proper channel signaling, total reload time should be reasonable + // (much less than what it would be with fixed delays) + expectedMaxTime := time.Duration(len(testSets)-1) * 100 * time.Millisecond // Allow 100ms per reload + assert.Less(t, totalReloadTime, expectedMaxTime, "Total reload time should be reasonable") +} + +// TestProperResourceCleanup verifies that channel-based signaling ensures proper resource cleanup +func TestProperResourceCleanup(t *testing.T) { + // This test verifies the key benefit of the channel approach: + // ensuring that resources are properly cleaned up before proceeding + + mockInstr := NewMockInstrumentation() + appID := uint64(12345) + + // Simulate resource state + resourceCleaned := false + + unloadCh := mockInstr.GetHookUnloadDone(appID) + inst := &InstrumentState{ + AppID: appID, + HookCancel: func() {}, + UnloadDone: unloadCh, + } + + // Simulate waiting for unload with resource cleanup + cleanupComplete := make(chan bool, 1) + go func() { + // Wait for unload signal + <-inst.UnloadDone + + // Verify resource was cleaned up before signal + if resourceCleaned { + cleanupComplete <- true + } else { + cleanupComplete <- false + } + }() + + // Simulate cleanup process + go func() { + time.Sleep(30 * time.Millisecond) // Simulate cleanup time + resourceCleaned = true // Mark resource as cleaned + mockInstr.CloseUnloadChannel(appID) // Signal completion only after cleanup + }() + + // Verify cleanup happened before signal + select { + case success := <-cleanupComplete: + assert.True(t, success, "Resource should be cleaned up before unload signal") + case <-time.After(200 * time.Millisecond): + t.Error("Cleanup should complete within timeout") + } + + // This demonstrates the key advantage: we only proceed after confirming cleanup is done + t.Log("Verified: Channel-based approach ensures proper resource cleanup before proceeding") +} + +// TestRaceConditionPrevention tests that channels prevent race conditions +func TestRaceConditionPrevention(t *testing.T) { + // This test demonstrates how channels prevent race conditions that could occur + // with time-based delays (where new operations might start before cleanup is complete) + + mockInstr := NewMockInstrumentation() + appID1 := uint64(12345) + appID2 := uint64(12346) + + // First operation + unloadCh1 := mockInstr.GetHookUnloadDone(appID1) + inst1 := &InstrumentState{ + AppID: appID1, + HookCancel: func() {}, + UnloadDone: unloadCh1, + } + + operationOrder := make([]string, 0) + orderMutex := make(chan struct{}, 1) + + // Start first operation cleanup + go func() { + <-inst1.UnloadDone + orderMutex <- struct{}{} + operationOrder = append(operationOrder, "first_cleanup_complete") + <-orderMutex + }() + + // Start second operation (should wait for first to complete) + go func() { + time.Sleep(10 * time.Millisecond) // Small delay to ensure order + orderMutex <- struct{}{} + operationOrder = append(operationOrder, "second_operation_start") + <-orderMutex + + // Create second instrument state + unloadCh2 := mockInstr.GetHookUnloadDone(appID2) + _ = &InstrumentState{ + AppID: appID2, + HookCancel: func() {}, + UnloadDone: unloadCh2, + } + + orderMutex <- struct{}{} + operationOrder = append(operationOrder, "second_operation_complete") + <-orderMutex + }() + + // Complete first operation + go func() { + time.Sleep(50 * time.Millisecond) // Simulate cleanup time + mockInstr.CloseUnloadChannel(appID1) + }() + + // Wait for completion + time.Sleep(200 * time.Millisecond) + + // Verify order: first cleanup should complete before second operation starts its resource allocation + t.Logf("Operation order: %v", operationOrder) + + // With proper channel signaling, we ensure ordered execution + assert.Contains(t, operationOrder, "first_cleanup_complete", "First operation should complete") + assert.Contains(t, operationOrder, "second_operation_complete", "Second operation should complete") + + // This test demonstrates that channels provide proper synchronization + t.Log("Verified: Channel-based approach prevents race conditions") +} diff --git a/keploy/pkg/service/replay/mock.go b/keploy/pkg/service/replay/mock.go new file mode 100644 index 0000000..c21f98e --- /dev/null +++ b/keploy/pkg/service/replay/mock.go @@ -0,0 +1,297 @@ +package replay + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +var osReadFile224 = os.ReadFile +var osCreate224 = os.Create +var timeSleep224 = time.Sleep +var extractClaimsWithoutVerification224 = extractClaimsWithoutVerification +var getLatestPlan224 = getLatestPlan + +type mock struct { + cfg *config.Config + storage Storage + logger *zap.Logger + tsConfigDB TestSetConfig + token string +} + +func (m *mock) setToken(token string) { + m.token = token +} + +func (m *mock) download(ctx context.Context, testSetID string) error { + // Add nil check protection to prevent segmentation fault + if m.storage == nil { + m.logger.Error("Storage service is not initialized, cannot download mocks") + return fmt.Errorf("storage service is not initialized") + } + + // Check if test-set config is present + tsConfig, err := m.tsConfigDB.Read(ctx, testSetID) + if err != nil || tsConfig == nil || tsConfig.MockRegistry == nil { + m.logger.Error("test set mock config not found", zap.String("testSetID", testSetID), zap.Error(err)) + return fmt.Errorf("mock registry config not found") + } + + if tsConfig.MockRegistry.Mock == "" { + m.logger.Error("Mock is empty in test-set config", zap.String("testSetID", testSetID)) + return fmt.Errorf("mock is empty in test-set config") + } + + if tsConfig.MockRegistry.App == "" { + m.logger.Warn("App name is empty in test-set config", zap.String("testSetID", testSetID)) + return fmt.Errorf("app name is empty in test-set config") + } + + // Check if mock file is already downloaded by previous test runs + localMockPath := filepath.Join(m.cfg.Path, testSetID, "mocks.yaml") + mockContent, err := osReadFile224(localMockPath) + if err == nil { + if tsConfig.MockRegistry.Mock == utils.Hash(mockContent) { + m.logger.Info("Mock file already exists, downloading from cloud is not necessary", zap.String("testSetID", testSetID), zap.String("mockPath", localMockPath)) + return nil + } + var response string + + if len(mockContent) == 0 { + m.logger.Warn("Local mock file is empty, proceeding with download from keploy registry", zap.String("testSetID", testSetID)) + response = "y" + } else { + m.logger.Warn("Local mock file is different from the one in the Keploy registry.") + // Prompt user for confirmation to override the local mock file + fmt.Print("The mock file present locally is different from the one in the Keploy registry. Do you want to override the local mock file with the version from the registry? (y/n): ") + } + + // Create a channel to listen for context cancellation (Ctrl+C) + cancelChan := make(chan struct{}) + + // Start a goroutine to wait for user input asynchronously + go func() { + + if len(mockContent) == 0 { + cancelChan <- struct{}{} + return + } + + _, err := fmt.Scanln(&response) + if err != nil { + response = "n" // Default to 'no' if there's an error reading input + } + cancelChan <- struct{}{} + }() + + select { + case <-cancelChan: + // Normalize user input + response = strings.ToLower(strings.TrimSpace(response)) + if response != "y" && response != "yes" { + m.logger.Info("Keeping the local mock file", zap.String("testSetID", testSetID)) + return nil + } + + m.logger.Info("Overriding the local mock file with the version from the Keploy registry", zap.String("testSetID", testSetID)) + + case <-ctx.Done(): // context cancellation (Ctrl+C) + m.logger.Warn("Download interrupted by user") + return ctx.Err() // Return the context cancellation error + } + } + + if tsConfig.MockRegistry.App != m.cfg.AppName { + m.logger.Warn("App name in the keploy.yml does not match with the app name in the config.yml in the test-set", zap.String("test-set-config-AppName", tsConfig.MockRegistry.App), zap.String("global-config-Appname", m.cfg.AppName)) + m.logger.Warn("Using app name from the test-set's config.yml for mock retrieval", zap.String("appName", tsConfig.MockRegistry.App)) + } + + m.logger.Info("Downloading mock file from cloud...", zap.String("testSetID", testSetID)) + cloudFile, err := m.storage.Download(ctx, tsConfig.MockRegistry.Mock, tsConfig.MockRegistry.App, tsConfig.MockRegistry.User, m.token) + if err != nil { + m.logger.Error("Failed to download mock file", zap.Error(err)) + return err + } + + // Save the downloaded mock file to local + file, err := osCreate224(localMockPath) + if err != nil { + m.logger.Error("Failed to create local file", zap.String("path", localMockPath), zap.Error(err)) + return err + } + defer func() { + err := file.Close() + if err != nil { + utils.LogError(m.logger, err, "failed to close the http response body") + } + }() + + done := make(chan struct{}) + + // Spinner goroutine + go func() { + spinnerChars := []rune{'|', '/', '-', '\\'} + i := 0 + for { + select { + case <-done: + fmt.Print("\r") // Clear spinner line after done + return + default: + fmt.Printf("\rDownloading... %c", spinnerChars[i%len(spinnerChars)]) + i++ + timeSleep224(100 * time.Millisecond) + } + } + }() + + _, err = io.Copy(file, cloudFile) + if err != nil { + close(done) + return err + } + close(done) + m.logger.Info("Mock file downloaded successfully") + + err = utils.AddToGitIgnore(m.logger, m.cfg.Path, "/*/mocks.yaml") + if err != nil { + utils.LogError(m.logger, err, "failed to add /*/mocks.yaml to .gitignore file") + } + + return nil +} + +func (m *mock) upload(ctx context.Context, testSetID string) error { + // Add nil check protection to prevent segmentation fault + if m.storage == nil { + m.logger.Error("Storage service is not initialized, cannot upload mocks") + return fmt.Errorf("storage service is not initialized") + } + + claims, err := extractClaimsWithoutVerification224(m.token) + var role, username string + var ok bool + if err != nil { + m.logger.Error("Failed to extract claim from token for mock upload", zap.Error(err)) + return err + } + + if role, ok = claims["role"].(string); !ok || role == "" { + m.logger.Error("Role not found in the token, skipping mock upload") + return fmt.Errorf("failed to upload mock file: role not found in the token") + } + + if username, ok = claims["username"].(string); !ok { + m.logger.Error("Username not found in the token, skipping mock upload") + return fmt.Errorf("failed to upload mock file: username not found in the token") + } + + // get the plan of the current user + plan, err := getLatestPlan224(ctx, m.logger, m.cfg.APIServerURL, m.token) + if err != nil { + m.logger.Error("Failed to get latest plan of the user", zap.Error(err)) + return err + } + + m.logger.Debug("The latest plan", zap.Any("Plan", plan)) + + // Inspect local mock file + localMockPath := filepath.Join(m.cfg.Path, testSetID, "mocks.yaml") + mockFileContent, err := osReadFile224(localMockPath) + if err != nil { + m.logger.Error("Failed to read mock file for mock upload", zap.String("path", localMockPath), zap.Error(err)) + return err + } + + // If mock file is empty, return error + if len(mockFileContent) == 0 { + m.logger.Warn("Mock file is empty, skipping upload", zap.String("testSetID", testSetID), zap.String("mockPath", localMockPath)) + return nil + } + + mockHash := utils.Hash(mockFileContent) + mockFileReader := bytes.NewReader(mockFileContent) + + // Cross verify the local mock file with the test-set config + tsConfig, err := m.tsConfigDB.Read(ctx, testSetID) + // If test-set config is not found, upload the mock file + if err != nil || tsConfig == nil || tsConfig.MockRegistry == nil { + // create ts config + var prescript, postscript string + var template map[string]interface{} + var metadata map[string]interface{} + if tsConfig != nil { + prescript = tsConfig.PreScript + postscript = tsConfig.PostScript + template = tsConfig.Template + metadata = tsConfig.Metadata + } + tsConfig = &models.TestSet{ + PreScript: prescript, + PostScript: postscript, + Template: template, + MockRegistry: &models.MockRegistry{ + Mock: mockHash, + App: m.cfg.AppName, + }, + Metadata: metadata, + } + + if plan == "Free" { + if username == "" { + m.logger.Error("Username not found in the token for Free plan") + return fmt.Errorf("failed to upload mock file: username not found in the token") + } + tsConfig.MockRegistry.User = username + } + + m.logger.Info("uploading mock file...", zap.Any("testSet", testSetID)) + + err = m.storage.Upload(ctx, mockFileReader, mockHash, m.cfg.AppName, m.token) + if err != nil { + m.logger.Error("Failed to upload mock file", zap.Error(err)) + return err + } + + err := m.tsConfigDB.Write(ctx, testSetID, tsConfig) + if err != nil { + m.logger.Error("Failed to write test set config", zap.Error(err)) + return err + } + return nil + } + + // If mock file is already uploaded, skip the upload + if tsConfig.MockRegistry.Mock == mockHash { + m.logger.Info("Mock file is already uploaded, skipping upload", zap.String("testSetID", testSetID), zap.String("mockPath", localMockPath)) + return nil + } + + // If mock file is changed, upload the new mock file + m.logger.Debug("Mock file has changed, uploading new mock", zap.String("testSetID", testSetID), zap.String("mockPath", localMockPath)) + + m.logger.Info("uploading mock file...", zap.Any("testSet", testSetID)) + err = m.storage.Upload(ctx, mockFileReader, mockHash, m.cfg.AppName, m.token) + if err != nil { + m.logger.Error("Failed to upload mock file", zap.Error(err)) + return err + } + + err = utils.AddToGitIgnore(m.logger, m.cfg.Path, "/*/mocks.yaml") + if err != nil { + utils.LogError(m.logger, err, "failed to add /*/mocks.yaml to .gitignore file") + } + + return nil +} diff --git a/keploy/pkg/service/replay/replay.go b/keploy/pkg/service/replay/replay.go new file mode 100644 index 0000000..365bf69 --- /dev/null +++ b/keploy/pkg/service/replay/replay.go @@ -0,0 +1,1793 @@ +package replay + +import ( + // "bytes" + + "context" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "sort" + "strconv" + "strings" + "syscall" + + "time" + + "facette.io/natsort" + "github.com/k0kubun/pp/v3" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg" + matcherUtils "go.keploy.io/server/v2/pkg/matcher" + grpcMatcher "go.keploy.io/server/v2/pkg/matcher/grpc" + httpMatcher "go.keploy.io/server/v2/pkg/matcher/http" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/platform/coverage" + "go.keploy.io/server/v2/pkg/platform/coverage/golang" + "go.keploy.io/server/v2/pkg/platform/coverage/java" + "go.keploy.io/server/v2/pkg/platform/coverage/javascript" + "go.keploy.io/server/v2/pkg/platform/coverage/python" + "go.keploy.io/server/v2/pkg/service" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" +) + +var completeTestReport = make(map[string]TestReportVerdict) +var totalTests int +var totalTestPassed int +var totalTestFailed int +var totalTestIgnored int +var totalTestTimeTaken time.Duration +var failedTCsBySetID = make(map[string][]string) + +var HookImpl TestHooks + +type Replayer struct { + logger *zap.Logger + testDB TestDB + mockDB MockDB + reportDB ReportDB + testSetConf TestSetConfig + telemetry Telemetry + instrumentation Instrumentation + config *config.Config + auth service.Auth + mock *mock + instrument bool + isLastTestSet bool + isLastTestCase bool +} + +func NewReplayer(logger *zap.Logger, testDB TestDB, mockDB MockDB, reportDB ReportDB, testSetConf TestSetConfig, telemetry Telemetry, instrumentation Instrumentation, auth service.Auth, storage Storage, config *config.Config) Service { + + // TODO: add some comment. + mock := &mock{ + cfg: config, + storage: storage, + logger: logger, + tsConfigDB: testSetConf, + } + + // set the request emulator for simulating test case requests, if not set + if HookImpl == nil { + SetTestHooks(NewHooks(logger, config, testSetConf, storage, auth, instrumentation, mock)) + } + + instrument := config.Command != "" + return &Replayer{ + logger: logger, + testDB: testDB, + mockDB: mockDB, + reportDB: reportDB, + testSetConf: testSetConf, + telemetry: telemetry, + instrumentation: instrumentation, + config: config, + instrument: instrument, + auth: auth, + mock: mock, + } +} + +func (r *Replayer) Start(ctx context.Context) error { + + // creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller + g, ctx := errgroup.WithContext(ctx) + ctx = context.WithValue(ctx, models.ErrGroupKey, g) + + var hookCancel context.CancelFunc + var stopReason = "replay completed successfully" + + // defering the stop function to stop keploy in case of any error in record or in case of context cancellation + defer func() { + select { + case <-ctx.Done(): + break + default: + r.logger.Info("stopping Keploy", zap.String("reason", stopReason)) + } + if hookCancel != nil { + hookCancel() + } + err := g.Wait() + if err != nil { + utils.LogError(r.logger, err, "failed to stop replaying") + } + }() + + testSetIDs, err := r.testDB.GetAllTestSetIDs(ctx) + if err != nil { + stopReason = fmt.Sprintf("failed to get all test set ids: %v", err) + utils.LogError(r.logger, err, stopReason) + return fmt.Errorf("%s", stopReason) + } + + if len(testSetIDs) == 0 { + recordCmd := models.HighlightGrayString("keploy record") + errMsg := fmt.Sprintf("No test sets found in the keploy folder. Please record testcases using %s command", recordCmd) + utils.LogError(r.logger, err, errMsg) + return fmt.Errorf("%s", errMsg) + } + + testRunID, err := r.GetNextTestRunID(ctx) + if err != nil { + stopReason = fmt.Sprintf("failed to get next test run id: %v", err) + utils.LogError(r.logger, err, stopReason) + return fmt.Errorf("%s", stopReason) + } + + var language config.Language + var executable string + // only find language to calculate coverage if instrument is true + if r.instrument { + language, executable = utils.DetectLanguage(r.logger, r.config.Command) + // if language is not provided and language detected is known + // then set the language to detected language + if r.config.Test.Language == "" { + if language == models.Unknown { + r.logger.Warn("failed to detect language, skipping coverage caluclation. please use --language to manually set the language") + r.config.Test.SkipCoverage = true + } else { + r.logger.Warn(fmt.Sprintf("%s language detected. please use --language to manually set the language if needed", language)) + } + r.config.Test.Language = language + } else if language != r.config.Test.Language && language != models.Unknown { + utils.LogError(r.logger, nil, "language detected is different from the language provided") + r.config.Test.SkipCoverage = true + } + } + + r.logger.Debug("language detected", zap.String("language", r.config.Test.Language.String()), zap.String("executable", executable)) + + var cov coverage.Service + switch r.config.Test.Language { + case models.Go: + cov = golang.New(ctx, r.logger, r.reportDB, r.config.Command, r.config.Test.CoverageReportPath, r.config.CommandType) + case models.Python: + // if the executable is not starting with "python" or "python3" then skipCoverage + if !strings.HasPrefix(executable, "python") && !strings.HasPrefix(executable, "python3") { + r.logger.Warn("python command not python or python3, skipping coverage calculation") + r.config.Test.SkipCoverage = true + } + cov = python.New(ctx, r.logger, r.reportDB, r.config.Command, executable) + case models.Javascript: + cov = javascript.New(ctx, r.logger, r.reportDB, r.config.Command) + case models.Java: + cov = java.New(ctx, r.logger, r.reportDB, r.config.Command, r.config.Test.JacocoAgentPath, executable) + default: + r.config.Test.SkipCoverage = true + } + if !r.config.Test.SkipCoverage { + if utils.CmdType(r.config.CommandType) == utils.Native { + r.config.Command, err = cov.PreProcess(r.config.Test.DisableLineCoverage) + + if err != nil { + r.config.Test.SkipCoverage = true + } + } + err = os.Setenv("CLEAN", "true") // related to javascript coverage calculation + if err != nil { + r.config.Test.SkipCoverage = true + r.logger.Warn("failed to set CLEAN env variable, skipping coverage caluclation", zap.Error(err)) + } + } + + // Instrument will load the hooks and start the proxy + inst, err := r.Instrument(ctx) + if err != nil { + stopReason = fmt.Sprintf("failed to instrument: %v", err) + utils.LogError(r.logger, err, stopReason) + if ctx.Err() == context.Canceled { + return err + } + return fmt.Errorf("%s", stopReason) + } + + hookCancel = inst.HookCancel + + var testSetResult bool + testRunResult := true + abortTestRun := false + var flakyTestSets []string + var testSets []string + for _, testSetID := range testSetIDs { + if _, ok := r.config.Test.SelectedTests[testSetID]; !ok && len(r.config.Test.SelectedTests) != 0 { + continue + } + testSets = append(testSets, testSetID) + } + if len(testSets) == 0 { + testSets = testSetIDs + } + + // Sort the testsets. + natsort.Sort(testSets) + + err = HookImpl.BeforeTestRun(ctx, testRunID) + if err != nil { + stopReason = fmt.Sprintf("failed to run before test run hook: %v", err) + utils.LogError(r.logger, err, stopReason) + } + + // setting the appId for the first test-set. + inst.AppID = r.config.AppID + for i, testSet := range testSets { + testSetResult = false + + // Reload hooks before each test set if this is not the first test set + // This ensures fresh eBPF state and prevents issues between test runs + if i > 0 && r.instrument { + + // Cancel the current hooks and wait for cleanup to complete + if hookCancel != nil { + hookCancel() + // Wait for hooks to be completely unloaded using the channel signal + // This ensures that all eBPF resources are properly released before we reload + r.logger.Debug("Waiting for hooks to be completely unloaded", zap.String("testSet", testSet)) + <-inst.UnloadDone + r.logger.Debug("Hooks unload completed", zap.String("testSet", testSet)) + } + + r.logger.Info("Reloading hooks for test set", zap.String("testSet", testSet), zap.Int("testSetIndex", i+1), zap.Int("totalTestSets", len(testSets))) + + // Reload hooks for the new test set with retry mechanism + newInst, err := r.reloadHooks(ctx, inst.AppID) + if err != nil { + stopReason = fmt.Sprintf("failed to reload hooks for test set %s: %v", testSet, err) + utils.LogError(r.logger, err, stopReason) + if ctx.Err() == context.Canceled { + return err + } + return fmt.Errorf("%s", stopReason) + } + hookCancel = newInst.HookCancel + // Update the inst with the new hook cancel function, app ID, and unload done channel + inst.HookCancel = newInst.HookCancel + inst.AppID = newInst.AppID + inst.UnloadDone = newInst.UnloadDone + r.logger.Info("Successfully reloaded hooks for test set", zap.String("testSet", testSet), zap.Uint64("newAppID", newInst.AppID)) + } + + err := HookImpl.BeforeTestSetRun(ctx, testSet) + if err != nil { + stopReason = fmt.Sprintf("failed to run before test hook: %v", err) + utils.LogError(r.logger, err, stopReason) + if ctx.Err() == context.Canceled { + return err + } + return fmt.Errorf("%s", stopReason) + } + + if !r.config.Test.SkipCoverage { + err = os.Setenv("TESTSETID", testSet) // related to java coverage calculation + if err != nil { + r.config.Test.SkipCoverage = true + r.logger.Warn("failed to set TESTSETID env variable, skipping coverage caluclation", zap.Error(err)) + } + } + + // check if its the last testset running - + + if i == len(testSets)-1 { + r.isLastTestSet = true + } + + var ( + initTotal, initPassed, initFailed, initIgnored int + initTimeTaken time.Duration + ) + + initTotal = totalTests + initPassed = totalTestPassed + initFailed = totalTestFailed + initIgnored = totalTestIgnored + initTimeTaken = totalTestTimeTaken + + var initialFailedTCs map[string]bool + flaky := false // only be changed during replay with --must-pass flag set + for attempt := 1; attempt <= int(r.config.Test.MaxFlakyChecks); attempt++ { + + // clearing testcase from map is required for 2 reasons: + // 1st: in next attempt, we need to append results in a fresh array, + // rather than appending in the old array which would contain outdated tc results. + // 2nd: in must-pass mode, we delete the failed testcases from the map + // if the array has some failed testcases, which has already been removed, then not cleaning + // the array would mean deleting the already deleted failed testcases again (error). + r.reportDB.ClearTestCaseResults(ctx, testRunID, testSet) + + // overwrite with values before testset run, so after all reruns we don't get a cummulative value + // gathered from rerunning, instead only metrics from the last rerun would get added to the variables. + totalTests = initTotal + totalTestPassed = initPassed + totalTestFailed = initFailed + totalTestIgnored = initIgnored + totalTestTimeTaken = initTimeTaken + + r.logger.Info("running", zap.String("test-set", models.HighlightString(testSet)), zap.Int("attempt", attempt)) + testSetStatus, err := r.RunTestSet(ctx, testSet, testRunID, inst.AppID, false) + if err != nil { + stopReason = fmt.Sprintf("failed to run test set: %v", err) + utils.LogError(r.logger, err, stopReason) + if ctx.Err() == context.Canceled { + return err + } + return fmt.Errorf("%s", stopReason) + } + switch testSetStatus { + case models.TestSetStatusAppHalted: + testSetResult = false + abortTestRun = true + case models.TestSetStatusInternalErr: + testSetResult = false + abortTestRun = true + case models.TestSetStatusFaultUserApp: + testSetResult = false + abortTestRun = true + case models.TestSetStatusUserAbort: + return nil + case models.TestSetStatusFailed: + testSetResult = false + case models.TestSetStatusPassed: + testSetResult = true + case models.TestSetStatusIgnored: + testSetResult = false + case models.TestSetStatusNoTestsToRun: + testSetResult = false + } + + if testSetStatus != models.TestSetStatusIgnored { + testRunResult = testRunResult && testSetResult + if abortTestRun { + break + } + } + + tcResults, err := r.reportDB.GetTestCaseResults(ctx, testRunID, testSet) + if err != nil { + if testSetStatus != models.TestSetStatusNoTestsToRun { + utils.LogError(r.logger, err, "failed to get testcase results") + } + break + } + failedTcIDs := getFailedTCs(tcResults) + failedTCsBySetID[testSet] = failedTcIDs + + // checking for flakiness when --must-pass flag is not set + // else if --must-pass is set, delete the failed testcases and rerun + if !r.config.Test.MustPass { + // populate the map only once at first iteration for flakiness test + if attempt == 1 { + initialFailedTCs = make(map[string]bool) + for _, id := range failedTcIDs { + initialFailedTCs[id] = true + } + continue + } + // checking if there is no mismatch in failed testcases across max retries + // check both length and value + if len(failedTcIDs) != len(initialFailedTCs) { + utils.LogError(r.logger, nil, "the testset is flaky, rerun the testset with --must-pass flag to remove flaky testcases", zap.String("testSet", testSet)) + // don't run more attempts if the testset is flaky + flakyTestSets = append(flakyTestSets, testSet) + break + } + for _, id := range failedTcIDs { + if _, ok := initialFailedTCs[id]; !ok { + flaky = true + utils.LogError(r.logger, nil, "the testset is flaky, rerun the testset with --must-pass flag to remove flaky testcases", zap.String("testSet", testSet)) + break + } + } + if flaky { + // don't run more attempts if the testset is flaky + flakyTestSets = append(flakyTestSets, testSet) + break + } + continue + } + + // this would be executed only when --must-pass flag is set + // we would be removing failed testcases + if r.config.Test.MaxFailAttempts == 0 { + utils.LogError(r.logger, nil, "no. of testset failure occured during rerun reached maximum limit, testset still failing, increase count of maxFailureAttempts", zap.String("testSet", testSet)) + break + } + if len(failedTcIDs) == 0 { + // if no testcase failed in this attempt move to next attempt + continue + } + + r.logger.Info("deleting failing testcases", zap.String("testSet", testSet), zap.Any("testCaseIDs", failedTcIDs)) + + if err := r.testDB.DeleteTests(ctx, testSet, failedTcIDs); err != nil { + utils.LogError(r.logger, err, "failed to delete failing testcases", zap.String("testSet", testSet), zap.Any("testCaseIDs", failedTcIDs)) + break + } + // after deleting rerun it maxFlakyChecks times to be sure that no further testcase fails + // and if it does then delete those failing testcases and rerun it again maxFlakyChecks times + r.config.Test.MaxFailAttempts-- + attempt = 0 + } + + if abortTestRun { + break + } + + err = HookImpl.AfterTestSetRun(ctx, testSet, testSetResult) + if err != nil { + utils.LogError(r.logger, err, "failed to execute after test set run hook", zap.Any("testSet", testSet)) + } + + if i == 0 && !r.config.Test.SkipCoverage { + err = os.Setenv("CLEAN", "false") // related to javascript coverage calculation + if err != nil { + r.config.Test.SkipCoverage = true + r.logger.Warn("failed to set CLEAN env variable, skipping coverage caluclation.", zap.Error(err)) + } + err = os.Setenv("APPEND", "--append") // related to python coverage calculation + if err != nil { + r.config.Test.SkipCoverage = true + r.logger.Warn("failed to set APPEND env variable, skipping coverage caluclation.", zap.Error(err)) + } + } + } + if !r.config.Test.SkipCoverage && r.config.Test.Language == models.Java { + err = java.MergeAndGenerateJacocoReport(ctx, r.logger) + if err != nil { + r.config.Test.SkipCoverage = true + } + } + + if len(flakyTestSets) > 0 { + r.logger.Warn("flaky testsets detected, please rerun the specific testsets with --must-pass flag to remove flaky testcases", zap.Any("testSets", flakyTestSets)) + } + + testRunStatus := "fail" + if testRunResult { + testRunStatus = "pass" + } + + if testRunResult && r.config.Test.DisableMockUpload { + r.logger.Warn("To enable storing mocks in cloud, please use --disableMockUpload=false flag or test:disableMockUpload:false in config file") + } + + r.telemetry.TestRun(totalTestPassed, totalTestFailed, len(testSets), testRunStatus) + + if !abortTestRun { + r.printSummary(ctx, testRunResult) + coverageData := models.TestCoverage{} + var err error + if !r.config.Test.SkipCoverage { + r.logger.Info("calculating coverage for the test run and inserting it into the report") + coverageData, err = cov.GetCoverage() + if err == nil { + r.logger.Sugar().Infoln(models.HighlightPassingString("Total Coverage Percentage: ", coverageData.TotalCov)) + err = cov.AppendCoverage(&coverageData, testRunID) + if err != nil { + utils.LogError(r.logger, err, "failed to update report with the coverage data") + } + + } else { + r.logger.Warn("failed to calculate coverage for the test run", zap.Any("error", err)) + } + } + + //executing afterTestRun hook, executed after running all the test-sets + err = HookImpl.AfterTestRun(ctx, testRunID, testSets, coverageData) + if err != nil { + utils.LogError(r.logger, err, "failed to execute after test run hook") + } + } + + // return non-zero error code so that pipeline processes + // know that there is a failure in tests + if !testRunResult { + utils.ErrCode = 1 + } + return nil +} + +func (r *Replayer) Instrument(ctx context.Context) (*InstrumentState, error) { + if !r.instrument { + r.logger.Info("Keploy will not mock the outgoing calls when base path is provided", zap.Any("base path", r.config.Test.BasePath)) + return &InstrumentState{}, nil + } + appID, err := r.instrumentation.Setup(ctx, r.config.Command, models.SetupOptions{Container: r.config.ContainerName, DockerNetwork: r.config.NetworkName, DockerDelay: r.config.BuildDelay}) + if err != nil { + if errors.Is(err, context.Canceled) { + return &InstrumentState{}, err + } + return &InstrumentState{}, fmt.Errorf("failed to setup instrumentation: %w", err) + } + r.config.AppID = appID + + var cancel context.CancelFunc + // starting the hooks and proxy + select { + case <-ctx.Done(): + return &InstrumentState{}, context.Canceled + default: + hookCtx := context.WithoutCancel(ctx) + hookCtx, cancel = context.WithCancel(hookCtx) + err = r.instrumentation.Hook(hookCtx, appID, models.HookOptions{Mode: models.MODE_TEST, EnableTesting: r.config.EnableTesting, Rules: r.config.BypassRules}) + if err != nil { + cancel() + if errors.Is(err, context.Canceled) { + return &InstrumentState{}, err + } + return &InstrumentState{}, fmt.Errorf("failed to start the hooks and proxy: %w", err) + } + } + return &InstrumentState{AppID: appID, HookCancel: cancel, UnloadDone: r.instrumentation.GetHookUnloadDone(appID)}, nil +} + +// reloadHooks cancels existing hooks and reloads them for the next test set. +// This ensures that any stale eBPF state is cleared and fresh hooks are loaded, +// which can help with reliability and prevent issues between test set runs. +// This method handles the app context preservation to avoid app deletion during reload. +func (r *Replayer) reloadHooks(ctx context.Context, appID uint64) (*InstrumentState, error) { + if !r.instrument { + return &InstrumentState{}, nil + } + + r.logger.Debug("Reloading eBPF hooks", zap.Uint64("appID", appID)) + + // The challenge is that calling Hook again will set up new cleanup that deletes the app + // when the context is canceled. We need to create a fresh setup. + + // First, set up the app again since it might have been deleted during cleanup + newAppID, err := r.instrumentation.Setup(ctx, r.config.Command, models.SetupOptions{ + Container: r.config.ContainerName, + DockerNetwork: r.config.NetworkName, + DockerDelay: r.config.BuildDelay, + }) + if err != nil { + return &InstrumentState{}, fmt.Errorf("failed to setup instrumentation during hook reload: %w", err) + } + + // Update the config with the new app ID + r.config.AppID = newAppID + + // Create a retry mechanism in case of temporary race conditions + var lastErr error + maxRetries := 5 + baseDelay := 200 * time.Millisecond + + for attempt := 1; attempt <= maxRetries; attempt++ { + // Check for context cancellation + select { + case <-ctx.Done(): + return &InstrumentState{}, context.Canceled + default: + } + + // Add a small delay before each attempt to let any remaining cleanup finish + if attempt > 1 { + delay := baseDelay * time.Duration(attempt) // Linear backoff + r.logger.Debug("Retrying hook reload", zap.Int("attempt", attempt), zap.Duration("delay", delay)) + time.Sleep(delay) + } + + // Start fresh hooks with the new app ID + hookCtx := context.WithoutCancel(ctx) + hookCtx, cancel := context.WithCancel(hookCtx) + + err := r.instrumentation.Hook(hookCtx, newAppID, models.HookOptions{ + Mode: models.MODE_TEST, + EnableTesting: r.config.EnableTesting, + Rules: r.config.BypassRules, + }) + if err != nil { + cancel() + lastErr = err + if errors.Is(err, context.Canceled) { + return &InstrumentState{}, err + } + // If this failed due to a race condition, wait and retry with exponential backoff + if attempt < maxRetries { + delay := baseDelay * time.Duration(attempt*attempt) // Quadratic backoff + r.logger.Debug("Hook reload failed, retrying", zap.Int("attempt", attempt), zap.Duration("delay", delay), zap.Error(err)) + time.Sleep(delay) + continue + } + return &InstrumentState{}, fmt.Errorf("failed to reload hooks after %d attempts: %w", maxRetries, lastErr) + } + + // Success - return the new hook state with the new app ID + r.logger.Debug("Successfully reloaded eBPF hooks", zap.Uint64("oldAppID", appID), zap.Uint64("newAppID", newAppID), zap.Int("attempt", attempt)) + return &InstrumentState{AppID: newAppID, HookCancel: cancel, UnloadDone: r.instrumentation.GetHookUnloadDone(newAppID)}, nil + } + + // This should never be reached, but just in case + return &InstrumentState{}, fmt.Errorf("failed to reload hooks after %d attempts: %w", maxRetries, lastErr) +} + +func (r *Replayer) GetNextTestRunID(ctx context.Context) (string, error) { + testRunIDs, err := r.reportDB.GetAllTestRunIDs(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return "", err + } + return "", fmt.Errorf("failed to get all test run ids: %w", err) + } + return pkg.NextID(testRunIDs, models.TestRunTemplateName), nil +} + +func (r *Replayer) GetAllTestSetIDs(ctx context.Context) ([]string, error) { + return r.testDB.GetAllTestSetIDs(ctx) +} + +func (r *Replayer) GetTestCases(ctx context.Context, testID string) ([]*models.TestCase, error) { + return r.testDB.GetTestCases(ctx, testID) +} + +func (r *Replayer) RunTestSet(ctx context.Context, testSetID string, testRunID string, appID uint64, serveTest bool) (models.TestSetStatus, error) { + + // creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller + runTestSetErrGrp, runTestSetCtx := errgroup.WithContext(ctx) + runTestSetCtx = context.WithValue(runTestSetCtx, models.ErrGroupKey, runTestSetErrGrp) + runTestSetCtx, runTestSetCtxCancel := context.WithCancel(runTestSetCtx) + + startTime := time.Now() + + exitLoopChan := make(chan bool, 2) + defer func() { + runTestSetCtxCancel() + err := runTestSetErrGrp.Wait() + if err != nil { + utils.LogError(r.logger, err, "error in testLoopErrGrp") + } + close(exitLoopChan) + }() + + testCases, err := r.testDB.GetTestCases(runTestSetCtx, testSetID) + if err != nil { + return models.TestSetStatusFailed, fmt.Errorf("failed to get test cases: %w", err) + } + + if len(testCases) == 0 { + r.logger.Warn("no valid test cases found to run for test set", zap.String("test-set", testSetID)) + + testReport := &models.TestReport{ + Version: models.GetVersion(), + TestSet: testSetID, + Status: string(models.TestSetStatusNoTestsToRun), + Total: 0, + Ignored: 0, + } + err = r.reportDB.InsertReport(runTestSetCtx, testRunID, testSetID, testReport) + if err != nil { + utils.LogError(r.logger, err, "failed to insert report") + return models.TestSetStatusFailed, err + } + return models.TestSetStatusNoTestsToRun, nil + } + + if _, ok := r.config.Test.IgnoredTests[testSetID]; ok && len(r.config.Test.IgnoredTests[testSetID]) == 0 { + testReport := &models.TestReport{ + Version: models.GetVersion(), + TestSet: testSetID, + Status: string(models.TestSetStatusIgnored), + Total: len(testCases), + Ignored: len(testCases), + } + + err = r.reportDB.InsertReport(runTestSetCtx, testRunID, testSetID, testReport) + if err != nil { + utils.LogError(r.logger, err, "failed to insert report") + return models.TestSetStatusFailed, err + } + + verdict := TestReportVerdict{ + total: testReport.Total, + failed: 0, + passed: 0, + ignored: testReport.Ignored, + status: true, + duration: time.Duration(0), + } + + completeTestReport[testSetID] = verdict + totalTests += testReport.Total + totalTestIgnored += testReport.Ignored + + return models.TestSetStatusIgnored, nil + } + + var conf *models.TestSet + conf, err = r.testSetConf.Read(runTestSetCtx, testSetID) + if err != nil { + if strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "The system cannot find the file specified") { + r.logger.Info("test-set config file not found, continuing execution...", zap.String("test-set", testSetID)) + } else { + return models.TestSetStatusFailed, fmt.Errorf("failed to read test set config: %w", err) + } + } + if conf == nil { + conf = &models.TestSet{} + } + + if conf.PreScript != "" { + r.logger.Info("Running Pre-script", zap.String("script", conf.PreScript), zap.String("test-set", testSetID)) + err := r.executeScript(runTestSetCtx, conf.PreScript) + if err != nil { + return models.TestSetStatusFaultScript, fmt.Errorf("failed to execute pre-script: %w", err) + } + } + + var appErrChan = make(chan models.AppError, 1) + var appErr models.AppError + var success int + var failure int + var ignored int + var totalConsumedMocks = map[string]models.MockState{} + + testSetStatus := models.TestSetStatusPassed + testSetStatusByErrChan := models.TestSetStatusRunning + + cmdType := utils.CmdType(r.config.CommandType) + var userIP string + + filteredMocks, unfilteredMocks, err := r.GetMocks(ctx, testSetID, models.BaseTime, time.Now()) + if err != nil { + return models.TestSetStatusFailed, err + } + + pkg.InitSortCounter(int64(max(len(filteredMocks), len(unfilteredMocks)))) + + err = r.instrumentation.MockOutgoing(runTestSetCtx, appID, models.OutgoingOptions{ + Rules: r.config.BypassRules, + MongoPassword: r.config.Test.MongoPassword, + SQLDelay: time.Duration(r.config.Test.Delay), + FallBackOnMiss: r.config.Test.FallBackOnMiss, + Mocking: r.config.Test.Mocking, + Backdate: testCases[0].HTTPReq.Timestamp, + }) + if err != nil { + utils.LogError(r.logger, err, "failed to mock outgoing") + return models.TestSetStatusFailed, err + } + + // filtering is redundant, but we need to set the mocks + err = r.FilterAndSetMocks(ctx, appID, filteredMocks, unfilteredMocks, models.BaseTime, time.Now(), totalConsumedMocks) + if err != nil { + return models.TestSetStatusFailed, err + } + + if r.instrument { + if !serveTest { + runTestSetErrGrp.Go(func() error { + defer utils.Recover(r.logger) + appErr = r.RunApplication(runTestSetCtx, appID, models.RunOptions{ + AppCommand: conf.AppCommand, + }) + if appErr.AppErrorType == models.ErrCtxCanceled { + return nil + } + appErrChan <- appErr + return nil + }) + } + + // Checking for errors in the mocking and application + runTestSetErrGrp.Go(func() error { + defer utils.Recover(r.logger) + select { + case err := <-appErrChan: + switch err.AppErrorType { + case models.ErrCommandError: + testSetStatusByErrChan = models.TestSetStatusFaultUserApp + case models.ErrUnExpected: + testSetStatusByErrChan = models.TestSetStatusAppHalted + case models.ErrAppStopped: + testSetStatusByErrChan = models.TestSetStatusAppHalted + case models.ErrCtxCanceled: + return nil + case models.ErrInternal: + testSetStatusByErrChan = models.TestSetStatusInternalErr + default: + testSetStatusByErrChan = models.TestSetStatusAppHalted + } + utils.LogError(r.logger, err, "application failed to run") + case <-runTestSetCtx.Done(): + testSetStatusByErrChan = models.TestSetStatusUserAbort + } + exitLoopChan <- true + runTestSetCtxCancel() + return nil + }) + + // Delay for user application to run + select { + case <-time.After(time.Duration(r.config.Test.Delay) * time.Second): + case <-runTestSetCtx.Done(): + return models.TestSetStatusUserAbort, context.Canceled + } + + if utils.IsDockerCmd(cmdType) { + userIP, err = r.instrumentation.GetContainerIP(ctx, appID) + if err != nil { + return models.TestSetStatusFailed, err + } + } + } + + selectedTests := matcherUtils.ArrayToMap(r.config.Test.SelectedTests[testSetID]) + ignoredTests := matcherUtils.ArrayToMap(r.config.Test.IgnoredTests[testSetID]) + + testCasesCount := len(testCases) + + if len(selectedTests) != 0 { + testCasesCount = len(selectedTests) + } + + // Inserting the initial report for the test set + testReport := &models.TestReport{ + Version: models.GetVersion(), + Total: testCasesCount, + Status: string(models.TestStatusRunning), + } + + err = r.reportDB.InsertReport(runTestSetCtx, testRunID, testSetID, testReport) + if err != nil { + utils.LogError(r.logger, err, "failed to insert report") + return models.TestSetStatusFailed, err + } + + // var to exit the loop + var exitLoop bool + // var to store the error in the loop + var loopErr error + utils.TemplatizedValues = conf.Template + utils.SecretValues = conf.Secret + + // Add secret files to .gitignore if they exist + if len(utils.SecretValues) > 0 { + err = utils.AddToGitIgnore(r.logger, r.config.Path, "/*/secret.yaml") + if err != nil { + r.logger.Warn("Failed to add secret files to .gitignore", zap.Error(err)) + } + } + + for idx, testCase := range testCases { + + // check if its the last test case running + if idx == len(testCases)-1 && r.isLastTestSet { + r.isLastTestCase = true + testCase.IsLast = true + } + + if _, ok := selectedTests[testCase.Name]; !ok && len(selectedTests) != 0 { + continue + } + + if _, ok := ignoredTests[testCase.Name]; ok { + testCaseResult := &models.TestResult{ + Kind: models.HTTP, + Name: testSetID, + Status: models.TestStatusIgnored, + TestCaseID: testCase.Name, + TestCasePath: filepath.Join(r.config.Path, testSetID), + MockPath: filepath.Join(r.config.Path, testSetID, "mocks.yaml"), + } + loopErr = r.reportDB.InsertTestCaseResult(runTestSetCtx, testRunID, testSetID, testCaseResult) + if loopErr != nil { + utils.LogError(r.logger, err, "failed to insert test case result") + break + } + ignored++ + continue + } + + // replace the request URL's BasePath/origin if provided + if r.config.Test.BasePath != "" { + newURL, err := ReplaceBaseURL(r.config.Test.BasePath, testCase.HTTPReq.URL) + if err != nil { + r.logger.Warn("failed to replace the request basePath", zap.String("testcase", testCase.Name), zap.String("basePath", r.config.Test.BasePath), zap.Error(err)) + } else { + testCase.HTTPReq.URL = newURL + } + r.logger.Debug("test case request origin", zap.String("testcase", testCase.Name), zap.String("TestCaseURL", testCase.HTTPReq.URL), zap.String("basePath", r.config.Test.BasePath)) + } + + // Checking for errors in the mocking and application + select { + case <-exitLoopChan: + testSetStatus = testSetStatusByErrChan + exitLoop = true + default: + } + + if exitLoop { + break + } + + var testStatus models.TestStatus + var testResult *models.Result + var testPass bool + var loopErr error + + err = r.FilterAndSetMocks(runTestSetCtx, appID, filteredMocks, unfilteredMocks, testCase.HTTPReq.Timestamp, testCase.HTTPResp.Timestamp, totalConsumedMocks) + if err != nil { + utils.LogError(r.logger, err, "failed to filter and set mocks") + break + } + + // Handle Docker environment IP replacement + if utils.IsDockerCmd(cmdType) { + err = r.replaceHostInTestCase(testCase, userIP, "docker container's IP") + if err != nil { + break + } + } + + // Handle user-provided host replacement + if r.config.Test.Host != "" { + err = r.replaceHostInTestCase(testCase, r.config.Test.Host, "host provided by the user") + if err != nil { + break + } + } + + // Handle user-provided port replacement + if r.config.Test.Port != 0 { + err = r.replacePortInTestCase(testCase, strconv.Itoa(int(r.config.Test.Port))) + if err != nil { + break + } + } + + started := time.Now().UTC() + resp, loopErr := HookImpl.SimulateRequest(runTestSetCtx, appID, testCase, testSetID) + if loopErr != nil { + utils.LogError(r.logger, loopErr, "failed to simulate request") + failure++ + testSetStatus = models.TestSetStatusFailed + testCaseResult := r.CreateFailedTestResult(testCase, testSetID, started, loopErr.Error()) + loopErr = r.reportDB.InsertTestCaseResult(runTestSetCtx, testRunID, testSetID, testCaseResult) + if loopErr != nil { + utils.LogError(r.logger, loopErr, "failed to insert test case result for simulation error") + break + } + continue + } + + var consumedMocks []models.MockState + if r.instrument { + consumedMocks, err = HookImpl.GetConsumedMocks(runTestSetCtx, appID) + if err != nil { + utils.LogError(r.logger, err, "failed to get consumed filtered mocks") + } + for _, m := range consumedMocks { + totalConsumedMocks[m.Name] = m + } + } + + r.logger.Debug("test case kind", zap.Any("kind", testCase.Kind)) + + switch testCase.Kind { + case models.HTTP: + httpResp, ok := resp.(*models.HTTPResp) + if !ok { + r.logger.Error("invalid response type for HTTP test case") + failure++ + testSetStatus = models.TestSetStatusFailed + testCaseResult := r.CreateFailedTestResult(testCase, testSetID, started, "invalid response type for HTTP test case") + loopErr = r.reportDB.InsertTestCaseResult(runTestSetCtx, testRunID, testSetID, testCaseResult) + if loopErr != nil { + utils.LogError(r.logger, loopErr, fmt.Sprintf("failed to insert test case result for type assertion error in %s test case", testCase.Kind)) + break + } + continue + } + testPass, testResult = r.compareHTTPResp(testCase, httpResp, testSetID) + + case models.GRPC_EXPORT: + grpcResp, ok := resp.(*models.GrpcResp) + if !ok { + r.logger.Error("invalid response type for gRPC test case") + failure++ + testSetStatus = models.TestSetStatusFailed + testCaseResult := r.CreateFailedTestResult(testCase, testSetID, started, "invalid response type for gRPC test case") + loopErr = r.reportDB.InsertTestCaseResult(runTestSetCtx, testRunID, testSetID, testCaseResult) + if loopErr != nil { + utils.LogError(r.logger, loopErr, "failed to insert test case result for type assertion error") + break + } + continue + } + testPass, testResult = r.compareGRPCResp(testCase, grpcResp, testSetID) + } + + if !testPass { + // log the consumed mocks during the test run of the test case for test set + r.logger.Info("result", zap.Any("testcase id", models.HighlightFailingString(testCase.Name)), zap.Any("testset id", models.HighlightFailingString(testSetID)), zap.Any("passed", models.HighlightFailingString(testPass))) + r.logger.Debug("Consumed Mocks", zap.Any("mocks", consumedMocks)) + } else { + r.logger.Info("result", zap.Any("testcase id", models.HighlightPassingString(testCase.Name)), zap.Any("testset id", models.HighlightPassingString(testSetID)), zap.Any("passed", models.HighlightPassingString(testPass))) + } + if testPass { + testStatus = models.TestStatusPassed + success++ + } else { + testStatus = models.TestStatusFailed + failure++ + testSetStatus = models.TestSetStatusFailed + } + + if testResult != nil { + var testCaseResult *models.TestResult + + switch testCase.Kind { + case models.HTTP: + httpResp := resp.(*models.HTTPResp) + + testCaseResult = &models.TestResult{ + Kind: models.HTTP, + Name: testSetID, + Status: testStatus, + Started: started.Unix(), + Completed: time.Now().UTC().Unix(), + TestCaseID: testCase.Name, + Req: models.HTTPReq{ + Method: testCase.HTTPReq.Method, + ProtoMajor: testCase.HTTPReq.ProtoMajor, + ProtoMinor: testCase.HTTPReq.ProtoMinor, + URL: testCase.HTTPReq.URL, + URLParams: testCase.HTTPReq.URLParams, + Header: testCase.HTTPReq.Header, + Body: testCase.HTTPReq.Body, + Binary: testCase.HTTPReq.Binary, + Form: testCase.HTTPReq.Form, + Timestamp: testCase.HTTPReq.Timestamp, + }, + Res: *httpResp, + TestCasePath: filepath.Join(r.config.Path, testSetID), + MockPath: filepath.Join(r.config.Path, testSetID, "mocks.yaml"), + Noise: testCase.Noise, + Result: *testResult, + } + case models.GRPC_EXPORT: + grpcResp := resp.(*models.GrpcResp) + + testCaseResult = &models.TestResult{ + Kind: models.GRPC_EXPORT, + Name: testSetID, + Status: testStatus, + Started: started.Unix(), + Completed: time.Now().UTC().Unix(), + TestCaseID: testCase.Name, + GrpcReq: testCase.GrpcReq, + GrpcRes: *grpcResp, + TestCasePath: filepath.Join(r.config.Path, testSetID), + MockPath: filepath.Join(r.config.Path, testSetID, "mocks.yaml"), + Noise: testCase.Noise, + Result: *testResult, + } + } + + if testCaseResult != nil { + loopErr = r.reportDB.InsertTestCaseResult(runTestSetCtx, testRunID, testSetID, testCaseResult) + if loopErr != nil { + utils.LogError(r.logger, loopErr, "failed to insert test case result") + break + } + } else { + utils.LogError(r.logger, nil, "test case result is nil") + break + } + } else { + utils.LogError(r.logger, nil, "test result is nil") + break + } + + // We need to sleep for a second to avoid mismatching of mocks during keploy testing via test-bench + if r.config.EnableTesting { + r.logger.Debug("sleeping for a second to avoid mismatching of mocks during keploy testing via test-bench") + time.Sleep(time.Second) + } + } + + if conf.PostScript != "" { + //Execute the Post-script after each test-set if provided + r.logger.Info("Running Post-script", zap.String("script", conf.PostScript), zap.String("test-set", testSetID)) + err = r.executeScript(runTestSetCtx, conf.PostScript) + if err != nil { + return models.TestSetStatusFaultScript, fmt.Errorf("failed to execute post-script: %w", err) + } + } + + timeTaken := time.Since((startTime)) + + testCaseResults, err := r.reportDB.GetTestCaseResults(runTestSetCtx, testRunID, testSetID) + if err != nil { + if runTestSetCtx.Err() != context.Canceled { + utils.LogError(r.logger, err, "failed to get test case results") + testSetStatus = models.TestSetStatusInternalErr + } + } + + // Checking errors for final iteration + // Checking for errors in the loop + if loopErr != nil && !errors.Is(loopErr, context.Canceled) { + testSetStatus = models.TestSetStatusInternalErr + } else { + // Checking for errors in the mocking and application + select { + case <-exitLoopChan: + testSetStatus = testSetStatusByErrChan + default: + } + } + + testReport = &models.TestReport{ + Version: models.GetVersion(), + TestSet: testSetID, + Status: string(testSetStatus), + Total: testCasesCount, + Success: success, + Failure: failure, + Ignored: ignored, + Tests: testCaseResults, + } + + // final report should have reason for sudden stop of the test run so this should get canceled + reportCtx := context.WithoutCancel(runTestSetCtx) + err = r.reportDB.InsertReport(reportCtx, testRunID, testSetID, testReport) + if err != nil { + utils.LogError(r.logger, err, "failed to insert report") + return models.TestSetStatusInternalErr, fmt.Errorf("failed to insert report") + } + + err = utils.AddToGitIgnore(r.logger, r.config.Path, "/reports/") + if err != nil { + utils.LogError(r.logger, err, "failed to create .gitignore file") + } + + // remove the unused mocks by the test cases of a testset (if the base path is not provided ) + if r.config.Test.RemoveUnusedMocks && testSetStatus == models.TestSetStatusPassed && r.instrument { + r.logger.Debug("consumed mocks from the completed testset", zap.Any("for test-set", testSetID), zap.Any("consumed mocks", totalConsumedMocks)) + // delete the unused mocks from the data store + r.logger.Info("deleting unused mocks from the data store", zap.Any("for test-set", testSetID)) + err = r.mockDB.UpdateMocks(runTestSetCtx, testSetID, totalConsumedMocks) + if err != nil { + utils.LogError(r.logger, err, "failed to delete unused mocks") + } + } + + // TODO Need to decide on whether to use global variable or not + verdict := TestReportVerdict{ + total: testReport.Total, + failed: testReport.Failure, + passed: testReport.Success, + ignored: testReport.Ignored, + status: testSetStatus == models.TestSetStatusPassed, + duration: timeTaken, + } + + completeTestReport[testSetID] = verdict + totalTests += testReport.Total + totalTestPassed += testReport.Success + totalTestFailed += testReport.Failure + totalTestIgnored += testReport.Ignored + totalTestTimeTaken += timeTaken + + timeTakenStr := timeWithUnits(timeTaken) + + if testSetStatus == models.TestSetStatusFailed || testSetStatus == models.TestSetStatusPassed { + if testSetStatus == models.TestSetStatusFailed { + pp.SetColorScheme(models.GetFailingColorScheme()) + } else { + pp.SetColorScheme(models.GetPassingColorScheme()) + } + if testReport.Ignored > 0 { + if _, err := pp.Printf("\n <=========================================> \n TESTRUN SUMMARY. For test-set: %s\n"+"\tTotal tests: %s\n"+"\tTotal test passed: %s\n"+"\tTotal test failed: %s\n"+"\tTotal test ignored: %s\n"+"\tTime Taken: %s\n <=========================================> \n\n", testReport.TestSet, testReport.Total, testReport.Success, testReport.Failure, testReport.Ignored, timeTakenStr); err != nil { + utils.LogError(r.logger, err, "failed to print testrun summary") + } + } else { + if _, err := pp.Printf("\n <=========================================> \n TESTRUN SUMMARY. For test-set: %s\n"+"\tTotal tests: %s\n"+"\tTotal test passed: %s\n"+"\tTotal test failed: %s\n"+"\tTime Taken: %s\n <=========================================> \n\n", testReport.TestSet, testReport.Total, testReport.Success, testReport.Failure, timeTakenStr); err != nil { + utils.LogError(r.logger, err, "failed to print testrun summary") + } + } + } + + r.telemetry.TestSetRun(testReport.Success, testReport.Failure, testSetID, string(testSetStatus)) + + if r.config.Test.UpdateTemplate || r.config.Test.BasePath != "" { + utils.RemoveDoubleQuotes(utils.TemplatizedValues) // Write the templatized values to the yaml. + if len(utils.TemplatizedValues) > 0 { + err = r.testSetConf.Write(ctx, testSetID, &models.TestSet{ + PreScript: conf.PreScript, + PostScript: conf.PostScript, + Template: utils.TemplatizedValues, + }) + if err != nil { + utils.LogError(r.logger, err, "failed to write the templatized values to the yaml") + } + } + } + + return testSetStatus, nil +} + +func (r *Replayer) GetMocks(ctx context.Context, testSetID string, afterTime time.Time, beforeTime time.Time) (filtered, unfiltered []*models.Mock, err error) { + filtered, err = r.mockDB.GetFilteredMocks(ctx, testSetID, afterTime, beforeTime) + if err != nil { + utils.LogError(r.logger, err, "failed to get filtered mocks") + return nil, nil, err + } + unfiltered, err = r.mockDB.GetUnFilteredMocks(ctx, testSetID, afterTime, beforeTime) + if err != nil { + utils.LogError(r.logger, err, "failed to get unfiltered mocks") + return nil, nil, err + } + return filtered, unfiltered, err +} + +func (r *Replayer) FilterAndSetMocks(ctx context.Context, appID uint64, filtered, unfiltered []*models.Mock, afterTime, beforeTime time.Time, totalConsumedMocks map[string]models.MockState) error { + if !r.instrument { + r.logger.Debug("Keploy will not filter and set mocks when base path is provided", zap.Any("base path", r.config.Test.BasePath)) + return nil + } + + filtered = pkg.FilterTcsMocks(ctx, r.logger, filtered, afterTime, beforeTime) + unfiltered = pkg.FilterConfigMocks(ctx, r.logger, unfiltered, afterTime, beforeTime) + + filterOutDeleted := func(in []*models.Mock) []*models.Mock { + out := make([]*models.Mock, 0, len(in)) + for _, m := range in { + // treat empty/missing names as never consumed + if m == nil || m.Name == "" { + out = append(out, m) + continue + } + // we are picking mocks that are not consumed till now (not present in map), + // and, mocks that are updated. + if k, ok := totalConsumedMocks[m.Name]; !ok || k.Usage != models.Deleted { + if ok { + m.TestModeInfo.IsFiltered = k.IsFiltered + m.TestModeInfo.SortOrder = k.SortOrder + } + out = append(out, m) + } + } + return out + } + + filtered = filterOutDeleted(filtered) + unfiltered = filterOutDeleted(unfiltered) + + err := r.instrumentation.SetMocks(ctx, appID, filtered, unfiltered) + if err != nil { + utils.LogError(r.logger, err, "failed to set mocks") + return err + } + + return nil +} + +func (r *Replayer) GetTestSetStatus(ctx context.Context, testRunID string, testSetID string) (models.TestSetStatus, error) { + testReport, err := r.reportDB.GetReport(ctx, testRunID, testSetID) + if err != nil { + return models.TestSetStatusFailed, fmt.Errorf("failed to get report: %w", err) + } + status, err := models.StringToTestSetStatus(testReport.Status) + if err != nil { + return models.TestSetStatusFailed, fmt.Errorf("failed to convert string to test set status: %w", err) + } + return status, nil +} + +func (r *Replayer) compareHTTPResp(tc *models.TestCase, actualResponse *models.HTTPResp, testSetID string) (bool, *models.Result) { + noiseConfig := r.config.Test.GlobalNoise.Global + if tsNoise, ok := r.config.Test.GlobalNoise.Testsets[testSetID]; ok { + noiseConfig = LeftJoinNoise(r.config.Test.GlobalNoise.Global, tsNoise) + } + return httpMatcher.Match(tc, actualResponse, noiseConfig, r.config.Test.IgnoreOrdering, r.logger) +} + +func (r *Replayer) compareGRPCResp(tc *models.TestCase, actualResp *models.GrpcResp, testSetID string) (bool, *models.Result) { + noiseConfig := r.config.Test.GlobalNoise.Global + if tsNoise, ok := r.config.Test.GlobalNoise.Testsets[testSetID]; ok { + noiseConfig = LeftJoinNoise(r.config.Test.GlobalNoise.Global, tsNoise) + } + + return grpcMatcher.Match(tc, actualResp, noiseConfig, r.logger) + +} + +func (r *Replayer) printSummary(_ context.Context, _ bool) { + if totalTests > 0 { + testSuiteNames := make([]string, 0, len(completeTestReport)) + for testSuiteName := range completeTestReport { + testSuiteNames = append(testSuiteNames, testSuiteName) + } + sort.SliceStable(testSuiteNames, func(i, j int) bool { + testSuitePartsI := strings.Split(testSuiteNames[i], "-") + testSuitePartsJ := strings.Split(testSuiteNames[j], "-") + if len(testSuitePartsI) < 3 || len(testSuitePartsJ) < 3 { + return testSuiteNames[i] < testSuiteNames[j] + } + testSuiteIDNumberI, err1 := strconv.Atoi(testSuitePartsI[2]) + testSuiteIDNumberJ, err2 := strconv.Atoi(testSuitePartsJ[2]) + if err1 != nil || err2 != nil { + return false + } + return testSuiteIDNumberI < testSuiteIDNumberJ + }) + + totalTestTimeTakenStr := timeWithUnits(totalTestTimeTaken) + + if totalTestIgnored > 0 { + if _, err := pp.Printf("\n <=========================================> \n COMPLETE TESTRUN SUMMARY. \n\tTotal tests: %s\n"+"\tTotal test passed: %s\n"+"\tTotal test failed: %s\n"+"\tTotal test ignored: %s\n"+"\tTotal time taken: %s\n", totalTests, totalTestPassed, totalTestFailed, totalTestIgnored, totalTestTimeTakenStr); err != nil { + utils.LogError(r.logger, err, "failed to print test run summary") + return + } + } else { + if _, err := pp.Printf("\n <=========================================> \n COMPLETE TESTRUN SUMMARY. \n\tTotal tests: %s\n"+"\tTotal test passed: %s\n"+"\tTotal test failed: %s\n"+"\tTotal time taken: %s\n", totalTests, totalTestPassed, totalTestFailed, totalTestTimeTakenStr); err != nil { + utils.LogError(r.logger, err, "failed to print test run summary") + return + } + } + + header := "\n\tTest Suite Name\t\tTotal Test\tPassed\t\tFailed" + if totalTestIgnored > 0 { + header += "\t\tIgnored" + } + header += "\t\tTime Taken" + if totalTestFailed > 0 { + header += "\tFailed Testcases" + } + header += "\t\n" + + _, err := pp.Printf(header) + if err != nil { + utils.LogError(r.logger, err, "failed to print test suite summary header") + return + } + + for _, testSuiteName := range testSuiteNames { + report := completeTestReport[testSuiteName] + if report.status { + pp.SetColorScheme(models.GetPassingColorScheme()) + } else { + pp.SetColorScheme(models.GetFailingColorScheme()) + } + + testSetTimeTakenStr := timeWithUnits(report.duration) + + var format strings.Builder + args := []interface{}{} + + // Using a more dynamic way to build format string and arguments + // to ensure correct tabbing and conditional column + format.WriteString("\n\t%s\t\t%s\t\t%s\t\t%s") + args = append(args, testSuiteName, report.total, report.passed, report.failed) + + if totalTestIgnored > 0 && !r.config.Test.MustPass { + format.WriteString("\t\t%s") + args = append(args, report.ignored) + } + + format.WriteString("\t\t%s") // Time Taken + args = append(args, testSetTimeTakenStr) + + if totalTestFailed > 0 && !r.config.Test.MustPass { + failedCasesStr := "-" + if failedCases, ok := failedTCsBySetID[testSuiteName]; ok && len(failedCases) > 0 { + failedCasesStr = strings.Join(failedCases, ", ") + } + format.WriteString("\t%s") + args = append(args, failedCasesStr) + } + + if _, err := pp.Printf(format.String(), args...); err != nil { + utils.LogError(r.logger, err, "failed to print test suite details") + return + } + } + if _, err := pp.Printf("\n<=========================================> \n\n"); err != nil { + utils.LogError(r.logger, err, "failed to print separator") + return + } + } +} + +func (r *Replayer) RunApplication(ctx context.Context, appID uint64, opts models.RunOptions) models.AppError { + return r.instrumentation.Run(ctx, appID, opts) +} + +func (r *Replayer) GetTestSetConf(ctx context.Context, testSet string) (*models.TestSet, error) { + return r.testSetConf.Read(ctx, testSet) +} + +func (r *Replayer) DenoiseTestCases(ctx context.Context, testSetID string, noiseParams []*models.NoiseParams) ([]*models.NoiseParams, error) { + + testCases, err := r.testDB.GetTestCases(ctx, testSetID) + if err != nil { + return nil, fmt.Errorf("failed to get test cases: %w", err) + } + + for _, v := range testCases { + for _, noiseParam := range noiseParams { + if v.Name == noiseParam.TestCaseID { + // append the noise map + if noiseParam.Ops == string(models.OpsAdd) { + v.Noise = mergeMaps(v.Noise, noiseParam.Assertion) + } else { + // remove from the original noise map + v.Noise = removeFromMap(v.Noise, noiseParam.Assertion) + } + err = r.testDB.UpdateTestCase(ctx, v, testSetID, true) + if err != nil { + return nil, fmt.Errorf("failed to update test case: %w", err) + } + noiseParam.AfterNoise = v.Noise + } + } + } + + return noiseParams, nil +} + +func (r *Replayer) Normalize(ctx context.Context) error { + + var testRun string + if r.config.Normalize.TestRun == "" { + testRunIDs, err := r.reportDB.GetAllTestRunIDs(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return err + } + return fmt.Errorf("failed to get all test run ids: %w", err) + } + testRun = pkg.LastID(testRunIDs, models.TestRunTemplateName) + } + + if len(r.config.Normalize.SelectedTests) == 0 { + testSetIDs, err := r.testDB.GetAllTestSetIDs(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return err + } + return fmt.Errorf("failed to get all test set ids: %w", err) + } + for _, testSetID := range testSetIDs { + r.config.Normalize.SelectedTests = append(r.config.Normalize.SelectedTests, config.SelectedTests{TestSet: testSetID}) + } + } + + for _, testSet := range r.config.Normalize.SelectedTests { + testSetID := testSet.TestSet + testCases := testSet.Tests + err := r.NormalizeTestCases(ctx, testRun, testSetID, testCases, nil) + if err != nil { + return err + } + } + r.logger.Info("Normalized test cases successfully. Please run keploy tests to verify the changes.") + return nil +} + +func (r *Replayer) NormalizeTestCases(ctx context.Context, testRun string, testSetID string, selectedTestCaseIDs []string, testCaseResults []models.TestResult) error { + + if len(testCaseResults) == 0 { + testReport, err := r.reportDB.GetReport(ctx, testRun, testSetID) + if err != nil { + return fmt.Errorf("failed to get test report: %w", err) + } + testCaseResults = testReport.Tests + } + + testCaseResultMap := make(map[string]models.TestResult) + testCases, err := r.testDB.GetTestCases(ctx, testSetID) + if err != nil { + return fmt.Errorf("failed to get test cases: %w", err) + } + selectedTestCases := make([]*models.TestCase, 0, len(selectedTestCaseIDs)) + + if len(selectedTestCaseIDs) == 0 { + selectedTestCases = testCases + } else { + for _, testCase := range testCases { + if _, ok := matcherUtils.ArrayToMap(selectedTestCaseIDs)[testCase.Name]; ok { + selectedTestCases = append(selectedTestCases, testCase) + } + } + } + + for _, testCaseResult := range testCaseResults { + testCaseResultMap[testCaseResult.TestCaseID] = testCaseResult + } + + for _, testCase := range selectedTestCases { + if _, ok := testCaseResultMap[testCase.Name]; !ok { + r.logger.Info("test case not found in the test report", zap.String("test-case-id", testCase.Name), zap.String("test-set-id", testSetID)) + continue + } + if testCaseResultMap[testCase.Name].Status == models.TestStatusPassed { + continue + } + // Handle normalization based on test case kind + switch testCase.Kind { + case models.HTTP: + // Store the original timestamp to preserve it during normalization + originalTimestamp := testCase.HTTPResp.Timestamp + testCase.HTTPResp = testCaseResultMap[testCase.Name].Res + // Restore the original timestamp after normalization + testCase.HTTPResp.Timestamp = originalTimestamp + + case models.GRPC_EXPORT: + // Store the original timestamp to preserve it during normalization + originalTimestamp := testCase.GrpcResp.Timestamp + testCase.GrpcResp = testCaseResultMap[testCase.Name].GrpcRes + // Restore the original timestamp after normalization + testCase.GrpcResp.Timestamp = originalTimestamp + } + err = r.testDB.UpdateTestCase(ctx, testCase, testSetID, true) + if err != nil { + return fmt.Errorf("failed to update test case: %w", err) + } + } + return nil +} + +func (r *Replayer) executeScript(ctx context.Context, script string) error { + + if script == "" { + return nil + } + + // Define the function to cancel the command + cmdCancel := func(cmd *exec.Cmd) func() error { + return func() error { + return utils.InterruptProcessTree(r.logger, cmd.Process.Pid, syscall.SIGINT) + } + } + + cmdErr := utils.ExecuteCommand(ctx, r.logger, script, cmdCancel, 25*time.Second) + if cmdErr.Err != nil { + return fmt.Errorf("failed to execute script: %w", cmdErr.Err) + } + return nil +} + +func (r *Replayer) DeleteTestSet(ctx context.Context, testSetID string) error { + return r.testDB.DeleteTestSet(ctx, testSetID) +} + +func (r *Replayer) DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error { + return r.testDB.DeleteTests(ctx, testSetID, testCaseIDs) +} + +func SetTestHooks(testHooks TestHooks) { + HookImpl = testHooks +} + +// CreateFailedTestResult creates a test result for failed test cases +func (r *Replayer) CreateFailedTestResult(testCase *models.TestCase, testSetID string, started time.Time, errorMessage string) *models.TestResult { + testCaseResult := &models.TestResult{ + Kind: testCase.Kind, + Name: testSetID, + Status: models.TestStatusFailed, + Started: started.Unix(), + Completed: time.Now().UTC().Unix(), + TestCaseID: testCase.Name, + TestCasePath: filepath.Join(r.config.Path, testSetID), + MockPath: filepath.Join(r.config.Path, testSetID, "mocks.yaml"), + Noise: testCase.Noise, + } + + var result *models.Result + + switch testCase.Kind { + case models.HTTP: + actualResponse := &models.HTTPResp{ + StatusCode: 0, + Header: make(map[string]string), + Body: errorMessage, + } + + _, result = r.compareHTTPResp(testCase, actualResponse, testSetID) + + testCaseResult.Req = models.HTTPReq{ + Method: testCase.HTTPReq.Method, + ProtoMajor: testCase.HTTPReq.ProtoMajor, + ProtoMinor: testCase.HTTPReq.ProtoMinor, + URL: testCase.HTTPReq.URL, + URLParams: testCase.HTTPReq.URLParams, + Header: testCase.HTTPReq.Header, + Body: testCase.HTTPReq.Body, + Binary: testCase.HTTPReq.Binary, + Form: testCase.HTTPReq.Form, + Timestamp: testCase.HTTPReq.Timestamp, + } + testCaseResult.Res = *actualResponse + + case models.GRPC_EXPORT: + actualResponse := &models.GrpcResp{ + Headers: models.GrpcHeaders{ + PseudoHeaders: make(map[string]string), + OrdinaryHeaders: make(map[string]string), + }, + Body: models.GrpcLengthPrefixedMessage{ + DecodedData: errorMessage, + }, + Trailers: models.GrpcHeaders{ + PseudoHeaders: make(map[string]string), + OrdinaryHeaders: make(map[string]string), + }, + } + + _, result = r.compareGRPCResp(testCase, actualResponse, testSetID) + + testCaseResult.GrpcReq = testCase.GrpcReq + testCaseResult.GrpcRes = *actualResponse + } + + if result != nil { + testCaseResult.Result = *result + } + + return testCaseResult +} + +func (r *Replayer) replaceHostInTestCase(testCase *models.TestCase, newHost, logContext string) error { + var err error + switch testCase.Kind { + case models.HTTP: + testCase.HTTPReq.URL, err = utils.ReplaceHost(testCase.HTTPReq.URL, newHost) + if err != nil { + utils.LogError(r.logger, err, fmt.Sprintf("failed to replace host to %s", logContext)) + return err + } + r.logger.Debug("", zap.Any(fmt.Sprintf("replaced %s", logContext), testCase.HTTPReq.URL)) + + case models.GRPC_EXPORT: + testCase.GrpcReq.Headers.PseudoHeaders[":authority"], err = utils.ReplaceGrpcHost(testCase.GrpcReq.Headers.PseudoHeaders[":authority"], newHost) + if err != nil { + utils.LogError(r.logger, err, fmt.Sprintf("failed to replace host to %s", logContext)) + return err + } + r.logger.Debug("", zap.Any(fmt.Sprintf("replaced %s", logContext), testCase.GrpcReq.Headers.PseudoHeaders[":authority"])) + } + return nil +} + +func (r *Replayer) replacePortInTestCase(testCase *models.TestCase, newPort string) error { + var err error + switch testCase.Kind { + case models.HTTP: + testCase.HTTPReq.URL, err = utils.ReplacePort(testCase.HTTPReq.URL, newPort) + case models.GRPC_EXPORT: + testCase.GrpcReq.Headers.PseudoHeaders[":authority"], err = utils.ReplaceGrpcPort(testCase.GrpcReq.Headers.PseudoHeaders[":authority"], newPort) + } + if err != nil { + utils.LogError(r.logger, err, "failed to replace port") + return err + } + return nil +} + +func (r *Replayer) GetSelectedTestSets(ctx context.Context) ([]string, error) { + // get all the testset ids + testSetIDs, err := r.testDB.GetAllTestSetIDs(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return nil, err + } + utils.LogError(r.logger, err, "failed to get all test set ids") + return nil, fmt.Errorf("mocks downloading failed to due to error in getting test set ids") + } + + var testSets []string + for _, testSetID := range testSetIDs { + if _, ok := r.config.Test.SelectedTests[testSetID]; !ok && len(r.config.Test.SelectedTests) != 0 { + continue + } + testSets = append(testSets, testSetID) + } + if len(testSets) == 0 { + testSets = testSetIDs + } + + // Sort the testsets. + natsort.Sort(testSets) + return testSets, nil +} + +func (r *Replayer) authenticateUser(ctx context.Context) error { + //authenticate the user + token, err := r.auth.GetToken(ctx) + if err != nil { + r.logger.Error("Failed to Authenticate user", zap.Error(err)) + r.logger.Warn("Looks like you haven't logged in, skipping mock upload/download") + r.logger.Warn("Please login using `keploy login` to perform mock upload/download action") + return fmt.Errorf("mocks downloading failed to due to authentication error") + } + + r.mock.setToken(token) + return nil +} + +func (r *Replayer) DownloadMocks(ctx context.Context) error { + // Authenticate the user for mock registry + err := r.authenticateUser(ctx) + if err != nil { + return err + } + + testSets, err := r.GetSelectedTestSets(ctx) + if err != nil { + utils.LogError(r.logger, err, "failed to get selected test sets") + return fmt.Errorf("mocks downloading failed to due to error in getting selected test sets") + } + + for _, testSetID := range testSets { + r.logger.Info("Downloading mocks for the testset", zap.String("testset", testSetID)) + + err := r.mock.download(ctx, testSetID) + if err != nil { + if errors.Is(err, context.Canceled) { + return err + } + utils.LogError(r.logger, err, "failed to download mocks", zap.Any("testset", testSetID)) + continue + } + + } + return nil +} + +func (r *Replayer) UploadMocks(ctx context.Context) error { + // Authenticate the user for mock registry + err := r.authenticateUser(ctx) + if err != nil { + return err + } + + testSets, err := r.GetSelectedTestSets(ctx) + if err != nil { + utils.LogError(r.logger, err, "failed to get selected test sets") + return fmt.Errorf("mocks uploading failed to due to error in getting selected test sets") + } + + for _, testSetID := range testSets { + r.logger.Info("Uploading mocks for the testset", zap.String("testset", testSetID)) + + err := r.mock.upload(ctx, testSetID) + if err != nil { + if errors.Is(err, context.Canceled) { + return err + } + utils.LogError(r.logger, err, "failed to upload mocks", zap.Any("testset", testSetID)) + continue + } + + } + + return nil +} diff --git a/keploy/pkg/service/replay/replay_test.go b/keploy/pkg/service/replay/replay_test.go new file mode 100644 index 0000000..1085da2 --- /dev/null +++ b/keploy/pkg/service/replay/replay_test.go @@ -0,0 +1,265 @@ +package replay + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestReplay_UnloadDoneChannelUsage(t *testing.T) { + // Test simulating the hook reload logic that uses the UnloadDone channel + + // Create mock instrumentation + mockInstr := NewMockInstrumentation() + appID := uint64(12345) + + // Get initial instrument state + unloadCh := mockInstr.GetHookUnloadDone(appID) + inst := &InstrumentState{ + AppID: appID, + HookCancel: func() {}, // Mock cancel function + UnloadDone: unloadCh, + } + + // Simulate the hook reload scenario + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + // Test the waiting logic similar to what's in replay.go + waitComplete := make(chan bool, 1) + + go func() { + // This simulates the actual wait logic from replay.go: + // <- inst.UnloadDone + <-inst.UnloadDone + waitComplete <- true + }() + + // Verify that the goroutine is waiting + select { + case <-waitComplete: + t.Error("Should not complete before unload signal") + case <-time.After(50 * time.Millisecond): + // Expected behavior - still waiting + } + + // Simulate hooks being unloaded (this would happen when hookCancel() is called) + mockInstr.CloseUnloadChannel(appID) + + // Now the wait should complete + select { + case result := <-waitComplete: + assert.True(t, result, "Wait should complete successfully") + case <-ctx.Done(): + t.Error("Wait should complete before context timeout") + } +} + +func TestReplay_MultipleTestSetReload(t *testing.T) { + // Test simulating multiple test set reloads with different channels + + mockInstr := NewMockInstrumentation() + + // Simulate processing multiple test sets + testSets := []string{"test-set-1", "test-set-2", "test-set-3"} + + var currentInst *InstrumentState + + for i, testSet := range testSets { + appID := uint64(12345 + i) // Different app ID for each test set + + // Get new instrument state for this test set + unloadCh := mockInstr.GetHookUnloadDone(appID) + newInst := &InstrumentState{ + AppID: appID, + HookCancel: func() {}, + UnloadDone: unloadCh, + } + + // If this is not the first test set, simulate waiting for previous unload + if i > 0 && currentInst != nil { + + // Start waiting for unload in background + unloadComplete := make(chan bool, 1) + go func(inst *InstrumentState) { + <-inst.UnloadDone + unloadComplete <- true + }(currentInst) + + // Simulate canceling previous hooks + // In real code: hookCancel() would trigger the unload + mockInstr.CloseUnloadChannel(currentInst.AppID) + + // Wait for unload to complete + select { + case <-unloadComplete: + // Expected behavior + case <-time.After(100 * time.Millisecond): + t.Errorf("Unload should complete for test set %s", testSet) + } + } + + // Update to new instrument state + currentInst = newInst + + // Verify the new channel is not closed + select { + case <-currentInst.UnloadDone: + t.Errorf("New channel for test set %s should not be closed", testSet) + default: + // Expected behavior + } + } +} + +func TestReplay_ConcurrentUnloadWaiters(t *testing.T) { + // Test multiple goroutines waiting on the same unload channel + // This simulates scenarios where multiple operations might be waiting + + mockInstr := NewMockInstrumentation() + appID := uint64(12345) + + unloadCh := mockInstr.GetHookUnloadDone(appID) + inst := &InstrumentState{ + AppID: appID, + HookCancel: func() {}, + UnloadDone: unloadCh, + } + + // Start multiple waiters + numWaiters := 3 + waitComplete := make(chan int, numWaiters) + + for i := 0; i < numWaiters; i++ { + go func(waiterID int) { + <-inst.UnloadDone + waitComplete <- waiterID + }(i) + } + + // Verify all are waiting + select { + case <-waitComplete: + t.Error("No waiter should complete before unload signal") + case <-time.After(50 * time.Millisecond): + // Expected behavior + } + + // Signal unload completion + mockInstr.CloseUnloadChannel(appID) + + // All waiters should complete + completedWaiters := make(map[int]bool) + for i := 0; i < numWaiters; i++ { + select { + case waiterID := <-waitComplete: + completedWaiters[waiterID] = true + case <-time.After(100 * time.Millisecond): + t.Errorf("Waiter %d should complete", i) + } + } + + // Verify all waiters completed + assert.Equal(t, numWaiters, len(completedWaiters), "All waiters should complete") +} + +func TestReplay_UnloadDoneChannelTimeout(t *testing.T) { + // Test scenario where unload takes too long and times out + + mockInstr := NewMockInstrumentation() + appID := uint64(12345) + + unloadCh := mockInstr.GetHookUnloadDone(appID) + inst := &InstrumentState{ + AppID: appID, + HookCancel: func() {}, + UnloadDone: unloadCh, + } + + // Create a context with short timeout + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + // Wait for either unload or timeout + select { + case <-inst.UnloadDone: + t.Error("Unload should not complete without signal") + case <-ctx.Done(): + // Expected behavior - timeout occurred + assert.Equal(t, context.DeadlineExceeded, ctx.Err(), "Should timeout") + } +} + +func TestReplay_UnloadDoneChannelImmediate(t *testing.T) { + // Test scenario where channel is already closed (immediate completion) + + mockInstr := NewMockInstrumentation() + appID := uint64(12345) + + // Get the channel first + unloadCh := mockInstr.GetHookUnloadDone(appID) + + // Then close it immediately + mockInstr.CloseUnloadChannel(appID) + + inst := &InstrumentState{ + AppID: appID, + HookCancel: func() {}, + UnloadDone: unloadCh, + } + + // Wait should complete immediately + done := make(chan bool, 1) + go func() { + <-inst.UnloadDone + done <- true + }() + + select { + case result := <-done: + assert.True(t, result, "Should complete immediately") + case <-time.After(100 * time.Millisecond): + t.Error("Should complete immediately when channel is already closed") + } +} + +func TestReplay_InstrumentStateChannelIntegrity(t *testing.T) { + // Test that the InstrumentState properly maintains the channel reference + + mockInstr := NewMockInstrumentation() + appID := uint64(12345) + + unloadCh := mockInstr.GetHookUnloadDone(appID) + inst := &InstrumentState{ + AppID: appID, + HookCancel: func() {}, + UnloadDone: unloadCh, + } + + // Verify channel is accessible through the struct + assert.NotNil(t, inst.UnloadDone, "UnloadDone channel should be accessible") + + // Verify it's the same channel + directCh := mockInstr.GetHookUnloadDone(appID) + assert.Equal(t, directCh, inst.UnloadDone, "Should be the same channel") + + // Test channel operations work through the struct + testComplete := make(chan bool, 1) + go func() { + <-inst.UnloadDone + testComplete <- true + }() + + // Close through mock + mockInstr.CloseUnloadChannel(appID) + + // Should complete through struct reference + select { + case <-testComplete: + // Expected behavior + case <-time.After(100 * time.Millisecond): + t.Error("Channel operation through struct should work") + } +} diff --git a/keploy/pkg/service/replay/service.go b/keploy/pkg/service/replay/service.go new file mode 100644 index 0000000..cb8ee56 --- /dev/null +++ b/keploy/pkg/service/replay/service.go @@ -0,0 +1,103 @@ +package replay + +import ( + "context" + "io" + "time" + + "go.keploy.io/server/v2/pkg/models" +) + +type Instrumentation interface { + //Setup prepares the environment for the recording + Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) + //Hook will load hooks and start the proxy server. + Hook(ctx context.Context, id uint64, opts models.HookOptions) error + // GetHookUnloadDone returns a channel that signals when hooks are completely unloaded + GetHookUnloadDone(id uint64) <-chan struct{} + MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error + // SetMocks Allows for setting mocks between test runs for better filtering and matching + SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error + // GetConsumedMocks to log the names of the mocks that were consumed during the test run of failed test cases + GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) + // Run is blocking call and will execute until error + Run(ctx context.Context, id uint64, opts models.RunOptions) models.AppError + + GetContainerIP(ctx context.Context, id uint64) (string, error) +} + +type Service interface { + Start(ctx context.Context) error + Instrument(ctx context.Context) (*InstrumentState, error) + GetNextTestRunID(ctx context.Context) (string, error) + GetAllTestSetIDs(ctx context.Context) ([]string, error) + RunTestSet(ctx context.Context, testSetID string, testRunID string, appID uint64, serveTest bool) (models.TestSetStatus, error) + GetTestSetStatus(ctx context.Context, testRunID string, testSetID string) (models.TestSetStatus, error) + GetTestCases(ctx context.Context, testID string) ([]*models.TestCase, error) + GetTestSetConf(ctx context.Context, testSetID string) (*models.TestSet, error) + RunApplication(ctx context.Context, appID uint64, opts models.RunOptions) models.AppError + Normalize(ctx context.Context) error + DenoiseTestCases(ctx context.Context, testSetID string, noiseParams []*models.NoiseParams) ([]*models.NoiseParams, error) + NormalizeTestCases(ctx context.Context, testRun string, testSetID string, selectedTestCaseIDs []string, testResult []models.TestResult) error + DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error + DeleteTestSet(ctx context.Context, testSetID string) error + + DownloadMocks(ctx context.Context) error + UploadMocks(ctx context.Context) error +} + +type TestDB interface { + GetAllTestSetIDs(ctx context.Context) ([]string, error) + GetTestCases(ctx context.Context, testSetID string) ([]*models.TestCase, error) + UpdateTestCase(ctx context.Context, testCase *models.TestCase, testSetID string, enableLog bool) error + DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error + DeleteTestSet(ctx context.Context, testSetID string) error +} + +type MockDB interface { + GetFilteredMocks(ctx context.Context, testSetID string, afterTime time.Time, beforeTime time.Time) ([]*models.Mock, error) + GetUnFilteredMocks(ctx context.Context, testSetID string, afterTime time.Time, beforeTime time.Time) ([]*models.Mock, error) + UpdateMocks(ctx context.Context, testSetID string, mockNames map[string]models.MockState) error +} + +type ReportDB interface { + GetAllTestRunIDs(ctx context.Context) ([]string, error) + GetTestCaseResults(ctx context.Context, testRunID string, testSetID string) ([]models.TestResult, error) + GetReport(ctx context.Context, testRunID string, testSetID string) (*models.TestReport, error) + ClearTestCaseResults(_ context.Context, testRunID string, testSetID string) + InsertTestCaseResult(ctx context.Context, testRunID string, testSetID string, result *models.TestResult) error // 1 + InsertReport(ctx context.Context, testRunID string, testSetID string, testReport *models.TestReport) error // 2 + UpdateReport(ctx context.Context, testRunID string, testCoverage any) error +} + +type TestSetConfig interface { + Read(ctx context.Context, testSetID string) (*models.TestSet, error) + Write(ctx context.Context, testSetID string, testSet *models.TestSet) error + ReadSecret(ctx context.Context, testSetID string) (map[string]interface{}, error) +} + +type Telemetry interface { + TestSetRun(success int, failure int, testSet string, runStatus string) + TestRun(success int, failure int, testSets int, runStatus string) + MockTestRun(utilizedMocks int) +} + +type TestHooks interface { + SimulateRequest(ctx context.Context, appID uint64, tc *models.TestCase, testSetID string) (interface{}, error) + GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) + BeforeTestRun(ctx context.Context, testRunID string) error + BeforeTestSetRun(ctx context.Context, testSetID string) error + AfterTestSetRun(ctx context.Context, testSetID string, status bool) error + AfterTestRun(ctx context.Context, testRunID string, testSetIDs []string, coverage models.TestCoverage) error // hook executed after running all the test-sets +} + +type Storage interface { + Upload(ctx context.Context, file io.Reader, mockName string, appName string, jwtToken string) error // 3 + Download(ctx context.Context, mockName string, appName string, userName string, jwtToken string) (io.Reader, error) +} + +type InstrumentState struct { + AppID uint64 + HookCancel context.CancelFunc + UnloadDone <-chan struct{} // Channel that will be closed when hooks are completely unloaded +} diff --git a/keploy/pkg/service/replay/service_test.go b/keploy/pkg/service/replay/service_test.go new file mode 100644 index 0000000..be4b8f2 --- /dev/null +++ b/keploy/pkg/service/replay/service_test.go @@ -0,0 +1,267 @@ +package replay + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.keploy.io/server/v2/pkg/models" +) + +// MockInstrumentation is a mock implementation of the Instrumentation interface +type MockInstrumentation struct { + unloadDoneChannels map[uint64]chan struct{} +} + +func NewMockInstrumentation() *MockInstrumentation { + return &MockInstrumentation{ + unloadDoneChannels: make(map[uint64]chan struct{}), + } +} + +func (m *MockInstrumentation) Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) { + return 12345, nil // Mock return value +} + +func (m *MockInstrumentation) Hook(ctx context.Context, id uint64, opts models.HookOptions) error { + return nil // Mock return value +} + +func (m *MockInstrumentation) GetHookUnloadDone(id uint64) <-chan struct{} { + if ch, exists := m.unloadDoneChannels[id]; exists { + return ch + } + // Create a new channel for this app ID + ch := make(chan struct{}) + m.unloadDoneChannels[id] = ch + return ch +} + +func (m *MockInstrumentation) MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error { + return nil // Mock return value +} + +func (m *MockInstrumentation) SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error { + return nil // Mock return value +} + +func (m *MockInstrumentation) GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) { + return []models.MockState{}, nil // Mock return value +} + +func (m *MockInstrumentation) Run(ctx context.Context, id uint64, opts models.RunOptions) models.AppError { + return models.AppError{} // Mock return value +} + +func (m *MockInstrumentation) GetContainerIP(ctx context.Context, id uint64) (string, error) { + return "127.0.0.1", nil // Mock return value +} + +// CloseUnloadChannel simulates hooks being unloaded by closing the channel +func (m *MockInstrumentation) CloseUnloadChannel(id uint64) { + if ch, exists := m.unloadDoneChannels[id]; exists { + close(ch) + } +} + +func TestInstrumentState_UnloadDoneChannel(t *testing.T) { + // Test the InstrumentState struct with UnloadDone channel + unloadCh := make(chan struct{}) + + state := &InstrumentState{ + AppID: 12345, + HookCancel: func() {}, // dummy cancel function + UnloadDone: unloadCh, + } + + // Verify the channel is present and not closed + assert.NotNil(t, state.UnloadDone, "UnloadDone channel should be present") + + select { + case <-state.UnloadDone: + t.Error("UnloadDone channel should not be closed initially") + default: + // Expected behavior + } + + // Close the channel to simulate unload completion + close(unloadCh) + + // Verify the channel is now closed + select { + case <-state.UnloadDone: + // Expected behavior - channel is closed + case <-time.After(100 * time.Millisecond): + t.Error("UnloadDone channel should be closed after unload") + } +} + +func TestMockInstrumentation_GetHookUnloadDone(t *testing.T) { + mockInstr := NewMockInstrumentation() + + appID := uint64(12345) + + // Get the channel + ch := mockInstr.GetHookUnloadDone(appID) + assert.NotNil(t, ch, "GetHookUnloadDone should return a channel") + + // Verify the channel is not closed initially + select { + case <-ch: + t.Error("Channel should not be closed initially") + default: + // Expected behavior + } + + // Multiple calls should return the same channel + ch2 := mockInstr.GetHookUnloadDone(appID) + assert.Equal(t, ch, ch2, "Multiple calls should return the same channel") +} + +func TestMockInstrumentation_GetHookUnloadDone_DifferentApps(t *testing.T) { + mockInstr := NewMockInstrumentation() + + appID1 := uint64(12345) + appID2 := uint64(67890) + + // Get channels for different app IDs + ch1 := mockInstr.GetHookUnloadDone(appID1) + ch2 := mockInstr.GetHookUnloadDone(appID2) + + assert.NotNil(t, ch1, "First app should return a channel") + assert.NotNil(t, ch2, "Second app should return a channel") + assert.NotEqual(t, ch1, ch2, "Different app IDs should return different channels") +} + +func TestMockInstrumentation_CloseUnloadChannel(t *testing.T) { + mockInstr := NewMockInstrumentation() + + appID := uint64(12345) + + // Get the channel + ch := mockInstr.GetHookUnloadDone(appID) + + // Verify not closed initially + select { + case <-ch: + t.Error("Channel should not be closed initially") + default: + // Expected behavior + } + + // Close the channel + mockInstr.CloseUnloadChannel(appID) + + // Verify the channel is now closed + select { + case <-ch: + // Expected behavior - channel is closed + case <-time.After(100 * time.Millisecond): + t.Error("Channel should be closed after CloseUnloadChannel") + } +} + +func TestInstrument_ChannelIntegration(t *testing.T) { + // Test scenario similar to how the channel is used in the replay logic + mockInstr := NewMockInstrumentation() + appID := uint64(12345) + + // Simulate getting the instrument state + unloadCh := mockInstr.GetHookUnloadDone(appID) + state := &InstrumentState{ + AppID: appID, + HookCancel: func() {}, + UnloadDone: unloadCh, + } + + // Simulate waiting for unload in a goroutine (similar to replay logic) + done := make(chan bool, 1) + go func() { + select { + case <-state.UnloadDone: + done <- true + case <-time.After(200 * time.Millisecond): + done <- false + } + }() + + // Simulate unload happening after a delay + go func() { + time.Sleep(50 * time.Millisecond) + mockInstr.CloseUnloadChannel(appID) + }() + + // Verify that the channel was closed and detected + result := <-done + assert.True(t, result, "Should detect channel closure within timeout") +} + +func TestInstrument_MultipleUnloadWaiters(t *testing.T) { + // Test multiple goroutines waiting on the same unload channel + mockInstr := NewMockInstrumentation() + appID := uint64(12345) + + unloadCh := mockInstr.GetHookUnloadDone(appID) + + // Create multiple waiters + numWaiters := 3 + results := make(chan bool, numWaiters) + + for i := 0; i < numWaiters; i++ { + go func() { + select { + case <-unloadCh: + results <- true + case <-time.After(200 * time.Millisecond): + results <- false + } + }() + } + + // Close the channel after a short delay + go func() { + time.Sleep(50 * time.Millisecond) + mockInstr.CloseUnloadChannel(appID) + }() + + // All waiters should detect the closure + for i := 0; i < numWaiters; i++ { + result := <-results + assert.True(t, result, "Waiter %d should detect channel closure", i) + } +} + +func TestInstrument_ChannelReuse(t *testing.T) { + // Test scenario where we get a new channel after the previous one was closed + mockInstr := NewMockInstrumentation() + appID := uint64(12345) + + // First channel + ch1 := mockInstr.GetHookUnloadDone(appID) + mockInstr.CloseUnloadChannel(appID) + + // Verify first channel is closed + select { + case <-ch1: + // Expected behavior + case <-time.After(100 * time.Millisecond): + t.Error("First channel should be closed") + } + + // Get a new channel (simulating a new load after unload) + // Note: In the real implementation, a new channel would be created for each load + // For this test, we'll manually create a new one + delete(mockInstr.unloadDoneChannels, appID) // Remove the old closed channel + ch2 := mockInstr.GetHookUnloadDone(appID) + + // Verify second channel is different and not closed + assert.NotEqual(t, ch1, ch2, "New channel should be different from the closed one") + + select { + case <-ch2: + t.Error("New channel should not be closed initially") + default: + // Expected behavior + } +} diff --git a/keploy/pkg/service/replay/utils.go b/keploy/pkg/service/replay/utils.go new file mode 100644 index 0000000..8899d46 --- /dev/null +++ b/keploy/pkg/service/replay/utils.go @@ -0,0 +1,118 @@ +package replay + +import ( + "fmt" + "net/url" + "path" + "time" + + // "encoding/json" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/models" +) + +type TestReportVerdict struct { + total int + passed int + failed int + ignored int + status bool + duration time.Duration +} + +func LeftJoinNoise(globalNoise config.GlobalNoise, tsNoise config.GlobalNoise) config.GlobalNoise { + noise := globalNoise + + if _, ok := noise["body"]; !ok { + noise["body"] = make(map[string][]string) + } + if tsNoiseBody, ok := tsNoise["body"]; ok { + for field, regexArr := range tsNoiseBody { + noise["body"][field] = regexArr + } + } + + if _, ok := noise["header"]; !ok { + noise["header"] = make(map[string][]string) + } + if tsNoiseHeader, ok := tsNoise["header"]; ok { + for field, regexArr := range tsNoiseHeader { + noise["header"][field] = regexArr + } + } + + return noise +} + +// ReplaceBaseURL replaces the baseUrl of the old URL with the new URL's. +func ReplaceBaseURL(newURL, oldURL string) (string, error) { + parsedOldURL, err := url.Parse(oldURL) + if err != nil { + return "", fmt.Errorf("failed to parse the old URL: %v", err) + } + + parsedNewURL, err := url.Parse(newURL) + if err != nil { + return "", fmt.Errorf("failed to parse the new URL: %v", err) + } + // if scheme is empty, then add the scheme from the old URL in order to parse it correctly + if parsedNewURL.Scheme == "" { + parsedNewURL.Scheme = parsedOldURL.Scheme + parsedNewURL, err = url.Parse(parsedNewURL.String()) + if err != nil { + return "", fmt.Errorf("failed to parse the scheme added new URL: %v", err) + } + } + + parsedOldURL.Scheme = parsedNewURL.Scheme + parsedOldURL.Host = parsedNewURL.Host + apiPath := path.Join(parsedNewURL.Path, parsedOldURL.Path) + + parsedOldURL.Path = apiPath + parsedOldURL.RawPath = apiPath + replacedURL := parsedOldURL.String() + decodedURL, err := url.PathUnescape(replacedURL) + if err != nil { + return "", fmt.Errorf("failed to decode the URL: %v", err) + } + return decodedURL, nil +} + +func mergeMaps(map1, map2 map[string][]string) map[string][]string { + for key, values := range map2 { + if _, exists := map1[key]; exists { + map1[key] = append(map1[key], values...) + } else { + map1[key] = values + } + } + return map1 +} + +func removeFromMap(map1, map2 map[string][]string) map[string][]string { + for key := range map2 { + delete(map1, key) + } + return map1 +} + +func timeWithUnits(duration time.Duration) string { + if duration.Seconds() < 1 { + return fmt.Sprintf("%v ms", duration.Milliseconds()) + } else if duration.Minutes() < 1 { + return fmt.Sprintf("%.2f s", duration.Seconds()) + } else if duration.Hours() < 1 { + return fmt.Sprintf("%.2f min", duration.Minutes()) + } + return fmt.Sprintf("%.2f hr", duration.Hours()) +} + +func getFailedTCs(results []models.TestResult) []string { + ids := make([]string, 0, len(results)) + for _, r := range results { + if r.Status == models.TestStatusFailed { + ids = append(ids, r.TestCaseID) + } + } + return ids +} diff --git a/keploy/pkg/service/report/report.go b/keploy/pkg/service/report/report.go new file mode 100644 index 0000000..433c41f --- /dev/null +++ b/keploy/pkg/service/report/report.go @@ -0,0 +1,283 @@ +package report + +import ( + "context" + "fmt" + "sort" + "strconv" + "strings" + + "github.com/k0kubun/pp/v3" + "go.keploy.io/server/v2/config" + matcherUtils "go.keploy.io/server/v2/pkg/matcher" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/service/tools" + "go.uber.org/zap" +) + +type Report struct { + logger *zap.Logger + config *config.Config + reportDB ReportDB + testDB TestDB +} + +const ( + ReportSuffix = "-report" + TestRunPrefix = "test-run-" +) + +func New(logger *zap.Logger, cfg *config.Config, reportDB ReportDB, testDB TestDB) *Report { + return &Report{ + logger: logger, + config: cfg, + reportDB: reportDB, + testDB: testDB, + } +} + +// GenerateReport orchestrates the entire report generation process +func (r *Report) GenerateReport(ctx context.Context) error { + latestRunID, err := r.getLatestTestRunID(ctx) + + if err != nil { + return err + } + + testSetIDs := r.extractTestSetIDs() + if len(testSetIDs) == 0 { + r.logger.Info("No test sets selected for report generation, Generating report for all test sets") + + var err error + testSetIDs, err = r.testDB.GetReportTestSets(ctx, latestRunID) + if err != nil { + r.logger.Error("failed to get all test set ids", zap.Error(err)) + return err + } + + if len(testSetIDs) == 0 { + r.logger.Warn("No test sets found for report generation") + return nil + } + } + + if latestRunID == "" { + r.logger.Warn("no test runs found") + return nil + } + + failedTests, err := r.collectFailedTests(ctx, latestRunID, testSetIDs) + if err != nil { + return err + } + + if len(failedTests) == 0 { + r.logger.Info("No failed tests found in the latest test run") + return nil + } + + err = r.printFailedTestReports(failedTests) + if err != nil { + r.logger.Error("failed to print failed test reports", zap.Error(err)) + return err + } + + r.logger.Info(fmt.Sprintf("✂️ CLI output truncated - see the %s report file for the complete diff.", latestRunID)) + + r.logger.Info("Report generation completed successfully") + + return nil +} + +// extractTestSetIDs extracts and cleans test set IDs from config +func (r *Report) extractTestSetIDs() []string { + var testSetIDs []string + for testSet := range r.config.Report.SelectedTestSets { + testSetIDs = append(testSetIDs, strings.TrimSpace(testSet)) + } + return testSetIDs +} + +// getLatestTestRunID retrieves and determines the latest test run ID +func (r *Report) getLatestTestRunID(ctx context.Context) (string, error) { + testRunIDs, err := r.reportDB.GetAllTestRunIDs(ctx) + if err != nil { + r.logger.Error("failed to get all test run ids", zap.Error(err)) + return "", err + } + + if len(testRunIDs) == 0 { + return "", nil + } + + sort.Slice(testRunIDs, func(i, j int) bool { + numi, erri := strconv.Atoi(strings.TrimPrefix(testRunIDs[i], TestRunPrefix)) + numj, errj := strconv.Atoi(strings.TrimPrefix(testRunIDs[j], TestRunPrefix)) + if erri != nil && errj != nil { + return testRunIDs[i] < testRunIDs[j] + } + if erri != nil { + return true // i is less if it can't be parsed + } + if errj != nil { + return false // j is less if it can't be parsed + } + return numi < numj + }) + + return testRunIDs[len(testRunIDs)-1], nil +} + +// collectFailedTests gathers all failed tests from the specified test sets +func (r *Report) collectFailedTests(ctx context.Context, runID string, testSetIDs []string) ([]models.TestResult, error) { + var failedTests []models.TestResult + + for _, testSetID := range testSetIDs { + cleanTestSetID := strings.TrimSuffix(testSetID, ReportSuffix) + + results, err := r.reportDB.GetReport(ctx, runID, cleanTestSetID) + if err != nil { + r.logger.Error("failed to get test case results for test set", + zap.String("test_set_id", cleanTestSetID), zap.Error(err)) + continue + } + + if results == nil { + r.logger.Warn("no results found for test set", zap.String("test_set_id", cleanTestSetID)) + continue + } + + failedTests = append(failedTests, r.extractFailedTestsFromResults(results.Tests)...) + } + + return failedTests, nil +} + +// extractFailedTestsFromResults filters out only the failed tests from results +func (r *Report) extractFailedTestsFromResults(tests []models.TestResult) []models.TestResult { + var failedTests []models.TestResult + for _, result := range tests { + if result.Status == models.TestStatusFailed { + failedTests = append(failedTests, result) + } + } + return failedTests +} + +// printFailedTestReports generates and prints reports for all failed tests +func (r *Report) printFailedTestReports(failedTests []models.TestResult) error { + for _, test := range failedTests { + if err := r.printSingleTestReport(test); err != nil { + return err + } + } + return nil +} + +// printSingleTestReport generates and prints a report for a single failed test +func (r *Report) printSingleTestReport(test models.TestResult) error { + logDiffs := matcherUtils.NewDiffsPrinter(test.Name) + printer := r.createFormattedPrinter() + + logs := r.generateTestHeader(test, printer) + + if err := r.addStatusCodeDiffs(test, &logDiffs); err != nil { + return err + } + + if err := r.addHeaderDiffs(test, &logDiffs); err != nil { + return err + } + + if err := r.addBodyDiffs(test, &logDiffs); err != nil { + return err + } + + if err := r.printAndRenderDiffs(printer, logs, &logDiffs); err != nil { + return err + } + + return nil +} + +// createFormattedPrinter creates a configured pretty printer +func (r *Report) createFormattedPrinter() *pp.PrettyPrinter { + printer := pp.New() + printer.WithLineInfo = false + printer.SetColorScheme(models.GetFailingColorScheme()) + return printer +} + +// generateTestHeader creates the test report header +func (r *Report) generateTestHeader(test models.TestResult, printer *pp.PrettyPrinter) string { + return printer.Sprintf("Testrun failed for testcase with id: %s\n\n--------------------------------------------------------------------\n\n", + test.TestCaseID) +} + +// addStatusCodeDiffs adds status code differences to the diff printer +func (r *Report) addStatusCodeDiffs(test models.TestResult, logDiffs *matcherUtils.DiffsPrinter) error { + if !test.Result.StatusCode.Normal { + logDiffs.PushStatusDiff( + fmt.Sprint(test.Result.StatusCode.Expected), + fmt.Sprint(test.Result.StatusCode.Actual), + ) + } + return nil +} + +// addHeaderDiffs adds header differences to the diff printer +func (r *Report) addHeaderDiffs(test models.TestResult, logDiffs *matcherUtils.DiffsPrinter) error { + for _, headerResult := range test.Result.HeadersResult { + if !headerResult.Normal { + actualValue := strings.Join(headerResult.Actual.Value, ", ") + expectedValue := strings.Join(headerResult.Expected.Value, ", ") + logDiffs.PushHeaderDiff(expectedValue, actualValue, headerResult.Actual.Key, nil) + } + } + return nil +} + +// addBodyDiffs adds body differences to the diff printer +func (r *Report) addBodyDiffs(test models.TestResult, logDiffs *matcherUtils.DiffsPrinter) error { + for _, bodyResult := range test.Result.BodyResult { + if !bodyResult.Normal { + actualValue, err := r.renderTemplateValue(bodyResult.Actual) + if err != nil { + return fmt.Errorf("failed to render actual body: %w", err) + } + + expectedValue, err := r.renderTemplateValue(bodyResult.Expected) + if err != nil { + return fmt.Errorf("failed to render expected body: %w", err) + } + + logDiffs.PushBodyDiff(fmt.Sprint(expectedValue), fmt.Sprint(actualValue), nil) + } + } + return nil +} + +// renderTemplateValue renders a templated value and returns the result +func (r *Report) renderTemplateValue(value interface{}) (interface{}, error) { + _, renderedValue, err := tools.RenderIfTemplatized(value) + if err != nil { + r.logger.Error("failed to render template value", zap.Error(err)) + return nil, err + } + return renderedValue, nil +} + +// printAndRenderDiffs prints the logs and renders the differences +func (r *Report) printAndRenderDiffs(printer *pp.PrettyPrinter, logs string, logDiffs *matcherUtils.DiffsPrinter) error { + if _, err := printer.Printf(logs); err != nil { + r.logger.Error("failed to print the logs", zap.Error(err)) + return err + } + + if err := logDiffs.Render(); err != nil { + r.logger.Error("failed to render the diffs", zap.Error(err)) + return err + } + + return nil +} diff --git a/keploy/pkg/service/report/service.go b/keploy/pkg/service/report/service.go new file mode 100644 index 0000000..38ce081 --- /dev/null +++ b/keploy/pkg/service/report/service.go @@ -0,0 +1,20 @@ +package report + +import ( + "context" + + "go.keploy.io/server/v2/pkg/models" +) + +type Service interface { + GenerateReport(ctx context.Context) error +} + +type ReportDB interface { + GetAllTestRunIDs(ctx context.Context) ([]string, error) + GetReport(ctx context.Context, testRunID string, testSetID string) (*models.TestReport, error) +} + +type TestDB interface { + GetReportTestSets(ctx context.Context, reportID string) ([]string, error) +} diff --git a/keploy/pkg/service/secure/secure.go b/keploy/pkg/service/secure/secure.go new file mode 100644 index 0000000..d66f0b1 --- /dev/null +++ b/keploy/pkg/service/secure/secure.go @@ -0,0 +1,1222 @@ +package secure + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "path/filepath" + "regexp" + "strings" + "syscall" + "time" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/service/testsuite" + "go.uber.org/zap" +) + +type SecurityCheck struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Severity string `json:"severity"` // "CRITICAL", "HIGH", "MEDIUM", "LOW" + Type string `json:"type"` // "header", "body", "cookie", "url" + Target string `json:"target"` // "request", "response" - where to apply the check + Key string `json:"key"` // Header name, JSON path, regex pattern, etc. + Value string `json:"value,omitempty"` // Expected value or pattern to match + Operation string `json:"operation"` // "exists", "equals", "contains", "regex", "not_exists" + Status string `json:"status"` // "enabled", "disabled" +} + +type SecurityResult struct { + CheckID string `json:"check_id"` + CheckName string `json:"check_name"` + Status string `json:"status"` // "passed", "failed", "warning" + Severity string `json:"severity"` + Description string `json:"description"` + Details string `json:"details,omitempty"` + Recommendation string `json:"recommendation,omitempty"` + // Step information + StepName string `json:"step_name"` + StepMethod string `json:"step_method"` + StepURL string `json:"step_url"` + StatusCode int `json:"status_code,omitempty"` + Target string `json:"target"` // "request" or "response" - where the check was applied +} + +type StepSecurityResults struct { + StepName string `json:"step_name"` + StepMethod string `json:"step_method"` + StepURL string `json:"step_url"` + Results []SecurityResult `json:"results"` + Passed int `json:"passed"` + Failed int `json:"failed"` + Warnings int `json:"warnings"` +} + +type SecurityReport struct { + TestSuite string `json:"test_suite"` + Timestamp string `json:"timestamp"` + TotalChecks int `json:"total_checks"` + Passed int `json:"passed"` + Failed int `json:"failed"` + Warnings int `json:"warnings"` + Steps []StepSecurityResults `json:"steps"` + Summary map[string]int `json:"summary"` // severity -> count +} + +type StepRequest struct { + Method string + Headers http.Header + Body string +} + +type StepResponse struct { + StatusCode int + Headers http.Header + Body string +} + +type Step struct { + Endpoint string + StepName string + StepRequest StepRequest + StepResponse StepResponse +} + +// Built-in security checks +// Only 10 Checks, commneted checks are for code testing purpose. +var BuiltInSecurityChecks = []SecurityCheck{ + { + ID: "https-enforcement", + Name: "HTTPS Enforcement", + Description: "Check if Strict-Transport-Security header is present", + Severity: "HIGH", + Type: "header", + Target: "response", + Key: "Strict-Transport-Security", + Operation: "exists", + Status: "enabled", + }, + { + ID: "x-content-type-options", + Name: "X-Content-Type-Options", + Description: "Check for X-Content-Type-Options nosniff header", + Severity: "HIGH", + Type: "header", + Target: "response", + Key: "X-Content-Type-Options", + Value: "nosniff", + Operation: "equals", + Status: "enabled", + }, + { + ID: "x-frame-options", + Name: "X-Frame-Options", + Description: "Check for X-Frame-Options header to prevent clickjacking", + Severity: "HIGH", + Type: "header", + Target: "response", + Key: "X-Frame-Options", + Operation: "exists", + Status: "enabled", + }, + // { + // ID: "content-security-policy", + // Name: "Content Security Policy", + // Description: "Check for Content-Security-Policy header", + // Severity: "HIGH", + // Type: "header", + // Target: "response", + // Key: "Content-Security-Policy", + // Operation: "exists", + // Status: "enabled", + // }, + // { + // ID: "email-exposure", + // Name: "Email Exposure", + // Description: "Check for email addresses in response body", + // Severity: "CRITICAL", + // Type: "body", + // Target: "response", + // Key: ".+@.+\\..+", + // Operation: "regex", + // Status: "enabled", + // }, + // { + // ID: "credit-card-exposure", + // Name: "Credit Card Exposure", + // Description: "Check for credit card numbers in response body", + // Severity: "CRITICAL", + // Type: "body", + // Target: "response", + // Key: "\\b(?:\\d[ -]*?){13,16}\\b", + // Operation: "regex", + // Status: "enabled", + // }, + // { + // ID: "api-key-exposure", + // Name: "API Key Exposure", + // Description: "Check for API keys in response body", + // Severity: "CRITICAL", + // Type: "body", + // Target: "response", + // Key: "sk_(live|test)_[a-zA-Z0-9]{24}", + // Operation: "regex", + // Status: "enabled", + // }, + { + ID: "secure-cookie", + Name: "Secure Cookie", + Description: "Check if cookies have Secure flag", + Severity: "HIGH", + Type: "cookie", + Target: "response", + Key: "Secure", + Operation: "exists", + Status: "enabled", + }, + { + ID: "httponly-cookie", + Name: "HttpOnly Cookie", + Description: "Check if cookies have HttpOnly flag", + Severity: "HIGH", + Type: "cookie", + Target: "response", + Key: "HttpOnly", + Operation: "exists", + Status: "enabled", + }, + { + ID: "samesite-cookie", + Name: "SameSite Cookie", + Description: "Check if cookies have SameSite attribute", + Severity: "MEDIUM", + Type: "cookie", + Target: "response", + Key: "SameSite", + Operation: "exists", + Status: "enabled", + }, + { + ID: "cors-misconfiguration", + Name: "CORS Misconfiguration", + Description: "Check for overly permissive CORS policy", + Severity: "MEDIUM", + Type: "header", + Target: "response", + Key: "Access-Control-Allow-Origin", + Value: "*", + Operation: "not_equals", + Status: "enabled", + }, + // { + // ID: "java-stack-trace", + // Name: "Java Stack Trace", + // Description: "Check for Java stack traces in response", + // Severity: "MEDIUM", + // Type: "body", + // Target: "response", + // Key: "java\\.lang\\.Exception|at com\\.|at java\\.", + // Operation: "regex", + // Status: "enabled", + // }, + // { + // ID: "python-stack-trace", + // Name: "Python Stack Trace", + // Description: "Check for Python stack traces in response", + // Severity: "MEDIUM", + // Type: "body", + // Target: "response", + // Key: "Traceback \\(most recent call last\\)", + // Operation: "regex", + // Status: "enabled", + // }, + // { + // ID: "nodejs-error", + // Name: "Node.js Error", + // Description: "Check for Node.js errors in response", + // Severity: "MEDIUM", + // Type: "body", + // Target: "response", + // Key: "Error: ENOENT|TypeError:|ReferenceError:", + // Operation: "regex", + // Status: "enabled", + // }, + { + ID: "server-version-leak", + Name: "Server Version Leak", + Description: "Check for server version information in headers", + Severity: "MEDIUM", + Type: "header", + Target: "response", + Key: "Server", + Operation: "not_exists", + Status: "enabled", + }, + { + ID: "x-powered-by-leak", + Name: "X-Powered-By Leak", + Description: "Check for X-Powered-By header disclosure", + Severity: "MEDIUM", + Type: "header", + Target: "response", + Key: "X-Powered-By", + Operation: "not_exists", + Status: "enabled", + }, + // Request-based security checks + { + ID: "authorization-header-present", + Name: "Authorization Header Present", + Description: "Check if Authorization header is present in request", + Severity: "HIGH", + Type: "header", + Target: "request", + Key: "Authorization", + Operation: "exists", + Status: "enabled", + }, + // { + // ID: "api-key-in-request-body", + // Name: "API Key in Request Body", + // Description: "Check for API keys in request body", + // Severity: "CRITICAL", + // Type: "body", + // Target: "request", + // Key: "api[_-]?key|apikey|access[_-]?token|secret[_-]?key", + // Operation: "regex", + // Status: "enabled", + // }, + // { + // ID: "password-in-request-body", + // Name: "Password in Request Body", + // Description: "Check for passwords in request body", + // Severity: "CRITICAL", + // Type: "body", + // Target: "request", + // Key: "\"password\"\\s*:\\s*\"[^\"]+\"", + // Operation: "regex", + // Status: "enabled", + // }, + // { + // ID: "sql-injection-in-request", + // Name: "SQL Injection in Request", + // Description: "Check for potential SQL injection patterns in request body", + // Severity: "HIGH", + // Type: "body", + // Target: "request", + // Key: "('|(\\-\\-)|;|\\||\\*|(%27)|(%2D%2D)|(%7C)|(%2A))", + // Operation: "regex", + // Status: "enabled", + // }, +} + +type SecurityChecker struct { + config *config.Config + logger *zap.Logger + testsuite *testsuite.TestSuite + ruleset string +} + +func NewSecurityChecker(cfg *config.Config, logger *zap.Logger) (*SecurityChecker, error) { + testsuitePath := filepath.Join(cfg.TestSuite.TSPath, cfg.TestSuite.TSFile) + logger.Info("Parsing TestSuite File", zap.String("path", testsuitePath)) + + testsuite, err := testsuite.TSParser(testsuitePath) + if err != nil { + logger.Error("Failed to parse TestSuite file", zap.Error(err)) + return nil, fmt.Errorf("failed to parse TestSuite file: %w", err) + } + + return &SecurityChecker{ + config: cfg, + logger: logger, + testsuite: &testsuite, + ruleset: testsuite.Spec.Security.Ruleset, + }, nil +} + +func (s *SecurityChecker) Start(ctx context.Context) (*SecurityReport, error) { + if s.ruleset == "" { + s.ruleset = "basic" // Default to basic ruleset if not specified + } + // CLI override + if ruleSetValue := ctx.Value("rule-set"); ruleSetValue != nil { + if ruleSetStr, ok := ruleSetValue.(string); ok && ruleSetStr != "basic" { + s.ruleset = ruleSetStr + } + } + + // Create and execute TestSuite to get step data + tsExecutor, err := testsuite.NewTSExecutor(s.config, s.logger, true) + if err != nil { + return nil, fmt.Errorf("failed to create testsuite executor: %w", err) + } + + tsExecutor.Testsuite = s.testsuite + + // Execute the testsuite + executionReport, err := tsExecutor.Execute(ctx, nil) + if err != nil { + return nil, fmt.Errorf("failed to execute testsuite: %w", err) + } + + // Convert execution results to Step structures for security analysis + steps := s.convertExecutionReportToSteps(executionReport, tsExecutor) + + // Run security checks on the steps + securityReport := s.runSecurityChecks(ctx, steps) + + // Print the security report + s.printSecurityReport(securityReport) + + return securityReport, nil +} + +func (s *SecurityChecker) AddCustomCheck(ctx context.Context) error { + // Set up signal handling for graceful exit on Ctrl+C + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) + defer signal.Stop(signalChan) + + // Create a context that can be cancelled by signals + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Handle signals in a goroutine + go func() { + select { + case <-signalChan: + fmt.Println("\n\n⚠️ Operation cancelled by user.") + cancel() + case <-ctx.Done(): + return + } + }() + + fmt.Println("\n🔒 Add Custom Security Check") + fmt.Println("=" + strings.Repeat("=", 50)) + + var check SecurityCheck + + // Get check ID with validation loop + for { + input, err := readInputWithCancel(ctx, "Enter check ID (unique identifier): ") + if err != nil { + return err + } + + if input != "" { + check.ID = input + break + } + fmt.Println("❌ Error: Check ID is required. Please try again.") + } + + // Get check name with validation loop + for { + input, err := readInputWithCancel(ctx, "Enter check name: ") + if err != nil { + return err + } + + if input != "" { + check.Name = input + break + } + fmt.Println("❌ Error: Check name is required. Please try again.") + } + + // Get check description with validation loop + for { + input, err := readInputWithCancel(ctx, "Enter check description: ") + if err != nil { + return err + } + + if input != "" { + check.Description = input + break + } + fmt.Println("❌ Error: Check description is required. Please try again.") + } + + // Get severity with validation loop + for { + input, err := readInputWithCancel(ctx, "Enter severity (CRITICAL/HIGH/MEDIUM/LOW): ") + if err != nil { + return err + } + + severity := strings.ToUpper(input) + if severity == "CRITICAL" || severity == "HIGH" || severity == "MEDIUM" || severity == "LOW" { + check.Severity = severity + break + } + fmt.Println("❌ Error: Invalid severity. Must be one of: CRITICAL, HIGH, MEDIUM, LOW. Please try again.") + } + + // Get check type with validation loop + for { + input, err := readInputWithCancel(ctx, "Enter check type (header/body/cookie/url): ") + if err != nil { + return err + } + + checkType := strings.ToLower(input) + if checkType == "header" || checkType == "body" || checkType == "cookie" || checkType == "url" { + check.Type = checkType + break + } + fmt.Println("❌ Error: Invalid type. Must be one of: header, body, cookie, url. Please try again.") + } + + // Get target with validation loop + for { + input, err := readInputWithCancel(ctx, "Enter target (request/response): ") + if err != nil { + return err + } + + target := strings.ToLower(input) + if target == "request" || target == "response" { + check.Target = target + break + } + fmt.Println("❌ Error: Invalid target. Must be one of: request, response. Please try again.") + } + + // Get key with validation loop + for { + input, err := readInputWithCancel(ctx, "Enter key (header name, regex pattern, etc.): ") + if err != nil { + return err + } + + if input != "" { + check.Key = input + break + } + fmt.Println("❌ Error: Key is required. Please try again.") + } + + // Get expected value (optional) + input, err := readInputWithCancel(ctx, "Enter expected value (optional, press Enter to skip): ") + if err != nil { + return err + } + check.Value = input + + // Get operation with validation loop + validOps := []string{"exists", "equals", "contains", "regex", "not_exists", "not_equals"} + for { + input, err := readInputWithCancel(ctx, "Enter operation (exists/equals/contains/regex/not_exists/not_equals): ") + if err != nil { + return err + } + + operation := strings.ToLower(input) + isValidOp := false + for _, op := range validOps { + if operation == op { + isValidOp = true + break + } + } + if isValidOp { + check.Operation = operation + break + } + fmt.Printf("❌ Error: Invalid operation. Must be one of: %s. Please try again.\n", strings.Join(validOps, ", ")) + } + + // Set default status + check.Status = "enabled" + + if err := s.saveCustomCheck(ctx, check); err != nil { + return fmt.Errorf("failed to save custom check: %w", err) + } + + fmt.Printf("\n✅ Custom security check '%s' added successfully!\n", check.Name) + return nil +} + +func (s *SecurityChecker) RemoveCustomCheck(ctx context.Context) error { + // Set up signal handling for graceful exit on Ctrl+C + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) + defer signal.Stop(signalChan) + + // Create a context that can be cancelled by signals + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Handle signals in a goroutine + go func() { + select { + case <-signalChan: + fmt.Println("\n\n⚠️ Operation cancelled by user.") + cancel() + case <-ctx.Done(): + return + } + }() + + fmt.Println("\n🔒 Remove Custom Security Check") + fmt.Println("=" + strings.Repeat("=", 50)) + + customChecks, err := s.loadCustomChecks(ctx) + if err != nil { + return fmt.Errorf("failed to load custom checks: %w", err) + } + + if len(customChecks) == 0 { + fmt.Println("No custom security checks found.") + return nil + } + + fmt.Println("\nExisting custom checks:") + for i, check := range customChecks { + fmt.Printf("%d. [%s] %s - %s\n", i+1, check.ID, check.Name, check.Severity) + } + + // Check the ID from the CLI before asking + checkID := "" + if idValue := ctx.Value("id"); idValue != nil { + if idStr, ok := idValue.(string); ok { + checkID = idStr + } + } + + // Get check ID with validation loop + for { + if checkID == "" { + input, err := readInputWithCancel(ctx, "\nEnter the ID of the check to remove: ") + if err != nil { + return err + } + checkID = input + } + + if checkID == "" { + fmt.Println("❌ Error: Check ID is required. Please try again.") + continue + } + + // Check if the ID exists + found := false + for _, check := range customChecks { + if check.ID == checkID { + found = true + break + } + } + + if found { + break + } + + fmt.Printf("❌ Error: Custom check with ID '%s' not found. Please try again.\n", checkID) + checkID = "" // Reset to ask again + } + + // Remove the check + var updatedChecks []SecurityCheck + for _, check := range customChecks { + if check.ID != checkID { + updatedChecks = append(updatedChecks, check) + } + } + + if err := s.saveCustomChecks(ctx, updatedChecks); err != nil { + return fmt.Errorf("failed to save updated custom checks: %w", err) + } + + fmt.Printf("\n✅ Custom security check '%s' removed successfully!\n", checkID) + return nil +} + +func (s *SecurityChecker) UpdateCustomCheck(ctx context.Context) error { + // Set up signal handling for graceful exit on Ctrl+C + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) + defer signal.Stop(signalChan) + + // Create a context that can be cancelled by signals + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Handle signals in a goroutine + go func() { + select { + case <-signalChan: + fmt.Println("\n\n⚠️ Operation cancelled by user.") + cancel() + case <-ctx.Done(): + return + } + }() + + fmt.Println("\n🔒 Update Custom Security Check") + fmt.Println("=" + strings.Repeat("=", 50)) + + customChecks, err := s.loadCustomChecks(ctx) + if err != nil { + return fmt.Errorf("failed to load custom checks: %w", err) + } + + if len(customChecks) == 0 { + fmt.Println("No custom security checks found.") + return nil + } + + fmt.Println("\nExisting custom checks:") + for i, check := range customChecks { + fmt.Printf("%d. [%s] %s - %s\n", i+1, check.ID, check.Name, check.Severity) + } + + // Check the ID from the CLI before asking + checkID := "" + if idValue := ctx.Value("id"); idValue != nil { + if idStr, ok := idValue.(string); ok { + checkID = idStr + } + } + + // Get check ID with validation loop + var checkIndex = -1 + for { + if checkID == "" { + input, err := readInputWithCancel(ctx, "\nEnter the ID of the check to update: ") + if err != nil { + return err + } + checkID = input + } + + if checkID == "" { + fmt.Println("❌ Error: Check ID is required. Please try again.") + continue + } + + // Find the check + for i, check := range customChecks { + if check.ID == checkID { + checkIndex = i + break + } + } + + if checkIndex != -1 { + break + } + + fmt.Printf("❌ Error: Custom check with ID '%s' not found. Please try again.\n", checkID) + checkID = "" // Reset to ask again + checkIndex = -1 + } + + check := &customChecks[checkIndex] + fmt.Printf("\nUpdating check: %s\n", check.Name) + fmt.Println("Press Enter to keep current value, or enter new value:") + + // Update name + input, err := readInputWithCancel(ctx, fmt.Sprintf("Name [%s]: ", check.Name)) + if err != nil { + return err + } + if newName := strings.TrimSpace(input); newName != "" { + check.Name = newName + } + + // Update description + input, err = readInputWithCancel(ctx, fmt.Sprintf("Description [%s]: ", check.Description)) + if err != nil { + return err + } + if newDesc := strings.TrimSpace(input); newDesc != "" { + check.Description = newDesc + } + + // Get severity with validation loop + for { + input, err := readInputWithCancel(ctx, fmt.Sprintf("Severity [%s] (CRITICAL/HIGH/MEDIUM/LOW): ", check.Severity)) + if err != nil { + return err + } + newSeverity := strings.ToUpper(strings.TrimSpace(input)) + + if newSeverity == "" { + // Keep current value + break + } + + if newSeverity == "CRITICAL" || newSeverity == "HIGH" || newSeverity == "MEDIUM" || newSeverity == "LOW" { + check.Severity = newSeverity + break + } + + fmt.Println("❌ Error: Invalid severity. Must be one of: CRITICAL, HIGH, MEDIUM, LOW. Please try again.") + } + + // Get type with validation loop + for { + input, err := readInputWithCancel(ctx, fmt.Sprintf("Type [%s] (header/body/cookie/url): ", check.Type)) + if err != nil { + return err + } + newType := strings.ToLower(strings.TrimSpace(input)) + + if newType == "" { + // Keep current value + break + } + + if newType == "header" || newType == "body" || newType == "cookie" || newType == "url" { + check.Type = newType + break + } + + fmt.Println("❌ Error: Invalid type. Must be one of: header, body, cookie, url. Please try again.") + } + + // Update key + input, err = readInputWithCancel(ctx, fmt.Sprintf("Key [%s]: ", check.Key)) + if err != nil { + return err + } + if newKey := strings.TrimSpace(input); newKey != "" { + check.Key = newKey + } + + // Update value + input, err = readInputWithCancel(ctx, fmt.Sprintf("Value [%s]: ", check.Value)) + if err != nil { + return err + } + if newValue := strings.TrimSpace(input); newValue != "" { + check.Value = newValue + } + + // Get operation with validation loop + validOps := []string{"exists", "equals", "contains", "regex", "not_exists", "not_equals"} + for { + input, err := readInputWithCancel(ctx, fmt.Sprintf("Operation [%s] (exists/equals/contains/regex/not_exists/not_equals): ", check.Operation)) + if err != nil { + return err + } + newOp := strings.ToLower(strings.TrimSpace(input)) + + if newOp == "" { + // Keep current value + break + } + + isValidOp := false + for _, op := range validOps { + if newOp == op { + isValidOp = true + break + } + } + + if isValidOp { + check.Operation = newOp + break + } + + fmt.Printf("❌ Error: Invalid operation. Must be one of: %s. Please try again.\n", strings.Join(validOps, ", ")) + } + + // Get status with validation loop + for { + input, err := readInputWithCancel(ctx, fmt.Sprintf("Status [%s] (enabled/disabled): ", check.Status)) + if err != nil { + return err + } + newStatus := strings.ToLower(strings.TrimSpace(input)) + + if newStatus == "" { + // Keep current value + break + } + + if newStatus == "enabled" || newStatus == "disabled" { + check.Status = newStatus + break + } + + fmt.Println("❌ Error: Invalid status. Must be 'enabled' or 'disabled'. Please try again.") + } + + if err := s.saveCustomChecks(ctx, customChecks); err != nil { + return fmt.Errorf("failed to save updated custom checks: %w", err) + } + + fmt.Printf("\n✅ Custom security check '%s' updated successfully!\n", check.Name) + return nil +} + +func (s *SecurityChecker) ListChecks(ctx context.Context) error { + if s.ruleset == "" { + s.ruleset = "basic" // Default to basic ruleset if not specified + } + // CLI override + if ruleSetValue := ctx.Value("rule-set"); ruleSetValue != nil { + if ruleSetStr, ok := ruleSetValue.(string); ok && ruleSetStr != "basic" { + s.ruleset = ruleSetStr + } + } + + switch s.ruleset { + case "basic", "built-in": + fmt.Println("\n🔒 Built-in Security Checks") + fmt.Println("=" + strings.Repeat("=", 50)) + + for _, check := range BuiltInSecurityChecks { + // Get effective status - check both the Status field and disable list + effectiveStatus := s.getEffectiveStatus(check) + + statusIcon := "✅ Enabled" + if effectiveStatus == "disabled" { + statusIcon = "❌ Disabled" + } + + fmt.Printf("\n[%s] %s - %s (%s)\n", check.ID, check.Name, check.Severity, statusIcon) + fmt.Printf(" Type: %s | Operation: %s | Status: %s\n", check.Type, check.Operation, check.Status) + fmt.Printf(" Description: %s\n", check.Description) + if check.Key != "" { + fmt.Printf(" Key: %s\n", check.Key) + } + if check.Value != "" { + fmt.Printf(" Value: %s\n", check.Value) + } + } + + fmt.Printf("\nRuleset: %s\n", s.ruleset) + if len(s.testsuite.Spec.Security.Disable) > 0 { + fmt.Printf("Disabled checks: %v\n", s.testsuite.Spec.Security.Disable) + } + + case "custom": + fmt.Println("\n🔒 Custom Security Checks") + fmt.Println("=" + strings.Repeat("=", 50)) + + customChecks, err := s.loadCustomChecks(ctx) + if err != nil { + fmt.Printf("Error loading custom checks: %v\n", err) + return nil + } + + if len(customChecks) == 0 { + fmt.Println("No custom security checks found.") + } else { + for _, check := range customChecks { + statusIcon := "✅ Enabled" + if check.Status == "disabled" { + statusIcon = "❌ Disabled" + } + + fmt.Printf("\n[%s] %s - %s (%s)\n", check.ID, check.Name, check.Severity, statusIcon) + fmt.Printf(" Type: %s | Operation: %s | Status: %s\n", check.Type, check.Operation, check.Status) + fmt.Printf(" Description: %s\n", check.Description) + if check.Key != "" { + fmt.Printf(" Key: %s\n", check.Key) + } + if check.Value != "" { + fmt.Printf(" Value: %s\n", check.Value) + } + } + } + fmt.Printf("\nCustom checks file: %s\n", ctx.Value("checks-path")) + + default: + return fmt.Errorf("invalid rule-set value: %s. Valid values are: basic, custom.", s.ruleset) + } + + return nil +} + +// ================================================================================================= + +func (s *SecurityChecker) runSecurityChecks(ctx context.Context, steps []Step) *SecurityReport { + report := &SecurityReport{ + TestSuite: s.testsuite.Name, + Timestamp: time.Now().Format(time.RFC3339), + Steps: make([]StepSecurityResults, 0), + Summary: make(map[string]int), + } + + // Get enabled checks based on ruleset + enabledChecks := s.getEnabledChecks(ctx) + + for _, step := range steps { + stepResults := StepSecurityResults{ + StepName: step.StepName, + StepMethod: step.StepRequest.Method, + StepURL: step.Endpoint, + Results: make([]SecurityResult, 0), + } + + for _, check := range enabledChecks { + result := s.executeCheck(check, step) + if result != nil { + stepResults.Results = append(stepResults.Results, *result) + report.Summary[result.Severity]++ + + switch result.Status { + case "passed": + report.Passed++ + stepResults.Passed++ + case "failed": + report.Failed++ + stepResults.Failed++ + case "warning": + report.Warnings++ + stepResults.Warnings++ + } + } + } + + report.Steps = append(report.Steps, stepResults) + } + + // Calculate total checks across all steps + totalChecks := 0 + for _, stepResult := range report.Steps { + totalChecks += len(stepResult.Results) + } + report.TotalChecks = totalChecks + + return report +} + +func (s *SecurityChecker) executeCheck(check SecurityCheck, step Step) *SecurityResult { + result := &SecurityResult{ + CheckID: check.ID, + CheckName: check.Name, + Severity: check.Severity, + Description: check.Description, + StepName: step.StepName, + StepMethod: step.StepRequest.Method, + StepURL: step.Endpoint, + StatusCode: step.StepResponse.StatusCode, + Target: check.Target, + } + + target := check.Target + if target == "" { + target = "response" + } + + switch check.Type { + case "header": + return s.checkHeader(check, step, result, target) + case "body": + return s.checkBody(check, step, result, target) + case "cookie": + return s.checkCookie(check, step, result) + case "url": + return s.checkURL(check, step, result) + } + + return nil +} + +func (s *SecurityChecker) checkHeader(check SecurityCheck, step Step, result *SecurityResult, target string) *SecurityResult { + var headerValue string + + if target == "request" { + headerValue = step.StepRequest.Headers.Get(check.Key) + } else { + headerValue = step.StepResponse.Headers.Get(check.Key) + } + + switch check.Operation { + case "exists": + if headerValue == "" { + result.Status = "failed" + result.Details = fmt.Sprintf("Missing %s header in %s", check.Key, target) + result.Recommendation = fmt.Sprintf("Add %s header to %s to improve security", check.Key, target) + } else { + result.Status = "passed" + result.Details = fmt.Sprintf("%s header present in %s: %s", check.Key, target, headerValue) + } + + case "equals": + if headerValue == "" { + result.Status = "failed" + result.Details = fmt.Sprintf("Missing %s header in %s", check.Key, target) + result.Recommendation = fmt.Sprintf("Add %s: %s header to %s", check.Key, check.Value, target) + } else if !strings.EqualFold(headerValue, check.Value) { + result.Status = "failed" + result.Details = fmt.Sprintf("%s header in %s has incorrect value: %s (expected: %s)", check.Key, target, headerValue, check.Value) + result.Recommendation = fmt.Sprintf("Set %s header in %s to %s", check.Key, target, check.Value) + } else { + result.Status = "passed" + result.Details = fmt.Sprintf("%s header in %s correctly set to %s", check.Key, target, check.Value) + } + + case "contains": + if headerValue == "" { + result.Status = "failed" + result.Details = fmt.Sprintf("Missing %s header in %s", check.Key, target) + result.Recommendation = fmt.Sprintf("Add %s header containing %s to %s", check.Key, check.Value, target) + } else if !strings.Contains(strings.ToLower(headerValue), strings.ToLower(check.Value)) { + result.Status = "failed" + result.Details = fmt.Sprintf("%s header in %s doesn't contain expected value: %s (looking for: %s)", check.Key, target, headerValue, check.Value) + result.Recommendation = fmt.Sprintf("Update %s header in %s to include %s", check.Key, target, check.Value) + } else { + result.Status = "passed" + result.Details = fmt.Sprintf("%s header in %s contains expected value", check.Key, target) + } + + case "not_exists": + if headerValue != "" { + result.Status = "failed" + result.Details = fmt.Sprintf("%s header should not be present in %s but found: %s", check.Key, target, headerValue) + result.Recommendation = fmt.Sprintf("Remove %s header from %s", check.Key, target) + } else { + result.Status = "passed" + result.Details = fmt.Sprintf("%s header correctly not present in %s", check.Key, target) + } + + case "not_equals": + if headerValue == check.Value { + result.Status = "failed" + result.Details = fmt.Sprintf("%s header in %s has insecure value: %s", check.Key, target, headerValue) + result.Recommendation = fmt.Sprintf("Change %s header value in %s from %s to a more secure configuration", check.Key, target, check.Value) + } else { + result.Status = "passed" + result.Details = fmt.Sprintf("%s header in %s has secure value", check.Key, target) + } + } + + return result +} + +func (s *SecurityChecker) checkBody(check SecurityCheck, step Step, result *SecurityResult, target string) *SecurityResult { + var body string + + if target == "request" { + body = step.StepRequest.Body + } else { + body = step.StepResponse.Body + } + + switch check.Operation { + case "regex": + regex, err := regexp.Compile(check.Key) + if err != nil { + s.logger.Error("Invalid regex pattern", zap.String("pattern", check.Key), zap.Error(err)) + return nil + } + + // Skip if key is in allowlist + if s.isInAllowList("keys", check.Name) { + result.Status = "passed" + result.Details = "Check skipped - in allowlist" + return result + } + + matches := regex.FindAllString(body, -1) + if len(matches) > 0 { + result.Status = "failed" + result.Details = fmt.Sprintf("Found %d potential matches in %s body", len(matches), target) + result.Recommendation = fmt.Sprintf("Remove sensitive data from %s body", target) + } else { + result.Status = "passed" + result.Details = fmt.Sprintf("No sensitive data patterns found in %s body", target) + } + + case "contains": + if strings.Contains(body, check.Key) { + result.Status = "failed" + result.Details = fmt.Sprintf("%s body contains: %s", target, check.Key) + result.Recommendation = fmt.Sprintf("Remove sensitive information from %s body", target) + } else { + result.Status = "passed" + result.Details = fmt.Sprintf("%s body doesn't contain sensitive information", target) + } + + case "not_contains": + if !strings.Contains(body, check.Key) { + result.Status = "passed" + result.Details = fmt.Sprintf("%s body correctly doesn't contain sensitive information", target) + } else { + result.Status = "failed" + result.Details = fmt.Sprintf("%s body should not contain: %s", target, check.Key) + result.Recommendation = fmt.Sprintf("Remove sensitive information from %s body", target) + } + } + + return result +} + +func (s *SecurityChecker) checkCookie(check SecurityCheck, step Step, result *SecurityResult) *SecurityResult { + // For cookies, we typically only check response cookies (Set-Cookie headers) + // since request cookies are usually sent via Cookie header which could be checked as headers + cookies := step.StepResponse.Headers["Set-Cookie"] + if len(cookies) == 0 { + result.Status = "passed" + result.Details = "No cookies set in response" + return result + } + + switch check.Operation { + case "exists": + found := false + for _, cookie := range cookies { + if strings.Contains(cookie, check.Key) { + found = true + break + } + } + + if !found { + result.Status = "failed" + result.Details = fmt.Sprintf("Cookies missing %s attribute", check.Key) + result.Recommendation = fmt.Sprintf("Add %s attribute to cookies", check.Key) + } else { + result.Status = "passed" + result.Details = fmt.Sprintf("Cookies have %s attribute", check.Key) + } + } + + return result +} + +func (s *SecurityChecker) checkURL(check SecurityCheck, step Step, result *SecurityResult) *SecurityResult { + switch check.Operation { + case "regex": + regex, err := regexp.Compile(check.Key) + if err != nil { + s.logger.Error("Invalid regex pattern", zap.String("pattern", check.Key), zap.Error(err)) + return nil + } + + if regex.MatchString(step.Endpoint) { + result.Status = "failed" + result.Details = fmt.Sprintf("URL matches insecure pattern: %s", check.Key) + result.Recommendation = "Review URL structure for security issues" + } else { + result.Status = "passed" + result.Details = "URL doesn't match insecure patterns" + } + + case "contains": + if strings.Contains(step.Endpoint, check.Key) { + result.Status = "failed" + result.Details = fmt.Sprintf("URL contains insecure element: %s", check.Key) + result.Recommendation = "Remove insecure elements from URL" + } else { + result.Status = "passed" + result.Details = "URL doesn't contain insecure elements" + } + } + + return result +} diff --git a/keploy/pkg/service/secure/service.go b/keploy/pkg/service/secure/service.go new file mode 100644 index 0000000..3687656 --- /dev/null +++ b/keploy/pkg/service/secure/service.go @@ -0,0 +1,11 @@ +package secure + +import "context" + +type Service interface { + Start(ctx context.Context) (*SecurityReport, error) + AddCustomCheck(ctx context.Context) error + RemoveCustomCheck(ctx context.Context) error + UpdateCustomCheck(ctx context.Context) error + ListChecks(ctx context.Context) error +} diff --git a/keploy/pkg/service/secure/utils.go b/keploy/pkg/service/secure/utils.go new file mode 100644 index 0000000..34fbd6f --- /dev/null +++ b/keploy/pkg/service/secure/utils.go @@ -0,0 +1,337 @@ +package secure + +import ( + "bufio" + "context" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + + "go.keploy.io/server/v2/pkg/service/testsuite" + "go.uber.org/zap" + "gopkg.in/yaml.v3" +) + +// convertExecutionReportToSteps converts testsuite execution results to Step structures for security checks +func (s *SecurityChecker) convertExecutionReportToSteps(report *testsuite.ExecutionReport, executor *testsuite.TSExecutor) []Step { + steps := make([]Step, 0, len(report.StepsResult)) + + for i, stepResult := range report.StepsResult { + // Skip failed steps - don't run security checks on failed steps + if stepResult.Status == "failed" { + s.logger.Info("Skipping security checks for failed step", + zap.String("stepName", stepResult.StepName), + zap.String("failureReason", stepResult.FailureReason)) + continue + } + + // Get the corresponding test step from the testsuite + if i >= len(executor.Testsuite.Spec.Steps) { + continue + } + testStep := executor.Testsuite.Spec.Steps[i] + + requestHeaders := make(http.Header) + for key, value := range testStep.Headers { + interpolatedValue := executor.InterpolateVariables(value) + requestHeaders.Add(key, interpolatedValue) + } + + // Get interpolated body + interpolatedBody := executor.InterpolateVariables(testStep.Body) + + responseHeaders := stepResult.Header + if responseHeaders == nil { + responseHeaders = make(http.Header) + } + + step := Step{ + Endpoint: stepResult.URL, + StepName: stepResult.StepName, + StepRequest: StepRequest{ + Method: stepResult.Method, + Headers: requestHeaders, + Body: interpolatedBody, + }, + StepResponse: StepResponse{ + StatusCode: stepResult.StatusCode, + Headers: responseHeaders, + Body: stepResult.Body, + }, + } + + steps = append(steps, step) + } + + return steps +} + +// readInputWithCancel reads a line of input from stdin with cancellation support +func readInputWithCancel(ctx context.Context, prompt string) (string, error) { + fmt.Print(prompt) + + inputChan := make(chan string, 1) + errorChan := make(chan error, 1) + + go func() { + scanner := bufio.NewScanner(os.Stdin) + if scanner.Scan() { + inputChan <- scanner.Text() + } else { + if err := scanner.Err(); err != nil { + errorChan <- err + } else { + errorChan <- fmt.Errorf("EOF") + } + } + }() + + select { + case <-ctx.Done(): + return "", fmt.Errorf("operation cancelled") + case input := <-inputChan: + return strings.TrimSpace(input), nil + case err := <-errorChan: + return "", err + } +} + +func (s *SecurityChecker) isInAllowList(category, value string) bool { + if s.testsuite.Spec.Security.AllowList.Headers != nil && category == "headers" { + for _, header := range s.testsuite.Spec.Security.AllowList.Headers { + if strings.EqualFold(header, value) { + return true + } + } + } + + if s.testsuite.Spec.Security.AllowList.Keys != nil && category == "keys" { + for _, key := range s.testsuite.Spec.Security.AllowList.Keys { + if strings.Contains(strings.ToLower(value), strings.ToLower(key)) { + return true + } + } + } + + return false +} + +func (s *SecurityChecker) printSecurityReport(report *SecurityReport) { + fmt.Printf("\n🔒 Security Analysis Report\n") + fmt.Printf("Test Suite: %s\n", report.TestSuite) + fmt.Printf("Timestamp: %s\n", report.Timestamp) + fmt.Printf("Total Checks: %d\n", report.TotalChecks) + fmt.Printf("✅ Passed: %d\n", report.Passed) + fmt.Printf("❌ Failed: %d\n", report.Failed) + fmt.Printf("⚠️ Warnings: %d\n\n", report.Warnings) + + // Print results grouped by step + for i, stepResult := range report.Steps { + fmt.Printf("=== STEP %d: %s ===\n", i+1, stepResult.StepName) + fmt.Printf("Method: %s | URL: %s\n", stepResult.StepMethod, stepResult.StepURL) + fmt.Printf("✅ Passed: %d | ❌ Failed: %d | ⚠️ Warnings: %d\n\n", + stepResult.Passed, stepResult.Failed, stepResult.Warnings) + + // Group step results by severity + severities := []string{"CRITICAL", "HIGH", "MEDIUM", "LOW"} + for _, severity := range severities { + results := s.filterResultsBySeverity(stepResult.Results, severity) + if len(results) > 0 { + fmt.Printf("--- %s SEVERITY ---\n", severity) + for _, result := range results { + var status string + switch result.Status { + case "passed": + status = "✅" + case "failed": + status = "❌" + case "warning": + status = "⚠️" + default: + status = "❓" // Unknown status + } + + fmt.Printf("%s [%s] %s\n", status, result.CheckID, result.CheckName) + fmt.Printf(" Target: %s | %s\n", result.Target, result.Details) + if result.Recommendation != "" { + fmt.Printf(" 💡 %s\n", result.Recommendation) + } + fmt.Println() + } + } + } + fmt.Println("================================================") + } +} + +func (s *SecurityChecker) filterResultsBySeverity(results []SecurityResult, severity string) []SecurityResult { + filtered := make([]SecurityResult, 0) + for _, result := range results { + if result.Severity == severity { + filtered = append(filtered, result) + } + } + return filtered +} + +func (s *SecurityChecker) getEnabledChecks(ctx context.Context) []SecurityCheck { + var enabled []SecurityCheck + + switch s.ruleset { + case "basic": + // Use built-in checks only + for _, check := range BuiltInSecurityChecks { + // Check effective status considering both Status field and disable list + effectiveStatus := s.getEffectiveStatus(check) + if effectiveStatus == "disabled" { + continue + } + enabled = append(enabled, check) + } + + case "custom": + // Use custom checks only + customChecks, err := s.loadCustomChecks(ctx) + if err != nil { + s.logger.Error("Failed to load custom checks", zap.Error(err)) + // Fallback to built-in checks if custom checks can't be loaded + for _, check := range BuiltInSecurityChecks { + effectiveStatus := s.getEffectiveStatus(check) + if effectiveStatus == "disabled" { + continue + } + enabled = append(enabled, check) + } + } else { + if len(customChecks) == 0 { + s.logger.Warn("No custom checks found, consider adding some or using 'basic' ruleset") + } + for _, check := range customChecks { + effectiveStatus := s.getEffectiveStatus(check) + if effectiveStatus == "disabled" { + continue + } + enabled = append(enabled, check) + } + } + + default: + // Default to built-in checks for unknown rulesets + s.logger.Warn("Unknown ruleset, falling back to basic", zap.String("ruleset", s.ruleset)) + for _, check := range BuiltInSecurityChecks { + effectiveStatus := s.getEffectiveStatus(check) + if effectiveStatus == "disabled" { + continue + } + enabled = append(enabled, check) + } + } + + s.logger.Debug("Loaded security checks", + zap.String("ruleset", s.ruleset), + zap.Int("totalChecks", len(enabled))) + + return enabled +} + +// getEffectiveStatus returns the effective status of a check considering both +// the Status field and the disable list in the testsuite +func (s *SecurityChecker) getEffectiveStatus(check SecurityCheck) string { + // If explicitly disabled in testsuite, it's disabled regardless of Status field + if s.isCheckDisabled(check.ID) { + return "disabled" + } + + // If Status field is set to disabled, it's disabled + if check.Status == "disabled" { + return "disabled" + } + + // Default to enabled + return "enabled" +} + +func (s *SecurityChecker) isCheckDisabled(checkID string) bool { + if s.testsuite.Spec.Security.Disable != nil { + for _, disabledID := range s.testsuite.Spec.Security.Disable { + if fmt.Sprintf("%v", disabledID) == checkID { + return true + } + } + } + return false +} + +// Helper methods for custom checks file management +func (s *SecurityChecker) saveCustomCheck(ctx context.Context, check SecurityCheck) error { + customChecks, err := s.loadCustomChecks(ctx) + if err != nil && !os.IsNotExist(err) { + return err + } + + // Check if check with same ID already exists + for _, existingCheck := range customChecks { + if existingCheck.ID == check.ID { + return fmt.Errorf("custom check with ID '%s' already exists", check.ID) + } + } + + customChecks = append(customChecks, check) + return s.saveCustomChecks(ctx, customChecks) +} + +func (s *SecurityChecker) loadCustomChecks(ctx context.Context) ([]SecurityCheck, error) { + customPath := s.getCustomChecksPath(ctx) + + data, err := os.ReadFile(customPath) + if err != nil { + if os.IsNotExist(err) { + return []SecurityCheck{}, nil + } + return nil, err + } + + var customChecks []SecurityCheck + if err := yaml.Unmarshal(data, &customChecks); err != nil { + return nil, fmt.Errorf("failed to parse custom checks file: %w", err) + } + + return customChecks, nil +} + +func (s *SecurityChecker) saveCustomChecks(ctx context.Context, checks []SecurityCheck) error { + customPath := s.getCustomChecksPath(ctx) + + // Create directory if it doesn't exist + if err := os.MkdirAll(filepath.Dir(customPath), 0755); err != nil { + return fmt.Errorf("failed to create custom checks directory: %w", err) + } + + data, err := yaml.Marshal(checks) + if err != nil { + return fmt.Errorf("failed to marshal custom checks: %w", err) + } + + if err := os.WriteFile(customPath, data, 0644); err != nil { + return fmt.Errorf("failed to write custom checks file: %w", err) + } + + return nil +} + +func (s *SecurityChecker) getCustomChecksPath(ctx context.Context) string { + path, _ := ctx.Value("checks-path").(string) + + // CLI Override + if path != "keploy/secure/custom-checks.yaml" { + return path + } + + if s.testsuite.Spec.Security.CustomPath != "" { + return s.testsuite.Spec.Security.CustomPath + } + + return path +} diff --git a/keploy/pkg/service/service.go b/keploy/pkg/service/service.go new file mode 100644 index 0000000..a39572d --- /dev/null +++ b/keploy/pkg/service/service.go @@ -0,0 +1,9 @@ +// Package service provides the service interface for the service package. +package service + +import "context" + +type Auth interface { + GetToken(ctx context.Context) (string, error) + Login(ctx context.Context) bool +} diff --git a/keploy/pkg/service/testsuite/service.go b/keploy/pkg/service/testsuite/service.go new file mode 100644 index 0000000..35690e9 --- /dev/null +++ b/keploy/pkg/service/testsuite/service.go @@ -0,0 +1,11 @@ +package testsuite + +import ( + "context" + + "golang.org/x/time/rate" +) + +type Service interface { + Execute(ctx context.Context, limiter *rate.Limiter) (*ExecutionReport, error) +} diff --git a/keploy/pkg/service/testsuite/testsuite.go b/keploy/pkg/service/testsuite/testsuite.go new file mode 100644 index 0000000..5a55d36 --- /dev/null +++ b/keploy/pkg/service/testsuite/testsuite.go @@ -0,0 +1,501 @@ +package testsuite + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "go.keploy.io/server/v2/config" + "go.uber.org/zap" + "golang.org/x/time/rate" + "gopkg.in/yaml.v3" +) + +// TestSuite represents the structure of a test suite YAML file +type TestSuite struct { + Version string `yaml:"version"` + Kind string `yaml:"kind"` + Name string `yaml:"name"` + Spec TestSuiteSpec `yaml:"spec"` +} + +// TestSuiteSpec contains the metadata and steps for a test suite +type TestSuiteSpec struct { + Metadata TestSuiteMetadata `yaml:"metadata"` + Security Security `yaml:"security,omitempty"` + Load LoadOptions `yaml:"load,omitempty"` + Steps []TestStep `yaml:"steps"` +} + +// TestSuiteMetadata contains description and other metadata for a test suite +type TestSuiteMetadata struct { + Description string `yaml:"description"` +} + +// Security contains security-related configurations for the test suite +type Security struct { + Ruleset string `yaml:"ruleset"` + CustomPath string `yaml:"custom_path,omitempty"` + SeverityThreshold string `yaml:"severity_threshold"` + Disable []string `yaml:"disable,omitempty"` + AllowList AllowList `yaml:"allow_list"` +} + +type AllowList struct { + Headers []string `yaml:"headers,omitempty"` + Keys []string `yaml:"keys,omitempty"` +} + +// LoadOptions represents load testing options +type LoadOptions struct { + Profile string `yaml:"profile"` + VUs int `yaml:"vus"` + Duration string `yaml:"duration"` + RPS int `yaml:"rps"` + Stages []LoadStage `yaml:"stages,omitempty"` + Thresholds []Threshold `yaml:"thresholds,omitempty"` +} + +// LoadStage represents a single stage in a load test +type LoadStage struct { + Duration string `yaml:"duration"` + Target int `yaml:"target"` +} + +// Threshold represents a performance threshold in load testing +type Threshold struct { + Metric string `yaml:"metric"` + Condition string `yaml:"condition"` + Severity string `yaml:"severity"` + Comment string `yaml:"comment,omitempty"` +} + +// TestStep represents a single API call step in the test suite +type TestStep struct { + Name string `yaml:"name"` + Method string `yaml:"method"` + URL string `yaml:"url"` + Body string `yaml:"body,omitempty"` + Headers map[string]string `yaml:"headers,omitempty"` + Extract map[string]string `yaml:"extract,omitempty"` + Assert []TSAssertion `yaml:"assert,omitempty"` +} + +// Assertion represents an assertion to validate API responses +type TSAssertion struct { + Type string `yaml:"type"` + Key string `yaml:"key,omitempty"` + ExpectedString string `yaml:"expected_string,omitempty"` +} + +// StepResult represents the result of executing a single test step +type StepResult struct { + StepName string `json:"step_name"` + Method string `json:"method"` + URL string `json:"url"` + Status string `json:"status"` + StatusCode int `json:"status_code,omitempty"` + Header http.Header `json:"header,omitempty"` + Body string `json:"body,omitempty"` + ResponseTime time.Duration `json:"response_time"` + FailureReason string `json:"failure_reason,omitempty"` + ExtractedVars map[string]string `json:"extracted_vars,omitempty"` + ReqBytes int64 `json:"req_bytes"` + ResBytes int64 `json:"res_bytes"` +} + +// ExecutionReport represents the summary of the test suite execution +type ExecutionReport struct { + SuiteName string `json:"suite_name"` + TotalSteps int `json:"total_steps"` + FailedSteps int `json:"failed_steps"` + StepsResult []StepResult `json:"steps_result"` + ExecutionTime time.Duration `json:"execution_time"` // Total execution of the test suite +} + +type TSExecutor struct { + config *config.Config + logger *zap.Logger + Testsuite *TestSuite + client *http.Client + baseURL string + tsPath string + tsFile string + variables map[string]string +} + +func NewTSExecutor(cfg *config.Config, logger *zap.Logger, skipParsing bool) (*TSExecutor, error) { + var testsuite *TestSuite + if !skipParsing { + if cfg.TestSuite.TSPath == "" { + logger.Error("test suite path is not set") + return nil, fmt.Errorf("test suite path is not set, use --ts-path flag to set it") + } + + if cfg.TestSuite.TSFile == "" { + logger.Error("test suite file is not set") + return nil, fmt.Errorf("test suite file is not set, use --ts-file flag to set it") + } + + testsuitePath := filepath.Join(cfg.TestSuite.TSPath, cfg.TestSuite.TSFile) + logger.Debug("parsing test suite file", zap.String("file", testsuitePath)) + + ts, err := TSParser(testsuitePath) + if err != nil { + logger.Error("failed to parse test suite", zap.Error(err)) + return nil, err + } + testsuite = &ts + logger.Info("test suite parsed successfully", zap.String("file", testsuitePath)) + } + + return &TSExecutor{ + config: cfg, + logger: logger, + Testsuite: testsuite, + client: &http.Client{ + Timeout: time.Duration(30) * time.Second, + Transport: &http.Transport{ + // disable tls check + //nolint:gosec + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + }, + baseURL: cfg.TestSuite.BaseURL, + tsPath: cfg.TestSuite.TSPath, + tsFile: cfg.TestSuite.TSFile, + variables: make(map[string]string), + }, nil +} + +func (e *TSExecutor) Execute(ctx context.Context, limiter *rate.Limiter) (*ExecutionReport, error) { + if e.baseURL == "" { + e.logger.Error("base URL is not set for the test suite execution") + return nil, fmt.Errorf("base URL is not set for the test suite execution") + } + + if e.client == nil { + e.logger.Error("HTTP client is not initialized for the test suite execution") + return nil, fmt.Errorf("HTTP client is not initialized for the test suite execution") + } + + if e.Testsuite == nil { + e.logger.Error("test suite is not set for execution") + return nil, fmt.Errorf("test suite is not set for execution, please provide a valid test suite using --ts-file or -f flag") + } + + if ctx.Value("command") == "testsuite" { + e.logger.Info("executing test suite", zap.String("path", e.tsPath), zap.String("baseURL", e.baseURL)) + } + + e.logger.Debug("test suite details", + zap.String("name", e.Testsuite.Name), + zap.String("version", e.Testsuite.Version), + zap.String("kind", e.Testsuite.Kind), + zap.String("description", e.Testsuite.Spec.Metadata.Description), + ) + e.logger.Debug("number of steps in the test suite", zap.Int("steps", len(e.Testsuite.Spec.Steps))) + e.logger.Debug("base URL for the test suite", zap.String("baseURL", e.baseURL)) + + er := &ExecutionReport{ + SuiteName: e.Testsuite.Name, + TotalSteps: len(e.Testsuite.Spec.Steps), + FailedSteps: 0, + StepsResult: make([]StepResult, 0, len(e.Testsuite.Spec.Steps)), + ExecutionTime: time.Duration(0), + } + + startTime := time.Now() + + for _, step := range e.Testsuite.Spec.Steps { + e.logger.Debug("executing step", zap.String("name", step.Name), zap.String("method", step.Method), zap.String("url", step.URL)) + if limiter != nil { + if err := limiter.Wait(ctx); err != nil { + e.logger.Debug("Rate limiter wait warn", zap.Error(err)) + continue + } + } + result, err := e.executeStep(step) + if err != nil { + e.logger.Error("failed to execute step", zap.String("step", step.Name), zap.Error(err)) + } + e.logger.Debug("step executed", zap.String("step", step.Name), zap.String("status", result.Status), zap.Any("result", result)) + er.StepsResult = append(er.StepsResult, *result) + if result.Status == "failed" { + er.FailedSteps++ + } + } + + er.ExecutionTime = time.Since(startTime) + + if ctx.Value("command") == "testsuite" { + fmt.Println("Test Suite Execution Report:") + fmt.Printf(" Suite Name: %s\n", er.SuiteName) + fmt.Printf(" Base URL: %s\n", e.baseURL) + fmt.Printf(" Total Steps: %d\n", er.TotalSteps) + fmt.Printf(" Failed Steps: %d\n", er.FailedSteps) + fmt.Printf(" Execution Time: %s\n", er.ExecutionTime) + fmt.Println(" Steps Result:") + for _, stepResult := range er.StepsResult { + fmt.Printf(" Step Name: %s\n", stepResult.StepName) + fmt.Printf(" Status: %s\n", stepResult.Status) + if stepResult.FailureReason != "" { + fmt.Printf(" Failure Reason: %s\n", stepResult.FailureReason) + } + } + + reportDir := filepath.Join(e.tsPath, "ts_reports") + if err := os.MkdirAll(reportDir, 0755); err != nil { + e.logger.Error("failed to create report directory", zap.String("dir", reportDir), zap.Error(err)) + return nil, fmt.Errorf("failed to create report directory: %v", err) + } + reportFile := filepath.Join(reportDir, fmt.Sprintf("%s_report_%s", time.Now().Format("20060102150405"), e.tsFile)) + file, err := os.Create(reportFile) + if err != nil { + e.logger.Error("failed to create report file", zap.String("file", reportFile), zap.Error(err)) + return nil, fmt.Errorf("failed to create report file: %v", err) + } + defer file.Close() + + data, err := yaml.Marshal(er) + if err != nil { + e.logger.Error("failed to marshal report data", zap.String("file", reportFile), zap.Error(err)) + return nil, fmt.Errorf("failed to marshal report data: %v", err) + } + _, err = file.Write(data) + if err != nil { + e.logger.Error("failed to write report data to file", zap.String("file", reportFile), zap.Error(err)) + return nil, fmt.Errorf("failed to write report data to file: %v", err) + } + e.logger.Info("test suite execution report saved", zap.String("file", reportFile)) + } + + return er, nil +} + +// executeStep executes a single test step and returns the result +func (e *TSExecutor) executeStep(step TestStep) (*StepResult, error) { + interpolatedURL := e.InterpolateVariables(step.URL) + interpolatedBody := e.InterpolateVariables(step.Body) + + result := &StepResult{ + StepName: step.Name, + Method: step.Method, + URL: interpolatedURL, + Status: "failed", // Default to failed, will update to passed if successful + ExtractedVars: make(map[string]string), + } + + fullURL := e.baseURL + interpolatedURL + e.logger.Debug("sending request", zap.String("url", fullURL), zap.String("method", step.Method)) + + req, err := http.NewRequest(step.Method, fullURL, strings.NewReader(interpolatedBody)) + if err != nil { + result.FailureReason = fmt.Sprintf("failed to create request: %v", err) + return result, err + } + if req.Body != nil { + bodyBytes, err := io.ReadAll(strings.NewReader(interpolatedBody)) + if err != nil { + result.FailureReason = fmt.Sprintf("failed to read request body: %v", err) + return result, err + } + result.ReqBytes = int64(len(bodyBytes)) + } else { + result.ReqBytes = 0 + } + + for key, value := range step.Headers { + interpolatedValue := e.InterpolateVariables(value) + req.Header.Add(key, interpolatedValue) + } + + const hardcodedJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc5NTU5LCJleHAiOjE3NjUzNzE1NTl9.6eYubLPlOcbpGyoM89848DKxom_AvvruU2tskv7l3Aw" + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", hardcodedJWT)) + + startTime := time.Now() + + resp, err := e.client.Do(req) + if err != nil { + result.FailureReason = fmt.Sprintf("failed to send request: %v", err) + return result, err + } + defer resp.Body.Close() + + result.ResponseTime = time.Since(startTime) + + body, err := io.ReadAll(resp.Body) + if err != nil { + result.FailureReason = fmt.Sprintf("failed to read response body: %v", err) + return result, err + } + result.ResBytes = int64(len(body)) + + result.StatusCode = resp.StatusCode + result.Header = resp.Header + result.Body = string(body) + + assertionsPassed := true + for _, assertion := range step.Assert { + interpolatedExpectedString := e.InterpolateVariables(assertion.ExpectedString) + assertionCopy := assertion + assertionCopy.ExpectedString = interpolatedExpectedString + + passed, reason := e.processAssertion(assertionCopy, resp, body) + if !passed { + assertionsPassed = false + result.FailureReason = reason + e.logger.Debug("assertion failed", + zap.String("type", assertion.Type), + zap.String("reason", reason)) + break + } + e.logger.Debug("assertion passed", zap.String("type", assertion.Type)) + } + + if assertionsPassed && len(step.Extract) > 0 { + extracted, err := e.extractVariables(step.Extract, body) + if err != nil { + result.FailureReason = fmt.Sprintf("failed to extract variables: %v", err) + return result, err + } + result.ExtractedVars = extracted + + for k, v := range extracted { + e.variables[k] = v + e.logger.Debug("variable extracted", zap.String("name", k), zap.String("value", v)) + } + } + + if assertionsPassed { + result.Status = "passed" + } + + return result, nil +} + +// Helper function to process assertions +func (e *TSExecutor) processAssertion(assertion TSAssertion, resp *http.Response, body []byte) (bool, string) { + switch assertion.Type { + case "status_code": + expectedCode := assertion.ExpectedString + actualCode := fmt.Sprintf("%d", resp.StatusCode) + if expectedCode != actualCode { + return false, fmt.Sprintf("expected status code %s but got %s", expectedCode, actualCode) + } + case "json_equal": + var jsonData interface{} + if err := json.Unmarshal(body, &jsonData); err != nil { + return false, fmt.Sprintf("failed to parse JSON response: %v", err) + } + + actualValue, err := extractJsonValue(jsonData, assertion.Key) + if err != nil { + return false, fmt.Sprintf("failed to extract JSON value for key %s: %v", assertion.Key, err) + } + + actualString := fmt.Sprintf("%v", actualValue) + + if actualString != assertion.ExpectedString { + return false, fmt.Sprintf("for key %s, expected value '%s' but got '%s'", + assertion.Key, assertion.ExpectedString, actualString) + } + case "json_array_contains": + var jsonData interface{} + if err := json.Unmarshal(body, &jsonData); err != nil { + return false, fmt.Sprintf("failed to parse JSON response: %v", err) + } + + arrayValue, err := extractJsonValue(jsonData, assertion.Key) + if err != nil { + return false, fmt.Sprintf("failed to extract JSON value for key %s: %v", assertion.Key, err) + } + + // Check if the extracted value is an array + array, ok := arrayValue.([]interface{}) + if !ok { + return false, fmt.Sprintf("value at key %s is not an array", assertion.Key) + } + + // Parse the expected string as JSON + var expectedJSON interface{} + if err := json.Unmarshal([]byte(assertion.ExpectedString), &expectedJSON); err != nil { + return false, fmt.Sprintf("failed to parse expected JSON: %v", err) + } + + // Check if any element in the array matches the expected JSON + for _, element := range array { + elementBytes, err := json.Marshal(element) + if err != nil { + continue + } + expectedBytes, err := json.Marshal(expectedJSON) + if err != nil { + continue + } + if string(elementBytes) == string(expectedBytes) { + return true, "" + } + } + + return false, fmt.Sprintf("array at key %s does not contain expected JSON object: %s", assertion.Key, assertion.ExpectedString) + default: + return false, fmt.Sprintf("unsupported assertion type: %s", assertion.Type) + } + + return true, "" +} + +// Helper function to interpolate variables in strings +func (e *TSExecutor) InterpolateVariables(input string) string { + if len(e.variables) == 0 || input == "" { + return input + } + + result := input + variableRegex := regexp.MustCompile(`\{\{(\w+)\}\}`) + + matches := variableRegex.FindAllStringSubmatch(input, -1) + for _, match := range matches { + if len(match) == 2 { + placeholder := match[0] // {{varname}} + varName := match[1] // varname + + if value, exists := e.variables[varName]; exists { + result = strings.Replace(result, placeholder, value, -1) + } + } + } + + return result +} + +// Helper function to extract variables from response +func (e *TSExecutor) extractVariables(extractMap map[string]string, body []byte) (map[string]string, error) { + var jsonData interface{} + if err := json.Unmarshal(body, &jsonData); err != nil { + return nil, fmt.Errorf("failed to parse JSON response: %v", err) + } + + result := make(map[string]string) + + for varName, jsonPath := range extractMap { + value, err := extractJsonValue(jsonData, jsonPath) + if err != nil { + return nil, fmt.Errorf("failed to extract variable %s from path %s: %v", + varName, jsonPath, err) + } + + result[varName] = fmt.Sprintf("%v", value) + } + + return result, nil +} diff --git a/keploy/pkg/service/testsuite/utils.go b/keploy/pkg/service/testsuite/utils.go new file mode 100644 index 0000000..0af7324 --- /dev/null +++ b/keploy/pkg/service/testsuite/utils.go @@ -0,0 +1,109 @@ +package testsuite + +import ( + "fmt" + "os" + "strings" + + "gopkg.in/yaml.v3" +) + +// TSParser parses a YAML file into a TestSuite struct +func TSParser(path string) (TestSuite, error) { + var ts TestSuite + + fileInfo, err := os.Stat(path) + if err != nil { + return ts, fmt.Errorf("error accessing file: %w", err) + } + + if fileInfo.IsDir() { + return ts, fmt.Errorf("path is a directory, expected a file") + } + + data, err := os.ReadFile(path) + if err != nil { + return ts, fmt.Errorf("error reading file: %w", err) + } + + err = yaml.Unmarshal(data, &ts) + if err != nil { + return ts, fmt.Errorf("error parsing YAML: %w", err) + } + + if ts.Kind != "TestSuite" { + return ts, fmt.Errorf("invalid Kind: expected 'TestSuite', got '%s'", ts.Kind) + } + + if len(ts.Spec.Steps) == 0 { + return ts, fmt.Errorf("no test steps found in the test suite") + } + + return ts, nil +} + +// Helper function to extract JSON values using dot notation +func extractJsonValue(data interface{}, path string) (interface{}, error) { + // Handle special case for root path ($) + if path == "$" { + return data, nil + } + + parts := strings.Split(path, ".") + current := data + + for _, part := range parts { + // Check if it's an array index + if strings.HasSuffix(part, "]") && strings.Contains(part, "[") { + // Extract the base name and index + openBracket := strings.Index(part, "[") + closeBracket := strings.Index(part, "]") + + if openBracket > 0 && closeBracket > openBracket { + baseName := part[:openBracket] + indexStr := part[openBracket+1 : closeBracket] + index := 0 + + if _, err := fmt.Sscanf(indexStr, "%d", &index); err != nil { + return nil, fmt.Errorf("invalid array index %s", indexStr) + } + + switch v := current.(type) { + case map[string]interface{}: + array, ok := v[baseName] + if !ok { + return nil, fmt.Errorf("key %s not found in JSON", baseName) + } + + arraySlice, ok := array.([]interface{}) + if !ok { + return nil, fmt.Errorf("%s is not an array", baseName) + } + + if index < 0 || index >= len(arraySlice) { + return nil, fmt.Errorf("index %d is out of bounds for array %s", index, baseName) + } + + current = arraySlice[index] + default: + return nil, fmt.Errorf("can't access %s in non-object value", part) + } + } else { + return nil, fmt.Errorf("invalid array syntax in path part %s", part) + } + } else { + switch v := current.(type) { + case map[string]interface{}: + var ok bool + current, ok = v[part] + if !ok { + return nil, fmt.Errorf("key %s not found in JSON", part) + } + default: + return nil, fmt.Errorf("can't access %s in non-object value", part) + } + } + } + + return current, nil +} diff --git a/keploy/pkg/service/tools/mock_Service.go b/keploy/pkg/service/tools/mock_Service.go new file mode 100644 index 0000000..1ea6dd7 --- /dev/null +++ b/keploy/pkg/service/tools/mock_Service.go @@ -0,0 +1,149 @@ +// Code generated by mockery v2.53.2. DO NOT EDIT. + +package tools + +import ( + context "context" + sync "sync" + + mock "github.com/stretchr/testify/mock" +) + +// MockService is an autogenerated mock type for the Service type +type MockService struct { + mock.Mock +} + +// CreateConfig provides a mock function with given fields: ctx, filePath, config +func (_m *MockService) CreateConfig(ctx context.Context, filePath string, config string) error { + ret := _m.Called(ctx, filePath, config) + + if len(ret) == 0 { + panic("no return value specified for CreateConfig") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, filePath, config) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Export provides a mock function with given fields: ctx +func (_m *MockService) Export(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Export") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Import provides a mock function with given fields: ctx, path, basePath +func (_m *MockService) Import(ctx context.Context, path string, basePath string) error { + ret := _m.Called(ctx, path, basePath) + + if len(ret) == 0 { + panic("no return value specified for Import") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, path, basePath) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Login provides a mock function with given fields: ctx +func (_m *MockService) Login(ctx context.Context) bool { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Login") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// SendTelemetry provides a mock function with given fields: event, output +func (_m *MockService) SendTelemetry(event string, output ...*sync.Map) { + _va := make([]interface{}, len(output)) + for _i := range output { + _va[_i] = output[_i] + } + var _ca []interface{} + _ca = append(_ca, event) + _ca = append(_ca, _va...) + _m.Called(_ca...) +} + +// Templatize provides a mock function with given fields: ctx +func (_m *MockService) Templatize(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Templatize") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Update provides a mock function with given fields: ctx +func (_m *MockService) Update(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockService(t interface { + mock.TestingT + Cleanup(func()) +}) *MockService { + mock := &MockService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/keploy/pkg/service/tools/mock_TestDB.go b/keploy/pkg/service/tools/mock_TestDB.go new file mode 100644 index 0000000..8a3a839 --- /dev/null +++ b/keploy/pkg/service/tools/mock_TestDB.go @@ -0,0 +1,143 @@ +// Code generated by mockery v2.53.2. DO NOT EDIT. + +package tools + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + models "go.keploy.io/server/v2/pkg/models" +) + +// MockTestDB is an autogenerated mock type for the TestDB type +type MockTestDB struct { + mock.Mock +} + +// DeleteTestSet provides a mock function with given fields: ctx, testSetID +func (_m *MockTestDB) DeleteTestSet(ctx context.Context, testSetID string) error { + ret := _m.Called(ctx, testSetID) + + if len(ret) == 0 { + panic("no return value specified for DeleteTestSet") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, testSetID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeleteTests provides a mock function with given fields: ctx, testSetID, testCaseIDs +func (_m *MockTestDB) DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error { + ret := _m.Called(ctx, testSetID, testCaseIDs) + + if len(ret) == 0 { + panic("no return value specified for DeleteTests") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { + r0 = rf(ctx, testSetID, testCaseIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetAllTestSetIDs provides a mock function with given fields: ctx +func (_m *MockTestDB) GetAllTestSetIDs(ctx context.Context) ([]string, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetAllTestSetIDs") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []string); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTestCases provides a mock function with given fields: ctx, testSetID +func (_m *MockTestDB) GetTestCases(ctx context.Context, testSetID string) ([]*models.TestCase, error) { + ret := _m.Called(ctx, testSetID) + + if len(ret) == 0 { + panic("no return value specified for GetTestCases") + } + + var r0 []*models.TestCase + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]*models.TestCase, error)); ok { + return rf(ctx, testSetID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []*models.TestCase); ok { + r0 = rf(ctx, testSetID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.TestCase) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, testSetID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateTestCase provides a mock function with given fields: ctx, testCase, testSetID, enableLog +func (_m *MockTestDB) UpdateTestCase(ctx context.Context, testCase *models.TestCase, testSetID string, enableLog bool) error { + ret := _m.Called(ctx, testCase, testSetID, enableLog) + + if len(ret) == 0 { + panic("no return value specified for UpdateTestCase") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *models.TestCase, string, bool) error); ok { + r0 = rf(ctx, testCase, testSetID, enableLog) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMockTestDB creates a new instance of MockTestDB. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockTestDB(t interface { + mock.TestingT + Cleanup(func()) +}) *MockTestDB { + mock := &MockTestDB{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/keploy/pkg/service/tools/mock_TestSetConfig.go b/keploy/pkg/service/tools/mock_TestSetConfig.go new file mode 100644 index 0000000..0b66756 --- /dev/null +++ b/keploy/pkg/service/tools/mock_TestSetConfig.go @@ -0,0 +1,107 @@ +// Code generated by mockery v2.53.2. DO NOT EDIT. + +package tools + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + models "go.keploy.io/server/v2/pkg/models" +) + +// MockTestSetConfig is an autogenerated mock type for the TestSetConfig type +type MockTestSetConfig struct { + mock.Mock +} + +// Read provides a mock function with given fields: ctx, testSetID +func (_m *MockTestSetConfig) Read(ctx context.Context, testSetID string) (*models.TestSet, error) { + ret := _m.Called(ctx, testSetID) + + if len(ret) == 0 { + panic("no return value specified for Read") + } + + var r0 *models.TestSet + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*models.TestSet, error)); ok { + return rf(ctx, testSetID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *models.TestSet); ok { + r0 = rf(ctx, testSetID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.TestSet) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, testSetID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ReadSecret provides a mock function with given fields: ctx, testSetID +func (_m *MockTestSetConfig) ReadSecret(ctx context.Context, testSetID string) (map[string]interface{}, error) { + ret := _m.Called(ctx, testSetID) + + if len(ret) == 0 { + panic("no return value specified for ReadSecret") + } + + var r0 map[string]interface{} + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (map[string]interface{}, error)); ok { + return rf(ctx, testSetID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) map[string]interface{}); ok { + r0 = rf(ctx, testSetID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]interface{}) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, testSetID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Write provides a mock function with given fields: ctx, testSetID, testSet +func (_m *MockTestSetConfig) Write(ctx context.Context, testSetID string, testSet *models.TestSet) error { + ret := _m.Called(ctx, testSetID, testSet) + + if len(ret) == 0 { + panic("no return value specified for Write") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, *models.TestSet) error); ok { + r0 = rf(ctx, testSetID, testSet) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMockTestSetConfig creates a new instance of MockTestSetConfig. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockTestSetConfig(t interface { + mock.TestingT + Cleanup(func()) +}) *MockTestSetConfig { + mock := &MockTestSetConfig{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/keploy/pkg/service/tools/service.go b/keploy/pkg/service/tools/service.go new file mode 100644 index 0000000..b4085d7 --- /dev/null +++ b/keploy/pkg/service/tools/service.go @@ -0,0 +1,37 @@ +// Package tools provides utility functions for the service package. +package tools + +import ( + "context" + "sync" + + "go.keploy.io/server/v2/pkg/models" +) + +type Service interface { + Update(ctx context.Context) error + CreateConfig(ctx context.Context, filePath string, config string) error + SendTelemetry(event string, output ...*sync.Map) + Login(ctx context.Context) bool + Export(ctx context.Context) error + Import(ctx context.Context, path, basePath string) error + Templatize(ctx context.Context) error +} + +type teleDB interface { + SendTelemetry(event string, output ...*sync.Map) +} + +type TestSetConfig interface { + Read(ctx context.Context, testSetID string) (*models.TestSet, error) + Write(ctx context.Context, testSetID string, testSet *models.TestSet) error + ReadSecret(ctx context.Context, testSetID string) (map[string]interface{}, error) +} + +type TestDB interface { + GetAllTestSetIDs(ctx context.Context) ([]string, error) + GetTestCases(ctx context.Context, testSetID string) ([]*models.TestCase, error) + UpdateTestCase(ctx context.Context, testCase *models.TestCase, testSetID string, enableLog bool) error + DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error + DeleteTestSet(ctx context.Context, testSetID string) error +} diff --git a/keploy/pkg/service/tools/templatize.go b/keploy/pkg/service/tools/templatize.go new file mode 100644 index 0000000..29c5ad7 --- /dev/null +++ b/keploy/pkg/service/tools/templatize.go @@ -0,0 +1,1106 @@ +package tools + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" + "strconv" + "strings" + "text/template" + + "github.com/7sDream/geko" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +func (t *Tools) Templatize(ctx context.Context) error { + + testSets := t.config.Templatize.TestSets + if len(testSets) == 0 { + all, err := t.testDB.GetAllTestSetIDs(ctx) + if err != nil { + utils.LogError(t.logger, err, "failed to get all test sets") + return err + } + testSets = all + } + + if len(testSets) == 0 { + t.logger.Warn("No test sets found to templatize") + return nil + } + + for _, testSetID := range testSets { + + testSet, err := t.testSetConf.Read(ctx, testSetID) + if err == nil && (testSet != nil && testSet.Template != nil) { + utils.TemplatizedValues = testSet.Template + } else { + utils.TemplatizedValues = make(map[string]interface{}) + } + + if err == nil && (testSet != nil && testSet.Secret != nil) { + utils.SecretValues = testSet.Secret + } else { + utils.SecretValues = make(map[string]interface{}) + } + + // Get test cases from the database + tcs, err := t.testDB.GetTestCases(ctx, testSetID) + if err != nil { + utils.LogError(t.logger, err, "failed to get test cases") + return err + } + + if len(tcs) == 0 { + t.logger.Warn("The test set is empty. Please record some test cases to templatize.", zap.String("testSet", testSetID)) + continue + } + + err = t.ProcessTestCases(ctx, tcs, testSetID) + if err != nil { + utils.LogError(t.logger, err, "failed to process test cases") + return err + } + } + return nil +} + +func (t *Tools) ProcessTestCases(ctx context.Context, tcs []*models.TestCase, testSetID string) error { + + // In test cases, we often use placeholders like {{float .id}} for templatized variables. Ideally, we should wrap + // them in double quotes, i.e., "{{float .id}}", to prevent errors during JSON unmarshaling. However, we avoid doing + // this to prevent user confusion. If a user sees "{{float .id}}", they might wonder whether it's a string or a float. + // + // To maintain clarity, we remove these placeholders during marshalling and reintroduce them during unmarshalling. + // + // Note: This conversion is applied only to `reqBody` and `respBody` because all other fields are strings, and + // templatized variables in those cases are simply concatenated. + // + // Example: + // + // Request: + // method: GET + // url: http://localhost:8080/api/employees/{{string .id}} + // + // Response: + // status_code: 200 + // header: + // Content-Type: application/json + // Date: Fri, 19 Jan 2024 06:06:03 GMT + // body: '{"id":{{float .id}},"firstName":"0","lastName":"0","email":"0"}' + // + // Notice that even if we omit quotes in the URL, marshalling does not fail. However, when unmarshalling `respBody`, + // it will throw an error if placeholders like `{{float .id}}` are not properly handled. + for _, tc := range tcs { + tc.HTTPReq.Body = addQuotesInTemplates(tc.HTTPReq.Body) + tc.HTTPResp.Body = addQuotesInTemplates(tc.HTTPResp.Body) + } + + // Process test cases for different scenarios and update the tcs and utils.TemplatizedValues + // Case 1: Response Body of one test case to Request Headers of other test cases + // (use case: Authorization token) + t.processRespBodyToReqHeader(ctx, tcs) + + // Case 2: Request Headers of one test case to Request Headers of other test cases + // (use case: Authorization token if Login API is not present in the test set) + t.processReqHeadersToReqHeader(ctx, tcs) + + // Case 3: Response Body of one test case to Response Headers of other + // (use case: POST - GET scenario) + t.processRespBodyToReqURL(ctx, tcs) + + // Case 4: Compare the req and resp body of one to other. + // (use case: POST - PUT scenario) + t.processRespBodyToReqBody(ctx, tcs) + + // Case 5: Compare the req and resp for same test case for any common fields. + // (use case: POST) request and response both have same fields. + t.processBody(ctx, tcs) + + // Case 6: Compare the req url with the response body of same test for any common fields. + // (use case: GET) URL might container same fields as response body. + t.processReqURLToRespBodySameTest(ctx, tcs) + + // case 7: Compare the resp body of one test with the response body of other tests for any common fields. + // (use case: POST - GET scenario) + t.processRespBodyToRespBody(ctx, tcs) + + // case 7: Compare the req body of one test with the response body of other tests for any common fields. + // (use case: POST - GET scenario) + t.processReqBodyToRespBody(ctx, tcs) + + // case 8: Compare the req body of one test with the req URL of other tests for any common fields. + // (use case: POST - GET scenario) + t.processReqBodyToReqURL(ctx, tcs) + + // case 9: Compare the req body of one test with the req body of other tests for any common fields. + // (use case: POST - PUT scenario) + t.processReqBodyToReqBody(ctx, tcs) + + // case 10: Compare the req URL of one test with the req body of other tests for any common fields. + // (use case: GET - PUT scenario) + t.processReqURLToReqBody(ctx, tcs) + + // case 11: Compare the req URL of one test with the req URL of other tests for any common fields + // (use case: GET - PUT scenario) + t.processReqURLToReqURL(ctx, tcs) + + // case 12: Compare the req URL of one test with the resp Body of other tests for any common fields + // (use case: GET - PUT scenario) + t.processReqURLToRespBody(ctx, tcs) + + for _, tc := range tcs { + tc.HTTPReq.Body = removeQuotesInTemplates(tc.HTTPReq.Body) + tc.HTTPResp.Body = removeQuotesInTemplates(tc.HTTPResp.Body) + err := t.testDB.UpdateTestCase(ctx, tc, testSetID, false) + if err != nil { + utils.LogError(t.logger, err, "failed to update test case") + return err + } + } + + utils.RemoveDoubleQuotes(utils.TemplatizedValues) + + var existingMetadata map[string]interface{} + existingTestSet, err := t.testSetConf.Read(ctx, testSetID) + if err == nil && existingTestSet != nil && existingTestSet.Metadata != nil { + existingMetadata = existingTestSet.Metadata + } + + err = t.testSetConf.Write(ctx, testSetID, &models.TestSet{ + PreScript: "", + PostScript: "", + Template: utils.TemplatizedValues, + Metadata: existingMetadata, + }) + if err != nil { + utils.LogError(t.logger, err, "failed to write test set") + return err + } + + if len(utils.SecretValues) > 0 { + err = utils.AddToGitIgnore(t.logger, t.config.Path, "/*/secret.yaml") + if err != nil { + t.logger.Warn("Failed to add secret files to .gitignore", zap.Error(err)) + } + } + + return nil +} + +func (t *Tools) processRespBodyToReqHeader(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) + if err != nil { + t.logger.Error("failed to parse response body, skipping RespBodyToReqHeader Template processing", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + if jsonResponse == nil { + t.logger.Debug("Skipping RespBodyToReqHeader Template processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + for j := i + 1; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + addTemplates(t.logger, tcs[j].HTTPReq.Header, jsonResponse) + } + tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) + } +} + +func (t *Tools) processReqHeadersToReqHeader(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + for j := i + 1; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + compareReqHeaders(t.logger, tcs[j].HTTPReq.Header, tcs[i].HTTPReq.Header) + } + } +} + +func (t *Tools) processRespBodyToReqURL(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) + if err != nil || jsonResponse == nil { + t.logger.Debug("Skipping response to URL processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + for j := i + 1; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + addTemplates(t.logger, &tcs[j].HTTPReq.URL, jsonResponse) + } + tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) + } +} + +func (t *Tools) processRespBodyToReqBody(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) + if err != nil || jsonResponse == nil { + t.logger.Debug("Skipping response to request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + for j := i + 1; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + jsonRequest, err := parseIntoJSON(tcs[j].HTTPReq.Body) + if err != nil || jsonRequest == nil { + t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) + continue + } + addTemplates(t.logger, jsonRequest, jsonResponse) + tcs[j].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) + } + tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) + } +} + +func (t *Tools) processBody(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs); i++ { + select { + case <-ctx.Done(): + return + default: + } + jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) + if err != nil || jsonResponse == nil { + t.logger.Debug("Skipping response to request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + jsonRequest, err := parseIntoJSON(tcs[i].HTTPReq.Body) + if err != nil || jsonRequest == nil { + t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + addTemplates(t.logger, jsonResponse, jsonRequest) + tcs[i].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) + tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) + } +} + +func (t *Tools) processReqURLToRespBodySameTest(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs); i++ { + select { + case <-ctx.Done(): + return + default: + } + jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) + if err != nil || jsonResponse == nil { + t.logger.Debug("Skipping response to URL processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + addTemplates(t.logger, &tcs[i].HTTPReq.URL, jsonResponse) + tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) + } +} + +func (t *Tools) processRespBodyToRespBody(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) + if err != nil || jsonResponse == nil { + t.logger.Debug("Skipping response to request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + for j := i + 1; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + jsonResponse2, err := parseIntoJSON(tcs[j].HTTPResp.Body) + if err != nil || jsonResponse2 == nil { + t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) + continue + } + addTemplates(t.logger, jsonResponse2, jsonResponse) + tcs[j].HTTPResp.Body = marshalJSON(jsonResponse2, t.logger) + } + tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) + } +} + +func (t *Tools) processReqBodyToRespBody(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + jsonRequest, err := parseIntoJSON(tcs[i].HTTPReq.Body) + if err != nil || jsonRequest == nil { + t.logger.Debug("Skipping response to request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + for j := i + 1; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + jsonResponse, err := parseIntoJSON(tcs[j].HTTPResp.Body) + if err != nil || jsonResponse == nil { + t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) + continue + } + addTemplates(t.logger, jsonResponse, jsonRequest) + tcs[j].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) + } + tcs[i].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) + } +} + +func (t *Tools) processReqBodyToReqURL(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + jsonRequest, err := parseIntoJSON(tcs[i].HTTPReq.Body) + if err != nil || jsonRequest == nil { + t.logger.Debug("Skipping response to URL processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + for j := i + 1; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + addTemplates(t.logger, &tcs[j].HTTPReq.URL, jsonRequest) + } + tcs[i].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) + } +} + +func (t *Tools) processReqBodyToReqBody(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + jsonRequest, err := parseIntoJSON(tcs[i].HTTPReq.Body) + if err != nil || jsonRequest == nil { + t.logger.Debug("Skipping response to request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) + continue + } + for j := i + 1; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + jsonRequest2, err := parseIntoJSON(tcs[j].HTTPReq.Body) + if err != nil || jsonRequest2 == nil { + t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) + continue + } + addTemplates(t.logger, jsonRequest2, jsonRequest) + tcs[j].HTTPReq.Body = marshalJSON(jsonRequest2, t.logger) + } + tcs[i].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) + } +} + +func (t *Tools) processReqURLToReqBody(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + for j := i + 1; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + jsonRequest, err := parseIntoJSON(tcs[j].HTTPReq.Body) + if err != nil || jsonRequest == nil { + t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) + continue + } + addTemplates(t.logger, jsonRequest, &tcs[i].HTTPReq.URL) + tcs[j].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) + } + } +} + +func (t *Tools) processReqURLToRespBody(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + for j := 0; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + jsonResponse, err := parseIntoJSON(tcs[j].HTTPResp.Body) + if err != nil || jsonResponse == nil { + t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) + continue + } + addTemplates(t.logger, jsonResponse, &tcs[i].HTTPReq.URL) + tcs[j].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) + } + } +} + +func (t *Tools) processReqURLToReqURL(ctx context.Context, tcs []*models.TestCase) { + for i := 0; i < len(tcs)-1; i++ { + for j := i + 1; j < len(tcs); j++ { + select { + case <-ctx.Done(): + return + default: + } + addTemplates(t.logger, &tcs[j].HTTPReq.URL, &tcs[i].HTTPReq.URL) + } + } +} + +// Utility function to safely marshal JSON and log errors +var jsonMarshal987 = json.Marshal + +// Utility function to safely marshal JSON and log errors +func marshalJSON(data interface{}, logger *zap.Logger) string { + jsonData, err := jsonMarshal987(data) + if err != nil { + utils.LogError(logger, err, "failed to marshal JSON data") + return "" + } + return string(jsonData) +} + +func parseIntoJSON(response string) (interface{}, error) { + if response == "" { + return nil, nil + } + // geko lib will maintain the order of the keys in the json. + result, err := geko.JSONUnmarshal([]byte(response)) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal the response: %v", err) + } + return result, nil +} + +func RenderIfTemplatized(val interface{}) (bool, interface{}, error) { + stringVal, ok := val.(string) + if !ok { + return false, val, nil + } + + // Check if the value is a template. + // Applied this nolint to ignore the staticcheck error here because of readability + // nolint:staticcheck + if !(strings.Contains(stringVal, "{{") && strings.Contains(stringVal, "}}")) { + return false, val, nil + } + + // Attempt to get the value from the template. + renderedVal, err := render(stringVal) + if err != nil { + // This captures execution errors from the render function. + return false, val, err + } + + // If render() failed to parse the template, it returns the original string. + // We check if the value has actually changed to determine if it was a real template. + if reflect.DeepEqual(renderedVal, val) { + return false, val, nil + } + + return true, renderedVal, nil +} + +func addTemplates(logger *zap.Logger, interface1 interface{}, interface2 interface{}) bool { + switch v := interface1.(type) { + case geko.ObjectItems: + keys := v.Keys() + vals := v.Values() + for i := range keys { + var err error + var isTemplatized bool + original := vals[i] + isTemplatized, vals[i], err = RenderIfTemplatized(vals[i]) + if err != nil { + return false + } + switch vals[i].(type) { + case string: + x := vals[i].(string) + addTemplates(logger, &x, interface2) + vals[i] = x + case float32, float64, int, int64: + x := interface{}(vals[i]) + addTemplates(logger, &x, interface2) + vals[i] = x + default: + addTemplates(logger, vals[i], interface2) + } + if isTemplatized { + v.SetValueByIndex(i, original) + } else { + v.SetValueByIndex(i, vals[i]) + } + } + case geko.Array: + for i, val := range v.List { + var err error + var isTemplatized bool + original := val + isTemplatized, val, err = RenderIfTemplatized(val) + if err != nil { + return false + } + switch x := val.(type) { + case string: + addTemplates(logger, &x, interface2) + v.List[i] = x + case float32, float64, int, int64: + x = interface{}(x) + addTemplates(logger, &x, interface2) + v.List[i] = x + default: + addTemplates(logger, v.List[i], interface2) + } + if isTemplatized { + v.Set(i, original) + } else { + v.Set(i, v.List[i]) + } + } + case map[string]string: + for key, val := range v { + var isTemplatized bool + original := val + isTemplatized, val1, err := RenderIfTemplatized(val) + if err != nil { + utils.LogError(logger, err, "failed to render for template") + return false + } + // just a type assertion check though it should always be string. + val, ok := (val1).(string) + if !ok { + continue + } + // Saving the auth type to add it to the template latet. + authType := "" + if key == "Authorization" && len(strings.Split(val, " ")) > 1 { + authType = strings.Split(val, " ")[0] + val = strings.Split(val, " ")[1] + } + ok = addTemplates1(logger, &val, interface2) + if !ok { + continue + } + if key == "Authorization" && len(strings.Split(val, " ")) > 1 { + val = authType + " " + val + } + + if isTemplatized { + v[key] = original + } else { + v[key] = val + } + } + case *string: + original := *v + isTemplatized, tempVal, err := RenderIfTemplatized(*v) + if err != nil { + utils.LogError(logger, err, "failed to render for template") + return false + } + var ok bool + // just a type assertion check though it should always be string. + *v, ok = (tempVal).(string) + if !ok { + return false + } + + // passing this v as reference so that it can be changed in the addTemplates1 function if required. + ok = addTemplates1(logger, v, interface2) + if ok { + return true + } + + originalURL, err := url.Parse(original) + if err != nil { + return false + } + + url, err := url.Parse(*v) + if err != nil || url.Scheme == "" || url.Host == "" { + return false + } + + // Checking the special case of the URL for path and query parameters. + urlParts := strings.Split(url.Path, "/") + originalURLParts := strings.Split(originalURL.Path, "/") + // checking if the last part of the URL is a template. + ok = addTemplates1(logger, &urlParts[len(urlParts)-1], interface2) + if isTemplatized { + urlParts[len(urlParts)-1] = originalURLParts[len(originalURLParts)-1] + } + + url.Path = strings.Join(urlParts, "/") + + if url.RawQuery != "" { + // Safely parse and templatize query parameter values. + queryParamsStr := strings.Split(url.RawQuery, "&") + for i, paramStr := range queryParamsStr { + // Use SplitN to safely handle params with or without values. + parts := strings.SplitN(paramStr, "=", 2) + // Only process parameters that have a value. + if len(parts) == 2 { + key, val := parts[0], parts[1] + addTemplates1(logger, &val, interface2) // Attempt to templatize the value. + queryParamsStr[i] = key + "=" + val // Reconstruct the parameter string. + } + // If len(parts) is 1, it's a key-only parameter (e.g., "?flag"), which we leave untouched. + } + // Reconstruct the raw query and update the URL. + url.RawQuery = strings.Join(queryParamsStr, "&") + *v = url.String() + return true + } + // reconstruct the URL with the templatized path. + *v = url.String() + return ok + case *interface{}: + switch w := (*v).(type) { + case float64, int64, int, float32: + var val string + switch x := w.(type) { + case float64: + val = utils.ToString(x) + case int64: + val = utils.ToString(x) + case int: + val = utils.ToString(x) + case float32: + val = utils.ToString(x) + } + addTemplates1(logger, &val, interface2) + parts := strings.Split(val, " ") + if len(parts) > 1 { // if the value is a template. + parts1 := strings.Split(parts[0], "{{") + if len(parts1) > 1 { + val = parts1[0] + "{{" + getType(w) + " " + parts[1] + "}}" + } + *v = val + return true + } + default: + logger.Error("unsupported type while templatizing", zap.Any("type", w)) + return false + } + } + return false +} + +// TODO: add better comment here and rename this function. +// Here we simplify the second interface and finally add the templates. +func addTemplates1(logger *zap.Logger, val1 *string, body interface{}) bool { + switch b := body.(type) { + case geko.ObjectItems: + keys := b.Keys() + vals := b.Values() + for i, key := range keys { + var err error + var isTemplatized bool + original := vals[i] + isTemplatized, vals[i], err = RenderIfTemplatized(vals[i]) + if err != nil { + utils.LogError(logger, err, "failed to render for template") + return false + } + var ok bool + switch vals[i].(type) { + case string: + x := vals[i].(string) + ok = addTemplates1(logger, val1, &x) + vals[i] = x + case float32: + x := vals[i].(float32) + ok = addTemplates1(logger, val1, &x) + vals[i] = x + case int: + x := vals[i].(int) + ok = addTemplates1(logger, val1, &x) + vals[i] = x + case int64: + x := vals[i].(int64) + ok = addTemplates1(logger, val1, &x) + vals[i] = x + case float64: + x := vals[i].(float64) + ok = addTemplates1(logger, val1, &x) + vals[i] = x + default: + ok = addTemplates1(logger, val1, vals[i]) + } + // we can't change if the type of vals[i] is also object item. + if ok && reflect.TypeOf(vals[i]) != reflect.TypeOf(b) { + newKey := insertUnique(key, *val1, utils.TemplatizedValues) + vals[i] = fmt.Sprintf("{{%s .%v }}", getType(vals[i]), newKey) + // Now change the value of the key in the object. + b.SetValueByIndex(i, vals[i]) + *val1 = fmt.Sprintf("{{%s .%v }}", getType(*val1), newKey) + return true + } + if isTemplatized { + vals[i] = original + } + + } + case geko.Array: + for i, v := range b.List { + switch x := v.(type) { + case string: + addTemplates1(logger, val1, &x) + b.List[i] = x + case float32: + addTemplates1(logger, val1, &x) + b.List[i] = x + case int: + addTemplates1(logger, val1, &x) + b.List[i] = x + case int64: + addTemplates1(logger, val1, &x) + b.List[i] = x + case float64: + addTemplates1(logger, val1, &x) + b.List[i] = x + default: + addTemplates1(logger, val1, b.List[i]) + } + b.Set(i, b.List[i]) + } + case map[string]string: + for key, val2 := range b { + var isTemplatized bool + original := val2 + isTemplatized, tempVal, err := RenderIfTemplatized(val2) + if err != nil { + utils.LogError(logger, err, "failed to render for template") + return false + } + val2, ok := (tempVal).(string) + if !ok { + continue + } + if *val1 == val2 { + newKey := insertUnique(key, val2, utils.TemplatizedValues) + b[key] = fmt.Sprintf("{{%s .%v }}", getType(val2), newKey) + *val1 = fmt.Sprintf("{{%s .%v }}", getType(*val1), newKey) + return true + } + if isTemplatized { + b[key] = original + } + + } + return false + case *string: + _, tempVal, err := RenderIfTemplatized(b) + if err != nil { + utils.LogError(logger, err, "failed to render for template") + return false + } + b, ok := (tempVal).(*string) + if !ok { + return false + } + if *val1 == *b { + return true + } + case map[string]interface{}: + for key, val2 := range b { + var err error + var isTemplatized bool + original := val2 + isTemplatized, val2, err = RenderIfTemplatized(val2) + if err != nil { + utils.LogError(logger, err, "failed to render for template") + return false + } + var ok bool + switch x := val2.(type) { + case string: + ok = addTemplates1(logger, val1, &x) + case float32: + ok = addTemplates1(logger, val1, &x) + case int: + ok = addTemplates1(logger, val1, &x) + case int64: + ok = addTemplates1(logger, val1, &x) + case float64: + ok = addTemplates1(logger, val1, &x) + default: + ok = addTemplates1(logger, val1, val2) + } + + if ok { + newKey := insertUnique(key, *val1, utils.TemplatizedValues) + if newKey == "" { + newKey = key + } + b[key] = fmt.Sprintf("{{%s .%v}}", getType(b[key]), newKey) + *val1 = fmt.Sprintf("{{%s .%v}}", getType(*val1), newKey) + } else { + if isTemplatized { + b[key] = original + } + } + } + case *float64, *int64, *int, *float32: + var val string + switch x := b.(type) { + case *float64: + val = utils.ToString(*x) + case *int64: + val = utils.ToString(*x) + case *int: + val = utils.ToString(*x) + case *float32: + val = utils.ToString(*x) + } + if *val1 == val { + return true + } + case []interface{}: + for i, val := range b { + switch x := val.(type) { + case string: + addTemplates1(logger, val1, &x) + b[i] = x + case float32: + addTemplates1(logger, val1, &x) + b[i] = x + case int: + addTemplates1(logger, val1, &x) + b[i] = x + case int64: + addTemplates1(logger, val1, &x) + b[i] = x + case float64: + addTemplates1(logger, val1, &x) + b[i] = x + default: + addTemplates1(logger, val1, b[i]) + } + b[i] = val + } + } + return false +} + +func getType(val interface{}) string { + switch val.(type) { + case string, *string: + return "string" + case int64, int, int32, *int64, *int, *int32: + return "int" + case float64, float32, *float64, *float32: + return "float" + } + //TODO: handle the default case properly, return some errot. + return "" +} + +// This function returns a unique key for each value, for instance if id already exists, it will return id1. +func insertUnique(baseKey, value string, myMap map[string]interface{}) string { + // If the key has more than one word seperated by a delimiter, remove the delimiter and add the key to the map. + if strings.Contains(baseKey, "-") { + baseKey = strings.ReplaceAll(baseKey, "-", "") + } + if myMap[baseKey] == value { + return baseKey + } + key := baseKey + i := 0 + for myMap[key] != value { + if _, exists := myMap[key]; !exists { + myMap[key] = value + break + } + i++ + key = baseKey + strconv.Itoa(i) + } + return key +} + +// TODO: Make this function generic for one value of string containing more than one template value. +// Duplicate function is being used in Simulate function as well. + +// render function gives the value of the templatized field. +func render(val string) (interface{}, error) { + // This is a map of helper functions that is used to convert the values to their appropriate types. + funcMap := template.FuncMap{ + "int": utils.ToInt, + "string": utils.ToString, + "float": utils.ToFloat, + } + + tmpl, err := template.New("template").Funcs(funcMap).Parse(val) + if err != nil { + // If parsing fails, it's likely not a valid template string, but a literal string + // that happens to contain "{{" and "}}". In this case, we should not treat it as an + // error but return the original value, as no substitution is possible. + return val, nil + } + + data := make(map[string]interface{}) + + for k, v := range utils.TemplatizedValues { + data[k] = v + } + + if len(utils.SecretValues) > 0 { + data["secret"] = utils.SecretValues + } + + var output bytes.Buffer + err = tmpl.Execute(&output, data) + if err != nil { + // An execution error (e.g., missing key) is a genuine problem and should be propagated. + return val, fmt.Errorf("failed to execute the template: %v", err) + } + + outputString := output.String() + + // Convert the string output to the appropriate type based on the template function used. + switch { + case strings.Contains(val, "{{int"): + return utils.ToInt(outputString), nil + case strings.Contains(val, "{{float"): + return utils.ToFloat(outputString), nil + } + + return outputString, nil +} + +// Compare the headers of 2 utils.TemplatizedValues requests and add the templates. +func compareReqHeaders(logger *zap.Logger, req1 map[string]string, req2 map[string]string) { + for key, val1 := range req1 { + // Check if the value is already present in the templatized values. + var isTemplatized1 bool + original1 := val1 + isTemplatized1, tempVal, err := RenderIfTemplatized(val1) + if err != nil { + utils.LogError(logger, err, "failed to render for template") + return + } + val, ok := (tempVal).(string) + if !ok { + continue + } + val1 = val + if val2, ok := req2[key]; ok { + var isTemplatized2 bool + original2 := val2 + isTemplatized2, tempVal, err := RenderIfTemplatized(val2) + if err != nil { + utils.LogError(logger, err, "failed to render for template") + return + } + val, ok = (tempVal).(string) + if !ok { + continue + } + val2 = val + if val1 == val2 { + // Trim the extra space in the string. + val2 = strings.Trim(val2, " ") + newKey := insertUnique(key, val2, utils.TemplatizedValues) + if newKey == "" { + newKey = key + } + req2[key] = fmt.Sprintf("{{%s .%v }}", getType(val2), newKey) + req1[key] = fmt.Sprintf("{{%s .%v }}", getType(val2), newKey) + } else { + if isTemplatized2 { + req2[key] = original2 + } + if isTemplatized1 { + req1[key] = original1 + } + } + } + } +} + +// removeQuotesInTemplates removes temporary quotes from non-string template placeholders before saving the test case. +// For example, it converts `{"id":"{{int .id}}}"` back to `{"id":{{int .id}}}`. +// String templates like `{"name":"{{string .name}}"}` are left untouched as they represent string literals. +func removeQuotesInTemplates(jsonStr string) string { + // Regular expression to find any quoted template placeholder and capture the template itself. + re := regexp.MustCompile(`"(\{\{[^{}]*\}\})"`) + + result := re.ReplaceAllStringFunc(jsonStr, func(match string) string { + // If the template is explicitly a string type, keep the surrounding quotes. + if strings.Contains(match, "{{string") { + return match + } + + // For all other templates (e.g., int, float, or implicit type), remove the quotes. + // The first capture group (submatches[1]) contains the template part, e.g., "{{.id}}". + submatches := re.FindStringSubmatch(match) + if len(submatches) >= 2 { + return submatches[1] + } + + // Fallback to simply trimming quotes, though it should not be reached with the current regex. + return strings.Trim(match, `"`) + }) + + return result +} + +// addQuotesInTemplates wraps template placeholders in quotes to ensure the JSON is valid for unmarshaling. +// It carefully parses the string to only wrap templates that are outside of existing JSON string literals, +// preventing corruption of JSON strings that contain "{{...}}" as part of their content. +func addQuotesInTemplates(jsonStr string) string { + var result strings.Builder + inString := false + i := 0 + for i < len(jsonStr) { + char := rune(jsonStr[i]) + + // Check for a double quote character. + if char == '"' { + // Count preceding backslashes to determine if the quote is escaped. + slashes := 0 + for k := i - 1; k >= 0 && jsonStr[k] == '\\'; k-- { + slashes++ + } + // If the number of preceding backslashes is even, this is a non-escaped quote. + if slashes%2 == 0 { + inString = !inString + } + } + + // Check for the start of a template `{{` when not inside a string literal. + if !inString && char == '{' && i+1 < len(jsonStr) && jsonStr[i+1] == '{' { + // Find the corresponding end of the template `}}`. + // This simple search assumes "}}" does not appear inside a template expression. + endIndex := strings.Index(jsonStr[i:], "}}") + if endIndex != -1 { + endIndex += i + 2 // Adjust index to be relative to the start of jsonStr. + template := jsonStr[i:endIndex] + + // Wrap the found template in quotes to make it a valid JSON string value for the parser. + result.WriteString(`"`) + result.WriteString(template) + result.WriteString(`"`) + i = endIndex + continue + } + } + + result.WriteRune(char) + i++ + } + return result.String() +} diff --git a/keploy/pkg/service/tools/templatize_test.go b/keploy/pkg/service/tools/templatize_test.go new file mode 100644 index 0000000..bf35d24 --- /dev/null +++ b/keploy/pkg/service/tools/templatize_test.go @@ -0,0 +1,162 @@ +package tools + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +// TestRenderIfTemplatized_NonTemplateString_002 tests the RenderIfTemplatized function for non-template strings. +func TestRenderIfTemplatized_NonTemplateString_002(t *testing.T) { + val := "plain string" + isTemplatized, renderedVal, err := RenderIfTemplatized(val) + + require.NoError(t, err) + assert.False(t, isTemplatized) + assert.Equal(t, val, renderedVal) +} + +// TestAddQuotesInTemplates_WrapTemplates_003 tests the addQuotesInTemplates function for wrapping templates in quotes. +func TestAddQuotesInTemplates_WrapTemplates_003(t *testing.T) { + input := `{"id":{{int .id}}}` + expected := `{"id":"{{int .id}}"}` + result := addQuotesInTemplates(input) + + assert.Equal(t, expected, result) +} + +// TestRemoveQuotesInTemplates_RemoveQuotes_004 tests the removeQuotesInTemplates function for removing quotes from templates. +func TestRemoveQuotesInTemplates_RemoveQuotes_004(t *testing.T) { + input := `{"id":"{{int .id}}"}` + expected := `{"id":{{int .id}}}` + result := removeQuotesInTemplates(input) + + assert.Equal(t, expected, result) +} + +// TestRender_ParseAndExecuteTemplates_005 tests the render function for parsing and executing templates. +func TestRender_ParseAndExecuteTemplates_005(t *testing.T) { + utils.TemplatizedValues = map[string]interface{}{"id": 123} + val := "{{int .id}}" + result, err := render(val) + + require.NoError(t, err) + assert.Equal(t, 123, result) +} + +// TestInsertUnique_GenerateUniqueKeys_006 tests the insertUnique function for generating unique keys. +func TestInsertUnique_GenerateUniqueKeys_006(t *testing.T) { + myMap := map[string]interface{}{"id": "123"} + key := insertUnique("id", "123", myMap) + + assert.Equal(t, "id", key) + + key = insertUnique("id", "456", myMap) + assert.Equal(t, "id1", key) +} + +// TestRenderIfTemplatized_TemplateProcessing_001 tests the RenderIfTemplatized function for valid template strings. +func TestRenderIfTemplatized_TemplateProcessing_001(t *testing.T) { + utils.TemplatizedValues = map[string]interface{}{"id": 123} + val := "{{int .id}}" + isTemplatized, renderedVal, err := RenderIfTemplatized(val) + + require.NoError(t, err) + assert.True(t, isTemplatized) + assert.Equal(t, 123, renderedVal) +} + +// TestUtilityFunctions_VariousScenarios_523 groups tests for several small helper functions +// to verify their behavior with different kinds of inputs. +func TestUtilityFunctions_VariousScenarios_523(t *testing.T) { + t.Run("getType should return correct type names", func(t *testing.T) { + assert.Equal(t, "string", getType("a string")) + assert.Equal(t, "string", getType(new(string))) + assert.Equal(t, "int", getType(int64(1))) + assert.Equal(t, "int", getType(1)) + assert.Equal(t, "int", getType(new(int))) + assert.Equal(t, "float", getType(float64(1.1))) + assert.Equal(t, "float", getType(float32(1.1))) + assert.Equal(t, "float", getType(new(float64))) + assert.Equal(t, "", getType(true)) // default case + }) + + t.Run("insertUnique should generate unique keys correctly", func(t *testing.T) { + m := make(map[string]interface{}) + // New key + key := insertUnique("id", "123", m) + assert.Equal(t, "id", key) + assert.Equal(t, "123", m["id"]) + // Existing key, same value + key = insertUnique("id", "123", m) + assert.Equal(t, "id", key) + // Existing key, different value + key = insertUnique("id", "456", m) + assert.Equal(t, "id1", key) + assert.Equal(t, "456", m["id1"]) + // Key with hyphen + key = insertUnique("user-id", "abc", m) + assert.Equal(t, "userid", key) + assert.Equal(t, "abc", m["userid"]) + }) + + t.Run("marshalJSON should handle errors", func(t *testing.T) { + // A channel cannot be marshaled to JSON + data := make(chan int) + result := marshalJSON(data, zap.NewNop()) + assert.Equal(t, "", result) + }) + + t.Run("parseIntoJSON should handle various inputs", func(t *testing.T) { + res, err := parseIntoJSON("") + assert.NoError(t, err) + assert.Nil(t, res) + + res, err = parseIntoJSON("{'key': 'val'}") // invalid JSON + assert.Error(t, err) + assert.Nil(t, res) + + res, err = parseIntoJSON(`{"key": "val"}`) + assert.NoError(t, err) + assert.NotNil(t, res) + }) +} + +// TestQuoteHandlingInTemplates_729 verifies that template placeholders are correctly +// quoted for JSON parsing and unquoted for storage, while respecting explicit string templates. +func TestQuoteHandlingInTemplates_729(t *testing.T) { + t.Run("addQuotesInTemplates", func(t *testing.T) { + // Wraps non-string template + input1 := `{"id":{{int .id}},"name":"test"}` + expected1 := `{"id":"{{int .id}}","name":"test"}` + assert.Equal(t, expected1, addQuotesInTemplates(input1)) + + // Ignores templates already inside a string + input2 := `{"query":"SELECT * FROM users WHERE name = '{{.name}}'"}` + assert.Equal(t, input2, addQuotesInTemplates(input2)) + + // Handles escaped quotes + input3 := `{"key":"value with \" and {{template}}"}` + expected3 := `{"key":"value with \" and {{template}}"}` + assert.Equal(t, expected3, addQuotesInTemplates(input3)) + }) + + t.Run("removeQuotesInTemplates", func(t *testing.T) { + // Removes quotes from non-string template + input1 := `{"id":"{{int .id}}"}` + expected1 := `{"id":{{int .id}}}` + assert.Equal(t, expected1, removeQuotesInTemplates(input1)) + + // Keeps quotes for string template + input2 := `{"name":"{{string .name}}"}` + assert.Equal(t, input2, removeQuotesInTemplates(input2)) + + // Handles mixed case + input3 := `{"id":"{{int .id}}","name":"{{string .name}}"}` + expected3 := `{"id":{{int .id}},"name":"{{string .name}}"}` + assert.Equal(t, expected3, removeQuotesInTemplates(input3)) + }) +} diff --git a/keploy/pkg/service/tools/tools.go b/keploy/pkg/service/tools/tools.go new file mode 100644 index 0000000..59fbc23 --- /dev/null +++ b/keploy/pkg/service/tools/tools.go @@ -0,0 +1,330 @@ +package tools + +import ( + "archive/tar" + "compress/gzip" + "context" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "sync" + + "github.com/charmbracelet/glamour" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/service" + "go.keploy.io/server/v2/pkg/service/export" + postmanimport "go.keploy.io/server/v2/pkg/service/import" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" + yamlLib "gopkg.in/yaml.v3" +) + +func NewTools(logger *zap.Logger, testsetConfig TestSetConfig, testDB TestDB, telemetry teleDB, auth service.Auth, config *config.Config) Service { + return &Tools{ + logger: logger, + telemetry: telemetry, + auth: auth, + testSetConf: testsetConfig, + testDB: testDB, + config: config, + } +} + +type Tools struct { + logger *zap.Logger + telemetry teleDB + testSetConf TestSetConfig + testDB TestDB + config *config.Config + auth service.Auth +} + +var ErrGitHubAPIUnresponsive = errors.New("GitHub API is unresponsive") + +func (t *Tools) SendTelemetry(event string, output ...*sync.Map) { + t.telemetry.SendTelemetry(event, output...) +} + +func (t *Tools) Export(ctx context.Context) error { + return export.Export(ctx, t.logger) +} + +func (t *Tools) Import(ctx context.Context, path, basePath string) error { + postmanImport := postmanimport.NewPostmanImporter(ctx, t.logger) + return postmanImport.Import(path, basePath) +} + +// Update initiates the tools process for the Keploy binary file. +func (t *Tools) Update(ctx context.Context) error { + currentVersion := "v" + utils.Version + isKeployInDocker := len(os.Getenv("KEPLOY_INDOCKER")) > 0 + if isKeployInDocker { + fmt.Println("As you are using docker version of keploy, please pull the latest Docker image of keploy to update keploy") + return nil + } + if strings.HasSuffix(currentVersion, "-dev") { + fmt.Println("you are using a development version of Keploy. Skipping update") + return nil + } + + releaseInfo, err := utils.GetLatestGitHubRelease(ctx, t.logger) + if err != nil { + if errors.Is(err, ErrGitHubAPIUnresponsive) { + return errors.New("gitHub API is unresponsive. Update process cannot continue") + } + return fmt.Errorf("failed to fetch latest GitHub release version: %v", err) + } + + latestVersion := releaseInfo.TagName + changelog := releaseInfo.Body + + if currentVersion == latestVersion { + fmt.Println("✅You are already on the latest version of Keploy: " + latestVersion) + return nil + } + + t.logger.Info("Updating to Version: " + latestVersion) + + downloadURL := "" + + if runtime.GOOS == "linux" { + if runtime.GOARCH == "amd64" { + downloadURL = "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" + } else { + downloadURL = "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_arm64.tar.gz" + } + } + + if runtime.GOOS == "darwin" { + downloadURL = "https://github.com/keploy/keploy/releases/latest/download/keploy_darwin_all.tar.gz" + } + + err = t.downloadAndUpdate(ctx, t.logger, downloadURL) + if err != nil { + return err + } + + t.logger.Info("Update Successful!") + + changelog = "\n" + string(changelog) + var renderer *glamour.TermRenderer + + var termRendererOpts []glamour.TermRendererOption + termRendererOpts = append(termRendererOpts, glamour.WithAutoStyle(), glamour.WithWordWrap(0)) + + renderer, err = glamour.NewTermRenderer(termRendererOpts...) + if err != nil { + utils.LogError(t.logger, err, "failed to initialize renderer") + return err + } + changelog, err = renderer.Render(changelog) + if err != nil { + utils.LogError(t.logger, err, "failed to render release notes") + return err + } + fmt.Println(changelog) + return nil +} + +func (t *Tools) downloadAndUpdate(ctx context.Context, logger *zap.Logger, downloadURL string) error { + // Create a new request with context + req, err := http.NewRequestWithContext(ctx, "GET", downloadURL, nil) + if err != nil { + return fmt.Errorf("failed to create request: %v", err) + } + + // Create a HTTP client and execute the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to download file: %v", err) + } + defer func() { + if cerr := resp.Body.Close(); cerr != nil { + utils.LogError(logger, cerr, "failed to close response body") + } + }() + + // Create a temporary file to store the downloaded tar.gz + tmpFile, err := os.CreateTemp("", "keploy-download-*.tar.gz") + if err != nil { + return fmt.Errorf("failed to create temporary file: %v", err) + } + defer func() { + if err := tmpFile.Close(); err != nil { + utils.LogError(logger, err, "failed to close temporary file") + } + if err := os.Remove(tmpFile.Name()); err != nil { + utils.LogError(logger, err, "failed to remove temporary file") + } + }() + + // Write the downloaded content to the temporary file + _, err = io.Copy(tmpFile, resp.Body) + if err != nil { + return fmt.Errorf("failed to write to temporary file: %v", err) + } + + // Extract the tar.gz file + if err := extractTarGz(tmpFile.Name(), "/tmp"); err != nil { + return fmt.Errorf("failed to extract tar.gz file: %v", err) + } + + // Determine the path based on the alias "keploy" + aliasPath := "/usr/local/bin/keploy" // Default path + + keployPath, err := exec.LookPath("keploy") + if err == nil && keployPath != "" { + aliasPath = keployPath + } + + // Check if the aliasPath is a valid path + _, err = os.Stat(aliasPath) + if os.IsNotExist(err) { + return fmt.Errorf("alias path %s does not exist", aliasPath) + } + + // Check if the aliasPath is a directory + if fileInfo, err := os.Stat(aliasPath); err == nil && fileInfo.IsDir() { + return fmt.Errorf("alias path %s is a directory, not a file", aliasPath) + } + + // Move the extracted binary to the alias path + if err := os.Rename("/tmp/keploy", aliasPath); err != nil { + return fmt.Errorf("failed to move keploy binary to %s: %v", aliasPath, err) + } + + if err := os.Chmod(aliasPath, 0777); err != nil { + return fmt.Errorf("failed to set execute permission on %s: %v", aliasPath, err) + } + + return nil +} + +func extractTarGz(gzipPath, destDir string) error { + file, err := os.Open(gzipPath) + if err != nil { + return err + } + + defer func() { + if err := file.Close(); err != nil { + utils.LogError(nil, err, "failed to close file") + } + }() + + gzipReader, err := gzip.NewReader(file) + if err != nil { + return err + } + + defer func() { + if err := gzipReader.Close(); err != nil { + utils.LogError(nil, err, "failed to close gzip reader") + } + }() + + tarReader := tar.NewReader(gzipReader) + + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + fileName := filepath.Clean(header.Name) + if strings.Contains(fileName, "..") { + return fmt.Errorf("invalid file path: %s", fileName) + } + + target := filepath.Join(destDir, header.Name) + + switch header.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(target, 0777); err != nil { + return err + } + case tar.TypeReg: + outFile, err := os.Create(target) + if err != nil { + return err + } + if _, err := io.Copy(outFile, tarReader); err != nil { + if err := outFile.Close(); err != nil { + return err + } + return err + } + if err := outFile.Close(); err != nil { + return err + } + } + } + return nil +} + +func (t *Tools) CreateConfig(_ context.Context, filePath string, configData string) error { + var node yamlLib.Node + var data []byte + var err error + + if configData != "" { + data = []byte(configData) + } else { + configData, err = config.Merge(config.InternalConfig, config.GetDefaultConfig()) + if err != nil { + utils.LogError(t.logger, err, "failed to create default config string") + return nil + } + data = []byte(configData) + } + + if err := yamlLib.Unmarshal(data, &node); err != nil { + utils.LogError(t.logger, err, "failed to unmarshal the config") + return nil + } + results, err := yamlLib.Marshal(node.Content[0]) + if err != nil { + utils.LogError(t.logger, err, "failed to marshal the config") + return nil + } + + finalOutput := append(results, []byte(utils.ConfigGuide)...) + finalOutput = append([]byte(utils.GetVersionAsComment()), finalOutput...) + + err = os.WriteFile(filePath, finalOutput, fs.ModePerm) + if err != nil { + utils.LogError(t.logger, err, "failed to write config file") + return nil + } + + err = os.Chmod(filePath, 0777) // Set permissions to 777 + if err != nil { + utils.LogError(t.logger, err, "failed to set the permission of config file") + return nil + } + + return nil +} + +func (t *Tools) IgnoreTests(_ context.Context, _ string, _ []string) error { + return nil +} + +func (t *Tools) IgnoreTestSet(_ context.Context, _ string) error { + return nil +} + +func (t *Tools) Login(ctx context.Context) bool { + return t.auth.Login(ctx) +} diff --git a/keploy/pkg/service/utgen/ai.go b/keploy/pkg/service/utgen/ai.go new file mode 100644 index 0000000..acc2e13 --- /dev/null +++ b/keploy/pkg/service/utgen/ai.go @@ -0,0 +1,391 @@ +package utgen + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" + + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/service" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type AIClient struct { + Model string + APIBase string + APIVersion string + APIKey string + APIServerURL string + Auth service.Auth + Logger *zap.Logger + SessionID string + FunctionUnderTest string +} + +type Prompt struct { + System string `json:"system"` + User string `json:"user"` +} + +type CompletionParams struct { + Model string `json:"model"` + Messages []Message `json:"messages"` + MaxTokens int `json:"max_tokens,omitempty"` + MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` + Stream *bool `json:"stream,omitempty"` + Temperature float32 `json:"temperature,omitempty"` + ReasoningEffort ReasoningType `json:"reasoning_effort,omitempty"` +} + +type ReasoningType string + +const ( + HighReasioning ReasoningType = "high" // HighReasioning represents high reasoning effort. + LowReasioning ReasoningType = "low" // LowReasioning represents low reasoning effort. + MediumReasioning ReasoningType = "medium" // MediumReasioning represents medium reasoning effort. +) + +type Message struct { + Role string `json:"role"` + Content string `json:"content"` +} + +type ModelResponse struct { + ID string `json:"id"` + Choices []Choice `json:"choices"` + Created int `json:"created"` + Model string `json:"model,omitempty"` + Object string `json:"object"` + SystemFingerprint string `json:"system_fingerprint,omitempty"` + Usage *Usage `json:"usage,omitempty"` +} + +type Usage struct { + PromptTokens int `json:"prompt_tokens"` + CompletionTokens int `json:"completion_tokens"` + TotalTokens int `json:"total_tokens"` +} + +type ResponseChunk struct { + Choices []Choice `json:"choices"` +} + +type Delta struct { + Content string `json:"content"` +} + +type AIResponse struct { + IsSuccess bool `json:"isSuccess"` + Error string `json:"error"` + FinalContent string `json:"finalContent"` + PromptTokens int `json:"promptTokens"` + CompletionTokens int `json:"completionTokens"` + APIKey string `json:"apiKey"` +} + +type AIRequest struct { + MaxTokens int `json:"maxTokens"` + Prompt Prompt `json:"prompt"` + SessionID string `json:"sessionId"` + Iteration int `json:"iteration"` + RequestPurpose PurposeType `json:"requestPurpose"` +} + +// PurposeType defines the type of purpose for the AI request. +type PurposeType string + +const ( + // TestForFunction represents a purpose type where the request is to test a function. + TestForFunction PurposeType = "TestForFunction" + + // TestForFile represents a purpose type where the request is to test a file. + TestForFile PurposeType = "TestForFile" +) + +type CompletionResponse struct { + ID string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + Choices []Choice `json:"choices"` + Usage UsageData `json:"usage"` +} + +type Choice struct { + Index int `json:"index"` + FinishReason string `json:"finish_reason"` + Message Message `json:"message"` + Delta Delta `json:"delta"` +} + +type UsageData struct { + PromptTokens int `json:"prompt_tokens"` + CompletionTokens int `json:"completion_tokens"` + TotalTokens int `json:"total_tokens"` +} + +func NewAIClient(genConfig config.UtGen, apiKey, apiServerURL string, auth service.Auth, sessionID string, logger *zap.Logger) *AIClient { + return &AIClient{ + Model: genConfig.Model, + APIBase: genConfig.APIBaseURL, + APIVersion: genConfig.APIVersion, + Logger: logger, + APIKey: apiKey, + APIServerURL: apiServerURL, + Auth: auth, + SessionID: sessionID, + FunctionUnderTest: genConfig.FunctionUnderTest, + } +} + +func (ai *AIClient) Call(ctx context.Context, completionParams CompletionParams, aiRequest AIRequest, stream bool) (string, error) { + + var apiBaseURL string + + var apiKey string + if ai.APIBase == ai.APIServerURL { + + token, err := ai.Auth.GetToken(ctx) + if err != nil { + return "", fmt.Errorf("error getting token: %v", err) + } + + ai.Logger.Debug("Making AI request to API server", zap.String("api_server_url", ai.APIServerURL), zap.String("token", token)) + httpClient := &http.Client{} + aiRequestBytes, err := json.Marshal(aiRequest) + if err != nil { + return "", fmt.Errorf("error marshalling AI request: %v", err) + } + + req, err := http.NewRequest("POST", fmt.Sprintf("%s/ai/call", ai.APIServerURL), bytes.NewBuffer(aiRequestBytes)) + if err != nil { + return "", fmt.Errorf("error creating request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + resp, err := httpClient.Do(req) + if err != nil { + return "", fmt.Errorf("error making request: %v", err) + } + + bodyBytes, _ := io.ReadAll(resp.Body) + var aiResponse AIResponse + err = json.Unmarshal(bodyBytes, &aiResponse) + if err != nil { + return "", fmt.Errorf("error unmarshalling response body: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("unexpected status code: %v, response body: %s", resp.StatusCode, aiResponse.Error) + } + defer func() { + err := resp.Body.Close() + if err != nil { + utils.LogError(ai.Logger, err, "failed to close response body for authentication") + } + }() + + return aiResponse.FinalContent, nil + } else if ai.APIBase != "" { + apiBaseURL = ai.APIBase + } else { + apiBaseURL = "https://api.openai.com/v1" + } + + if completionParams.Model == "" { + completionParams.Model = "gpt-4o" + if ai.Model != "" { + completionParams.Model = ai.Model + } + } + + if len(completionParams.Messages) == 0 { + var messages []Message + if aiRequest.Prompt.System == "" { + messages = []Message{ + {Role: "user", Content: aiRequest.Prompt.User}, + } + } else { + messages = []Message{ + {Role: "system", Content: aiRequest.Prompt.System}, + {Role: "user", Content: aiRequest.Prompt.User}, + } + } + + completionParams.Messages = messages + } + + requestBody, err := json.Marshal(completionParams) + if err != nil { + return "", fmt.Errorf("error marshalling request body: %v", err) + } + + queryParams := "" + if ai.APIVersion != "" { + queryParams = "?api-version=" + ai.APIVersion + } + + req, err := http.NewRequestWithContext(ctx, "POST", apiBaseURL+"/chat/completions"+queryParams, bytes.NewBuffer(requestBody)) + if err != nil { + return "", fmt.Errorf("error creating request: %v", err) + } + + if ai.APIKey == "" { + apiKey = os.Getenv("API_KEY") + } else { + apiKey = ai.APIKey + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+apiKey) + req.Header.Set("api-key", apiKey) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("error making request: %v", err) + } + defer func() { + if err := resp.Body.Close(); err != nil { + utils.LogError(ai.Logger, err, "Error closing response body") + } + }() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + bodyString := string(bodyBytes) + return "", fmt.Errorf("unexpected status code: %v, response body: %s", resp.StatusCode, bodyString) + } + + var contentBuilder strings.Builder + reader := bufio.NewReader(resp.Body) + + if ai.Logger.Level() == zap.DebugLevel { + fmt.Println("Streaming results from LLM model...") + } + + fmt.Println("Streaming results from LLM model...") + if stream { + for { + line, err := reader.ReadString('\n') + if err != nil && err != io.EOF { + utils.LogError(ai.Logger, err, "Error reading stream") + return "", err + } + line = strings.TrimSpace(strings.TrimPrefix(line, "data: ")) + if line == "[DONE]" { + break + } + + if line == "" { + continue + } + + var chunk ModelResponse + err = json.Unmarshal([]byte(line), &chunk) + if err != nil { + utils.LogError(ai.Logger, err, "Error unmarshalling chunk") + continue + } + + if len(chunk.Choices) > 0 { + if chunk.Choices[0].Delta != (Delta{}) { + contentBuilder.WriteString(chunk.Choices[0].Delta.Content) + if ai.Logger.Level() == zap.DebugLevel { + fmt.Print(chunk.Choices[0].Delta.Content) + } + } + } + + if err == io.EOF { + break + } + time.Sleep(10 * time.Millisecond) + } + } else { + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("error reading response: %v", err) + } + var completionResponse CompletionResponse + err = json.Unmarshal(responseData, &completionResponse) + if err != nil { + return "", fmt.Errorf("error unmarshalling response: %v", err) + } + if len(completionResponse.Choices) > 0 { + finalContent := completionResponse.Choices[0].Message.Content + return finalContent, nil + } + } + finalContent := contentBuilder.String() + + return finalContent, nil +} + +func (ai *AIClient) SendCoverageUpdate(ctx context.Context, sessionID string, oldCoverage, newCoverage float64, iterationCount int) error { + // Construct the request body with session ID, old coverage, and new coverage + requestPurpose := TestForFile + if len(ai.FunctionUnderTest) > 0 { + requestPurpose = TestForFunction + } + requestBody, err := json.Marshal(map[string]interface{}{ + "sessionId": sessionID, + "initalCoverage": oldCoverage, + "finalCoverage": newCoverage, + "iteration": iterationCount, + "requestPurpose": requestPurpose, + }) + if err != nil { + return fmt.Errorf("error marshalling request body: %v", err) + } + + // Determine the base URL + var apiBaseURL string + if ai.APIBase != "" { + apiBaseURL = ai.APIBase + } + // Create a POST request + req, err := http.NewRequestWithContext(ctx, "POST", apiBaseURL+"/ai/coverage/update", bytes.NewBuffer(requestBody)) + if err != nil { + return fmt.Errorf("error creating request: %v", err) + } + + token, err := ai.Auth.GetToken(ctx) + + if err != nil { + return fmt.Errorf("error getting token: %v", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + // Execute the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("error making request: %v", err) + } + defer func() { + if err := resp.Body.Close(); err != nil { + utils.LogError(ai.Logger, err, "Error closing response body") + } + }() + + // Check for successful response + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + bodyString := string(bodyBytes) + return fmt.Errorf("unexpected status code: %v, response body: %s", resp.StatusCode, bodyString) + } + + ai.Logger.Debug("Coverage update sent successfully", zap.String("session_id", sessionID)) + return nil +} diff --git a/keploy/pkg/service/utgen/assets/config.go b/keploy/pkg/service/utgen/assets/config.go new file mode 100644 index 0000000..0b9a461 --- /dev/null +++ b/keploy/pkg/service/utgen/assets/config.go @@ -0,0 +1,58 @@ +// Package settings provides prompt settings for the test generation +package settings + +import ( + "bytes" + "embed" + "log" + "sync" + + "github.com/spf13/viper" +) + +// SingletonSettings manages the singleton instance of the configuration settings +type SingletonSettings struct { + viper *viper.Viper +} + +var instance *SingletonSettings +var once sync.Once + +//go:embed *.toml +var settings embed.FS + +// NewSingletonSettings initializes the singleton settings instance +func NewSingletonSettings() *SingletonSettings { + once.Do(func() { + + settingsFiles := []string{ + "test_generation.toml", + "language.toml", + "indentation.toml", + "insert_line.toml", + } + + v := viper.New() + v.SetConfigType("toml") + for _, file := range settingsFiles { + fileContent, err := settings.ReadFile(file) + if err != nil { + log.Fatalf("Failed to read settings file %s: %v", file, err) + } + v.SetConfigFile(file) + if err := v.MergeConfig(bytes.NewBuffer(fileContent)); err != nil { + log.Fatalf("Error loading config file : %v", err) + } + } + + instance = &SingletonSettings{ + viper: v, + } + }) + return instance +} + +// GetSettings returns the singleton settings instance +func GetSettings() *viper.Viper { + return NewSingletonSettings().viper +} diff --git a/keploy/pkg/service/utgen/assets/indentation.toml b/keploy/pkg/service/utgen/assets/indentation.toml new file mode 100644 index 0000000..fba1bb9 --- /dev/null +++ b/keploy/pkg/service/utgen/assets/indentation.toml @@ -0,0 +1,41 @@ +[indentation] +system="""\ +""" + +user="""\ +## Overview +You are a code assistant designed to analyze a {{ .language }} test file. +Your task is to provide specific feedback on this file, including the programming language, the testing framework required, the number of tests, and the indentation level of the test headers. +You will be given the test file, named `{{ .test_file_name }}` with existing tests, with line numbers added for clarity. +These line numbers are not part of the original code. +========= +{{ .test_file | trim }} +========= + + +Analyze the provided test file and generate a YAML object matching the $TestsAnalysis type, according to these Pydantic definitions: +===== + +class TestsAnalysis(BaseModel): + language: str = Field(description="The programming language used by the test file") + testing_framework: str = Field(description="The testing framework needed to run the tests in the test file") + number_of_tests: int = Field(description="The number of tests in the test file") + test_headers_indentation: int = Field(description="The indentation of the test headers in the test file.\ + For example, "def test_..." has an indentation of 0, " def test_..." has an indentation of 2, " def test_..." has an indentation of 4, and so on.") + +===== + + +Example output: +```yaml +language: {{ .language }} +testing_framework: ... +number_of_tests: ... +test_headers_indentation: ... +``` + +The Response should be only a valid YAML object, without any introduction text or follow-up text. + +Answer: +```yaml +""" diff --git a/keploy/pkg/service/utgen/assets/insert_line.toml b/keploy/pkg/service/utgen/assets/insert_line.toml new file mode 100644 index 0000000..4605c16 --- /dev/null +++ b/keploy/pkg/service/utgen/assets/insert_line.toml @@ -0,0 +1,37 @@ +[insert_line] +system="""\ +""" + +user="""\ +## Overview +You are a code assistant designed to analyze a {{ .language }} test file. +Your task is to provide specific feedback on this file, including the programming language, the testing framework required, the number of tests, and the line number after which new tests should be inserted to be part of the existing test suite. +You will be given the test file, named `{{ .test_file_name }}`, with line numbers added for clarity and existing tests if there are any. +========= +{{ .test_file_numbered | trim }} +========= + + +Analyze the provided test file and generate a YAML object matching the $TestsAnalysis type, according to these Pydantic definitions: +===== +class TestsAnalysis(BaseModel): + language: str = Field(description="The programming language used by the test file") + testing_framework: str = Field(description="The testing framework needed to run the tests in the test file") + number_of_tests: int = Field(description="The number of tests in the test file") + relevant_line_number_to_insert_after: int = Field(description="The line number in the test file, **after which** the new tests should be inserted, so they will be a part of the existing test suite. Place the new tests after the last test in the suite.") +===== + + +Example output: +```yaml +language: {{ .language }} +testing_framework: ... +number_of_tests: ... +relevant_line_number_to_insert_after: ... +``` + +The Response should be only a valid YAML object, without any introduction text or follow-up text. + +Answer: +```yaml +""" diff --git a/keploy/pkg/service/utgen/assets/language.toml b/keploy/pkg/service/utgen/assets/language.toml new file mode 100644 index 0000000..c13992e --- /dev/null +++ b/keploy/pkg/service/utgen/assets/language.toml @@ -0,0 +1,439 @@ +[bad_extensions] +default = [ + 'app', + 'bin', + 'bmp', + 'bz2', + 'class', + 'csv', + 'dat', + 'db', + 'dll', + 'dylib', + 'egg', + 'eot', + 'exe', + 'gif', + 'gitignore', + 'glif', + 'gradle', + 'gz', + 'ico', + 'jar', + 'jpeg', + 'jpg', + 'lo', + 'lock', + 'log', + 'mp3', + 'mp4', + 'nar', + 'o', + 'ogg', + 'otf', + 'p', + 'pdf', + 'png', + 'pickle', + 'pkl', + 'pyc', + 'pyd', + 'pyo', + 'rkt', + 'so', + 'ss', + 'svg', + 'tar', + 'tgz', + 'tsv', + 'ttf', + 'war', + 'webm', + 'woff', + 'woff2', + 'xz', + 'zip', + 'zst', + 'snap', + 'lockb' +] +extra = [ + 'md', + 'txt' +] + +[language_extension_map_org] +ABAP = [".abap", ] +"AGS Script" = [".ash", ] +AMPL = [".ampl", ] +ANTLR = [".g4", ] +"API Blueprint" = [".apib", ] +APL = [".apl", ".dyalog", ] +ASP = [".asp", ".asax", ".ascx", ".ashx", ".asmx", ".aspx", ".axd", ] +ATS = [".dats", ".hats", ".sats", ] +ActionScript = [".as", ] +Ada = [".adb", ".ada", ".ads", ] +Agda = [".agda", ] +Alloy = [".als", ] +ApacheConf = [".apacheconf", ".vhost", ] +AppleScript = [".applescript", ".scpt", ] +Arc = [".arc", ] +Arduino = [".ino", ] +AsciiDoc = [".asciidoc", ".adoc", ] +AspectJ = [".aj", ] +Assembly = [".asm", ".a51", ".nasm", ] +Augeas = [".aug", ] +AutoHotkey = [".ahk", ".ahkl", ] +AutoIt = [".au3", ] +Awk = [".awk", ".auk", ".gawk", ".mawk", ".nawk", ] +Batchfile = [".bat", ".cmd", ] +Befunge = [".befunge", ] +Bison = [".bison", ] +BitBake = [".bb", ] +BlitzBasic = [".decls", ] +BlitzMax = [".bmx", ] +Bluespec = [".bsv", ] +Boo = [".boo", ] +Brainfuck = [".bf", ] +Brightscript = [".brs", ] +Bro = [".bro", ] +C = [".c", ".cats", ".h", ".idc", ".w", ] +"C#" = [".cs", ".cake", ".cshtml", ".csx", ] +"C++" = [".cpp", ".c++", ".cc", ".cp", ".cxx", ".h++", ".hh", ".hpp", ".hxx", ".inl", ".ipp", ".tcc", ".tpp", ".C", ".H", ] +C-ObjDump = [".c-objdump", ] +"C2hs Haskell" = [".chs", ] +CLIPS = [".clp", ] +CMake = [".cmake", ".cmake.in", ] +COBOL = [".cob", ".cbl", ".ccp", ".cobol", ".cpy", ] +CSS = [".css", ] +CSV = [".csv", ] +"Cap'n Proto" = [".capnp", ] +CartoCSS = [".mss", ] +Ceylon = [".ceylon", ] +Chapel = [".chpl", ] +ChucK = [".ck", ] +Cirru = [".cirru", ] +Clarion = [".clw", ] +Clean = [".icl", ".dcl", ] +Click = [".click", ] +Clojure = [".clj", ".boot", ".cl2", ".cljc", ".cljs", ".cljs.hl", ".cljscm", ".cljx", ".hic", ] +CoffeeScript = [".coffee", "._coffee", ".cjsx", ".cson", ".iced", ] +ColdFusion = [".cfm", ".cfml", ] +"ColdFusion CFC" = [".cfc", ] +"Common Lisp" = [".lisp", ".asd", ".lsp", ".ny", ".podsl", ".sexp", ] +"Component Pascal" = [".cps", ] +Coq = [".coq", ] +Cpp-ObjDump = [".cppobjdump", ".c++-objdump", ".c++objdump", ".cpp-objdump", ".cxx-objdump", ] +Creole = [".creole", ] +Crystal = [".cr", ] +Csound = [".csd", ] +Cucumber = [".feature", ] +Cuda = [".cu", ".cuh", ] +Cycript = [".cy", ] +Cython = [".pyx", ".pxd", ".pxi", ] +D = [".di", ] +D-ObjDump = [".d-objdump", ] +"DIGITAL Command Language" = [".com", ] +DM = [".dm", ] +"DNS Zone" = [".zone", ".arpa", ] +"Darcs Patch" = [".darcspatch", ".dpatch", ] +Dart = [".dart", ] +Diff = [".diff", ".patch", ] +Dockerfile = [".dockerfile", "Dockerfile", ] +Dogescript = [".djs", ] +Dylan = [".dylan", ".dyl", ".intr", ".lid", ] +E = [".E", ] +ECL = [".ecl", ".eclxml", ] +Eagle = [".sch", ".brd", ] +"Ecere Projects" = [".epj", ] +Eiffel = [".e", ] +Elixir = [".ex", ".exs", ] +Elm = [".elm", ] +"Emacs Lisp" = [".el", ".emacs", ".emacs.desktop", ] +EmberScript = [".em", ".emberscript", ] +Erlang = [".erl", ".escript", ".hrl", ".xrl", ".yrl", ] +"F#" = [".fs", ".fsi", ".fsx", ] +FLUX = [".flux", ] +FORTRAN = [".f90", ".f", ".f03", ".f08", ".f77", ".f95", ".for", ".fpp", ] +Factor = [".factor", ] +Fancy = [".fy", ".fancypack", ] +Fantom = [".fan", ] +Formatted = [".eam.fs", ] +Forth = [".fth", ".4th", ".forth", ".frt", ] +FreeMarker = [".ftl", ] +G-code = [".g", ".gco", ".gcode", ] +GAMS = [".gms", ] +GAP = [".gap", ".gi", ] +GAS = [".s", ] +GDScript = [".gd", ] +GLSL = [".glsl", ".fp", ".frag", ".frg", ".fsh", ".fshader", ".geo", ".geom", ".glslv", ".gshader", ".shader", ".vert", ".vrx", ".vsh", ".vshader", ] +Genshi = [".kid", ] +"Gentoo Ebuild" = [".ebuild", ] +"Gentoo Eclass" = [".eclass", ] +"Gettext Catalog" = [".po", ".pot", ] +Glyph = [".glf", ] +Gnuplot = [".gp", ".gnu", ".gnuplot", ".plot", ".plt", ] +Go = [".go", ] +Golo = [".golo", ] +Gosu = [".gst", ".gsx", ".vark", ] +Grace = [".grace", ] +Gradle = [".gradle", ] +"Grammatical Framework" = [".gf", ] +GraphQL = [".graphql", ] +"Graphviz (DOT)" = [".dot", ".gv", ] +Groff = [".man", ".1", ".1in", ".1m", ".1x", ".2", ".3", ".3in", ".3m", ".3qt", ".3x", ".4", ".5", ".6", ".7", ".8", ".9", ".me", ".rno", ".roff", ] +Groovy = [".groovy", ".grt", ".gtpl", ".gvy", ] +"Groovy Server Pages" = [".gsp", ] +HCL = [".hcl", ".tf", ] +HLSL = [".hlsl", ".fxh", ".hlsli", ] +HTML = [".html", ".htm", ".html.hl", ".xht", ".xhtml", ] +"HTML+Django" = [".mustache", ".jinja", ] +"HTML+EEX" = [".eex", ] +"HTML+ERB" = [".erb", ".erb.deface", ] +"HTML+PHP" = [".phtml", ] +HTTP = [".http", ] +Haml = [".haml", ".haml.deface", ] +Handlebars = [".handlebars", ".hbs", ] +Harbour = [".hb", ] +Haskell = [".hs", ".hsc", ] +Haxe = [".hx", ".hxsl", ] +Hy = [".hy", ] +IDL = [".dlm", ] +"IGOR Pro" = [".ipf", ] +INI = [".ini", ".cfg", ".prefs", ".properties", ] +"IRC log" = [".irclog", ".weechatlog", ] +Idris = [".idr", ".lidr", ] +"Inform 7" = [".ni", ".i7x", ] +"Inno Setup" = [".iss", ] +Io = [".io", ] +Ioke = [".ik", ] +Isabelle = [".thy", ] +J = [".ijs", ] +JFlex = [".flex", ".jflex", ] +JSON = [".json", ".geojson", ".lock", ".topojson", ] +JSON5 = [".json5", ] +JSONLD = [".jsonld", ] +JSONiq = [".jq", ] +JSX = [".jsx", ] +Jade = [".jade", ] +Jasmin = [".j", ] +Java = [".java", ] +"Java Server Pages" = [".jsp", ] +JavaScript = [".js", "._js", ".bones", ".es6", ".jake", ".jsb", ".jscad", ".jsfl", ".jsm", ".jss", ".njs", ".pac", ".sjs", ".ssjs", ".xsjs", ".xsjslib", ] +Julia = [".jl", ] +"Jupyter Notebook" = [".ipynb", ] +KRL = [".krl", ] +KiCad = [".kicad_pcb", ] +Kit = [".kit", ] +Kotlin = [".kt", ".ktm", ".kts", ] +LFE = [".lfe", ] +LLVM = [".ll", ] +LOLCODE = [".lol", ] +LSL = [".lsl", ".lslp", ] +LabVIEW = [".lvproj", ] +Lasso = [".lasso", ".las", ".lasso8", ".lasso9", ".ldml", ] +Latte = [".latte", ] +Lean = [".lean", ".hlean", ] +Less = [".less", ] +Lex = [".lex", ] +LilyPond = [".ly", ".ily", ] +"Linker Script" = [".ld", ".lds", ] +Liquid = [".liquid", ] +"Literate Agda" = [".lagda", ] +"Literate CoffeeScript" = [".litcoffee", ] +"Literate Haskell" = [".lhs", ] +LiveScript = [".ls", "._ls", ] +Logos = [".xm", ".x", ".xi", ] +Logtalk = [".lgt", ".logtalk", ] +LookML = [".lookml", ] +Lua = [".lua", ".nse", ".pd_lua", ".rbxs", ".wlua", ] +M = [".mumps", ] +M4 = [".m4", ] +MAXScript = [".mcr", ] +MTML = [".mtml", ] +MUF = [".muf", ] +Makefile = [".mak", ".mk", ".mkfile", "Makefile", ] +Mako = [".mako", ".mao", ] +Maple = [".mpl", ] +Markdown = [".md", ".markdown", ".mkd", ".mkdn", ".mkdown", ".ron", ] +Mask = [".mask", ] +Mathematica = [".mathematica", ".cdf", ".ma", ".mt", ".nb", ".nbp", ".wl", ".wlt", ] +Matlab = [".matlab", ] +Max = [".maxpat", ".maxhelp", ".maxproj", ".mxt", ".pat", ] +MediaWiki = [".mediawiki", ".wiki", ] +Metal = [".metal", ] +MiniD = [".minid", ] +Mirah = [".druby", ".duby", ".mir", ".mirah", ] +Modelica = [".mo", ] +"Module Management System" = [".mms", ".mmk", ] +Monkey = [".monkey", ] +MoonScript = [".moon", ] +Myghty = [".myt", ] +NSIS = [".nsi", ".nsh", ] +NetLinx = [".axs", ".axi", ] +"NetLinx+ERB" = [".axs.erb", ".axi.erb", ] +NetLogo = [".nlogo", ] +Nginx = [".nginxconf", ] +Nimrod = [".nim", ".nimrod", ] +Ninja = [".ninja", ] +Nit = [".nit", ] +Nix = [".nix", ] +Nu = [".nu", ] +NumPy = [".numpy", ".numpyw", ".numsc", ] +OCaml = [".ml", ".eliom", ".eliomi", ".ml4", ".mli", ".mll", ".mly", ] +ObjDump = [".objdump", ] +"Objective-C++" = [".mm", ] +Objective-J = [".sj", ] +Octave = [".oct", ] +Omgrofl = [".omgrofl", ] +Opa = [".opa", ] +Opal = [".opal", ] +OpenCL = [".cl", ".opencl", ] +"OpenEdge ABL" = [".p", ] +OpenSCAD = [".scad", ] +Org = [".org", ] +Ox = [".ox", ".oxh", ".oxo", ] +Oxygene = [".oxygene", ] +Oz = [".oz", ] +PAWN = [".pwn", ] +PHP = [".php", ".aw", ".ctp", ".php3", ".php4", ".php5", ".phps", ".phpt", ] +"POV-Ray SDL" = [".pov", ] +Pan = [".pan", ] +Papyrus = [".psc", ] +Parrot = [".parrot", ] +"Parrot Assembly" = [".pasm", ] +"Parrot Internal Representation" = [".pir", ] +Pascal = [".pas", ".dfm", ".dpr", ".lpr", ] +Perl = [".pl", ".al", ".perl", ".ph", ".plx", ".pm", ".psgi", ".t", ] +Perl6 = [".6pl", ".6pm", ".nqp", ".p6", ".p6l", ".p6m", ".pl6", ".pm6", ] +Pickle = [".pkl", ] +PigLatin = [".pig", ] +Pike = [".pike", ".pmod", ] +Pod = [".pod", ] +PogoScript = [".pogo", ] +Pony = [".pony", ] +PostScript = [".ps", ".eps", ] +PowerShell = [".ps1", ".psd1", ".psm1", ] +Processing = [".pde", ] +Prolog = [".prolog", ".yap", ] +"Propeller Spin" = [".spin", ] +"Protocol Buffer" = [".proto", ] +"Public Key" = [".pub", ] +"Pure Data" = [".pd", ] +PureBasic = [".pb", ".pbi", ] +PureScript = [".purs", ] +Python = [".py", ".bzl", ".gyp", ".lmi", ".pyde", ".pyp", ".pyt", ".pyw", ".tac", ".wsgi", ".xpy", ] +"Python traceback" = [".pytb", ] +QML = [".qml", ".qbs", ] +QMake = [".pri", ] +R = [".r", ".rd", ".rsx", ] +RAML = [".raml", ] +RDoc = [".rdoc", ] +REALbasic = [".rbbas", ".rbfrm", ".rbmnu", ".rbres", ".rbtbar", ".rbuistate", ] +RHTML = [".rhtml", ] +RMarkdown = [".rmd", ] +Racket = [".rkt", ".rktd", ".rktl", ".scrbl", ] +"Ragel in Ruby Host" = [".rl", ] +"Raw token data" = [".raw", ] +Rebol = [".reb", ".r2", ".r3", ".rebol", ] +Red = [".red", ".reds", ] +Redcode = [".cw", ] +"Ren'Py" = [".rpy", ] +RenderScript = [".rsh", ] +RobotFramework = [".robot", ] +Rouge = [".rg", ] +Ruby = [".rb", ".builder", ".gemspec", ".god", ".irbrc", ".jbuilder", ".mspec", ".podspec", ".rabl", ".rake", ".rbuild", ".rbw", ".rbx", ".ru", ".ruby", ".thor", ".watchr", ] +Rust = [".rs", ".rs.in", ] +SAS = [".sas", ] +SCSS = [".scss", ] +SMT = [".smt2", ".smt", ] +SPARQL = [".sparql", ".rq", ] +SQF = [".sqf", ".hqf", ] +SQL = [".pls", ".pck", ".pkb", ".pks", ".plb", ".plsql", ".sql", ".cql", ".ddl", ".prc", ".tab", ".udf", ".viw", ".db2", ] +STON = [".ston", ] +SVG = [".svg", ] +Sage = [".sage", ".sagews", ] +SaltStack = [".sls", ] +Sass = [".sass", ] +Scala = [".scala", ".sbt", ] +Scaml = [".scaml", ] +Scheme = [".scm", ".sld", ".sps", ".ss", ] +Scilab = [".sci", ".sce", ] +Self = [".self", ] +Shell = [".sh", ".bash", ".bats", ".command", ".ksh", ".sh.in", ".tmux", ".tool", ".zsh", ] +ShellSession = [".sh-session", ] +Shen = [".shen", ] +Slash = [".sl", ] +Slim = [".slim", ] +Smali = [".smali", ] +Smalltalk = [".st", ] +Smarty = [".tpl", ] +Solidity = [".sol", ] +SourcePawn = [".sp", ".sma", ] +Squirrel = [".nut", ] +Stan = [".stan", ] +"Standard ML" = [".ML", ".fun", ".sig", ".sml", ] +Stata = [".do", ".ado", ".doh", ".ihlp", ".mata", ".matah", ".sthlp", ] +Stylus = [".styl", ] +SuperCollider = [".scd", ] +Swift = [".swift", ] +SystemVerilog = [".sv", ".svh", ".vh", ] +TOML = [".toml", ] +TXL = [".txl", ] +Tcl = [".tcl", ".adp", ".tm", ] +Tcsh = [".tcsh", ".csh", ] +TeX = [".tex", ".aux", ".bbx", ".bib", ".cbx", ".dtx", ".ins", ".lbx", ".ltx", ".mkii", ".mkiv", ".mkvi", ".sty", ".toc", ] +Tea = [".tea", ] +Text = [".txt", ".no", ] +Textile = [".textile", ] +Thrift = [".thrift", ] +Turing = [".tu", ] +Turtle = [".ttl", ] +Twig = [".twig", ] +TypeScript = [".ts", ".tsx", ] +"Unified Parallel C" = [".upc", ] +"Unity3D Asset" = [".anim", ".asset", ".mat", ".meta", ".prefab", ".unity", ] +Uno = [".uno", ] +UnrealScript = [".uc", ] +UrWeb = [".ur", ".urs", ] +VCL = [".vcl", ] +VHDL = [".vhdl", ".vhd", ".vhf", ".vhi", ".vho", ".vhs", ".vht", ".vhw", ] +Vala = [".vala", ".vapi", ] +Verilog = [".veo", ] +VimL = [".vim", ] +"Visual Basic" = [".vb", ".bas", ".frm", ".frx", ".vba", ".vbhtml", ".vbs", ] +Volt = [".volt", ] +Vue = [".vue", ] +"Web Ontology Language" = [".owl", ] +WebAssembly = [".wat", ] +WebIDL = [".webidl", ] +X10 = [".x10", ] +XC = [".xc", ] +XML = [".xml", ".ant", ".axml", ".ccxml", ".clixml", ".cproject", ".csl", ".csproj", ".ct", ".dita", ".ditamap", ".ditaval", ".dll.config", ".dotsettings", ".filters", ".fsproj", ".fxml", ".glade", ".grxml", ".iml", ".ivy", ".jelly", ".jsproj", ".kml", ".launch", ".mdpolicy", ".mxml", ".nproj", ".nuspec", ".odd", ".osm", ".plist", ".props", ".ps1xml", ".psc1", ".pt", ".rdf", ".rss", ".scxml", ".srdf", ".storyboard", ".stTheme", ".sublime-snippet", ".targets", ".tmCommand", ".tml", ".tmLanguage", ".tmPreferences", ".tmSnippet", ".tmTheme", ".ui", ".urdf", ".ux", ".vbproj", ".vcxproj", ".vssettings", ".vxml", ".wsdl", ".wsf", ".wxi", ".wxl", ".wxs", ".x3d", ".xacro", ".xaml", ".xib", ".xlf", ".xliff", ".xmi", ".xml.dist", ".xproj", ".xsd", ".xul", ".zcml", ] +XPages = [".xsp-config", ".xsp.metadata", ] +XProc = [".xpl", ".xproc", ] +XQuery = [".xquery", ".xq", ".xql", ".xqm", ".xqy", ] +XS = [".xs", ] +XSLT = [".xslt", ".xsl", ] +Xojo = [".xojo_code", ".xojo_menu", ".xojo_report", ".xojo_script", ".xojo_toolbar", ".xojo_window", ] +Xtend = [".xtend", ] +YAML = [".yml", ".reek", ".rviz", ".sublime-syntax", ".syntax", ".yaml", ".yaml-tmlanguage", ] +YANG = [".yang", ] +Yacc = [".y", ".yacc", ".yy", ] +Zephir = [".zep", ] +Zig = [".zig", ] +Zimpl = [".zimpl", ".zmpl", ".zpl", ] +desktop = [".desktop", ".desktop.in", ] +eC = [".ec", ".eh", ] +edn = [".edn", ] +fish = [".fish", ] +mupad = [".mu", ] +nesC = [".nc", ] +ooc = [".ooc", ] +reStructuredText = [".rst", ".rest", ".rest.txt", ".rst.txt", ] +wisp = [".wisp", ] +xBase = [".prg", ".prw", ] + +[docs_blacklist_extensions] +# Disable docs for these extensions of text files and scripts that are not programming languages of function, classes and methods +docs_blacklist = ['sql', 'txt', 'yaml', 'json', 'xml', 'md', 'rst', 'rest', 'rest.txt', 'rst.txt', 'mdpolicy', 'mdown', 'markdown', 'mdwn', 'mkd', 'mkdn', 'mkdown', 'sh'] \ No newline at end of file diff --git a/keploy/pkg/service/utgen/assets/test_generation.toml b/keploy/pkg/service/utgen/assets/test_generation.toml new file mode 100644 index 0000000..c2b9d99 --- /dev/null +++ b/keploy/pkg/service/utgen/assets/test_generation.toml @@ -0,0 +1,355 @@ +[test_generation] +system="""\ +""" + +user="""\ +## Overview +You are a code assistant designed to accept a {{ .language }} source file and a {{ .language }} test file. +Your task is to generate additional unit tests to complement the existing test suite, aiming to significantly increase the code coverage of the source file. + +### Requirements for Creating Tests: + +- **Analyze the Provided Code:** + - Understand its purpose, inputs, outputs, and key logic or calculations. + - **Identify Return Types:** + - Determine the data types of return values for each function or method. + - Use return type information to guide the creation of relevant test cases. + +- **Refactor for Testability:** + - **Refactor the provided source code to improve testability**, including making external dependencies easily mockable, especially for asynchronous interactions. + - Ensure refactoring enhances testability without altering functionality or breaking existing behavior. + - Provide refactored code in the `refactored_source_code` field if changes are made. + - **Refactoring Techniques:** + - Use dependency injection to manage dependencies. + - Separate concerns to isolate different parts of the code. + - Implement interfaces or abstract classes to make components easily mockable. + +- **Utilize the Code Coverage Report:** + - Identify specific parts of the code not yet covered by tests. + - Focus on uncovered lines, branches, and conditions. + - **Highlight Critical Areas:** + - Prioritize testing for high-risk or critical sections of the code. + - **Coverage Metrics:** + - Aim for a minimum coverage threshold (e.g., 80%) and provide guidance on interpreting coverage metrics. + +- **Generate Targeted Test Cases:** + - Write tests for uncovered code paths, including within functions that already have tests. + - Include edge cases, error conditions, and scenarios with complex or async logic. + - **Boundary Conditions:** + - Test boundary values and limits. + - **Concurrency and Performance:** + - Include tests that assess concurrency or performance where applicable. + - **Security and Validation:** + - Write tests that validate input sanitization, authentication, and authorization where applicable. + - **Data Type Specific Tests:** + - **Validate Return Types:** + - Ensure that functions return data of the expected type. + - Create tests that check the integrity and structure of the returned data. + - **Type-Based Scenarios:** + - Generate test cases based on different data types (e.g., strings, integers, objects, arrays) to cover various input and output scenarios. + +- **Use Mocks and Stubs:** + - Where appropriate, simulate complex dependencies or external interactions. + - For asynchronous operations, use async-compatible mocking methods. + - Test for async edge cases, ensuring proper event loop handling and responses. + - **Mocking Strategies:** + - Use appropriate libraries (e.g., `unittest.mock` for Python, Mockito for Java). + - Simulate external API calls with predefined responses. + - Mock asynchronous functions using libraries compatible with async operations. + - Dont Mock Databases/Redis/Any Client + +- **Maximize Coverage:** + - Try to include as many functions and code paths as possible. + - Cover all branches, error handling paths, and edge cases. + - **Comprehensive Data Coverage:** + - Ensure that all possible data types and structures returned by functions are adequately tested. + - Include tests for both typical and atypical data types where applicable. + +- **Ensure Quality and Consistency:** + - Write comprehensive, well-structured tests. + - Follow the style and conventions of the existing test suite. + - Ensure test names are unique within the test suite. + - **Best Practices:** + - Adhere to naming conventions (e.g., `test_methodName_condition_expectedResult`). + - Add docstrings or comments within tests to explain their purpose. + - Avoid redundant tests by cross-referencing test behaviors. + - **Data Type Validation:** + - Incorporate checks to verify that returned data types match expected types. + +- **Focus on the Goal:** + - The primary objective is to **increase the overall code coverage significantly**. + - Do not include the code coverage report or any policies in your response. + +{{ if .function_under_test }} +- **Focus Function:** + - You must generate test cases specifically targeting the function named `{{ .function_under_test }}`. + - Ensure that the tests for this function cover all logic paths, edge cases, and error handling scenarios. + - **Data Type Consideration:** + - Analyze the return type of `{{ .function_under_test }}` and create tests that validate the correctness and integrity of the returned data. +{{ end }} + +{{ if .additional_command }} +- {{ .additional_command }} +{{ end }} + +## Source File +Here is the source file that you will be writing tests against, called `{{ .source_file_name }}`. Line numbers have been added for clarity and are not part of the original code. +========= +{{ .source_file_numbered | trim }} +========= + +## Test File +Here is the file that contains the existing tests, called `{{ .test_file_name }}`. +========= +{{ .test_file | trim }} +========= + +## Installed Packages +The following packages are already installed in the environment. Use these when writing tests to avoid redundant installations: + +========= +{{ .installed_packages | trim }} +========= + +{{ if .additional_includes_section | trim }} +{{ .additional_includes_section | trim }} +{{ end }} + +{{ if .failed_tests_section | trim }} +{{ .failed_tests_section | trim }} +{{ end }} + +{{if .module_name | trim}} +The module name of the go project is + +{{.module_name | trim}} + +{{end}} + +## Code Coverage +The following is the existing code coverage report. Use this to determine what tests to write, as you should only write tests that increase the overall coverage: +========= +{{ .code_coverage_report| trim }} +========= + +## Refactoring Guidelines +To improve testability without altering functionality, consider the following refactoring techniques: +- **Dependency Injection:** Pass dependencies as parameters to functions or constructors. +- **Separation of Concerns:** Isolate different parts of the code to simplify testing. +- **Use of Interfaces/Abstract Classes:** Define interfaces or abstract classes for components to facilitate mocking. + +Provide any refactored source code in the `refactored_source_code` field if changes are made. + +## Mocking Strategies +When simulating dependencies or external interactions: +- Use appropriate mocking libraries based on the language (e.g., `unittest.mock` for Python, Mockito for Java). +- Simulate external API calls with predefined responses. +- Mock asynchronous functions using libraries compatible with async operations. + +Ensure that mocks accurately represent the behavior of the actual dependencies to maintain test reliability. + +## Best Practices and Standards +- **Naming Conventions:** Follow a consistent naming pattern for tests, such as `test_methodName_condition_expectedResult`. +- **Test Documentation:** Include docstrings or comments to explain the purpose and logic of each test case. +- **Avoid Redundancy:** Ensure new tests are not duplicating existing ones by cross-referencing test behaviors. +- **Data Type Validation:** Incorporate checks to verify that returned data types match expected types. + +## Feedback Mechanism +- **Review and Iterate:** Periodically review generated tests to identify gaps or areas for improvement. +- **User Feedback Integration:** Allow users to provide feedback on the usefulness and coverage of generated tests to refine the generation logic. + +## Handling Complex Scenarios +Address more intricate testing scenarios to ensure comprehensive coverage: +- **Integration Tests:** Consider how integration tests fit into the overall testing strategy alongside unit tests. +- **Stateful Components:** Provide guidance on testing components that maintain state or have side effects. + +## YAML Response Structure +Ensure the YAML output adheres to the expected schema and is optimized for readability and maintainability: +- **Consistent Formatting:** Maintain uniform indentation and structure. +- **Modular Sections:** Organize the YAML into manageable sections. +- **Validation:** Ensure the YAML is free from syntax errors and conforms to the required schema. + +## Response +The output must be a YAML object equivalent to type $NewTests, according to the following Pydantic definitions: +===== +class SingleTest(BaseModel): + test_behavior: str = Field(description="Short description of the behavior the test covers") +{{ if or (eq .language "python") (eq .language "java") }} + test_name: str = Field(description="A short test name, in snake case, that reflects the behaviour to test") +{{ else }} + test_name: str = Field(description="A short unique test name, that should reflect the test objective") +{{ end }} + test_code: str = Field(description="A single test function, that tests the behavior described in 'test_behavior'. The test should be a written like its a part of the existing test suite, if there is one, and it can use existing helper functions, setup, or teardown code.") + new_imports_code: str = Field(description="Code for new imports that are required for the new test function, and are not already present in the test file.") + library_installation_code: str = Field(description="If new libraries are needed, specify the installation commands for each library separately.") + test_tags: str = Field(description="A single label that best describes the test, out of: ['happy path', 'edge case','other']") + +class NewTests(BaseModel): + language: str = Field(description="The programming language of the source code") + existing_test_function_signature: str = Field(description="A single line repeating a signature header of one of the existing test functions") + new_tests: List[SingleTest] = Field(min_items=1, max_items={{ .max_tests }}, description="A list of new test functions to append to the existing test suite, aiming to increase the code coverage. Each test should run as-is, without requiring any additional inputs or setup code.") + refactored_source_code: str = Field(description="The refactored source code that improves testability while retaining original functionality.") + +===== + +Example output: +```yaml +language: {{ .language }} +existing_test_function_signature: | + ... +new_tests: +- test_behavior: | + Test that the function returns the correct output for a single element list +{{- if (or (eq .language "python") (eq .language "java")) }} + test_name: | + test_single_element_list +{{- else }} + test_name: | + ... +{{- end }} + test_code: | +{{- if eq .language "python" }} + def ... +{{- else }} + ... +{{- end }} + new_imports_code: | + {{- if eq .language "python" }} + "import pytest" + "from my_module import my_function" + {{- else if eq .language "java" }} + "import org.junit.jupiter.api.Test;" + "import my.package.MyFunction;" + {{- else if eq .language "go" }} + "import "testing" " + "import "my_module"" + {{- else if eq .language "javascript" }} + "const assert = require('assert');" + "const myFunction = require('my_module').myFunction;" + {{- else if eq .language "typescript" }} + "import { assert } from 'assert';" + "import { myFunction } from 'my_module';" + {{- end }} + library_installation_code: | + {{- if eq .language "python" }} + pip install pytest + {{- else if eq .language "java" }} + # Add the following to your Maven pom.xml: + # + # org.junit.jupiter + # junit-jupiter-api + # 5.7.0 + # test + # + {{- else if eq .language "go" }} + go get github.com/my_module + {{- else if eq .language "javascript" }} + npm install assert + {{- else if eq .language "typescript" }} + npm install assert + {{- end }} + test_tags: happy path + +refactored_source_code: | + # Here is the modified source code that retains original functionality but improves testability. + ... +``` + +additions: + additional_instructions_for_tests: | + {{- if or (eq .language "javascript") (eq .language "typescript") }} + In JavaScript and TypeScript, to handle asynchronous tests, please use testing frameworks like Jest or Mocha that natively support async/await. Ensure that you: + - Import the necessary testing library (e.g., Jest). + - Use `async` functions for tests that involve asynchronous operations. + - Utilize appropriate hooks (`beforeAll`, `afterAll`, `beforeEach`, `afterEach`) for setup and teardown. + - Handle promises correctly to avoid unhandled rejections. + + Example for Jest: + ```javascript + const { someAsyncFunction } = require('./sourceFile'); + + test('should handle async operation correctly', async () => { + const result = await someAsyncFunction(); + expect(result).toBe(expectedValue); + }); + ``` + In TypeScript, ensure type definitions are correctly handled in your tests. + + {{- else if eq .language "go" }} + For Golang, ensure that your test functions follow the naming convention `TestFunctionName` and accept a `*testing.T` parameter. Import the `testing` package and utilize it to manage test cases. + + Example: + ```go + import ( + "testing" + ) + + func TestFunctionName(t *testing.T) { + result := FunctionUnderTest() + if result != expected { + t.Errorf("Expected %v, got %v", expected, result) + } + } + ``` + {{- else if eq .language "python" }} + In Python, to handle asynchronous tests and resolve issues like unclosed client sessions, please: + - Use `pytest-asyncio` for managing async tests. + - Decorate async test functions with `@pytest.mark.asyncio`. + - Import `pytest` and `pytest-asyncio` in your test files. + + Example: + ```python + import pytest + @pytest.mark.asyncio + async def test_async_function(): + result = await some_async_function() + assert result == expected_value + ``` + + {{- else if eq .language "java" }} + In Java, to handle asynchronous tests: + - Use JUnit 5 or similar frameworks that support async testing. + - Import necessary JUnit libraries. + - Utilize `CompletableFuture` or other async constructs. + - Annotate test methods with `@Test`. + + Example: + ```java + import org.junit.jupiter.api.Test; + import static org.junit.jupiter.api.Assertions.*; + + public class MyAsyncTests { + + @Test + public void testAsyncFunction() throws Exception { + CompletableFuture future = someAsyncOperation(); + String result = future.get(); + assertEquals("expectedValue", result); + } + } + ``` + {{- end }} + +Use block scalar('|') to format each YAML output. + +{{- if .import_details | trim }} +Imported Structs Overview: +Below is a list of all structs and functions imported and utilized within this file. These structs are integral to the functionality, +and they should be carefully considered when creating mocks and tests. This list serves as a reference for dependencies +and external types that interact with the main code. + +{{ .import_details | trim }} + +{{- end }} + +# Configuration for handling refactored code output + +[refactor] + +# Response to send if the refactored_source_code field looks like `no refactor response` or is empty +response_if_no_refactor = "blank output don't refactor code" + + +Response (should be a valid YAML, and nothing else): +```yaml +""" diff --git a/keploy/pkg/service/utgen/coverage.go b/keploy/pkg/service/utgen/coverage.go new file mode 100644 index 0000000..888609d --- /dev/null +++ b/keploy/pkg/service/utgen/coverage.go @@ -0,0 +1,249 @@ +package utgen + +import ( + "bytes" + "encoding/xml" + "fmt" + "os" + "strconv" + "strings" + "time" + + "go.keploy.io/server/v2/pkg/models" +) + +// CoverageProcessor handles the processing of coverage reports +type CoverageProcessor struct { + ReportPath string + SrcPath string + Format string +} + +// NewCoverageProcessor initializes a CoverageProcessor object +func NewCoverageProcessor(reportPath, srcpath, format string) *CoverageProcessor { + return &CoverageProcessor{ + ReportPath: reportPath, + SrcPath: srcpath, + Format: format, + } +} + +// ProcessCoverageReport verifies the report and parses it based on its type +func (cp *CoverageProcessor) ProcessCoverageReport(latestTime int64) (*models.CoverageResult, error) { + err := cp.VerifyReportUpdate(latestTime) + if err != nil { + return nil, err + } + return cp.ParseCoverageReport() +} + +// VerifyReportUpdate verifies the coverage report's existence and update time +func (cp *CoverageProcessor) VerifyReportUpdate(latestTime int64) error { + if _, err := os.Stat(cp.ReportPath); os.IsNotExist(err) { + return fmt.Errorf("fatal: coverage report \"%s\" was not generated", cp.ReportPath) + } + + fileInfo, err := os.Stat(cp.ReportPath) + if err != nil { + return err + } + fileModTimeMs := fileInfo.ModTime().UnixNano() / int64(time.Millisecond) + + if fileModTimeMs < latestTime { + time.Sleep(2 * time.Second) + fileInfo, err = os.Stat(cp.ReportPath) + if err != nil { + return err + } + fileModTimeMs = fileInfo.ModTime().UnixNano() / int64(time.Millisecond) + if fileModTimeMs < latestTime { + return fmt.Errorf("fatal: the coverage report file was not updated after the test command. file_mod_time_ms: %d, time_of_test_command: %d", fileModTimeMs, latestTime) + } + } + return nil +} + +// ParseCoverageReport parses the coverage report based on its type +func (cp *CoverageProcessor) ParseCoverageReport() (*models.CoverageResult, error) { + switch cp.Format { + case "cobertura": + return cp.ParseCoverageReportCobertura() + case "jacoco": + return cp.ParseCoverageReportJacoco() + case "lcov": + return nil, fmt.Errorf("parsing for %s coverage reports is not implemented yet", cp.Format) + default: + return nil, fmt.Errorf("unsupported coverage report type: %s", cp.Format) + } +} + +func (cp *CoverageProcessor) ParseCoverageReportCobertura() (*models.CoverageResult, error) { + + filesToCover := make([]string, 0) + // Open the XML file + xmlFile, err := os.Open(cp.ReportPath) + if err != nil { + return nil, err + } + + defer func() { + if err := xmlFile.Close(); err != nil { + return + } + }() + + // Decode the XML file into a Coverage struct + var cov models.Cobertura + if err := xml.NewDecoder(xmlFile).Decode(&cov); err != nil { + return nil, err + } + + // Find coverage for the specified file + var linesCovered, linesMissed []int + var totalLines, coveredLines int + var filteredClasses []models.Class + for _, pkg := range cov.Packages { + for _, cls := range pkg.Classes { + if cp.SrcPath == "." { + filesToCover = append(filesToCover, cls.FileName) + } + if strings.HasSuffix(cls.FileName, cp.SrcPath) { + for _, line := range cls.Lines { + totalLines++ + if line.Hits > 0 { + coveredLines++ + linesCovered = append(linesCovered, line.Number) + } else { + linesMissed = append(linesMissed, line.Number) + } + } + filteredClasses = append(filteredClasses, cls) + break + } + } + } + + var coveragePercentage float64 + if totalLines > 0 { + coveragePercentage = float64(len(linesCovered)) / float64(totalLines) + } + + // Reconstruct the coverage report with only the filtered class + filteredCov := models.Cobertura{ + Packages: []models.Package{ + { + Classes: filteredClasses, + }, + }, + } + + // Encode the filtered coverage report to XML + var filteredBuf bytes.Buffer + xmlEncoder := xml.NewEncoder(&filteredBuf) + xmlEncoder.Indent("", " ") + if err := xmlEncoder.Encode(filteredCov); err != nil { + return nil, err + } + + coverageResult := &models.CoverageResult{ + LinesCovered: linesCovered, + LinesMissed: linesMissed, + Coverage: coveragePercentage, + Files: filesToCover, + ReportContent: filteredBuf.String(), + } + + return coverageResult, nil +} + +func (cp *CoverageProcessor) ParseCoverageReportJacoco() (*models.CoverageResult, error) { + + filesToCover := make([]string, 0) + // Open the XML file + xmlFile, err := os.Open(cp.ReportPath) + if err != nil { + return nil, err + } + + defer func() { + if err := xmlFile.Close(); err != nil { + return + } + }() + + // Decode the XML file into a Coverage struct + var jacoco models.Jacoco + if err := xml.NewDecoder(xmlFile).Decode(&jacoco); err != nil { + return nil, err + } + + // Find coverage for the specified file + var linesCovered, linesMissed []int + var totalLines, coveredLines int + var filteredSourceFiles []models.JacocoSourceFile + + for _, pkg := range jacoco.Packages { + for _, src := range pkg.SourceFiles { + if cp.SrcPath == "." { + filesToCover = append(filesToCover, src.Name) + } + if strings.HasSuffix(src.Name, cp.SrcPath) { + for _, line := range src.Lines { + totalLines++ + missedInstructions, errMissed := strconv.Atoi(line.MissedInstructions) + coveredInstructions, errCovered := strconv.Atoi(line.CoveredInstructions) + if errMissed != nil || errCovered != nil { + // Handle conversion error + continue + } + if coveredInstructions > 0 { + coveredLines++ + lineNumber, err := strconv.Atoi(line.Number) + if err == nil { + linesCovered = append(linesCovered, lineNumber) + } + } else if missedInstructions > 0 { + // Use missedInstructions to check if a line has any missed instructions + lineNumber, err := strconv.Atoi(line.Number) + if err == nil { + linesMissed = append(linesMissed, lineNumber) + } + } + } + filteredSourceFiles = append(filteredSourceFiles, src) + break + } + } + } + var coveragePercentage float64 + if totalLines > 0 { + coveragePercentage = float64(len(linesCovered)) / float64(totalLines) + } + + // Reconstruct the coverage report with only the filtered class + filteredCov := models.Jacoco{ + Packages: []models.JacocoPackage{ + { + SourceFiles: filteredSourceFiles, + }, + }, + } + + // Encode the filtered coverage report to XML + var filteredBuf bytes.Buffer + xmlEncoder := xml.NewEncoder(&filteredBuf) + xmlEncoder.Indent("", " ") + if err := xmlEncoder.Encode(filteredCov); err != nil { + return nil, err + } + + coverageResult := &models.CoverageResult{ + LinesCovered: linesCovered, + LinesMissed: linesMissed, + Coverage: coveragePercentage, + Files: filesToCover, + ReportContent: filteredBuf.String(), + } + + return coverageResult, nil +} diff --git a/keploy/pkg/service/utgen/gen.go b/keploy/pkg/service/utgen/gen.go new file mode 100644 index 0000000..a446af8 --- /dev/null +++ b/keploy/pkg/service/utgen/gen.go @@ -0,0 +1,813 @@ +// Package utgen is a service that generates unit tests for a given source code file. +package utgen + +import ( + "context" + "fmt" + "math" + "os" + "path/filepath" + "strings" + "time" + + "github.com/google/uuid" + "github.com/k0kubun/pp/v3" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/models" + "go.keploy.io/server/v2/pkg/service" + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +type Coverage struct { + Path string + Format string + Desired float64 + Current float64 + Content string +} + +type Cursor struct { + Line int + Indentation int +} + +type UnitTestGenerator struct { + srcPath string + testPath string + cmd string + dir string + cov *Coverage + lang string + cur *Cursor + failedTests []*models.FailedUT + prompt *Prompt + ai *AIClient + logger *zap.Logger + promptBuilder *PromptBuilder + injector *Injector + maxIterations int + Files []string + tel Telemetry + additionalPrompt string + totalTestCase int + testCasePassed int + testCaseFailed int + noCoverageTest int + flakiness bool +} + +var discardedTestsFilename = "discardedTests.txt" + +func NewUnitTestGenerator( + cfg *config.Config, + tel Telemetry, + auth service.Auth, + logger *zap.Logger, +) (*UnitTestGenerator, error) { + genConfig := cfg.Gen + + generator := &UnitTestGenerator{ + srcPath: genConfig.SourceFilePath, + testPath: genConfig.TestFilePath, + cmd: genConfig.TestCommand, + dir: genConfig.TestDir, + maxIterations: genConfig.MaxIterations, + logger: logger, + tel: tel, + ai: NewAIClient(genConfig, "", cfg.APIServerURL, auth, uuid.NewString(), logger), + cov: &Coverage{ + Path: genConfig.CoverageReportPath, + Format: genConfig.CoverageFormat, + Desired: genConfig.DesiredCoverage, + }, + additionalPrompt: genConfig.AdditionalPrompt, + cur: &Cursor{}, + flakiness: genConfig.Flakiness, + } + return generator, nil +} + +func (g *UnitTestGenerator) Start(ctx context.Context) error { + g.tel.GenerateUT() + + // Check for context cancellation before proceeding + select { + case <-ctx.Done(): + return fmt.Errorf("process cancelled by user") + default: + // Continue if no cancellation + } + + // To find the source files if the source path is not provided + if g.srcPath == "" { + if err := g.runCoverage(); err != nil { + return err + } + if len(g.Files) == 0 { + return fmt.Errorf("couldn't identify the source files. Please mention source file and test file using flags") + } + } + const paddingHeight = 1 + columnWidths3 := []int{29, 29, 29} + columnWidths2 := []int{40, 40} + + for i := 0; i < len(g.Files)+1; i++ { + newTestFile := false + var err error + + // Respect context cancellation in each iteration + select { + case <-ctx.Done(): + return fmt.Errorf("process cancelled by user") + default: + } + + // If the source file path is not provided, iterate over all the source files and test files + if i < len(g.Files) { + g.srcPath = g.Files[i] + g.testPath, err = getTestFilePath(g.srcPath, g.dir) + if err != nil || g.testPath == "" { + g.logger.Error("Error getting test file path", zap.Error(err)) + continue + } + isCreated, err := createTestFile(g.testPath, g.srcPath) + if err != nil { + g.logger.Error("Error creating test file", zap.Error(err)) + continue + } + newTestFile = isCreated + } + + g.logger.Info(fmt.Sprintf("Generating tests for file: %s", g.srcPath)) + isEmpty, err := utils.IsFileEmpty(g.testPath) + if err != nil { + g.logger.Error("Error checking if test file is empty", zap.Error(err)) + return err + } + if isEmpty { + newTestFile = true + } + if !newTestFile { + if err = g.runCoverage(); err != nil { + return err + } + } else { + g.cov.Current = 0 + } + + iterationCount := 1 + g.lang = GetCodeLanguage(g.srcPath) + g.promptBuilder, err = NewPromptBuilder(g.srcPath, g.testPath, g.cov.Content, "", "", g.lang, g.additionalPrompt, g.ai.FunctionUnderTest, g.logger) + g.injector = NewInjectorBuilder(g.logger, g.lang) + + if err != nil { + utils.LogError(g.logger, err, "Error creating prompt builder") + return err + } + if !isEmpty { + if err := g.setCursor(ctx); err != nil { + utils.LogError(g.logger, err, "Error during initial test suite analysis") + return err + } + } + initialCoverage := g.cov.Current + // Respect context cancellation in the inner loop + for g.cov.Current < (g.cov.Desired/100) && iterationCount <= g.maxIterations { + passedTests, noCoverageTest, failedBuild, totalTest := 0, 0, 0, 0 + select { + case <-ctx.Done(): + return fmt.Errorf("process cancelled by user") + default: + } + + pp.SetColorScheme(models.GetPassingColorScheme()) + if _, err := pp.Printf("Current Coverage: %s%% for file %s\n", math.Round(g.cov.Current*100), g.srcPath); err != nil { + utils.LogError(g.logger, err, "failed to print coverage") + } + if _, err := pp.Printf("Desired Coverage: %s%% for file %s\n", g.cov.Desired, g.srcPath); err != nil { + utils.LogError(g.logger, err, "failed to print coverage") + } + + // Check for failed tests: + failedTestRunsValue := "" + if len(g.failedTests) > 0 { + for _, failedTest := range g.failedTests { + code := failedTest.TestCode + errorMessage := failedTest.ErrorMsg + failedTestRunsValue += fmt.Sprintf("Failed Test:\n\n%s\n\n", code) + if errorMessage != "" { + failedTestRunsValue += fmt.Sprintf("Error message for test above:\n%s\n\n\n", errorMessage) + } else { + failedTestRunsValue += "\n\n" + } + } + } + + g.promptBuilder.InstalledPackages, err = g.injector.libraryInstalled() + g.promptBuilder.ImportDetails = g.injector.getModelDetails(g.srcPath) + g.promptBuilder.ModuleName, _ = g.injector.GetModuleName(g.srcPath) + if err != nil { + utils.LogError(g.logger, err, "Error getting installed packages") + } + g.prompt, err = g.promptBuilder.BuildPrompt("test_generation", failedTestRunsValue) + if err != nil { + utils.LogError(g.logger, err, "Error building prompt") + return err + } + g.failedTests = []*models.FailedUT{} + testsDetails, err := g.GenerateTests(ctx, iterationCount) + if err != nil { + utils.LogError(g.logger, err, "Error generating tests") + return err + } + if testsDetails == nil { + g.logger.Info("No tests generated") + continue + } + + var originalSrcCode string + var codeModified bool + + // Check if code refactoring is needed + if len(testsDetails.RefactoredSourceCode) != 0 { + // Read the original source code + originalSrcCode, err = readFile(g.srcPath) + if err != nil { + return fmt.Errorf("failed to read the source code: %w", err) + } + + // modify the source code for refactoring. + if !(strings.Contains(testsDetails.RefactoredSourceCode, "blank output don't refactor code") || strings.Contains(testsDetails.RefactoredSourceCode, "no refactoring")) { + if err := os.WriteFile(g.srcPath, []byte(testsDetails.RefactoredSourceCode), 0644); err != nil { + return fmt.Errorf("failed to refactor source code:%w", err) + } + codeModified = true + } + } + + var overallCovInc = false + + g.logger.Info("Validating new generated tests one by one") + g.totalTestCase += len(testsDetails.NewTests) + totalTest = len(testsDetails.NewTests) + + for _, generatedTest := range testsDetails.NewTests { + installedPackages, err := g.injector.libraryInstalled() + if err != nil { + g.logger.Warn("Error getting installed packages", zap.Error(err)) + } + select { + case <-ctx.Done(): + return fmt.Errorf("process cancelled by user") + default: + } + coverageInc, err := g.ValidateTest(generatedTest, &passedTests, &noCoverageTest, &failedBuild, installedPackages) + if err != nil { + utils.LogError(g.logger, err, "Error validating test") + + rerr := revertSourceCode(g.srcPath, originalSrcCode, codeModified) + if rerr != nil { + utils.LogError(g.logger, rerr, "Error reverting source code") + } + + return err + } + + // if any test increases the coverage, set the flag to true + overallCovInc = overallCovInc || coverageInc + } + // if any of the test couldn't increase the coverage, revert the source code + if !overallCovInc { + err := revertSourceCode(g.srcPath, originalSrcCode, codeModified) + if err != nil { + utils.LogError(g.logger, err, "Error reverting source code") + } + } else { + g.promptBuilder.Src.Code = testsDetails.RefactoredSourceCode + } + if g.cov.Current < (g.cov.Desired/100) && g.cov.Current > 0 { + if err := g.runCoverage(); err != nil { + utils.LogError(g.logger, err, "Error running coverage") + return err + } + } + + if len(g.failedTests) > 0 { + err := g.saveFailedTestCasesToFile() + if err != nil { + utils.LogError(g.logger, err, "Error adding failed test cases to file") + } + } + + fmt.Printf("\n<=========================================>\n") + fmt.Printf(("Tests generated in Session") + "\n") + fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") + fmt.Printf("| %s | %s | %s |\n", + centerAlignText("Total Test Cases", 29), + centerAlignText("Test Cases Passed", 29), + centerAlignText("Test Cases Failed", 29)) + fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") + fmt.Print(addHeightPadding(paddingHeight, 3, columnWidths3)) + fmt.Printf("| \033[33m%s\033[0m | \033[32m%s\033[0m | \033[33m%s\033[0m |\n", + centerAlignText(fmt.Sprintf("%d", totalTest), 29), + centerAlignText(fmt.Sprintf("%d", passedTests), 29), + centerAlignText(fmt.Sprintf("%d", failedBuild+noCoverageTest), 29)) + fmt.Print(addHeightPadding(paddingHeight, 3, columnWidths3)) + fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") + fmt.Printf(("Discarded tests in session") + "\n") + fmt.Printf("+------------------------------------------+------------------------------------------+\n") + fmt.Printf("| %s | %s |\n", + centerAlignText("Build failures", 40), + centerAlignText("No Coverage output", 40)) + fmt.Printf("+------------------------------------------+------------------------------------------+\n") + fmt.Print(addHeightPadding(paddingHeight, 2, columnWidths2)) + fmt.Printf("| \033[35m%s\033[0m | \033[92m%s\033[0m |\n", + centerAlignText(fmt.Sprintf("%d", failedBuild), 40), + centerAlignText(fmt.Sprintf("%d", noCoverageTest), 40)) + fmt.Print(addHeightPadding(paddingHeight, 2, columnWidths2)) + fmt.Printf("+------------------------------------------+------------------------------------------+\n") + fmt.Printf("<=========================================>\n") + err = g.ai.SendCoverageUpdate(ctx, g.ai.SessionID, initialCoverage, g.cov.Current, iterationCount) + if err != nil { + utils.LogError(g.logger, err, "Error sending coverage update") + } + + iterationCount++ + } + + if g.cov.Current == 0 && newTestFile { + err := os.Remove(g.testPath) + if err != nil { + g.logger.Error("Error removing test file", zap.Error(err)) + } + } + + pp.SetColorScheme(models.GetPassingColorScheme()) + if g.cov.Current >= (g.cov.Desired / 100) { + if _, err := pp.Printf("For File %s Reached above target coverage of %s%% (Current Coverage: %s%%) in %s iterations.\n", g.srcPath, g.cov.Desired, math.Round(g.cov.Current*100), iterationCount); err != nil { + utils.LogError(g.logger, err, "failed to print coverage") + } + } else if iterationCount > g.maxIterations { + if _, err := pp.Printf("For File %s Reached maximum iteration limit without achieving desired coverage. Current Coverage: %s%%\n", g.srcPath, math.Round(g.cov.Current*100)); err != nil { + utils.LogError(g.logger, err, "failed to print coverage") + } + } + } + fmt.Printf("\n<=========================================>\n") + fmt.Printf(("COMPLETE TEST GENERATE SUMMARY") + "\n") + fmt.Printf(("Total Test Summary") + "\n") + + fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") + fmt.Printf("| %s | %s | %s |\n", + centerAlignText("Total Test Cases", 29), + centerAlignText("Test Cases Passed", 29), + centerAlignText("Test Cases Failed", 29)) + + fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") + fmt.Print(addHeightPadding(paddingHeight, 3, columnWidths3)) + fmt.Printf("| \033[33m%s\033[0m | \033[32m%s\033[0m | \033[33m%s\033[0m |\n", + centerAlignText(fmt.Sprintf("%d", g.totalTestCase), 29), + centerAlignText(fmt.Sprintf("%d", g.testCasePassed), 29), + centerAlignText(fmt.Sprintf("%d", g.testCaseFailed+g.noCoverageTest), 29)) + fmt.Print(addHeightPadding(paddingHeight, 3, columnWidths3)) + fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") + + fmt.Printf(("Discarded Cases Summary") + "\n") + fmt.Printf("+------------------------------------------+------------------------------------------+\n") + fmt.Printf("| %s | %s |\n", + centerAlignText("Build failures", 40), + centerAlignText("No Coverage output", 40)) + + fmt.Printf("+------------------------------------------+------------------------------------------+\n") + fmt.Print(addHeightPadding(paddingHeight, 2, columnWidths2)) + fmt.Printf("| \033[35m%s\033[0m | \033[92m%s\033[0m |\n", + centerAlignText(fmt.Sprintf("%d", g.testCaseFailed), 40), + centerAlignText(fmt.Sprintf("%d", g.noCoverageTest), 40)) + fmt.Print(addHeightPadding(paddingHeight, 2, columnWidths2)) + fmt.Printf("+------------------------------------------+------------------------------------------+\n") + + fmt.Printf("<=========================================>\n") + return nil +} + +func revertSourceCode(srcPath, originalSrcCode string, codeModified bool) error { + if !codeModified { + return nil + } + + if err := os.WriteFile(srcPath, []byte(originalSrcCode), 0644); err != nil { + return fmt.Errorf("failed to revert source code to the original state:%w", err) + } + return nil +} + +func centerAlignText(text string, width int) string { + text = strings.Trim(text, "\"") + + textLen := len(text) + if textLen >= width { + return text + } + + leftPadding := (width - textLen) / 2 + rightPadding := width - textLen - leftPadding + + return fmt.Sprintf("%s%s%s", strings.Repeat(" ", leftPadding), text, strings.Repeat(" ", rightPadding)) +} + +func addHeightPadding(rows int, columns int, columnWidths []int) string { + padding := "" + for i := 0; i < rows; i++ { + for j := 0; j < columns; j++ { + if j == columns-1 { + padding += fmt.Sprintf("| %-*s |\n", columnWidths[j], "") + } else { + padding += fmt.Sprintf("| %-*s ", columnWidths[j], "") + } + } + } + return padding +} + +func statusUpdater(stop <-chan bool) { + messages := []string{ + "Running tests... Please wait.", + "Still running tests... Hang tight!", + "Tests are still executing... Almost there!", + } + i := 0 + for { + select { + case <-stop: + fmt.Printf("\r\033[K") + return + default: + fmt.Printf("\r\033[K%s", messages[i%len(messages)]) + time.Sleep(5 * time.Second) + i++ + } + } +} + +func (g *UnitTestGenerator) runCoverage() error { + // Perform an initial build/test command to generate coverage report and get a baseline + if g.srcPath != "" { + g.logger.Info(fmt.Sprintf("Running test command to generate coverage report: '%s'", g.cmd)) + } + + stopStatus := make(chan bool) + go statusUpdater(stopStatus) + + startTime := time.Now() + + _, _, exitCode, lastUpdatedTime, err := RunCommand(g.cmd, g.dir, g.logger) + duration := time.Since(startTime) + stopStatus <- true + g.logger.Info(fmt.Sprintf("Test command completed in %v", formatDuration(duration))) + + if err != nil { + g.logger.Warn("Test command failed. Ensure no tests are failing, and rerun the command.") + return fmt.Errorf("error running test command: %s", g.cmd) + } + if exitCode != 0 { + utils.LogError(g.logger, err, "Error running test command") + } + coverageProcessor := NewCoverageProcessor(g.cov.Path, getFilename(g.srcPath), g.cov.Format) + coverageResult, err := coverageProcessor.ProcessCoverageReport(lastUpdatedTime) + if err != nil { + utils.LogError(g.logger, err, "Error in coverage processing") + return fmt.Errorf("error in coverage processing: %w", err) + } + g.cov.Current = coverageResult.Coverage + g.cov.Content = coverageResult.ReportContent + if g.srcPath == "" { + g.Files = coverageResult.Files + } + return nil +} + +func (g *UnitTestGenerator) GenerateTests(ctx context.Context, iterationCount int) (*models.UTDetails, error) { + fmt.Println("Generating Tests...") + + select { + case <-ctx.Done(): + err := ctx.Err() + return &models.UTDetails{}, err + default: + } + + requestPurpose := TestForFile + if len(g.ai.FunctionUnderTest) > 0 { + requestPurpose = TestForFunction + } + + updatedTestContent, err := readFile(g.testPath) + if err != nil { + g.logger.Error("Error reading updated test file content", zap.Error(err)) + return &models.UTDetails{}, err + } + g.promptBuilder.Test.Code = updatedTestContent + g.promptBuilder.CovReportContent = g.cov.Content + g.prompt, err = g.promptBuilder.BuildPrompt("test_generation", "") + if err != nil { + utils.LogError(g.logger, err, "Error building prompt") + return &models.UTDetails{}, err + } + + aiRequest := AIRequest{ + MaxTokens: 4096, + Prompt: *g.prompt, + SessionID: g.ai.SessionID, + Iteration: iterationCount, + RequestPurpose: requestPurpose, + } + + response, err := g.ai.Call(ctx, CompletionParams{}, aiRequest, false) + if err != nil { + return &models.UTDetails{}, err + } + + testsDetails, err := unmarshalYamlTestDetails(response) + if err != nil { + utils.LogError(g.logger, err, "Error unmarshalling test details") + return &models.UTDetails{}, err + } + + return testsDetails, nil +} + +func (g *UnitTestGenerator) setCursor(ctx context.Context) error { + fmt.Println("Getting indentation for new Tests...") + indentation, err := g.getIndentation(ctx) + if err != nil { + return fmt.Errorf("failed to analyze test headers indentation: %w", err) + } + fmt.Println("Getting Line number for new Tests...") + line, err := g.getLine(ctx) + if err != nil { + return fmt.Errorf("failed to analyze relevant line number to insert new tests: %w", err) + } + g.cur.Indentation = indentation + g.cur.Line = line + return nil +} + +func (g *UnitTestGenerator) getIndentation(ctx context.Context) (int, error) { + indentation := -1 + allowedAttempts := 3 + counterAttempts := 0 + for indentation == -1 && counterAttempts < allowedAttempts { + prompt, err := g.promptBuilder.BuildPrompt("indentation", "") + if err != nil { + return 0, fmt.Errorf("error building prompt: %w", err) + } + + aiRequest := AIRequest{ + MaxTokens: 4096, + Prompt: *prompt, + SessionID: g.ai.SessionID, + } + response, err := g.ai.Call(ctx, CompletionParams{}, aiRequest, false) + if err != nil { + utils.LogError(g.logger, err, "Error calling AI model") + return 0, err + } + testsDetails, err := unmarshalYamlTestHeaders(response) + if err != nil { + utils.LogError(g.logger, err, "Error unmarshalling test headers") + return 0, err + } + + indentation, err = convertToInt(testsDetails.Indentation) + if err != nil { + return 0, fmt.Errorf("error converting test_headers_indentation to int: %w", err) + } + counterAttempts++ + } + if indentation == -1 { + return 0, fmt.Errorf("failed to analyze the test headers indentation") + } + return indentation, nil +} + +func (g *UnitTestGenerator) getLine(ctx context.Context) (int, error) { + line := -1 + allowedAttempts := 3 + counterAttempts := 0 + for line == -1 && counterAttempts < allowedAttempts { + prompt, err := g.promptBuilder.BuildPrompt("insert_line", "") + if err != nil { + return 0, fmt.Errorf("error building prompt: %w", err) + } + + aiRequest := AIRequest{ + MaxTokens: 4096, + Prompt: *prompt, + SessionID: g.ai.SessionID, + } + response, err := g.ai.Call(ctx, CompletionParams{}, aiRequest, false) + if err != nil { + utils.LogError(g.logger, err, "Error calling AI model") + return 0, err + } + testsDetails, err := unmarshalYamlTestLine(response) + if err != nil { + utils.LogError(g.logger, err, "Error unmarshalling test line") + return 0, err + } + + line, err = convertToInt(testsDetails.Line) + if err != nil { + return 0, fmt.Errorf("error converting relevant_line_number_to_insert_after to int: %w", err) + } + counterAttempts++ + } + if line == -1 { + return 0, fmt.Errorf("failed to analyze the relevant line number to insert new tests") + } + return line, nil +} + +func (g *UnitTestGenerator) ValidateTest( + generatedTest models.UT, + passedTests, + noCoverageTest, + failedBuild *int, + installedPackages []string, +) (bool, error) { + testCode := strings.TrimSpace(generatedTest.TestCode) + InsertAfter := g.cur.Line + Indent := g.cur.Indentation + testCodeIndented := testCode + if Indent != 0 { + initialIndent := len(testCode) - len(strings.TrimLeft(testCode, " ")) + deltaIndent := Indent - initialIndent + if deltaIndent > 0 { + lines := strings.Split(testCode, "\n") + for i, line := range lines { + lines[i] = strings.Repeat(" ", deltaIndent) + line + } + testCodeIndented = strings.Join(lines, "\n") + } + } + testCodeIndented = "\n" + g.injector.addCommentToTest(strings.TrimSpace(testCodeIndented)) + "\n" + originalContent, err := readFile(g.testPath) + if err != nil { + return false, fmt.Errorf("failed to read test file: %w", err) + } + originalContentLines := strings.Split(originalContent, "\n") + testCodeLines := strings.Split(testCodeIndented, "\n") + if InsertAfter > len(originalContentLines) { + InsertAfter = len(originalContentLines) + } + processedTestLines := append(originalContentLines[:InsertAfter], testCodeLines...) + processedTestLines = append(processedTestLines, originalContentLines[InsertAfter:]...) + processedTest := strings.Join(processedTestLines, "\n") + if err := os.WriteFile(g.testPath, []byte(processedTest), 0644); err != nil { + return false, fmt.Errorf("failed to write test file: %w", err) + } + importLen, err := g.injector.updateImports(g.testPath, generatedTest.NewImportsCode) + if err != nil { + g.logger.Warn("Error updating imports", zap.Error(err)) + } + newInstalledPackages, err := g.injector.installLibraries(generatedTest.LibraryInstallationCode, installedPackages) + if err != nil { + g.logger.Debug("Error installing libraries", zap.Error(err)) + } + + g.logger.Info(fmt.Sprintf("Running Test with command: '%s'", g.cmd)) + stdout, stderr, exitCode, timeOfTestCommand, _ := RunCommand(g.cmd, g.dir, g.logger) + if exitCode != 0 { + g.logger.Info("Test Run Failed") + if err := os.WriteFile(g.testPath, []byte(originalContent), 0644); err != nil { + return false, fmt.Errorf("failed to revert test file: %w", err) + } + err = g.injector.uninstallLibraries(newInstalledPackages) + if err != nil { + g.logger.Warn("Error uninstalling libraries", zap.Error(err)) + } + // Mark test as failed + g.failedTests = append(g.failedTests, &models.FailedUT{ + TestCode: generatedTest.TestCode, + ErrorMsg: extractErrorMessage(stdout, stderr, g.lang), + NewImportsCode: generatedTest.NewImportsCode, + LibraryInstallationCode: generatedTest.LibraryInstallationCode, + }) + g.testCaseFailed++ + *failedBuild++ + return false, nil + } + + coverageProcessor := NewCoverageProcessor(g.cov.Path, getFilename(g.srcPath), g.cov.Format) + coverageResult, err := coverageProcessor.ProcessCoverageReport(timeOfTestCommand) + if err != nil { + utils.LogError(g.logger, err, "Error in coverage processing") + return false, fmt.Errorf("error in coverage processing: %w", err) + } + initialCoverage := g.cov.Current + g.cov.Current = coverageResult.Coverage + g.cov.Content = coverageResult.ReportContent + if g.srcPath == "" { + g.Files = coverageResult.Files + } + + coverageIncreased := g.cov.Current > initialCoverage + if !coverageIncreased { + g.logger.Info("No coverage increase detected after initial test run.") + // Revert test file to original content + if err := os.WriteFile(g.testPath, []byte(originalContent), 0644); err != nil { + return false, fmt.Errorf("failed to revert test file: %w", err) + } + // Uninstall any installed libraries + err = g.injector.uninstallLibraries(newInstalledPackages) + if err != nil { + g.logger.Warn("Error uninstalling libraries", zap.Error(err)) + } + // Mark test as ineffective + g.noCoverageTest++ + *noCoverageTest++ + return false, nil + } + + if g.flakiness { + // Run the Test Five Times to Check for Flakiness + g.logger.Info("Coverage increased. Running additional test iterations to check for flakiness.") + for i := 0; i < 5; i++ { + g.logger.Info(fmt.Sprintf("Flakiness Check - Iteration no: %d", i+1)) + stdout, stderr, exitCode, _, _ := RunCommand(g.cmd, g.dir, g.logger) + if exitCode != 0 { + g.logger.Info(fmt.Sprintf("Flaky test detected on iteration %d: %s", i+1, stderr)) + // Revert test file to original content + if err := os.WriteFile(g.testPath, []byte(originalContent), 0644); err != nil { + return false, fmt.Errorf("failed to revert test file: %w", err) + } + // Uninstall any installed libraries + err = g.injector.uninstallLibraries(newInstalledPackages) + if err != nil { + g.logger.Warn("Error uninstalling libraries", zap.Error(err)) + } + g.failedTests = append(g.failedTests, &models.FailedUT{ + TestCode: generatedTest.TestCode, + ErrorMsg: extractErrorMessage(stdout, stderr, g.lang), + NewImportsCode: generatedTest.NewImportsCode, + LibraryInstallationCode: generatedTest.LibraryInstallationCode, + }) + g.testCaseFailed++ + *failedBuild++ + return false, nil + } + } + } + g.testCasePassed++ + *passedTests++ + g.cov.Current = coverageResult.Coverage + g.logger.Info("Generated test passed and increased coverage") + g.cur.Line = g.cur.Line + len(testCodeLines) + importLen + return true, nil +} + +func (g *UnitTestGenerator) saveFailedTestCasesToFile() error { + dir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error getting current directory: %w", err) + } + + newFilePath := filepath.Join(dir, discardedTestsFilename) + + fileHandle, err := os.OpenFile(newFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("error opening discarded tests file: %w", err) + } + + defer func() { + err := fileHandle.Close() + if err != nil { + g.logger.Error("Error closing file handle", zap.Error(err)) + } + }() + + var builder strings.Builder + + // Writing Test Cases To File + for _, failedTest := range g.failedTests { + builder.WriteString("\n" + strings.Repeat("-", 20) + "Test Case" + strings.Repeat("-", 20) + "\n") + if len(failedTest.NewImportsCode) > 0 { + builder.WriteString(fmt.Sprintf("Import Statements:\n%s\n", failedTest.NewImportsCode)) + } + if len(failedTest.LibraryInstallationCode) > 0 { + builder.WriteString(fmt.Sprintf("Required Library Installation\n%s\n", failedTest.LibraryInstallationCode)) + } + builder.WriteString(fmt.Sprintf("Test Implementation:\n%s\n\n", failedTest.TestCode)) + if len(failedTest.ErrorMsg) > 0 { + builder.WriteString(fmt.Sprintf("Error Message:\n%s\n", failedTest.ErrorMsg)) + } + builder.WriteString(strings.Repeat("-", 49) + "\n") + } + + _, err = fileHandle.WriteString(fmt.Sprintf("%s\n", builder.String())) + if err != nil { + return fmt.Errorf("error writing to discarded tests file: %w", err) + } + return nil +} diff --git a/keploy/pkg/service/utgen/injector.go b/keploy/pkg/service/utgen/injector.go new file mode 100644 index 0000000..4019ebd --- /dev/null +++ b/keploy/pkg/service/utgen/injector.go @@ -0,0 +1,830 @@ +package utgen + +import ( + "bufio" + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/fs" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "go.uber.org/zap" +) + +type Injector struct { + logger *zap.Logger + language string +} + +func NewInjectorBuilder(logger *zap.Logger, language string) *Injector { + injectBuilder := &Injector{ + logger: logger, + language: language, + } + + return injectBuilder +} + +func (i *Injector) libraryInstalled() ([]string, error) { + switch strings.ToLower(i.language) { + case "go": + out, err := exec.Command("go", "list", "-m", "all").Output() + if err != nil { + return nil, fmt.Errorf("failed to get Go dependencies: %w", err) + } + return i.extractGoPackageNames(out), nil + + case "java": + out, err := exec.Command("mvn", "dependency:list", "-DincludeScope=compile", "-Dstyle.color=never", "-B").Output() + if err != nil { + return nil, fmt.Errorf("failed to get Java dependencies: %w", err) + } + return i.extractJavaDependencies(out), nil + + case "python": + out, err := exec.Command("pip", "freeze").Output() + if err != nil { + i.logger.Info("Error getting Python dependencies with `pip` command, trying `pip3` command") + out, err = exec.Command("pip3", "freeze").Output() + if err != nil { + return nil, fmt.Errorf("failed to get Python dependencies: %w", err) + } + } + return i.extractPackageNames(out), nil + + case "typescript", "javascript": + cmd := exec.Command("sh", "-c", "npm list --depth=0 --parseable | sed 's|.*/||'") + out, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("failed to get JavaScript/TypeScript dependencies: %w", err) + } + return extractString(out), nil + + default: + return nil, fmt.Errorf("unsupported language: %s", i.language) + } +} + +func (i *Injector) extractGoPackageNames(output []byte) []string { + lines := strings.Split(string(output), "\n") + var packages []string + for _, line := range lines { + if len(line) > 0 { + parts := strings.Split(line, " ") + if len(parts) > 0 { + packages = append(packages, parts[0]) + } + } + } + return packages +} + +func (i *Injector) extractPackageNames(output []byte) []string { + lines := strings.Split(string(output), "\n") + var packages []string + for _, line := range lines { + parts := strings.Split(line, "==") + if len(parts) > 0 { + packages = append(packages, parts[0]) + } + } + return packages +} + +func (i *Injector) installLibraries(libraryCommands string, installedPackages []string) ([]string, error) { + var newInstalledPackages []string + libraryCommands = strings.TrimSpace(libraryCommands) + if libraryCommands == "" || libraryCommands == "\"\"" { + return newInstalledPackages, nil + } + + commands := strings.Split(libraryCommands, "\n") + for _, command := range commands { + command = strings.ReplaceAll(command, "-", "") + packageName := i.extractPackageName(command) + if isStringInarray(installedPackages, packageName) { + continue + } + _, _, exitCode, _, err := RunCommand(command, "", i.logger) + if exitCode != 0 || err != nil { + return newInstalledPackages, fmt.Errorf("failed to install library: %s", command) + } + installedPackages = append(installedPackages, packageName) + newInstalledPackages = append(newInstalledPackages, packageName) + } + return newInstalledPackages, nil +} + +func (i *Injector) extractPackageName(command string) string { + fields := strings.Fields(command) + if len(fields) < 3 { + return "" + } + return fields[2] +} + +func (i *Injector) uninstallLibraries(installedPackages []string) error { + for _, command := range installedPackages { + i.logger.Info(fmt.Sprintf("Uninstalling library: %s", command)) + + var uninstallCommand string + switch strings.ToLower(i.language) { + case "go": + uninstallCommand = fmt.Sprintf("go mod edit -droprequire %s && go mod tidy", command) + case "python": + uninstallCommand = fmt.Sprintf("pip uninstall -y %s", command) + case "javascript": + uninstallCommand = fmt.Sprintf("npm uninstall %s", command) + case "java": + uninstallCommand = fmt.Sprintf("mvn dependency:purge-local-repository -DreResolve=false -Dinclude=%s", command) + } + if uninstallCommand != "" { + i.logger.Info(fmt.Sprintf("Uninstalling library with command: %s", uninstallCommand)) + _, _, exitCode, _, err := RunCommand(uninstallCommand, "", i.logger) + if exitCode != 0 || err != nil { + i.logger.Warn(fmt.Sprintf("Failed to uninstall library: %s", uninstallCommand), zap.Error(err)) + } + } + } + return nil +} + +func (i *Injector) updateJavaScriptImports(importedContent string, newImports []string) (string, int, error) { + importRegex := regexp.MustCompile(`(?m)^\s*(import\s+.*?from\s+['"].*?['"];?|const\s+.*?=\s+require\(['"].*?['"]\);?)`) + existingImportsSet := make(map[string]bool) + sanitisedImports := []string{} + existingImports := importRegex.FindAllString(importedContent, -1) + for _, imp := range existingImports { + imp = strings.TrimSpace(imp) + cleanedImport := strings.ReplaceAll(imp, " ", "") + if cleanedImport != "" && !existingImportsSet[cleanedImport] { + existingImportsSet[cleanedImport] = true + sanitisedImports = append(sanitisedImports, imp) + } + } + + for _, imp := range newImports { + imp = strings.Trim(imp, `"- `) + cleanedImport := strings.ReplaceAll(imp, " ", "") + if importRegex.MatchString(imp) && !existingImportsSet[cleanedImport] { + existingImportsSet[cleanedImport] = true + sanitisedImports = append(sanitisedImports, imp) + } + } + updatedImports := strings.Join(sanitisedImports, "\n") + "\n\n" + + contentWithoutImports := importRegex.ReplaceAllString(importedContent, "") + contentWithoutImports = strings.TrimLeft(contentWithoutImports, "\n") + + updatedContent := updatedImports + "\n" + contentWithoutImports + + originalLines := strings.Split(importedContent, "\n") + updatedLines := strings.Split(updatedContent, "\n") + importLength := len(updatedLines) - len(originalLines) + + if importLength < 0 { + importLength = 0 + } + + return updatedContent, importLength, nil +} + +func (i *Injector) updateImports(filePath string, imports string) (int, error) { + importLines := strings.Split(imports, "\n") + var newImports []string + + for _, imp := range importLines { + trimmedImp := strings.TrimSpace(imp) + if strings.Contains(trimmedImp, "No new imports") || strings.Contains(trimmedImp, "None") { + continue + } + newImports = append(newImports, trimmedImp) + } + contentBytes, err := os.ReadFile(filePath) + if err != nil { + return 0, err + } + content := string(contentBytes) + + var updatedContent string + var importLength int + switch strings.ToLower(i.language) { + case "go": + updatedContent, importLength, err = i.updateGoImports(content, newImports) + case "java": + updatedContent, importLength, err = i.updateJavaImports(content, newImports) + case "python": + updatedContent, err = i.updatePythonImports(content, newImports) + case "typescript": + updatedContent, importLength, err = i.updateTypeScriptImports(content, newImports) + case "javascript": + updatedContent, importLength, err = i.updateJavaScriptImports(content, newImports) + default: + return 0, fmt.Errorf("unsupported language: %s", i.language) + } + if err != nil { + return 0, err + } + err = os.WriteFile(filePath, []byte(updatedContent), fs.ModePerm) + + if err != nil { + return 0, err + } + + return importLength, nil +} + +func (i *Injector) updateGoImports(codeBlock string, newImports []string) (string, int, error) { + importRegex := regexp.MustCompile(`(?ms)import\s*(\([\s\S]*?\)|"[^"]+")`) + existingImportsSet := make(map[string]bool) + matches := importRegex.FindStringSubmatch(codeBlock) + if matches != nil { + importBlock := matches[0] + importLines := strings.Split(importBlock, "\n") + allImports := []string{} + existingImports := i.extractGoImports(importLines, true) + for _, imp := range existingImports { + trimmedImp := strings.TrimSpace(imp) + if trimmedImp != "" { + existingImportsSet[trimmedImp] = true + } + allImports = append(allImports, imp) + } + newImports = i.extractGoImports(newImports, false) + for _, importStatement := range newImports { + importStatement = strings.TrimSpace(importStatement) + if !existingImportsSet[importStatement] { + existingImportsSet[importStatement] = true + allImports = append(allImports, importStatement) + } + } + importBlockNew := i.createGoImportBlock(allImports) + updatedContent := importRegex.ReplaceAllString(codeBlock, importBlockNew) + return updatedContent, len(strings.Split(importBlockNew, "\n")) - len(importLines), nil + } + packageRegex := regexp.MustCompile(`package\s+\w+`) + + pkgMatch := packageRegex.FindStringIndex(codeBlock) + if pkgMatch == nil { + return "", 0, fmt.Errorf("could not find package declaration") + } + newImports = i.extractGoImports(newImports, false) + importBlock := i.createGoImportBlock(newImports) + insertPos := pkgMatch[1] + updatedContent := codeBlock[:insertPos] + "\n\n" + importBlock + "\n" + codeBlock[insertPos:] + return updatedContent, len(strings.Split(importBlock, "\n")) + 1, nil + +} + +func (i *Injector) extractGoImports(importLines []string, ignoreSpace bool) []string { + imports := []string{} + for _, line := range importLines { + line = strings.TrimSpace(line) + if line == "import (" || line == ")" { + continue + } + if line == "" { + if ignoreSpace { + imports = append(imports, "") + } + continue + } + line = strings.TrimPrefix(line, "import ") + line = strings.Trim(line, `"`) + imports = append(imports, line) + } + return imports +} + +func (i *Injector) createGoImportBlock(imports []string) string { + importBlock := "import (\n" + for _, importLine := range imports { + importLine = strings.TrimSpace(importLine) + if importLine == "" { + importBlock += "\n" + continue + } + importLine = strings.Trim(importLine, `"`) + importBlock += fmt.Sprintf(` "%s"`+"\n", importLine) + } + importBlock += ")" + return importBlock +} + +func (i *Injector) updateJavaImports(codeContent string, newImports []string) (string, int, error) { + importRegex := regexp.MustCompile(`(?m)^import\s+.*?;`) + existingImportsSet := make(map[string]bool) + existingImportMatches := importRegex.FindAllStringIndex(codeContent, -1) + + for _, match := range existingImportMatches { + imp := codeContent[match[0]:match[1]] + existingImportsSet[imp] = true + } + + importsToAdd := []string{} + for _, importStatement := range newImports { + importStatement = strings.ReplaceAll(importStatement, "-", "") + importStatement = strings.TrimSpace(importStatement) + importStatement = strings.Trim(importStatement, "\"") + if importRegex.MatchString(importStatement) && !existingImportsSet[importStatement] { + existingImportsSet[importStatement] = true + importsToAdd = append(importsToAdd, importStatement) + } + } + if len(importsToAdd) > 0 { + insertPos := 0 + if len(existingImportMatches) > 0 { + lastImportMatch := existingImportMatches[len(existingImportMatches)-1] + insertPos = lastImportMatch[1] // position after last existing import + } else { + packageRegex := regexp.MustCompile(`(?m)^package\s+.*?;`) + pkgMatch := packageRegex.FindStringIndex(codeContent) + if pkgMatch != nil { + insertPos = pkgMatch[1] + } else { + insertPos = 0 + } + } + + importedContent := "\n" + strings.Join(importsToAdd, "\n") + "\n" + + updatedContent := codeContent[:insertPos] + importedContent + codeContent[insertPos:] + + return updatedContent, len(importsToAdd), nil + } + return codeContent, 0, nil + +} + +func (i *Injector) updatePythonImports(content string, newImports []string) (string, error) { + scanner := bufio.NewScanner(strings.NewReader(content)) + existingImportsMap := make(map[string]map[string]bool) + codeLines := []string{} + importLines := []string{} + + ignoredPrefixes := "# checking coverage for file - do not remove" + + for scanner.Scan() { + line := scanner.Text() + trimmedLine := strings.TrimSpace(line) + + if trimmedLine == "" { + continue + } + shouldIgnore := (strings.HasPrefix(trimmedLine, "import ") || strings.HasPrefix(trimmedLine, "from ")) && strings.Contains(trimmedLine, ignoredPrefixes) + if shouldIgnore { + parts := strings.Split(trimmedLine, "#") + coreImport := strings.TrimSpace(parts[0]) + + if strings.HasPrefix(coreImport, "from ") { + fields := strings.Fields(coreImport) + moduleName := fields[1] + importPart := coreImport[strings.Index(coreImport, "import")+len("import "):] + importedItems := strings.Split(importPart, ",") + + if _, exists := existingImportsMap[moduleName]; !exists { + existingImportsMap[moduleName] = make(map[string]bool) + } + for _, item := range importedItems { + cleanedItem := strings.TrimSpace(item) + if cleanedItem != "" { + existingImportsMap[moduleName][cleanedItem] = true + } + } + } + codeLines = append(codeLines, line) + continue + } + + if strings.HasPrefix(trimmedLine, "import ") || strings.HasPrefix(trimmedLine, "from ") { + codeLines = append(codeLines, line) + } else { + codeLines = append(codeLines, line) + } + } + + for _, imp := range newImports { + imp = strings.TrimSpace(imp) + if imp == "\"\"" || len(imp) == 0 { + continue + } + if strings.HasPrefix(imp, "from ") { + fields := strings.Fields(imp) + moduleName := fields[1] + importIndex := -1 + for i, field := range fields { + if field == "import" { + importIndex = i + break + } + } + if importIndex == -1 { + continue + } + importPart := strings.Join(fields[importIndex+1:], " ") + importedItems := strings.Split(importPart, ",") + if _, exists := existingImportsMap[moduleName]; !exists { + existingImportsMap[moduleName] = make(map[string]bool) + } + for _, item := range importedItems { + cleanedItem := strings.TrimSpace(item) + if cleanedItem != "" { + existingImportsMap[moduleName][strings.TrimSpace(item)] = true + } + } + } else if strings.HasPrefix(imp, "import ") { + fields := strings.Fields(imp) + moduleName := fields[1] + if _, exists := existingImportsMap[moduleName]; !exists { + existingImportsMap[moduleName] = make(map[string]bool) + } + } + } + for i, line := range codeLines { + trimmedLine := strings.TrimSpace(line) + + if strings.HasPrefix(trimmedLine, "from ") { + fields := strings.Fields(trimmedLine) + moduleName := fields[1] + + if itemsMap, exists := existingImportsMap[moduleName]; exists && len(itemsMap) > 0 { + items := mapKeysToSortedSlice(itemsMap) + importLine := fmt.Sprintf("from %s import %s", moduleName, strings.Join(items, ", ")) + + if strings.Contains(trimmedLine, ignoredPrefixes) { + importLine += " " + ignoredPrefixes + } + codeLines[i] = importLine + delete(existingImportsMap, moduleName) + } + } + } + + for module, itemsMap := range existingImportsMap { + if len(itemsMap) > 0 { + items := mapKeysToSortedSlice(itemsMap) + importLine := fmt.Sprintf("from %s import %s", module, strings.Join(items, ", ")) + importLine += " " + ignoredPrefixes + importLines = append(importLines, importLine) + } + } + nonEmptyCodeLines := []string{} + for _, line := range codeLines { + if strings.TrimSpace(line) != "" { + nonEmptyCodeLines = append(nonEmptyCodeLines, line) + } + } + + nonEmptyImportLines := []string{} + for _, line := range importLines { + if strings.TrimSpace(line) != "" { + nonEmptyImportLines = append(nonEmptyImportLines, line) + } + } + + updatedContent := strings.Join(nonEmptyImportLines, "\n") + "\n" + strings.Join(nonEmptyCodeLines, "\n") + return updatedContent, nil +} + +func (i *Injector) updateTypeScriptImports(importedContent string, newImports []string) (string, int, error) { + importRegex := regexp.MustCompile(`(?m)^import\s+.*?;`) + existingImportsSet := make(map[string]bool) + sanitisedImports := []string{} + existingImports := importRegex.FindAllString(importedContent, -1) + for _, imp := range existingImports { + imp = strings.TrimSpace(imp) + cleanedImport := strings.ReplaceAll(imp, " ", "") + if cleanedImport != "" && !existingImportsSet[cleanedImport] { + existingImportsSet[cleanedImport] = true + sanitisedImports = append(sanitisedImports, imp) + } + } + + for _, imp := range newImports { + imp = strings.Trim(imp, `"- `) + cleanedImport := strings.ReplaceAll(imp, " ", "") + if importRegex.MatchString(imp) && !existingImportsSet[cleanedImport] { + existingImportsSet[cleanedImport] = true + sanitisedImports = append(sanitisedImports, imp) + } + } + updatedImports := strings.Join(sanitisedImports, "\n") + "\n\n" + + contentWithoutImports := importRegex.ReplaceAllString(importedContent, "") + contentWithoutImports = strings.TrimLeft(contentWithoutImports, "\n") + + updatedContent := updatedImports + "\n" + contentWithoutImports + + originalLines := strings.Split(importedContent, "\n") + updatedLines := strings.Split(updatedContent, "\n") + importLength := len(updatedLines) - len(originalLines) + + if importLength < 0 { + importLength = 0 + } + return updatedContent, importLength, nil +} + +func (i *Injector) extractJavaDependencies(output []byte) []string { + lines := strings.Split(string(output), "\n") + var dependencies []string + inDependencySection := false + + depRegex := regexp.MustCompile(`^\[INFO\]\s+[\+\|\\\-]{1,2}\s+([\w\.\-]+:[\w\.\-]+):jar:([\w\.\-]+):([\w\.\-]+)`) + + for _, line := range lines { + cleanedLine := strings.TrimSpace(line) + if strings.HasPrefix(cleanedLine, "[INFO]") { + cleanedLine = "[INFO]" + strings.TrimSpace(cleanedLine[6:]) + } + if strings.Contains(cleanedLine, "maven-dependency-plugin") && strings.Contains(cleanedLine, ":list") { + inDependencySection = true + continue + } + + if inDependencySection && (strings.Contains(cleanedLine, "BUILD SUCCESS") || strings.Contains(cleanedLine, "---")) { + inDependencySection = false + continue + } + + if inDependencySection && strings.HasPrefix(cleanedLine, "[INFO]") { + matches := depRegex.FindStringSubmatch(cleanedLine) + if len(matches) >= 4 { + groupArtifact := matches[1] + version := matches[2] + dep := fmt.Sprintf("%s:%s", groupArtifact, version) + dependencies = append(dependencies, dep) + } else { + cleanedLine = strings.TrimPrefix(cleanedLine, "[INFO]") + cleanedLine = strings.TrimSpace(cleanedLine) + + cleanedLine = strings.TrimPrefix(cleanedLine, "+-") + cleanedLine = strings.TrimPrefix(cleanedLine, "\\-") + cleanedLine = strings.TrimPrefix(cleanedLine, "|") + + cleanedLine = strings.TrimSpace(cleanedLine) + + depParts := strings.Split(cleanedLine, ":") + if len(depParts) >= 5 { + dep := fmt.Sprintf("%s:%s:%s", depParts[0], depParts[1], depParts[3]) + dependencies = append(dependencies, dep) + } + } + } + } + return dependencies +} + +func (i *Injector) addCommentToTest(testCode string) string { + comment := " Test generated using Keploy" + switch i.language { + case "python": + comment = "#" + comment + case "go", "javascript", "typescript", "java": + comment = "//" + comment + } + return fmt.Sprintf("%s\n%s", comment, testCode) +} + +func (i *Injector) getModelDetails(sourceFilePath string) string { + switch i.language { + case "go": + return i.getGoImportData(sourceFilePath) + default: + return "" + } +} + +func (i *Injector) getGoImportData(sourceFilePath string) string { + builtInTypes := map[string]struct{}{ + "string": {}, + "int": {}, + "float64": {}, + "bool": {}, + "error": {}, + "byte": {}, + "rune": {}, + "uint": {}, + "int64": {}, + "uint64": {}, + "complex64": {}, + "complex128": {}, + "float32": {}, + "int32": {}, + } + + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, sourceFilePath, nil, parser.AllErrors) + if err != nil { + return "" + } + + imports := make(map[string]string) + for _, imp := range node.Imports { + pkgPath := strings.Trim(imp.Path.Value, "\"") + var alias string + + if imp.Name != nil { + if imp.Name.Name == "_" || imp.Name.Name == "." { + continue + } + alias = imp.Name.Name + } else { + parts := strings.Split(pkgPath, "/") + alias = parts[len(parts)-1] + } + + imports[alias] = pkgPath + } + // Set to store unique structs with their package paths + structSet := make(map[string]struct{}) + funcSet := make(map[string]struct{}) + + var collectStructs func(expr ast.Expr) + collectStructs = func(expr ast.Expr) { + switch t := expr.(type) { + case *ast.Ident: + structName := t.Name + if _, isBuiltIn := builtInTypes[structName]; isBuiltIn { + return + } + structKey := fmt.Sprintf("%s.%s", node.Name.Name, structName) + structSet[structKey] = struct{}{} + + case *ast.SelectorExpr: + if ident, ok := t.X.(*ast.Ident); ok { + pkgAlias := ident.Name + structName := t.Sel.Name + if pkgPath, exists := imports[pkgAlias]; exists { + structKey := fmt.Sprintf("%s.%s", pkgPath, structName) + structSet[structKey] = struct{}{} + } else { + structKey := fmt.Sprintf("%s.%s", pkgAlias, structName) + structSet[structKey] = struct{}{} + } + } + + case *ast.StarExpr: + collectStructs(t.X) + + case *ast.ArrayType: + collectStructs(t.Elt) + + case *ast.MapType: + collectStructs(t.Key) + collectStructs(t.Value) + + case *ast.StructType: + packageName := node.Name.Name + structSet[fmt.Sprintf("%s.", packageName)] = struct{}{} + } + } + + // Traverse the AST to collect structs + ast.Inspect(node, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.TypeSpec: + if _, ok := x.Type.(*ast.StructType); ok { + structName := x.Name.Name + packageName := node.Name.Name + structKey := fmt.Sprintf("%s.%s", packageName, structName) + structSet[structKey] = struct{}{} + } + + case *ast.CompositeLit: + collectStructs(x.Type) + + case *ast.ValueSpec: + if x.Type != nil { + collectStructs(x.Type) + } + for _, val := range x.Values { + collectStructs(val) + } + + case *ast.Field: + collectStructs(x.Type) + + case *ast.FuncDecl: + if x.Type.Params != nil { + for _, field := range x.Type.Params.List { + collectStructs(field.Type) + } + } + if x.Type.Results != nil { + for _, field := range x.Type.Results.List { + collectStructs(field.Type) + } + } + case *ast.CallExpr: + if sel, ok := x.Fun.(*ast.SelectorExpr); ok { + if ident, ok := sel.X.(*ast.Ident); ok { + pkgAlias := ident.Name + funcName := sel.Sel.Name + if pkgPath, exists := imports[pkgAlias]; exists { + // Construct the fully qualified function name + funcKey := fmt.Sprintf("%s.%s", pkgPath, funcName) + funcSet[funcKey] = struct{}{} + } + } + } else if ident, ok := x.Fun.(*ast.Ident); ok { + moduleName, relativePath := i.GetModuleName(sourceFilePath) + packageName, _ := GetPackageName(sourceFilePath) + + if packageName != "main" { + relativePath = TrimParentFolder(relativePath) + } + var funcKey string + // Construct the function key conditionally to handle empty relativePath + if packageName == "main" { + // If the package is `main`, use the module name without extra path details + funcKey = fmt.Sprintf("%s/%s.%s", moduleName, relativePath, ident.Name) + } else { + if relativePath == "" { + funcKey = fmt.Sprintf("%s/%s.%s", moduleName, packageName, ident.Name) + } else { + funcKey = fmt.Sprintf("%s/%s/%s.%s", moduleName, relativePath, packageName, ident.Name) + } + } + funcSet[funcKey] = struct{}{} + } + + default: + } + return true + }) + + data := "" + for structKey := range structSet { + var out bytes.Buffer + cmd := exec.Command("go", "doc", structKey) + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + continue + } + data += (out.String()) + "\n" + } + for funcKey := range funcSet { + var out bytes.Buffer + cmd := exec.Command("go", "doc", funcKey) + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + continue + } + data += (out.String()) + "\n" + } + return data +} + +func (i *Injector) GetModuleName(sourceFilePath string) (string, string) { + file, err := os.Open("go.mod") + if err != nil { + return "", "" + } + defer func() { + if err := file.Close(); err != nil { + i.logger.Error("Error closing file", zap.Error(err)) + } + }() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "module ") { + curDir, _ := os.Getwd() + dirPath := filepath.Dir(sourceFilePath) + + relativePath, _ := filepath.Rel(curDir, dirPath) + + if relativePath == "." { + return strings.TrimSpace(strings.TrimPrefix(line, "module ")), "" + } + + return strings.TrimSpace(strings.TrimPrefix(line, "module ")), relativePath + } + } + + return "", "" +} + +func GetPackageName(sourceFilePath string) (string, error) { + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, sourceFilePath, nil, parser.PackageClauseOnly) + if err != nil { + return "", err + } + return node.Name.Name, nil +} + +func TrimParentFolder(path string) string { + parts := strings.Split(path, string(filepath.Separator)) + if len(parts) > 1 { + return filepath.Join(parts[:len(parts)-1]...) // Exclude the last part (file name's parent directory) + } + return path +} diff --git a/keploy/pkg/service/utgen/prompt.go b/keploy/pkg/service/utgen/prompt.go new file mode 100644 index 0000000..bb4749d --- /dev/null +++ b/keploy/pkg/service/utgen/prompt.go @@ -0,0 +1,207 @@ +package utgen + +import ( + "bytes" + "fmt" + "html" + "html/template" + "os" + "strings" + + settings "go.keploy.io/server/v2/pkg/service/utgen/assets" + "go.uber.org/zap" +) + +const MAX_TESTS_PER_RUN = 6 + +const ADDITIONAL_INCLUDES_TEXT = ` +## Additional Includes +The following is a set of included files used as context for the source code above. This is usually included libraries needed as context to write better tests: +====== +{{.IncludedFiles}} +====== +` + +const ADDITIONAL_INSTRUCTIONS_TEXT = ` +## Additional Instructions +====== +{{.AdditionalInstructions}} +====== +` + +const FAILED_TESTS_TEXT = ` +## Previous Iterations Failed Tests +Below is a list of failed tests that you generated in previous iterations. Do not generate the same tests again, and take the failed tests into account when generating new tests. +====== +{{.FailedTestRuns}} +====== +` + +type Source struct { + Name string + Code string + CodeNumbered string +} + +type Test struct { + Name string + Code string + CodeNumbered string +} + +type PromptBuilder struct { + Src *Source + Test *Test + CovReportContent string + IncludedFiles string + AdditionalInstructions string + Language string + Logger *zap.Logger + AdditionalPrompt string + InstalledPackages []string + FunctionUnderTest string + ImportDetails string + ModuleName string +} + +func NewPromptBuilder(srcPath, testPath, covReportContent, includedFiles, additionalInstructions, language, additionalPrompt, functionUnderTest string, logger *zap.Logger) (*PromptBuilder, error) { + var err error + src := &Source{ + Name: srcPath, + } + test := &Test{ + Name: testPath, + } + promptBuilder := &PromptBuilder{ + Src: src, + Test: test, + Language: language, + CovReportContent: covReportContent, + Logger: logger, + AdditionalPrompt: additionalPrompt, + FunctionUnderTest: functionUnderTest, + } + promptBuilder.Src.Code, err = readFile(srcPath) + if err != nil { + return nil, err + } + promptBuilder.Test.Code, err = readFile(testPath) + if err != nil { + return nil, err + } + promptBuilder.IncludedFiles, err = formatSection(includedFiles, ADDITIONAL_INCLUDES_TEXT) + if err != nil { + return nil, err + } + promptBuilder.AdditionalInstructions, err = formatSection(additionalInstructions, ADDITIONAL_INSTRUCTIONS_TEXT) + if err != nil { + return nil, err + } + return promptBuilder, nil +} + +func readFile(filePath string) (string, error) { + content, err := os.ReadFile(filePath) + if err != nil { + return "", fmt.Errorf("error reading %s: %v", filePath, err) + } + return string(content), nil +} + +func formatSection(content, templateText string) (string, error) { + if content == "" { + return "", nil + } + tmpl, err := template.New("section").Parse(templateText) + if err != nil { + return "", fmt.Errorf("Error parsing section template: %v", err) + } + var buffer bytes.Buffer + err = tmpl.Execute(&buffer, map[string]string{ + "IncludedFiles": content, + "AdditionalInstructions": content, + "FailedTestRuns": content, + }) + if err != nil { + return "", fmt.Errorf("Error executing section template: %v", err) + } + return buffer.String(), nil +} + +func (pb *PromptBuilder) BuildPrompt(file, failedTestRuns string) (*Prompt, error) { + pb.Src.CodeNumbered = numberLines(pb.Src.Code) + pb.Test.CodeNumbered = numberLines(pb.Test.Code) + variables := map[string]interface{}{ + "source_file_name": pb.Src.Name, + "test_file_name": pb.Test.Name, + "source_file_numbered": pb.Src.CodeNumbered, + "test_file_numbered": pb.Test.CodeNumbered, + "source_file": pb.Src.Code, + "test_file": pb.Test.Code, + "code_coverage_report": pb.CovReportContent, + "additional_includes_section": pb.IncludedFiles, + "failed_tests_section": failedTestRuns, + "additional_instructions_text": pb.AdditionalInstructions, + "language": pb.Language, + "max_tests": MAX_TESTS_PER_RUN, + "additional_command": pb.AdditionalPrompt, + "function_under_test": pb.FunctionUnderTest, + "installed_packages": formatInstalledPackages(pb.InstalledPackages), + "import_details": pb.ImportDetails, + "module_name": pb.ModuleName, + } + + settings := settings.GetSettings() + + prompt := &Prompt{} + + systemPrompt, err := renderTemplate(settings.GetString(file+".system"), variables) + if err != nil { + prompt.System = "" + prompt.User = "" + return prompt, fmt.Errorf("Error rendering system prompt: %v", err) + } + prompt.System = systemPrompt + + userPrompt, err := renderTemplate(settings.GetString(file+".user"), variables) + if err != nil { + prompt.System = "" + prompt.User = "" + return prompt, fmt.Errorf("Error rendering user prompt: %v", err) + } + userPrompt = html.UnescapeString(userPrompt) + prompt.User = userPrompt + return prompt, nil +} + +func formatInstalledPackages(packages []string) string { + var sb strings.Builder + for _, pkg := range packages { + sb.WriteString(fmt.Sprintf("- %s\n", pkg)) + } + return sb.String() +} + +func renderTemplate(templateText string, variables map[string]interface{}) (string, error) { + funcMap := template.FuncMap{ + "trim": strings.TrimSpace, + } + tmpl, err := template.New("prompt").Funcs(funcMap).Parse(templateText) + if err != nil { + return "", err + } + var buffer bytes.Buffer + err = tmpl.Execute(&buffer, variables) + if err != nil { + return "", err + } + return buffer.String(), nil +} + +func numberLines(text string) string { + lines := strings.Split(text, "\n") + for i, line := range lines { + lines[i] = fmt.Sprintf("%d %s", i+1, line) + } + return strings.Join(lines, "\n") +} diff --git a/keploy/pkg/service/utgen/service.go b/keploy/pkg/service/utgen/service.go new file mode 100644 index 0000000..2f99a72 --- /dev/null +++ b/keploy/pkg/service/utgen/service.go @@ -0,0 +1,13 @@ +package utgen + +import ( + "context" +) + +type Service interface { + Start(ctx context.Context) error +} + +type Telemetry interface { + GenerateUT() +} diff --git a/keploy/pkg/service/utgen/utils.go b/keploy/pkg/service/utgen/utils.go new file mode 100644 index 0000000..3d2dc88 --- /dev/null +++ b/keploy/pkg/service/utgen/utils.go @@ -0,0 +1,330 @@ +package utgen + +import ( + "bufio" + "bytes" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "sort" + "strconv" + "strings" + "time" + + "go.keploy.io/server/v2/pkg/models" + settings "go.keploy.io/server/v2/pkg/service/utgen/assets" + "go.uber.org/zap" + + "gopkg.in/yaml.v2" +) + +func GetCodeLanguage(sourceFilePath string) string { + // Retrieve the mapping of languages to their file extensions from settings + // Create a map to hold the language extensions + languageExtensionMapOrg := make(map[string][]string) + + setting := settings.GetSettings() + + // Unmarshal the language_extension_map_org section into the map + if err := setting.UnmarshalKey("language_extension_map_org", &languageExtensionMapOrg); err != nil { + log.Fatalf("Error unmarshaling language extension map, %s", err) + } + + // Initialize a dictionary to map file extensions to their corresponding languages + extensionToLanguage := make(map[string]string) + + // Populate the extensionToLanguage dictionary + for language, extensions := range languageExtensionMapOrg { + for _, ext := range extensions { + extensionToLanguage[ext] = language + } + } + + // Extract the file extension from the source file path + parts := strings.Split(sourceFilePath, ".") + extensionS := "." + parts[len(parts)-1] + // Initialize the default language name as 'unknown' + languageName := "unknown" + + // Check if the extracted file extension is in the dictionary + if val, ok := extensionToLanguage[extensionS]; ok { + languageName = val + } + + // Return the language name in lowercase + return strings.ToLower(languageName) +} + +func unmarshalYamlTestDetails(yamlStr string) (*models.UTDetails, error) { + yamlStr = strings.TrimSpace(yamlStr) + yamlStr = strings.TrimPrefix(yamlStr, "```yaml") + yamlStr = strings.TrimSuffix(yamlStr, "```") + var data *models.UTDetails + err := yaml.Unmarshal([]byte(yamlStr), &data) + if err != nil { + return nil, fmt.Errorf("error unmarshaling yaml: %s", err) + } + return data, nil +} + +func unmarshalYamlTestHeaders(yamlStr string) (*models.UTIndentationInfo, error) { + yamlStr = strings.TrimSpace(yamlStr) + yamlStr = strings.TrimPrefix(yamlStr, "```yaml") + yamlStr = strings.TrimSuffix(yamlStr, "```") + + var data *models.UTIndentationInfo + err := yaml.Unmarshal([]byte(yamlStr), &data) + if err != nil { + return nil, fmt.Errorf("error unmarshaling yaml: %s", err) + } + return data, nil +} + +func unmarshalYamlTestLine(yamlStr string) (*models.UTInsertionInfo, error) { + yamlStr = strings.TrimSpace(yamlStr) + yamlStr = strings.TrimPrefix(yamlStr, "```yaml") + yamlStr = strings.TrimSuffix(yamlStr, "```") + var data *models.UTInsertionInfo + err := yaml.Unmarshal([]byte(yamlStr), &data) + if err != nil { + return nil, fmt.Errorf("error unmarshaling yaml: %s", err) + } + return data, nil +} + +func convertToInt(value interface{}) (int, error) { + switch v := value.(type) { + case int: + return v, nil + case float64: + return int(v), nil + case string: + return strconv.Atoi(v) + default: + return 0, fmt.Errorf("unsupported type for conversion to int: %T", value) + } +} + +func extractErrorMessage(outputMessage, failMessage, language string) string { + const maxLines = 15 + var pattern string + message := failMessage + switch language { + case "python": + pattern = `^=+ ERRORS =+$` + message = outputMessage + case "java": + pattern = `^\[ERROR\].*` + message = outputMessage + case "go": + pattern = `(?i)(^FAIL|panic:|undefined:)` + case "javascript": + pattern = `(?i)● .*` + } + re := regexp.MustCompile(pattern) + scanner := bufio.NewScanner(strings.NewReader(message)) + var result []string + matching := false + + for scanner.Scan() { + line := scanner.Text() + if re.MatchString(line) { + matching = true + } + if matching { + result = append(result, line) + if len(result) >= maxLines { + break + } + } + } + return strings.Join(result, "\n") +} + +func getFilename(filePath string) string { + return filepath.Base(filePath) +} + +func formatDuration(duration time.Duration) string { + if duration >= time.Minute { + minutes := int(duration.Minutes()) + seconds := duration.Seconds() - float64(minutes*60) + return fmt.Sprintf("%dm%.2fs", minutes, seconds) + } + return fmt.Sprintf("%.2fs", duration.Seconds()) +} + +func extractString(output []byte) []string { + lines := strings.Split(string(output), "\n") + var dependencies []string + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if trimmed != "" { + dependencies = append(dependencies, trimmed) + } + } + return dependencies +} + +func isStringInarray(array []string, text string) bool { + for _, elem := range array { + if strings.EqualFold(elem, text) { + return true + } + } + return false +} + +func mapKeysToSortedSlice(itemsMap map[string]bool) []string { + items := []string{} + for item := range itemsMap { + items = append(items, item) + } + sort.Strings(items) + return items +} + +func RunCommand(command string, cwd string, logger *zap.Logger) (stdout string, stderr string, exitCode int, commandStartTime int64, err error) { + // Get the current time before running the test command, in milliseconds + commandStartTime = time.Now().UnixNano() / int64(time.Millisecond) + + var cmd *exec.Cmd + + if runtime.GOOS == "windows" { + cmdArgs := strings.Fields(command) + cmd = exec.Command(cmdArgs[0], cmdArgs[1:]...) + } else { + // Create the command with the specified working directory + cmd = exec.Command("sh", "-c", command) + if cwd != "" { + cmd.Dir = cwd + } + } + + // Capture the stdout and stderr + var outBuf, errBuf bytes.Buffer + + // Set the output of the command + cmd.Stdout = &outBuf + cmd.Stderr = &errBuf + + if logger.Level() == zap.DebugLevel { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + + // Run the command + err = cmd.Run() + + // Get the exit code + exitCode = cmd.ProcessState.ExitCode() + + // Return the captured output and other information + stdout = outBuf.String() + stderr = errBuf.String() + return stdout, stderr, exitCode, commandStartTime, err +} + +func getTestFilePath(sourceFilePath, testDirectory string) (string, error) { + + language := GetCodeLanguage(sourceFilePath) + + // Extract the base name and extension of the source file + baseName := filepath.Base(sourceFilePath) + extension := filepath.Ext(sourceFilePath) + + // Remove the extension from the base name + baseNameWithoutExt := strings.TrimSuffix(baseName, extension) + + var testFileBaseNames []string + + switch language { + case "go": + testFileBaseNames = []string{baseNameWithoutExt + "_test" + extension} + case "javascript": + testFileBaseNames = []string{baseNameWithoutExt + ".test" + extension} + case "python": + testFileBaseNames = []string{baseNameWithoutExt + "_test" + extension, "test_" + baseName} + default: + return "", fmt.Errorf("unsupported language: %s", language) + } + + // Find the most specific existing test file + testFilePath, err := findTestFile(testDirectory, testFileBaseNames) + if err != nil { + return "", err + } + + // If a test file was found, return it + if testFilePath != "" { + return testFilePath, nil + } + + // Construct the relative path for the new test file + relativeDir := strings.TrimPrefix(filepath.Dir(sourceFilePath), "src") + testFilePath = filepath.Join(testDirectory, relativeDir, testFileBaseNames[0]) + + return testFilePath, nil +} + +func findTestFile(testDirectory string, testFileBaseNames []string) (string, error) { + var bestMatch string + + err := filepath.Walk(testDirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + for _, testFileBaseName := range testFileBaseNames { + if strings.HasSuffix(path, testFileBaseName) { + if bestMatch == "" || len(path) < len(bestMatch) { + bestMatch = path + } + } + } + } + return nil + }) + + if err != nil { + return "", err + } + + return bestMatch, nil +} + +func createTestFile(testFilePath string, sourceFilePath string) (bool, error) { + // Ensure the directory exists + err := os.MkdirAll(filepath.Dir(testFilePath), os.ModePerm) + if err != nil { + return false, err + } + + // Check if the test file exists + if _, err := os.Stat(testFilePath); os.IsNotExist(err) { + // Create the test file if it does not exist + file, err := os.OpenFile(testFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return false, err + } + defer func() { + if err := file.Close(); err != nil { + return + } + }() + + // Write initial content to the test file + _, err = file.WriteString(fmt.Sprintf("// Unit test for %s\n", filepath.Base(sourceFilePath))) + if err != nil { + return false, err + } + + return true, nil + } + + return false, nil +} diff --git a/keploy/pkg/util.go b/keploy/pkg/util.go new file mode 100755 index 0000000..1cf5ead --- /dev/null +++ b/keploy/pkg/util.go @@ -0,0 +1,637 @@ +// Package pkg provides utility functions for Keploy. +package pkg + +import ( + "bufio" + "bytes" + "compress/gzip" + "context" + "encoding/json" + "encoding/xml" + "fmt" + "io" + "io/fs" + "net" + "net/http" + "net/url" + "os" + "sort" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/andybalholm/brotli" + "go.keploy.io/server/v2/pkg/models" + + "go.keploy.io/server/v2/utils" + "go.uber.org/zap" +) + +var Emoji = "\U0001F430" + " Keploy:" + +var SortCounter int64 = -1 + +func InitSortCounter(counter int64) { + atomic.StoreInt64(&SortCounter, counter) +} + +func GetNextSortNum() int64 { + return atomic.AddInt64(&SortCounter, 1) +} + +// URLParams returns the Url and Query parameters from the request url. +func URLParams(r *http.Request) map[string]string { + qp := r.URL.Query() + result := make(map[string]string) + + for key, values := range qp { + result[key] = strings.Join(values, ", ") + } + + return result +} + +// ToYamlHTTPHeader converts the http header into yaml format +func ToYamlHTTPHeader(httpHeader http.Header) map[string]string { + header := map[string]string{} + for i, j := range httpHeader { + header[i] = strings.Join(j, ",") + } + return header +} + +func ToHTTPHeader(mockHeader map[string]string) http.Header { + header := http.Header{} + for i, j := range mockHeader { + match := IsTime(j) + if match { + //Values like "Tue, 17 Jan 2023 16:34:58 IST" should be considered as single element + header[i] = []string{j} + continue + } + header[i] = strings.Split(j, ",") + } + return header +} + +// IsTime verifies whether a given string represents a valid date or not. +func IsTime(stringDate string) bool { + date := strings.TrimSpace(stringDate) + if secondsFloat, err := strconv.ParseFloat(date, 64); err == nil { + seconds := int64(secondsFloat / 1e9) + nanoseconds := int64(secondsFloat) % 1e9 + expectedTime := time.Unix(seconds, nanoseconds) + currentTime := time.Now() + if currentTime.Sub(expectedTime) < 24*time.Hour && currentTime.Sub(expectedTime) > -24*time.Hour { + return true + } + } + for _, dateFormat := range dateFormats { + _, err := time.Parse(dateFormat, date) + if err == nil { + return true + } + } + return false +} + +func SimulateHTTP(ctx context.Context, tc *models.TestCase, testSet string, logger *zap.Logger, apiTimeout uint64) (*models.HTTPResp, error) { + var resp *models.HTTPResp + + //TODO: adjust this logic in the render function in order to remove the redundant code + // convert testcase to string and render the template values. + // Render any template values in the test case before simulation. + if len(utils.TemplatizedValues) > 0 || len(utils.SecretValues) > 0 { + testCaseStr, err := json.Marshal(tc) + if err != nil { + utils.LogError(logger, err, "failed to marshal the testcase for templating") + return nil, err + } + + // Prepare the data for template execution. + templateData := make(map[string]interface{}) + for k, v := range utils.TemplatizedValues { + templateData[k] = v + } + if len(utils.SecretValues) > 0 { + templateData["secret"] = utils.SecretValues + } + + renderedStr, err := utils.RenderTemplatesInString(logger, string(testCaseStr), templateData) + if err != nil { + utils.LogError(logger, err, "failed to render some template values", zap.Any("TestCase", tc.Name), zap.Any("TestSet", testSet)) + } + + // Unmarshal the rendered string back into the test case struct. + err = json.Unmarshal([]byte(renderedStr), &tc) + if err != nil { + utils.LogError(logger, err, "failed to unmarshal the rendered testcase", zap.Any("RenderedString", renderedStr)) + return nil, err + } + } + + reqBody := []byte(tc.HTTPReq.Body) + var err error + + if tc.HTTPReq.Header["Content-Encoding"] != "" { + reqBody, err = Compress(logger, tc.HTTPReq.Header["Content-Encoding"], reqBody) + if err != nil { + utils.LogError(logger, err, "failed to compress the request body") + return nil, err + } + } + + logger.Info("starting test for of", zap.Any("test case", models.HighlightString(tc.Name)), zap.Any("test set", models.HighlightString(testSet))) + req, err := http.NewRequestWithContext(ctx, string(tc.HTTPReq.Method), tc.HTTPReq.URL, bytes.NewBuffer(reqBody)) + if err != nil { + utils.LogError(logger, err, "failed to create a http request from the yaml document") + return nil, err + } + req.Header = ToHTTPHeader(tc.HTTPReq.Header) + req.ProtoMajor = tc.HTTPReq.ProtoMajor + req.ProtoMinor = tc.HTTPReq.ProtoMinor + req.Header.Set("KEPLOY-TEST-ID", tc.Name) + req.Header.Set("KEPLOY-TEST-SET-ID", testSet) + // send if its the last testcase + if tc.IsLast { + req.Header.Set("KEPLOY-LAST-TESTCASE", "true") + } + logger.Debug(fmt.Sprintf("Sending request to user app:%v", req)) + + // override host header if present in the request + hostHeader := tc.HTTPReq.Header["Host"] + if hostHeader != "" { + logger.Debug("overriding host header", zap.String("host", hostHeader)) + req.Host = hostHeader + } + + // Creating the client and disabling redirects + var client *http.Client + + _, hasAcceptEncoding := req.Header["Accept-Encoding"] + disableCompression := !hasAcceptEncoding + + keepAlive, ok := req.Header["Connection"] + if ok && strings.EqualFold(keepAlive[0], "keep-alive") { + logger.Debug("simulating request with conn:keep-alive") + client = &http.Client{ + Timeout: time.Second * time.Duration(apiTimeout), + CheckRedirect: func(_ *http.Request, _ []*http.Request) error { + return http.ErrUseLastResponse + }, + Transport: &http.Transport{ + DisableCompression: disableCompression, + }, + } + } else if ok && strings.EqualFold(keepAlive[0], "close") { + logger.Debug("simulating request with conn:close") + client = &http.Client{ + Timeout: time.Second * time.Duration(apiTimeout), + CheckRedirect: func(_ *http.Request, _ []*http.Request) error { + return http.ErrUseLastResponse + }, + Transport: &http.Transport{ + DisableKeepAlives: true, + DisableCompression: disableCompression, + }, + } + } else { + logger.Debug("simulating request with conn:keep-alive (maxIdleConn=1)") + client = &http.Client{ + Timeout: time.Second * time.Duration(apiTimeout), + CheckRedirect: func(_ *http.Request, _ []*http.Request) error { + return http.ErrUseLastResponse + }, + Transport: &http.Transport{ + DisableKeepAlives: false, + MaxIdleConns: 1, + DisableCompression: disableCompression, + }, + } + } + + httpResp, errHTTPReq := client.Do(req) + if errHTTPReq != nil { + utils.LogError(logger, errHTTPReq, "failed to send testcase request to app") + return nil, errHTTPReq + } + + defer func() { + if httpResp != nil && httpResp.Body != nil { + if err := httpResp.Body.Close(); err != nil { + utils.LogError(logger, err, "failed to close response body") + } + } + }() + + respBody, errReadRespBody := io.ReadAll(httpResp.Body) + if errReadRespBody != nil { + utils.LogError(logger, errReadRespBody, "failed reading response body") + return nil, errReadRespBody + } + + if httpResp.Header.Get("Content-Encoding") != "" { + respBody, err = Decompress(logger, httpResp.Header.Get("Content-Encoding"), respBody) + if err != nil { + utils.LogError(logger, err, "failed to decode response body") + return nil, err + } + } + + statusMessage := http.StatusText(httpResp.StatusCode) + + resp = &models.HTTPResp{ + StatusCode: httpResp.StatusCode, + StatusMessage: statusMessage, + Body: string(respBody), + Header: ToYamlHTTPHeader(httpResp.Header), + } + + return resp, errHTTPReq +} + +func ParseHTTPRequest(requestBytes []byte) (*http.Request, error) { + // Parse the request using the http.ReadRequest function + request, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(requestBytes))) + if err != nil { + return nil, err + } + request.Header.Set("Host", request.Host) + + return request, nil +} + +func ParseHTTPResponse(data []byte, request *http.Request) (*http.Response, error) { + buffer := bytes.NewBuffer(data) + reader := bufio.NewReader(buffer) + response, err := http.ReadResponse(reader, request) + if err != nil { + return nil, err + } + return response, nil +} + +func MakeCurlCommand(tc models.HTTPReq) string { + curl := fmt.Sprintf("curl --request %s \\\n", string(tc.Method)) + curl = curl + fmt.Sprintf(" --url %s \\\n", tc.URL) + header := ToHTTPHeader(tc.Header) + + for k, v := range ToYamlHTTPHeader(header) { + if k != "Content-Length" { + curl = curl + fmt.Sprintf(" --header '%s: %s' \\\n", k, v) + } + } + if len(tc.Form) > 0 { + for _, form := range tc.Form { + key := form.Key + if len(form.Values) == 0 { + continue + } + value := form.Values[0] + curl = curl + fmt.Sprintf(" --form '%s=%s' \\\n", key, value) + } + } else if tc.Body != "" { + curl = curl + fmt.Sprintf(" --data %s", strconv.Quote(tc.Body)) + } + return curl +} + +func ReadSessionIndices(path string, Logger *zap.Logger) ([]string, error) { + indices := []string{} + + dir, err := os.OpenFile(path, os.O_RDONLY, fs.FileMode(os.O_RDONLY)) + if err != nil { + Logger.Debug("creating a folder for the keploy generated testcases", zap.Error(err)) + return indices, err + } + defer func() { + if closeErr := dir.Close(); closeErr != nil { + Logger.Debug("failed to close directory", zap.Error(closeErr)) + } + }() + + files, err := dir.ReadDir(0) + if err != nil { + Logger.Debug("failed to read directory contents", zap.Error(err)) + return indices, err + } + + for _, v := range files { + if v.Name() != "reports" && v.IsDir() { + indices = append(indices, v.Name()) + } + } + + return indices, nil +} + +func NextID(IDs []string, identifier string) string { + latestIndx := 0 + for _, ID := range IDs { + namePackets := strings.Split(ID, "-") + if len(namePackets) == 3 { + Indx, err := strconv.Atoi(namePackets[2]) + if err != nil { + continue + } + if latestIndx < Indx+1 { + latestIndx = Indx + 1 + } + } + } + return fmt.Sprintf("%s%v", identifier, latestIndx) +} + +func LastID(IDs []string, identifier string) string { + latestIndx := 0 + for _, ID := range IDs { + namePackets := strings.Split(ID, "-") + if len(namePackets) == 3 { + Indx, err := strconv.Atoi(namePackets[2]) + if err != nil { + continue + } + if latestIndx < Indx { + latestIndx = Indx + } + } + } + return fmt.Sprintf("%s%v", identifier, latestIndx) +} + +var ( + dateFormats = []string{ + time.Layout, + time.ANSIC, + time.UnixDate, + time.RubyDate, + time.RFC822, + time.RFC822Z, + time.RFC850, + time.RFC1123, + time.RFC1123Z, + time.RFC3339, + time.RFC3339Nano, + time.Kitchen, + time.Stamp, + time.StampMilli, + time.StampMicro, + time.StampNano, + time.DateTime, + time.DateOnly, + time.TimeOnly, + } +) + +// ExtractPort extracts the port from a given URL string, defaulting to 80 if no port is specified. +func ExtractPort(rawURL string) (uint32, error) { + parsedURL, err := url.Parse(rawURL) + if err != nil { + return 0, err + } + + host := parsedURL.Host + if strings.Contains(host, ":") { + // Split the host by ":" and return the port part + parts := strings.Split(host, ":") + port, err := strconv.ParseUint(parts[len(parts)-1], 10, 32) + if err != nil { + return 0, fmt.Errorf("invalid port in URL: %s", rawURL) + } + return uint32(port), nil + } + + // Default ports based on scheme + switch parsedURL.Scheme { + case "https": + return 443, nil + default: + return 80, nil + } +} + +func ExtractHostAndPort(curlCmd string) (string, string, error) { + // Split the command string to find the URL + parts := strings.Split(curlCmd, " ") + for _, part := range parts { + if strings.HasPrefix(part, "http") { + u, err := url.Parse(part) + if err != nil { + return "", "", err + } + host := u.Hostname() + port := u.Port() + if port == "" { + if u.Scheme == "https" { + port = "443" + } else { + port = "80" + } + } + return host, port, nil + } + } + return "", "", fmt.Errorf("no URL found in CURL command") +} + +func WaitForPort(ctx context.Context, host string, port string, timeout time.Duration) error { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + timer := time.NewTimer(timeout) + defer timer.Stop() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), 800*time.Millisecond) + if err == nil { + err := conn.Close() + if err != nil { + return err + } + return nil + } + case <-timer.C: + msg := "Please add delay if your application takes more time to start" + return fmt.Errorf("timeout after %v waiting for port %s:%s, %s", timeout, host, port, msg) + } + } +} + +func FilterTcsMocks(ctx context.Context, logger *zap.Logger, m []*models.Mock, afterTime time.Time, beforeTime time.Time) []*models.Mock { + filteredMocks, _ := filterByTimeStamp(ctx, logger, m, afterTime, beforeTime) + + sort.SliceStable(filteredMocks, func(i, j int) bool { + return filteredMocks[i].Spec.ReqTimestampMock.Before(filteredMocks[j].Spec.ReqTimestampMock) + }) + + return filteredMocks +} + +func FilterConfigMocks(ctx context.Context, logger *zap.Logger, m []*models.Mock, afterTime time.Time, beforeTime time.Time) []*models.Mock { + filteredMocks, unfilteredMocks := filterByTimeStamp(ctx, logger, m, afterTime, beforeTime) + + sort.SliceStable(filteredMocks, func(i, j int) bool { + return filteredMocks[i].Spec.ReqTimestampMock.Before(filteredMocks[j].Spec.ReqTimestampMock) + }) + + sort.SliceStable(unfilteredMocks, func(i, j int) bool { + return unfilteredMocks[i].Spec.ReqTimestampMock.Before(unfilteredMocks[j].Spec.ReqTimestampMock) + }) + + return append(filteredMocks, unfilteredMocks...) +} + +func filterByTimeStamp(_ context.Context, logger *zap.Logger, m []*models.Mock, afterTime time.Time, beforeTime time.Time) ([]*models.Mock, []*models.Mock) { + + filteredMocks := make([]*models.Mock, 0) + unfilteredMocks := make([]*models.Mock, 0) + + if afterTime.Equal(time.Time{}) { + return m, unfilteredMocks + } + + if beforeTime.Equal(time.Time{}) { + return m, unfilteredMocks + } + + isNonKeploy := false + + for _, mock := range m { + // doing deep copy to prevent data race, which was happening due to the write to isFiltered + // field in this for loop, and write in mockmanager functions. + tmp := *mock + p := &tmp + if p.Version != "api.keploy.io/v1beta1" && p.Version != "api.keploy.io/v1beta2" { + isNonKeploy = true + } + if p.Spec.ReqTimestampMock.Equal(time.Time{}) || p.Spec.ResTimestampMock.Equal(time.Time{}) { + logger.Debug("request or response timestamp of mock is missing") + p.TestModeInfo.IsFiltered = true + filteredMocks = append(filteredMocks, p) + continue + } + + if p.Spec.ReqTimestampMock.After(afterTime) && p.Spec.ResTimestampMock.Before(beforeTime) { + p.TestModeInfo.IsFiltered = true + filteredMocks = append(filteredMocks, p) + continue + } + p.TestModeInfo.IsFiltered = false + unfilteredMocks = append(unfilteredMocks, p) + } + if isNonKeploy { + logger.Debug("Few mocks in the mock File are not recorded by keploy ignoring them") + } + return filteredMocks, unfilteredMocks +} + +func GuessContentType(data []byte) models.BodyType { + // Use net/http library's DetectContentType for basic MIME type detection + mimeType := http.DetectContentType(data) + + // Additional checks to further specify the content type + switch { + case IsJSON(data): + return models.JSON + case IsXML(data): + return models.XML + case strings.Contains(mimeType, "text/html"): + return models.HTML + case strings.Contains(mimeType, "text/plain"): + if IsCSV(data) { + return models.CSV + } + return models.Plain + } + + return models.UnknownType +} + +func IsJSON(body []byte) bool { + var js interface{} + return json.Unmarshal(body, &js) == nil +} + +func IsXML(data []byte) bool { + var xm xml.Name + return xml.Unmarshal(data, &xm) == nil +} + +// IsCSV checks if data can be parsed as CSV by looking for common characteristics +func IsCSV(data []byte) bool { + // Very simple CSV check: look for commas in the first line + content := string(data) + if lines := strings.Split(content, "\n"); len(lines) > 0 { + return strings.Contains(lines[0], ",") + } + return false +} + +func Decompress(logger *zap.Logger, encoding string, data []byte) ([]byte, error) { + switch encoding { + case "br": + logger.Debug("decompressing brotli compressed data") + reader := brotli.NewReader(bytes.NewReader(data)) + decodedData, err := io.ReadAll(reader) + if err != nil { + utils.LogError(logger, err, "failed to read the brotli compressed data") + return nil, err + } + return decodedData, nil + case "gzip": + logger.Debug("decoding gzip compressed data") + reader, err := gzip.NewReader(bytes.NewReader(data)) + if err != nil { + utils.LogError(logger, err, "failed to create gzip reader") + return nil, err + } + defer reader.Close() + decodedData, err := io.ReadAll(reader) + if err != nil { + utils.LogError(logger, err, "failed to read the gzip compressed data") + return nil, err + } + return decodedData, nil + } + return data, nil +} + +func Compress(logger *zap.Logger, encoding string, data []byte) ([]byte, error) { + switch encoding { + case "gzip": + logger.Debug("compressing data using gzip") + var compressedBuffer bytes.Buffer + gw := gzip.NewWriter(&compressedBuffer) + _, err := gw.Write(data) + if err != nil { + utils.LogError(logger, err, "failed to write compressed data to gzip writer") + return nil, err + } + err = gw.Close() + if err != nil { + utils.LogError(logger, err, "failed to close gzip writer") + return nil, err + } + return compressedBuffer.Bytes(), nil + case "br": + logger.Debug("compressing data using brotli") + var compressedBuffer bytes.Buffer + bw := brotli.NewWriter(&compressedBuffer) + _, err := bw.Write(data) + if err != nil { + utils.LogError(logger, err, "failed to write compressed data to brotli writer") + return nil, err + } + err = bw.Close() + if err != nil { + utils.LogError(logger, err, "failed to close brotli writer") + return nil, err + } + return compressedBuffer.Bytes(), nil + } + return data, nil +} diff --git a/keploy/pkg/util_test.go b/keploy/pkg/util_test.go new file mode 100644 index 0000000..72dad75 --- /dev/null +++ b/keploy/pkg/util_test.go @@ -0,0 +1,247 @@ +package pkg + +import ( + "context" + "fmt" + "io" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +// TestSimulateHTTP_NewRequestError_303 ensures that SimulateHTTP returns an error +// when http.NewRequestWithContext fails. This is triggered by providing an invalid +// HTTP method string in the test case. +func TestSimulateHTTP_NewRequestError_303(t *testing.T) { + // Arrange + ctx := context.Background() + logger := zap.NewNop() + tc := &models.TestCase{ + Name: "test-case-new-request-error", + HTTPReq: models.HTTPReq{ + Method: "INVALID METHOD", // Invalid method + URL: "http://example.com/test", + Body: `{"key":"value"}`, + }, + } + + // Act + resp, err := SimulateHTTP(ctx, tc, "test-set", logger, 10) + + // Assert + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid method") + assert.Nil(t, resp) +} + +// TestIsTime_VariousFormats_808 covers multiple scenarios for the IsTime function, +// including standard date formats (RFC3339, UnixDate), numeric timestamps as strings, +// and invalid inputs to ensure it correctly identifies time-like strings. +func TestIsTime_VariousFormats_808(t *testing.T) { + testCases := []struct { + name string + input string + expected bool + }{ + {"RFC3339", "2023-01-17T16:34:58Z", true}, + {"UnixDate", "Tue Jan 17 16:34:58 UTC 2023", true}, + {"NumericTimestamp", fmt.Sprintf("%f", float64(time.Now().UnixNano())), true}, + {"AlmostNow", fmt.Sprintf("%f", float64(time.Now().Add(-time.Hour).UnixNano())), true}, + {"TooOldNumeric", fmt.Sprintf("%f", float64(time.Now().Add(-48*time.Hour).UnixNano())), false}, + {"InvalidString", "not a date", false}, + {"EmptyString", "", false}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, IsTime(tc.input)) + }) + } +} + +// TestToHTTPHeader_WithTimeValue_909 verifies that the ToHTTPHeader function correctly +// converts a map of strings to an http.Header object. It specifically checks that +// header values recognized as timestamps are not split by commas, while other +// comma-separated values are correctly split into slices. +func TestToHTTPHeader_WithTimeValue_909(t *testing.T) { + // Arrange + mockHeader := map[string]string{ + "Date": "Tue, 17 Jan 2023 16:34:58 IST", + "X-Custom-Header": "value1,value2", + "Content-Type": "application/json", + } + + // Act + httpHeader := ToHTTPHeader(mockHeader) + + // Assert + require.NotNil(t, httpHeader) + assert.Equal(t, []string{"Tue, 17 Jan 2023 16:34:58 IST"}, httpHeader["Date"]) + assert.Equal(t, []string{"value1", "value2"}, httpHeader["X-Custom-Header"]) + assert.Equal(t, []string{"application/json"}, httpHeader["Content-Type"]) +} + +// TestParseHTTPRequest_And_Response_111 contains sub-tests for ParseHTTPRequest and +// ParseHTTPResponse, validating both success and failure cases for parsing raw +// HTTP data into their respective struct representations. +func TestParseHTTPRequest_And_Response_111(t *testing.T) { + t.Run("ParseHTTPRequest_Valid", func(t *testing.T) { + rawReq := "GET /test HTTP/1.1\r\nHost: example.com\r\n\r\n" + req, err := ParseHTTPRequest([]byte(rawReq)) + require.NoError(t, err) + assert.Equal(t, "GET", req.Method) + assert.Equal(t, "/test", req.URL.Path) + assert.Equal(t, "example.com", req.Host) + }) + + t.Run("ParseHTTPRequest_Invalid", func(t *testing.T) { + rawReq := "this is not a valid request" + _, err := ParseHTTPRequest([]byte(rawReq)) + require.Error(t, err) + }) + + t.Run("ParseHTTPResponse_Valid", func(t *testing.T) { + rawResp := "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, world!" + req, _ := http.NewRequest("GET", "http://example.com", nil) + resp, err := ParseHTTPResponse([]byte(rawResp), req) + require.NoError(t, err) + defer func() { + if err := resp.Body.Close(); err != nil { + t.Logf("failed to close response body: %v", err) + } + }() + assert.Equal(t, 200, resp.StatusCode) + body, _ := io.ReadAll(resp.Body) + assert.Equal(t, "Hello, world!", string(body)) + }) + + t.Run("ParseHTTPResponse_Invalid", func(t *testing.T) { + rawResp := "this is not a valid response" + req, _ := http.NewRequest("GET", "http://example.com", nil) + _, err := ParseHTTPResponse([]byte(rawResp), req) + require.Error(t, err) + }) +} + +// TestCompressDecompress_AllEncodings_555 provides comprehensive testing for the +// Compress and Decompress functions. It checks: +// - Successful round-trip (compress then decompress) for 'gzip' and 'br' (brotli). +// - No-op behavior for unknown encodings. +// - Error handling for invalid compressed data. +func TestCompressDecompress_AllEncodings_555(t *testing.T) { + logger := zap.NewNop() + + t.Run("Gzip", func(t *testing.T) { + originalData := []byte("hello world, this is a test") + compressedData, err := Compress(logger, "gzip", originalData) + require.NoError(t, err) + assert.NotEqual(t, originalData, compressedData) + decompressedData, err := Decompress(logger, "gzip", compressedData) + require.NoError(t, err) + assert.Equal(t, originalData, decompressedData) + }) + + t.Run("Brotli", func(t *testing.T) { + originalData := []byte("hello world, this is a brotli test") + compressedData, err := Compress(logger, "br", originalData) + require.NoError(t, err) + assert.NotEqual(t, originalData, compressedData) + decompressedData, err := Decompress(logger, "br", compressedData) + require.NoError(t, err) + assert.Equal(t, originalData, decompressedData) + }) + + t.Run("UnknownEncoding", func(t *testing.T) { + originalData := []byte("hello world") + compressedData, err := Compress(logger, "unknown", originalData) + require.NoError(t, err) + assert.Equal(t, originalData, compressedData) + decompressedData, err := Decompress(logger, "unknown", originalData) + require.NoError(t, err) + assert.Equal(t, originalData, decompressedData) + }) + + t.Run("DecompressError", func(t *testing.T) { + invalidGzipData := []byte("not gzip") + _, err := Decompress(logger, "gzip", invalidGzipData) + require.Error(t, err) + + invalidBrotliData := []byte{0xce, 0xb2, 0xcf, 0x81} + _, err = Decompress(logger, "br", invalidBrotliData) + require.Error(t, err) + }) +} + +// TestFilterMocks_678 validates the filtering and sorting of mocks in Test and Config modes. +func TestFilterMocks_678(t *testing.T) { + logger := zap.NewNop() + ctx := context.Background() + + now := time.Now() + mock1 := &models.Mock{Name: "mock1", Version: "api.keploy.io/v1beta1", Spec: models.MockSpec{ReqTimestampMock: now.Add(-2 * time.Hour), ResTimestampMock: now.Add(-2 * time.Hour)}} + mock2 := &models.Mock{Name: "mock2", Version: "api.keploy.io/v1beta1", Spec: models.MockSpec{ReqTimestampMock: now, ResTimestampMock: now}} + mock3 := &models.Mock{Name: "mock3", Version: "api.keploy.io/v1beta1", Spec: models.MockSpec{ReqTimestampMock: now.Add(2 * time.Hour), ResTimestampMock: now.Add(2 * time.Hour)}} + mockNoTime := &models.Mock{Name: "mockNoTime", Version: "api.keploy.io/v1beta1", Spec: models.MockSpec{}} + mockNonKeploy := &models.Mock{Name: "mockNonKeploy", Version: "v1", Spec: models.MockSpec{ReqTimestampMock: now, ResTimestampMock: now}} + + allMocks := []*models.Mock{mock3, mock1, mock2, mockNoTime, mockNonKeploy} + + t.Run("filterByTimeStamp_NoFilters", func(t *testing.T) { + filtered, unfiltered := filterByTimeStamp(ctx, logger, allMocks, time.Time{}, time.Time{}) + assert.Len(t, filtered, 5) + assert.Len(t, unfiltered, 0) + }) + + t.Run("filterByTimeStamp_ValidRange", func(t *testing.T) { + after := now.Add(-1 * time.Hour) + before := now.Add(1 * time.Hour) + filtered, unfiltered := filterByTimeStamp(ctx, logger, allMocks, after, before) + + // Check filtered mocks + require.Len(t, filtered, 3) + filteredNames := []string{} + for _, m := range filtered { + filteredNames = append(filteredNames, m.Name) + assert.True(t, m.TestModeInfo.IsFiltered) + } + assert.ElementsMatch(t, []string{"mock2", "mockNoTime", "mockNonKeploy"}, filteredNames) + + // Check unfiltered mocks + require.Len(t, unfiltered, 2) + unfilteredNames := []string{} + for _, m := range unfiltered { + unfilteredNames = append(unfilteredNames, m.Name) + assert.False(t, m.TestModeInfo.IsFiltered) + } + assert.ElementsMatch(t, []string{"mock1", "mock3"}, unfilteredNames) + }) + + t.Run("FilterTcsMocks", func(t *testing.T) { + after := now.Add(-1 * time.Hour) + before := now.Add(1 * time.Hour) + result := FilterTcsMocks(ctx, logger, allMocks, after, before) + require.Len(t, result, 3) + assert.Equal(t, "mockNoTime", result[0].Name) // Zero time comes first + assert.Equal(t, "mock2", result[1].Name) + assert.Equal(t, "mockNonKeploy", result[2].Name) + }) + + t.Run("FilterConfigMocks", func(t *testing.T) { + after := now.Add(-1 * time.Hour) + before := now.Add(1 * time.Hour) + result := FilterConfigMocks(ctx, logger, allMocks, after, before) + require.Len(t, result, 5) + // Sorted filtered part + assert.Equal(t, "mockNoTime", result[0].Name) + assert.Equal(t, "mock2", result[1].Name) + assert.Equal(t, "mockNonKeploy", result[2].Name) + // Sorted unfiltered part + assert.Equal(t, "mock1", result[3].Name) + assert.Equal(t, "mock3", result[4].Name) + }) +} diff --git a/keploy/utils/ctx.go b/keploy/utils/ctx.go new file mode 100644 index 0000000..f9a744d --- /dev/null +++ b/keploy/utils/ctx.go @@ -0,0 +1,69 @@ +// Package utils provides utility functions for the Keploy application. +package utils + +import ( + "context" + "errors" + "fmt" + "os" + "os/signal" + "syscall" + + "go.uber.org/zap" +) + +var cancel context.CancelFunc + +func NewCtx() context.Context { + // Create a context that can be canceled + ctx, cancel := context.WithCancel(context.Background()) + + SetCancel(cancel) + // Set up a channel to listen for signals + sigs := make(chan os.Signal, 1) + // os.Interrupt is more portable than syscall.SIGINT + // there is no equivalent for syscall.SIGTERM in os.Signal + signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) + + // Start a goroutine that will cancel the context when a signal is received + go func() { + sig := <-sigs // this received signal will be inside keploy docker container if running in docker else on the host. + fmt.Printf("Signal received: %s, canceling context...\n", sig) + cancel() + }() + + return ctx +} + +// Stop requires a reason to stop the server. +// this is to ensure that the server is not stopped accidentally. +// and to trace back the stopper +func Stop(logger *zap.Logger, reason string) error { + // Stop the server. + if logger == nil { + return errors.New("logger is not set") + } + if cancel == nil { + err := errors.New("cancel function is not set") + LogError(logger, err, "failed stopping keploy") + return err + } + + if reason == "" { + err := errors.New("cannot stop keploy without a reason") + LogError(logger, err, "failed stopping keploy") + return err + } + + logger.Info("stopping Keploy", zap.String("reason", reason)) + ExecCancel() + return nil +} + +func ExecCancel() { + cancel() +} + +func SetCancel(c context.CancelFunc) { + cancel = c +} diff --git a/keploy/utils/inc.go b/keploy/utils/inc.go new file mode 100644 index 0000000..be4da2a --- /dev/null +++ b/keploy/utils/inc.go @@ -0,0 +1,23 @@ +package utils + +import "sync" + +type AutoInc struct { + sync.Mutex // ensures autoInc is goroutine-safe + id int +} + +func (a *AutoInc) Next() (id int) { + a.Lock() + defer a.Unlock() + + id = a.id + a.id++ + return +} + +func (a *AutoInc) Reset() { + a.Lock() + a.id = 0 + a.Unlock() +} diff --git a/keploy/utils/log/colors.go b/keploy/utils/log/colors.go new file mode 100644 index 0000000..da80aa2 --- /dev/null +++ b/keploy/utils/log/colors.go @@ -0,0 +1,48 @@ +// Package log provides utility functions for logging. +package log + +import ( + "bytes" + + "go.uber.org/zap" + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +type color struct { + *zapcore.EncoderConfig + zapcore.Encoder +} + +func NewColor(cfg zapcore.EncoderConfig, enableColor bool) (enc zapcore.Encoder) { + if enableColor { + return color{ + EncoderConfig: &cfg, + Encoder: zapcore.NewConsoleEncoder(cfg), + } + } + // fmt.Println("Color is disabled") + return zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) +} + +// EncodeEntry overrides ConsoleEncoder's EncodeEntry +func (c color) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (buf *buffer.Buffer, err error) { + buff, err := c.Encoder.EncodeEntry(ent, fields) // Utilize the existing implementation of zap + if err != nil { + return nil, err + } + + bytesArr := bytes.ReplaceAll(buff.Bytes(), []byte("\\u001b"), []byte("\u001b")) + buff.Reset() + buff.AppendString(string(bytesArr)) + return buff, err +} + +// Clone overrides ConsoleEncoder's Clone +func (c color) Clone() zapcore.Encoder { + clone := c.Encoder.Clone() + return color{ + EncoderConfig: c.EncoderConfig, + Encoder: clone, + } +} diff --git a/keploy/utils/log/logger.go b/keploy/utils/log/logger.go new file mode 100644 index 0000000..19ee06d --- /dev/null +++ b/keploy/utils/log/logger.go @@ -0,0 +1,99 @@ +package log + +import ( + "fmt" + "os" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var Emoji = "\U0001F430" + " Keploy:" + +// TODO find better way than global variable + +var LogCfg zap.Config + +func New() (*zap.Logger, *os.File, error) { + _ = zap.RegisterEncoder("colorConsole", func(config zapcore.EncoderConfig) (zapcore.Encoder, error) { + return NewColor(config, true), nil + }) + _ = zap.RegisterEncoder("nonColorConsole", func(config zapcore.EncoderConfig) (zapcore.Encoder, error) { + return NewColor(config, false), nil + }) + + logFile, err := os.OpenFile("keploy-logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0777) + if err != nil { + return nil, nil, fmt.Errorf("failed to open log file: %v", err) + } + + err = os.Chmod("keploy-logs.txt", 0777) + if err != nil { + return nil, nil, fmt.Errorf("failed to set the log file permission to 777: %v", err) + } + + writer := zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(logFile)) + + LogCfg = zap.NewDevelopmentConfig() + + LogCfg.Encoding = "colorConsole" + + // Customize the encoder config to put the emoji at the beginning. + LogCfg.EncoderConfig.EncodeTime = customTimeEncoder + LogCfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + + LogCfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel) + LogCfg.DisableStacktrace = true + LogCfg.EncoderConfig.EncodeCaller = nil + + core := zapcore.NewCore( + zapcore.NewConsoleEncoder(LogCfg.EncoderConfig), + writer, + LogCfg.Level, + ) + + logger := zap.New(core) + + return logger, logFile, nil +} + +func ChangeLogLevel(level zapcore.Level) (*zap.Logger, error) { + LogCfg.Level = zap.NewAtomicLevelAt(level) + if level == zap.DebugLevel { + LogCfg.DisableStacktrace = false + LogCfg.EncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder + } + + logger, err := LogCfg.Build() + if err != nil { + return nil, fmt.Errorf("failed to build config for logger: %v", err) + } + return logger, nil +} + +func AddMode(mode string) (*zap.Logger, error) { + // Get the current logger configuration + cfg := LogCfg + // Update the time encoder with the new values + cfg.EncoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + emoji := "\U0001F430" + mode := fmt.Sprintf("Keploy(%s):", mode) + enc.AppendString(emoji + " " + mode + " " + t.Format(time.RFC3339) + " ") + } + // Rebuild the logger with the updated configuration + newLogger, err := cfg.Build() + if err != nil { + return nil, fmt.Errorf("failed to add mode to logger: %v", err) + } + return newLogger, nil +} + +func ChangeColorEncoding() (*zap.Logger, error) { + LogCfg.Encoding = "nonColorConsole" + logger, err := LogCfg.Build() + if err != nil { + return nil, fmt.Errorf("failed to build config for logger: %v", err) + } + return logger, nil +} diff --git a/keploy/utils/log/time.go b/keploy/utils/log/time.go new file mode 100644 index 0000000..48509f7 --- /dev/null +++ b/keploy/utils/log/time.go @@ -0,0 +1,12 @@ +package log + +import ( + "time" + + "go.uber.org/zap/zapcore" +) + +func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + emoji := "\U0001F430" + " Keploy:" + enc.AppendString(emoji + " " + t.Format(time.RFC3339) + " ") +} diff --git a/keploy/utils/mask_others.go b/keploy/utils/mask_others.go new file mode 100644 index 0000000..3630178 --- /dev/null +++ b/keploy/utils/mask_others.go @@ -0,0 +1,13 @@ +//go:build !windows + +package utils + +import "syscall" + +func SetUmask() int { + return syscall.Umask(0) +} + +func RestoreUmask(oldMask int) { + syscall.Umask(oldMask) +} diff --git a/keploy/utils/mask_windows.go b/keploy/utils/mask_windows.go new file mode 100644 index 0000000..7b9eb3e --- /dev/null +++ b/keploy/utils/mask_windows.go @@ -0,0 +1,11 @@ +//go:build windows + +package utils + +func SetUmask() int { + return 0 +} + +func RestoreUmask(oldMask int) { + // Do nothing +} diff --git a/keploy/utils/signal_others.go b/keploy/utils/signal_others.go new file mode 100644 index 0000000..9475a1b --- /dev/null +++ b/keploy/utils/signal_others.go @@ -0,0 +1,104 @@ +//go:build linux || darwin + +package utils + +import ( + "context" + "fmt" + "os" + "os/exec" + "strconv" + "syscall" + "time" + + "go.uber.org/zap" +) + +func SendSignal(logger *zap.Logger, pid int, sig syscall.Signal) error { + err := syscall.Kill(pid, sig) + if err != nil { + // ignore the ESRCH error as it means the process is already dead + if errno, ok := err.(syscall.Errno); ok && errno == syscall.ESRCH { + return nil + } + logger.Error("failed to send signal to process", zap.Int("pid", pid), zap.Error(err)) + return err + } + logger.Debug("signal sent to process successfully", zap.Int("pid", pid), zap.String("signal", sig.String())) + + return nil +} + +func ExecuteCommand(ctx context.Context, logger *zap.Logger, userCmd string, cancel func(cmd *exec.Cmd) func() error, waitDelay time.Duration) CmdError { + // Run the app as the user who invoked sudo + username := os.Getenv("SUDO_USER") + + // Get the current hard limit for the number of open file descriptors + var rlimit syscall.Rlimit + hardLimit := 0 + var err error + if username != "" { + // get sudoers rlimit - (reason) https://github.com/keploy/keploy/issues/2899 + out, err := exec.Command("sudo", "-u", username, "sh", "-c", "ulimit -Hn").Output() + if err != nil { + logger.Warn("failed to get the hard limit for the number of open file descriptors for the user", zap.String("username", username), zap.Error(err)) + } else { + output := string(out)[:len(out)-1] + if output != "unlimited" { + limit, err := strconv.Atoi(output) + if err != nil { + logger.Warn("failed to parse the hard limit for the number of open file descriptors for the user", zap.String("username", username), zap.Error(err)) + } else { + hardLimit = limit + } + } + } + } else { + err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit) + if err != nil { + logger.Warn("failed to get the hard limit for the number of open file descriptors for the root user", zap.Error(err)) + } else { + hardLimit = int(rlimit.Max) + } + } + + if hardLimit != 0 { + userCmd = fmt.Sprintf("ulimit -S -n %d && %s", hardLimit, userCmd) + } + + cmd := exec.CommandContext(ctx, "sh", "-c", userCmd) + if username != "" { + // print all environment variables + logger.Debug("env inherited from the cmd", zap.Any("env", os.Environ())) + // Run the command as the user who invoked sudo to preserve the user environment variables and PATH + cmd = exec.CommandContext(ctx, "sudo", "-E", "-u", os.Getenv("SUDO_USER"), "env", "PATH="+os.Getenv("PATH"), "sh", "-c", userCmd) + } + + // Set the cancel function for the command + cmd.Cancel = cancel(cmd) + + // wait after sending the interrupt signal, before sending the kill signal + cmd.WaitDelay = waitDelay + + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + + // Set the output of the command + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + logger.Debug("", zap.Any("executing cli", cmd.String())) + + err = cmd.Start() + if err != nil { + return CmdError{Type: Init, Err: err} + } + + err = cmd.Wait() + if err != nil { + return CmdError{Type: Runtime, Err: err} + } + + return CmdError{} +} diff --git a/keploy/utils/signal_windows.go b/keploy/utils/signal_windows.go new file mode 100644 index 0000000..f2713ba --- /dev/null +++ b/keploy/utils/signal_windows.go @@ -0,0 +1,44 @@ +//go:build windows + +package utils + +import ( + "context" + "errors" + "os/exec" + "syscall" + "time" + + "go.uber.org/zap" + "golang.org/x/sys/windows" +) + +func SendSignal(logger *zap.Logger, pid int, sig syscall.Signal) error { + handle, err := syscall.OpenProcess(syscall.PROCESS_TERMINATE, false, uint32(pid)) + if err != nil { + if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER { + // ERROR_INVALID_PARAMETER means the process does not exist + return nil + } + logger.Error("failed to open process", zap.Int("pid", pid), zap.Error(err)) + return err + } + defer syscall.CloseHandle(handle) + + var retVal int32 + if sig == syscall.SIGKILL || sig == syscall.SIGTERM { + retVal = 1 // Default exit code for termination + } + + if err := syscall.TerminateProcess(handle, uint32(retVal)); err != nil { + logger.Error("failed to terminate process", zap.Int("pid", pid), zap.Error(err)) + return err + } + + logger.Debug("signal sent to process successfully", zap.Int("pid", pid), zap.String("signal", sig.String())) + return nil +} + +func ExecuteCommand(ctx context.Context, logger *zap.Logger, userCmd string, cancel func(cmd *exec.Cmd) func() error, waitDelay time.Duration) CmdError { + return CmdError{Type: Init, Err: errors.New("not implemented")} +} diff --git a/keploy/utils/utils.go b/keploy/utils/utils.go new file mode 100644 index 0000000..0353732 --- /dev/null +++ b/keploy/utils/utils.go @@ -0,0 +1,1292 @@ +package utils + +import ( + "bufio" + "bytes" + "context" + "crypto/sha256" + "debug/elf" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "os/user" + "path/filepath" + "regexp" + "runtime/debug" + "strconv" + "strings" + "syscall" + "text/template" + "time" + + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "github.com/getsentry/sentry-go" + netLib "github.com/shirou/gopsutil/v3/net" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" + "helm.sh/helm/v3/pkg/strvals" +) + +var WarningSign = "\U000026A0" + +var TemplatizedValues = map[string]interface{}{} +var SecretValues = map[string]interface{}{} + +var ErrCode = 0 + +func ReplaceHost(currentURL string, ipAddress string) (string, error) { + // Parse the current URL + parsedURL, err := url.Parse(currentURL) + + if err != nil { + // Return the original URL if parsing fails + return currentURL, err + } + + if ipAddress == "" { + return currentURL, fmt.Errorf("failed to replace url in case of docker env") + } + + // Replace hostname with the IP address + parsedURL.Host = strings.Replace(parsedURL.Host, parsedURL.Hostname(), ipAddress, 1) + // Return the modified URL + return parsedURL.String(), nil +} + +func ReplaceGrpcHost(authority string, ipAddress string) (string, error) { + // Check if ipAddress is empty + if ipAddress == "" { + return authority, fmt.Errorf("failed to replace authority in case of docker env: empty IP address") + } + + // Split authority into host and port + parts := strings.Split(authority, ":") + if len(parts) != 2 { + return authority, fmt.Errorf("invalid authority format, expected host:port but got %s", authority) + } + + // Replace the host part with ipAddress, keeping the port + return ipAddress + ":" + parts[1], nil +} + +func ReplaceGrpcPort(authority string, port string) (string, error) { + // Check if port is empty + if port == "" { + return authority, fmt.Errorf("failed to replace port in case of docker env: empty port") + } + + // Split authority into host and port + parts := strings.Split(authority, ":") + if len(parts) == 0 { + return authority, fmt.Errorf("invalid authority format, got empty string") + } + + // If there's no port in the authority, append the new port + if len(parts) == 1 { + return parts[0] + ":" + port, nil + } + + // Replace the port part, keeping the host + return parts[0] + ":" + port, nil +} + +// ReplaceBaseURL replaces the base URL (scheme + host) of the given URL with the provided baseURL. +// It returns the updated URL as a string or an error if the operation fails. +func ReplaceBaseURL(currentURL string, baseURL string) (string, error) { + // Parse the current URL + parsedURL, err := url.Parse(currentURL) + if err != nil { + return currentURL, err + } + + // Check if baseURL is valid + if baseURL == "" { + return currentURL, fmt.Errorf("failed to replace baseURL: baseURL is empty") + } + + // Parse the new baseURL + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return currentURL, fmt.Errorf("invalid baseURL: %w", err) + } + + // Replace the scheme and host + parsedURL.Scheme = newBaseURL.Scheme + parsedURL.Host = newBaseURL.Host + + // Return the updated URL as a string + return parsedURL.String(), nil +} + +func ReplacePort(currentURL string, port string) (string, error) { + if port == "" { + return currentURL, fmt.Errorf("failed to replace port in case of docker env") + } + + parsedURL, err := url.Parse(currentURL) + + if err != nil { + return currentURL, err + } + + if parsedURL.Port() == "" { + parsedURL.Host = parsedURL.Host + ":" + port + } else { + parsedURL.Host = strings.Replace(parsedURL.Host, parsedURL.Port(), port, 1) + } + + return parsedURL.String(), nil +} + +// GetReqMeta returns the metadata of the request +func GetReqMeta(req *http.Request) map[string]string { + reqMeta := map[string]string{} + if req != nil { + // get request metadata + reqMeta = map[string]string{ + "method": req.Method, + "url": req.URL.String(), + "host": req.Host, + } + } + return reqMeta +} + +func IsPassThrough(logger *zap.Logger, req *http.Request, destPort uint, opts models.OutgoingOptions) bool { + passThrough := false + + for _, bypass := range opts.Rules { + if bypass.Host != "" { + regex, err := regexp.Compile(bypass.Host) + if err != nil { + LogError(logger, err, "failed to compile the host regex", zap.Any("metadata", GetReqMeta(req))) + continue + } + passThrough = regex.MatchString(req.Host) + if !passThrough { + continue + } + } + if bypass.Path != "" { + regex, err := regexp.Compile(bypass.Path) + if err != nil { + LogError(logger, err, "failed to compile the path regex", zap.Any("metadata", GetReqMeta(req))) + continue + } + passThrough = regex.MatchString(req.URL.String()) + if !passThrough { + continue + } + } + + if passThrough { + if bypass.Port == 0 || bypass.Port == destPort { + return true + } + passThrough = false + } + } + + return passThrough +} + +func kebabToCamel(s string) string { + parts := strings.Split(s, "-") + for i := 1; i < len(parts); i++ { + parts[i] = cases.Title(language.English).String(parts[i]) + } + return strings.Join(parts, "") +} + +func BindFlagsToViper(logger *zap.Logger, cmd *cobra.Command, viperKeyPrefix string) error { + var bindErr error + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + camelCaseName := kebabToCamel(flag.Name) + err := viper.BindPFlag(camelCaseName, flag) + if err != nil { + LogError(logger, err, "failed to bind flag Name to flag") + bindErr = err + } + // Construct the Viper key and the env variable name + if viperKeyPrefix == "" { + viperKeyPrefix = cmd.Name() + } + viperKey := viperKeyPrefix + "." + camelCaseName + envVarName := strings.ToUpper(viperKeyPrefix + "_" + camelCaseName) + envVarName = strings.ReplaceAll(envVarName, ".", "_") // Why do we need this? + + // Bind the flag to Viper with the constructed key + err = viper.BindPFlag(viperKey, flag) + if err != nil { + LogError(logger, err, "failed to bind flag to config") + bindErr = err + } + + // Tell Viper to also read this flag's value from the corresponding env variable + err = viper.BindEnv(viperKey, envVarName) + logger.Debug("Binding flag to viper", zap.String("viperKey", viperKey), zap.String("envVarName", envVarName)) + if err != nil { + LogError(logger, err, "failed to bind environment variables to config") + bindErr = err + } + }) + return bindErr +} + +//func ModifyToSentryLogger(ctx context.Context, logger *zap.Logger, client *sentry.Client, configDb *configdb.ConfigDb) *zap.Logger { +// cfg := zapsentry.Configuration{ +// Level: zapcore.ErrorLevel, //when to send message to sentry +// EnableBreadcrumbs: true, // enable sending breadcrumbs to Sentry +// BreadcrumbLevel: zapcore.InfoLevel, // at what level should we sent breadcrumbs to sentry +// Tags: map[string]string{ +// "component": "system", +// }, +// } +// +// core, err := zapsentry.NewCore(cfg, zapsentry.NewSentryClientFromClient(client)) +// //in case of err it will return noop core. So we don't need to attach it to log. +// if err != nil { +// logger.Debug("failed to init zap", zap.Error(err)) +// return logger +// } +// +// logger = zapsentry.AttachCoreToLogger(core, logger) +// kernelVersion := "" +// if runtime.GOOS == "linux" { +// cmd := exec.CommandContext(ctx, "uname", "-r") +// kernelBytes, err := cmd.Output() +// if err != nil { +// logger.Debug("failed to get kernel version", zap.Error(err)) +// } else { +// kernelVersion = string(kernelBytes) +// } +// } +// +// arch := runtime.GOARCH +// installationID, err := configDb.GetInstallationId(ctx) +// if err != nil { +// logger.Debug("failed to get installationID", zap.Error(err)) +// } +// sentry.ConfigureScope(func(scope *sentry.Scope) { +// scope.SetTag("Keploy Version", Version) +// scope.SetTag("Linux Kernel Version", kernelVersion) +// scope.SetTag("Architecture", arch) +// scope.SetTag("Installation ID", installationID) +// }) +// return logger +//} + +// LogError logs the error with the provided fields if the error is not context.Canceled. +func LogError(logger *zap.Logger, err error, msg string, fields ...zap.Field) { + if logger == nil { + fmt.Println("Failed to log error. Logger is nil.") + return + } + if !errors.Is(err, context.Canceled) { + logger.Error(msg, append(fields, zap.Error(err))...) + } +} + +// RemoveDoubleQuotes removes all double quotes from the values in the provided template map. +// This function handles cases where the templating engine fails to parse values containing both single and double quotes. +// For example: +// Input: '"Not/A)Brand";v="8", "Chromium";v="126", "Brave";v="126"' +// Output: Not/A)Brand;v=8, Chromium;v=126, Brave;v=126 +func RemoveDoubleQuotes(tempMap map[string]interface{}) { + // Remove double quotes + for key, val := range tempMap { + if str, ok := val.(string); ok { + tempMap[key] = strings.ReplaceAll(str, `"`, "") + } + } +} + +func DeleteFileIfNotExists(logger *zap.Logger, name string) (err error) { + //Check if file exists + _, err = os.Stat(name) + if os.IsNotExist(err) { + return nil + } + //If it does, remove it. + err = os.Remove(name) + if err != nil { + LogError(logger, err, "Error removing file") + return err + } + + return nil +} + +type GitHubRelease struct { + TagName string `json:"tag_name"` + Body string `json:"body"` +} + +var ErrGitHubAPIUnresponsive = errors.New("GitHub API is unresponsive") + +var Emoji = "\U0001F430" + " Keploy:" +var ConfigGuide = ` +# Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file. +` + +// AskForConfirmation asks the user for confirmation. A user must type in "yes" or "no" and +// then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as +// confirmations. If the input is not recognized or interrupted, exit gracefully as "no". +func AskForConfirmation(ctx context.Context, s string) (bool, error) { + reader := bufio.NewReader(os.Stdin) + + fmt.Printf("%s [y/n]: ", s) + + respCh := make(chan string, 1) + errCh := make(chan error, 1) + + go func() { + response, err := reader.ReadString('\n') + if err != nil { + errCh <- err + } else { + respCh <- response + } + }() + + select { + case <-ctx.Done(): + // Cobra caught SIGINT (Ctrl+C) and cancelled its root context + return false, nil + case err := <-errCh: + return false, err + case response := <-respCh: + response = strings.ToLower(strings.TrimSpace(response)) + if response == "y" || response == "yes" { + return true, nil + } + return false, nil + } +} + +func CheckFileExists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + return true +} + +var Version string +var VersionIdenitfier string +var LogFile *os.File + +func GetVersionAsComment() string { + return fmt.Sprintf("# Generated by Keploy (%s)\n", Version) +} + +func attachLogFileToSentry(logger *zap.Logger, logFilePath string) error { + file, err := os.Open(logFilePath) + if err != nil { + return fmt.Errorf("error opening log file: %s", err.Error()) + } + defer func() { + if err := file.Close(); err != nil { + LogError(logger, err, "Error closing log file") + } + }() + + content, err := io.ReadAll(file) + if err != nil { + return fmt.Errorf("error reading log file: %s", err.Error()) + } + + sentry.ConfigureScope(func(scope *sentry.Scope) { + scope.SetExtra("logfile", string(content)) + }) + sentry.Flush(time.Second * 5) + return nil +} + +// HandleRecovery handles the common logic for recovering from a panic. +func HandleRecovery(logger *zap.Logger, r interface{}, errMsg string) { + err := attachLogFileToSentry(logger, "./keploy-logs.txt") + if err != nil { + LogError(logger, err, "failed to attach log file to sentry") + } + sentry.CaptureException(errors.New(fmt.Sprint(r))) + // Get the stack trace + stackTrace := debug.Stack() + LogError(logger, nil, errMsg, zap.String("stack trace", string(stackTrace))) +} + +// Recover recovers from a panic and logs the stack trace to Sentry. +// It also stops the global context. +func Recover(logger *zap.Logger) { + if logger == nil { + fmt.Println(Emoji + "Failed to recover from panic. Logger is nil.") + return + } + sentry.Flush(2 * time.Second) + if r := recover(); r != nil { + HandleRecovery(logger, r, "Recovered from panic") + err := Stop(logger, fmt.Sprintf("Recovered from: %s", r)) + if err != nil { + LogError(logger, err, "failed to stop the global context") + } + sentry.Flush(2 * time.Second) + } +} + +// GenerateGithubActions generates a GitHub Actions workflow file for Keploy +func GenerateGithubActions(logger *zap.Logger, appCmd string) { + // Determine the path based on the alias "keploy" + logger.Debug("Generating GitHub Actions workflow file") + // Define the content of the GitHub Actions workflow file + actionsFileContent := `name: Keploy +on: + push: + branches: + - main + pull_request: + types: [opened, reopened, synchronize] +jobs: + e2e-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Test-Report + uses: keploy/testgpt@main + with: + working-directory: ./ + keploy-path: ./ + command: ` + appCmd + ` +` + + // Define the file path where the GitHub Actions workflow file will be saved + filePath := "/githubactions/keploy.yml" + + //create the file path + if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { + logger.Error("Error creating directory for GitHub Actions workflow file", zap.Error(err)) + return + } + // Write the content to the file + if err := os.WriteFile(filePath, []byte(actionsFileContent), 0644); err != nil { + logger.Error("Error writing GitHub Actions workflow file", zap.Error(err)) + return + } + + logger.Info("GitHub Actions workflow file generated successfully", zap.String("path", filePath)) +} + +// GetLatestGitHubRelease fetches the latest version and release body from GitHub releases with a timeout. +func GetLatestGitHubRelease(ctx context.Context, logger *zap.Logger) (GitHubRelease, error) { + // GitHub repository details + repoOwner := "keploy" + repoName := "keploy" + + apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", repoOwner, repoName) + + client := http.Client{ + Timeout: 4 * time.Second, + } + + req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) + if err != nil { + return GitHubRelease{}, err + } + + resp, err := client.Do(req) + if err != nil { + var netErr net.Error + if errors.As(err, &netErr) && netErr.Timeout() { + return GitHubRelease{}, ErrGitHubAPIUnresponsive + } + return GitHubRelease{}, err + } + defer func() { + if err := resp.Body.Close(); err != nil { + LogError(logger, err, "failed to close response body") + } + }() + + var release GitHubRelease + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return GitHubRelease{}, err + } + return release, nil +} + +// FindDockerCmd checks if the cli is related to docker or not, it also returns if it is a docker compose file +func FindDockerCmd(cmd string) CmdType { + if cmd == "" { + return Empty + } + // Convert command to lowercase for case-insensitive comparison + cmdLower := strings.TrimSpace(strings.ToLower(cmd)) + + // Define patterns for Docker and Docker Compose + dockerRunPatterns := []string{"docker run", "sudo docker run", "docker container run", "sudo docker container run"} + dockerStartPatterns := []string{"docker start", "sudo docker start", "docker container start", "sudo docker container start"} + dockerComposePatterns := []string{"docker-compose", "sudo docker-compose", "docker compose", "sudo docker compose"} + + // Check for Docker Compose command patterns and file extensions + for _, pattern := range dockerComposePatterns { + if strings.HasPrefix(cmdLower, pattern) { + return DockerCompose + } + } + // Check for Docker start command patterns + for _, pattern := range dockerStartPatterns { + if strings.HasPrefix(cmdLower, pattern) { + return DockerStart + } + } + // Check for Docker run command patterns + for _, pattern := range dockerRunPatterns { + if strings.HasPrefix(cmdLower, pattern) { + return DockerRun + } + } + return Native +} + +type CmdType string + +// CmdType constants +const ( + DockerRun CmdType = "docker-run" + DockerStart CmdType = "docker-start" + DockerCompose CmdType = "docker-compose" + Native CmdType = "native" + Empty CmdType = "" +) + +func ToInt(value interface{}) int { + switch v := value.(type) { + case int: + return v + case string: + i, err := strconv.Atoi(v) + if err != nil { + fmt.Printf("failed to convert string to int: %v", err) + return 0 + } + return i + case float64: + return int(v) + + } + return 0 +} + +// ToString remove all types of value to strings for comparison. +func ToString(val interface{}) string { + switch v := val.(type) { + case int: + return strconv.Itoa(v) + case float64: + return strconv.FormatFloat(v, 'f', -1, 64) + case float32: + return strconv.FormatFloat(float64(v), 'f', -1, 32) + case int64: + return strconv.FormatInt(v, 10) + case int32: + return strconv.FormatInt(int64(v), 10) + case string: + return v + } + return "" +} + +func ToFloat(value interface{}) float64 { + switch v := value.(type) { + case float64: + return v + case string: + f, err := strconv.ParseFloat(v, 64) + if err != nil { + fmt.Printf("failed to convert string to float: %v", err) + return 0 + } + return f + case int: + return float64(v) + } + return 0 +} + +// Keys returns an array containing the keys of the given map. +func Keys(m map[string][]string) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} + +func SentryInit(logger *zap.Logger, dsn string) { + err := sentry.Init(sentry.ClientOptions{ + Dsn: dsn, + TracesSampleRate: 1.0, + }) + if err != nil { + logger.Debug("Could not initialise sentry.", zap.Error(err)) + } +} + +//func FetchHomeDirectory(isNewConfigPath bool) string { +// var configFolder = "/.keploy-config" +// +// if isNewConfigPath { +// configFolder = "/.keploy" +// } +// if runtime.GOOS == "windows" { +// home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") +// if home == "" { +// home = os.Getenv("USERPROFILE") +// } +// return home + configFolder +// } +// +// return os.Getenv("HOME") + configFolder +//} + +func GetAbsPath(path string) (string, error) { + absPath, err := filepath.Abs(path) + if err != nil { + return "", err + } + return absPath, nil +} + +func ToAbsPath(logger *zap.Logger, originalPath string) string { + path := originalPath + //if user provides relative path + if len(path) > 0 && path[0] != '/' { + absPath, err := filepath.Abs(path) + if err != nil { + LogError(logger, err, "failed to get the absolute path from relative path") + } + path = absPath + } else if len(path) == 0 { // if user doesn't provide any path + cdirPath, err := os.Getwd() + if err != nil { + LogError(logger, err, "failed to get the path of current directory") + } + path = cdirPath + } + path += "/keploy" + return path +} + +// makeDirectory creates a directory if not exists with all user access +func makeDirectory(path string) error { + err := os.MkdirAll(path, 0777) + if err != nil { + return err + } + return nil +} + +// SetCoveragePath takes a goCovPath and sets the coverage path accordingly. +// It returns an error if the path is a file or if the path does not exist. +func SetCoveragePath(logger *zap.Logger, goCovPath string) (string, error) { + if goCovPath == "" { + // Calculate the current path and create a coverage-reports directory + currentPath, err := GetAbsPath("") + if err != nil { + LogError(logger, err, "failed to get the current working directory") + return "", err + } + goCovPath = currentPath + "/coverage-reports" + if err := makeDirectory(goCovPath); err != nil { + LogError(logger, err, "failed to create coverage-reports directory", zap.String("CoverageReportPath", goCovPath)) + return "", err + } + return goCovPath, nil + } + + goCovPath, err := GetAbsPath(goCovPath) + if err != nil { + LogError(logger, err, "failed to get the absolute path for the coverage report path", zap.String("CoverageReportPath", goCovPath)) + return "", err + } + // Check if the path is a directory + dirInfo, err := os.Stat(goCovPath) + if err != nil { + if os.IsNotExist(err) { + LogError(logger, err, "the provided path does not exist", zap.String("CoverageReportPath", goCovPath)) + return "", err + } + LogError(logger, err, "failed to check the coverage report path", zap.String("CoverageReportPath", goCovPath)) + return "", err + } + if !dirInfo.IsDir() { + msg := "the coverage report path is not a directory. Please provide a valid path to a directory for go coverage reports" + + LogError(logger, nil, msg, zap.String("CoverageReportPath", goCovPath)) + return "", errors.New("the path provided is not a directory") + } + + return goCovPath, nil +} + +type ErrType string + +// ErrType constants to get the type of error, during init or runtime +const ( + Init ErrType = "init" + Runtime ErrType = "runtime" +) + +type CmdError struct { + Type ErrType + Err error +} + +// InterruptProcessTree interrupts an entire process tree using the given signal +func InterruptProcessTree(logger *zap.Logger, ppid int, sig syscall.Signal) error { + // Find all descendant PIDs of the given PID & then signal them. + // Any shell doesn't signal its children when it receives a signal. + // Children may have their own process groups, so we need to signal them separately. + + logger.Debug("Interrupting process tree", zap.Int("pid", ppid), zap.String("signal", sig.String())) + + children, err := findChildPIDs(ppid) + if err != nil { + return err + } + + children = append(children, ppid) + + logger.Debug("Found child PIDs", zap.Ints("children", children)) + + uniqueProcess, err := uniqueProcessGroups(children) + if err != nil { + logger.Error("failed to find unique process groups", zap.Int("pid", ppid), zap.Error(err)) + uniqueProcess = children + } + + logger.Debug("Unique process groups", zap.Ints("uniqueProcess", uniqueProcess)) + + // Send signal to interrupt each process and wait for them to exit one by one + for _, pid := range uniqueProcess { + err := SendSignal(logger, -pid, sig) + if err != nil { + logger.Error("error sending signal to the process group id", zap.Int("pgid", pid), zap.Error(err)) + continue + } + // Wait for this particular process to exit with a timeout of 3 seconds + err = waitForProcessExit(pid, 3*time.Second, logger) + if err != nil { + logger.Error("error waiting for process to exit", zap.Int("pid", pid), zap.Error(err)) + } + } + return nil +} + +// waitForProcessExit waits for the process to exit or times out after the specified duration +func waitForProcessExit(pid int, timeout time.Duration, logger *zap.Logger) error { + // Create a timeout channel + timeoutCh := time.After(timeout) + + // Loop to check if the process is running + for { + select { + case <-timeoutCh: + // If the timeout is reached, log the timeout and break + logger.Warn("Timed out waiting for process to exit", zap.Int("pid", pid)) + return nil + default: + // Check if the process is running + isRunning, err := isProcessRunning(pid) + if err != nil { + return err + } + + if !isRunning { + logger.Debug("Process exited", zap.Int("pid", pid)) + return nil + } + + // Wait for 1 second before checking again + time.Sleep(300 * time.Millisecond) + } + } +} + +// isProcessRunning checks if a particular process is still running using os.FindProcess +func isProcessRunning(pid int) (bool, error) { + // Try to find the process + process, err := os.FindProcess(pid) + if err != nil { + return false, err // Process lookup failed + } + + // Attempt to signal the process (0 means no action, just error check) + err = process.Signal(syscall.Signal(0)) + if err != nil { + // If error occurs when signaling, it means the process is no longer running + if errors.Is(err, os.ErrProcessDone) || err == syscall.ESRCH { + return false, nil + } + // If there’s another error, consider the process as still running + return true, nil + } + + // No error means the process is running + return true, nil +} + +func uniqueProcessGroups(pids []int) ([]int, error) { + uniqueGroups := make(map[int]bool) + var uniqueGPIDs []int + + for _, pid := range pids { + pgid, err := getProcessGroupID(pid) + if err != nil { + return nil, err + } + if !uniqueGroups[pgid] { + uniqueGroups[pgid] = true + uniqueGPIDs = append(uniqueGPIDs, pgid) + } + } + + return uniqueGPIDs, nil +} + +func getProcessGroupID(pid int) (int, error) { + statusPath := filepath.Join("/proc", strconv.Itoa(pid), "status") + statusBytes, err := os.ReadFile(statusPath) + if err != nil { + return 0, err + } + + status := string(statusBytes) + for _, line := range strings.Split(status, "\n") { + if strings.HasPrefix(line, "NSpgid:") { + return extractIDFromStatusLine(line), nil + } + } + + return 0, nil +} + +// extractIDFromStatusLine extracts the ID from a status line in the format "Key:\tValue". +func extractIDFromStatusLine(line string) int { + fields := strings.Fields(line) + if len(fields) == 2 { + id, err := strconv.Atoi(fields[1]) + if err == nil { + return id + } + } + return -1 +} + +// findChildPIDs takes a parent PID and returns a slice of all descendant PIDs. +func findChildPIDs(parentPID int) ([]int, error) { + var childPIDs []int + + // Recursive helper function to find all descendants of a given PID. + var findDescendants func(int) + findDescendants = func(pid int) { + procDirs, err := os.ReadDir("/proc") + if err != nil { + return + } + + for _, procDir := range procDirs { + if !procDir.IsDir() { + continue + } + + childPid, err := strconv.Atoi(procDir.Name()) + if err != nil { + continue + } + + statusPath := filepath.Join("/proc", procDir.Name(), "status") + statusBytes, err := os.ReadFile(statusPath) + if err != nil { + continue + } + + status := string(statusBytes) + for _, line := range strings.Split(status, "\n") { + if strings.HasPrefix(line, "PPid:") { + fields := strings.Fields(line) + if len(fields) == 2 { + ppid, err := strconv.Atoi(fields[1]) + if err != nil { + break + } + if ppid == pid { + childPIDs = append(childPIDs, childPid) + findDescendants(childPid) + } + } + break + } + } + } + } + + // Start the recursion with the initial parent PID. + findDescendants(parentPID) + + return childPIDs, nil +} + +func GetPIDFromPort(_ context.Context, logger *zap.Logger, port int) (uint32, error) { + logger.Debug("Getting pid using port", zap.Int("port", port)) + + connections, err := netLib.Connections("inet") + if err != nil { + return 0, err + } + + for _, conn := range connections { + if conn.Status == "LISTEN" && conn.Laddr.Port == uint32(port) { + if conn.Pid > 0 { + return uint32(conn.Pid), nil + } + return 0, fmt.Errorf("pid %d is out of bounds", conn.Pid) + } + } + + // If we get here, no process was found using the given port + return 0, fmt.Errorf("no process found using port %d", port) +} + +func EnsureRmBeforeName(cmd string) string { + parts := strings.Split(cmd, " ") + rmIndex := -1 + nameIndex := -1 + + for i, part := range parts { + if part == "--rm" { + rmIndex = i + } else if part == "--name" { + nameIndex = i + break // Assuming --name will always have an argument, we can break here + } + } + if rmIndex == -1 && nameIndex != -1 { + parts = append(parts[:nameIndex], append([]string{"--rm"}, parts[nameIndex:]...)...) + } + + return strings.Join(parts, " ") +} + +func isGoBinary(logger *zap.Logger, filePath string) bool { + f, err := elf.Open(filePath) + if err != nil { + logger.Debug(fmt.Sprintf("failed to open file %s", filePath), zap.Error(err)) + return false + } + if err := f.Close(); err != nil { + LogError(logger, err, "failed to close file", zap.String("file", filePath)) + } + + // Check for section names typical to Go binaries + sections := []string{".go.buildinfo", ".gopclntab"} + for _, section := range sections { + if sect := f.Section(section); sect != nil { + fmt.Println(section) + return true + } + } + return false +} + +// DetectLanguage detects the language of the test command and returns the executable +func DetectLanguage(logger *zap.Logger, cmd string) (config.Language, string) { + if cmd == "" { + return models.Unknown, "" + } + fields := strings.Fields(cmd) + + // Find the actual executable by skipping environment variable assignments + executable := "" + for _, field := range fields { + // Skip environment variable assignments (KEY=VALUE format) + if strings.Contains(field, "=") && !strings.HasPrefix(field, "/") { + continue + } + // This is the actual executable + executable = field + break + } + + if executable == "" { + return models.Unknown, "" + } + + // Check for Python + pythonRegex := regexp.MustCompile(`(?i)(^|.*/)(python(\d+(\.\d+)*)?)$`) + if pythonRegex.MatchString(executable) { + return models.Python, executable + } + + // Check for Node.js + if executable == "node" || executable == "npm" || executable == "yarn" { + return models.Javascript, executable + } + + // Check for Java + if executable == "java" { + return models.Java, executable + } + + // Check for Go + if executable == "go" || (isGoBinary(logger, executable)) { + return models.Go, executable + } + + return models.Unknown, executable +} + +// FileExists checks if a file exists and is not a directory at the given path. +func FileExists(path string) (bool, error) { + fileInfo, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return fileInfo.Mode().IsRegular(), nil +} + +// ExpandPath expands a given path, replacing the tilde with the user's home directory +func ExpandPath(path string) (string, error) { + if strings.HasPrefix(path, "~/") { + homeDir, err := getHomeDir() + if err != nil { + return "", err + } + return strings.Replace(path, "~", homeDir, 1), nil + } + return path, nil +} + +// getHomeDir retrieves the appropriate home directory based on the execution context +func getHomeDir() (string, error) { + if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { + if usr, err := user.Lookup(sudoUser); err == nil { + return usr.HomeDir, nil + } + } + // Fallback to the current user's home directory + if usr, err := user.Current(); err == nil { + return usr.HomeDir, nil + } + // Fallback if neither method works + return "", errors.New("failed to retrieve current user info") +} + +func IsDockerCmd(kind CmdType) bool { + return (kind == DockerRun || kind == DockerStart || kind == DockerCompose) +} + +func AddToGitIgnore(logger *zap.Logger, path string, ignoreString string) error { + gitignorePath := path + "/.gitignore" + + file, err := os.OpenFile(gitignorePath, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return fmt.Errorf("error opening or creating .gitignore file: %v", err) + } + + defer func() { + if err := file.Close(); err != nil { + logger.Error("error closing .gitignore file: %v", zap.Error(err)) + } + }() + + scanner := bufio.NewScanner(file) + found := false + for scanner.Scan() { + if strings.TrimSpace(scanner.Text()) == ignoreString { + found = true + break + } + } + + if !found { + if _, err := file.WriteString("\n" + ignoreString + "\n"); err != nil { + return fmt.Errorf("error writing to .gitignore file: %v", err) + } + return nil + } + + return nil +} + +func Hash(data []byte) string { + hasher := sha256.New() + hasher.Write(data) + return hex.EncodeToString(hasher.Sum(nil)) +} + +func GetLastDirectory() (string, error) { + // Get the current working directory + dir, err := os.Getwd() + if err != nil { + return "", err + } + + // Extract the base (last directory) + lastDir := filepath.Base(dir) + return lastDir, nil +} + +func IsFileEmpty(filePath string) (bool, error) { + fileInfo, err := os.Stat(filePath) + if err != nil { + return false, err + } + return fileInfo.Size() == 0, nil +} + +func IsXMLResponse(resp *models.HTTPResp) bool { + if resp == nil || resp.Header == nil { + return false + } + + contentType, exists := resp.Header["Content-Type"] + if !exists || contentType == "" { + return false + } + return strings.Contains(contentType, "application/xml") || strings.Contains(contentType, "text/xml") +} + +// TrimSpaces removes unwanted spaces around unescaped ',' and '=' +func TrimSpaces(input string) string { + var output strings.Builder + var lastWasEscape bool // tracks if the previous rune was a backslash + var skippingSpaces bool // set when we just wrote a separator + + for _, ch := range input { + // after writing a separator, drop any spaces + if skippingSpaces { + if ch == ' ' { + continue + } + skippingSpaces = false + } + + // handle escape character + if ch == '\\' && !lastWasEscape { + lastWasEscape = true + output.WriteRune(ch) + continue + } + + // if this is an unescaped separator, trim before & skip after + if (ch == ',' || ch == '=') && !lastWasEscape { + // remove trailing spaces before the separator + trimmed := strings.TrimRight(output.String(), " ") + output.Reset() + output.WriteString(trimmed) + + // write the separator itself + output.WriteRune(ch) + + // skip any spaces that follow + skippingSpaces = true + lastWasEscape = false + continue + } + + // normal character (or escaped separator) + output.WriteRune(ch) + lastWasEscape = false + } + + return output.String() +} + +func ParseMetadata(metadataStr string) (map[string]interface{}, error) { + if metadataStr == "" { + return nil, nil + } + m := make(map[string]interface{}) + if err := strvals.ParseInto(metadataStr, m); err != nil { + return nil, fmt.Errorf("cannot parse metadata: %w", err) + } + return m, nil +} + +// RenderTemplatesInString finds all template placeholders (e.g., {{.name}} or {{string .name}}) in a string, +// executes them with the provided data, and replaces them with the result. +// It is robust against strings that contain non-template curly braces by using a strict regex. +func RenderTemplatesInString(logger *zap.Logger, input string, templateData map[string]interface{}) (string, error) { + // This regex is specifically designed to match valid Keploy templates: + // - It must start with {{ and optional whitespace. + // - It can optionally have a function call ("string", "int", "float") followed by whitespace. + // - It MUST contain a dot (.) to indicate a field access. + // - It non-greedily matches characters until the closing braces. + // This prevents it from matching invalid syntax like {{u^2}}. + re := regexp.MustCompile(`\{\{\s*(?:string\s+|int\s+|float\s+)?\.[^{}]*?\}\}`) + + funcMap := template.FuncMap{ + "int": ToInt, + "string": ToString, + "float": ToFloat, + } + + var firstErr error + + result := re.ReplaceAllStringFunc(input, func(match string) string { + // Only parse and execute the matched placeholder, not the entire string. + tmpl, err := template.New("sub").Funcs(funcMap).Parse(match) + if err != nil { + + logger.Debug("failed to parse a valid-looking template placeholder", zap.String("placeholder", match), zap.Error(err)) + return match + } + + var output bytes.Buffer + err = tmpl.Execute(&output, templateData) + if err != nil { + if firstErr == nil { + firstErr = fmt.Errorf("failed to execute template placeholder '%s': %v", match, err) + } + return match + } + + return output.String() + }) + + return result, firstErr +} + +// // XMLToMap converts an XML string into a map[string]interface{} +// func XMLToMap(data string) (map[string]any, error) { +// mv, err := mxj.NewMapXml([]byte(data)) +// if err != nil { +// return nil, err +// } +// return mv, nil +// } + +// // MapToXML converts a map[string]interface{} into an XML string +// func MapToXML(data map[string]any) (string, error) { +// mv := mxj.Map(data) +// xmlBytes, err := mv.Xml() +// if err != nil { +// return "", err +// } +// return string(xmlBytes), nil +// } diff --git a/keploy/utils/utils_test.go b/keploy/utils/utils_test.go new file mode 100644 index 0000000..2f57d9b --- /dev/null +++ b/keploy/utils/utils_test.go @@ -0,0 +1,348 @@ +package utils + +import ( + "testing" + + "os" + "path/filepath" + "strings" + + "net/http" + "net/url" + + "context" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" +) + +// TestReplaceHost_ValidAndInvalidInputs_001 tests ReplaceHost function with valid and invalid inputs. +func TestReplaceHost_ValidAndInvalidInputs_001(t *testing.T) { + validURL := "http://example.com" + invalidURL := "://invalid-url" + ipAddress := "192.168.1.1" + + // Test valid URL + result, err := ReplaceHost(validURL, ipAddress) + require.NoError(t, err) + assert.Equal(t, "http://192.168.1.1", result) + + // Test invalid URL + result, err = ReplaceHost(invalidURL, ipAddress) + require.Error(t, err) + assert.Equal(t, invalidURL, result) + + // Test empty IP address + result, err = ReplaceHost(validURL, "") + require.Error(t, err) + assert.Equal(t, validURL, result) +} + +// TestReplaceBaseURL_ValidAndInvalidInputs_002 tests ReplaceBaseURL function with valid and invalid inputs. +func TestReplaceBaseURL_ValidAndInvalidInputs_002(t *testing.T) { + validURL := "http://example.com/path" + baseURL := "https://newbase.com" + invalidBaseURL := "://invalid-base-url" + + // Test valid baseURL + result, err := ReplaceBaseURL(validURL, baseURL) + require.NoError(t, err) + assert.Equal(t, "https://newbase.com/path", result) + + // Test invalid baseURL + result, err = ReplaceBaseURL(validURL, invalidBaseURL) + require.Error(t, err) + assert.Equal(t, validURL, result) + + // Test empty baseURL + result, err = ReplaceBaseURL(validURL, "") + require.Error(t, err) + assert.Equal(t, validURL, result) +} + +// TestPathAndFileFunctions_AllCases_114 tests functions related to path manipulation and file system interactions. +func TestPathAndFileFunctions_AllCases_114(t *testing.T) { + logger := zap.NewNop() + + t.Run("GetAbsPath and ToAbsPath", func(t *testing.T) { + absPath, err := GetAbsPath(".") + require.NoError(t, err) + assert.True(t, filepath.IsAbs(absPath)) + + toAbs := ToAbsPath(logger, "some/relative/path") + assert.True(t, filepath.IsAbs(toAbs)) + assert.True(t, strings.HasSuffix(toAbs, "/some/relative/path/keploy")) + + toAbsEmpty := ToAbsPath(logger, "") + assert.True(t, filepath.IsAbs(toAbsEmpty)) + assert.True(t, strings.HasSuffix(toAbsEmpty, "/keploy")) + }) + + t.Run("makeDirectory and DeleteFileIfNotExists", func(t *testing.T) { + tempDir := t.TempDir() + newDir := filepath.Join(tempDir, "newdir") + err := makeDirectory(newDir) + require.NoError(t, err) + _, err = os.Stat(newDir) + assert.NoError(t, err) + + newFile := filepath.Join(tempDir, "newfile.txt") + err = os.WriteFile(newFile, []byte("content"), 0644) + require.NoError(t, err) + err = DeleteFileIfNotExists(logger, newFile) + require.NoError(t, err) + _, err = os.Stat(newFile) + assert.True(t, os.IsNotExist(err)) + + err = DeleteFileIfNotExists(logger, "/non/existent/file") + require.NoError(t, err) + }) + + t.Run("CheckFileExists and FileExists", func(t *testing.T) { + tempDir := t.TempDir() + existingFile := filepath.Join(tempDir, "exists.txt") + err := os.WriteFile(existingFile, []byte("content"), 0644) + require.NoError(t, err) + + assert.True(t, CheckFileExists(existingFile)) + assert.False(t, CheckFileExists(filepath.Join(tempDir, "notexists.txt"))) + + exists, err := FileExists(existingFile) + require.NoError(t, err) + assert.True(t, exists) + + exists, err = FileExists(tempDir) // is a directory + require.NoError(t, err) + assert.False(t, exists) + }) + + t.Run("IsFileEmpty", func(t *testing.T) { + tempDir := t.TempDir() + emptyFile := filepath.Join(tempDir, "empty.txt") + err := os.WriteFile(emptyFile, []byte{}, 0644) + require.NoError(t, err) + nonEmptyFile := filepath.Join(tempDir, "nonempty.txt") + err = os.WriteFile(nonEmptyFile, []byte("data"), 0644) + require.NoError(t, err) + + isEmpty, err := IsFileEmpty(emptyFile) + require.NoError(t, err) + assert.True(t, isEmpty) + + isEmpty, err = IsFileEmpty(nonEmptyFile) + require.NoError(t, err) + assert.False(t, isEmpty) + + _, err = IsFileEmpty("nonexistent.txt") + require.Error(t, err) + }) + + t.Run("GetLastDirectory", func(t *testing.T) { + // This is hard to test reliably, but we can check it doesn't error + _, err := GetLastDirectory() + assert.NoError(t, err) + }) +} + +// TestTypeConversionFunctions_AllCases_116 tests the type conversion utility functions ToInt, ToString, and ToFloat. +func TestTypeConversionFunctions_AllCases_116(t *testing.T) { + t.Run("ToInt", func(t *testing.T) { + assert.Equal(t, 123, ToInt(123)) + assert.Equal(t, 45, ToInt("45")) + assert.Equal(t, 78, ToInt(78.9)) + assert.Equal(t, 0, ToInt("abc")) + assert.Equal(t, 0, ToInt(nil)) + }) + t.Run("ToString", func(t *testing.T) { + assert.Equal(t, "123", ToString(123)) + assert.Equal(t, "45.6", ToString(45.6)) + assert.Equal(t, "hello", ToString("hello")) + assert.Equal(t, "789", ToString(int64(789))) + assert.Equal(t, "1234", ToString(int32(1234))) + assert.Equal(t, "3.14", ToString(float32(3.14))) + assert.Equal(t, "", ToString(nil)) + }) + t.Run("ToFloat", func(t *testing.T) { + assert.Equal(t, 123.0, ToFloat(123)) + assert.Equal(t, 45.6, ToFloat("45.6")) + assert.Equal(t, 78.9, ToFloat(78.9)) + assert.Equal(t, 0.0, ToFloat("abc")) + assert.Equal(t, 0.0, ToFloat(nil)) + }) +} + +// TestConfigAndViper_AllCases_119 tests configuration related functions, including flag binding with Viper. +func TestConfigAndViper_AllCases_119(t *testing.T) { + logger := zap.NewNop() + + t.Run("BindFlagsToViper", func(t *testing.T) { + viper.Reset() + cmd := &cobra.Command{Use: "testcmd"} + cmd.Flags().String("my-flag", "default", "a test flag") + cmd.Flags().Int("another-flag", 123, "another flag") + + err := BindFlagsToViper(logger, cmd, "keploy") + require.NoError(t, err) + + assert.Equal(t, "default", viper.GetString("keploy.myFlag")) + assert.Equal(t, 123, viper.GetInt("keploy.anotherFlag")) + + // Test env var binding + os.Setenv("KEPLOY_MYFLAG", "from_env") + defer os.Unsetenv("KEPLOY_MYFLAG") + // Re-bind to pick up env var + err = BindFlagsToViper(logger, cmd, "keploy") + require.NoError(t, err) + assert.Equal(t, "from_env", viper.GetString("keploy.myFlag")) + }) + + t.Run("SetCoveragePath", func(t *testing.T) { + tempDir := t.TempDir() + // Case 1: Empty path + covPath, err := SetCoveragePath(logger, "") + require.NoError(t, err) + assert.Contains(t, covPath, "coverage-reports") + os.RemoveAll(covPath) // clean up + + // Case 2: Valid directory + covPath, err = SetCoveragePath(logger, tempDir) + require.NoError(t, err) + assert.Equal(t, tempDir, covPath) + + // Case 3: Path is a file + file, err := os.Create(filepath.Join(tempDir, "file.txt")) + require.NoError(t, err) + file.Close() + _, err = SetCoveragePath(logger, file.Name()) + require.Error(t, err) + + // Case 4: Path does not exist + _, err = SetCoveragePath(logger, filepath.Join(tempDir, "nonexistent")) + require.Error(t, err) + }) +} + +// TestIsPassThrough_AllCases_121 tests the IsPassThrough function with various rules and request combinations. +func TestIsPassThrough_AllCases_121(t *testing.T) { + logger := zap.NewNop() + req, _ := http.NewRequest("GET", "http://example.com/path/123", nil) + req.Host = "example.com:8080" + + tests := []struct { + name string + opts models.OutgoingOptions + destPort uint + want bool + }{ + { + name: "match host and port", + opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: "example.com", Port: 8080}}}, + destPort: 8080, + want: true, + }, + { + name: "match host regex", + opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: `^ex.*\.com$`, Port: 8080}}}, + destPort: 8080, + want: true, + }, + { + name: "match path regex", + opts: models.OutgoingOptions{Rules: []config.BypassRule{{Path: `/path/\d+$`}}}, + destPort: 80, + want: true, + }, + { + name: "match host but not port", + opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: "example.com", Port: 9090}}}, + destPort: 8080, + want: false, + }, + { + name: "no match", + opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: "google.com"}}}, + destPort: 8080, + want: false, + }, + { + name: "match with port 0", + opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: "example.com", Port: 0}}}, + destPort: 8080, + want: true, + }, + { + name: "invalid host regex", + opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: `[`}}}, + destPort: 8080, + want: false, + }, + { + name: "invalid path regex", + opts: models.OutgoingOptions{Rules: []config.BypassRule{{Path: `[`}}}, + destPort: 8080, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Since req.URL.String() includes the host, we need to parse it correctly for path matching + parsedURL, _ := url.Parse(req.URL.String()) + req.URL = parsedURL + req.Host = "example.com" // Host should not contain port for regex matching against host rule + + got := IsPassThrough(logger, req, tt.destPort, tt.opts) + assert.Equal(t, tt.want, got) + }) + } +} + +// TestAskForConfirmation_AllCases_122 tests the user confirmation prompt function under various conditions. +func TestAskForConfirmation_AllCases_122(t *testing.T) { + originalStdin := os.Stdin + defer func() { os.Stdin = originalStdin }() + + runTest := func(input string, expected bool, expectErr bool) { + r, w, _ := os.Pipe() + os.Stdin = r + go func() { + defer w.Close() + w.Write([]byte(input + "\n")) + }() + + // Capture stdout to prevent printing to console during test + oldStdout := os.Stdout + devNull, _ := os.Open(os.DevNull) + os.Stdout = devNull + defer func() { + os.Stdout = oldStdout + devNull.Close() + }() + + got, err := AskForConfirmation(context.Background(), "Confirm?") + if expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, expected, got) + } + } + + t.Run("confirm with y", func(t *testing.T) { runTest("y", true, false) }) + t.Run("confirm with yes", func(t *testing.T) { runTest("yes", true, false) }) + t.Run("decline with n", func(t *testing.T) { runTest("n", false, false) }) + t.Run("decline with other", func(t *testing.T) { runTest("anything else", false, false) }) + + t.Run("context cancelled", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + got, err := AskForConfirmation(ctx, "Confirm?") + require.NoError(t, err) + assert.False(t, got) + }) +} diff --git a/scripts/user_service_flow.sh b/scripts/user_service_flow.sh new file mode 100755 index 0000000..561357e --- /dev/null +++ b/scripts/user_service_flow.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +# User Service flow using curl based on postman/collection.microservices.json +# Endpoints covered: +# - POST /login (get JWT) +# - POST /users (create user) +# - POST /users/{id}/addresses (create default address) +# - GET /users/{id}/addresses (list addresses) +# - GET /users/{id} (get user) +# - DELETE /users/{id} (delete user) +# +# Requirements: curl, jq +# +# Env overrides (with sensible defaults): +# USER_BASE - default http://localhost:8082/api/v1 +# USERNAME - default alice +# EMAIL - default alice@example.com +# PASSWORD - default p@ssw0rd +# ADDRESS_* vars - override address fields if needed +# SKIP_DELETE=1 - if set, skip deleting the created user +# +set -euo pipefail + +if ! command -v curl >/dev/null 2>&1; then + echo "Error: curl is required" >&2; exit 1 +fi +if ! command -v jq >/dev/null 2>&1; then + echo "Error: jq is required (e.g. sudo apt-get install jq)" >&2; exit 1 +fi + +USER_BASE=${USER_BASE:-"http://localhost:8082/api/v1"} +USERNAME=${USERNAME:-"alice"} +EMAIL=${EMAIL:-"alice@example.com"} +PASSWORD=${PASSWORD:-"p@ssw0rd"} + +# Address defaults (matches Postman example) +ADDRESS_LINE1=${ADDRESS_LINE1:-"1 Main St"} +ADDRESS_CITY=${ADDRESS_CITY:-"NYC"} +ADDRESS_STATE=${ADDRESS_STATE:-"NY"} +ADDRESS_POSTAL=${ADDRESS_POSTAL:-"10001"} +ADDRESS_COUNTRY=${ADDRESS_COUNTRY:-"US"} +ADDRESS_PHONE=${ADDRESS_PHONE:-"+1-555-0000"} +ADDRESS_IS_DEFAULT=${ADDRESS_IS_DEFAULT:-true} + +# Slight randomness to avoid collisions when the backend enforces unique usernames/emails +RAND_SUFFIX=$(printf "%04d" $((RANDOM % 10000))) +RAND_EMAIL_TAG=$(date +%s) +USERNAME_UNIQ=${USERNAME}-${RAND_SUFFIX} +EMAIL_UNIQ=${EMAIL/@@/@} # noop if not placeholder +if [[ "$EMAIL" == *"@"* ]]; then + EMAIL_UNIQ="${EMAIL%%@*}+${RAND_EMAIL_TAG}@${EMAIL##*@}" +fi + +SEP="__HTTP_STATUS__SEPARATOR__" + +req() { + # Usage: req METHOD URL DATA_JSON [AUTH_TOKEN] + local method="$1" url="$2" data="${3:-}" token="${4:-}" + local headers=("-H" "Accept: application/json") + if [[ -n "$data" ]]; then + headers+=("-H" "Content-Type: application/json" "--data" "$data") + fi + if [[ -n "$token" ]]; then + headers+=("-H" "Authorization: Bearer $token") + fi + # -sS silent but show errors, include http code with delimiter + curl -sS -X "$method" "$url" "${headers[@]}" -w "${SEP}%{http_code}" +} + +handle_response() { + # Split body and status using our delimiter + local resp="$1" + local http_code body + http_code="${resp##*${SEP}}" + body="${resp%${SEP}*}" + echo "$http_code" "$body" +} + +say() { printf "\n==> %s\n" "$*"; } + +say "User base: $USER_BASE" + +# 1) Login to get JWT +say "Logging in to get JWT" +LOGIN_PAYLOAD=$(jq -n --arg u "$USERNAME" --arg p "$PASSWORD" '{username: $u, password: $p}') +resp=$(req POST "$USER_BASE/login" "$LOGIN_PAYLOAD") +read -r code body < <(handle_response "$resp") +echo "HTTP $code" +echo "$body" | jq '.' || true +if [[ "$code" != "200" && "$code" != "201" ]]; then + echo "Error: login failed (HTTP $code)" >&2; exit 1 +fi +JWT=$(echo "$body" | jq -r '.token // empty') +if [[ -z "${JWT}" || "$JWT" == "null" ]]; then + echo "Error: token not found in login response" >&2; exit 1 +fi +say "JWT acquired" + +# 2) Create user (authorized) +say "Creating user: $USERNAME_UNIQ ($EMAIL_UNIQ)" +CREATE_USER_PAYLOAD=$(jq -n \ + --arg u "$USERNAME_UNIQ" \ + --arg e "$EMAIL_UNIQ" \ + --arg p "$PASSWORD" \ + '{username: $u, email: $e, password: $p}') +resp=$(req POST "$USER_BASE/users" "$CREATE_USER_PAYLOAD" "$JWT") +read -r code body < <(handle_response "$resp") +echo "HTTP $code" +echo "$body" | jq '.' || true +if [[ "$code" != "200" && "$code" != "201" ]]; then + echo "Warning: create user returned HTTP $code; continuing" >&2 +fi +USER_ID=$(echo "$body" | jq -r '.id // empty') +if [[ -z "$USER_ID" || "$USER_ID" == "null" ]]; then + # Try to login again with the unique username to fetch a user profile if backend supports it + USER_ID=$(echo "$body" | jq -r '.user.id // empty') +fi +if [[ -z "$USER_ID" || "$USER_ID" == "null" ]]; then + echo "Warning: user id not returned; attempting to fetch via GET /users (may not be supported)" >&2 +fi + +# 3) Add default address if we have a user id +if [[ -n "${USER_ID:-}" ]]; then + say "Adding default address for user $USER_ID" + ADDR_PAYLOAD=$(jq -n \ + --arg l1 "$ADDRESS_LINE1" \ + --arg city "$ADDRESS_CITY" \ + --arg st "$ADDRESS_STATE" \ + --arg pc "$ADDRESS_POSTAL" \ + --arg ctry "$ADDRESS_COUNTRY" \ + --arg ph "$ADDRESS_PHONE" \ + --argjson def "$ADDRESS_IS_DEFAULT" \ + '{line1: $l1, city: $city, state: $st, postal_code: $pc, country: $ctry, phone: $ph, is_default: $def}') + resp=$(req POST "$USER_BASE/users/$USER_ID/addresses" "$ADDR_PAYLOAD" "$JWT") + read -r code body < <(handle_response "$resp") + echo "HTTP $code" + echo "$body" | jq '.' || true + ADDRESS_ID=$(echo "$body" | jq -r '.id // empty') +else + echo "Skipping address creation (no USER_ID)" >&2 +fi + +# 4) List addresses (if we have a user id) +if [[ -n "${USER_ID:-}" ]]; then + say "Listing addresses for user $USER_ID" + resp=$(req GET "$USER_BASE/users/$USER_ID/addresses" "" "$JWT") + read -r code body < <(handle_response "$resp") + echo "HTTP $code" + echo "$body" | jq '.' || true +fi + +# 5) Get user (if we have a user id) +if [[ -n "${USER_ID:-}" ]]; then + say "Fetching user $USER_ID" + resp=$(req GET "$USER_BASE/users/$USER_ID" "" "$JWT") + read -r code body < <(handle_response "$resp") + echo "HTTP $code" + echo "$body" | jq '.' || true +fi + +# 6) Optionally delete the user +if [[ -z "${SKIP_DELETE:-}" && -n "${USER_ID:-}" ]]; then + say "Deleting user $USER_ID (set SKIP_DELETE=1 to keep)" + resp=$(req DELETE "$USER_BASE/users/$USER_ID" "" "$JWT") + read -r code body < <(handle_response "$resp") + echo "HTTP $code" + if [[ "$code" == "200" || "$code" == "404" ]]; then + echo "Delete completed (ok or not found)" + else + echo "$body" | jq '.' || true + fi +else + say "Skip delete (SKIP_DELETE=1 or missing USER_ID)" +fi + +say "Done." diff --git a/scripts/user_service_manual_tests.sh b/scripts/user_service_manual_tests.sh new file mode 100755 index 0000000..1498ade --- /dev/null +++ b/scripts/user_service_manual_tests.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# Replay user_service/keploy/manual/tests (test-1..test-5) as curl commands +# +# test-1: POST /login (admin/admin123) +# test-2: POST /users (alice) +# test-3: POST /users/{userId}/addresses (default) +# test-4: GET /users/{userId}/addresses +# test-5: GET /users/{userId} +# +# Requirements: curl, jq +# Overrides via env: +# USER_BASE=http://localhost:8082/api/v1 +# ADMIN_USER=admin +# ADMIN_PASS=admin123 +# NEW_USERNAME=alice +# NEW_EMAIL=alice@example.com +# NEW_PASSWORD='p@ssw0rd' +set -euo pipefail + +if ! command -v curl >/dev/null 2>&1; then echo "curl required" >&2; exit 1; fi +if ! command -v jq >/dev/null 2>&1; then echo "jq required (sudo apt-get install jq)" >&2; exit 1; fi + +USER_BASE=${USER_BASE:-"http://localhost:8082/api/v1"} +ADMIN_USER=${ADMIN_USER:-"admin"} +ADMIN_PASS=${ADMIN_PASS:-"admin123"} +NEW_USERNAME=${NEW_USERNAME:-"alice"} +NEW_EMAIL=${NEW_EMAIL:-"alice@example.com"} +NEW_PASSWORD=${NEW_PASSWORD:-"p@ssw0rd"} + +say(){ printf "\n==> %s\n" "$*"; } +SEP="__SEP__HTTP__" + +req(){ + # req METHOD URL DATA_JSON [AUTH_TOKEN] + local method="$1" url="$2" data="${3:-}" token="${4:-}"; + local args=( -sS -X "$method" "$url" -H "Accept: application/json" ); + if [[ -n "$data" ]]; then + args+=( -H "Content-Type: application/json" --data "$data" ); + fi + if [[ -n "$token" ]]; then + args+=( -H "Authorization: Bearer $token" ); + fi + curl "${args[@]}" -w "${SEP}%{http_code}" +} + +parse(){ + local resp="$1"; local code body; + code="${resp##*${SEP}}"; body="${resp%${SEP}*}"; + echo "$code" "$body"; +} + +echo "USER_BASE=$USER_BASE" + +# test-1: Login +say "test-1: POST /login" +LOGIN_PAYLOAD=$(jq -n --arg u "$ADMIN_USER" --arg p "$ADMIN_PASS" '{username:$u,password:$p}') +resp=$(req POST "$USER_BASE/login" "$LOGIN_PAYLOAD") +read -r code body < <(parse "$resp"); echo "HTTP $code"; echo "$body" | jq '.' || true +if [[ "$code" != 200 && "$code" != 201 ]]; then echo "Login failed" >&2; exit 1; fi +TOKEN=$(echo "$body" | jq -r '.token // empty'); [[ -n "$TOKEN" ]] || { echo "No token" >&2; exit 1; } + +# test-2: Create user +say "test-2: POST /users (create $NEW_USERNAME)" +# Avoid unique constraint conflicts by default; insert a short suffix. +# Disable by setting DISABLE_RANDOM_SUFFIX=1 +if [[ -z "${DISABLE_RANDOM_SUFFIX:-}" ]]; then + TS=$(date +%s) + SUFFIX="-${TS}-${RANDOM}" + USERNAME_EFF="${NEW_USERNAME}${SUFFIX}" + if [[ "$NEW_EMAIL" == *"@"* ]]; then + EMAIL_EFF="${NEW_EMAIL%%@*}${SUFFIX}@${NEW_EMAIL##*@}" + else + EMAIL_EFF="${NEW_EMAIL}${SUFFIX}" + fi +else + USERNAME_EFF="$NEW_USERNAME"; EMAIL_EFF="$NEW_EMAIL" +fi +echo "Using username: $USERNAME_EFF" +echo "Using email : $EMAIL_EFF" +CREATE_PAYLOAD=$(jq -n --arg u "$USERNAME_EFF" --arg e "$EMAIL_EFF" --arg p "$NEW_PASSWORD" '{username:$u,email:$e,password:$p}') +resp=$(req POST "$USER_BASE/users" "$CREATE_PAYLOAD" "$TOKEN") +read -r code body < <(parse "$resp"); echo "HTTP $code"; echo "$body" | jq '.' || true +if [[ "$code" != 200 && "$code" != 201 ]]; then + echo "Create user returned $code (continuing)" >&2 + # Common cause: unique constraint violation on username/email when reusing same inputs. + # You can set DISABLE_RANDOM_SUFFIX= to keep inputs stable, or change NEW_USERNAME/NEW_EMAIL. +fi +USER_ID=$(echo "$body" | jq -r '.id // empty'); +if [[ -z "$USER_ID" || "$USER_ID" == null ]]; then echo "No user id in response, cannot continue" >&2; exit 1; fi + +# test-3: Add address +say "test-3: POST /users/$USER_ID/addresses (default)" +ADDR_PAYLOAD='{"line1":"1 Main St","city":"NYC","state":"NY","postal_code":"10001","country":"US","phone":"+1-555-0000","is_default":true}' +resp=$(req POST "$USER_BASE/users/$USER_ID/addresses" "$ADDR_PAYLOAD" "$TOKEN") +read -r code body < <(parse "$resp"); echo "HTTP $code"; echo "$body" | jq '.' || true +ADDR_ID=$(echo "$body" | jq -r '.id // empty') || true + +# test-4: List addresses +say "test-4: GET /users/$USER_ID/addresses" +resp=$(req GET "$USER_BASE/users/$USER_ID/addresses" "" "$TOKEN") +read -r code body < <(parse "$resp"); echo "HTTP $code"; echo "$body" | jq '.' || true + +# test-5: Get user +say "test-5: GET /users/$USER_ID" +resp=$(req GET "$USER_BASE/users/$USER_ID" "" "$TOKEN") +read -r code body < <(parse "$resp"); echo "HTTP $code"; echo "$body" | jq '.' || true + +say "Done (manual tests 1-5 replayed)." diff --git a/user_service/keploy-logs.txt b/user_service/keploy-logs.txt new file mode 100755 index 0000000..7aa38a7 --- /dev/null +++ b/user_service/keploy-logs.txt @@ -0,0 +1,15 @@ +🐰 Keploy: 2025-11-10T13:02:03Z WARN AppName in config (microservices) does not match current directory name (user_service). using current directory name as appName +🐰 Keploy: 2025-11-10T13:02:03Z INFO Parsing TestSuite File {"path": "keploy/testsuite/suite-0.yaml"} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Starting load test {"vus": 50, "duration": "5m", "rps": 0} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Parsing TestSuite File {"path": "keploy/testsuite/suite-0.yaml"} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Skipping security checks for failed step {"stepName": "Login_User", "failureReason": "expected status code 200 but got 401"} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Skipping security checks for failed step {"stepName": "Create_Address", "failureReason": "expected status code 201 but got 404"} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Skipping security checks for failed step {"stepName": "Create_Order", "failureReason": "expected status code 201 but got 404"} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Skipping security checks for failed step {"stepName": "Get_Order_Details", "failureReason": "expected status code 200 but got 404"} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Skipping security checks for failed step {"stepName": "Delete_User", "failureReason": "expected status code 204 but got 404"} +🐰 Keploy: 2025-11-10T13:02:03Z WARN Unknown ruleset, falling back to basic {"ruleset": "strict"} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Starting dashboard server {"URL": "http://localhost:3000"} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Opening browser on dashboard {"URL": "http://localhost:3000/?dashboard=20251110_130203"} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Metrics server starting on port {"port": 9090} +🐰 Keploy: 2025-11-10T13:02:03Z INFO Starting token server on port 2345 +🐰 Keploy: 2025-11-10T13:02:03Z ERROR Dashboard server failed {"error": "listen tcp :3000: bind: address already in use"} diff --git a/user_service/keploy/atg/mocks.yaml b/user_service/keploy/atg/mocks.yaml new file mode 100755 index 0000000..09033da --- /dev/null +++ b/user_service/keploy/atg/mocks.yaml @@ -0,0 +1,121443 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-0 +spec: + metadata: + connID: "0" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [93, 45, 95, 242, 103, 141, 51, 196, 21, 178, 106, 136, 44, 44, 24, 165, 119, 70, 94, 208, 62, 254, 126, 80, 91, 12, 133, 50, 155, 3, 167, 231] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1059" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + - header: + header: + payload_length: 9 + sequence_id: 4 + packet_type: plain_password + message: cGFzc3dvcmQA + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 9 + auth_plugin_data: [111, 106, 3, 127, 126, 87, 5, 4, 19, 58, 115, 80, 2, 60, 29, 69, 62, 32, 18, 116, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: PerformFullAuthentication + - header: + header: + payload_length: 19 + sequence_id: 5 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776550 + reqtimestampmock: 2025-11-10T12:09:10.262787101Z + restimestampmock: 2025-11-10T12:09:10.275262328Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1 +spec: + metadata: + connID: "0" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776550 + reqtimestampmock: 2025-11-10T12:09:10.275731573Z + restimestampmock: 2025-11-10T12:09:10.275945481Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2 +spec: + metadata: + connID: "0" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776550 + reqtimestampmock: 2025-11-10T12:09:10.2760796Z + restimestampmock: 2025-11-10T12:09:10.276228929Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-3 +spec: + metadata: + connID: "0" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776550 + reqtimestampmock: 2025-11-10T12:09:10.276360817Z + restimestampmock: 2025-11-10T12:09:10.276505775Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-4 +spec: + metadata: + connID: "0" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 241 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: "\n CREATE TABLE IF NOT EXISTS schema_migrations (\n id INT AUTO_INCREMENT PRIMARY KEY,\n version VARCHAR(64) NOT NULL UNIQUE,\n applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP\n )\n " + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 1 + info: "" + created: 1762776550 + reqtimestampmock: 2025-11-10T12:09:10.276652605Z + restimestampmock: 2025-11-10T12:09:10.283838223Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-5 +spec: + metadata: + connID: "0" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 40 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT version FROM schema_migrations + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 77 + sequence_id: 2 + catalog: def + schema: user_db + table: schema_migrations + org_table: schema_migrations + name: version + org_name: version + fixed_length: 12 + character_set: 255 + column_length: 256 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 5 + sequence_id: 3 + values: + - type: 253 + name: version + value: "0001" + unsigned: false + - header: + payload_length: 5 + sequence_id: 4 + values: + - type: 253 + name: version + value: "0002" + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 5 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776550 + reqtimestampmock: 2025-11-10T12:09:10.284011172Z + restimestampmock: 2025-11-10T12:09:10.284947313Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-6 +spec: + metadata: + connID: "2" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [220, 69, 35, 64, 60, 254, 62, 35, 181, 64, 165, 2, 125, 189, 114, 99, 109, 163, 14, 15, 34, 103, 165, 177, 177, 32, 239, 235, 118, 56, 176, 68] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 60 + auth_plugin_data: [45, 46, 39, 85, 15, 103, 3, 77, 65, 104, 127, 118, 19, 51, 114, 45, 19, 34, 2, 24, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776804 + reqtimestampmock: 2025-11-10T12:13:24.928394232Z + restimestampmock: 2025-11-10T12:13:24.93890796Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-7 +spec: + metadata: + connID: "2" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776804 + reqtimestampmock: 2025-11-10T12:13:24.939384175Z + restimestampmock: 2025-11-10T12:13:24.939602423Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-8 +spec: + metadata: + connID: "2" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776804 + reqtimestampmock: 2025-11-10T12:13:24.939746603Z + restimestampmock: 2025-11-10T12:13:24.939899201Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-9 +spec: + metadata: + connID: "2" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776804 + reqtimestampmock: 2025-11-10T12:13:24.940018619Z + restimestampmock: 2025-11-10T12:13:24.940157058Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-10 +spec: + metadata: + connID: "2" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776804 + reqtimestampmock: 2025-11-10T12:13:24.940384846Z + restimestampmock: 2025-11-10T12:13:24.943135502Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-11 +spec: + metadata: + connID: "4" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [53, 190, 152, 44, 52, 45, 239, 101, 74, 105, 121, 148, 79, 73, 5, 200, 62, 68, 158, 198, 83, 15, 56, 174, 147, 204, 143, 165, 58, 49, 148, 82] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 62 + auth_plugin_data: [106, 108, 108, 100, 95, 6, 84, 16, 47, 27, 56, 101, 71, 20, 13, 85, 87, 113, 81, 121, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776811 + reqtimestampmock: 2025-11-10T12:13:31.193208483Z + restimestampmock: 2025-11-10T12:13:31.199525187Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-12 +spec: + metadata: + connID: "4" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776811 + reqtimestampmock: 2025-11-10T12:13:31.199707496Z + restimestampmock: 2025-11-10T12:13:31.199909294Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-13 +spec: + metadata: + connID: "4" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776811 + reqtimestampmock: 2025-11-10T12:13:31.200042313Z + restimestampmock: 2025-11-10T12:13:31.2002338Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-14 +spec: + metadata: + connID: "4" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776811 + reqtimestampmock: 2025-11-10T12:13:31.200396849Z + restimestampmock: 2025-11-10T12:13:31.200488598Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-15 +spec: + metadata: + connID: "4" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776811 + reqtimestampmock: 2025-11-10T12:13:31.200687947Z + restimestampmock: 2025-11-10T12:13:31.201132533Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-16 +spec: + metadata: + connID: "6" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [64, 175, 32, 245, 66, 94, 98, 82, 84, 103, 61, 74, 8, 177, 221, 96, 222, 204, 93, 212, 135, 5, 32, 130, 239, 26, 54, 71, 56, 100, 227, 66] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 65 + auth_plugin_data: [39, 5, 23, 12, 127, 56, 71, 120, 102, 41, 64, 46, 78, 99, 80, 82, 18, 48, 110, 70, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776817 + reqtimestampmock: 2025-11-10T12:13:37.141535202Z + restimestampmock: 2025-11-10T12:13:37.148642659Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-17 +spec: + metadata: + connID: "6" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776817 + reqtimestampmock: 2025-11-10T12:13:37.148893937Z + restimestampmock: 2025-11-10T12:13:37.149096865Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-18 +spec: + metadata: + connID: "6" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776817 + reqtimestampmock: 2025-11-10T12:13:37.149255033Z + restimestampmock: 2025-11-10T12:13:37.149463991Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-19 +spec: + metadata: + connID: "6" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776817 + reqtimestampmock: 2025-11-10T12:13:37.149619181Z + restimestampmock: 2025-11-10T12:13:37.14972578Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-20 +spec: + metadata: + connID: "6" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776817 + reqtimestampmock: 2025-11-10T12:13:37.149901307Z + restimestampmock: 2025-11-10T12:13:37.150335284Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-21 +spec: + metadata: + connID: "8" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [252, 193, 224, 38, 251, 3, 239, 214, 123, 116, 81, 48, 67, 124, 169, 154, 93, 157, 2, 153, 101, 61, 116, 165, 114, 16, 19, 36, 98, 236, 187, 97] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 67 + auth_plugin_data: [106, 101, 71, 127, 59, 69, 73, 29, 125, 126, 73, 64, 12, 101, 72, 118, 7, 48, 121, 106, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776823 + reqtimestampmock: 2025-11-10T12:13:43.21542866Z + restimestampmock: 2025-11-10T12:13:43.221431407Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-22 +spec: + metadata: + connID: "8" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776823 + reqtimestampmock: 2025-11-10T12:13:43.221627155Z + restimestampmock: 2025-11-10T12:13:43.221788793Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-23 +spec: + metadata: + connID: "8" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776823 + reqtimestampmock: 2025-11-10T12:13:43.221884952Z + restimestampmock: 2025-11-10T12:13:43.222100691Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-24 +spec: + metadata: + connID: "8" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776823 + reqtimestampmock: 2025-11-10T12:13:43.222289279Z + restimestampmock: 2025-11-10T12:13:43.222398208Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-25 +spec: + metadata: + connID: "8" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776823 + reqtimestampmock: 2025-11-10T12:13:43.222600896Z + restimestampmock: 2025-11-10T12:13:43.223188811Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-26 +spec: + metadata: + connID: "10" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [71, 188, 217, 186, 161, 142, 83, 226, 38, 200, 188, 119, 122, 187, 27, 183, 198, 37, 57, 214, 66, 206, 97, 87, 143, 162, 129, 148, 231, 17, 179, 128] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 68 + auth_plugin_data: [104, 112, 1, 72, 121, 97, 38, 29, 54, 9, 77, 118, 38, 40, 103, 90, 102, 51, 66, 126, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776824 + reqtimestampmock: 2025-11-10T12:13:44.067859708Z + restimestampmock: 2025-11-10T12:13:44.075062914Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-27 +spec: + metadata: + connID: "10" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776824 + reqtimestampmock: 2025-11-10T12:13:44.075418291Z + restimestampmock: 2025-11-10T12:13:44.075626709Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-28 +spec: + metadata: + connID: "10" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776824 + reqtimestampmock: 2025-11-10T12:13:44.075801408Z + restimestampmock: 2025-11-10T12:13:44.075962756Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-29 +spec: + metadata: + connID: "10" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776824 + reqtimestampmock: 2025-11-10T12:13:44.076086515Z + restimestampmock: 2025-11-10T12:13:44.076212623Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-30 +spec: + metadata: + connID: "10" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 103 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='nonexistentuser_{{$randomInt}}' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 6 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776824 + reqtimestampmock: 2025-11-10T12:13:44.076425381Z + restimestampmock: 2025-11-10T12:13:44.076902048Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-31 +spec: + metadata: + connID: "12" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [214, 91, 121, 143, 186, 79, 102, 136, 62, 143, 50, 73, 32, 35, 73, 87, 10, 195, 182, 59, 84, 112, 246, 149, 53, 16, 147, 81, 52, 230, 81, 11] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 69 + auth_plugin_data: [5, 87, 125, 120, 116, 78, 116, 72, 82, 5, 20, 5, 87, 37, 66, 31, 62, 111, 90, 88, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776824 + reqtimestampmock: 2025-11-10T12:13:44.872216412Z + restimestampmock: 2025-11-10T12:13:44.878446296Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-32 +spec: + metadata: + connID: "12" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776824 + reqtimestampmock: 2025-11-10T12:13:44.878627774Z + restimestampmock: 2025-11-10T12:13:44.878826143Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-33 +spec: + metadata: + connID: "12" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776824 + reqtimestampmock: 2025-11-10T12:13:44.878944412Z + restimestampmock: 2025-11-10T12:13:44.87908626Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-34 +spec: + metadata: + connID: "12" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776824 + reqtimestampmock: 2025-11-10T12:13:44.879191209Z + restimestampmock: 2025-11-10T12:13:44.879290539Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-35 +spec: + metadata: + connID: "12" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776824 + reqtimestampmock: 2025-11-10T12:13:44.879472927Z + restimestampmock: 2025-11-10T12:13:44.880052182Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-36 +spec: + metadata: + connID: "14" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [29, 233, 134, 213, 9, 107, 226, 76, 31, 34, 197, 38, 13, 246, 51, 143, 229, 254, 66, 108, 10, 65, 10, 231, 3, 30, 35, 191, 85, 21, 243, 235] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 71 + auth_plugin_data: [52, 73, 46, 19, 92, 72, 114, 1, 4, 90, 109, 29, 21, 99, 100, 113, 13, 51, 4, 121, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776827 + reqtimestampmock: 2025-11-10T12:13:47.602642619Z + restimestampmock: 2025-11-10T12:13:47.611180873Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-37 +spec: + metadata: + connID: "14" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776827 + reqtimestampmock: 2025-11-10T12:13:47.611380122Z + restimestampmock: 2025-11-10T12:13:47.61162159Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-38 +spec: + metadata: + connID: "14" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776827 + reqtimestampmock: 2025-11-10T12:13:47.611763109Z + restimestampmock: 2025-11-10T12:13:47.611896617Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-39 +spec: + metadata: + connID: "14" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776827 + reqtimestampmock: 2025-11-10T12:13:47.612038976Z + restimestampmock: 2025-11-10T12:13:47.612238394Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-40 +spec: + metadata: + connID: "14" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776827 + reqtimestampmock: 2025-11-10T12:13:47.612570502Z + restimestampmock: 2025-11-10T12:13:47.613285595Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-41 +spec: + metadata: + connID: "16" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [18, 37, 107, 157, 208, 55, 216, 105, 28, 3, 91, 69, 246, 50, 134, 160, 233, 141, 124, 187, 95, 73, 68, 219, 87, 250, 106, 174, 95, 123, 73, 148] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 72 + auth_plugin_data: [45, 4, 63, 49, 80, 56, 85, 99, 68, 64, 5, 10, 73, 46, 106, 122, 50, 109, 119, 8, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776830 + reqtimestampmock: 2025-11-10T12:13:50.208501981Z + restimestampmock: 2025-11-10T12:13:50.215080243Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-42 +spec: + metadata: + connID: "16" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776830 + reqtimestampmock: 2025-11-10T12:13:50.21529299Z + restimestampmock: 2025-11-10T12:13:50.215501698Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-43 +spec: + metadata: + connID: "16" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776830 + reqtimestampmock: 2025-11-10T12:13:50.215615227Z + restimestampmock: 2025-11-10T12:13:50.215779026Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-44 +spec: + metadata: + connID: "16" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776830 + reqtimestampmock: 2025-11-10T12:13:50.215911075Z + restimestampmock: 2025-11-10T12:13:50.216060334Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-45 +spec: + metadata: + connID: "16" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776830 + reqtimestampmock: 2025-11-10T12:13:50.216244452Z + restimestampmock: 2025-11-10T12:13:50.216805027Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-46 +spec: + metadata: + connID: "18" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [208, 140, 24, 254, 208, 46, 162, 158, 197, 66, 198, 133, 112, 174, 201, 35, 158, 119, 142, 42, 168, 251, 126, 5, 185, 112, 157, 30, 3, 200, 176, 71] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 74 + auth_plugin_data: [92, 13, 56, 51, 39, 87, 105, 74, 114, 56, 93, 50, 68, 19, 4, 80, 6, 20, 42, 126, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776832 + reqtimestampmock: 2025-11-10T12:13:52.853509601Z + restimestampmock: 2025-11-10T12:13:52.859719996Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-47 +spec: + metadata: + connID: "18" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776832 + reqtimestampmock: 2025-11-10T12:13:52.859899305Z + restimestampmock: 2025-11-10T12:13:52.860066464Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-48 +spec: + metadata: + connID: "18" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776832 + reqtimestampmock: 2025-11-10T12:13:52.860202032Z + restimestampmock: 2025-11-10T12:13:52.860340751Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-49 +spec: + metadata: + connID: "18" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776832 + reqtimestampmock: 2025-11-10T12:13:52.8604467Z + restimestampmock: 2025-11-10T12:13:52.86054311Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-50 +spec: + metadata: + connID: "18" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776832 + reqtimestampmock: 2025-11-10T12:13:52.860737127Z + restimestampmock: 2025-11-10T12:13:52.861257474Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-51 +spec: + metadata: + connID: "20" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [79, 234, 23, 225, 119, 239, 93, 209, 1, 233, 234, 103, 157, 252, 96, 189, 131, 234, 227, 168, 54, 151, 115, 75, 20, 195, 175, 145, 53, 62, 22, 219] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 75 + auth_plugin_data: [93, 70, 2, 29, 7, 125, 105, 57, 84, 35, 70, 94, 82, 106, 6, 16, 73, 80, 62, 93, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776836 + reqtimestampmock: 2025-11-10T12:13:56.212070149Z + restimestampmock: 2025-11-10T12:13:56.218507552Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-52 +spec: + metadata: + connID: "20" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776836 + reqtimestampmock: 2025-11-10T12:13:56.218738122Z + restimestampmock: 2025-11-10T12:13:56.218945899Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-53 +spec: + metadata: + connID: "20" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776836 + reqtimestampmock: 2025-11-10T12:13:56.219114717Z + restimestampmock: 2025-11-10T12:13:56.219434675Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-54 +spec: + metadata: + connID: "20" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776836 + reqtimestampmock: 2025-11-10T12:13:56.219573904Z + restimestampmock: 2025-11-10T12:13:56.219698802Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-55 +spec: + metadata: + connID: "20" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776836 + reqtimestampmock: 2025-11-10T12:13:56.219886551Z + restimestampmock: 2025-11-10T12:13:56.220404406Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-56 +spec: + metadata: + connID: "22" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [70, 142, 155, 169, 240, 183, 90, 254, 147, 68, 61, 126, 42, 179, 200, 221, 76, 193, 163, 231, 147, 161, 231, 97, 68, 166, 24, 218, 226, 156, 90, 138] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 77 + auth_plugin_data: [35, 121, 51, 8, 94, 97, 81, 124, 11, 33, 37, 66, 62, 38, 82, 19, 33, 99, 111, 34, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776839 + reqtimestampmock: 2025-11-10T12:13:59.49795436Z + restimestampmock: 2025-11-10T12:13:59.504412663Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-57 +spec: + metadata: + connID: "22" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776839 + reqtimestampmock: 2025-11-10T12:13:59.504561212Z + restimestampmock: 2025-11-10T12:13:59.50476045Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-58 +spec: + metadata: + connID: "22" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776839 + reqtimestampmock: 2025-11-10T12:13:59.504879349Z + restimestampmock: 2025-11-10T12:13:59.505058827Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-59 +spec: + metadata: + connID: "22" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776839 + reqtimestampmock: 2025-11-10T12:13:59.505172537Z + restimestampmock: 2025-11-10T12:13:59.505235556Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-60 +spec: + metadata: + connID: "22" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776839 + reqtimestampmock: 2025-11-10T12:13:59.505438774Z + restimestampmock: 2025-11-10T12:13:59.506106228Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-61 +spec: + metadata: + connID: "24" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [198, 105, 30, 107, 75, 90, 225, 144, 7, 73, 34, 48, 64, 135, 192, 119, 67, 249, 172, 154, 108, 20, 233, 7, 110, 81, 140, 148, 145, 135, 233, 131] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 79 + auth_plugin_data: [52, 41, 74, 19, 14, 106, 60, 21, 79, 66, 61, 120, 96, 94, 41, 69, 89, 113, 6, 54, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776842 + reqtimestampmock: 2025-11-10T12:14:02.618856524Z + restimestampmock: 2025-11-10T12:14:02.625113118Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-62 +spec: + metadata: + connID: "24" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776842 + reqtimestampmock: 2025-11-10T12:14:02.625312827Z + restimestampmock: 2025-11-10T12:14:02.625489764Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-63 +spec: + metadata: + connID: "24" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776842 + reqtimestampmock: 2025-11-10T12:14:02.625632333Z + restimestampmock: 2025-11-10T12:14:02.625859691Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-64 +spec: + metadata: + connID: "24" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776842 + reqtimestampmock: 2025-11-10T12:14:02.62605805Z + restimestampmock: 2025-11-10T12:14:02.626195258Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-65 +spec: + metadata: + connID: "24" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776842 + reqtimestampmock: 2025-11-10T12:14:02.626375447Z + restimestampmock: 2025-11-10T12:14:02.626948962Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-66 +spec: + metadata: + connID: "26" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [110, 162, 10, 196, 29, 143, 128, 164, 153, 40, 18, 135, 219, 121, 179, 71, 213, 140, 169, 130, 119, 139, 187, 35, 53, 208, 231, 17, 33, 58, 60, 36] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 80 + auth_plugin_data: [3, 68, 37, 122, 9, 103, 108, 122, 97, 35, 91, 110, 84, 59, 71, 38, 89, 5, 125, 8, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776845 + reqtimestampmock: 2025-11-10T12:14:05.635633251Z + restimestampmock: 2025-11-10T12:14:05.641874236Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-67 +spec: + metadata: + connID: "26" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776845 + reqtimestampmock: 2025-11-10T12:14:05.642031605Z + restimestampmock: 2025-11-10T12:14:05.642210133Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-68 +spec: + metadata: + connID: "26" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776845 + reqtimestampmock: 2025-11-10T12:14:05.642350652Z + restimestampmock: 2025-11-10T12:14:05.642555Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-69 +spec: + metadata: + connID: "26" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776845 + reqtimestampmock: 2025-11-10T12:14:05.642844048Z + restimestampmock: 2025-11-10T12:14:05.642920577Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-70 +spec: + metadata: + connID: "26" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776845 + reqtimestampmock: 2025-11-10T12:14:05.643080465Z + restimestampmock: 2025-11-10T12:14:05.64367129Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-71 +spec: + metadata: + connID: "28" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [117, 21, 108, 18, 32, 52, 137, 216, 129, 88, 11, 213, 152, 180, 194, 61, 122, 229, 82, 206, 127, 9, 59, 252, 121, 233, 124, 45, 234, 230, 238, 74] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 82 + auth_plugin_data: [20, 111, 8, 31, 67, 81, 103, 44, 76, 24, 28, 91, 109, 63, 126, 24, 43, 72, 111, 120, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776847 + reqtimestampmock: 2025-11-10T12:14:07.445186084Z + restimestampmock: 2025-11-10T12:14:07.454240615Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-72 +spec: + metadata: + connID: "28" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776847 + reqtimestampmock: 2025-11-10T12:14:07.454469102Z + restimestampmock: 2025-11-10T12:14:07.454661002Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-73 +spec: + metadata: + connID: "28" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776847 + reqtimestampmock: 2025-11-10T12:14:07.454801869Z + restimestampmock: 2025-11-10T12:14:07.454968159Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-74 +spec: + metadata: + connID: "28" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776847 + reqtimestampmock: 2025-11-10T12:14:07.455090528Z + restimestampmock: 2025-11-10T12:14:07.455198436Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-75 +spec: + metadata: + connID: "28" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776847 + reqtimestampmock: 2025-11-10T12:14:07.455387515Z + restimestampmock: 2025-11-10T12:14:07.456022299Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-76 +spec: + metadata: + connID: "30" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [68, 5, 112, 35, 105, 243, 106, 209, 73, 181, 246, 79, 24, 81, 16, 89, 115, 167, 191, 170, 49, 232, 246, 206, 104, 151, 131, 175, 212, 248, 80, 63] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 83 + auth_plugin_data: [82, 117, 104, 51, 22, 9, 74, 78, 27, 83, 84, 23, 55, 26, 22, 30, 37, 22, 102, 79, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776851 + reqtimestampmock: 2025-11-10T12:14:11.156712877Z + restimestampmock: 2025-11-10T12:14:11.162851554Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-77 +spec: + metadata: + connID: "30" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776851 + reqtimestampmock: 2025-11-10T12:14:11.162994572Z + restimestampmock: 2025-11-10T12:14:11.16316159Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-78 +spec: + metadata: + connID: "30" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776851 + reqtimestampmock: 2025-11-10T12:14:11.163273729Z + restimestampmock: 2025-11-10T12:14:11.163420598Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-79 +spec: + metadata: + connID: "30" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776851 + reqtimestampmock: 2025-11-10T12:14:11.163518177Z + restimestampmock: 2025-11-10T12:14:11.163625186Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-80 +spec: + metadata: + connID: "30" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776851 + reqtimestampmock: 2025-11-10T12:14:11.163822074Z + restimestampmock: 2025-11-10T12:14:11.164312501Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-81 +spec: + metadata: + connID: "32" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [130, 74, 42, 71, 32, 219, 244, 24, 134, 40, 228, 205, 253, 80, 15, 97, 8, 18, 63, 81, 143, 98, 21, 199, 22, 92, 103, 143, 74, 49, 207, 163] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 85 + auth_plugin_data: [127, 35, 97, 46, 1, 98, 82, 80, 45, 20, 122, 98, 80, 118, 13, 68, 109, 48, 127, 66, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776855 + reqtimestampmock: 2025-11-10T12:14:15.241090357Z + restimestampmock: 2025-11-10T12:14:15.24751717Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-82 +spec: + metadata: + connID: "32" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776855 + reqtimestampmock: 2025-11-10T12:14:15.247702068Z + restimestampmock: 2025-11-10T12:14:15.247917756Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-83 +spec: + metadata: + connID: "32" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776855 + reqtimestampmock: 2025-11-10T12:14:15.248054576Z + restimestampmock: 2025-11-10T12:14:15.248291614Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-84 +spec: + metadata: + connID: "32" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776855 + reqtimestampmock: 2025-11-10T12:14:15.248387853Z + restimestampmock: 2025-11-10T12:14:15.248492901Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-85 +spec: + metadata: + connID: "32" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776855 + reqtimestampmock: 2025-11-10T12:14:15.24862439Z + restimestampmock: 2025-11-10T12:14:15.249248995Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-86 +spec: + metadata: + connID: "34" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [14, 218, 148, 110, 190, 46, 70, 22, 234, 8, 243, 39, 167, 253, 77, 251, 48, 106, 79, 245, 153, 169, 245, 229, 150, 35, 184, 173, 44, 144, 209, 10] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 87 + auth_plugin_data: [28, 121, 2, 12, 11, 113, 2, 123, 17, 120, 118, 69, 72, 81, 101, 29, 2, 110, 114, 23, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776860 + reqtimestampmock: 2025-11-10T12:14:20.569315036Z + restimestampmock: 2025-11-10T12:14:20.57565713Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-87 +spec: + metadata: + connID: "34" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776860 + reqtimestampmock: 2025-11-10T12:14:20.575861778Z + restimestampmock: 2025-11-10T12:14:20.576045087Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-88 +spec: + metadata: + connID: "34" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776860 + reqtimestampmock: 2025-11-10T12:14:20.576209215Z + restimestampmock: 2025-11-10T12:14:20.576431734Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-89 +spec: + metadata: + connID: "34" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776860 + reqtimestampmock: 2025-11-10T12:14:20.576625271Z + restimestampmock: 2025-11-10T12:14:20.576871549Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-90 +spec: + metadata: + connID: "34" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776860 + reqtimestampmock: 2025-11-10T12:14:20.576922389Z + restimestampmock: 2025-11-10T12:14:20.577395525Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-91 +spec: + metadata: + connID: "36" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [219, 14, 159, 127, 62, 8, 34, 56, 200, 91, 152, 157, 104, 167, 36, 210, 10, 121, 239, 179, 191, 118, 38, 55, 101, 252, 118, 205, 155, 81, 170, 170] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 89 + auth_plugin_data: [57, 34, 86, 49, 4, 79, 13, 65, 18, 113, 95, 59, 24, 61, 69, 15, 83, 51, 108, 82, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776865 + reqtimestampmock: 2025-11-10T12:14:25.988396479Z + restimestampmock: 2025-11-10T12:14:25.994831553Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-92 +spec: + metadata: + connID: "36" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776865 + reqtimestampmock: 2025-11-10T12:14:25.995005321Z + restimestampmock: 2025-11-10T12:14:25.995200569Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-93 +spec: + metadata: + connID: "36" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776865 + reqtimestampmock: 2025-11-10T12:14:25.995322828Z + restimestampmock: 2025-11-10T12:14:25.995486077Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-94 +spec: + metadata: + connID: "36" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776865 + reqtimestampmock: 2025-11-10T12:14:25.995618066Z + restimestampmock: 2025-11-10T12:14:25.995727355Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-95 +spec: + metadata: + connID: "36" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776865 + reqtimestampmock: 2025-11-10T12:14:25.995887934Z + restimestampmock: 2025-11-10T12:14:25.996466758Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-96 +spec: + metadata: + connID: "38" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [32, 97, 142, 199, 118, 28, 57, 39, 150, 240, 22, 103, 117, 168, 109, 189, 136, 121, 85, 244, 65, 8, 111, 6, 76, 221, 33, 113, 96, 237, 131, 82] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 91 + auth_plugin_data: [27, 68, 64, 122, 92, 42, 34, 108, 33, 68, 87, 90, 87, 19, 58, 19, 39, 9, 19, 127, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776871 + reqtimestampmock: 2025-11-10T12:14:31.48684901Z + restimestampmock: 2025-11-10T12:14:31.493288134Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-97 +spec: + metadata: + connID: "38" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776871 + reqtimestampmock: 2025-11-10T12:14:31.493452513Z + restimestampmock: 2025-11-10T12:14:31.493616671Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-98 +spec: + metadata: + connID: "38" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776871 + reqtimestampmock: 2025-11-10T12:14:31.49373049Z + restimestampmock: 2025-11-10T12:14:31.493912269Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-99 +spec: + metadata: + connID: "38" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776871 + reqtimestampmock: 2025-11-10T12:14:31.494036327Z + restimestampmock: 2025-11-10T12:14:31.494122526Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-100 +spec: + metadata: + connID: "38" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776871 + reqtimestampmock: 2025-11-10T12:14:31.494292656Z + restimestampmock: 2025-11-10T12:14:31.49480272Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-101 +spec: + metadata: + connID: "40" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [128, 114, 60, 100, 5, 48, 98, 158, 31, 181, 21, 167, 61, 209, 23, 5, 168, 216, 220, 194, 236, 57, 69, 19, 255, 193, 13, 89, 57, 161, 225, 178] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 93 + auth_plugin_data: [69, 80, 79, 112, 65, 49, 117, 8, 37, 47, 84, 81, 81, 41, 12, 12, 85, 51, 90, 49, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776873 + reqtimestampmock: 2025-11-10T12:14:33.054703952Z + restimestampmock: 2025-11-10T12:14:33.061580471Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-102 +spec: + metadata: + connID: "40" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776873 + reqtimestampmock: 2025-11-10T12:14:33.061779211Z + restimestampmock: 2025-11-10T12:14:33.062012418Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-103 +spec: + metadata: + connID: "40" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776873 + reqtimestampmock: 2025-11-10T12:14:33.062138427Z + restimestampmock: 2025-11-10T12:14:33.062301305Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-104 +spec: + metadata: + connID: "40" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776873 + reqtimestampmock: 2025-11-10T12:14:33.062415894Z + restimestampmock: 2025-11-10T12:14:33.062537293Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-105 +spec: + metadata: + connID: "40" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776873 + reqtimestampmock: 2025-11-10T12:14:33.062687582Z + restimestampmock: 2025-11-10T12:14:33.063235738Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-106 +spec: + metadata: + connID: "42" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [227, 23, 66, 212, 122, 80, 239, 74, 234, 83, 30, 36, 231, 107, 204, 162, 155, 126, 254, 96, 126, 220, 222, 53, 55, 65, 123, 157, 46, 129, 169, 67] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 95 + auth_plugin_data: [60, 74, 15, 42, 37, 33, 17, 61, 124, 46, 96, 53, 114, 45, 2, 125, 118, 22, 11, 77, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776878 + reqtimestampmock: 2025-11-10T12:14:38.550364426Z + restimestampmock: 2025-11-10T12:14:38.556747521Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-107 +spec: + metadata: + connID: "42" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776878 + reqtimestampmock: 2025-11-10T12:14:38.55690912Z + restimestampmock: 2025-11-10T12:14:38.557084309Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-108 +spec: + metadata: + connID: "42" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776878 + reqtimestampmock: 2025-11-10T12:14:38.557213327Z + restimestampmock: 2025-11-10T12:14:38.557366405Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-109 +spec: + metadata: + connID: "42" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776878 + reqtimestampmock: 2025-11-10T12:14:38.557487084Z + restimestampmock: 2025-11-10T12:14:38.557579344Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-110 +spec: + metadata: + connID: "42" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776878 + reqtimestampmock: 2025-11-10T12:14:38.557763772Z + restimestampmock: 2025-11-10T12:14:38.558226158Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-111 +spec: + metadata: + connID: "44" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [16, 22, 126, 135, 167, 58, 96, 209, 78, 188, 198, 10, 112, 87, 171, 66, 232, 171, 138, 62, 75, 64, 40, 109, 130, 233, 1, 7, 83, 200, 56, 147] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 97 + auth_plugin_data: [121, 85, 1, 40, 97, 7, 10, 54, 121, 61, 65, 68, 28, 54, 26, 49, 108, 123, 76, 43, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776884 + reqtimestampmock: 2025-11-10T12:14:44.320070452Z + restimestampmock: 2025-11-10T12:14:44.326814944Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-112 +spec: + metadata: + connID: "44" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776884 + reqtimestampmock: 2025-11-10T12:14:44.327033552Z + restimestampmock: 2025-11-10T12:14:44.32727054Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-113 +spec: + metadata: + connID: "44" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776884 + reqtimestampmock: 2025-11-10T12:14:44.327471348Z + restimestampmock: 2025-11-10T12:14:44.327699266Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-114 +spec: + metadata: + connID: "44" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776884 + reqtimestampmock: 2025-11-10T12:14:44.327829284Z + restimestampmock: 2025-11-10T12:14:44.327939704Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-115 +spec: + metadata: + connID: "44" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776884 + reqtimestampmock: 2025-11-10T12:14:44.328137633Z + restimestampmock: 2025-11-10T12:14:44.328628709Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-116 +spec: + metadata: + connID: "46" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [125, 127, 199, 208, 174, 223, 149, 153, 136, 157, 94, 91, 50, 46, 142, 227, 143, 185, 182, 5, 137, 104, 208, 6, 61, 226, 43, 0, 20, 27, 195, 156] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 99 + auth_plugin_data: [97, 95, 86, 125, 45, 116, 116, 125, 18, 3, 38, 105, 27, 25, 68, 26, 120, 93, 65, 15, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776888 + reqtimestampmock: 2025-11-10T12:14:48.020676145Z + restimestampmock: 2025-11-10T12:14:48.030077823Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-117 +spec: + metadata: + connID: "46" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776888 + reqtimestampmock: 2025-11-10T12:14:48.030305501Z + restimestampmock: 2025-11-10T12:14:48.030504918Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-118 +spec: + metadata: + connID: "46" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776888 + reqtimestampmock: 2025-11-10T12:14:48.030647188Z + restimestampmock: 2025-11-10T12:14:48.030812087Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-119 +spec: + metadata: + connID: "46" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776888 + reqtimestampmock: 2025-11-10T12:14:48.030974434Z + restimestampmock: 2025-11-10T12:14:48.031083104Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-120 +spec: + metadata: + connID: "46" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776888 + reqtimestampmock: 2025-11-10T12:14:48.031354471Z + restimestampmock: 2025-11-10T12:14:48.031791517Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-121 +spec: + metadata: + connID: "48" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [57, 39, 104, 37, 63, 150, 140, 75, 17, 206, 82, 236, 206, 206, 17, 88, 89, 214, 131, 142, 204, 85, 235, 35, 126, 31, 163, 255, 153, 96, 162, 121] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 100 + auth_plugin_data: [73, 99, 1, 22, 90, 16, 108, 18, 88, 45, 82, 1, 69, 61, 58, 18, 61, 9, 73, 56, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776889 + reqtimestampmock: 2025-11-10T12:14:49.525239901Z + restimestampmock: 2025-11-10T12:14:49.534687889Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-122 +spec: + metadata: + connID: "48" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776889 + reqtimestampmock: 2025-11-10T12:14:49.534837407Z + restimestampmock: 2025-11-10T12:14:49.535030525Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-123 +spec: + metadata: + connID: "48" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776889 + reqtimestampmock: 2025-11-10T12:14:49.535163615Z + restimestampmock: 2025-11-10T12:14:49.535377623Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-124 +spec: + metadata: + connID: "48" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776889 + reqtimestampmock: 2025-11-10T12:14:49.535479411Z + restimestampmock: 2025-11-10T12:14:49.53574618Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-125 +spec: + metadata: + connID: "48" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='pooja_login_01' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 6 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776889 + reqtimestampmock: 2025-11-10T12:14:49.535791569Z + restimestampmock: 2025-11-10T12:14:49.536242855Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-126 +spec: + metadata: + connID: "50" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [37, 238, 206, 55, 63, 82, 234, 238, 115, 157, 125, 9, 134, 228, 49, 231, 156, 217, 169, 121, 140, 234, 147, 54, 251, 29, 237, 174, 27, 187, 63, 152] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 102 + auth_plugin_data: [116, 52, 45, 23, 30, 120, 53, 116, 37, 102, 4, 77, 116, 68, 53, 118, 66, 96, 38, 115, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776892 + reqtimestampmock: 2025-11-10T12:14:52.721844523Z + restimestampmock: 2025-11-10T12:14:52.728382006Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-127 +spec: + metadata: + connID: "50" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776892 + reqtimestampmock: 2025-11-10T12:14:52.728586055Z + restimestampmock: 2025-11-10T12:14:52.728765423Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-128 +spec: + metadata: + connID: "50" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776892 + reqtimestampmock: 2025-11-10T12:14:52.728880402Z + restimestampmock: 2025-11-10T12:14:52.729027311Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-129 +spec: + metadata: + connID: "50" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776892 + reqtimestampmock: 2025-11-10T12:14:52.729209459Z + restimestampmock: 2025-11-10T12:14:52.729273609Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-130 +spec: + metadata: + connID: "50" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776892 + reqtimestampmock: 2025-11-10T12:14:52.729467306Z + restimestampmock: 2025-11-10T12:14:52.730083082Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-131 +spec: + metadata: + connID: "52" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [149, 39, 153, 247, 28, 142, 150, 47, 135, 170, 214, 122, 180, 196, 42, 111, 198, 122, 233, 179, 130, 241, 120, 138, 115, 219, 220, 238, 189, 191, 225, 151] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 103 + auth_plugin_data: [95, 22, 69, 101, 44, 113, 76, 71, 108, 115, 37, 1, 19, 29, 90, 9, 15, 83, 107, 2, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776896 + reqtimestampmock: 2025-11-10T12:14:56.67932Z + restimestampmock: 2025-11-10T12:14:56.685779893Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-132 +spec: + metadata: + connID: "52" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776896 + reqtimestampmock: 2025-11-10T12:14:56.685941543Z + restimestampmock: 2025-11-10T12:14:56.686129401Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-133 +spec: + metadata: + connID: "52" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776896 + reqtimestampmock: 2025-11-10T12:14:56.686252459Z + restimestampmock: 2025-11-10T12:14:56.686393888Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-134 +spec: + metadata: + connID: "52" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776896 + reqtimestampmock: 2025-11-10T12:14:56.686495497Z + restimestampmock: 2025-11-10T12:14:56.686619606Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-135 +spec: + metadata: + connID: "52" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776896 + reqtimestampmock: 2025-11-10T12:14:56.686775156Z + restimestampmock: 2025-11-10T12:14:56.687303721Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-136 +spec: + metadata: + connID: "54" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [60, 146, 183, 238, 193, 69, 252, 30, 90, 244, 124, 218, 132, 243, 232, 43, 254, 224, 88, 251, 30, 163, 154, 47, 226, 108, 55, 5, 85, 37, 153, 253] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 105 + auth_plugin_data: [23, 46, 59, 9, 88, 46, 76, 81, 14, 9, 78, 83, 13, 64, 58, 22, 25, 97, 31, 108, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776900 + reqtimestampmock: 2025-11-10T12:15:00.31604395Z + restimestampmock: 2025-11-10T12:15:00.322331106Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-137 +spec: + metadata: + connID: "54" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776900 + reqtimestampmock: 2025-11-10T12:15:00.322505595Z + restimestampmock: 2025-11-10T12:15:00.322690763Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-138 +spec: + metadata: + connID: "54" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776900 + reqtimestampmock: 2025-11-10T12:15:00.322789091Z + restimestampmock: 2025-11-10T12:15:00.323009121Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-139 +spec: + metadata: + connID: "54" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776900 + reqtimestampmock: 2025-11-10T12:15:00.323131929Z + restimestampmock: 2025-11-10T12:15:00.323223778Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-140 +spec: + metadata: + connID: "54" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776900 + reqtimestampmock: 2025-11-10T12:15:00.323423786Z + restimestampmock: 2025-11-10T12:15:00.323934722Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-141 +spec: + metadata: + connID: "56" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [211, 157, 38, 157, 158, 130, 251, 124, 2, 247, 136, 137, 16, 43, 45, 120, 32, 230, 245, 71, 128, 225, 188, 169, 217, 154, 16, 147, 132, 240, 138, 71] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 107 + auth_plugin_data: [56, 51, 82, 101, 73, 1, 112, 23, 48, 117, 48, 125, 68, 124, 113, 37, 41, 100, 104, 26, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776905 + reqtimestampmock: 2025-11-10T12:15:05.18433628Z + restimestampmock: 2025-11-10T12:15:05.190711395Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-142 +spec: + metadata: + connID: "56" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776905 + reqtimestampmock: 2025-11-10T12:15:05.190857273Z + restimestampmock: 2025-11-10T12:15:05.19108238Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-143 +spec: + metadata: + connID: "56" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776905 + reqtimestampmock: 2025-11-10T12:15:05.19120356Z + restimestampmock: 2025-11-10T12:15:05.191368218Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-144 +spec: + metadata: + connID: "56" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776905 + reqtimestampmock: 2025-11-10T12:15:05.191476347Z + restimestampmock: 2025-11-10T12:15:05.191605957Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-145 +spec: + metadata: + connID: "56" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776905 + reqtimestampmock: 2025-11-10T12:15:05.191751425Z + restimestampmock: 2025-11-10T12:15:05.192223041Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-146 +spec: + metadata: + connID: "58" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [171, 190, 195, 110, 174, 222, 29, 123, 11, 216, 80, 111, 30, 117, 116, 125, 66, 87, 141, 212, 246, 189, 69, 25, 254, 149, 170, 139, 178, 250, 24, 155] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 109 + auth_plugin_data: [54, 121, 121, 81, 16, 60, 28, 24, 106, 33, 89, 100, 78, 87, 39, 46, 33, 49, 117, 118, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776911 + reqtimestampmock: 2025-11-10T12:15:11.076217798Z + restimestampmock: 2025-11-10T12:15:11.082380044Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-147 +spec: + metadata: + connID: "58" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776911 + reqtimestampmock: 2025-11-10T12:15:11.082529653Z + restimestampmock: 2025-11-10T12:15:11.082682401Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-148 +spec: + metadata: + connID: "58" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776911 + reqtimestampmock: 2025-11-10T12:15:11.082784981Z + restimestampmock: 2025-11-10T12:15:11.082905991Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-149 +spec: + metadata: + connID: "58" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776911 + reqtimestampmock: 2025-11-10T12:15:11.083025569Z + restimestampmock: 2025-11-10T12:15:11.083118898Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-150 +spec: + metadata: + connID: "58" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776911 + reqtimestampmock: 2025-11-10T12:15:11.083260287Z + restimestampmock: 2025-11-10T12:15:11.083704973Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-151 +spec: + metadata: + connID: "60" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [137, 232, 252, 72, 28, 91, 216, 92, 95, 199, 63, 179, 158, 158, 94, 121, 240, 51, 62, 246, 208, 162, 213, 223, 201, 172, 202, 89, 233, 149, 252, 227] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 112 + auth_plugin_data: [10, 7, 48, 9, 32, 119, 17, 125, 15, 16, 37, 92, 8, 65, 97, 9, 126, 127, 25, 7, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776920 + reqtimestampmock: 2025-11-10T12:15:20.646029475Z + restimestampmock: 2025-11-10T12:15:20.652518579Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-152 +spec: + metadata: + connID: "60" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776920 + reqtimestampmock: 2025-11-10T12:15:20.652699847Z + restimestampmock: 2025-11-10T12:15:20.652878586Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-153 +spec: + metadata: + connID: "60" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776920 + reqtimestampmock: 2025-11-10T12:15:20.652984034Z + restimestampmock: 2025-11-10T12:15:20.653139602Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-154 +spec: + metadata: + connID: "60" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776920 + reqtimestampmock: 2025-11-10T12:15:20.653298791Z + restimestampmock: 2025-11-10T12:15:20.65340579Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-155 +spec: + metadata: + connID: "60" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776920 + reqtimestampmock: 2025-11-10T12:15:20.65360614Z + restimestampmock: 2025-11-10T12:15:20.653999995Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-156 +spec: + metadata: + connID: "62" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [232, 248, 151, 226, 42, 238, 208, 6, 58, 199, 178, 74, 244, 44, 252, 106, 137, 198, 132, 98, 46, 249, 167, 99, 230, 180, 119, 147, 196, 158, 179, 34] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 114 + auth_plugin_data: [43, 8, 117, 62, 9, 23, 52, 42, 67, 123, 93, 71, 109, 28, 1, 99, 95, 102, 50, 53, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776924 + reqtimestampmock: 2025-11-10T12:15:24.603025073Z + restimestampmock: 2025-11-10T12:15:24.609183769Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-157 +spec: + metadata: + connID: "62" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776924 + reqtimestampmock: 2025-11-10T12:15:24.609386548Z + restimestampmock: 2025-11-10T12:15:24.609563216Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-158 +spec: + metadata: + connID: "62" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776924 + reqtimestampmock: 2025-11-10T12:15:24.609673354Z + restimestampmock: 2025-11-10T12:15:24.609830414Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-159 +spec: + metadata: + connID: "62" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776924 + reqtimestampmock: 2025-11-10T12:15:24.609937663Z + restimestampmock: 2025-11-10T12:15:24.610043742Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-160 +spec: + metadata: + connID: "62" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776924 + reqtimestampmock: 2025-11-10T12:15:24.610283149Z + restimestampmock: 2025-11-10T12:15:24.610782235Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-161 +spec: + metadata: + connID: "64" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [136, 42, 78, 221, 78, 22, 239, 137, 54, 207, 191, 248, 107, 72, 211, 5, 74, 251, 239, 143, 91, 25, 233, 77, 158, 77, 60, 89, 32, 75, 98, 253] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 116 + auth_plugin_data: [72, 125, 63, 31, 86, 57, 68, 25, 28, 126, 123, 121, 21, 38, 61, 37, 57, 95, 107, 106, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776927 + reqtimestampmock: 2025-11-10T12:15:27.302740145Z + restimestampmock: 2025-11-10T12:15:27.30899906Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-162 +spec: + metadata: + connID: "64" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776927 + reqtimestampmock: 2025-11-10T12:15:27.30920676Z + restimestampmock: 2025-11-10T12:15:27.309373827Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-163 +spec: + metadata: + connID: "64" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776927 + reqtimestampmock: 2025-11-10T12:15:27.309481016Z + restimestampmock: 2025-11-10T12:15:27.309645855Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-164 +spec: + metadata: + connID: "64" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776927 + reqtimestampmock: 2025-11-10T12:15:27.309737435Z + restimestampmock: 2025-11-10T12:15:27.309835453Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-165 +spec: + metadata: + connID: "64" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776927 + reqtimestampmock: 2025-11-10T12:15:27.310002592Z + restimestampmock: 2025-11-10T12:15:27.310502708Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-166 +spec: + metadata: + connID: "66" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [54, 169, 248, 155, 0, 72, 107, 6, 74, 18, 21, 121, 33, 130, 61, 41, 168, 31, 38, 123, 153, 58, 32, 141, 6, 157, 16, 217, 204, 82, 218, 218] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 117 + auth_plugin_data: [113, 57, 6, 53, 41, 11, 1, 59, 107, 64, 31, 34, 25, 68, 54, 103, 53, 92, 62, 119, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776928 + reqtimestampmock: 2025-11-10T12:15:28.933097273Z + restimestampmock: 2025-11-10T12:15:28.939450598Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-167 +spec: + metadata: + connID: "66" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776928 + reqtimestampmock: 2025-11-10T12:15:28.939615287Z + restimestampmock: 2025-11-10T12:15:28.939779975Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-168 +spec: + metadata: + connID: "66" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776928 + reqtimestampmock: 2025-11-10T12:15:28.939877305Z + restimestampmock: 2025-11-10T12:15:28.940085393Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-169 +spec: + metadata: + connID: "66" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776928 + reqtimestampmock: 2025-11-10T12:15:28.9403025Z + restimestampmock: 2025-11-10T12:15:28.9403734Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-170 +spec: + metadata: + connID: "66" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776928 + reqtimestampmock: 2025-11-10T12:15:28.940551839Z + restimestampmock: 2025-11-10T12:15:28.940982915Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-171 +spec: + metadata: + connID: "68" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [218, 57, 55, 147, 183, 25, 122, 201, 195, 20, 158, 230, 131, 80, 97, 210, 202, 93, 180, 67, 97, 241, 1, 255, 109, 222, 108, 201, 114, 41, 239, 87] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 118 + auth_plugin_data: [87, 111, 25, 106, 73, 98, 85, 112, 52, 109, 2, 116, 109, 34, 13, 30, 59, 8, 121, 52, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776930 + reqtimestampmock: 2025-11-10T12:15:30.516182175Z + restimestampmock: 2025-11-10T12:15:30.52242641Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-172 +spec: + metadata: + connID: "68" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776930 + reqtimestampmock: 2025-11-10T12:15:30.522578408Z + restimestampmock: 2025-11-10T12:15:30.522747838Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-173 +spec: + metadata: + connID: "68" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776930 + reqtimestampmock: 2025-11-10T12:15:30.522880246Z + restimestampmock: 2025-11-10T12:15:30.523019916Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-174 +spec: + metadata: + connID: "68" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776930 + reqtimestampmock: 2025-11-10T12:15:30.523138424Z + restimestampmock: 2025-11-10T12:15:30.523247643Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-175 +spec: + metadata: + connID: "68" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776930 + reqtimestampmock: 2025-11-10T12:15:30.523489601Z + restimestampmock: 2025-11-10T12:15:30.523864908Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-176 +spec: + metadata: + connID: "70" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [13, 77, 203, 186, 10, 71, 53, 120, 112, 193, 254, 74, 168, 71, 224, 146, 210, 21, 38, 220, 87, 19, 194, 156, 203, 49, 59, 170, 111, 27, 192, 72] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 120 + auth_plugin_data: [20, 23, 62, 78, 96, 107, 43, 67, 52, 94, 108, 124, 22, 44, 92, 47, 21, 60, 98, 99, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776935 + reqtimestampmock: 2025-11-10T12:15:35.838945598Z + restimestampmock: 2025-11-10T12:15:35.84572039Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-177 +spec: + metadata: + connID: "70" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776935 + reqtimestampmock: 2025-11-10T12:15:35.845938087Z + restimestampmock: 2025-11-10T12:15:35.846163596Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-178 +spec: + metadata: + connID: "70" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776935 + reqtimestampmock: 2025-11-10T12:15:35.846335244Z + restimestampmock: 2025-11-10T12:15:35.846525793Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-179 +spec: + metadata: + connID: "70" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776935 + reqtimestampmock: 2025-11-10T12:15:35.846636081Z + restimestampmock: 2025-11-10T12:15:35.84674104Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-180 +spec: + metadata: + connID: "70" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776935 + reqtimestampmock: 2025-11-10T12:15:35.846976218Z + restimestampmock: 2025-11-10T12:15:35.847528504Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-181 +spec: + metadata: + connID: "72" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [55, 223, 113, 142, 181, 96, 126, 141, 119, 173, 49, 136, 194, 79, 182, 108, 30, 62, 84, 53, 62, 119, 155, 181, 16, 204, 169, 90, 146, 186, 244, 251] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 122 + auth_plugin_data: [91, 116, 21, 17, 117, 120, 4, 69, 49, 79, 64, 23, 119, 7, 51, 16, 30, 16, 71, 53, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776937 + reqtimestampmock: 2025-11-10T12:15:37.545626945Z + restimestampmock: 2025-11-10T12:15:37.552398717Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-182 +spec: + metadata: + connID: "72" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776937 + reqtimestampmock: 2025-11-10T12:15:37.552562294Z + restimestampmock: 2025-11-10T12:15:37.552751413Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-183 +spec: + metadata: + connID: "72" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776937 + reqtimestampmock: 2025-11-10T12:15:37.552897152Z + restimestampmock: 2025-11-10T12:15:37.55304301Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-184 +spec: + metadata: + connID: "72" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776937 + reqtimestampmock: 2025-11-10T12:15:37.5531606Z + restimestampmock: 2025-11-10T12:15:37.553257928Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-185 +spec: + metadata: + connID: "72" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776937 + reqtimestampmock: 2025-11-10T12:15:37.553488438Z + restimestampmock: 2025-11-10T12:15:37.553925273Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-186 +spec: + metadata: + connID: "74" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [192, 206, 87, 242, 147, 234, 99, 100, 176, 30, 153, 218, 57, 192, 248, 88, 223, 66, 115, 251, 112, 235, 3, 68, 161, 108, 222, 214, 253, 166, 103, 198] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 123 + auth_plugin_data: [1, 59, 19, 60, 59, 80, 2, 65, 124, 75, 1, 10, 6, 13, 63, 13, 23, 85, 127, 26, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776939 + reqtimestampmock: 2025-11-10T12:15:39.123983115Z + restimestampmock: 2025-11-10T12:15:39.13037835Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-187 +spec: + metadata: + connID: "74" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776939 + reqtimestampmock: 2025-11-10T12:15:39.13053417Z + restimestampmock: 2025-11-10T12:15:39.130728857Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-188 +spec: + metadata: + connID: "74" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776939 + reqtimestampmock: 2025-11-10T12:15:39.130855327Z + restimestampmock: 2025-11-10T12:15:39.131029086Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-189 +spec: + metadata: + connID: "74" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776939 + reqtimestampmock: 2025-11-10T12:15:39.131224413Z + restimestampmock: 2025-11-10T12:15:39.131343443Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-190 +spec: + metadata: + connID: "74" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776939 + reqtimestampmock: 2025-11-10T12:15:39.13160229Z + restimestampmock: 2025-11-10T12:15:39.132085356Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-191 +spec: + metadata: + connID: "76" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [12, 131, 183, 136, 163, 12, 143, 45, 20, 187, 127, 212, 192, 92, 176, 206, 46, 74, 54, 129, 230, 71, 174, 197, 160, 196, 185, 234, 105, 24, 212, 178] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 124 + auth_plugin_data: [49, 14, 81, 83, 34, 100, 39, 34, 74, 59, 56, 14, 73, 10, 104, 104, 96, 79, 17, 50, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776941 + reqtimestampmock: 2025-11-10T12:15:41.937442889Z + restimestampmock: 2025-11-10T12:15:41.943459037Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-192 +spec: + metadata: + connID: "76" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776941 + reqtimestampmock: 2025-11-10T12:15:41.943597547Z + restimestampmock: 2025-11-10T12:15:41.943785885Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-193 +spec: + metadata: + connID: "76" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776941 + reqtimestampmock: 2025-11-10T12:15:41.943913993Z + restimestampmock: 2025-11-10T12:15:41.944046463Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-194 +spec: + metadata: + connID: "76" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776941 + reqtimestampmock: 2025-11-10T12:15:41.944167881Z + restimestampmock: 2025-11-10T12:15:41.94431101Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-195 +spec: + metadata: + connID: "76" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776941 + reqtimestampmock: 2025-11-10T12:15:41.94443858Z + restimestampmock: 2025-11-10T12:15:41.944860336Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-196 +spec: + metadata: + connID: "78" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [144, 253, 165, 4, 121, 222, 162, 220, 88, 88, 224, 215, 71, 52, 174, 196, 32, 54, 172, 245, 6, 240, 146, 110, 23, 9, 228, 93, 216, 136, 31, 213] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 126 + auth_plugin_data: [108, 110, 63, 10, 7, 85, 1, 107, 112, 6, 68, 35, 56, 12, 124, 55, 28, 8, 23, 74, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776944 + reqtimestampmock: 2025-11-10T12:15:44.881687655Z + restimestampmock: 2025-11-10T12:15:44.8880936Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-197 +spec: + metadata: + connID: "78" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776944 + reqtimestampmock: 2025-11-10T12:15:44.888309087Z + restimestampmock: 2025-11-10T12:15:44.888481877Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-198 +spec: + metadata: + connID: "78" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776944 + reqtimestampmock: 2025-11-10T12:15:44.888608244Z + restimestampmock: 2025-11-10T12:15:44.888828733Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-199 +spec: + metadata: + connID: "78" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776944 + reqtimestampmock: 2025-11-10T12:15:44.888951362Z + restimestampmock: 2025-11-10T12:15:44.889074401Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-200 +spec: + metadata: + connID: "78" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776944 + reqtimestampmock: 2025-11-10T12:15:44.889227239Z + restimestampmock: 2025-11-10T12:15:44.889610116Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-201 +spec: + metadata: + connID: "80" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [180, 32, 209, 252, 154, 56, 64, 152, 119, 76, 60, 85, 84, 187, 83, 108, 176, 40, 79, 244, 32, 65, 56, 223, 117, 25, 138, 204, 230, 235, 20, 31] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 128 + auth_plugin_data: [116, 54, 32, 57, 74, 55, 12, 122, 34, 57, 25, 49, 61, 48, 28, 127, 24, 55, 122, 9, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776947 + reqtimestampmock: 2025-11-10T12:15:47.526198658Z + restimestampmock: 2025-11-10T12:15:47.534428228Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-202 +spec: + metadata: + connID: "80" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776947 + reqtimestampmock: 2025-11-10T12:15:47.534757195Z + restimestampmock: 2025-11-10T12:15:47.534802404Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-203 +spec: + metadata: + connID: "80" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776947 + reqtimestampmock: 2025-11-10T12:15:47.534908863Z + restimestampmock: 2025-11-10T12:15:47.535070993Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-204 +spec: + metadata: + connID: "80" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776947 + reqtimestampmock: 2025-11-10T12:15:47.53523622Z + restimestampmock: 2025-11-10T12:15:47.5353393Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-205 +spec: + metadata: + connID: "80" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776947 + reqtimestampmock: 2025-11-10T12:15:47.535526588Z + restimestampmock: 2025-11-10T12:15:47.535950725Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-206 +spec: + metadata: + connID: "82" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [232, 102, 217, 141, 139, 87, 1, 32, 81, 214, 144, 50, 52, 93, 174, 218, 150, 178, 3, 184, 227, 107, 65, 94, 37, 186, 129, 233, 186, 67, 134, 177] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 129 + auth_plugin_data: [9, 19, 41, 62, 2, 18, 1, 23, 2, 54, 53, 34, 112, 109, 90, 102, 25, 34, 19, 76, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776950 + reqtimestampmock: 2025-11-10T12:15:50.825539965Z + restimestampmock: 2025-11-10T12:15:50.831803079Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-207 +spec: + metadata: + connID: "82" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776950 + reqtimestampmock: 2025-11-10T12:15:50.832022469Z + restimestampmock: 2025-11-10T12:15:50.832239076Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-208 +spec: + metadata: + connID: "82" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776950 + reqtimestampmock: 2025-11-10T12:15:50.832347325Z + restimestampmock: 2025-11-10T12:15:50.832607243Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-209 +spec: + metadata: + connID: "82" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776950 + reqtimestampmock: 2025-11-10T12:15:50.832735062Z + restimestampmock: 2025-11-10T12:15:50.8329617Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-210 +spec: + metadata: + connID: "82" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776950 + reqtimestampmock: 2025-11-10T12:15:50.833002429Z + restimestampmock: 2025-11-10T12:15:50.833552274Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-211 +spec: + metadata: + connID: "84" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [120, 139, 116, 139, 211, 60, 36, 95, 104, 220, 47, 220, 0, 229, 244, 125, 225, 108, 130, 93, 87, 50, 76, 209, 29, 146, 243, 233, 119, 65, 190, 24] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 131 + auth_plugin_data: [31, 60, 70, 20, 91, 92, 98, 72, 69, 112, 124, 59, 27, 86, 30, 42, 49, 116, 46, 45, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776953 + reqtimestampmock: 2025-11-10T12:15:53.675796545Z + restimestampmock: 2025-11-10T12:15:53.68209351Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-212 +spec: + metadata: + connID: "84" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776953 + reqtimestampmock: 2025-11-10T12:15:53.682267339Z + restimestampmock: 2025-11-10T12:15:53.682438407Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-213 +spec: + metadata: + connID: "84" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776953 + reqtimestampmock: 2025-11-10T12:15:53.682544917Z + restimestampmock: 2025-11-10T12:15:53.682702405Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-214 +spec: + metadata: + connID: "84" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776953 + reqtimestampmock: 2025-11-10T12:15:53.682850744Z + restimestampmock: 2025-11-10T12:15:53.682939823Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-215 +spec: + metadata: + connID: "84" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='admin' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 164 + sequence_id: 6 + values: + - type: 253 + name: id + value: 8bd161aa-bb12-11f0-982a-c678de5766ef + unsigned: false + - type: 253 + name: username + value: admin + unsigned: false + - type: 253 + name: email + value: admin@example.com + unsigned: false + - type: 253 + name: password_hash + value: pbkdf2:sha256:600000$nBh786TvaYCbJPcQ$b1429e79b521a37444b162e4526d576a3acd87525c7f7413e5f94108c775815c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776953 + reqtimestampmock: 2025-11-10T12:15:53.683118842Z + restimestampmock: 2025-11-10T12:15:53.683506639Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-216 +spec: + metadata: + connID: "86" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [228, 20, 109, 5, 224, 79, 250, 101, 174, 49, 231, 34, 214, 202, 11, 11, 246, 109, 223, 250, 93, 131, 8, 81, 75, 221, 109, 63, 120, 90, 86, 22] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 132 + auth_plugin_data: [42, 4, 91, 24, 77, 121, 38, 73, 72, 104, 120, 3, 125, 76, 27, 77, 7, 34, 81, 77, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762776953 + reqtimestampmock: 2025-11-10T12:15:53.984124106Z + restimestampmock: 2025-11-10T12:15:53.993703313Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-217 +spec: + metadata: + connID: "86" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762776953 + reqtimestampmock: 2025-11-10T12:15:53.993847072Z + restimestampmock: 2025-11-10T12:15:53.994026141Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-218 +spec: + metadata: + connID: "86" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762776953 + reqtimestampmock: 2025-11-10T12:15:53.99413972Z + restimestampmock: 2025-11-10T12:15:53.994350868Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-219 +spec: + metadata: + connID: "86" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762776953 + reqtimestampmock: 2025-11-10T12:15:53.994462557Z + restimestampmock: 2025-11-10T12:15:53.994560045Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-220 +spec: + metadata: + connID: "86" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 78 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='alice' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 224 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9fdac0ef-7d77-4798-b07f-a7076b80aa63 + unsigned: false + - type: 253 + name: username + value: alice + unsigned: false + - type: 253 + name: email + value: alice@example.com + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$NOz3kOZZlytXrAm8$4eae1648817bb25348bd87d9b3bf54c4006ba518df01fd4d400cfbe6f8d3037c12f7638ee0059e1117651f816b0cb7a6b7687a9bb4d6ef0c634d10490297af4f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762776953 + reqtimestampmock: 2025-11-10T12:15:53.994728795Z + restimestampmock: 2025-11-10T12:15:53.995182301Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-221 +spec: + metadata: + connID: "88" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [192, 45, 43, 255, 152, 105, 206, 172, 232, 168, 2, 118, 52, 15, 75, 69, 189, 54, 167, 22, 254, 126, 132, 250, 208, 31, 112, 141, 16, 3, 48, 1] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 146 + auth_plugin_data: [48, 54, 93, 67, 127, 73, 8, 73, 59, 51, 50, 16, 85, 50, 94, 59, 51, 49, 4, 54, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777020 + reqtimestampmock: 2025-11-10T12:17:00.359788952Z + restimestampmock: 2025-11-10T12:17:00.365872299Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-222 +spec: + metadata: + connID: "88" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777020 + reqtimestampmock: 2025-11-10T12:17:00.366033258Z + restimestampmock: 2025-11-10T12:17:00.366216007Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-223 +spec: + metadata: + connID: "88" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777020 + reqtimestampmock: 2025-11-10T12:17:00.366325436Z + restimestampmock: 2025-11-10T12:17:00.366468235Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-224 +spec: + metadata: + connID: "88" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777020 + reqtimestampmock: 2025-11-10T12:17:00.366587164Z + restimestampmock: 2025-11-10T12:17:00.366690653Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-225 +spec: + metadata: + connID: "88" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 327 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('16be51aa-dcaf-4681-b125-ccffcc64e663', 'priya_e2e_01', 'priya_e2e_01@example.in', 'scrypt:32768:8:1$9pjU5vePTjMyU6jn$09f4905c2ccf91cdc6e94f1fe9a673c6c6aeded3e09895562b48bd761fc3c3bc2c10149cd518bcd4e7ad12c7cc3619557f541fd358e430ba2e4984f835329a6f', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777020 + reqtimestampmock: 2025-11-10T12:17:00.413987369Z + restimestampmock: 2025-11-10T12:17:00.418620029Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-226 +spec: + metadata: + connID: "88" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777020 + reqtimestampmock: 2025-11-10T12:17:00.418789407Z + restimestampmock: 2025-11-10T12:17:00.427628262Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-227 +spec: + metadata: + connID: "90" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [248, 91, 43, 199, 173, 203, 76, 115, 175, 131, 32, 128, 29, 202, 41, 51, 160, 89, 8, 205, 44, 44, 137, 151, 137, 98, 62, 44, 98, 47, 140, 155] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 147 + auth_plugin_data: [78, 51, 111, 48, 44, 1, 74, 52, 9, 52, 23, 103, 120, 20, 47, 10, 97, 78, 51, 113, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.141286923Z + restimestampmock: 2025-11-10T12:17:01.148034654Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-228 +spec: + metadata: + connID: "90" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.148310982Z + restimestampmock: 2025-11-10T12:17:01.14855375Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-229 +spec: + metadata: + connID: "90" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.148690109Z + restimestampmock: 2025-11-10T12:17:01.148851027Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-230 +spec: + metadata: + connID: "90" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.149017115Z + restimestampmock: 2025-11-10T12:17:01.149215433Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-231 +spec: + metadata: + connID: "90" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='priya_e2e_01' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 237 + sequence_id: 6 + values: + - type: 253 + name: id + value: 16be51aa-dcaf-4681-b125-ccffcc64e663 + unsigned: false + - type: 253 + name: username + value: priya_e2e_01 + unsigned: false + - type: 253 + name: email + value: priya_e2e_01@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$9pjU5vePTjMyU6jn$09f4905c2ccf91cdc6e94f1fe9a673c6c6aeded3e09895562b48bd761fc3c3bc2c10149cd518bcd4e7ad12c7cc3619557f541fd358e430ba2e4984f835329a6f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.149419483Z + restimestampmock: 2025-11-10T12:17:01.149795309Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-232 +spec: + metadata: + connID: "92" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [120, 152, 144, 36, 253, 147, 140, 136, 219, 38, 59, 136, 20, 42, 209, 144, 23, 252, 239, 35, 231, 203, 134, 216, 216, 124, 62, 43, 31, 169, 136, 5] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 148 + auth_plugin_data: [79, 62, 118, 97, 82, 23, 80, 69, 88, 2, 45, 4, 118, 65, 17, 62, 54, 75, 123, 51, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.961014457Z + restimestampmock: 2025-11-10T12:17:01.969473154Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-233 +spec: + metadata: + connID: "92" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.969662843Z + restimestampmock: 2025-11-10T12:17:01.969839501Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-234 +spec: + metadata: + connID: "92" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.969944081Z + restimestampmock: 2025-11-10T12:17:01.970160629Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-235 +spec: + metadata: + connID: "92" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.970252448Z + restimestampmock: 2025-11-10T12:17:01.970381397Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-236 +spec: + metadata: + connID: "92" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 109 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, phone, created_at FROM users WHERE id = '16be51aa-dcaf-4681-b125-ccffcc64e663' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 5 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 59 + sequence_id: 6 + catalog: def + schema: user_db + table: users + org_table: users + name: created_at + org_name: created_at + fixed_length: 12 + character_set: 63 + column_length: 19 + type: 7 + flags: 1153 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 95 + sequence_id: 7 + values: + - type: 253 + name: id + value: 16be51aa-dcaf-4681-b125-ccffcc64e663 + unsigned: false + - type: 253 + name: username + value: priya_e2e_01 + unsigned: false + - type: 253 + name: email + value: priya_e2e_01@example.in + unsigned: false + - type: 253 + name: phone + value: null + unsigned: false + - type: 7 + name: created_at + value: "2025-11-10 12:17:00" + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 8 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.970546385Z + restimestampmock: 2025-11-10T12:17:01.971023331Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-237 +spec: + metadata: + connID: "92" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 190 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, line1, line2, city, state, postal_code, country, phone, is_default FROM addresses WHERE user_id='16be51aa-dcaf-4681-b125-ccffcc64e663' ORDER BY is_default DESC, created_at DESC + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 9 + columns: + - header: + payload_length: 51 + sequence_id: 2 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 3 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line1 + org_name: line1 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 4 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line2 + org_name: line2 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 5 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: city + org_name: city + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 6 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: state + org_name: state + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 69 + sequence_id: 7 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: postal_code + org_name: postal_code + fixed_length: 12 + character_set: 255 + column_length: 80 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 61 + sequence_id: 8 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: country + org_name: country + fixed_length: 12 + character_set: 255 + column_length: 8 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 9 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 67 + sequence_id: 10 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: is_default + org_name: is_default + fixed_length: 12 + character_set: 63 + column_length: 1 + type: 1 + flags: 1 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 11 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777021 + reqtimestampmock: 2025-11-10T12:17:01.971251969Z + restimestampmock: 2025-11-10T12:17:01.973638039Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-238 +spec: + metadata: + connID: "94" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [47, 8, 50, 186, 124, 199, 167, 156, 105, 212, 48, 181, 147, 126, 32, 253, 157, 235, 85, 24, 173, 228, 38, 80, 104, 39, 9, 127, 86, 133, 43, 129] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 149 + auth_plugin_data: [122, 64, 116, 1, 63, 68, 32, 27, 109, 94, 92, 103, 105, 70, 69, 112, 98, 33, 37, 19, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777022 + reqtimestampmock: 2025-11-10T12:17:02.708566918Z + restimestampmock: 2025-11-10T12:17:02.714821555Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-239 +spec: + metadata: + connID: "94" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777022 + reqtimestampmock: 2025-11-10T12:17:02.715007213Z + restimestampmock: 2025-11-10T12:17:02.715199372Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-240 +spec: + metadata: + connID: "94" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777022 + reqtimestampmock: 2025-11-10T12:17:02.715309851Z + restimestampmock: 2025-11-10T12:17:02.715460329Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-241 +spec: + metadata: + connID: "94" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777022 + reqtimestampmock: 2025-11-10T12:17:02.715619708Z + restimestampmock: 2025-11-10T12:17:02.715769407Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-242 +spec: + metadata: + connID: "94" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='16be51aa-dcaf-4681-b125-ccffcc64e663' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 16be51aa-dcaf-4681-b125-ccffcc64e663 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777022 + reqtimestampmock: 2025-11-10T12:17:02.715941426Z + restimestampmock: 2025-11-10T12:17:02.716283593Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-243 +spec: + metadata: + connID: "94" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 268 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('63f3fd41-0c2d-4609-9934-999884526bda','16be51aa-dcaf-4681-b125-ccffcc64e663','123 MG Road',NULL,'Bengaluru','Karnataka','560001','IN','+919876543210',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777022 + reqtimestampmock: 2025-11-10T12:17:02.71661122Z + restimestampmock: 2025-11-10T12:17:02.717023536Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-244 +spec: + metadata: + connID: "94" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='16be51aa-dcaf-4681-b125-ccffcc64e663' AND id<>'63f3fd41-0c2d-4609-9934-999884526bda' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777022 + reqtimestampmock: 2025-11-10T12:17:02.717446082Z + restimestampmock: 2025-11-10T12:17:02.722319571Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-245 +spec: + metadata: + connID: "94" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777022 + reqtimestampmock: 2025-11-10T12:17:02.72247252Z + restimestampmock: 2025-11-10T12:17:02.733589864Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-246 +spec: + metadata: + connID: "96" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [167, 185, 78, 21, 75, 232, 47, 147, 77, 229, 163, 196, 172, 165, 183, 107, 40, 238, 28, 203, 84, 67, 241, 254, 196, 192, 78, 225, 189, 166, 33, 93] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 151 + auth_plugin_data: [17, 86, 106, 10, 18, 22, 6, 125, 110, 48, 23, 35, 82, 56, 44, 123, 49, 72, 40, 3, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777023 + reqtimestampmock: 2025-11-10T12:17:03.521803589Z + restimestampmock: 2025-11-10T12:17:03.53223849Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-247 +spec: + metadata: + connID: "96" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777023 + reqtimestampmock: 2025-11-10T12:17:03.532401399Z + restimestampmock: 2025-11-10T12:17:03.532590687Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-248 +spec: + metadata: + connID: "96" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777023 + reqtimestampmock: 2025-11-10T12:17:03.532700476Z + restimestampmock: 2025-11-10T12:17:03.532816755Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-249 +spec: + metadata: + connID: "96" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777023 + reqtimestampmock: 2025-11-10T12:17:03.532909915Z + restimestampmock: 2025-11-10T12:17:03.532992764Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-250 +spec: + metadata: + connID: "96" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='16be51aa-dcaf-4681-b125-ccffcc64e663' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 16be51aa-dcaf-4681-b125-ccffcc64e663 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777023 + reqtimestampmock: 2025-11-10T12:17:03.533181511Z + restimestampmock: 2025-11-10T12:17:03.533497549Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-251 +spec: + metadata: + connID: "96" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 190 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, line1, line2, city, state, postal_code, country, phone, is_default FROM addresses WHERE user_id='16be51aa-dcaf-4681-b125-ccffcc64e663' ORDER BY is_default DESC, created_at DESC + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 9 + columns: + - header: + payload_length: 51 + sequence_id: 2 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 3 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line1 + org_name: line1 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 4 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line2 + org_name: line2 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 5 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: city + org_name: city + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 6 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: state + org_name: state + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 69 + sequence_id: 7 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: postal_code + org_name: postal_code + fixed_length: 12 + character_set: 255 + column_length: 80 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 61 + sequence_id: 8 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: country + org_name: country + fixed_length: 12 + character_set: 255 + column_length: 8 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 9 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 67 + sequence_id: 10 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: is_default + org_name: is_default + fixed_length: 12 + character_set: 63 + column_length: 1 + type: 1 + flags: 1 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 96 + sequence_id: 11 + values: + - type: 253 + name: id + value: 63f3fd41-0c2d-4609-9934-999884526bda + unsigned: false + - type: 253 + name: line1 + value: 123 MG Road + unsigned: false + - type: 253 + name: line2 + value: null + unsigned: false + - type: 253 + name: city + value: Bengaluru + unsigned: false + - type: 253 + name: state + value: Karnataka + unsigned: false + - type: 253 + name: postal_code + value: "560001" + unsigned: false + - type: 253 + name: country + value: IN + unsigned: false + - type: 253 + name: phone + value: "+919876543210" + unsigned: false + - type: 1 + name: is_default + value: "1" + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 12 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777023 + reqtimestampmock: 2025-11-10T12:17:03.533699968Z + restimestampmock: 2025-11-10T12:17:03.534212833Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-252 +spec: + metadata: + connID: "98" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [255, 139, 222, 92, 33, 220, 67, 237, 64, 47, 40, 91, 227, 32, 208, 139, 22, 250, 228, 237, 121, 71, 199, 158, 133, 108, 111, 193, 206, 253, 232, 213] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 152 + auth_plugin_data: [55, 53, 33, 14, 101, 41, 62, 58, 109, 59, 3, 79, 114, 25, 71, 23, 86, 11, 29, 97, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777024 + reqtimestampmock: 2025-11-10T12:17:04.28018839Z + restimestampmock: 2025-11-10T12:17:04.286713074Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-253 +spec: + metadata: + connID: "98" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777024 + reqtimestampmock: 2025-11-10T12:17:04.286856002Z + restimestampmock: 2025-11-10T12:17:04.28707058Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-254 +spec: + metadata: + connID: "98" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777024 + reqtimestampmock: 2025-11-10T12:17:04.287191539Z + restimestampmock: 2025-11-10T12:17:04.287378968Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-255 +spec: + metadata: + connID: "98" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777024 + reqtimestampmock: 2025-11-10T12:17:04.287476997Z + restimestampmock: 2025-11-10T12:17:04.287571397Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-256 +spec: + metadata: + connID: "98" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='16be51aa-dcaf-4681-b125-ccffcc64e663' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 16be51aa-dcaf-4681-b125-ccffcc64e663 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777024 + reqtimestampmock: 2025-11-10T12:17:04.287733584Z + restimestampmock: 2025-11-10T12:17:04.288069582Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-257 +spec: + metadata: + connID: "98" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='16be51aa-dcaf-4681-b125-ccffcc64e663' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777024 + reqtimestampmock: 2025-11-10T12:17:04.28831858Z + restimestampmock: 2025-11-10T12:17:04.288753786Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-258 +spec: + metadata: + connID: "98" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='16be51aa-dcaf-4681-b125-ccffcc64e663' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777024 + reqtimestampmock: 2025-11-10T12:17:04.288980684Z + restimestampmock: 2025-11-10T12:17:04.289279041Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-259 +spec: + metadata: + connID: "98" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777024 + reqtimestampmock: 2025-11-10T12:17:04.289401891Z + restimestampmock: 2025-11-10T12:17:04.30354454Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-260 +spec: + metadata: + connID: "100" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [175, 190, 189, 157, 163, 72, 11, 250, 98, 195, 30, 203, 113, 187, 10, 150, 219, 133, 132, 112, 43, 182, 191, 3, 98, 146, 32, 13, 138, 93, 178, 146] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 153 + auth_plugin_data: [1, 119, 63, 46, 37, 21, 11, 102, 12, 121, 115, 10, 2, 124, 120, 117, 99, 9, 62, 126, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777026 + reqtimestampmock: 2025-11-10T12:17:06.374810525Z + restimestampmock: 2025-11-10T12:17:06.383281432Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-261 +spec: + metadata: + connID: "100" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777026 + reqtimestampmock: 2025-11-10T12:17:06.38344688Z + restimestampmock: 2025-11-10T12:17:06.383632809Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-262 +spec: + metadata: + connID: "100" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777026 + reqtimestampmock: 2025-11-10T12:17:06.383726688Z + restimestampmock: 2025-11-10T12:17:06.383845787Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-263 +spec: + metadata: + connID: "100" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777026 + reqtimestampmock: 2025-11-10T12:17:06.383946426Z + restimestampmock: 2025-11-10T12:17:06.384042566Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-264 +spec: + metadata: + connID: "100" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('18ae4cc0-1e25-49fd-bb2a-1002466783c4', 'rohan_order_01', 'rohan_order_01@example.in', 'scrypt:32768:8:1$i0g4HAh5wOvXaMb4$b513b263cfb680f52a007c24a203c039455a500b4da72eab2ea9b00a30bfae3441c291fa7c743185d41cceba25b443fda2a3fa140329a95a334eeb0089df793b', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777026 + reqtimestampmock: 2025-11-10T12:17:06.434281177Z + restimestampmock: 2025-11-10T12:17:06.434676753Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-265 +spec: + metadata: + connID: "100" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777026 + reqtimestampmock: 2025-11-10T12:17:06.434799632Z + restimestampmock: 2025-11-10T12:17:06.448568104Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-266 +spec: + metadata: + connID: "102" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [182, 151, 170, 147, 74, 215, 112, 5, 253, 217, 43, 251, 126, 35, 11, 10, 4, 70, 97, 15, 242, 138, 192, 127, 219, 130, 135, 48, 45, 23, 10, 29] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 154 + auth_plugin_data: [54, 78, 95, 17, 118, 34, 15, 39, 22, 80, 11, 88, 12, 21, 15, 51, 81, 22, 74, 57, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.208624083Z + restimestampmock: 2025-11-10T12:17:07.214793351Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-267 +spec: + metadata: + connID: "102" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.214939298Z + restimestampmock: 2025-11-10T12:17:07.215141878Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-268 +spec: + metadata: + connID: "102" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.215241726Z + restimestampmock: 2025-11-10T12:17:07.215376745Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-269 +spec: + metadata: + connID: "102" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.215469284Z + restimestampmock: 2025-11-10T12:17:07.215537524Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-270 +spec: + metadata: + connID: "102" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='rohan_order_01' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: 18ae4cc0-1e25-49fd-bb2a-1002466783c4 + unsigned: false + - type: 253 + name: username + value: rohan_order_01 + unsigned: false + - type: 253 + name: email + value: rohan_order_01@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$i0g4HAh5wOvXaMb4$b513b263cfb680f52a007c24a203c039455a500b4da72eab2ea9b00a30bfae3441c291fa7c743185d41cceba25b443fda2a3fa140329a95a334eeb0089df793b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.215715212Z + restimestampmock: 2025-11-10T12:17:07.216165078Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-271 +spec: + metadata: + connID: "104" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [124, 44, 43, 51, 130, 188, 252, 124, 42, 31, 81, 113, 118, 14, 19, 204, 213, 22, 54, 156, 50, 231, 42, 197, 46, 146, 103, 177, 73, 41, 5, 93] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 156 + auth_plugin_data: [50, 34, 47, 49, 28, 65, 97, 45, 50, 5, 41, 75, 20, 94, 118, 35, 109, 40, 120, 50, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.945379071Z + restimestampmock: 2025-11-10T12:17:07.951720007Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-272 +spec: + metadata: + connID: "104" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.951875935Z + restimestampmock: 2025-11-10T12:17:07.952056574Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-273 +spec: + metadata: + connID: "104" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.952183882Z + restimestampmock: 2025-11-10T12:17:07.952379371Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-274 +spec: + metadata: + connID: "104" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.952555629Z + restimestampmock: 2025-11-10T12:17:07.952690808Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-275 +spec: + metadata: + connID: "104" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='18ae4cc0-1e25-49fd-bb2a-1002466783c4' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 18ae4cc0-1e25-49fd-bb2a-1002466783c4 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.952877566Z + restimestampmock: 2025-11-10T12:17:07.953275693Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-276 +spec: + metadata: + connID: "104" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 270 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('e19a385a-5d5c-4e5c-8022-32988d92a112','18ae4cc0-1e25-49fd-bb2a-1002466783c4','45 BTM Layout',NULL,'Bengaluru','Karnataka','560076','IN','+918765432109',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.953455672Z + restimestampmock: 2025-11-10T12:17:07.953821839Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-277 +spec: + metadata: + connID: "104" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='18ae4cc0-1e25-49fd-bb2a-1002466783c4' AND id<>'e19a385a-5d5c-4e5c-8022-32988d92a112' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.954028736Z + restimestampmock: 2025-11-10T12:17:07.954297275Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-278 +spec: + metadata: + connID: "104" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777027 + reqtimestampmock: 2025-11-10T12:17:07.954423413Z + restimestampmock: 2025-11-10T12:17:07.963609465Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-279 +spec: + metadata: + connID: "106" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [34, 4, 63, 172, 81, 116, 23, 78, 113, 254, 180, 240, 165, 233, 47, 231, 22, 241, 112, 87, 29, 127, 242, 69, 58, 95, 89, 15, 109, 22, 47, 83] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 157 + auth_plugin_data: [34, 78, 27, 18, 46, 90, 3, 3, 2, 105, 92, 7, 5, 49, 15, 87, 70, 90, 6, 113, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777029 + reqtimestampmock: 2025-11-10T12:17:09.529181417Z + restimestampmock: 2025-11-10T12:17:09.535194055Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-280 +spec: + metadata: + connID: "106" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777029 + reqtimestampmock: 2025-11-10T12:17:09.535391213Z + restimestampmock: 2025-11-10T12:17:09.535572842Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-281 +spec: + metadata: + connID: "106" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777029 + reqtimestampmock: 2025-11-10T12:17:09.535661241Z + restimestampmock: 2025-11-10T12:17:09.53585837Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-282 +spec: + metadata: + connID: "106" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777029 + reqtimestampmock: 2025-11-10T12:17:09.536003768Z + restimestampmock: 2025-11-10T12:17:09.536099717Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-283 +spec: + metadata: + connID: "106" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='18ae4cc0-1e25-49fd-bb2a-1002466783c4' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 18ae4cc0-1e25-49fd-bb2a-1002466783c4 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777029 + reqtimestampmock: 2025-11-10T12:17:09.536277115Z + restimestampmock: 2025-11-10T12:17:09.536673762Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-284 +spec: + metadata: + connID: "106" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='18ae4cc0-1e25-49fd-bb2a-1002466783c4' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777029 + reqtimestampmock: 2025-11-10T12:17:09.536829151Z + restimestampmock: 2025-11-10T12:17:09.537259577Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-285 +spec: + metadata: + connID: "106" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='18ae4cc0-1e25-49fd-bb2a-1002466783c4' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777029 + reqtimestampmock: 2025-11-10T12:17:09.537424886Z + restimestampmock: 2025-11-10T12:17:09.537710844Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-286 +spec: + metadata: + connID: "106" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777029 + reqtimestampmock: 2025-11-10T12:17:09.537830092Z + restimestampmock: 2025-11-10T12:17:09.5486655Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-287 +spec: + metadata: + connID: "108" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [28, 145, 15, 11, 38, 200, 225, 61, 241, 163, 5, 186, 102, 238, 73, 32, 167, 53, 1, 115, 11, 70, 6, 14, 84, 250, 28, 60, 93, 202, 140, 137] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 158 + auth_plugin_data: [51, 92, 25, 117, 65, 41, 76, 57, 10, 19, 62, 50, 84, 70, 55, 30, 89, 113, 68, 14, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777032 + reqtimestampmock: 2025-11-10T12:17:12.699662168Z + restimestampmock: 2025-11-10T12:17:12.706198562Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-288 +spec: + metadata: + connID: "108" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777032 + reqtimestampmock: 2025-11-10T12:17:12.706380431Z + restimestampmock: 2025-11-10T12:17:12.70653906Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-289 +spec: + metadata: + connID: "108" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777032 + reqtimestampmock: 2025-11-10T12:17:12.706645269Z + restimestampmock: 2025-11-10T12:17:12.706769007Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-290 +spec: + metadata: + connID: "108" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777032 + reqtimestampmock: 2025-11-10T12:17:12.706872636Z + restimestampmock: 2025-11-10T12:17:12.706967866Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-291 +spec: + metadata: + connID: "108" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('46442b8f-133b-4c4a-beca-096cc6a034cd', 'anika_login_fail', 'anika_login_fail@example.in', 'scrypt:32768:8:1$1Z8yJHz7in9Puej2$60f47a81090045c9fed68f70eb1f1ca273afebc1c7dae16ae40b9580117af4390f0652eccb7e78693912646a0c4475d52b0067f48abe3542c99fd10e9b5c7776', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777032 + reqtimestampmock: 2025-11-10T12:17:12.753887635Z + restimestampmock: 2025-11-10T12:17:12.754384881Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-292 +spec: + metadata: + connID: "108" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777032 + reqtimestampmock: 2025-11-10T12:17:12.754560589Z + restimestampmock: 2025-11-10T12:17:12.766431698Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-293 +spec: + metadata: + connID: "110" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [231, 181, 139, 137, 231, 97, 115, 167, 140, 131, 148, 225, 84, 96, 88, 87, 128, 215, 102, 210, 98, 208, 188, 150, 164, 130, 21, 77, 138, 74, 53, 107] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 160 + auth_plugin_data: [84, 116, 23, 48, 104, 30, 116, 24, 11, 50, 20, 56, 106, 25, 112, 83, 125, 111, 99, 10, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777033 + reqtimestampmock: 2025-11-10T12:17:13.503955032Z + restimestampmock: 2025-11-10T12:17:13.510128739Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-294 +spec: + metadata: + connID: "110" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777033 + reqtimestampmock: 2025-11-10T12:17:13.510316599Z + restimestampmock: 2025-11-10T12:17:13.510507986Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-295 +spec: + metadata: + connID: "110" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777033 + reqtimestampmock: 2025-11-10T12:17:13.510628435Z + restimestampmock: 2025-11-10T12:17:13.510766784Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-296 +spec: + metadata: + connID: "110" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777033 + reqtimestampmock: 2025-11-10T12:17:13.510878703Z + restimestampmock: 2025-11-10T12:17:13.511005923Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-297 +spec: + metadata: + connID: "110" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='anika_login_fail' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: 46442b8f-133b-4c4a-beca-096cc6a034cd + unsigned: false + - type: 253 + name: username + value: anika_login_fail + unsigned: false + - type: 253 + name: email + value: anika_login_fail@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$1Z8yJHz7in9Puej2$60f47a81090045c9fed68f70eb1f1ca273afebc1c7dae16ae40b9580117af4390f0652eccb7e78693912646a0c4475d52b0067f48abe3542c99fd10e9b5c7776 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777033 + reqtimestampmock: 2025-11-10T12:17:13.51126619Z + restimestampmock: 2025-11-10T12:17:13.511684657Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-298 +spec: + metadata: + connID: "112" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [79, 165, 134, 217, 82, 184, 117, 15, 126, 6, 167, 74, 123, 20, 119, 212, 125, 126, 67, 123, 203, 81, 185, 149, 235, 197, 117, 98, 187, 169, 216, 9] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 161 + auth_plugin_data: [80, 47, 6, 83, 122, 43, 44, 51, 121, 125, 4, 75, 118, 120, 60, 41, 118, 21, 120, 33, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777034 + reqtimestampmock: 2025-11-10T12:17:14.228656667Z + restimestampmock: 2025-11-10T12:17:14.234764615Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-299 +spec: + metadata: + connID: "112" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777034 + reqtimestampmock: 2025-11-10T12:17:14.234921323Z + restimestampmock: 2025-11-10T12:17:14.235104842Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-300 +spec: + metadata: + connID: "112" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777034 + reqtimestampmock: 2025-11-10T12:17:14.235278221Z + restimestampmock: 2025-11-10T12:17:14.235421899Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-301 +spec: + metadata: + connID: "112" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777034 + reqtimestampmock: 2025-11-10T12:17:14.235522508Z + restimestampmock: 2025-11-10T12:17:14.235616547Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-302 +spec: + metadata: + connID: "112" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='anika_login_fail' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: 46442b8f-133b-4c4a-beca-096cc6a034cd + unsigned: false + - type: 253 + name: username + value: anika_login_fail + unsigned: false + - type: 253 + name: email + value: anika_login_fail@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$1Z8yJHz7in9Puej2$60f47a81090045c9fed68f70eb1f1ca273afebc1c7dae16ae40b9580117af4390f0652eccb7e78693912646a0c4475d52b0067f48abe3542c99fd10e9b5c7776 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777034 + reqtimestampmock: 2025-11-10T12:17:14.235807846Z + restimestampmock: 2025-11-10T12:17:14.236381891Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-303 +spec: + metadata: + connID: "114" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [42, 8, 98, 15, 30, 34, 36, 99, 82, 195, 59, 217, 221, 54, 53, 30, 81, 208, 238, 253, 60, 183, 40, 207, 204, 243, 117, 147, 10, 178, 179, 41] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 162 + auth_plugin_data: [75, 29, 89, 52, 72, 101, 119, 6, 30, 78, 31, 1, 6, 104, 56, 90, 77, 122, 43, 79, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.046726915Z + restimestampmock: 2025-11-10T12:17:15.053095311Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-304 +spec: + metadata: + connID: "114" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.05328555Z + restimestampmock: 2025-11-10T12:17:15.053453078Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-305 +spec: + metadata: + connID: "114" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.053599947Z + restimestampmock: 2025-11-10T12:17:15.053826324Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-306 +spec: + metadata: + connID: "114" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.054004563Z + restimestampmock: 2025-11-10T12:17:15.054119793Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-307 +spec: + metadata: + connID: "114" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='46442b8f-133b-4c4a-beca-096cc6a034cd' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 46442b8f-133b-4c4a-beca-096cc6a034cd + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.0543499Z + restimestampmock: 2025-11-10T12:17:15.054672367Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-308 +spec: + metadata: + connID: "114" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='46442b8f-133b-4c4a-beca-096cc6a034cd' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.055258893Z + restimestampmock: 2025-11-10T12:17:15.05558681Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-309 +spec: + metadata: + connID: "114" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='46442b8f-133b-4c4a-beca-096cc6a034cd' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.055725518Z + restimestampmock: 2025-11-10T12:17:15.059700945Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-310 +spec: + metadata: + connID: "114" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.059830003Z + restimestampmock: 2025-11-10T12:17:15.068443559Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-311 +spec: + metadata: + connID: "116" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [229, 0, 239, 40, 251, 17, 191, 171, 215, 160, 73, 128, 10, 68, 134, 29, 209, 131, 123, 228, 116, 8, 76, 233, 216, 111, 75, 184, 219, 90, 99, 193] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 163 + auth_plugin_data: [66, 117, 126, 43, 103, 104, 97, 80, 15, 6, 66, 88, 76, 30, 60, 6, 58, 90, 121, 7, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.886185633Z + restimestampmock: 2025-11-10T12:17:15.89229382Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-312 +spec: + metadata: + connID: "116" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.892425889Z + restimestampmock: 2025-11-10T12:17:15.892585228Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-313 +spec: + metadata: + connID: "116" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.892725977Z + restimestampmock: 2025-11-10T12:17:15.892945135Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-314 +spec: + metadata: + connID: "116" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.893052954Z + restimestampmock: 2025-11-10T12:17:15.893168753Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-315 +spec: + metadata: + connID: "116" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='ghost_user_999' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 6 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777035 + reqtimestampmock: 2025-11-10T12:17:15.893361672Z + restimestampmock: 2025-11-10T12:17:15.893747918Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-316 +spec: + metadata: + connID: "118" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [54, 131, 65, 158, 127, 219, 128, 66, 135, 237, 198, 107, 107, 60, 15, 202, 212, 29, 62, 26, 8, 220, 70, 6, 37, 193, 214, 57, 221, 53, 79, 160] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 164 + auth_plugin_data: [91, 49, 47, 88, 91, 96, 83, 122, 74, 25, 57, 22, 55, 6, 10, 6, 29, 89, 53, 86, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777036 + reqtimestampmock: 2025-11-10T12:17:16.711325853Z + restimestampmock: 2025-11-10T12:17:16.717452041Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-317 +spec: + metadata: + connID: "118" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777036 + reqtimestampmock: 2025-11-10T12:17:16.717662228Z + restimestampmock: 2025-11-10T12:17:16.717875347Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-318 +spec: + metadata: + connID: "118" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777036 + reqtimestampmock: 2025-11-10T12:17:16.718024055Z + restimestampmock: 2025-11-10T12:17:16.718188604Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-319 +spec: + metadata: + connID: "118" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777036 + reqtimestampmock: 2025-11-10T12:17:16.718304153Z + restimestampmock: 2025-11-10T12:17:16.718402682Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-320 +spec: + metadata: + connID: "118" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('5c02e676-4397-466c-ad59-7a47f2026a13', 'vikram_duplicate', 'vikram_duplicate@example.in', 'scrypt:32768:8:1$Z8I05SeP2jOUgv8j$4699e43d9c86655cc2b4fa9476274f0515c9c0a73ce907cf1da1a5a3775fb047fce9e35142bcea133c6c5cf70f8b81503310f6235f9870782d6b68e4920b7d6f', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777036 + reqtimestampmock: 2025-11-10T12:17:16.767119556Z + restimestampmock: 2025-11-10T12:17:16.767513073Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-321 +spec: + metadata: + connID: "118" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777036 + reqtimestampmock: 2025-11-10T12:17:16.767634482Z + restimestampmock: 2025-11-10T12:17:16.779490441Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-322 +spec: + metadata: + connID: "120" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [231, 160, 210, 254, 214, 104, 25, 95, 175, 203, 15, 143, 214, 208, 75, 99, 145, 55, 31, 51, 204, 183, 87, 145, 164, 123, 34, 162, 136, 6, 118, 114] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 165 + auth_plugin_data: [20, 112, 53, 11, 75, 112, 19, 103, 51, 83, 97, 127, 54, 94, 20, 69, 9, 124, 21, 7, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777037 + reqtimestampmock: 2025-11-10T12:17:17.457343838Z + restimestampmock: 2025-11-10T12:17:17.466161083Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-323 +spec: + metadata: + connID: "120" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777037 + reqtimestampmock: 2025-11-10T12:17:17.466386521Z + restimestampmock: 2025-11-10T12:17:17.466581818Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-324 +spec: + metadata: + connID: "120" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777037 + reqtimestampmock: 2025-11-10T12:17:17.466683798Z + restimestampmock: 2025-11-10T12:17:17.466917406Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-325 +spec: + metadata: + connID: "120" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777037 + reqtimestampmock: 2025-11-10T12:17:17.467058574Z + restimestampmock: 2025-11-10T12:17:17.467208114Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-326 +spec: + metadata: + connID: "120" + requestOperation: COM_QUERY + responseOperation: ERR + type: mocks + requests: + - header: + header: + payload_length: 332 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('7b663cf2-1145-4b28-a1ae-a537bd3932bb', 'vikram_duplicate', 'another_email@example.in', 'scrypt:32768:8:1$DlFk0tgJ1d64bal9$f807bde8e59ec8d672bee110a5b6190fc9eab33ddef1de395a73412592c53621da8ad5fbb271addd4c12faedf7ae68cd8a8ac6b53bcfb23979c9620e3f61045c', NULL) + responses: + - header: + header: + payload_length: 77 + sequence_id: 1 + packet_type: ERR + message: + header: 255 + error_code: 1062 + sql_state_marker: '#' + sql_state: "23000" + error_message: Duplicate entry 'vikram_duplicate' for key 'users.uq_users_username' + created: 1762777037 + reqtimestampmock: 2025-11-10T12:17:17.52388413Z + restimestampmock: 2025-11-10T12:17:17.527794607Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-327 +spec: + metadata: + connID: "120" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 11 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: rollback + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777037 + reqtimestampmock: 2025-11-10T12:17:17.528198023Z + restimestampmock: 2025-11-10T12:17:17.533512647Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-328 +spec: + metadata: + connID: "122" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [233, 13, 13, 109, 152, 217, 206, 125, 65, 254, 74, 82, 17, 221, 96, 133, 34, 226, 231, 195, 10, 83, 87, 230, 241, 227, 190, 53, 105, 151, 19, 227] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 167 + auth_plugin_data: [22, 91, 73, 97, 41, 88, 119, 117, 8, 7, 110, 74, 115, 48, 21, 127, 58, 29, 112, 102, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.227310808Z + restimestampmock: 2025-11-10T12:17:18.233417157Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-329 +spec: + metadata: + connID: "122" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.233626215Z + restimestampmock: 2025-11-10T12:17:18.233794704Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-330 +spec: + metadata: + connID: "122" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.233931893Z + restimestampmock: 2025-11-10T12:17:18.234066412Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-331 +spec: + metadata: + connID: "122" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.234191821Z + restimestampmock: 2025-11-10T12:17:18.234625986Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-332 +spec: + metadata: + connID: "122" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='vikram_duplicate' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: 5c02e676-4397-466c-ad59-7a47f2026a13 + unsigned: false + - type: 253 + name: username + value: vikram_duplicate + unsigned: false + - type: 253 + name: email + value: vikram_duplicate@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Z8I05SeP2jOUgv8j$4699e43d9c86655cc2b4fa9476274f0515c9c0a73ce907cf1da1a5a3775fb047fce9e35142bcea133c6c5cf70f8b81503310f6235f9870782d6b68e4920b7d6f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.234908655Z + restimestampmock: 2025-11-10T12:17:18.23544034Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-333 +spec: + metadata: + connID: "124" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [31, 37, 161, 20, 230, 6, 173, 150, 127, 156, 254, 202, 135, 20, 64, 149, 232, 6, 6, 127, 96, 236, 66, 119, 32, 178, 4, 70, 180, 162, 213, 158] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 168 + auth_plugin_data: [94, 105, 123, 127, 10, 87, 33, 48, 6, 81, 76, 94, 97, 103, 96, 49, 66, 89, 91, 53, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.979687162Z + restimestampmock: 2025-11-10T12:17:18.986290906Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-334 +spec: + metadata: + connID: "124" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.986449125Z + restimestampmock: 2025-11-10T12:17:18.986669462Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-335 +spec: + metadata: + connID: "124" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.986784411Z + restimestampmock: 2025-11-10T12:17:18.986956489Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-336 +spec: + metadata: + connID: "124" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.987053769Z + restimestampmock: 2025-11-10T12:17:18.987139688Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-337 +spec: + metadata: + connID: "124" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5c02e676-4397-466c-ad59-7a47f2026a13' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5c02e676-4397-466c-ad59-7a47f2026a13 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.987302318Z + restimestampmock: 2025-11-10T12:17:18.987646624Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-338 +spec: + metadata: + connID: "124" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='5c02e676-4397-466c-ad59-7a47f2026a13' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.988024201Z + restimestampmock: 2025-11-10T12:17:18.988303609Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-339 +spec: + metadata: + connID: "124" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='5c02e676-4397-466c-ad59-7a47f2026a13' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.988463467Z + restimestampmock: 2025-11-10T12:17:18.988758194Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-340 +spec: + metadata: + connID: "124" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777038 + reqtimestampmock: 2025-11-10T12:17:18.988865944Z + restimestampmock: 2025-11-10T12:17:18.998396292Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-341 +spec: + metadata: + connID: "126" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [39, 18, 162, 154, 25, 140, 69, 207, 130, 236, 89, 139, 229, 203, 37, 41, 185, 215, 173, 174, 247, 144, 7, 26, 64, 69, 201, 174, 235, 71, 94, 146] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 169 + auth_plugin_data: [79, 40, 109, 93, 34, 114, 74, 107, 121, 85, 64, 118, 39, 56, 95, 78, 2, 16, 13, 41, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777041 + reqtimestampmock: 2025-11-10T12:17:21.449654496Z + restimestampmock: 2025-11-10T12:17:21.455669146Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-342 +spec: + metadata: + connID: "126" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777041 + reqtimestampmock: 2025-11-10T12:17:21.455856724Z + restimestampmock: 2025-11-10T12:17:21.456031413Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-343 +spec: + metadata: + connID: "126" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777041 + reqtimestampmock: 2025-11-10T12:17:21.456137991Z + restimestampmock: 2025-11-10T12:17:21.456276891Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-344 +spec: + metadata: + connID: "126" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777041 + reqtimestampmock: 2025-11-10T12:17:21.45636421Z + restimestampmock: 2025-11-10T12:17:21.456506929Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-345 +spec: + metadata: + connID: "126" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('ce7a9d8f-2c45-4db1-a930-b46dcc8ffb2c', 'sunita_email', 'sunita_duplicate@example.in', 'scrypt:32768:8:1$Ba5d3qSM01rGhcc8$c6382bd678050a4c421fd7b8fb2db5a0664b8c3f31e8fb8d3fa341c5777aba1dc62dba8c672f5dc5675c0e6cf625529529d63096141e2741ad1986559923f8d3', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777041 + reqtimestampmock: 2025-11-10T12:17:21.503901375Z + restimestampmock: 2025-11-10T12:17:21.50440982Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-346 +spec: + metadata: + connID: "126" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777041 + reqtimestampmock: 2025-11-10T12:17:21.504549319Z + restimestampmock: 2025-11-10T12:17:21.513469143Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-347 +spec: + metadata: + connID: "128" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [61, 82, 90, 147, 179, 120, 44, 122, 36, 197, 225, 94, 116, 243, 148, 38, 224, 245, 226, 144, 19, 52, 5, 213, 200, 188, 120, 67, 91, 57, 198, 132] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 170 + auth_plugin_data: [117, 9, 5, 98, 55, 71, 91, 115, 84, 122, 55, 118, 81, 41, 110, 74, 72, 110, 104, 47, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.198114224Z + restimestampmock: 2025-11-10T12:17:22.204670028Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-348 +spec: + metadata: + connID: "128" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.204856897Z + restimestampmock: 2025-11-10T12:17:22.205012845Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-349 +spec: + metadata: + connID: "128" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.205115345Z + restimestampmock: 2025-11-10T12:17:22.205324803Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-350 +spec: + metadata: + connID: "128" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.205412472Z + restimestampmock: 2025-11-10T12:17:22.205511361Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-351 +spec: + metadata: + connID: "128" + requestOperation: COM_QUERY + responseOperation: ERR + type: mocks + requests: + - header: + header: + payload_length: 333 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('816d4e6f-a7ac-4011-8b35-f1f022097b55', 'another_sunita', 'sunita_duplicate@example.in', 'scrypt:32768:8:1$3T9IHSnB58DVY1Go$4d1914640fa14ab34581e6e3be020bc3a2e7b4be5e637d28607c167fdc38760e5eb17e974a5a2027e60a200aedf4c41938d3df30c99f7dd6faa7fcf886642cc3', NULL) + responses: + - header: + header: + payload_length: 85 + sequence_id: 1 + packet_type: ERR + message: + header: 255 + error_code: 1062 + sql_state_marker: '#' + sql_state: "23000" + error_message: Duplicate entry 'sunita_duplicate@example.in' for key 'users.uq_users_email' + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.255971912Z + restimestampmock: 2025-11-10T12:17:22.256525066Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-352 +spec: + metadata: + connID: "128" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 11 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: rollback + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.256670355Z + restimestampmock: 2025-11-10T12:17:22.261427835Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-353 +spec: + metadata: + connID: "130" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [12, 168, 46, 132, 128, 230, 24, 63, 243, 149, 136, 180, 155, 123, 134, 224, 114, 241, 133, 4, 194, 42, 77, 199, 120, 111, 84, 8, 160, 88, 67, 71] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 172 + auth_plugin_data: [16, 126, 33, 21, 83, 75, 8, 84, 102, 86, 76, 69, 62, 76, 114, 112, 14, 7, 89, 21, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.958500481Z + restimestampmock: 2025-11-10T12:17:22.964814837Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-354 +spec: + metadata: + connID: "130" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.964992645Z + restimestampmock: 2025-11-10T12:17:22.965198954Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-355 +spec: + metadata: + connID: "130" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.965293683Z + restimestampmock: 2025-11-10T12:17:22.965442701Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-356 +spec: + metadata: + connID: "130" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.9655641Z + restimestampmock: 2025-11-10T12:17:22.965686849Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-357 +spec: + metadata: + connID: "130" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='sunita_email' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: ce7a9d8f-2c45-4db1-a930-b46dcc8ffb2c + unsigned: false + - type: 253 + name: username + value: sunita_email + unsigned: false + - type: 253 + name: email + value: sunita_duplicate@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Ba5d3qSM01rGhcc8$c6382bd678050a4c421fd7b8fb2db5a0664b8c3f31e8fb8d3fa341c5777aba1dc62dba8c672f5dc5675c0e6cf625529529d63096141e2741ad1986559923f8d3 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777042 + reqtimestampmock: 2025-11-10T12:17:22.965858348Z + restimestampmock: 2025-11-10T12:17:22.966265595Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-358 +spec: + metadata: + connID: "132" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [234, 27, 152, 171, 102, 90, 221, 26, 42, 183, 109, 89, 37, 51, 100, 51, 113, 120, 18, 198, 121, 104, 10, 4, 219, 145, 109, 202, 204, 240, 102, 227] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 173 + auth_plugin_data: [82, 18, 47, 102, 45, 41, 97, 58, 100, 103, 114, 90, 37, 98, 62, 118, 106, 20, 93, 37, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777043 + reqtimestampmock: 2025-11-10T12:17:23.702742626Z + restimestampmock: 2025-11-10T12:17:23.70921003Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-359 +spec: + metadata: + connID: "132" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777043 + reqtimestampmock: 2025-11-10T12:17:23.709362909Z + restimestampmock: 2025-11-10T12:17:23.709536197Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-360 +spec: + metadata: + connID: "132" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777043 + reqtimestampmock: 2025-11-10T12:17:23.709652966Z + restimestampmock: 2025-11-10T12:17:23.709816335Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-361 +spec: + metadata: + connID: "132" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777043 + reqtimestampmock: 2025-11-10T12:17:23.709991474Z + restimestampmock: 2025-11-10T12:17:23.710074213Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-362 +spec: + metadata: + connID: "132" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='ce7a9d8f-2c45-4db1-a930-b46dcc8ffb2c' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: ce7a9d8f-2c45-4db1-a930-b46dcc8ffb2c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777043 + reqtimestampmock: 2025-11-10T12:17:23.710267661Z + restimestampmock: 2025-11-10T12:17:23.710626559Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-363 +spec: + metadata: + connID: "132" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='ce7a9d8f-2c45-4db1-a930-b46dcc8ffb2c' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777043 + reqtimestampmock: 2025-11-10T12:17:23.710822157Z + restimestampmock: 2025-11-10T12:17:23.711040675Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-364 +spec: + metadata: + connID: "132" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='ce7a9d8f-2c45-4db1-a930-b46dcc8ffb2c' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777043 + reqtimestampmock: 2025-11-10T12:17:23.711191594Z + restimestampmock: 2025-11-10T12:17:23.71150406Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-365 +spec: + metadata: + connID: "132" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777043 + reqtimestampmock: 2025-11-10T12:17:23.71161272Z + restimestampmock: 2025-11-10T12:17:23.7233324Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-366 +spec: + metadata: + connID: "134" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [148, 214, 241, 71, 183, 133, 116, 235, 123, 12, 5, 233, 102, 33, 72, 75, 128, 227, 126, 252, 45, 27, 41, 81, 162, 79, 155, 178, 63, 121, 109, 145] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 174 + auth_plugin_data: [26, 85, 113, 14, 46, 127, 71, 55, 39, 91, 35, 6, 74, 15, 115, 41, 20, 51, 14, 50, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777046 + reqtimestampmock: 2025-11-10T12:17:26.278386159Z + restimestampmock: 2025-11-10T12:17:26.284777415Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-367 +spec: + metadata: + connID: "134" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777046 + reqtimestampmock: 2025-11-10T12:17:26.284970863Z + restimestampmock: 2025-11-10T12:17:26.285165521Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-368 +spec: + metadata: + connID: "134" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777046 + reqtimestampmock: 2025-11-10T12:17:26.28527058Z + restimestampmock: 2025-11-10T12:17:26.285426009Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-369 +spec: + metadata: + connID: "134" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777046 + reqtimestampmock: 2025-11-10T12:17:26.285573097Z + restimestampmock: 2025-11-10T12:17:26.285718927Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-370 +spec: + metadata: + connID: "134" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 325 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('a7ef1d19-4a3a-4a51-ab42-038fd59367b3', 'noauth_user', 'noauth_user@example.in', 'scrypt:32768:8:1$X0VCRZpSKa4Yq2EF$396a7432b5dea777ede45b89df3bf0b7e20d4fc0c463181ce3652e60bc68ee860c3727ca4bd142b971a519a5fe5b662fc7ffa5296702049a7d2d9dc9c726873c', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777046 + reqtimestampmock: 2025-11-10T12:17:26.334730069Z + restimestampmock: 2025-11-10T12:17:26.335231255Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-371 +spec: + metadata: + connID: "134" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777046 + reqtimestampmock: 2025-11-10T12:17:26.335370673Z + restimestampmock: 2025-11-10T12:17:26.346356539Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-372 +spec: + metadata: + connID: "136" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [41, 148, 210, 226, 25, 196, 86, 66, 245, 219, 106, 73, 74, 220, 52, 90, 53, 241, 3, 133, 107, 232, 21, 209, 84, 178, 101, 255, 96, 180, 23, 51] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 175 + auth_plugin_data: [53, 115, 72, 69, 121, 13, 1, 85, 88, 20, 91, 1, 49, 19, 51, 62, 53, 98, 95, 60, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.1206637Z + restimestampmock: 2025-11-10T12:17:27.127329563Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-373 +spec: + metadata: + connID: "136" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.127510641Z + restimestampmock: 2025-11-10T12:17:27.127717559Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-374 +spec: + metadata: + connID: "136" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.127805759Z + restimestampmock: 2025-11-10T12:17:27.127981757Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-375 +spec: + metadata: + connID: "136" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.128137737Z + restimestampmock: 2025-11-10T12:17:27.128244936Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-376 +spec: + metadata: + connID: "136" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 109 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, phone, created_at FROM users WHERE id = 'a7ef1d19-4a3a-4a51-ab42-038fd59367b3' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 5 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 59 + sequence_id: 6 + catalog: def + schema: user_db + table: users + org_table: users + name: created_at + org_name: created_at + fixed_length: 12 + character_set: 63 + column_length: 19 + type: 7 + flags: 1153 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 93 + sequence_id: 7 + values: + - type: 253 + name: id + value: a7ef1d19-4a3a-4a51-ab42-038fd59367b3 + unsigned: false + - type: 253 + name: username + value: noauth_user + unsigned: false + - type: 253 + name: email + value: noauth_user@example.in + unsigned: false + - type: 253 + name: phone + value: null + unsigned: false + - type: 7 + name: created_at + value: "2025-11-10 12:17:26" + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 8 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.128425385Z + restimestampmock: 2025-11-10T12:17:27.128959469Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-377 +spec: + metadata: + connID: "136" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 190 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, line1, line2, city, state, postal_code, country, phone, is_default FROM addresses WHERE user_id='a7ef1d19-4a3a-4a51-ab42-038fd59367b3' ORDER BY is_default DESC, created_at DESC + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 9 + columns: + - header: + payload_length: 51 + sequence_id: 2 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 3 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line1 + org_name: line1 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 4 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line2 + org_name: line2 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 5 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: city + org_name: city + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 6 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: state + org_name: state + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 69 + sequence_id: 7 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: postal_code + org_name: postal_code + fixed_length: 12 + character_set: 255 + column_length: 80 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 61 + sequence_id: 8 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: country + org_name: country + fixed_length: 12 + character_set: 255 + column_length: 8 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 9 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 67 + sequence_id: 10 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: is_default + org_name: is_default + fixed_length: 12 + character_set: 63 + column_length: 1 + type: 1 + flags: 1 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 11 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.129140788Z + restimestampmock: 2025-11-10T12:17:27.129849612Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-378 +spec: + metadata: + connID: "138" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [81, 192, 152, 141, 110, 92, 196, 239, 30, 216, 203, 56, 66, 115, 103, 255, 88, 196, 108, 139, 118, 182, 17, 10, 83, 153, 179, 201, 203, 76, 251, 170] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 176 + auth_plugin_data: [94, 124, 95, 75, 26, 62, 109, 67, 57, 70, 6, 85, 49, 116, 60, 73, 75, 56, 119, 101, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.87754243Z + restimestampmock: 2025-11-10T12:17:27.883691007Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-379 +spec: + metadata: + connID: "138" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.883890076Z + restimestampmock: 2025-11-10T12:17:27.884118804Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-380 +spec: + metadata: + connID: "138" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.884295132Z + restimestampmock: 2025-11-10T12:17:27.88443103Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-381 +spec: + metadata: + connID: "138" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.88453962Z + restimestampmock: 2025-11-10T12:17:27.884761689Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-382 +spec: + metadata: + connID: "138" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 84 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='noauth_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 235 + sequence_id: 6 + values: + - type: 253 + name: id + value: a7ef1d19-4a3a-4a51-ab42-038fd59367b3 + unsigned: false + - type: 253 + name: username + value: noauth_user + unsigned: false + - type: 253 + name: email + value: noauth_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$X0VCRZpSKa4Yq2EF$396a7432b5dea777ede45b89df3bf0b7e20d4fc0c463181ce3652e60bc68ee860c3727ca4bd142b971a519a5fe5b662fc7ffa5296702049a7d2d9dc9c726873c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777047 + reqtimestampmock: 2025-11-10T12:17:27.884790258Z + restimestampmock: 2025-11-10T12:17:27.885257264Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-383 +spec: + metadata: + connID: "140" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [4, 196, 129, 234, 123, 153, 214, 151, 246, 144, 230, 255, 196, 213, 189, 75, 133, 127, 45, 160, 224, 79, 203, 112, 65, 97, 131, 70, 214, 34, 207, 224] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 178 + auth_plugin_data: [46, 56, 42, 101, 80, 48, 33, 112, 91, 62, 79, 6, 121, 34, 56, 68, 22, 11, 28, 111, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777048 + reqtimestampmock: 2025-11-10T12:17:28.698514703Z + restimestampmock: 2025-11-10T12:17:28.70474188Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-384 +spec: + metadata: + connID: "140" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777048 + reqtimestampmock: 2025-11-10T12:17:28.704890448Z + restimestampmock: 2025-11-10T12:17:28.705049487Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-385 +spec: + metadata: + connID: "140" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777048 + reqtimestampmock: 2025-11-10T12:17:28.705157946Z + restimestampmock: 2025-11-10T12:17:28.705350695Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-386 +spec: + metadata: + connID: "140" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777048 + reqtimestampmock: 2025-11-10T12:17:28.705437484Z + restimestampmock: 2025-11-10T12:17:28.705551373Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-387 +spec: + metadata: + connID: "140" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a7ef1d19-4a3a-4a51-ab42-038fd59367b3' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a7ef1d19-4a3a-4a51-ab42-038fd59367b3 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777048 + reqtimestampmock: 2025-11-10T12:17:28.705731441Z + restimestampmock: 2025-11-10T12:17:28.706071548Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-388 +spec: + metadata: + connID: "140" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='a7ef1d19-4a3a-4a51-ab42-038fd59367b3' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777048 + reqtimestampmock: 2025-11-10T12:17:28.706267596Z + restimestampmock: 2025-11-10T12:17:28.706528194Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-389 +spec: + metadata: + connID: "140" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='a7ef1d19-4a3a-4a51-ab42-038fd59367b3' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777048 + reqtimestampmock: 2025-11-10T12:17:28.706748262Z + restimestampmock: 2025-11-10T12:17:28.70704269Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-390 +spec: + metadata: + connID: "140" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777048 + reqtimestampmock: 2025-11-10T12:17:28.707193019Z + restimestampmock: 2025-11-10T12:17:28.719340235Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-391 +spec: + metadata: + connID: "142" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [6, 190, 244, 66, 84, 86, 248, 147, 133, 126, 198, 136, 4, 119, 215, 128, 139, 249, 252, 199, 134, 73, 191, 11, 179, 73, 69, 123, 101, 148, 172, 90] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 179 + auth_plugin_data: [77, 100, 6, 59, 10, 16, 114, 43, 72, 102, 41, 115, 27, 92, 115, 120, 58, 38, 44, 87, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777050 + reqtimestampmock: 2025-11-10T12:17:30.509332182Z + restimestampmock: 2025-11-10T12:17:30.515630829Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-392 +spec: + metadata: + connID: "142" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777050 + reqtimestampmock: 2025-11-10T12:17:30.515817286Z + restimestampmock: 2025-11-10T12:17:30.516000425Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-393 +spec: + metadata: + connID: "142" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777050 + reqtimestampmock: 2025-11-10T12:17:30.516121004Z + restimestampmock: 2025-11-10T12:17:30.516320312Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-394 +spec: + metadata: + connID: "142" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777050 + reqtimestampmock: 2025-11-10T12:17:30.516515211Z + restimestampmock: 2025-11-10T12:17:30.51660839Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-395 +spec: + metadata: + connID: "142" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 339 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('753085ca-8366-43b0-bfd1-3c017574933e', 'temp_user_for_auth', 'temp_user_for_auth@example.in', 'scrypt:32768:8:1$rOfiINEzMNjeqRUg$6f046b1225c5ab3ad6ae8575a51c5d2de9aadd7b1804c50d5f202e0e4be590243908f5244c1604cd89a5680a756601c4a0312b9cefe6930e377a60c23bf1db2f', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777050 + reqtimestampmock: 2025-11-10T12:17:30.563602699Z + restimestampmock: 2025-11-10T12:17:30.563973736Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-396 +spec: + metadata: + connID: "142" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777050 + reqtimestampmock: 2025-11-10T12:17:30.564088455Z + restimestampmock: 2025-11-10T12:17:30.57529717Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-397 +spec: + metadata: + connID: "144" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [211, 19, 68, 215, 145, 203, 31, 247, 239, 192, 183, 5, 45, 126, 173, 175, 137, 35, 181, 43, 85, 157, 182, 184, 158, 59, 74, 255, 80, 21, 162, 108] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 180 + auth_plugin_data: [7, 112, 11, 52, 43, 7, 58, 52, 1, 122, 30, 108, 50, 89, 17, 62, 53, 123, 67, 26, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777051 + reqtimestampmock: 2025-11-10T12:17:31.324300939Z + restimestampmock: 2025-11-10T12:17:31.330466796Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-398 +spec: + metadata: + connID: "144" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777051 + reqtimestampmock: 2025-11-10T12:17:31.330675354Z + restimestampmock: 2025-11-10T12:17:31.330894143Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-399 +spec: + metadata: + connID: "144" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777051 + reqtimestampmock: 2025-11-10T12:17:31.331098991Z + restimestampmock: 2025-11-10T12:17:31.33129181Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-400 +spec: + metadata: + connID: "144" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777051 + reqtimestampmock: 2025-11-10T12:17:31.331470528Z + restimestampmock: 2025-11-10T12:17:31.331572856Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-401 +spec: + metadata: + connID: "144" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 91 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='temp_user_for_auth' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 249 + sequence_id: 6 + values: + - type: 253 + name: id + value: 753085ca-8366-43b0-bfd1-3c017574933e + unsigned: false + - type: 253 + name: username + value: temp_user_for_auth + unsigned: false + - type: 253 + name: email + value: temp_user_for_auth@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$rOfiINEzMNjeqRUg$6f046b1225c5ab3ad6ae8575a51c5d2de9aadd7b1804c50d5f202e0e4be590243908f5244c1604cd89a5680a756601c4a0312b9cefe6930e377a60c23bf1db2f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777051 + reqtimestampmock: 2025-11-10T12:17:31.331807685Z + restimestampmock: 2025-11-10T12:17:31.33231082Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-402 +spec: + metadata: + connID: "146" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [94, 91, 214, 43, 15, 64, 29, 249, 101, 34, 251, 147, 140, 245, 142, 2, 160, 46, 88, 168, 241, 64, 86, 98, 195, 175, 127, 242, 87, 3, 68, 108] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 181 + auth_plugin_data: [97, 57, 56, 72, 94, 80, 47, 97, 23, 14, 102, 27, 10, 120, 49, 39, 12, 41, 93, 28, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.080542057Z + restimestampmock: 2025-11-10T12:17:32.087046452Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-403 +spec: + metadata: + connID: "146" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.08724834Z + restimestampmock: 2025-11-10T12:17:32.087453048Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-404 +spec: + metadata: + connID: "146" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.087594517Z + restimestampmock: 2025-11-10T12:17:32.087751736Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-405 +spec: + metadata: + connID: "146" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.087852295Z + restimestampmock: 2025-11-10T12:17:32.087971284Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-406 +spec: + metadata: + connID: "146" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 109 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, phone, created_at FROM users WHERE id = '00000000-0000-0000-0000-000000000000' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 5 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 59 + sequence_id: 6 + catalog: def + schema: user_db + table: users + org_table: users + name: created_at + org_name: created_at + fixed_length: 12 + character_set: 63 + column_length: 19 + type: 7 + flags: 1153 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.088135843Z + restimestampmock: 2025-11-10T12:17:32.088512179Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-407 +spec: + metadata: + connID: "148" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [218, 215, 193, 94, 139, 176, 14, 231, 12, 28, 188, 88, 117, 122, 178, 186, 252, 24, 236, 101, 220, 60, 147, 33, 129, 25, 107, 218, 23, 70, 19, 136] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 182 + auth_plugin_data: [127, 88, 110, 54, 85, 109, 9, 37, 20, 57, 91, 75, 34, 108, 11, 70, 71, 50, 83, 108, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.892874258Z + restimestampmock: 2025-11-10T12:17:32.900227535Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-408 +spec: + metadata: + connID: "148" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.900372424Z + restimestampmock: 2025-11-10T12:17:32.900541152Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-409 +spec: + metadata: + connID: "148" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.900635251Z + restimestampmock: 2025-11-10T12:17:32.90077333Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-410 +spec: + metadata: + connID: "148" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.90085502Z + restimestampmock: 2025-11-10T12:17:32.900947579Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-411 +spec: + metadata: + connID: "148" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='753085ca-8366-43b0-bfd1-3c017574933e' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 753085ca-8366-43b0-bfd1-3c017574933e + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.901097078Z + restimestampmock: 2025-11-10T12:17:32.901456705Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-412 +spec: + metadata: + connID: "148" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='753085ca-8366-43b0-bfd1-3c017574933e' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.901619813Z + restimestampmock: 2025-11-10T12:17:32.901929871Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-413 +spec: + metadata: + connID: "148" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='753085ca-8366-43b0-bfd1-3c017574933e' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.902076079Z + restimestampmock: 2025-11-10T12:17:32.902399107Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-414 +spec: + metadata: + connID: "148" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777052 + reqtimestampmock: 2025-11-10T12:17:32.902497166Z + restimestampmock: 2025-11-10T12:17:32.914209735Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-415 +spec: + metadata: + connID: "150" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [123, 177, 18, 129, 97, 155, 232, 243, 240, 179, 211, 159, 82, 236, 11, 52, 174, 152, 194, 98, 249, 51, 87, 69, 228, 90, 230, 192, 209, 232, 30, 107] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 184 + auth_plugin_data: [33, 3, 84, 9, 71, 16, 104, 107, 88, 73, 103, 117, 81, 112, 44, 10, 60, 32, 108, 89, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777053 + reqtimestampmock: 2025-11-10T12:17:33.718684476Z + restimestampmock: 2025-11-10T12:17:33.724724973Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-416 +spec: + metadata: + connID: "150" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777053 + reqtimestampmock: 2025-11-10T12:17:33.724908521Z + restimestampmock: 2025-11-10T12:17:33.72509537Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-417 +spec: + metadata: + connID: "150" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777053 + reqtimestampmock: 2025-11-10T12:17:33.725204939Z + restimestampmock: 2025-11-10T12:17:33.725332648Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-418 +spec: + metadata: + connID: "150" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777053 + reqtimestampmock: 2025-11-10T12:17:33.725418347Z + restimestampmock: 2025-11-10T12:17:33.725511807Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-419 +spec: + metadata: + connID: "150" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 347 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('3b923b66-00fd-4468-ba80-4925b266fe45', 'temp_user_for_auth_del', 'temp_user_for_auth_del@example.in', 'scrypt:32768:8:1$rY06cDYeAPGTWtUE$f975afbfa03a00f7b7050a5e75daa29d8d1c29516ede9d5e55d2fcb9d99bcce4e45e1a9200f46963e7b3122d43915423a5438d8778a4233341ce360db28116fb', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777053 + reqtimestampmock: 2025-11-10T12:17:33.772433507Z + restimestampmock: 2025-11-10T12:17:33.772916583Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-420 +spec: + metadata: + connID: "150" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777053 + reqtimestampmock: 2025-11-10T12:17:33.773083482Z + restimestampmock: 2025-11-10T12:17:33.785298988Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-421 +spec: + metadata: + connID: "152" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [113, 227, 252, 96, 58, 149, 95, 245, 108, 49, 19, 242, 193, 134, 175, 244, 254, 246, 167, 205, 7, 14, 4, 43, 157, 160, 94, 24, 32, 197, 172, 176] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 185 + auth_plugin_data: [40, 37, 15, 89, 116, 113, 74, 125, 21, 107, 39, 76, 73, 109, 123, 29, 69, 37, 7, 27, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777054 + reqtimestampmock: 2025-11-10T12:17:34.485738471Z + restimestampmock: 2025-11-10T12:17:34.492069837Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-422 +spec: + metadata: + connID: "152" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777054 + reqtimestampmock: 2025-11-10T12:17:34.492252916Z + restimestampmock: 2025-11-10T12:17:34.492452005Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-423 +spec: + metadata: + connID: "152" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777054 + reqtimestampmock: 2025-11-10T12:17:34.492564143Z + restimestampmock: 2025-11-10T12:17:34.492742792Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-424 +spec: + metadata: + connID: "152" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777054 + reqtimestampmock: 2025-11-10T12:17:34.492817351Z + restimestampmock: 2025-11-10T12:17:34.49291158Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-425 +spec: + metadata: + connID: "152" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 95 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='temp_user_for_auth_del' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 257 + sequence_id: 6 + values: + - type: 253 + name: id + value: 3b923b66-00fd-4468-ba80-4925b266fe45 + unsigned: false + - type: 253 + name: username + value: temp_user_for_auth_del + unsigned: false + - type: 253 + name: email + value: temp_user_for_auth_del@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$rY06cDYeAPGTWtUE$f975afbfa03a00f7b7050a5e75daa29d8d1c29516ede9d5e55d2fcb9d99bcce4e45e1a9200f46963e7b3122d43915423a5438d8778a4233341ce360db28116fb + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777054 + reqtimestampmock: 2025-11-10T12:17:34.493091859Z + restimestampmock: 2025-11-10T12:17:34.493485746Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-426 +spec: + metadata: + connID: "154" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [123, 209, 118, 163, 245, 88, 188, 151, 119, 19, 9, 189, 188, 75, 73, 243, 33, 83, 191, 65, 196, 207, 98, 252, 1, 188, 29, 146, 127, 141, 249, 234] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 186 + auth_plugin_data: [82, 29, 70, 89, 82, 111, 117, 30, 1, 101, 45, 42, 5, 125, 78, 94, 30, 114, 76, 37, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777055 + reqtimestampmock: 2025-11-10T12:17:35.223622117Z + restimestampmock: 2025-11-10T12:17:35.230113292Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-427 +spec: + metadata: + connID: "154" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777055 + reqtimestampmock: 2025-11-10T12:17:35.230295211Z + restimestampmock: 2025-11-10T12:17:35.230495939Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-428 +spec: + metadata: + connID: "154" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777055 + reqtimestampmock: 2025-11-10T12:17:35.230672437Z + restimestampmock: 2025-11-10T12:17:35.230835096Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-429 +spec: + metadata: + connID: "154" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777055 + reqtimestampmock: 2025-11-10T12:17:35.230955305Z + restimestampmock: 2025-11-10T12:17:35.231113994Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-430 +spec: + metadata: + connID: "154" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='00000000-0000-0000-0000-000000000000' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 3 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777055 + reqtimestampmock: 2025-11-10T12:17:35.231131483Z + restimestampmock: 2025-11-10T12:17:35.23146694Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-431 +spec: + metadata: + connID: "156" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [4, 239, 246, 66, 8, 136, 59, 48, 148, 154, 140, 254, 3, 204, 79, 180, 140, 167, 17, 151, 206, 226, 34, 170, 28, 73, 124, 190, 37, 253, 144, 48] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 187 + auth_plugin_data: [94, 9, 37, 26, 64, 68, 102, 98, 35, 19, 73, 95, 95, 31, 61, 37, 11, 126, 15, 84, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.022694482Z + restimestampmock: 2025-11-10T12:17:36.029535854Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-432 +spec: + metadata: + connID: "156" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.029760512Z + restimestampmock: 2025-11-10T12:17:36.029951371Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-433 +spec: + metadata: + connID: "156" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.030056639Z + restimestampmock: 2025-11-10T12:17:36.030220268Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-434 +spec: + metadata: + connID: "156" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.030332196Z + restimestampmock: 2025-11-10T12:17:36.030435817Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-435 +spec: + metadata: + connID: "156" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='3b923b66-00fd-4468-ba80-4925b266fe45' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 3b923b66-00fd-4468-ba80-4925b266fe45 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.030610575Z + restimestampmock: 2025-11-10T12:17:36.030985582Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-436 +spec: + metadata: + connID: "156" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='3b923b66-00fd-4468-ba80-4925b266fe45' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.03117581Z + restimestampmock: 2025-11-10T12:17:36.031506827Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-437 +spec: + metadata: + connID: "156" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='3b923b66-00fd-4468-ba80-4925b266fe45' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.032020033Z + restimestampmock: 2025-11-10T12:17:36.03238965Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-438 +spec: + metadata: + connID: "156" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.032515329Z + restimestampmock: 2025-11-10T12:17:36.044362928Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-439 +spec: + metadata: + connID: "158" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [136, 17, 156, 124, 147, 34, 17, 16, 32, 221, 145, 51, 44, 187, 82, 126, 235, 84, 2, 13, 56, 246, 183, 151, 90, 25, 8, 228, 231, 187, 175, 242] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 188 + auth_plugin_data: [37, 27, 2, 61, 63, 48, 45, 44, 63, 47, 127, 31, 2, 34, 116, 31, 85, 120, 34, 51, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.868079964Z + restimestampmock: 2025-11-10T12:17:36.874507009Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-440 +spec: + metadata: + connID: "158" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.874680567Z + restimestampmock: 2025-11-10T12:17:36.874875817Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-441 +spec: + metadata: + connID: "158" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.874999596Z + restimestampmock: 2025-11-10T12:17:36.875145914Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-442 +spec: + metadata: + connID: "158" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.875278732Z + restimestampmock: 2025-11-10T12:17:36.875377872Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-443 +spec: + metadata: + connID: "158" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('76e07df1-2f87-4705-89fe-c61e78001e39', 'product_tester', 'product_tester@example.in', 'scrypt:32768:8:1$aBmo9zFT7sufCoEs$c2685f5b4e484a132c6be244b22782899b77e8f34c73fb7cbc131d38935a8a684ab252c9236e5c111073765e978ead8b8888f6b8574daa030c607d63986df5ca', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.925879552Z + restimestampmock: 2025-11-10T12:17:36.926341218Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-444 +spec: + metadata: + connID: "158" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777056 + reqtimestampmock: 2025-11-10T12:17:36.926482837Z + restimestampmock: 2025-11-10T12:17:36.936225075Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-445 +spec: + metadata: + connID: "160" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [177, 205, 74, 23, 73, 84, 48, 94, 23, 39, 82, 160, 160, 147, 219, 99, 108, 242, 101, 217, 170, 140, 82, 166, 66, 230, 158, 84, 204, 91, 217, 154] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 189 + auth_plugin_data: [63, 77, 100, 83, 19, 116, 23, 67, 72, 113, 87, 91, 48, 70, 89, 60, 80, 3, 3, 112, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777057 + reqtimestampmock: 2025-11-10T12:17:37.613532267Z + restimestampmock: 2025-11-10T12:17:37.620499678Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-446 +spec: + metadata: + connID: "160" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777057 + reqtimestampmock: 2025-11-10T12:17:37.620703277Z + restimestampmock: 2025-11-10T12:17:37.620935144Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-447 +spec: + metadata: + connID: "160" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777057 + reqtimestampmock: 2025-11-10T12:17:37.621060194Z + restimestampmock: 2025-11-10T12:17:37.621242432Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-448 +spec: + metadata: + connID: "160" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777057 + reqtimestampmock: 2025-11-10T12:17:37.62137323Z + restimestampmock: 2025-11-10T12:17:37.6214958Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-449 +spec: + metadata: + connID: "160" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='product_tester' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: 76e07df1-2f87-4705-89fe-c61e78001e39 + unsigned: false + - type: 253 + name: username + value: product_tester + unsigned: false + - type: 253 + name: email + value: product_tester@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$aBmo9zFT7sufCoEs$c2685f5b4e484a132c6be244b22782899b77e8f34c73fb7cbc131d38935a8a684ab252c9236e5c111073765e978ead8b8888f6b8574daa030c607d63986df5ca + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777057 + reqtimestampmock: 2025-11-10T12:17:37.621705807Z + restimestampmock: 2025-11-10T12:17:37.622219863Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-450 +spec: + metadata: + connID: "162" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [58, 194, 61, 58, 4, 208, 81, 217, 99, 9, 150, 114, 24, 138, 100, 76, 255, 113, 73, 11, 9, 83, 28, 213, 204, 76, 39, 40, 121, 148, 41, 190] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 191 + auth_plugin_data: [60, 65, 28, 3, 113, 14, 106, 8, 5, 68, 61, 25, 52, 4, 109, 18, 95, 30, 87, 109, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777059 + reqtimestampmock: 2025-11-10T12:17:39.181856486Z + restimestampmock: 2025-11-10T12:17:39.188301681Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-451 +spec: + metadata: + connID: "162" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777059 + reqtimestampmock: 2025-11-10T12:17:39.188496129Z + restimestampmock: 2025-11-10T12:17:39.188715387Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-452 +spec: + metadata: + connID: "162" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777059 + reqtimestampmock: 2025-11-10T12:17:39.188848096Z + restimestampmock: 2025-11-10T12:17:39.189001185Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-453 +spec: + metadata: + connID: "162" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777059 + reqtimestampmock: 2025-11-10T12:17:39.189107454Z + restimestampmock: 2025-11-10T12:17:39.189231113Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-454 +spec: + metadata: + connID: "162" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='76e07df1-2f87-4705-89fe-c61e78001e39' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 76e07df1-2f87-4705-89fe-c61e78001e39 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777059 + reqtimestampmock: 2025-11-10T12:17:39.189415772Z + restimestampmock: 2025-11-10T12:17:39.189779359Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-455 +spec: + metadata: + connID: "162" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='76e07df1-2f87-4705-89fe-c61e78001e39' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777059 + reqtimestampmock: 2025-11-10T12:17:39.189965266Z + restimestampmock: 2025-11-10T12:17:39.190190035Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-456 +spec: + metadata: + connID: "162" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='76e07df1-2f87-4705-89fe-c61e78001e39' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777059 + reqtimestampmock: 2025-11-10T12:17:39.190348793Z + restimestampmock: 2025-11-10T12:17:39.190637581Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-457 +spec: + metadata: + connID: "162" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777059 + reqtimestampmock: 2025-11-10T12:17:39.19080652Z + restimestampmock: 2025-11-10T12:17:39.203261223Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-458 +spec: + metadata: + connID: "164" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [120, 207, 232, 164, 16, 86, 46, 190, 37, 95, 159, 103, 50, 176, 238, 79, 94, 69, 118, 176, 226, 121, 16, 129, 45, 176, 210, 239, 94, 102, 132, 202] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 192 + auth_plugin_data: [109, 81, 96, 4, 14, 50, 109, 19, 12, 47, 74, 110, 7, 20, 114, 16, 50, 27, 122, 65, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777061 + reqtimestampmock: 2025-11-10T12:17:41.539316289Z + restimestampmock: 2025-11-10T12:17:41.546936343Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-459 +spec: + metadata: + connID: "164" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777061 + reqtimestampmock: 2025-11-10T12:17:41.547087242Z + restimestampmock: 2025-11-10T12:17:41.547282181Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-460 +spec: + metadata: + connID: "164" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777061 + reqtimestampmock: 2025-11-10T12:17:41.547390749Z + restimestampmock: 2025-11-10T12:17:41.547537319Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-461 +spec: + metadata: + connID: "164" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777061 + reqtimestampmock: 2025-11-10T12:17:41.547634818Z + restimestampmock: 2025-11-10T12:17:41.547740516Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-462 +spec: + metadata: + connID: "164" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 337 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('c4f415bd-b650-453c-852e-592ef525edac', 'inventory_manager', 'inventory_manager@example.in', 'scrypt:32768:8:1$eeD9sNSCOThqspwl$63294588a48330985c68037367fc34f11bd124993f0114c2c34368c1622458a44e070c305752cd1fe243004b45fddc110ef7ac9d47aa7a4abe2fdbc41d027fc4', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777061 + reqtimestampmock: 2025-11-10T12:17:41.597219255Z + restimestampmock: 2025-11-10T12:17:41.597679361Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-463 +spec: + metadata: + connID: "164" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777061 + reqtimestampmock: 2025-11-10T12:17:41.5978113Z + restimestampmock: 2025-11-10T12:17:41.60724618Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-464 +spec: + metadata: + connID: "166" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [251, 106, 11, 56, 43, 166, 56, 28, 197, 52, 155, 78, 98, 51, 128, 211, 185, 77, 219, 143, 125, 196, 50, 107, 0, 204, 153, 56, 22, 193, 149, 216] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 193 + auth_plugin_data: [107, 102, 68, 32, 78, 12, 79, 121, 1, 20, 51, 51, 43, 2, 48, 8, 111, 87, 63, 57, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777062 + reqtimestampmock: 2025-11-10T12:17:42.328554541Z + restimestampmock: 2025-11-10T12:17:42.334803358Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-465 +spec: + metadata: + connID: "166" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777062 + reqtimestampmock: 2025-11-10T12:17:42.334950786Z + restimestampmock: 2025-11-10T12:17:42.335132215Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-466 +spec: + metadata: + connID: "166" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777062 + reqtimestampmock: 2025-11-10T12:17:42.335253744Z + restimestampmock: 2025-11-10T12:17:42.335389063Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-467 +spec: + metadata: + connID: "166" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777062 + reqtimestampmock: 2025-11-10T12:17:42.335549841Z + restimestampmock: 2025-11-10T12:17:42.335647601Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-468 +spec: + metadata: + connID: "166" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 90 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='inventory_manager' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 247 + sequence_id: 6 + values: + - type: 253 + name: id + value: c4f415bd-b650-453c-852e-592ef525edac + unsigned: false + - type: 253 + name: username + value: inventory_manager + unsigned: false + - type: 253 + name: email + value: inventory_manager@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$eeD9sNSCOThqspwl$63294588a48330985c68037367fc34f11bd124993f0114c2c34368c1622458a44e070c305752cd1fe243004b45fddc110ef7ac9d47aa7a4abe2fdbc41d027fc4 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777062 + reqtimestampmock: 2025-11-10T12:17:42.335860209Z + restimestampmock: 2025-11-10T12:17:42.336277806Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-469 +spec: + metadata: + connID: "168" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [71, 152, 30, 115, 139, 227, 42, 162, 238, 54, 30, 225, 162, 89, 46, 98, 226, 72, 177, 247, 191, 245, 246, 97, 57, 47, 133, 22, 65, 18, 106, 174] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 195 + auth_plugin_data: [117, 22, 110, 18, 10, 57, 105, 33, 84, 87, 30, 40, 124, 105, 53, 68, 17, 22, 35, 2, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777063 + reqtimestampmock: 2025-11-10T12:17:43.913345075Z + restimestampmock: 2025-11-10T12:17:43.921204758Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-470 +spec: + metadata: + connID: "168" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777063 + reqtimestampmock: 2025-11-10T12:17:43.921362727Z + restimestampmock: 2025-11-10T12:17:43.921568525Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-471 +spec: + metadata: + connID: "168" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777063 + reqtimestampmock: 2025-11-10T12:17:43.921701554Z + restimestampmock: 2025-11-10T12:17:43.921836193Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-472 +spec: + metadata: + connID: "168" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777063 + reqtimestampmock: 2025-11-10T12:17:43.921943422Z + restimestampmock: 2025-11-10T12:17:43.922048801Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-473 +spec: + metadata: + connID: "168" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='c4f415bd-b650-453c-852e-592ef525edac' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: c4f415bd-b650-453c-852e-592ef525edac + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777063 + reqtimestampmock: 2025-11-10T12:17:43.922233139Z + restimestampmock: 2025-11-10T12:17:43.922611477Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-474 +spec: + metadata: + connID: "168" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='c4f415bd-b650-453c-852e-592ef525edac' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777063 + reqtimestampmock: 2025-11-10T12:17:43.922724455Z + restimestampmock: 2025-11-10T12:17:43.922995862Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-475 +spec: + metadata: + connID: "168" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='c4f415bd-b650-453c-852e-592ef525edac' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777063 + reqtimestampmock: 2025-11-10T12:17:43.923139571Z + restimestampmock: 2025-11-10T12:17:43.923424809Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-476 +spec: + metadata: + connID: "168" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777063 + reqtimestampmock: 2025-11-10T12:17:43.923525728Z + restimestampmock: 2025-11-10T12:17:43.935205508Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-477 +spec: + metadata: + connID: "170" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [87, 197, 167, 132, 95, 104, 255, 148, 22, 196, 78, 78, 106, 81, 224, 120, 192, 78, 33, 98, 166, 118, 36, 125, 94, 160, 235, 254, 101, 229, 76, 86] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 196 + auth_plugin_data: [119, 21, 65, 112, 40, 64, 22, 24, 76, 40, 96, 11, 13, 39, 41, 10, 15, 82, 107, 26, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777066 + reqtimestampmock: 2025-11-10T12:17:46.517424111Z + restimestampmock: 2025-11-10T12:17:46.523778327Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-478 +spec: + metadata: + connID: "170" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777066 + reqtimestampmock: 2025-11-10T12:17:46.523938926Z + restimestampmock: 2025-11-10T12:17:46.524126734Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-479 +spec: + metadata: + connID: "170" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777066 + reqtimestampmock: 2025-11-10T12:17:46.524457522Z + restimestampmock: 2025-11-10T12:17:46.52461972Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-480 +spec: + metadata: + connID: "170" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777066 + reqtimestampmock: 2025-11-10T12:17:46.524860638Z + restimestampmock: 2025-11-10T12:17:46.524974968Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-481 +spec: + metadata: + connID: "170" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('79211abf-4c4b-4339-a13c-ee6106a32e6b', 'stock_checker', 'stock_checker@example.in', 'scrypt:32768:8:1$Vr54OEwCQRkXcHwB$f1caaa80fa7f691eadc2c5b9d98a0a8d1ad50baef32f6d893899f36aaf88d21890624e21b845e9872ffb14a05ebac722715d5c23538442fcdacf64f33a1c34ad', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777066 + reqtimestampmock: 2025-11-10T12:17:46.578585211Z + restimestampmock: 2025-11-10T12:17:46.579090107Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-482 +spec: + metadata: + connID: "170" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777066 + reqtimestampmock: 2025-11-10T12:17:46.579270895Z + restimestampmock: 2025-11-10T12:17:46.588071531Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-483 +spec: + metadata: + connID: "172" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [242, 132, 138, 243, 119, 215, 22, 17, 69, 239, 68, 108, 208, 133, 72, 46, 61, 98, 36, 128, 6, 184, 163, 181, 50, 122, 30, 215, 25, 49, 114, 167] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 197 + auth_plugin_data: [27, 100, 122, 108, 106, 23, 103, 115, 72, 65, 68, 60, 11, 82, 41, 81, 60, 114, 87, 11, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777067 + reqtimestampmock: 2025-11-10T12:17:47.295803Z + restimestampmock: 2025-11-10T12:17:47.301973859Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-484 +spec: + metadata: + connID: "172" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777067 + reqtimestampmock: 2025-11-10T12:17:47.302142586Z + restimestampmock: 2025-11-10T12:17:47.302368585Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-485 +spec: + metadata: + connID: "172" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777067 + reqtimestampmock: 2025-11-10T12:17:47.302476364Z + restimestampmock: 2025-11-10T12:17:47.302689972Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-486 +spec: + metadata: + connID: "172" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777067 + reqtimestampmock: 2025-11-10T12:17:47.302806321Z + restimestampmock: 2025-11-10T12:17:47.30290733Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-487 +spec: + metadata: + connID: "172" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='stock_checker' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: 79211abf-4c4b-4339-a13c-ee6106a32e6b + unsigned: false + - type: 253 + name: username + value: stock_checker + unsigned: false + - type: 253 + name: email + value: stock_checker@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Vr54OEwCQRkXcHwB$f1caaa80fa7f691eadc2c5b9d98a0a8d1ad50baef32f6d893899f36aaf88d21890624e21b845e9872ffb14a05ebac722715d5c23538442fcdacf64f33a1c34ad + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777067 + reqtimestampmock: 2025-11-10T12:17:47.303199908Z + restimestampmock: 2025-11-10T12:17:47.303627703Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-488 +spec: + metadata: + connID: "174" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [129, 49, 46, 80, 44, 207, 161, 225, 212, 250, 180, 192, 197, 120, 164, 189, 154, 135, 172, 63, 64, 188, 210, 119, 148, 228, 149, 38, 169, 47, 130, 169] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 199 + auth_plugin_data: [30, 20, 28, 30, 18, 39, 46, 43, 53, 42, 24, 83, 57, 15, 98, 7, 105, 120, 53, 71, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777068 + reqtimestampmock: 2025-11-10T12:17:48.83845677Z + restimestampmock: 2025-11-10T12:17:48.844611378Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-489 +spec: + metadata: + connID: "174" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777068 + reqtimestampmock: 2025-11-10T12:17:48.844785516Z + restimestampmock: 2025-11-10T12:17:48.844947585Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-490 +spec: + metadata: + connID: "174" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777068 + reqtimestampmock: 2025-11-10T12:17:48.845061714Z + restimestampmock: 2025-11-10T12:17:48.845207743Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-491 +spec: + metadata: + connID: "174" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777068 + reqtimestampmock: 2025-11-10T12:17:48.845306502Z + restimestampmock: 2025-11-10T12:17:48.845403212Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-492 +spec: + metadata: + connID: "174" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='79211abf-4c4b-4339-a13c-ee6106a32e6b' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 79211abf-4c4b-4339-a13c-ee6106a32e6b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777068 + reqtimestampmock: 2025-11-10T12:17:48.845626679Z + restimestampmock: 2025-11-10T12:17:48.845976277Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-493 +spec: + metadata: + connID: "174" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='79211abf-4c4b-4339-a13c-ee6106a32e6b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777068 + reqtimestampmock: 2025-11-10T12:17:48.846175265Z + restimestampmock: 2025-11-10T12:17:48.846390113Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-494 +spec: + metadata: + connID: "174" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='79211abf-4c4b-4339-a13c-ee6106a32e6b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777068 + reqtimestampmock: 2025-11-10T12:17:48.846543482Z + restimestampmock: 2025-11-10T12:17:48.846817089Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-495 +spec: + metadata: + connID: "174" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777068 + reqtimestampmock: 2025-11-10T12:17:48.846923248Z + restimestampmock: 2025-11-10T12:17:48.859161094Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-496 +spec: + metadata: + connID: "176" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [82, 77, 143, 6, 13, 3, 152, 81, 37, 220, 151, 221, 17, 98, 13, 24, 186, 184, 146, 201, 100, 217, 164, 166, 86, 138, 174, 148, 168, 74, 181, 214] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 200 + auth_plugin_data: [81, 95, 46, 57, 122, 113, 106, 35, 126, 53, 88, 23, 100, 97, 20, 37, 22, 124, 44, 51, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.068063411Z + restimestampmock: 2025-11-10T12:17:51.074414387Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-497 +spec: + metadata: + connID: "176" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.074595395Z + restimestampmock: 2025-11-10T12:17:51.074767624Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-498 +spec: + metadata: + connID: "176" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.074916122Z + restimestampmock: 2025-11-10T12:17:51.075074731Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-499 +spec: + metadata: + connID: "176" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.07523405Z + restimestampmock: 2025-11-10T12:17:51.075347319Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-500 +spec: + metadata: + connID: "176" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('bb49cd03-583c-4f27-a003-e2050840ab3b', 'deepa_idem_01', 'deepa_idem_01@example.in', 'scrypt:32768:8:1$h90CiDW4J9IaSW3N$0660f280844f671b574a7b9766fadbe54d014ee97ef1127d3a86bb57099f7962e79d2a48c3a89b5f695f4643e6b1d80378fdc9ef77912f6ed70e100436c1972b', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.127140108Z + restimestampmock: 2025-11-10T12:17:51.127626344Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-501 +spec: + metadata: + connID: "176" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.127797083Z + restimestampmock: 2025-11-10T12:17:51.140007628Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-502 +spec: + metadata: + connID: "178" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [145, 228, 73, 173, 122, 80, 198, 26, 139, 44, 44, 197, 168, 59, 177, 155, 185, 63, 28, 153, 194, 169, 220, 190, 223, 105, 176, 251, 105, 37, 90, 50] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 201 + auth_plugin_data: [27, 86, 89, 124, 8, 117, 105, 60, 121, 39, 25, 111, 108, 2, 35, 3, 52, 83, 55, 113, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.981561114Z + restimestampmock: 2025-11-10T12:17:51.987775281Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-503 +spec: + metadata: + connID: "178" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.98792684Z + restimestampmock: 2025-11-10T12:17:51.988167708Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-504 +spec: + metadata: + connID: "178" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.988259017Z + restimestampmock: 2025-11-10T12:17:51.988465635Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-505 +spec: + metadata: + connID: "178" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.988573933Z + restimestampmock: 2025-11-10T12:17:51.988652234Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-506 +spec: + metadata: + connID: "178" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='deepa_idem_01' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: bb49cd03-583c-4f27-a003-e2050840ab3b + unsigned: false + - type: 253 + name: username + value: deepa_idem_01 + unsigned: false + - type: 253 + name: email + value: deepa_idem_01@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$h90CiDW4J9IaSW3N$0660f280844f671b574a7b9766fadbe54d014ee97ef1127d3a86bb57099f7962e79d2a48c3a89b5f695f4643e6b1d80378fdc9ef77912f6ed70e100436c1972b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777071 + reqtimestampmock: 2025-11-10T12:17:51.988852482Z + restimestampmock: 2025-11-10T12:17:51.989293368Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-507 +spec: + metadata: + connID: "180" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [33, 171, 2, 197, 144, 60, 49, 243, 24, 50, 231, 126, 14, 197, 70, 232, 60, 43, 140, 236, 145, 102, 204, 211, 42, 62, 21, 21, 8, 64, 192, 190] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 202 + auth_plugin_data: [110, 112, 40, 84, 116, 38, 75, 54, 106, 127, 39, 95, 1, 106, 110, 79, 24, 47, 58, 77, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777072 + reqtimestampmock: 2025-11-10T12:17:52.815129308Z + restimestampmock: 2025-11-10T12:17:52.821184266Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-508 +spec: + metadata: + connID: "180" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777072 + reqtimestampmock: 2025-11-10T12:17:52.821327425Z + restimestampmock: 2025-11-10T12:17:52.821476294Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-509 +spec: + metadata: + connID: "180" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777072 + reqtimestampmock: 2025-11-10T12:17:52.821586053Z + restimestampmock: 2025-11-10T12:17:52.821697432Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-510 +spec: + metadata: + connID: "180" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777072 + reqtimestampmock: 2025-11-10T12:17:52.821779801Z + restimestampmock: 2025-11-10T12:17:52.82187054Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-511 +spec: + metadata: + connID: "180" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='bb49cd03-583c-4f27-a003-e2050840ab3b' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: bb49cd03-583c-4f27-a003-e2050840ab3b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777072 + reqtimestampmock: 2025-11-10T12:17:52.822023059Z + restimestampmock: 2025-11-10T12:17:52.822410756Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-512 +spec: + metadata: + connID: "180" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 267 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('8ce1b5f8-3b4d-427f-8bf5-e9703ff9df27','bb49cd03-583c-4f27-a003-e2050840ab3b','22 Connaught Place',NULL,'Delhi','Delhi','110001','IN','+919812345678',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777072 + reqtimestampmock: 2025-11-10T12:17:52.822532225Z + restimestampmock: 2025-11-10T12:17:52.822959881Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-513 +spec: + metadata: + connID: "180" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='bb49cd03-583c-4f27-a003-e2050840ab3b' AND id<>'8ce1b5f8-3b4d-427f-8bf5-e9703ff9df27' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777072 + reqtimestampmock: 2025-11-10T12:17:52.823063991Z + restimestampmock: 2025-11-10T12:17:52.823360407Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-514 +spec: + metadata: + connID: "180" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777072 + reqtimestampmock: 2025-11-10T12:17:52.823449137Z + restimestampmock: 2025-11-10T12:17:52.835016079Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-515 +spec: + metadata: + connID: "182" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [1, 159, 239, 43, 48, 174, 150, 152, 255, 19, 111, 58, 232, 44, 247, 96, 240, 51, 217, 107, 207, 131, 153, 230, 153, 5, 78, 236, 81, 165, 163, 99] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 204 + auth_plugin_data: [38, 91, 92, 101, 82, 47, 28, 102, 122, 34, 10, 61, 21, 66, 87, 30, 65, 78, 110, 73, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777074 + reqtimestampmock: 2025-11-10T12:17:54.444387438Z + restimestampmock: 2025-11-10T12:17:54.450442786Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-516 +spec: + metadata: + connID: "182" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777074 + reqtimestampmock: 2025-11-10T12:17:54.450625455Z + restimestampmock: 2025-11-10T12:17:54.450794943Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-517 +spec: + metadata: + connID: "182" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777074 + reqtimestampmock: 2025-11-10T12:17:54.450914172Z + restimestampmock: 2025-11-10T12:17:54.451040911Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-518 +spec: + metadata: + connID: "182" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777074 + reqtimestampmock: 2025-11-10T12:17:54.45119707Z + restimestampmock: 2025-11-10T12:17:54.451342349Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-519 +spec: + metadata: + connID: "182" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='bb49cd03-583c-4f27-a003-e2050840ab3b' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: bb49cd03-583c-4f27-a003-e2050840ab3b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777074 + reqtimestampmock: 2025-11-10T12:17:54.451475688Z + restimestampmock: 2025-11-10T12:17:54.451835175Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-520 +spec: + metadata: + connID: "182" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='bb49cd03-583c-4f27-a003-e2050840ab3b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777074 + reqtimestampmock: 2025-11-10T12:17:54.451940814Z + restimestampmock: 2025-11-10T12:17:54.45236052Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-521 +spec: + metadata: + connID: "182" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='bb49cd03-583c-4f27-a003-e2050840ab3b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777074 + reqtimestampmock: 2025-11-10T12:17:54.452457319Z + restimestampmock: 2025-11-10T12:17:54.453054474Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-522 +spec: + metadata: + connID: "182" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777074 + reqtimestampmock: 2025-11-10T12:17:54.453230883Z + restimestampmock: 2025-11-10T12:17:54.466019004Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-523 +spec: + metadata: + connID: "184" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [207, 188, 17, 138, 66, 65, 23, 239, 24, 115, 250, 182, 38, 196, 19, 227, 16, 159, 234, 202, 89, 168, 247, 10, 113, 201, 119, 170, 156, 215, 166, 123] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 205 + auth_plugin_data: [40, 67, 32, 123, 43, 2, 5, 34, 104, 28, 81, 57, 22, 43, 61, 17, 102, 51, 90, 87, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777077 + reqtimestampmock: 2025-11-10T12:17:57.174667611Z + restimestampmock: 2025-11-10T12:17:57.18076736Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-524 +spec: + metadata: + connID: "184" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777077 + reqtimestampmock: 2025-11-10T12:17:57.180922499Z + restimestampmock: 2025-11-10T12:17:57.181071618Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-525 +spec: + metadata: + connID: "184" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777077 + reqtimestampmock: 2025-11-10T12:17:57.181190657Z + restimestampmock: 2025-11-10T12:17:57.181324615Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-526 +spec: + metadata: + connID: "184" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777077 + reqtimestampmock: 2025-11-10T12:17:57.181417525Z + restimestampmock: 2025-11-10T12:17:57.181524913Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-527 +spec: + metadata: + connID: "184" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('9a082290-3fed-4a09-ac50-d25e81f0222c', 'cancel_user_01', 'cancel_user_01@example.in', 'scrypt:32768:8:1$OakZnLMbGUoK08aK$f2640effb318a212f1bed6637549be27713e6f36170a68d06a46a81fed16fc929b5abaf2c8649f8b52e5bbfa961fb036445c405b1e801b579d289549ef2d9052', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777077 + reqtimestampmock: 2025-11-10T12:17:57.230545816Z + restimestampmock: 2025-11-10T12:17:57.230947594Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-528 +spec: + metadata: + connID: "184" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777077 + reqtimestampmock: 2025-11-10T12:17:57.231057203Z + restimestampmock: 2025-11-10T12:17:57.244056573Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-529 +spec: + metadata: + connID: "186" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [248, 67, 110, 44, 117, 15, 136, 115, 245, 185, 76, 249, 250, 132, 138, 57, 131, 123, 55, 82, 106, 236, 38, 178, 233, 218, 103, 9, 172, 95, 138, 117] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 206 + auth_plugin_data: [71, 119, 126, 56, 98, 125, 75, 3, 28, 102, 34, 24, 67, 22, 126, 33, 60, 101, 76, 17, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.100895511Z + restimestampmock: 2025-11-10T12:17:58.108595695Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-530 +spec: + metadata: + connID: "186" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.108774724Z + restimestampmock: 2025-11-10T12:17:58.108951273Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-531 +spec: + metadata: + connID: "186" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.109089251Z + restimestampmock: 2025-11-10T12:17:58.10927566Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-532 +spec: + metadata: + connID: "186" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.109415639Z + restimestampmock: 2025-11-10T12:17:58.109502838Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-533 +spec: + metadata: + connID: "186" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='cancel_user_01' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9a082290-3fed-4a09-ac50-d25e81f0222c + unsigned: false + - type: 253 + name: username + value: cancel_user_01 + unsigned: false + - type: 253 + name: email + value: cancel_user_01@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$OakZnLMbGUoK08aK$f2640effb318a212f1bed6637549be27713e6f36170a68d06a46a81fed16fc929b5abaf2c8649f8b52e5bbfa961fb036445c405b1e801b579d289549ef2d9052 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.109650537Z + restimestampmock: 2025-11-10T12:17:58.110119182Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-534 +spec: + metadata: + connID: "188" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [29, 98, 195, 39, 161, 88, 235, 91, 224, 105, 68, 33, 112, 244, 185, 36, 177, 114, 153, 101, 146, 194, 117, 126, 138, 227, 97, 248, 105, 74, 127, 224] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 208 + auth_plugin_data: [81, 12, 94, 62, 17, 98, 98, 93, 11, 21, 114, 13, 65, 78, 57, 118, 22, 69, 84, 10, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.963279444Z + restimestampmock: 2025-11-10T12:17:58.969412121Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-535 +spec: + metadata: + connID: "188" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.96960182Z + restimestampmock: 2025-11-10T12:17:58.969764529Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-536 +spec: + metadata: + connID: "188" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.969923738Z + restimestampmock: 2025-11-10T12:17:58.970070706Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-537 +spec: + metadata: + connID: "188" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.970173906Z + restimestampmock: 2025-11-10T12:17:58.970390753Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-538 +spec: + metadata: + connID: "188" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='9a082290-3fed-4a09-ac50-d25e81f0222c' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 9a082290-3fed-4a09-ac50-d25e81f0222c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.970588321Z + restimestampmock: 2025-11-10T12:17:58.970997538Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-539 +spec: + metadata: + connID: "188" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 264 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('942d93ed-78d4-4e03-9d60-4931b6ab2e88','9a082290-3fed-4a09-ac50-d25e81f0222c','33 FC Road',NULL,'Pune','Maharashtra','411004','IN','+919876501234',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.971137767Z + restimestampmock: 2025-11-10T12:17:58.971500154Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-540 +spec: + metadata: + connID: "188" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='9a082290-3fed-4a09-ac50-d25e81f0222c' AND id<>'942d93ed-78d4-4e03-9d60-4931b6ab2e88' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.971693552Z + restimestampmock: 2025-11-10T12:17:58.97199509Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-541 +spec: + metadata: + connID: "188" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777078 + reqtimestampmock: 2025-11-10T12:17:58.972141679Z + restimestampmock: 2025-11-10T12:17:58.984016427Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-542 +spec: + metadata: + connID: "190" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [74, 205, 84, 248, 173, 83, 188, 160, 147, 157, 185, 228, 3, 153, 70, 171, 73, 24, 157, 213, 160, 34, 83, 205, 227, 187, 113, 59, 97, 16, 240, 73] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 209 + auth_plugin_data: [126, 81, 39, 88, 42, 86, 60, 96, 81, 5, 91, 25, 67, 89, 112, 86, 10, 62, 43, 3, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777080 + reqtimestampmock: 2025-11-10T12:18:00.764026495Z + restimestampmock: 2025-11-10T12:18:00.770999046Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-543 +spec: + metadata: + connID: "190" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777080 + reqtimestampmock: 2025-11-10T12:18:00.771213004Z + restimestampmock: 2025-11-10T12:18:00.771418872Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-544 +spec: + metadata: + connID: "190" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777080 + reqtimestampmock: 2025-11-10T12:18:00.771537362Z + restimestampmock: 2025-11-10T12:18:00.77169429Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-545 +spec: + metadata: + connID: "190" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777080 + reqtimestampmock: 2025-11-10T12:18:00.771829759Z + restimestampmock: 2025-11-10T12:18:00.771929268Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-546 +spec: + metadata: + connID: "190" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='9a082290-3fed-4a09-ac50-d25e81f0222c' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 9a082290-3fed-4a09-ac50-d25e81f0222c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777080 + reqtimestampmock: 2025-11-10T12:18:00.772075426Z + restimestampmock: 2025-11-10T12:18:00.772481013Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-547 +spec: + metadata: + connID: "190" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='9a082290-3fed-4a09-ac50-d25e81f0222c' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777080 + reqtimestampmock: 2025-11-10T12:18:00.772673182Z + restimestampmock: 2025-11-10T12:18:00.773136047Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-548 +spec: + metadata: + connID: "190" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='9a082290-3fed-4a09-ac50-d25e81f0222c' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777080 + reqtimestampmock: 2025-11-10T12:18:00.773295137Z + restimestampmock: 2025-11-10T12:18:00.773582384Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-549 +spec: + metadata: + connID: "190" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777080 + reqtimestampmock: 2025-11-10T12:18:00.773679384Z + restimestampmock: 2025-11-10T12:18:00.789022982Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-550 +spec: + metadata: + connID: "192" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [64, 146, 126, 146, 253, 93, 92, 77, 155, 150, 208, 63, 36, 245, 172, 110, 39, 129, 133, 188, 53, 156, 129, 68, 101, 6, 169, 18, 11, 32, 17, 164] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 211 + auth_plugin_data: [27, 44, 57, 97, 45, 17, 61, 26, 78, 74, 37, 74, 52, 32, 76, 2, 117, 115, 32, 123, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777083 + reqtimestampmock: 2025-11-10T12:18:03.419297941Z + restimestampmock: 2025-11-10T12:18:03.425779456Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-551 +spec: + metadata: + connID: "192" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777083 + reqtimestampmock: 2025-11-10T12:18:03.425945143Z + restimestampmock: 2025-11-10T12:18:03.426150382Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-552 +spec: + metadata: + connID: "192" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777083 + reqtimestampmock: 2025-11-10T12:18:03.426276361Z + restimestampmock: 2025-11-10T12:18:03.426448789Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-553 +spec: + metadata: + connID: "192" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777083 + reqtimestampmock: 2025-11-10T12:18:03.426539979Z + restimestampmock: 2025-11-10T12:18:03.426645328Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-554 +spec: + metadata: + connID: "192" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('be716f2f-f071-42fe-8a85-a92c8a1c1b68', 'paid_cancel_fail', 'paid_cancel_fail@example.in', 'scrypt:32768:8:1$Vzrxnf3k2xQRvIsB$d5ce2bbde61fbe6ef845d3eb39c32f5abdf9a68145fdb1030619316e3ce595cd4dad7abcb4232cac53f333717b943c557df55d57cf8141415511f9ec426b64c0', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777083 + reqtimestampmock: 2025-11-10T12:18:03.477187979Z + restimestampmock: 2025-11-10T12:18:03.477687575Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-555 +spec: + metadata: + connID: "192" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777083 + reqtimestampmock: 2025-11-10T12:18:03.477820503Z + restimestampmock: 2025-11-10T12:18:03.486949286Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-556 +spec: + metadata: + connID: "194" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [210, 220, 26, 177, 180, 202, 225, 145, 78, 160, 205, 129, 146, 17, 30, 254, 166, 0, 107, 211, 1, 226, 71, 97, 85, 68, 193, 81, 31, 87, 164, 16] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 212 + auth_plugin_data: [16, 99, 52, 115, 32, 81, 123, 123, 63, 18, 71, 113, 5, 29, 57, 12, 92, 32, 82, 89, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777084 + reqtimestampmock: 2025-11-10T12:18:04.634743468Z + restimestampmock: 2025-11-10T12:18:04.640836326Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-557 +spec: + metadata: + connID: "194" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777084 + reqtimestampmock: 2025-11-10T12:18:04.641030494Z + restimestampmock: 2025-11-10T12:18:04.641216583Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-558 +spec: + metadata: + connID: "194" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777084 + reqtimestampmock: 2025-11-10T12:18:04.641332741Z + restimestampmock: 2025-11-10T12:18:04.64147854Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-559 +spec: + metadata: + connID: "194" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777084 + reqtimestampmock: 2025-11-10T12:18:04.641585099Z + restimestampmock: 2025-11-10T12:18:04.641687319Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-560 +spec: + metadata: + connID: "194" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='paid_cancel_fail' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: be716f2f-f071-42fe-8a85-a92c8a1c1b68 + unsigned: false + - type: 253 + name: username + value: paid_cancel_fail + unsigned: false + - type: 253 + name: email + value: paid_cancel_fail@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Vzrxnf3k2xQRvIsB$d5ce2bbde61fbe6ef845d3eb39c32f5abdf9a68145fdb1030619316e3ce595cd4dad7abcb4232cac53f333717b943c557df55d57cf8141415511f9ec426b64c0 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777084 + reqtimestampmock: 2025-11-10T12:18:04.641838648Z + restimestampmock: 2025-11-10T12:18:04.642475883Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-561 +spec: + metadata: + connID: "196" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [6, 82, 112, 192, 84, 191, 74, 94, 160, 7, 236, 189, 50, 191, 65, 148, 176, 229, 151, 228, 200, 177, 90, 161, 190, 64, 144, 177, 29, 114, 40, 36] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 213 + auth_plugin_data: [116, 75, 69, 53, 112, 119, 14, 15, 110, 63, 54, 58, 94, 111, 127, 7, 90, 61, 52, 120, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777085 + reqtimestampmock: 2025-11-10T12:18:05.438130905Z + restimestampmock: 2025-11-10T12:18:05.444354612Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-562 +spec: + metadata: + connID: "196" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777085 + reqtimestampmock: 2025-11-10T12:18:05.444500211Z + restimestampmock: 2025-11-10T12:18:05.444723389Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-563 +spec: + metadata: + connID: "196" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777085 + reqtimestampmock: 2025-11-10T12:18:05.444841558Z + restimestampmock: 2025-11-10T12:18:05.444996427Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-564 +spec: + metadata: + connID: "196" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777085 + reqtimestampmock: 2025-11-10T12:18:05.445099096Z + restimestampmock: 2025-11-10T12:18:05.445232114Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-565 +spec: + metadata: + connID: "196" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='be716f2f-f071-42fe-8a85-a92c8a1c1b68' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: be716f2f-f071-42fe-8a85-a92c8a1c1b68 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777085 + reqtimestampmock: 2025-11-10T12:18:05.445382713Z + restimestampmock: 2025-11-10T12:18:05.44577331Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-566 +spec: + metadata: + connID: "196" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 271 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('7aac94f7-3f51-4ad1-b262-d2acdd7429f2','be716f2f-f071-42fe-8a85-a92c8a1c1b68','55 Park Street',NULL,'Kolkata','West Bengal','700016','IN','+919000011111',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777085 + reqtimestampmock: 2025-11-10T12:18:05.44588617Z + restimestampmock: 2025-11-10T12:18:05.446196026Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-567 +spec: + metadata: + connID: "196" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='be716f2f-f071-42fe-8a85-a92c8a1c1b68' AND id<>'7aac94f7-3f51-4ad1-b262-d2acdd7429f2' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777085 + reqtimestampmock: 2025-11-10T12:18:05.446355505Z + restimestampmock: 2025-11-10T12:18:05.446588494Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-568 +spec: + metadata: + connID: "196" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777085 + reqtimestampmock: 2025-11-10T12:18:05.446763392Z + restimestampmock: 2025-11-10T12:18:05.459826591Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-569 +spec: + metadata: + connID: "198" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [108, 105, 13, 24, 62, 53, 195, 132, 157, 127, 107, 49, 224, 174, 204, 26, 10, 59, 181, 119, 10, 213, 103, 112, 253, 103, 180, 62, 125, 83, 209, 75] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 214 + auth_plugin_data: [25, 113, 59, 61, 43, 72, 87, 86, 21, 5, 117, 117, 56, 41, 120, 105, 8, 126, 118, 27, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777087 + reqtimestampmock: 2025-11-10T12:18:06.996978349Z + restimestampmock: 2025-11-10T12:18:07.003324875Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-570 +spec: + metadata: + connID: "198" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777087 + reqtimestampmock: 2025-11-10T12:18:07.003487334Z + restimestampmock: 2025-11-10T12:18:07.003659602Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-571 +spec: + metadata: + connID: "198" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777087 + reqtimestampmock: 2025-11-10T12:18:07.003752081Z + restimestampmock: 2025-11-10T12:18:07.003894981Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-572 +spec: + metadata: + connID: "198" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777087 + reqtimestampmock: 2025-11-10T12:18:07.00401131Z + restimestampmock: 2025-11-10T12:18:07.004164099Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-573 +spec: + metadata: + connID: "198" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='be716f2f-f071-42fe-8a85-a92c8a1c1b68' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: be716f2f-f071-42fe-8a85-a92c8a1c1b68 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777087 + reqtimestampmock: 2025-11-10T12:18:07.004380267Z + restimestampmock: 2025-11-10T12:18:07.004782144Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-574 +spec: + metadata: + connID: "198" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='be716f2f-f071-42fe-8a85-a92c8a1c1b68' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777087 + reqtimestampmock: 2025-11-10T12:18:07.004891352Z + restimestampmock: 2025-11-10T12:18:07.005285949Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-575 +spec: + metadata: + connID: "198" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='be716f2f-f071-42fe-8a85-a92c8a1c1b68' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777087 + reqtimestampmock: 2025-11-10T12:18:07.005496647Z + restimestampmock: 2025-11-10T12:18:07.005770344Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-576 +spec: + metadata: + connID: "198" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777087 + reqtimestampmock: 2025-11-10T12:18:07.005887383Z + restimestampmock: 2025-11-10T12:18:07.015936148Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-577 +spec: + metadata: + connID: "200" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [44, 48, 59, 133, 82, 56, 174, 38, 251, 68, 118, 132, 181, 173, 109, 249, 246, 103, 128, 213, 107, 166, 26, 160, 149, 60, 103, 235, 3, 197, 5, 149] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 216 + auth_plugin_data: [5, 78, 59, 32, 106, 116, 21, 95, 80, 37, 66, 57, 69, 8, 27, 14, 97, 100, 9, 80, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.119162384Z + restimestampmock: 2025-11-10T12:18:10.125665579Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-578 +spec: + metadata: + connID: "200" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.125820617Z + restimestampmock: 2025-11-10T12:18:10.125995596Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-579 +spec: + metadata: + connID: "200" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.126176214Z + restimestampmock: 2025-11-10T12:18:10.126399863Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-580 +spec: + metadata: + connID: "200" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.126529432Z + restimestampmock: 2025-11-10T12:18:10.126640031Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-581 +spec: + metadata: + connID: "200" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 333 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('4dec0e0b-8950-49ab-aefb-acea23952e05', 'list_order_user', 'list_order_user@example.in', 'scrypt:32768:8:1$SbUTzWeteoZ3uNNn$be932e7db95f957f8688558c52a92a44a6d6c70683c6a425fe13f038689029446298c4aab6f08b042005e7b58ecede625e248404a922826e10c469af10e2676a', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.176288108Z + restimestampmock: 2025-11-10T12:18:10.176698776Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-582 +spec: + metadata: + connID: "200" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.176847384Z + restimestampmock: 2025-11-10T12:18:10.185986717Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-583 +spec: + metadata: + connID: "202" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [212, 24, 30, 119, 213, 6, 204, 43, 211, 108, 175, 161, 77, 42, 229, 214, 138, 179, 218, 118, 130, 91, 18, 31, 238, 27, 32, 152, 71, 46, 94, 21] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 217 + auth_plugin_data: [65, 13, 115, 41, 16, 37, 104, 58, 112, 37, 89, 76, 107, 56, 95, 19, 72, 31, 18, 50, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.95851268Z + restimestampmock: 2025-11-10T12:18:10.965425831Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-584 +spec: + metadata: + connID: "202" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.965626609Z + restimestampmock: 2025-11-10T12:18:10.965808807Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-585 +spec: + metadata: + connID: "202" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.965932096Z + restimestampmock: 2025-11-10T12:18:10.966104935Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-586 +spec: + metadata: + connID: "202" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.966229344Z + restimestampmock: 2025-11-10T12:18:10.966342723Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-587 +spec: + metadata: + connID: "202" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 88 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='list_order_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 243 + sequence_id: 6 + values: + - type: 253 + name: id + value: 4dec0e0b-8950-49ab-aefb-acea23952e05 + unsigned: false + - type: 253 + name: username + value: list_order_user + unsigned: false + - type: 253 + name: email + value: list_order_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$SbUTzWeteoZ3uNNn$be932e7db95f957f8688558c52a92a44a6d6c70683c6a425fe13f038689029446298c4aab6f08b042005e7b58ecede625e248404a922826e10c469af10e2676a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777090 + reqtimestampmock: 2025-11-10T12:18:10.966500422Z + restimestampmock: 2025-11-10T12:18:10.966987138Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-588 +spec: + metadata: + connID: "204" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [55, 168, 113, 23, 11, 142, 224, 77, 186, 167, 173, 51, 6, 171, 245, 80, 90, 22, 239, 154, 92, 231, 37, 194, 166, 98, 0, 218, 91, 114, 9, 240] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 218 + auth_plugin_data: [34, 108, 117, 25, 86, 19, 51, 19, 84, 106, 66, 34, 8, 37, 103, 12, 120, 23, 91, 81, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777091 + reqtimestampmock: 2025-11-10T12:18:11.718584898Z + restimestampmock: 2025-11-10T12:18:11.724820325Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-589 +spec: + metadata: + connID: "204" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777091 + reqtimestampmock: 2025-11-10T12:18:11.724966864Z + restimestampmock: 2025-11-10T12:18:11.725140123Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-590 +spec: + metadata: + connID: "204" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777091 + reqtimestampmock: 2025-11-10T12:18:11.725298271Z + restimestampmock: 2025-11-10T12:18:11.725436959Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-591 +spec: + metadata: + connID: "204" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777091 + reqtimestampmock: 2025-11-10T12:18:11.725544499Z + restimestampmock: 2025-11-10T12:18:11.725632758Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-592 +spec: + metadata: + connID: "204" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='4dec0e0b-8950-49ab-aefb-acea23952e05' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 4dec0e0b-8950-49ab-aefb-acea23952e05 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777091 + reqtimestampmock: 2025-11-10T12:18:11.725841387Z + restimestampmock: 2025-11-10T12:18:11.726233304Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-593 +spec: + metadata: + connID: "204" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 272 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('149209af-8c0e-440f-99fb-704a2209cf84','4dec0e0b-8950-49ab-aefb-acea23952e05','7 Jubilee Hills',NULL,'Hyderabad','Telangana','500033','IN','+919123456789',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777091 + reqtimestampmock: 2025-11-10T12:18:11.726415262Z + restimestampmock: 2025-11-10T12:18:11.72667446Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-594 +spec: + metadata: + connID: "204" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='4dec0e0b-8950-49ab-aefb-acea23952e05' AND id<>'149209af-8c0e-440f-99fb-704a2209cf84' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777091 + reqtimestampmock: 2025-11-10T12:18:11.726819099Z + restimestampmock: 2025-11-10T12:18:11.727125966Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-595 +spec: + metadata: + connID: "204" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777091 + reqtimestampmock: 2025-11-10T12:18:11.727241585Z + restimestampmock: 2025-11-10T12:18:11.739847658Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-596 +spec: + metadata: + connID: "206" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [79, 113, 70, 35, 27, 161, 184, 193, 165, 198, 100, 212, 115, 200, 164, 112, 119, 248, 225, 228, 67, 64, 74, 11, 7, 41, 150, 209, 236, 103, 69, 156] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 220 + auth_plugin_data: [37, 82, 122, 60, 69, 86, 23, 43, 87, 45, 45, 64, 53, 105, 38, 10, 76, 43, 103, 116, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777094 + reqtimestampmock: 2025-11-10T12:18:14.005307652Z + restimestampmock: 2025-11-10T12:18:14.011976016Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-597 +spec: + metadata: + connID: "206" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777094 + reqtimestampmock: 2025-11-10T12:18:14.012181464Z + restimestampmock: 2025-11-10T12:18:14.012348112Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-598 +spec: + metadata: + connID: "206" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777094 + reqtimestampmock: 2025-11-10T12:18:14.012511581Z + restimestampmock: 2025-11-10T12:18:14.01265361Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-599 +spec: + metadata: + connID: "206" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777094 + reqtimestampmock: 2025-11-10T12:18:14.012750449Z + restimestampmock: 2025-11-10T12:18:14.012850539Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-600 +spec: + metadata: + connID: "206" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='4dec0e0b-8950-49ab-aefb-acea23952e05' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 4dec0e0b-8950-49ab-aefb-acea23952e05 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777094 + reqtimestampmock: 2025-11-10T12:18:14.013037257Z + restimestampmock: 2025-11-10T12:18:14.013396623Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-601 +spec: + metadata: + connID: "206" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='4dec0e0b-8950-49ab-aefb-acea23952e05' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777094 + reqtimestampmock: 2025-11-10T12:18:14.013534442Z + restimestampmock: 2025-11-10T12:18:14.013888579Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-602 +spec: + metadata: + connID: "206" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='4dec0e0b-8950-49ab-aefb-acea23952e05' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777094 + reqtimestampmock: 2025-11-10T12:18:14.014049588Z + restimestampmock: 2025-11-10T12:18:14.014643233Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-603 +spec: + metadata: + connID: "206" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777094 + reqtimestampmock: 2025-11-10T12:18:14.014748352Z + restimestampmock: 2025-11-10T12:18:14.023892115Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-604 +spec: + metadata: + connID: "208" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [249, 41, 107, 107, 241, 79, 241, 31, 173, 0, 116, 238, 25, 2, 184, 149, 189, 191, 56, 237, 155, 210, 146, 83, 122, 189, 214, 247, 22, 29, 11, 2] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 221 + auth_plugin_data: [121, 38, 95, 88, 14, 105, 21, 86, 11, 55, 1, 13, 120, 9, 29, 126, 57, 61, 125, 70, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777097 + reqtimestampmock: 2025-11-10T12:18:17.46026213Z + restimestampmock: 2025-11-10T12:18:17.467046851Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-605 +spec: + metadata: + connID: "208" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777097 + reqtimestampmock: 2025-11-10T12:18:17.4672169Z + restimestampmock: 2025-11-10T12:18:17.467477598Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-606 +spec: + metadata: + connID: "208" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777097 + reqtimestampmock: 2025-11-10T12:18:17.467600107Z + restimestampmock: 2025-11-10T12:18:17.467766046Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-607 +spec: + metadata: + connID: "208" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777097 + reqtimestampmock: 2025-11-10T12:18:17.467853455Z + restimestampmock: 2025-11-10T12:18:17.467958624Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-608 +spec: + metadata: + connID: "208" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 337 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('2a16540a-f916-4b11-b715-7636c0946697', 'default_addr_user', 'default_addr_user@example.in', 'scrypt:32768:8:1$KsxgWPhrbSWrUTJE$f9435c1ad531231d87ff05271bfddeea09ec1cf012ebe20951cb3c838e945e36ce204f593b8f65156fdd6acbb7142a611cad83040271df6fc81af01b9b5c4194', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777097 + reqtimestampmock: 2025-11-10T12:18:17.517747882Z + restimestampmock: 2025-11-10T12:18:17.518241697Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-609 +spec: + metadata: + connID: "208" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777097 + reqtimestampmock: 2025-11-10T12:18:17.518460885Z + restimestampmock: 2025-11-10T12:18:17.531867032Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-610 +spec: + metadata: + connID: "210" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [76, 5, 218, 143, 151, 61, 28, 20, 80, 152, 219, 50, 195, 31, 39, 0, 165, 99, 217, 11, 25, 140, 39, 75, 197, 88, 206, 54, 79, 62, 130, 30] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 222 + auth_plugin_data: [84, 89, 59, 10, 87, 58, 115, 31, 11, 100, 91, 56, 89, 89, 120, 37, 16, 85, 77, 127, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777098 + reqtimestampmock: 2025-11-10T12:18:18.205133771Z + restimestampmock: 2025-11-10T12:18:18.211600396Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-611 +spec: + metadata: + connID: "210" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777098 + reqtimestampmock: 2025-11-10T12:18:18.211746315Z + restimestampmock: 2025-11-10T12:18:18.211913334Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-612 +spec: + metadata: + connID: "210" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777098 + reqtimestampmock: 2025-11-10T12:18:18.212035103Z + restimestampmock: 2025-11-10T12:18:18.21236223Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-613 +spec: + metadata: + connID: "210" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777098 + reqtimestampmock: 2025-11-10T12:18:18.21239598Z + restimestampmock: 2025-11-10T12:18:18.212506208Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-614 +spec: + metadata: + connID: "210" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 90 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='default_addr_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 247 + sequence_id: 6 + values: + - type: 253 + name: id + value: 2a16540a-f916-4b11-b715-7636c0946697 + unsigned: false + - type: 253 + name: username + value: default_addr_user + unsigned: false + - type: 253 + name: email + value: default_addr_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$KsxgWPhrbSWrUTJE$f9435c1ad531231d87ff05271bfddeea09ec1cf012ebe20951cb3c838e945e36ce204f593b8f65156fdd6acbb7142a611cad83040271df6fc81af01b9b5c4194 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777098 + reqtimestampmock: 2025-11-10T12:18:18.212687227Z + restimestampmock: 2025-11-10T12:18:18.213186103Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-615 +spec: + metadata: + connID: "212" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [99, 18, 26, 225, 22, 2, 95, 153, 137, 27, 13, 133, 11, 249, 107, 101, 178, 133, 250, 198, 163, 158, 116, 230, 149, 240, 224, 18, 91, 122, 164, 130] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 224 + auth_plugin_data: [51, 29, 51, 11, 115, 112, 2, 70, 85, 41, 1, 28, 93, 30, 102, 25, 25, 37, 57, 18, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777099 + reqtimestampmock: 2025-11-10T12:18:19.033631334Z + restimestampmock: 2025-11-10T12:18:19.040418826Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-616 +spec: + metadata: + connID: "212" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777099 + reqtimestampmock: 2025-11-10T12:18:19.040608304Z + restimestampmock: 2025-11-10T12:18:19.040798103Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-617 +spec: + metadata: + connID: "212" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777099 + reqtimestampmock: 2025-11-10T12:18:19.040910952Z + restimestampmock: 2025-11-10T12:18:19.04108222Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-618 +spec: + metadata: + connID: "212" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777099 + reqtimestampmock: 2025-11-10T12:18:19.041202199Z + restimestampmock: 2025-11-10T12:18:19.041301389Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-619 +spec: + metadata: + connID: "212" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='2a16540a-f916-4b11-b715-7636c0946697' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 2a16540a-f916-4b11-b715-7636c0946697 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777099 + reqtimestampmock: 2025-11-10T12:18:19.041470067Z + restimestampmock: 2025-11-10T12:18:19.041824544Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-620 +spec: + metadata: + connID: "212" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 268 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('d54f10f7-0200-468b-9b9f-e90a9c484959','2a16540a-f916-4b11-b715-7636c0946697','9 Anna Salai',NULL,'Chennai','Tamil Nadu','600002','IN','+919998887776',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777099 + reqtimestampmock: 2025-11-10T12:18:19.042047382Z + restimestampmock: 2025-11-10T12:18:19.04240539Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-621 +spec: + metadata: + connID: "212" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='2a16540a-f916-4b11-b715-7636c0946697' AND id<>'d54f10f7-0200-468b-9b9f-e90a9c484959' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777099 + reqtimestampmock: 2025-11-10T12:18:19.042564977Z + restimestampmock: 2025-11-10T12:18:19.042890605Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-622 +spec: + metadata: + connID: "212" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777099 + reqtimestampmock: 2025-11-10T12:18:19.043006094Z + restimestampmock: 2025-11-10T12:18:19.054778624Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-623 +spec: + metadata: + connID: "214" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [198, 221, 16, 71, 131, 92, 223, 129, 56, 2, 43, 14, 238, 34, 90, 164, 142, 151, 182, 158, 163, 163, 5, 34, 6, 72, 106, 115, 36, 131, 107, 238] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 225 + auth_plugin_data: [68, 102, 29, 49, 70, 43, 118, 21, 67, 49, 8, 112, 97, 116, 107, 24, 98, 32, 116, 67, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777100 + reqtimestampmock: 2025-11-10T12:18:20.616073023Z + restimestampmock: 2025-11-10T12:18:20.62227698Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-624 +spec: + metadata: + connID: "214" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777100 + reqtimestampmock: 2025-11-10T12:18:20.622415049Z + restimestampmock: 2025-11-10T12:18:20.622599668Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-625 +spec: + metadata: + connID: "214" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777100 + reqtimestampmock: 2025-11-10T12:18:20.622685937Z + restimestampmock: 2025-11-10T12:18:20.622892815Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-626 +spec: + metadata: + connID: "214" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777100 + reqtimestampmock: 2025-11-10T12:18:20.623000444Z + restimestampmock: 2025-11-10T12:18:20.623199823Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-627 +spec: + metadata: + connID: "214" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='2a16540a-f916-4b11-b715-7636c0946697' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 2a16540a-f916-4b11-b715-7636c0946697 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777100 + reqtimestampmock: 2025-11-10T12:18:20.623360481Z + restimestampmock: 2025-11-10T12:18:20.623663028Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-628 +spec: + metadata: + connID: "214" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='2a16540a-f916-4b11-b715-7636c0946697' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777100 + reqtimestampmock: 2025-11-10T12:18:20.623798258Z + restimestampmock: 2025-11-10T12:18:20.624139194Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-629 +spec: + metadata: + connID: "214" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='2a16540a-f916-4b11-b715-7636c0946697' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777100 + reqtimestampmock: 2025-11-10T12:18:20.624288764Z + restimestampmock: 2025-11-10T12:18:20.624548012Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-630 +spec: + metadata: + connID: "214" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777100 + reqtimestampmock: 2025-11-10T12:18:20.62464583Z + restimestampmock: 2025-11-10T12:18:20.636693688Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-631 +spec: + metadata: + connID: "216" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [194, 1, 188, 227, 222, 106, 46, 25, 238, 134, 239, 100, 145, 182, 99, 2, 58, 227, 229, 8, 166, 159, 29, 28, 166, 197, 229, 76, 61, 97, 131, 216] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 226 + auth_plugin_data: [1, 44, 72, 64, 76, 57, 118, 45, 127, 57, 54, 60, 97, 55, 77, 97, 15, 125, 34, 38, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.006030485Z + restimestampmock: 2025-11-10T12:18:23.012225493Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-632 +spec: + metadata: + connID: "216" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.012370403Z + restimestampmock: 2025-11-10T12:18:23.01261737Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-633 +spec: + metadata: + connID: "216" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.012802418Z + restimestampmock: 2025-11-10T12:18:23.012975757Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-634 +spec: + metadata: + connID: "216" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.013118856Z + restimestampmock: 2025-11-10T12:18:23.013220475Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-635 +spec: + metadata: + connID: "216" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 327 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('3afeaa74-af93-4845-a994-2421dfbb9178', 'no_addr_user', 'no_addr_user@example.in', 'scrypt:32768:8:1$x510CVuvrE8pxWbB$58e139cb8a8c6cf0cf8712501355c72160bee1f17c8a8c28d5c8ac993c37320b78a3032016d3140ee55e84560b2dd7e6ba2055683a83aa7c0cdb77030bebd461', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.063260651Z + restimestampmock: 2025-11-10T12:18:23.063652118Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-636 +spec: + metadata: + connID: "216" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.063814455Z + restimestampmock: 2025-11-10T12:18:23.072672731Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-637 +spec: + metadata: + connID: "218" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [143, 54, 60, 15, 181, 251, 5, 121, 46, 183, 212, 29, 127, 16, 84, 18, 230, 59, 195, 139, 46, 217, 103, 30, 98, 31, 217, 80, 112, 54, 35, 213] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 228 + auth_plugin_data: [52, 19, 63, 53, 97, 2, 111, 6, 47, 73, 20, 45, 37, 15, 118, 95, 6, 75, 6, 72, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.769332474Z + restimestampmock: 2025-11-10T12:18:23.775568381Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-638 +spec: + metadata: + connID: "218" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.775770749Z + restimestampmock: 2025-11-10T12:18:23.775965988Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-639 +spec: + metadata: + connID: "218" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.776091577Z + restimestampmock: 2025-11-10T12:18:23.776240725Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-640 +spec: + metadata: + connID: "218" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.776364234Z + restimestampmock: 2025-11-10T12:18:23.776474744Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-641 +spec: + metadata: + connID: "218" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='no_addr_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 237 + sequence_id: 6 + values: + - type: 253 + name: id + value: 3afeaa74-af93-4845-a994-2421dfbb9178 + unsigned: false + - type: 253 + name: username + value: no_addr_user + unsigned: false + - type: 253 + name: email + value: no_addr_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$x510CVuvrE8pxWbB$58e139cb8a8c6cf0cf8712501355c72160bee1f17c8a8c28d5c8ac993c37320b78a3032016d3140ee55e84560b2dd7e6ba2055683a83aa7c0cdb77030bebd461 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777103 + reqtimestampmock: 2025-11-10T12:18:23.776783711Z + restimestampmock: 2025-11-10T12:18:23.777308366Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-642 +spec: + metadata: + connID: "220" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [177, 63, 180, 189, 1, 104, 102, 228, 8, 57, 147, 50, 128, 161, 222, 189, 253, 156, 38, 56, 5, 154, 162, 27, 88, 46, 201, 227, 144, 144, 150, 80] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 229 + auth_plugin_data: [59, 22, 1, 122, 79, 124, 123, 59, 81, 81, 27, 98, 70, 20, 5, 16, 30, 41, 121, 44, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777105 + reqtimestampmock: 2025-11-10T12:18:25.3032221Z + restimestampmock: 2025-11-10T12:18:25.309226669Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-643 +spec: + metadata: + connID: "220" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777105 + reqtimestampmock: 2025-11-10T12:18:25.309362008Z + restimestampmock: 2025-11-10T12:18:25.309551406Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-644 +spec: + metadata: + connID: "220" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777105 + reqtimestampmock: 2025-11-10T12:18:25.309633846Z + restimestampmock: 2025-11-10T12:18:25.309768115Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-645 +spec: + metadata: + connID: "220" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777105 + reqtimestampmock: 2025-11-10T12:18:25.309869404Z + restimestampmock: 2025-11-10T12:18:25.309988072Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-646 +spec: + metadata: + connID: "220" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='3afeaa74-af93-4845-a994-2421dfbb9178' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 3afeaa74-af93-4845-a994-2421dfbb9178 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777105 + reqtimestampmock: 2025-11-10T12:18:25.310126861Z + restimestampmock: 2025-11-10T12:18:25.310660236Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-647 +spec: + metadata: + connID: "220" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='3afeaa74-af93-4845-a994-2421dfbb9178' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777105 + reqtimestampmock: 2025-11-10T12:18:25.310812995Z + restimestampmock: 2025-11-10T12:18:25.311023042Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-648 +spec: + metadata: + connID: "220" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='3afeaa74-af93-4845-a994-2421dfbb9178' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777105 + reqtimestampmock: 2025-11-10T12:18:25.311123013Z + restimestampmock: 2025-11-10T12:18:25.31143693Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-649 +spec: + metadata: + connID: "220" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777105 + reqtimestampmock: 2025-11-10T12:18:25.311608438Z + restimestampmock: 2025-11-10T12:18:25.323703996Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-650 +spec: + metadata: + connID: "222" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [184, 11, 151, 28, 128, 233, 72, 195, 193, 251, 86, 163, 100, 63, 145, 18, 242, 201, 29, 143, 99, 201, 240, 30, 127, 38, 127, 36, 133, 253, 197, 181] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 231 + auth_plugin_data: [110, 44, 35, 3, 95, 73, 28, 37, 66, 54, 57, 67, 2, 6, 54, 28, 101, 107, 51, 84, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777109 + reqtimestampmock: 2025-11-10T12:18:29.480448162Z + restimestampmock: 2025-11-10T12:18:29.487078845Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-651 +spec: + metadata: + connID: "222" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777109 + reqtimestampmock: 2025-11-10T12:18:29.487249625Z + restimestampmock: 2025-11-10T12:18:29.487445633Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-652 +spec: + metadata: + connID: "222" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777109 + reqtimestampmock: 2025-11-10T12:18:29.487569401Z + restimestampmock: 2025-11-10T12:18:29.487722461Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-653 +spec: + metadata: + connID: "222" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777109 + reqtimestampmock: 2025-11-10T12:18:29.487840689Z + restimestampmock: 2025-11-10T12:18:29.487936129Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-654 +spec: + metadata: + connID: "222" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 347 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('b002b30e-2c02-4c4e-afc6-0f374d902571', 'temp_user_for_auth_get', 'temp_user_for_auth_get@example.in', 'scrypt:32768:8:1$8lEhEPVhNviLrvm7$4edd58e73c1c331a9ad8a188972a39738ba237ddc82287cf79e850eb300f01a893b00fac948650a990bcf125f1d21cedae0b841242a47e9d31bb5b13be9dd846', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777109 + reqtimestampmock: 2025-11-10T12:18:29.537731547Z + restimestampmock: 2025-11-10T12:18:29.538137743Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-655 +spec: + metadata: + connID: "222" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777109 + reqtimestampmock: 2025-11-10T12:18:29.538310702Z + restimestampmock: 2025-11-10T12:18:29.549592487Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-656 +spec: + metadata: + connID: "224" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [191, 138, 119, 204, 52, 62, 138, 207, 111, 94, 65, 175, 40, 225, 248, 187, 45, 55, 204, 179, 24, 221, 154, 173, 16, 230, 201, 0, 164, 87, 172, 174] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 232 + auth_plugin_data: [10, 73, 118, 75, 58, 94, 31, 40, 73, 100, 111, 9, 68, 37, 87, 52, 77, 33, 113, 85, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777110 + reqtimestampmock: 2025-11-10T12:18:30.256584284Z + restimestampmock: 2025-11-10T12:18:30.262953641Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-657 +spec: + metadata: + connID: "224" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777110 + reqtimestampmock: 2025-11-10T12:18:30.26314101Z + restimestampmock: 2025-11-10T12:18:30.263337968Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-658 +spec: + metadata: + connID: "224" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777110 + reqtimestampmock: 2025-11-10T12:18:30.263452137Z + restimestampmock: 2025-11-10T12:18:30.263611755Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-659 +spec: + metadata: + connID: "224" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777110 + reqtimestampmock: 2025-11-10T12:18:30.263697805Z + restimestampmock: 2025-11-10T12:18:30.263811614Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-660 +spec: + metadata: + connID: "224" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 95 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='temp_user_for_auth_get' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 257 + sequence_id: 6 + values: + - type: 253 + name: id + value: b002b30e-2c02-4c4e-afc6-0f374d902571 + unsigned: false + - type: 253 + name: username + value: temp_user_for_auth_get + unsigned: false + - type: 253 + name: email + value: temp_user_for_auth_get@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$8lEhEPVhNviLrvm7$4edd58e73c1c331a9ad8a188972a39738ba237ddc82287cf79e850eb300f01a893b00fac948650a990bcf125f1d21cedae0b841242a47e9d31bb5b13be9dd846 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777110 + reqtimestampmock: 2025-11-10T12:18:30.263948792Z + restimestampmock: 2025-11-10T12:18:30.264441409Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-661 +spec: + metadata: + connID: "226" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [158, 196, 186, 146, 234, 177, 75, 86, 177, 68, 97, 142, 66, 222, 181, 28, 50, 218, 225, 232, 174, 178, 222, 242, 163, 125, 20, 10, 228, 44, 16, 29] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 233 + auth_plugin_data: [10, 102, 6, 117, 118, 92, 80, 82, 1, 58, 100, 42, 34, 98, 46, 60, 1, 70, 35, 40, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777111 + reqtimestampmock: 2025-11-10T12:18:31.827500613Z + restimestampmock: 2025-11-10T12:18:31.833938479Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-662 +spec: + metadata: + connID: "226" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777111 + reqtimestampmock: 2025-11-10T12:18:31.834110748Z + restimestampmock: 2025-11-10T12:18:31.834307616Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-663 +spec: + metadata: + connID: "226" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777111 + reqtimestampmock: 2025-11-10T12:18:31.834418434Z + restimestampmock: 2025-11-10T12:18:31.834621963Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-664 +spec: + metadata: + connID: "226" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777111 + reqtimestampmock: 2025-11-10T12:18:31.834728992Z + restimestampmock: 2025-11-10T12:18:31.83487276Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-665 +spec: + metadata: + connID: "226" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='b002b30e-2c02-4c4e-afc6-0f374d902571' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: b002b30e-2c02-4c4e-afc6-0f374d902571 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777111 + reqtimestampmock: 2025-11-10T12:18:31.835206448Z + restimestampmock: 2025-11-10T12:18:31.835584896Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-666 +spec: + metadata: + connID: "226" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='b002b30e-2c02-4c4e-afc6-0f374d902571' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777111 + reqtimestampmock: 2025-11-10T12:18:31.835736104Z + restimestampmock: 2025-11-10T12:18:31.835988452Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-667 +spec: + metadata: + connID: "226" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='b002b30e-2c02-4c4e-afc6-0f374d902571' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777111 + reqtimestampmock: 2025-11-10T12:18:31.8361824Z + restimestampmock: 2025-11-10T12:18:31.836510637Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-668 +spec: + metadata: + connID: "226" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777111 + reqtimestampmock: 2025-11-10T12:18:31.836613067Z + restimestampmock: 2025-11-10T12:18:31.848603485Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-669 +spec: + metadata: + connID: "228" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [67, 108, 251, 39, 183, 133, 23, 22, 225, 124, 165, 204, 141, 61, 128, 92, 157, 166, 200, 216, 149, 83, 186, 112, 44, 214, 93, 209, 86, 101, 206, 191] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 234 + auth_plugin_data: [125, 118, 46, 95, 117, 30, 73, 55, 102, 39, 67, 60, 65, 92, 114, 26, 104, 101, 60, 92, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777112 + reqtimestampmock: 2025-11-10T12:18:32.658647071Z + restimestampmock: 2025-11-10T12:18:32.664692711Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-670 +spec: + metadata: + connID: "228" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777112 + reqtimestampmock: 2025-11-10T12:18:32.664868008Z + restimestampmock: 2025-11-10T12:18:32.665043717Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-671 +spec: + metadata: + connID: "228" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777112 + reqtimestampmock: 2025-11-10T12:18:32.665161827Z + restimestampmock: 2025-11-10T12:18:32.665280965Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-672 +spec: + metadata: + connID: "228" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777112 + reqtimestampmock: 2025-11-10T12:18:32.665382154Z + restimestampmock: 2025-11-10T12:18:32.665502123Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-673 +spec: + metadata: + connID: "228" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 347 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('595b6f58-990a-47b2-8245-e03d98d9a2df', 'temp_user_for_auth_pay', 'temp_user_for_auth_pay@example.in', 'scrypt:32768:8:1$ltmBGA5YA1bh5T8G$165b0b6a3f96bb4a150aba16aaae043b56cb7f56bb99d27668d25068d4b5962e9f9ba99670cc96f2d6dc085ccf533fe4342463a28875b84b09fec3d2236dfda0', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777112 + reqtimestampmock: 2025-11-10T12:18:32.714403718Z + restimestampmock: 2025-11-10T12:18:32.714883765Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-674 +spec: + metadata: + connID: "228" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777112 + reqtimestampmock: 2025-11-10T12:18:32.715023723Z + restimestampmock: 2025-11-10T12:18:32.726687164Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-675 +spec: + metadata: + connID: "230" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [125, 129, 161, 157, 65, 230, 244, 158, 153, 40, 242, 208, 82, 13, 81, 192, 132, 91, 8, 87, 197, 195, 113, 99, 190, 109, 225, 68, 67, 118, 10, 72] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 236 + auth_plugin_data: [96, 107, 87, 37, 106, 7, 55, 106, 84, 21, 12, 94, 16, 48, 67, 80, 113, 56, 116, 103, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777113 + reqtimestampmock: 2025-11-10T12:18:33.409305832Z + restimestampmock: 2025-11-10T12:18:33.41770111Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-676 +spec: + metadata: + connID: "230" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777113 + reqtimestampmock: 2025-11-10T12:18:33.417846049Z + restimestampmock: 2025-11-10T12:18:33.418042048Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-677 +spec: + metadata: + connID: "230" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777113 + reqtimestampmock: 2025-11-10T12:18:33.418136287Z + restimestampmock: 2025-11-10T12:18:33.418294946Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-678 +spec: + metadata: + connID: "230" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777113 + reqtimestampmock: 2025-11-10T12:18:33.418390315Z + restimestampmock: 2025-11-10T12:18:33.418503914Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-679 +spec: + metadata: + connID: "230" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 95 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='temp_user_for_auth_pay' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 257 + sequence_id: 6 + values: + - type: 253 + name: id + value: 595b6f58-990a-47b2-8245-e03d98d9a2df + unsigned: false + - type: 253 + name: username + value: temp_user_for_auth_pay + unsigned: false + - type: 253 + name: email + value: temp_user_for_auth_pay@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$ltmBGA5YA1bh5T8G$165b0b6a3f96bb4a150aba16aaae043b56cb7f56bb99d27668d25068d4b5962e9f9ba99670cc96f2d6dc085ccf533fe4342463a28875b84b09fec3d2236dfda0 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777113 + reqtimestampmock: 2025-11-10T12:18:33.418677542Z + restimestampmock: 2025-11-10T12:18:33.419162548Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-680 +spec: + metadata: + connID: "232" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [130, 223, 185, 147, 180, 117, 10, 247, 188, 125, 21, 67, 147, 175, 77, 178, 99, 237, 250, 222, 166, 138, 171, 1, 127, 216, 42, 169, 133, 204, 47, 169] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 237 + auth_plugin_data: [87, 17, 127, 75, 109, 92, 113, 16, 85, 75, 1, 89, 8, 85, 32, 51, 7, 120, 57, 104, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777114 + reqtimestampmock: 2025-11-10T12:18:34.977346278Z + restimestampmock: 2025-11-10T12:18:34.983911543Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-681 +spec: + metadata: + connID: "232" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777114 + reqtimestampmock: 2025-11-10T12:18:34.984045111Z + restimestampmock: 2025-11-10T12:18:34.98425538Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-682 +spec: + metadata: + connID: "232" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777114 + reqtimestampmock: 2025-11-10T12:18:34.984482807Z + restimestampmock: 2025-11-10T12:18:34.984536047Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-683 +spec: + metadata: + connID: "232" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777114 + reqtimestampmock: 2025-11-10T12:18:34.984630806Z + restimestampmock: 2025-11-10T12:18:34.984750505Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-684 +spec: + metadata: + connID: "232" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='595b6f58-990a-47b2-8245-e03d98d9a2df' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 595b6f58-990a-47b2-8245-e03d98d9a2df + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777114 + reqtimestampmock: 2025-11-10T12:18:34.984909434Z + restimestampmock: 2025-11-10T12:18:34.985262811Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-685 +spec: + metadata: + connID: "232" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='595b6f58-990a-47b2-8245-e03d98d9a2df' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777114 + reqtimestampmock: 2025-11-10T12:18:34.98542345Z + restimestampmock: 2025-11-10T12:18:34.985644398Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-686 +spec: + metadata: + connID: "232" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='595b6f58-990a-47b2-8245-e03d98d9a2df' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777114 + reqtimestampmock: 2025-11-10T12:18:34.985791866Z + restimestampmock: 2025-11-10T12:18:34.986111213Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-687 +spec: + metadata: + connID: "232" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777114 + reqtimestampmock: 2025-11-10T12:18:34.986209932Z + restimestampmock: 2025-11-10T12:18:34.994457622Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-688 +spec: + metadata: + connID: "234" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [175, 254, 214, 30, 61, 133, 172, 124, 63, 43, 105, 226, 209, 123, 11, 6, 230, 14, 84, 110, 37, 6, 23, 61, 215, 79, 90, 21, 114, 118, 183, 33] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 238 + auth_plugin_data: [120, 71, 53, 39, 81, 15, 29, 81, 106, 60, 55, 5, 46, 26, 126, 21, 22, 19, 20, 79, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777115 + reqtimestampmock: 2025-11-10T12:18:35.799602562Z + restimestampmock: 2025-11-10T12:18:35.805683482Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-689 +spec: + metadata: + connID: "234" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777115 + reqtimestampmock: 2025-11-10T12:18:35.80582575Z + restimestampmock: 2025-11-10T12:18:35.805995348Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-690 +spec: + metadata: + connID: "234" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777115 + reqtimestampmock: 2025-11-10T12:18:35.806109278Z + restimestampmock: 2025-11-10T12:18:35.806271617Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-691 +spec: + metadata: + connID: "234" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777115 + reqtimestampmock: 2025-11-10T12:18:35.806371216Z + restimestampmock: 2025-11-10T12:18:35.806466735Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-692 +spec: + metadata: + connID: "234" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 353 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('54158f89-4d6f-4b9e-a7f1-10ac725e2da5', 'temp_user_for_auth_cancel', 'temp_user_for_auth_cancel@example.in', 'scrypt:32768:8:1$xi2pUASbDaMTAZCz$1bb272cd8b8f4a2c84757ac5a7d53b7c02305e00ecae176285beb46d43d8a7dbca71aabfd0ef400107e37bdbc7b86961797eb1dea2ea06370e9a4cf5038ae3b9', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777115 + reqtimestampmock: 2025-11-10T12:18:35.85657145Z + restimestampmock: 2025-11-10T12:18:35.856975357Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-693 +spec: + metadata: + connID: "234" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777115 + reqtimestampmock: 2025-11-10T12:18:35.857092696Z + restimestampmock: 2025-11-10T12:18:35.865960181Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-694 +spec: + metadata: + connID: "236" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [122, 184, 212, 96, 43, 237, 200, 217, 198, 100, 165, 222, 69, 61, 159, 229, 232, 150, 66, 128, 26, 172, 218, 208, 8, 225, 137, 163, 199, 250, 151, 12] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 239 + auth_plugin_data: [67, 109, 8, 6, 13, 51, 89, 84, 60, 13, 37, 81, 4, 108, 122, 13, 121, 73, 28, 51, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777116 + reqtimestampmock: 2025-11-10T12:18:36.560206531Z + restimestampmock: 2025-11-10T12:18:36.566278569Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-695 +spec: + metadata: + connID: "236" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777116 + reqtimestampmock: 2025-11-10T12:18:36.566473968Z + restimestampmock: 2025-11-10T12:18:36.566654826Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-696 +spec: + metadata: + connID: "236" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777116 + reqtimestampmock: 2025-11-10T12:18:36.566776285Z + restimestampmock: 2025-11-10T12:18:36.566901503Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-697 +spec: + metadata: + connID: "236" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777116 + reqtimestampmock: 2025-11-10T12:18:36.567008343Z + restimestampmock: 2025-11-10T12:18:36.567134662Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-698 +spec: + metadata: + connID: "236" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 98 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='temp_user_for_auth_cancel' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 263 + sequence_id: 6 + values: + - type: 253 + name: id + value: 54158f89-4d6f-4b9e-a7f1-10ac725e2da5 + unsigned: false + - type: 253 + name: username + value: temp_user_for_auth_cancel + unsigned: false + - type: 253 + name: email + value: temp_user_for_auth_cancel@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$xi2pUASbDaMTAZCz$1bb272cd8b8f4a2c84757ac5a7d53b7c02305e00ecae176285beb46d43d8a7dbca71aabfd0ef400107e37bdbc7b86961797eb1dea2ea06370e9a4cf5038ae3b9 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777116 + reqtimestampmock: 2025-11-10T12:18:36.567365211Z + restimestampmock: 2025-11-10T12:18:36.567783536Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-699 +spec: + metadata: + connID: "238" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [62, 115, 80, 150, 161, 126, 9, 122, 13, 52, 148, 50, 227, 63, 20, 184, 30, 181, 252, 233, 195, 111, 167, 204, 12, 103, 26, 96, 36, 165, 15, 24] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 240 + auth_plugin_data: [34, 64, 16, 105, 26, 92, 64, 85, 43, 117, 54, 51, 126, 35, 44, 99, 89, 124, 112, 37, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777118 + reqtimestampmock: 2025-11-10T12:18:38.121054562Z + restimestampmock: 2025-11-10T12:18:38.127605586Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-700 +spec: + metadata: + connID: "238" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777118 + reqtimestampmock: 2025-11-10T12:18:38.127777804Z + restimestampmock: 2025-11-10T12:18:38.127976402Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-701 +spec: + metadata: + connID: "238" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777118 + reqtimestampmock: 2025-11-10T12:18:38.128145201Z + restimestampmock: 2025-11-10T12:18:38.12841195Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-702 +spec: + metadata: + connID: "238" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777118 + reqtimestampmock: 2025-11-10T12:18:38.128534798Z + restimestampmock: 2025-11-10T12:18:38.128671097Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-703 +spec: + metadata: + connID: "238" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='54158f89-4d6f-4b9e-a7f1-10ac725e2da5' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 54158f89-4d6f-4b9e-a7f1-10ac725e2da5 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777118 + reqtimestampmock: 2025-11-10T12:18:38.128851235Z + restimestampmock: 2025-11-10T12:18:38.129281972Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-704 +spec: + metadata: + connID: "238" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='54158f89-4d6f-4b9e-a7f1-10ac725e2da5' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777118 + reqtimestampmock: 2025-11-10T12:18:38.129386761Z + restimestampmock: 2025-11-10T12:18:38.129655059Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-705 +spec: + metadata: + connID: "238" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='54158f89-4d6f-4b9e-a7f1-10ac725e2da5' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777118 + reqtimestampmock: 2025-11-10T12:18:38.129777798Z + restimestampmock: 2025-11-10T12:18:38.130082944Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-706 +spec: + metadata: + connID: "238" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777118 + reqtimestampmock: 2025-11-10T12:18:38.130208903Z + restimestampmock: 2025-11-10T12:18:38.139593214Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-707 +spec: + metadata: + connID: "240" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [161, 102, 216, 139, 161, 15, 215, 5, 78, 2, 97, 31, 201, 38, 114, 178, 158, 145, 37, 105, 183, 63, 113, 93, 62, 1, 75, 216, 184, 226, 171, 69] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 242 + auth_plugin_data: [58, 12, 108, 56, 61, 67, 50, 111, 24, 79, 92, 103, 27, 120, 98, 126, 80, 59, 5, 15, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777121 + reqtimestampmock: 2025-11-10T12:18:41.369811943Z + restimestampmock: 2025-11-10T12:18:41.376123829Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-708 +spec: + metadata: + connID: "240" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777121 + reqtimestampmock: 2025-11-10T12:18:41.376316587Z + restimestampmock: 2025-11-10T12:18:41.376469346Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-709 +spec: + metadata: + connID: "240" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777121 + reqtimestampmock: 2025-11-10T12:18:41.376580735Z + restimestampmock: 2025-11-10T12:18:41.376786443Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-710 +spec: + metadata: + connID: "240" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777121 + reqtimestampmock: 2025-11-10T12:18:41.376886202Z + restimestampmock: 2025-11-10T12:18:41.377005241Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-711 +spec: + metadata: + connID: "240" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('3aa6b50d-82a0-45d8-b37c-d82fa83742fb', 'noauth_addr_user', 'noauth_addr_user@example.in', 'scrypt:32768:8:1$zZvZ0yXCF3IEidSS$9729b3c2b295af116f4b94a5d9b1e7ca80a340d8f69f400a45d0e89f90669f3fd4bee2da30263e9f940a4a8b6a9b43da8cf69ab1f50a32dd2ee878996bc183f3', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777121 + reqtimestampmock: 2025-11-10T12:18:41.424645207Z + restimestampmock: 2025-11-10T12:18:41.425061884Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-712 +spec: + metadata: + connID: "240" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777121 + reqtimestampmock: 2025-11-10T12:18:41.425224123Z + restimestampmock: 2025-11-10T12:18:41.434569224Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-713 +spec: + metadata: + connID: "242" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [170, 253, 81, 22, 122, 159, 88, 239, 102, 246, 132, 165, 114, 106, 95, 34, 219, 184, 219, 91, 197, 17, 121, 35, 210, 127, 51, 189, 103, 76, 179, 220] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 243 + auth_plugin_data: [42, 72, 76, 27, 22, 108, 31, 48, 26, 114, 110, 41, 66, 125, 111, 35, 95, 39, 70, 73, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.218167579Z + restimestampmock: 2025-11-10T12:18:42.224348167Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-714 +spec: + metadata: + connID: "242" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.224490246Z + restimestampmock: 2025-11-10T12:18:42.224715375Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-715 +spec: + metadata: + connID: "242" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.224826233Z + restimestampmock: 2025-11-10T12:18:42.224973562Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-716 +spec: + metadata: + connID: "242" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.225123931Z + restimestampmock: 2025-11-10T12:18:42.22523748Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-717 +spec: + metadata: + connID: "242" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='3aa6b50d-82a0-45d8-b37c-d82fa83742fb' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 3aa6b50d-82a0-45d8-b37c-d82fa83742fb + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.225470678Z + restimestampmock: 2025-11-10T12:18:42.225832265Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-718 +spec: + metadata: + connID: "242" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 268 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('8cd8a687-17ae-433c-9822-068378f0076f','3aa6b50d-82a0-45d8-b37c-d82fa83742fb','123 MG Road',NULL,'Bengaluru','Karnataka','560001','IN','+919876543210',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.226055003Z + restimestampmock: 2025-11-10T12:18:42.226759417Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-719 +spec: + metadata: + connID: "242" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='3aa6b50d-82a0-45d8-b37c-d82fa83742fb' AND id<>'8cd8a687-17ae-433c-9822-068378f0076f' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.226928425Z + restimestampmock: 2025-11-10T12:18:42.227206764Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-720 +spec: + metadata: + connID: "242" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.227327062Z + restimestampmock: 2025-11-10T12:18:42.236547934Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-721 +spec: + metadata: + connID: "244" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [97, 251, 179, 120, 163, 65, 44, 249, 207, 186, 84, 226, 238, 127, 200, 224, 58, 87, 14, 223, 115, 86, 112, 145, 242, 55, 162, 145, 243, 102, 118, 113] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 244 + auth_plugin_data: [10, 98, 103, 68, 100, 37, 97, 49, 58, 116, 107, 93, 67, 71, 11, 68, 34, 84, 18, 48, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.947252367Z + restimestampmock: 2025-11-10T12:18:42.953424085Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-722 +spec: + metadata: + connID: "244" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.953589233Z + restimestampmock: 2025-11-10T12:18:42.953772562Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-723 +spec: + metadata: + connID: "244" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.953889941Z + restimestampmock: 2025-11-10T12:18:42.95403592Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-724 +spec: + metadata: + connID: "244" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.954127629Z + restimestampmock: 2025-11-10T12:18:42.954277558Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-725 +spec: + metadata: + connID: "244" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='noauth_addr_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: 3aa6b50d-82a0-45d8-b37c-d82fa83742fb + unsigned: false + - type: 253 + name: username + value: noauth_addr_user + unsigned: false + - type: 253 + name: email + value: noauth_addr_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$zZvZ0yXCF3IEidSS$9729b3c2b295af116f4b94a5d9b1e7ca80a340d8f69f400a45d0e89f90669f3fd4bee2da30263e9f940a4a8b6a9b43da8cf69ab1f50a32dd2ee878996bc183f3 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777122 + reqtimestampmock: 2025-11-10T12:18:42.954451187Z + restimestampmock: 2025-11-10T12:18:42.954951113Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-726 +spec: + metadata: + connID: "246" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [33, 120, 7, 155, 193, 53, 151, 198, 200, 23, 82, 161, 163, 28, 168, 206, 202, 91, 251, 150, 220, 16, 202, 127, 70, 52, 118, 241, 37, 164, 12, 118] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 246 + auth_plugin_data: [37, 29, 20, 111, 39, 55, 106, 69, 107, 17, 89, 86, 102, 99, 120, 106, 82, 68, 75, 97, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777123 + reqtimestampmock: 2025-11-10T12:18:43.68874593Z + restimestampmock: 2025-11-10T12:18:43.697971312Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-727 +spec: + metadata: + connID: "246" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777123 + reqtimestampmock: 2025-11-10T12:18:43.698122101Z + restimestampmock: 2025-11-10T12:18:43.698324739Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-728 +spec: + metadata: + connID: "246" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777123 + reqtimestampmock: 2025-11-10T12:18:43.698437658Z + restimestampmock: 2025-11-10T12:18:43.698588537Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-729 +spec: + metadata: + connID: "246" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777123 + reqtimestampmock: 2025-11-10T12:18:43.698690576Z + restimestampmock: 2025-11-10T12:18:43.698789466Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-730 +spec: + metadata: + connID: "246" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='3aa6b50d-82a0-45d8-b37c-d82fa83742fb' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 3aa6b50d-82a0-45d8-b37c-d82fa83742fb + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777123 + reqtimestampmock: 2025-11-10T12:18:43.698951014Z + restimestampmock: 2025-11-10T12:18:43.699320581Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-731 +spec: + metadata: + connID: "246" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='3aa6b50d-82a0-45d8-b37c-d82fa83742fb' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777123 + reqtimestampmock: 2025-11-10T12:18:43.699456789Z + restimestampmock: 2025-11-10T12:18:43.699823106Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-732 +spec: + metadata: + connID: "246" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='3aa6b50d-82a0-45d8-b37c-d82fa83742fb' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777123 + reqtimestampmock: 2025-11-10T12:18:43.699953145Z + restimestampmock: 2025-11-10T12:18:43.700239993Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-733 +spec: + metadata: + connID: "246" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777123 + reqtimestampmock: 2025-11-10T12:18:43.700336372Z + restimestampmock: 2025-11-10T12:18:43.713518821Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-734 +spec: + metadata: + connID: "248" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [18, 30, 170, 54, 62, 212, 64, 254, 230, 150, 26, 247, 31, 72, 88, 173, 3, 166, 8, 124, 24, 217, 100, 112, 42, 42, 240, 198, 198, 92, 6, 205] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 247 + auth_plugin_data: [80, 59, 32, 46, 14, 50, 103, 64, 21, 72, 54, 104, 123, 19, 24, 78, 87, 82, 3, 81, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777125 + reqtimestampmock: 2025-11-10T12:18:45.611620754Z + restimestampmock: 2025-11-10T12:18:45.618244947Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-735 +spec: + metadata: + connID: "248" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777125 + reqtimestampmock: 2025-11-10T12:18:45.618410017Z + restimestampmock: 2025-11-10T12:18:45.618594515Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-736 +spec: + metadata: + connID: "248" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777125 + reqtimestampmock: 2025-11-10T12:18:45.618688844Z + restimestampmock: 2025-11-10T12:18:45.618834473Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-737 +spec: + metadata: + connID: "248" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777125 + reqtimestampmock: 2025-11-10T12:18:45.618945982Z + restimestampmock: 2025-11-10T12:18:45.619000721Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-738 +spec: + metadata: + connID: "248" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6', 'noauth_list_addr', 'noauth_list_addr@example.in', 'scrypt:32768:8:1$pnh4J4uEBP3I5Nar$edb4262cb1fb36f1434d22f0eba4e1df2b545dccec0dbac53f91db295b5eae9b5fbcf0eeecde60133243468ff3bf8b72410a4137e801aa0b32c2dccedfccccca', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777125 + reqtimestampmock: 2025-11-10T12:18:45.668434023Z + restimestampmock: 2025-11-10T12:18:45.66883237Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-739 +spec: + metadata: + connID: "248" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777125 + reqtimestampmock: 2025-11-10T12:18:45.668958379Z + restimestampmock: 2025-11-10T12:18:45.680616651Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-740 +spec: + metadata: + connID: "250" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [165, 248, 46, 36, 185, 232, 188, 38, 198, 133, 67, 221, 164, 99, 91, 0, 97, 127, 194, 25, 187, 124, 47, 127, 140, 101, 164, 164, 44, 176, 117, 162] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 248 + auth_plugin_data: [60, 20, 61, 3, 48, 109, 61, 25, 91, 62, 11, 58, 37, 64, 48, 14, 6, 109, 81, 1, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777126 + reqtimestampmock: 2025-11-10T12:18:46.410607681Z + restimestampmock: 2025-11-10T12:18:46.416583241Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-741 +spec: + metadata: + connID: "250" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777126 + reqtimestampmock: 2025-11-10T12:18:46.41674412Z + restimestampmock: 2025-11-10T12:18:46.416921048Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-742 +spec: + metadata: + connID: "250" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777126 + reqtimestampmock: 2025-11-10T12:18:46.417018948Z + restimestampmock: 2025-11-10T12:18:46.417166556Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-743 +spec: + metadata: + connID: "250" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777126 + reqtimestampmock: 2025-11-10T12:18:46.417260785Z + restimestampmock: 2025-11-10T12:18:46.417359995Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-744 +spec: + metadata: + connID: "250" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777126 + reqtimestampmock: 2025-11-10T12:18:46.417540233Z + restimestampmock: 2025-11-10T12:18:46.417917189Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-745 +spec: + metadata: + connID: "250" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 190 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, line1, line2, city, state, postal_code, country, phone, is_default FROM addresses WHERE user_id='e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6' ORDER BY is_default DESC, created_at DESC + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 9 + columns: + - header: + payload_length: 51 + sequence_id: 2 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 3 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line1 + org_name: line1 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 4 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line2 + org_name: line2 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 5 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: city + org_name: city + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 6 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: state + org_name: state + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 69 + sequence_id: 7 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: postal_code + org_name: postal_code + fixed_length: 12 + character_set: 255 + column_length: 80 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 61 + sequence_id: 8 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: country + org_name: country + fixed_length: 12 + character_set: 255 + column_length: 8 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 9 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 67 + sequence_id: 10 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: is_default + org_name: is_default + fixed_length: 12 + character_set: 63 + column_length: 1 + type: 1 + flags: 1 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 11 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777126 + reqtimestampmock: 2025-11-10T12:18:46.418054289Z + restimestampmock: 2025-11-10T12:18:46.418657384Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-746 +spec: + metadata: + connID: "252" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [210, 51, 22, 172, 149, 204, 202, 203, 18, 13, 241, 190, 31, 32, 20, 223, 21, 253, 104, 143, 61, 159, 125, 83, 188, 138, 114, 31, 64, 204, 251, 119] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 249 + auth_plugin_data: [4, 72, 120, 105, 113, 117, 39, 42, 50, 20, 25, 26, 110, 111, 124, 110, 126, 22, 127, 69, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.14587429Z + restimestampmock: 2025-11-10T12:18:47.152362265Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-747 +spec: + metadata: + connID: "252" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.152579863Z + restimestampmock: 2025-11-10T12:18:47.152751982Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-748 +spec: + metadata: + connID: "252" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.152870271Z + restimestampmock: 2025-11-10T12:18:47.153137198Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-749 +spec: + metadata: + connID: "252" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.153245668Z + restimestampmock: 2025-11-10T12:18:47.153350747Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-750 +spec: + metadata: + connID: "252" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='noauth_list_addr' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6 + unsigned: false + - type: 253 + name: username + value: noauth_list_addr + unsigned: false + - type: 253 + name: email + value: noauth_list_addr@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$pnh4J4uEBP3I5Nar$edb4262cb1fb36f1434d22f0eba4e1df2b545dccec0dbac53f91db295b5eae9b5fbcf0eeecde60133243468ff3bf8b72410a4137e801aa0b32c2dccedfccccca + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.153522875Z + restimestampmock: 2025-11-10T12:18:47.153950401Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-751 +spec: + metadata: + connID: "254" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [214, 31, 153, 16, 187, 181, 233, 85, 234, 51, 211, 31, 244, 45, 141, 124, 106, 96, 159, 156, 136, 64, 122, 107, 39, 95, 236, 198, 118, 144, 107, 233] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 250 + auth_plugin_data: [121, 30, 112, 100, 12, 20, 54, 91, 61, 70, 60, 37, 44, 67, 96, 67, 73, 63, 58, 73, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.972105707Z + restimestampmock: 2025-11-10T12:18:47.978461703Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-752 +spec: + metadata: + connID: "254" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.978619432Z + restimestampmock: 2025-11-10T12:18:47.97879591Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-753 +spec: + metadata: + connID: "254" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.97891348Z + restimestampmock: 2025-11-10T12:18:47.979081138Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-754 +spec: + metadata: + connID: "254" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.979198427Z + restimestampmock: 2025-11-10T12:18:47.979311596Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-755 +spec: + metadata: + connID: "254" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.979497525Z + restimestampmock: 2025-11-10T12:18:47.979819422Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-756 +spec: + metadata: + connID: "254" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.98001008Z + restimestampmock: 2025-11-10T12:18:47.980358778Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-757 +spec: + metadata: + connID: "254" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.980493417Z + restimestampmock: 2025-11-10T12:18:47.980841104Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-758 +spec: + metadata: + connID: "254" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777127 + reqtimestampmock: 2025-11-10T12:18:47.980954063Z + restimestampmock: 2025-11-10T12:18:47.990503462Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-759 +spec: + metadata: + connID: "256" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [147, 168, 231, 199, 153, 245, 67, 222, 224, 253, 154, 37, 119, 84, 65, 246, 142, 88, 92, 187, 33, 184, 133, 165, 60, 117, 56, 19, 65, 168, 233, 133] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 256 + auth_plugin_data: [73, 83, 53, 3, 69, 42, 24, 40, 66, 33, 107, 83, 101, 78, 72, 100, 42, 70, 106, 44, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777149 + reqtimestampmock: 2025-11-10T12:19:09.882621643Z + restimestampmock: 2025-11-10T12:19:09.889586935Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-760 +spec: + metadata: + connID: "256" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777149 + reqtimestampmock: 2025-11-10T12:19:09.889770673Z + restimestampmock: 2025-11-10T12:19:09.889931732Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-761 +spec: + metadata: + connID: "256" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777149 + reqtimestampmock: 2025-11-10T12:19:09.89003903Z + restimestampmock: 2025-11-10T12:19:09.89018929Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-762 +spec: + metadata: + connID: "256" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777149 + reqtimestampmock: 2025-11-10T12:19:09.890307289Z + restimestampmock: 2025-11-10T12:19:09.890412697Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-763 +spec: + metadata: + connID: "256" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 327 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('3363bd32-cba3-4bb9-b6b9-3257a46415fa', 'rahul_mumbai', 'rahul_mumbai@example.in', 'scrypt:32768:8:1$QfLqQ8J2sb9hRlXa$2686e11caf52fbb25fc475fb7c85337ae8017c6e279cb2aa37d0ef9f1b3668b0134536a1299cfbe4da11b9e8f17d1aecb78db0eaf8759e93dbde69623e0e327a', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777149 + reqtimestampmock: 2025-11-10T12:19:09.940253646Z + restimestampmock: 2025-11-10T12:19:09.940726862Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-764 +spec: + metadata: + connID: "256" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777149 + reqtimestampmock: 2025-11-10T12:19:09.940906491Z + restimestampmock: 2025-11-10T12:19:09.953287486Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-765 +spec: + metadata: + connID: "258" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [8, 93, 98, 58, 158, 105, 106, 226, 43, 238, 78, 126, 119, 75, 43, 108, 128, 251, 128, 163, 135, 199, 33, 174, 31, 248, 80, 101, 95, 206, 25, 214] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 257 + auth_plugin_data: [34, 17, 1, 90, 53, 37, 105, 127, 121, 79, 37, 58, 119, 123, 33, 102, 90, 12, 109, 37, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777150 + reqtimestampmock: 2025-11-10T12:19:10.63587111Z + restimestampmock: 2025-11-10T12:19:10.642014608Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-766 +spec: + metadata: + connID: "258" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777150 + reqtimestampmock: 2025-11-10T12:19:10.642245536Z + restimestampmock: 2025-11-10T12:19:10.642424124Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-767 +spec: + metadata: + connID: "258" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777150 + reqtimestampmock: 2025-11-10T12:19:10.642530174Z + restimestampmock: 2025-11-10T12:19:10.64286458Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-768 +spec: + metadata: + connID: "258" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777150 + reqtimestampmock: 2025-11-10T12:19:10.642977919Z + restimestampmock: 2025-11-10T12:19:10.64299461Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-769 +spec: + metadata: + connID: "258" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='rahul_mumbai' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 237 + sequence_id: 6 + values: + - type: 253 + name: id + value: 3363bd32-cba3-4bb9-b6b9-3257a46415fa + unsigned: false + - type: 253 + name: username + value: rahul_mumbai + unsigned: false + - type: 253 + name: email + value: rahul_mumbai@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$QfLqQ8J2sb9hRlXa$2686e11caf52fbb25fc475fb7c85337ae8017c6e279cb2aa37d0ef9f1b3668b0134536a1299cfbe4da11b9e8f17d1aecb78db0eaf8759e93dbde69623e0e327a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777150 + reqtimestampmock: 2025-11-10T12:19:10.643183257Z + restimestampmock: 2025-11-10T12:19:10.643614054Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-770 +spec: + metadata: + connID: "260" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [201, 227, 190, 163, 145, 13, 125, 225, 93, 247, 237, 70, 113, 236, 156, 82, 52, 6, 128, 232, 144, 233, 124, 183, 110, 22, 151, 48, 237, 196, 208, 151] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 258 + auth_plugin_data: [39, 58, 68, 38, 113, 73, 101, 90, 4, 56, 85, 113, 96, 103, 70, 119, 118, 59, 8, 12, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777151 + reqtimestampmock: 2025-11-10T12:19:11.456286169Z + restimestampmock: 2025-11-10T12:19:11.462445736Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-771 +spec: + metadata: + connID: "260" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777151 + reqtimestampmock: 2025-11-10T12:19:11.462626365Z + restimestampmock: 2025-11-10T12:19:11.462838852Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-772 +spec: + metadata: + connID: "260" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777151 + reqtimestampmock: 2025-11-10T12:19:11.462958202Z + restimestampmock: 2025-11-10T12:19:11.46320139Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-773 +spec: + metadata: + connID: "260" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777151 + reqtimestampmock: 2025-11-10T12:19:11.463373968Z + restimestampmock: 2025-11-10T12:19:11.463480108Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-774 +spec: + metadata: + connID: "260" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='3363bd32-cba3-4bb9-b6b9-3257a46415fa' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 3363bd32-cba3-4bb9-b6b9-3257a46415fa + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777151 + reqtimestampmock: 2025-11-10T12:19:11.463666427Z + restimestampmock: 2025-11-10T12:19:11.464035754Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-775 +spec: + metadata: + connID: "260" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 271 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('b1a0dd95-5f84-435b-a6ee-59dfe1aa6155','3363bd32-cba3-4bb9-b6b9-3257a46415fa','101 Bandra West',NULL,'Mumbai','Maharashtra','400050','IN','+919820098200',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777151 + reqtimestampmock: 2025-11-10T12:19:11.464215321Z + restimestampmock: 2025-11-10T12:19:11.464574208Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-776 +spec: + metadata: + connID: "260" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='3363bd32-cba3-4bb9-b6b9-3257a46415fa' AND id<>'b1a0dd95-5f84-435b-a6ee-59dfe1aa6155' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777151 + reqtimestampmock: 2025-11-10T12:19:11.464719187Z + restimestampmock: 2025-11-10T12:19:11.464944886Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-777 +spec: + metadata: + connID: "260" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777151 + reqtimestampmock: 2025-11-10T12:19:11.465075633Z + restimestampmock: 2025-11-10T12:19:11.477194672Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-778 +spec: + metadata: + connID: "262" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [88, 115, 40, 75, 199, 177, 205, 182, 47, 162, 194, 29, 6, 31, 99, 171, 185, 171, 123, 106, 174, 114, 91, 120, 120, 237, 40, 190, 27, 182, 81, 238] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 259 + auth_plugin_data: [47, 7, 88, 12, 121, 34, 119, 62, 2, 31, 17, 5, 120, 61, 120, 93, 46, 39, 79, 103, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777152 + reqtimestampmock: 2025-11-10T12:19:12.204137661Z + restimestampmock: 2025-11-10T12:19:12.210028412Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-779 +spec: + metadata: + connID: "262" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777152 + reqtimestampmock: 2025-11-10T12:19:12.21020825Z + restimestampmock: 2025-11-10T12:19:12.210384048Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-780 +spec: + metadata: + connID: "262" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777152 + reqtimestampmock: 2025-11-10T12:19:12.210501577Z + restimestampmock: 2025-11-10T12:19:12.210658676Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-781 +spec: + metadata: + connID: "262" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777152 + reqtimestampmock: 2025-11-10T12:19:12.210768665Z + restimestampmock: 2025-11-10T12:19:12.210857204Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-782 +spec: + metadata: + connID: "262" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='3363bd32-cba3-4bb9-b6b9-3257a46415fa' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 3363bd32-cba3-4bb9-b6b9-3257a46415fa + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777152 + reqtimestampmock: 2025-11-10T12:19:12.211030923Z + restimestampmock: 2025-11-10T12:19:12.21138379Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-783 +spec: + metadata: + connID: "262" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 190 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, line1, line2, city, state, postal_code, country, phone, is_default FROM addresses WHERE user_id='3363bd32-cba3-4bb9-b6b9-3257a46415fa' ORDER BY is_default DESC, created_at DESC + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 9 + columns: + - header: + payload_length: 51 + sequence_id: 2 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 3 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line1 + org_name: line1 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 4 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line2 + org_name: line2 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 5 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: city + org_name: city + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 6 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: state + org_name: state + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 69 + sequence_id: 7 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: postal_code + org_name: postal_code + fixed_length: 12 + character_set: 255 + column_length: 80 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 61 + sequence_id: 8 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: country + org_name: country + fixed_length: 12 + character_set: 255 + column_length: 8 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 9 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 67 + sequence_id: 10 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: is_default + org_name: is_default + fixed_length: 12 + character_set: 63 + column_length: 1 + type: 1 + flags: 1 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 99 + sequence_id: 11 + values: + - type: 253 + name: id + value: b1a0dd95-5f84-435b-a6ee-59dfe1aa6155 + unsigned: false + - type: 253 + name: line1 + value: 101 Bandra West + unsigned: false + - type: 253 + name: line2 + value: null + unsigned: false + - type: 253 + name: city + value: Mumbai + unsigned: false + - type: 253 + name: state + value: Maharashtra + unsigned: false + - type: 253 + name: postal_code + value: "400050" + unsigned: false + - type: 253 + name: country + value: IN + unsigned: false + - type: 253 + name: phone + value: "+919820098200" + unsigned: false + - type: 1 + name: is_default + value: "1" + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 12 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777152 + reqtimestampmock: 2025-11-10T12:19:12.211535098Z + restimestampmock: 2025-11-10T12:19:12.212085264Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-784 +spec: + metadata: + connID: "264" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [169, 149, 221, 15, 43, 196, 241, 232, 22, 227, 157, 71, 153, 251, 176, 175, 132, 36, 43, 149, 253, 211, 187, 127, 46, 82, 138, 27, 212, 19, 171, 254] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 260 + auth_plugin_data: [59, 110, 100, 37, 102, 68, 126, 123, 16, 62, 65, 57, 59, 48, 109, 109, 112, 95, 90, 122, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777153 + reqtimestampmock: 2025-11-10T12:19:13.029817386Z + restimestampmock: 2025-11-10T12:19:13.036122983Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-785 +spec: + metadata: + connID: "264" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777153 + reqtimestampmock: 2025-11-10T12:19:13.03632188Z + restimestampmock: 2025-11-10T12:19:13.03653402Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-786 +spec: + metadata: + connID: "264" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777153 + reqtimestampmock: 2025-11-10T12:19:13.036669069Z + restimestampmock: 2025-11-10T12:19:13.036836596Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-787 +spec: + metadata: + connID: "264" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777153 + reqtimestampmock: 2025-11-10T12:19:13.037003216Z + restimestampmock: 2025-11-10T12:19:13.03758532Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-788 +spec: + metadata: + connID: "264" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='3363bd32-cba3-4bb9-b6b9-3257a46415fa' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 3363bd32-cba3-4bb9-b6b9-3257a46415fa + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777153 + reqtimestampmock: 2025-11-10T12:19:13.03762581Z + restimestampmock: 2025-11-10T12:19:13.037989167Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-789 +spec: + metadata: + connID: "264" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='3363bd32-cba3-4bb9-b6b9-3257a46415fa' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777153 + reqtimestampmock: 2025-11-10T12:19:13.038191055Z + restimestampmock: 2025-11-10T12:19:13.038597682Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-790 +spec: + metadata: + connID: "264" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='3363bd32-cba3-4bb9-b6b9-3257a46415fa' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777153 + reqtimestampmock: 2025-11-10T12:19:13.03872487Z + restimestampmock: 2025-11-10T12:19:13.038972289Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-791 +spec: + metadata: + connID: "264" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777153 + reqtimestampmock: 2025-11-10T12:19:13.039103497Z + restimestampmock: 2025-11-10T12:19:13.052219967Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-792 +spec: + metadata: + connID: "266" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [69, 140, 213, 183, 49, 38, 25, 100, 186, 61, 226, 227, 112, 50, 6, 237, 245, 170, 106, 182, 32, 108, 103, 252, 147, 183, 87, 10, 103, 184, 186, 96] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 262 + auth_plugin_data: [40, 121, 106, 47, 104, 37, 25, 1, 32, 107, 1, 86, 57, 28, 20, 113, 39, 88, 7, 86, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777155 + reqtimestampmock: 2025-11-10T12:19:15.213354843Z + restimestampmock: 2025-11-10T12:19:15.219845309Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-793 +spec: + metadata: + connID: "266" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777155 + reqtimestampmock: 2025-11-10T12:19:15.220001756Z + restimestampmock: 2025-11-10T12:19:15.220223266Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-794 +spec: + metadata: + connID: "266" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777155 + reqtimestampmock: 2025-11-10T12:19:15.220345585Z + restimestampmock: 2025-11-10T12:19:15.220513143Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-795 +spec: + metadata: + connID: "266" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777155 + reqtimestampmock: 2025-11-10T12:19:15.220609082Z + restimestampmock: 2025-11-10T12:19:15.220712161Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-796 +spec: + metadata: + connID: "266" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 325 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a', 'sneha_delhi', 'sneha_delhi@example.in', 'scrypt:32768:8:1$ZWw7AvMu4jrz36W8$6d808434ee2a3678ae70f0c4bbe0516cf1113fae3a033ed8fb96a47e4933c33a7d6f0243f8920fdd9e99fd108f468b148da1d9856158bf1674c51449c1f9cc1d', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777155 + reqtimestampmock: 2025-11-10T12:19:15.270263702Z + restimestampmock: 2025-11-10T12:19:15.270674119Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-797 +spec: + metadata: + connID: "266" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777155 + reqtimestampmock: 2025-11-10T12:19:15.270808938Z + restimestampmock: 2025-11-10T12:19:15.282181602Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-798 +spec: + metadata: + connID: "268" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [20, 71, 175, 146, 253, 67, 99, 68, 160, 2, 200, 254, 4, 184, 185, 141, 137, 194, 64, 179, 19, 153, 102, 133, 77, 173, 5, 209, 31, 184, 123, 187] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 263 + auth_plugin_data: [98, 34, 113, 8, 31, 16, 48, 20, 94, 8, 116, 28, 127, 125, 111, 63, 75, 48, 92, 104, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.158883408Z + restimestampmock: 2025-11-10T12:19:16.164923287Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-799 +spec: + metadata: + connID: "268" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.165159935Z + restimestampmock: 2025-11-10T12:19:16.165301994Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-800 +spec: + metadata: + connID: "268" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.165473211Z + restimestampmock: 2025-11-10T12:19:16.165616791Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-801 +spec: + metadata: + connID: "268" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.16572011Z + restimestampmock: 2025-11-10T12:19:16.165809689Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-802 +spec: + metadata: + connID: "268" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 84 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='sneha_delhi' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 235 + sequence_id: 6 + values: + - type: 253 + name: id + value: 5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a + unsigned: false + - type: 253 + name: username + value: sneha_delhi + unsigned: false + - type: 253 + name: email + value: sneha_delhi@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$ZWw7AvMu4jrz36W8$6d808434ee2a3678ae70f0c4bbe0516cf1113fae3a033ed8fb96a47e4933c33a7d6f0243f8920fdd9e99fd108f468b148da1d9856158bf1674c51449c1f9cc1d + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.165965148Z + restimestampmock: 2025-11-10T12:19:16.166402475Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-803 +spec: + metadata: + connID: "270" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [187, 39, 106, 130, 21, 62, 127, 225, 167, 116, 27, 177, 53, 117, 90, 200, 107, 214, 222, 185, 93, 145, 131, 71, 235, 195, 2, 122, 186, 210, 0, 29] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 264 + auth_plugin_data: [72, 65, 107, 101, 47, 12, 2, 83, 33, 93, 63, 103, 31, 100, 17, 81, 71, 64, 62, 125, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.935824196Z + restimestampmock: 2025-11-10T12:19:16.942729039Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-804 +spec: + metadata: + connID: "270" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.942934066Z + restimestampmock: 2025-11-10T12:19:16.943184154Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-805 +spec: + metadata: + connID: "270" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.943306633Z + restimestampmock: 2025-11-10T12:19:16.943450462Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-806 +spec: + metadata: + connID: "270" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.943574451Z + restimestampmock: 2025-11-10T12:19:16.94361456Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-807 +spec: + metadata: + connID: "270" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.9437491Z + restimestampmock: 2025-11-10T12:19:16.944199475Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-808 +spec: + metadata: + connID: "270" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 273 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('223a9700-5da2-454b-a432-8de30c59cf5d','5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a','25 Hauz Khas Village',NULL,'New Delhi','Delhi','110016','IN','+919810098100',0) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.944319095Z + restimestampmock: 2025-11-10T12:19:16.944638922Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-809 +spec: + metadata: + connID: "270" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777156 + reqtimestampmock: 2025-11-10T12:19:16.944715542Z + restimestampmock: 2025-11-10T12:19:16.956185715Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-810 +spec: + metadata: + connID: "272" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [29, 160, 51, 122, 193, 135, 112, 114, 88, 206, 162, 103, 52, 240, 138, 227, 80, 100, 105, 79, 15, 0, 96, 5, 41, 90, 91, 211, 232, 48, 203, 177] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 265 + auth_plugin_data: [124, 121, 53, 54, 20, 106, 10, 33, 44, 111, 83, 44, 1, 29, 98, 75, 37, 39, 82, 110, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777157 + reqtimestampmock: 2025-11-10T12:19:17.739219153Z + restimestampmock: 2025-11-10T12:19:17.748017959Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-811 +spec: + metadata: + connID: "272" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777157 + reqtimestampmock: 2025-11-10T12:19:17.748216987Z + restimestampmock: 2025-11-10T12:19:17.748400565Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-812 +spec: + metadata: + connID: "272" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777157 + reqtimestampmock: 2025-11-10T12:19:17.748526894Z + restimestampmock: 2025-11-10T12:19:17.748680033Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-813 +spec: + metadata: + connID: "272" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777157 + reqtimestampmock: 2025-11-10T12:19:17.748808501Z + restimestampmock: 2025-11-10T12:19:17.748911571Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-814 +spec: + metadata: + connID: "272" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777157 + reqtimestampmock: 2025-11-10T12:19:17.749180548Z + restimestampmock: 2025-11-10T12:19:17.749519955Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-815 +spec: + metadata: + connID: "272" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 190 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, line1, line2, city, state, postal_code, country, phone, is_default FROM addresses WHERE user_id='5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a' ORDER BY is_default DESC, created_at DESC + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 9 + columns: + - header: + payload_length: 51 + sequence_id: 2 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 3 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line1 + org_name: line1 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 4 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line2 + org_name: line2 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 5 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: city + org_name: city + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 6 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: state + org_name: state + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 69 + sequence_id: 7 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: postal_code + org_name: postal_code + fixed_length: 12 + character_set: 255 + column_length: 80 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 61 + sequence_id: 8 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: country + org_name: country + fixed_length: 12 + character_set: 255 + column_length: 8 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 9 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 67 + sequence_id: 10 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: is_default + org_name: is_default + fixed_length: 12 + character_set: 63 + column_length: 1 + type: 1 + flags: 1 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 101 + sequence_id: 11 + values: + - type: 253 + name: id + value: 223a9700-5da2-454b-a432-8de30c59cf5d + unsigned: false + - type: 253 + name: line1 + value: 25 Hauz Khas Village + unsigned: false + - type: 253 + name: line2 + value: null + unsigned: false + - type: 253 + name: city + value: New Delhi + unsigned: false + - type: 253 + name: state + value: Delhi + unsigned: false + - type: 253 + name: postal_code + value: "110016" + unsigned: false + - type: 253 + name: country + value: IN + unsigned: false + - type: 253 + name: phone + value: "+919810098100" + unsigned: false + - type: 1 + name: is_default + value: "0" + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 12 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777157 + reqtimestampmock: 2025-11-10T12:19:17.749681484Z + restimestampmock: 2025-11-10T12:19:17.750440867Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-816 +spec: + metadata: + connID: "274" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [63, 196, 140, 110, 173, 126, 61, 62, 141, 168, 68, 113, 190, 137, 194, 244, 203, 58, 177, 51, 144, 248, 146, 233, 156, 169, 125, 145, 232, 11, 101, 201] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 266 + auth_plugin_data: [80, 72, 44, 69, 28, 1, 18, 59, 34, 109, 110, 87, 70, 92, 18, 32, 68, 124, 4, 124, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777158 + reqtimestampmock: 2025-11-10T12:19:18.495993662Z + restimestampmock: 2025-11-10T12:19:18.502047702Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-817 +spec: + metadata: + connID: "274" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777158 + reqtimestampmock: 2025-11-10T12:19:18.50219497Z + restimestampmock: 2025-11-10T12:19:18.502385158Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-818 +spec: + metadata: + connID: "274" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777158 + reqtimestampmock: 2025-11-10T12:19:18.502471797Z + restimestampmock: 2025-11-10T12:19:18.502597276Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-819 +spec: + metadata: + connID: "274" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777158 + reqtimestampmock: 2025-11-10T12:19:18.502694707Z + restimestampmock: 2025-11-10T12:19:18.502770205Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-820 +spec: + metadata: + connID: "274" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777158 + reqtimestampmock: 2025-11-10T12:19:18.502958465Z + restimestampmock: 2025-11-10T12:19:18.503328471Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-821 +spec: + metadata: + connID: "274" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777158 + reqtimestampmock: 2025-11-10T12:19:18.50346525Z + restimestampmock: 2025-11-10T12:19:18.503764857Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-822 +spec: + metadata: + connID: "274" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777158 + reqtimestampmock: 2025-11-10T12:19:18.503887206Z + restimestampmock: 2025-11-10T12:19:18.504271593Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-823 +spec: + metadata: + connID: "274" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777158 + reqtimestampmock: 2025-11-10T12:19:18.504358061Z + restimestampmock: 2025-11-10T12:19:18.517069565Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-824 +spec: + metadata: + connID: "276" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [248, 51, 98, 51, 247, 86, 99, 25, 232, 154, 174, 143, 148, 196, 134, 162, 30, 244, 168, 81, 66, 86, 140, 240, 83, 77, 135, 128, 128, 133, 26, 35] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 268 + auth_plugin_data: [75, 87, 97, 45, 1, 42, 71, 115, 19, 33, 83, 65, 3, 62, 93, 49, 119, 30, 41, 64, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777160 + reqtimestampmock: 2025-11-10T12:19:20.389677364Z + restimestampmock: 2025-11-10T12:19:20.396132849Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-825 +spec: + metadata: + connID: "276" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777160 + reqtimestampmock: 2025-11-10T12:19:20.396277938Z + restimestampmock: 2025-11-10T12:19:20.396439466Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-826 +spec: + metadata: + connID: "276" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777160 + reqtimestampmock: 2025-11-10T12:19:20.396553545Z + restimestampmock: 2025-11-10T12:19:20.396706484Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-827 +spec: + metadata: + connID: "276" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777160 + reqtimestampmock: 2025-11-10T12:19:20.396838973Z + restimestampmock: 2025-11-10T12:19:20.396949342Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-828 +spec: + metadata: + connID: "276" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('80b19253-5d7a-4d05-9036-bff647acbaaa', 'bad_prod_user', 'bad_prod_user@example.in', 'scrypt:32768:8:1$Qw1guDJXcnMZVv97$59115be41d771ad8a9be0a34427ccde4be9f51be541cbaef6141696a88b6e297e9ee04a555ea2ef03e303428f35d8e724d291c6cdc7c0fa9f0081ee3474e9269', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777160 + reqtimestampmock: 2025-11-10T12:19:20.445042916Z + restimestampmock: 2025-11-10T12:19:20.445508853Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-829 +spec: + metadata: + connID: "276" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777160 + reqtimestampmock: 2025-11-10T12:19:20.445652822Z + restimestampmock: 2025-11-10T12:19:20.459182667Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-830 +spec: + metadata: + connID: "278" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [44, 159, 215, 13, 148, 175, 42, 50, 214, 15, 78, 167, 238, 218, 35, 109, 106, 79, 58, 64, 94, 59, 236, 137, 95, 0, 177, 42, 115, 122, 60, 161] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 269 + auth_plugin_data: [125, 121, 30, 100, 20, 68, 115, 79, 16, 58, 33, 120, 32, 56, 82, 90, 21, 113, 114, 54, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.136007523Z + restimestampmock: 2025-11-10T12:19:21.142800076Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-831 +spec: + metadata: + connID: "278" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.143190312Z + restimestampmock: 2025-11-10T12:19:21.143426761Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-832 +spec: + metadata: + connID: "278" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.143561538Z + restimestampmock: 2025-11-10T12:19:21.143726987Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-833 +spec: + metadata: + connID: "278" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.143835807Z + restimestampmock: 2025-11-10T12:19:21.144003806Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-834 +spec: + metadata: + connID: "278" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='bad_prod_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: 80b19253-5d7a-4d05-9036-bff647acbaaa + unsigned: false + - type: 253 + name: username + value: bad_prod_user + unsigned: false + - type: 253 + name: email + value: bad_prod_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Qw1guDJXcnMZVv97$59115be41d771ad8a9be0a34427ccde4be9f51be541cbaef6141696a88b6e297e9ee04a555ea2ef03e303428f35d8e724d291c6cdc7c0fa9f0081ee3474e9269 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.144210303Z + restimestampmock: 2025-11-10T12:19:21.144743809Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-835 +spec: + metadata: + connID: "280" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [101, 55, 206, 145, 175, 197, 197, 52, 171, 199, 117, 0, 131, 20, 236, 245, 30, 32, 66, 231, 178, 212, 82, 52, 12, 89, 189, 58, 111, 2, 159, 150] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 270 + auth_plugin_data: [39, 10, 89, 82, 112, 71, 55, 21, 79, 123, 25, 53, 22, 11, 53, 103, 123, 125, 113, 1, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.936336866Z + restimestampmock: 2025-11-10T12:19:21.943574155Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-836 +spec: + metadata: + connID: "280" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.943742903Z + restimestampmock: 2025-11-10T12:19:21.943920253Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-837 +spec: + metadata: + connID: "280" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.944047081Z + restimestampmock: 2025-11-10T12:19:21.94421288Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-838 +spec: + metadata: + connID: "280" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.944321859Z + restimestampmock: 2025-11-10T12:19:21.944419538Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-839 +spec: + metadata: + connID: "280" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='80b19253-5d7a-4d05-9036-bff647acbaaa' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 80b19253-5d7a-4d05-9036-bff647acbaaa + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.944599076Z + restimestampmock: 2025-11-10T12:19:21.944939004Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-840 +spec: + metadata: + connID: "280" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 272 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('9b315486-d4a1-4e7f-a2b0-9dd8736af86c','80b19253-5d7a-4d05-9036-bff647acbaaa','123 Sector 17',NULL,'Chandigarh','Chandigarh','160017','IN','+919876512345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.945130051Z + restimestampmock: 2025-11-10T12:19:21.945497298Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-841 +spec: + metadata: + connID: "280" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='80b19253-5d7a-4d05-9036-bff647acbaaa' AND id<>'9b315486-d4a1-4e7f-a2b0-9dd8736af86c' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.945639878Z + restimestampmock: 2025-11-10T12:19:21.945915745Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-842 +spec: + metadata: + connID: "280" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777161 + reqtimestampmock: 2025-11-10T12:19:21.946025675Z + restimestampmock: 2025-11-10T12:19:21.960085045Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-843 +spec: + metadata: + connID: "282" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [41, 80, 106, 194, 163, 173, 18, 174, 150, 250, 100, 190, 214, 198, 211, 231, 219, 182, 241, 149, 202, 21, 189, 212, 193, 148, 115, 237, 163, 229, 231, 174] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 271 + auth_plugin_data: [111, 32, 9, 16, 81, 35, 9, 9, 126, 48, 8, 84, 50, 96, 91, 89, 126, 25, 19, 122, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777163 + reqtimestampmock: 2025-11-10T12:19:23.517554529Z + restimestampmock: 2025-11-10T12:19:23.52698295Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-844 +spec: + metadata: + connID: "282" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777163 + reqtimestampmock: 2025-11-10T12:19:23.527200528Z + restimestampmock: 2025-11-10T12:19:23.527400546Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-845 +spec: + metadata: + connID: "282" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777163 + reqtimestampmock: 2025-11-10T12:19:23.527525105Z + restimestampmock: 2025-11-10T12:19:23.527683154Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-846 +spec: + metadata: + connID: "282" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777163 + reqtimestampmock: 2025-11-10T12:19:23.527795853Z + restimestampmock: 2025-11-10T12:19:23.527893352Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-847 +spec: + metadata: + connID: "282" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='80b19253-5d7a-4d05-9036-bff647acbaaa' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 80b19253-5d7a-4d05-9036-bff647acbaaa + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777163 + reqtimestampmock: 2025-11-10T12:19:23.52808823Z + restimestampmock: 2025-11-10T12:19:23.528456387Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-848 +spec: + metadata: + connID: "282" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='80b19253-5d7a-4d05-9036-bff647acbaaa' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777163 + reqtimestampmock: 2025-11-10T12:19:23.528650956Z + restimestampmock: 2025-11-10T12:19:23.529015503Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-849 +spec: + metadata: + connID: "282" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='80b19253-5d7a-4d05-9036-bff647acbaaa' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777163 + reqtimestampmock: 2025-11-10T12:19:23.529145521Z + restimestampmock: 2025-11-10T12:19:23.529438129Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-850 +spec: + metadata: + connID: "282" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777163 + reqtimestampmock: 2025-11-10T12:19:23.529554149Z + restimestampmock: 2025-11-10T12:19:23.54114338Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-851 +spec: + metadata: + connID: "284" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [70, 210, 144, 116, 20, 137, 147, 149, 86, 242, 194, 192, 193, 186, 209, 146, 227, 106, 193, 55, 153, 221, 10, 240, 242, 54, 109, 168, 251, 177, 169, 188] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 273 + auth_plugin_data: [117, 111, 110, 100, 41, 26, 62, 100, 94, 88, 72, 10, 63, 28, 93, 25, 81, 56, 57, 31, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777165 + reqtimestampmock: 2025-11-10T12:19:25.946539778Z + restimestampmock: 2025-11-10T12:19:25.953232042Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-852 +spec: + metadata: + connID: "284" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777165 + reqtimestampmock: 2025-11-10T12:19:25.953390081Z + restimestampmock: 2025-11-10T12:19:25.953573719Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-853 +spec: + metadata: + connID: "284" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777165 + reqtimestampmock: 2025-11-10T12:19:25.953679308Z + restimestampmock: 2025-11-10T12:19:25.953853517Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-854 +spec: + metadata: + connID: "284" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777165 + reqtimestampmock: 2025-11-10T12:19:25.953972695Z + restimestampmock: 2025-11-10T12:19:25.954064205Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-855 +spec: + metadata: + connID: "284" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 333 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('d2e41f94-240a-471b-a0fb-78c3380c60c2', 'zero_quant_user', 'zero_quant_user@example.in', 'scrypt:32768:8:1$mSz0PK2Tg0PBS0AB$e6a6f447956c2dbe5d019c8cb4bd8bd25ae01d0b78ee7b3cfdff79d1292aec06e85824a0b512321fdbef6e3907f4535fc2fef64d2f729c36832ba2275719b336', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777166 + reqtimestampmock: 2025-11-10T12:19:26.002418087Z + restimestampmock: 2025-11-10T12:19:26.002955692Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-856 +spec: + metadata: + connID: "284" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777166 + reqtimestampmock: 2025-11-10T12:19:26.003062341Z + restimestampmock: 2025-11-10T12:19:26.01509528Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-857 +spec: + metadata: + connID: "286" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [118, 143, 191, 88, 5, 220, 226, 233, 213, 63, 21, 188, 185, 149, 48, 179, 241, 92, 220, 86, 30, 51, 140, 53, 224, 121, 71, 132, 246, 250, 210, 225] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 274 + auth_plugin_data: [13, 85, 18, 13, 29, 77, 30, 48, 103, 58, 107, 98, 45, 96, 76, 51, 124, 117, 19, 47, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777166 + reqtimestampmock: 2025-11-10T12:19:26.878225225Z + restimestampmock: 2025-11-10T12:19:26.884573251Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-858 +spec: + metadata: + connID: "286" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777166 + reqtimestampmock: 2025-11-10T12:19:26.884729Z + restimestampmock: 2025-11-10T12:19:26.884945938Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-859 +spec: + metadata: + connID: "286" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777166 + reqtimestampmock: 2025-11-10T12:19:26.885050167Z + restimestampmock: 2025-11-10T12:19:26.885192656Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-860 +spec: + metadata: + connID: "286" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777166 + reqtimestampmock: 2025-11-10T12:19:26.885351845Z + restimestampmock: 2025-11-10T12:19:26.885392615Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-861 +spec: + metadata: + connID: "286" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 88 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='zero_quant_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 243 + sequence_id: 6 + values: + - type: 253 + name: id + value: d2e41f94-240a-471b-a0fb-78c3380c60c2 + unsigned: false + - type: 253 + name: username + value: zero_quant_user + unsigned: false + - type: 253 + name: email + value: zero_quant_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$mSz0PK2Tg0PBS0AB$e6a6f447956c2dbe5d019c8cb4bd8bd25ae01d0b78ee7b3cfdff79d1292aec06e85824a0b512321fdbef6e3907f4535fc2fef64d2f729c36832ba2275719b336 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777166 + reqtimestampmock: 2025-11-10T12:19:26.885511214Z + restimestampmock: 2025-11-10T12:19:26.885834861Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-862 +spec: + metadata: + connID: "288" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [97, 141, 139, 59, 58, 219, 83, 52, 131, 225, 62, 126, 232, 75, 204, 251, 154, 152, 33, 222, 141, 196, 159, 128, 223, 189, 167, 74, 244, 151, 171, 176] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 275 + auth_plugin_data: [105, 101, 62, 31, 124, 70, 25, 120, 9, 81, 22, 59, 87, 125, 7, 92, 110, 60, 76, 14, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777167 + reqtimestampmock: 2025-11-10T12:19:27.702162152Z + restimestampmock: 2025-11-10T12:19:27.711609333Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-863 +spec: + metadata: + connID: "288" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777167 + reqtimestampmock: 2025-11-10T12:19:27.711816691Z + restimestampmock: 2025-11-10T12:19:27.711993269Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-864 +spec: + metadata: + connID: "288" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777167 + reqtimestampmock: 2025-11-10T12:19:27.712116908Z + restimestampmock: 2025-11-10T12:19:27.712286128Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-865 +spec: + metadata: + connID: "288" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777167 + reqtimestampmock: 2025-11-10T12:19:27.712403887Z + restimestampmock: 2025-11-10T12:19:27.712564334Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-866 +spec: + metadata: + connID: "288" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='d2e41f94-240a-471b-a0fb-78c3380c60c2' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: d2e41f94-240a-471b-a0fb-78c3380c60c2 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777167 + reqtimestampmock: 2025-11-10T12:19:27.712786703Z + restimestampmock: 2025-11-10T12:19:27.71314225Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-867 +spec: + metadata: + connID: "288" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 269 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('16862c52-8fa2-4677-81ba-cc26143ddddc','d2e41f94-240a-471b-a0fb-78c3380c60c2','45 Ashram Road',NULL,'Ahmedabad','Gujarat','380009','IN','+919988776655',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777167 + reqtimestampmock: 2025-11-10T12:19:27.713319268Z + restimestampmock: 2025-11-10T12:19:27.713753964Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-868 +spec: + metadata: + connID: "288" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='d2e41f94-240a-471b-a0fb-78c3380c60c2' AND id<>'16862c52-8fa2-4677-81ba-cc26143ddddc' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777167 + reqtimestampmock: 2025-11-10T12:19:27.713892674Z + restimestampmock: 2025-11-10T12:19:27.714279871Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-869 +spec: + metadata: + connID: "288" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777167 + reqtimestampmock: 2025-11-10T12:19:27.714444158Z + restimestampmock: 2025-11-10T12:19:27.725984871Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-870 +spec: + metadata: + connID: "290" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [101, 152, 105, 157, 180, 237, 73, 108, 134, 136, 129, 224, 130, 237, 168, 7, 145, 106, 123, 30, 11, 53, 20, 80, 62, 102, 12, 161, 131, 44, 95, 228] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 277 + auth_plugin_data: [70, 98, 37, 37, 109, 74, 66, 51, 16, 41, 100, 25, 10, 47, 122, 6, 118, 61, 49, 95, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777169 + reqtimestampmock: 2025-11-10T12:19:29.270738537Z + restimestampmock: 2025-11-10T12:19:29.277258202Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-871 +spec: + metadata: + connID: "290" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777169 + reqtimestampmock: 2025-11-10T12:19:29.277452739Z + restimestampmock: 2025-11-10T12:19:29.277636828Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-872 +spec: + metadata: + connID: "290" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777169 + reqtimestampmock: 2025-11-10T12:19:29.277751567Z + restimestampmock: 2025-11-10T12:19:29.277900466Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-873 +spec: + metadata: + connID: "290" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777169 + reqtimestampmock: 2025-11-10T12:19:29.278014395Z + restimestampmock: 2025-11-10T12:19:29.278127564Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-874 +spec: + metadata: + connID: "290" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='d2e41f94-240a-471b-a0fb-78c3380c60c2' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: d2e41f94-240a-471b-a0fb-78c3380c60c2 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777169 + reqtimestampmock: 2025-11-10T12:19:29.278321993Z + restimestampmock: 2025-11-10T12:19:29.278687319Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-875 +spec: + metadata: + connID: "290" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='d2e41f94-240a-471b-a0fb-78c3380c60c2' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777169 + reqtimestampmock: 2025-11-10T12:19:29.278867287Z + restimestampmock: 2025-11-10T12:19:29.279205154Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-876 +spec: + metadata: + connID: "290" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='d2e41f94-240a-471b-a0fb-78c3380c60c2' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777169 + reqtimestampmock: 2025-11-10T12:19:29.279317505Z + restimestampmock: 2025-11-10T12:19:29.279612411Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-877 +spec: + metadata: + connID: "290" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777169 + reqtimestampmock: 2025-11-10T12:19:29.280064558Z + restimestampmock: 2025-11-10T12:19:29.290164462Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-878 +spec: + metadata: + connID: "292" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [14, 148, 115, 9, 107, 57, 175, 70, 194, 224, 174, 83, 20, 253, 97, 221, 186, 178, 175, 144, 113, 83, 9, 169, 72, 92, 45, 240, 253, 209, 78, 47] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 278 + auth_plugin_data: [55, 96, 15, 45, 91, 4, 90, 76, 101, 41, 7, 98, 13, 55, 60, 20, 57, 76, 90, 92, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777171 + reqtimestampmock: 2025-11-10T12:19:31.650055351Z + restimestampmock: 2025-11-10T12:19:31.656403529Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-879 +spec: + metadata: + connID: "292" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777171 + reqtimestampmock: 2025-11-10T12:19:31.656554616Z + restimestampmock: 2025-11-10T12:19:31.656731355Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-880 +spec: + metadata: + connID: "292" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777171 + reqtimestampmock: 2025-11-10T12:19:31.656854525Z + restimestampmock: 2025-11-10T12:19:31.657006263Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-881 +spec: + metadata: + connID: "292" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777171 + reqtimestampmock: 2025-11-10T12:19:31.657100722Z + restimestampmock: 2025-11-10T12:19:31.657215941Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-882 +spec: + metadata: + connID: "292" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('c6906a40-98ca-4161-9d35-da12bc129a7f', 'no_items_user', 'no_items_user@example.in', 'scrypt:32768:8:1$s2eCS6yHOLefZIQG$42a63b40c72dac1a8b47b27593e0343456616464b8c0683ad2c4df805c6165d22ac8c18f90e2fc5643f0f8c96bcb83168347dfac0768b5d41acf56553d3d07ec', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777171 + reqtimestampmock: 2025-11-10T12:19:31.706973892Z + restimestampmock: 2025-11-10T12:19:31.707376118Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-883 +spec: + metadata: + connID: "292" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777171 + reqtimestampmock: 2025-11-10T12:19:31.707475956Z + restimestampmock: 2025-11-10T12:19:31.716917138Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-884 +spec: + metadata: + connID: "294" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [23, 90, 42, 2, 93, 133, 126, 213, 194, 48, 114, 27, 144, 252, 107, 53, 202, 142, 155, 136, 179, 200, 69, 215, 124, 106, 47, 230, 139, 130, 67, 176] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 279 + auth_plugin_data: [126, 125, 110, 126, 99, 50, 120, 39, 78, 122, 18, 78, 11, 62, 28, 118, 56, 94, 30, 40, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777172 + reqtimestampmock: 2025-11-10T12:19:32.426103804Z + restimestampmock: 2025-11-10T12:19:32.43247812Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-885 +spec: + metadata: + connID: "294" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777172 + reqtimestampmock: 2025-11-10T12:19:32.432649109Z + restimestampmock: 2025-11-10T12:19:32.432899347Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-886 +spec: + metadata: + connID: "294" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777172 + reqtimestampmock: 2025-11-10T12:19:32.433011826Z + restimestampmock: 2025-11-10T12:19:32.433162775Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-887 +spec: + metadata: + connID: "294" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777172 + reqtimestampmock: 2025-11-10T12:19:32.433255594Z + restimestampmock: 2025-11-10T12:19:32.433366243Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-888 +spec: + metadata: + connID: "294" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='no_items_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: c6906a40-98ca-4161-9d35-da12bc129a7f + unsigned: false + - type: 253 + name: username + value: no_items_user + unsigned: false + - type: 253 + name: email + value: no_items_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$s2eCS6yHOLefZIQG$42a63b40c72dac1a8b47b27593e0343456616464b8c0683ad2c4df805c6165d22ac8c18f90e2fc5643f0f8c96bcb83168347dfac0768b5d41acf56553d3d07ec + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777172 + reqtimestampmock: 2025-11-10T12:19:32.433511722Z + restimestampmock: 2025-11-10T12:19:32.433999688Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-889 +spec: + metadata: + connID: "296" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [159, 215, 15, 231, 56, 104, 136, 109, 187, 253, 77, 220, 25, 212, 203, 235, 239, 248, 120, 82, 185, 38, 212, 117, 189, 218, 209, 156, 9, 60, 30, 94] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 280 + auth_plugin_data: [37, 99, 99, 21, 62, 4, 72, 110, 62, 55, 73, 55, 71, 38, 18, 115, 40, 21, 69, 59, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777173 + reqtimestampmock: 2025-11-10T12:19:33.173173582Z + restimestampmock: 2025-11-10T12:19:33.182706841Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-890 +spec: + metadata: + connID: "296" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777173 + reqtimestampmock: 2025-11-10T12:19:33.18288106Z + restimestampmock: 2025-11-10T12:19:33.183057769Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-891 +spec: + metadata: + connID: "296" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777173 + reqtimestampmock: 2025-11-10T12:19:33.183197168Z + restimestampmock: 2025-11-10T12:19:33.183343066Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-892 +spec: + metadata: + connID: "296" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777173 + reqtimestampmock: 2025-11-10T12:19:33.183428916Z + restimestampmock: 2025-11-10T12:19:33.183523614Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-893 +spec: + metadata: + connID: "296" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='c6906a40-98ca-4161-9d35-da12bc129a7f' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: c6906a40-98ca-4161-9d35-da12bc129a7f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777173 + reqtimestampmock: 2025-11-10T12:19:33.183679394Z + restimestampmock: 2025-11-10T12:19:33.18404734Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-894 +spec: + metadata: + connID: "296" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 264 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('4fa7bac9-4929-4b7c-9809-47681ee46b29','c6906a40-98ca-4161-9d35-da12bc129a7f','67 MI Road',NULL,'Jaipur','Rajasthan','302001','IN','+919829012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777173 + reqtimestampmock: 2025-11-10T12:19:33.184170049Z + restimestampmock: 2025-11-10T12:19:33.184437468Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-895 +spec: + metadata: + connID: "296" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='c6906a40-98ca-4161-9d35-da12bc129a7f' AND id<>'4fa7bac9-4929-4b7c-9809-47681ee46b29' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777173 + reqtimestampmock: 2025-11-10T12:19:33.184598375Z + restimestampmock: 2025-11-10T12:19:33.184876893Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-896 +spec: + metadata: + connID: "296" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777173 + reqtimestampmock: 2025-11-10T12:19:33.184985782Z + restimestampmock: 2025-11-10T12:19:33.194013216Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-897 +spec: + metadata: + connID: "298" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [0, 11, 212, 165, 165, 145, 93, 57, 96, 236, 16, 19, 247, 174, 111, 134, 107, 38, 5, 59, 232, 144, 29, 200, 22, 21, 39, 197, 65, 172, 117, 231] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 282 + auth_plugin_data: [37, 31, 28, 116, 114, 118, 104, 98, 59, 102, 16, 62, 87, 11, 31, 68, 39, 39, 86, 56, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777174 + reqtimestampmock: 2025-11-10T12:19:34.734950578Z + restimestampmock: 2025-11-10T12:19:34.741475603Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-898 +spec: + metadata: + connID: "298" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777174 + reqtimestampmock: 2025-11-10T12:19:34.741648222Z + restimestampmock: 2025-11-10T12:19:34.74184838Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-899 +spec: + metadata: + connID: "298" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777174 + reqtimestampmock: 2025-11-10T12:19:34.74193597Z + restimestampmock: 2025-11-10T12:19:34.742107987Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-900 +spec: + metadata: + connID: "298" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777174 + reqtimestampmock: 2025-11-10T12:19:34.742207906Z + restimestampmock: 2025-11-10T12:19:34.742302727Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-901 +spec: + metadata: + connID: "298" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='c6906a40-98ca-4161-9d35-da12bc129a7f' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: c6906a40-98ca-4161-9d35-da12bc129a7f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777174 + reqtimestampmock: 2025-11-10T12:19:34.742480634Z + restimestampmock: 2025-11-10T12:19:34.742848641Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-902 +spec: + metadata: + connID: "298" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='c6906a40-98ca-4161-9d35-da12bc129a7f' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777174 + reqtimestampmock: 2025-11-10T12:19:34.742986611Z + restimestampmock: 2025-11-10T12:19:34.743323448Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-903 +spec: + metadata: + connID: "298" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='c6906a40-98ca-4161-9d35-da12bc129a7f' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777174 + reqtimestampmock: 2025-11-10T12:19:34.743444897Z + restimestampmock: 2025-11-10T12:19:34.743685305Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-904 +spec: + metadata: + connID: "298" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777174 + reqtimestampmock: 2025-11-10T12:19:34.743798094Z + restimestampmock: 2025-11-10T12:19:34.755045659Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-905 +spec: + metadata: + connID: "300" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [134, 153, 112, 228, 255, 209, 123, 16, 142, 52, 88, 6, 155, 5, 216, 228, 159, 212, 86, 49, 97, 43, 209, 1, 36, 174, 185, 123, 164, 139, 177, 188] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 283 + auth_plugin_data: [69, 43, 101, 53, 41, 88, 58, 1, 107, 29, 45, 19, 108, 76, 25, 96, 73, 98, 43, 123, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777176 + reqtimestampmock: 2025-11-10T12:19:36.772438273Z + restimestampmock: 2025-11-10T12:19:36.77870224Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-906 +spec: + metadata: + connID: "300" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777176 + reqtimestampmock: 2025-11-10T12:19:36.778867879Z + restimestampmock: 2025-11-10T12:19:36.779032448Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-907 +spec: + metadata: + connID: "300" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777176 + reqtimestampmock: 2025-11-10T12:19:36.779134097Z + restimestampmock: 2025-11-10T12:19:36.779341964Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-908 +spec: + metadata: + connID: "300" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777176 + reqtimestampmock: 2025-11-10T12:19:36.779487643Z + restimestampmock: 2025-11-10T12:19:36.779627153Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-909 +spec: + metadata: + connID: "300" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('05b4f265-384a-4be5-b72f-b3281743867c', 'pay_twice_user', 'pay_twice_user@example.in', 'scrypt:32768:8:1$AqPCxA0dgCyPzJSw$a692b86ba74b5aec183690e0ec1a12ee10b66dfc92068b207003a64fe97fcf63cd22215b0c85fa9bb223c419ed3bcc07f6c73439c2c5f6b2cddc6a49a2e2cba6', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777176 + reqtimestampmock: 2025-11-10T12:19:36.827803136Z + restimestampmock: 2025-11-10T12:19:36.828239223Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-910 +spec: + metadata: + connID: "300" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777176 + reqtimestampmock: 2025-11-10T12:19:36.828400541Z + restimestampmock: 2025-11-10T12:19:36.83688756Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-911 +spec: + metadata: + connID: "302" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [18, 66, 255, 23, 212, 248, 155, 55, 238, 75, 50, 85, 50, 148, 225, 170, 209, 103, 30, 242, 115, 62, 120, 129, 207, 190, 141, 12, 0, 242, 56, 130] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 284 + auth_plugin_data: [15, 1, 78, 7, 31, 31, 119, 47, 100, 123, 45, 7, 86, 101, 55, 1, 91, 18, 124, 11, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777177 + reqtimestampmock: 2025-11-10T12:19:37.658870747Z + restimestampmock: 2025-11-10T12:19:37.665845148Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-912 +spec: + metadata: + connID: "302" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777177 + reqtimestampmock: 2025-11-10T12:19:37.666034697Z + restimestampmock: 2025-11-10T12:19:37.666249196Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-913 +spec: + metadata: + connID: "302" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777177 + reqtimestampmock: 2025-11-10T12:19:37.666387034Z + restimestampmock: 2025-11-10T12:19:37.666553522Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-914 +spec: + metadata: + connID: "302" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777177 + reqtimestampmock: 2025-11-10T12:19:37.666652562Z + restimestampmock: 2025-11-10T12:19:37.666754831Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-915 +spec: + metadata: + connID: "302" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='pay_twice_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: 05b4f265-384a-4be5-b72f-b3281743867c + unsigned: false + - type: 253 + name: username + value: pay_twice_user + unsigned: false + - type: 253 + name: email + value: pay_twice_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$AqPCxA0dgCyPzJSw$a692b86ba74b5aec183690e0ec1a12ee10b66dfc92068b207003a64fe97fcf63cd22215b0c85fa9bb223c419ed3bcc07f6c73439c2c5f6b2cddc6a49a2e2cba6 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777177 + reqtimestampmock: 2025-11-10T12:19:37.666927438Z + restimestampmock: 2025-11-10T12:19:37.667424025Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-916 +spec: + metadata: + connID: "304" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [80, 167, 162, 10, 38, 38, 39, 102, 159, 27, 213, 149, 92, 236, 146, 208, 9, 31, 15, 219, 163, 150, 22, 225, 180, 181, 18, 92, 150, 82, 154, 211] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 285 + auth_plugin_data: [50, 7, 62, 66, 102, 81, 100, 47, 127, 37, 58, 123, 121, 86, 101, 54, 75, 13, 70, 27, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777178 + reqtimestampmock: 2025-11-10T12:19:38.418167433Z + restimestampmock: 2025-11-10T12:19:38.424406411Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-917 +spec: + metadata: + connID: "304" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777178 + reqtimestampmock: 2025-11-10T12:19:38.42455714Z + restimestampmock: 2025-11-10T12:19:38.424737778Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-918 +spec: + metadata: + connID: "304" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777178 + reqtimestampmock: 2025-11-10T12:19:38.424889027Z + restimestampmock: 2025-11-10T12:19:38.425143425Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-919 +spec: + metadata: + connID: "304" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777178 + reqtimestampmock: 2025-11-10T12:19:38.425256274Z + restimestampmock: 2025-11-10T12:19:38.425359123Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-920 +spec: + metadata: + connID: "304" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='05b4f265-384a-4be5-b72f-b3281743867c' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 05b4f265-384a-4be5-b72f-b3281743867c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777178 + reqtimestampmock: 2025-11-10T12:19:38.425518002Z + restimestampmock: 2025-11-10T12:19:38.425928389Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-921 +spec: + metadata: + connID: "304" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 271 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('a0789c4d-45b2-486a-86ea-ae6f06226459','05b4f265-384a-4be5-b72f-b3281743867c','8 Hazratganj',NULL,'Lucknow','Uttar Pradesh','226001','IN','+919415012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777178 + reqtimestampmock: 2025-11-10T12:19:38.426119047Z + restimestampmock: 2025-11-10T12:19:38.426457793Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-922 +spec: + metadata: + connID: "304" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='05b4f265-384a-4be5-b72f-b3281743867c' AND id<>'a0789c4d-45b2-486a-86ea-ae6f06226459' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777178 + reqtimestampmock: 2025-11-10T12:19:38.426610482Z + restimestampmock: 2025-11-10T12:19:38.42691392Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-923 +spec: + metadata: + connID: "304" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777178 + reqtimestampmock: 2025-11-10T12:19:38.42700574Z + restimestampmock: 2025-11-10T12:19:38.437853969Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-924 +spec: + metadata: + connID: "306" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [183, 44, 186, 186, 253, 223, 229, 200, 46, 145, 117, 222, 244, 118, 106, 93, 244, 219, 65, 72, 141, 1, 180, 190, 124, 142, 190, 10, 152, 58, 40, 107] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 287 + auth_plugin_data: [64, 45, 52, 84, 76, 27, 26, 61, 102, 33, 103, 84, 100, 19, 35, 39, 42, 41, 50, 114, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777180 + reqtimestampmock: 2025-11-10T12:19:40.010879103Z + restimestampmock: 2025-11-10T12:19:40.01711823Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-925 +spec: + metadata: + connID: "306" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777180 + reqtimestampmock: 2025-11-10T12:19:40.017277049Z + restimestampmock: 2025-11-10T12:19:40.017437477Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-926 +spec: + metadata: + connID: "306" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777180 + reqtimestampmock: 2025-11-10T12:19:40.017501657Z + restimestampmock: 2025-11-10T12:19:40.017635575Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-927 +spec: + metadata: + connID: "306" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777180 + reqtimestampmock: 2025-11-10T12:19:40.017715275Z + restimestampmock: 2025-11-10T12:19:40.017808004Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-928 +spec: + metadata: + connID: "306" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='05b4f265-384a-4be5-b72f-b3281743867c' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 05b4f265-384a-4be5-b72f-b3281743867c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777180 + reqtimestampmock: 2025-11-10T12:19:40.017959943Z + restimestampmock: 2025-11-10T12:19:40.01829998Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-929 +spec: + metadata: + connID: "306" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='05b4f265-384a-4be5-b72f-b3281743867c' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777180 + reqtimestampmock: 2025-11-10T12:19:40.018432399Z + restimestampmock: 2025-11-10T12:19:40.018871746Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-930 +spec: + metadata: + connID: "306" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='05b4f265-384a-4be5-b72f-b3281743867c' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777180 + reqtimestampmock: 2025-11-10T12:19:40.019007654Z + restimestampmock: 2025-11-10T12:19:40.019322131Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-931 +spec: + metadata: + connID: "306" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777180 + reqtimestampmock: 2025-11-10T12:19:40.01942273Z + restimestampmock: 2025-11-10T12:19:40.027937148Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-932 +spec: + metadata: + connID: "308" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [158, 148, 55, 25, 34, 37, 80, 0, 93, 180, 73, 126, 57, 161, 207, 235, 73, 232, 53, 49, 174, 216, 194, 23, 85, 24, 97, 241, 86, 108, 188, 109] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 288 + auth_plugin_data: [59, 25, 59, 47, 55, 1, 22, 61, 93, 89, 97, 25, 124, 98, 58, 99, 57, 32, 114, 59, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777183 + reqtimestampmock: 2025-11-10T12:19:43.228265376Z + restimestampmock: 2025-11-10T12:19:43.234374715Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-933 +spec: + metadata: + connID: "308" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777183 + reqtimestampmock: 2025-11-10T12:19:43.234513444Z + restimestampmock: 2025-11-10T12:19:43.234706322Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-934 +spec: + metadata: + connID: "308" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777183 + reqtimestampmock: 2025-11-10T12:19:43.23481522Z + restimestampmock: 2025-11-10T12:19:43.234974949Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-935 +spec: + metadata: + connID: "308" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777183 + reqtimestampmock: 2025-11-10T12:19:43.235069519Z + restimestampmock: 2025-11-10T12:19:43.235185887Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-936 +spec: + metadata: + connID: "308" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a', 'bad_addr_user', 'bad_addr_user@example.in', 'scrypt:32768:8:1$WIStSA6GULhzFQmz$61c905d0834462278d321021aa4e219ae359e00178f5bb4fa876b01d18562db9b3ddd3c8c19c6c23d0d64c138e87b1557e18894f4a084b428dfeccb3c98c7522', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777183 + reqtimestampmock: 2025-11-10T12:19:43.282610987Z + restimestampmock: 2025-11-10T12:19:43.283079643Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-937 +spec: + metadata: + connID: "308" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777183 + reqtimestampmock: 2025-11-10T12:19:43.283202333Z + restimestampmock: 2025-11-10T12:19:43.296069544Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-938 +spec: + metadata: + connID: "310" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [213, 168, 39, 199, 138, 112, 192, 198, 237, 195, 122, 234, 68, 189, 255, 159, 100, 144, 139, 38, 185, 245, 141, 94, 175, 70, 196, 62, 204, 124, 29, 175] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 290 + auth_plugin_data: [93, 101, 88, 1, 32, 62, 4, 42, 115, 47, 54, 25, 1, 41, 112, 79, 109, 51, 94, 112, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777184 + reqtimestampmock: 2025-11-10T12:19:44.0391066Z + restimestampmock: 2025-11-10T12:19:44.045372157Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-939 +spec: + metadata: + connID: "310" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777184 + reqtimestampmock: 2025-11-10T12:19:44.045554705Z + restimestampmock: 2025-11-10T12:19:44.045792753Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-940 +spec: + metadata: + connID: "310" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777184 + reqtimestampmock: 2025-11-10T12:19:44.045898572Z + restimestampmock: 2025-11-10T12:19:44.046087361Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-941 +spec: + metadata: + connID: "310" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777184 + reqtimestampmock: 2025-11-10T12:19:44.046320349Z + restimestampmock: 2025-11-10T12:19:44.046423127Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-942 +spec: + metadata: + connID: "310" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='bad_addr_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a + unsigned: false + - type: 253 + name: username + value: bad_addr_user + unsigned: false + - type: 253 + name: email + value: bad_addr_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$WIStSA6GULhzFQmz$61c905d0834462278d321021aa4e219ae359e00178f5bb4fa876b01d18562db9b3ddd3c8c19c6c23d0d64c138e87b1557e18894f4a084b428dfeccb3c98c7522 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777184 + reqtimestampmock: 2025-11-10T12:19:44.046648836Z + restimestampmock: 2025-11-10T12:19:44.047056332Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-943 +spec: + metadata: + connID: "312" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [21, 202, 68, 212, 66, 179, 22, 132, 116, 222, 61, 220, 16, 33, 141, 76, 28, 138, 50, 238, 162, 143, 253, 220, 85, 194, 128, 132, 131, 225, 55, 42] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 291 + auth_plugin_data: [14, 74, 100, 16, 30, 107, 4, 71, 123, 126, 93, 101, 119, 26, 113, 15, 105, 68, 13, 126, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777185 + reqtimestampmock: 2025-11-10T12:19:45.546369984Z + restimestampmock: 2025-11-10T12:19:45.552355294Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-944 +spec: + metadata: + connID: "312" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777185 + reqtimestampmock: 2025-11-10T12:19:45.552513242Z + restimestampmock: 2025-11-10T12:19:45.552672821Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-945 +spec: + metadata: + connID: "312" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777185 + reqtimestampmock: 2025-11-10T12:19:45.552786509Z + restimestampmock: 2025-11-10T12:19:45.552903659Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-946 +spec: + metadata: + connID: "312" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777185 + reqtimestampmock: 2025-11-10T12:19:45.552997118Z + restimestampmock: 2025-11-10T12:19:45.553140637Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-947 +spec: + metadata: + connID: "312" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777185 + reqtimestampmock: 2025-11-10T12:19:45.553307265Z + restimestampmock: 2025-11-10T12:19:45.553743372Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-948 +spec: + metadata: + connID: "312" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777185 + reqtimestampmock: 2025-11-10T12:19:45.553909611Z + restimestampmock: 2025-11-10T12:19:45.554185458Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-949 +spec: + metadata: + connID: "312" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777185 + reqtimestampmock: 2025-11-10T12:19:45.554317837Z + restimestampmock: 2025-11-10T12:19:45.554647414Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-950 +spec: + metadata: + connID: "312" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777185 + reqtimestampmock: 2025-11-10T12:19:45.554743004Z + restimestampmock: 2025-11-10T12:19:45.567852473Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-951 +spec: + metadata: + connID: "314" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [203, 18, 103, 33, 190, 1, 168, 253, 222, 17, 7, 64, 249, 207, 166, 195, 236, 219, 66, 165, 181, 152, 20, 99, 180, 106, 186, 44, 191, 98, 195, 195] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 292 + auth_plugin_data: [105, 35, 11, 71, 65, 40, 94, 50, 27, 45, 37, 61, 98, 79, 88, 117, 82, 1, 110, 12, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777186 + reqtimestampmock: 2025-11-10T12:19:46.361632483Z + restimestampmock: 2025-11-10T12:19:46.3677932Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-952 +spec: + metadata: + connID: "314" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777186 + reqtimestampmock: 2025-11-10T12:19:46.367975749Z + restimestampmock: 2025-11-10T12:19:46.368217996Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-953 +spec: + metadata: + connID: "314" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777186 + reqtimestampmock: 2025-11-10T12:19:46.368341986Z + restimestampmock: 2025-11-10T12:19:46.368514524Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-954 +spec: + metadata: + connID: "314" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777186 + reqtimestampmock: 2025-11-10T12:19:46.368683344Z + restimestampmock: 2025-11-10T12:19:46.368806212Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-955 +spec: + metadata: + connID: "314" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 339 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('0381a46c-7bc7-4054-b914-322bb96c2cbf', 'temp_user_for_prod', 'temp_user_for_prod@example.in', 'scrypt:32768:8:1$eSMJHJJcI0x3lv0h$154cf49b80712b015a57469334a1d24dbfd6be4172a9b9de79dc4d377950ab26fb105ba51a8788bf3baa340ded19fc19e10b12b7c96b4dfa3cde7b375cc3a9c6', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777186 + reqtimestampmock: 2025-11-10T12:19:46.416926166Z + restimestampmock: 2025-11-10T12:19:46.417389653Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-956 +spec: + metadata: + connID: "314" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777186 + reqtimestampmock: 2025-11-10T12:19:46.417568771Z + restimestampmock: 2025-11-10T12:19:46.435798697Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-957 +spec: + metadata: + connID: "316" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [231, 104, 57, 138, 213, 92, 164, 38, 168, 232, 154, 200, 24, 153, 128, 214, 155, 79, 175, 181, 165, 97, 38, 174, 142, 248, 243, 95, 64, 153, 7, 242] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 293 + auth_plugin_data: [60, 77, 69, 35, 24, 93, 19, 110, 102, 85, 10, 74, 24, 38, 58, 4, 122, 10, 88, 31, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777187 + reqtimestampmock: 2025-11-10T12:19:47.115245139Z + restimestampmock: 2025-11-10T12:19:47.121809683Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-958 +spec: + metadata: + connID: "316" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777187 + reqtimestampmock: 2025-11-10T12:19:47.122021002Z + restimestampmock: 2025-11-10T12:19:47.122299889Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-959 +spec: + metadata: + connID: "316" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777187 + reqtimestampmock: 2025-11-10T12:19:47.122453109Z + restimestampmock: 2025-11-10T12:19:47.122625197Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-960 +spec: + metadata: + connID: "316" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777187 + reqtimestampmock: 2025-11-10T12:19:47.122749067Z + restimestampmock: 2025-11-10T12:19:47.122808285Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-961 +spec: + metadata: + connID: "316" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 91 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='temp_user_for_prod' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 249 + sequence_id: 6 + values: + - type: 253 + name: id + value: 0381a46c-7bc7-4054-b914-322bb96c2cbf + unsigned: false + - type: 253 + name: username + value: temp_user_for_prod + unsigned: false + - type: 253 + name: email + value: temp_user_for_prod@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$eSMJHJJcI0x3lv0h$154cf49b80712b015a57469334a1d24dbfd6be4172a9b9de79dc4d377950ab26fb105ba51a8788bf3baa340ded19fc19e10b12b7c96b4dfa3cde7b375cc3a9c6 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777187 + reqtimestampmock: 2025-11-10T12:19:47.123265122Z + restimestampmock: 2025-11-10T12:19:47.123392111Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-962 +spec: + metadata: + connID: "318" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [166, 26, 154, 192, 84, 26, 67, 131, 112, 67, 235, 174, 197, 154, 134, 213, 36, 14, 218, 235, 115, 113, 233, 95, 116, 198, 255, 199, 39, 249, 231, 2] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 294 + auth_plugin_data: [6, 69, 22, 66, 37, 97, 107, 6, 88, 104, 80, 17, 38, 60, 11, 73, 121, 40, 37, 53, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777188 + reqtimestampmock: 2025-11-10T12:19:48.674670336Z + restimestampmock: 2025-11-10T12:19:48.680682275Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-963 +spec: + metadata: + connID: "318" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777188 + reqtimestampmock: 2025-11-10T12:19:48.680816394Z + restimestampmock: 2025-11-10T12:19:48.680999712Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-964 +spec: + metadata: + connID: "318" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777188 + reqtimestampmock: 2025-11-10T12:19:48.681138511Z + restimestampmock: 2025-11-10T12:19:48.681350379Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-965 +spec: + metadata: + connID: "318" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777188 + reqtimestampmock: 2025-11-10T12:19:48.681460159Z + restimestampmock: 2025-11-10T12:19:48.681585988Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-966 +spec: + metadata: + connID: "318" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='0381a46c-7bc7-4054-b914-322bb96c2cbf' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 0381a46c-7bc7-4054-b914-322bb96c2cbf + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777188 + reqtimestampmock: 2025-11-10T12:19:48.681753056Z + restimestampmock: 2025-11-10T12:19:48.682122043Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-967 +spec: + metadata: + connID: "318" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='0381a46c-7bc7-4054-b914-322bb96c2cbf' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777188 + reqtimestampmock: 2025-11-10T12:19:48.682375301Z + restimestampmock: 2025-11-10T12:19:48.682644079Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-968 +spec: + metadata: + connID: "318" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='0381a46c-7bc7-4054-b914-322bb96c2cbf' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777188 + reqtimestampmock: 2025-11-10T12:19:48.682784967Z + restimestampmock: 2025-11-10T12:19:48.683104964Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-969 +spec: + metadata: + connID: "318" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777188 + reqtimestampmock: 2025-11-10T12:19:48.683218875Z + restimestampmock: 2025-11-10T12:19:48.695851187Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-970 +spec: + metadata: + connID: "320" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [242, 253, 181, 131, 97, 64, 9, 161, 135, 104, 29, 63, 143, 74, 181, 154, 25, 190, 39, 236, 139, 177, 172, 149, 177, 124, 37, 21, 83, 91, 29, 17] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 296 + auth_plugin_data: [26, 5, 102, 127, 110, 71, 55, 34, 39, 115, 45, 97, 46, 118, 87, 47, 38, 28, 72, 109, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777189 + reqtimestampmock: 2025-11-10T12:19:49.513192679Z + restimestampmock: 2025-11-10T12:19:49.52016678Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-971 +spec: + metadata: + connID: "320" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777189 + reqtimestampmock: 2025-11-10T12:19:49.520394468Z + restimestampmock: 2025-11-10T12:19:49.520566647Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-972 +spec: + metadata: + connID: "320" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777189 + reqtimestampmock: 2025-11-10T12:19:49.520691225Z + restimestampmock: 2025-11-10T12:19:49.520844954Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-973 +spec: + metadata: + connID: "320" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777189 + reqtimestampmock: 2025-11-10T12:19:49.520949894Z + restimestampmock: 2025-11-10T12:19:49.521036142Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-974 +spec: + metadata: + connID: "320" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 337 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('148553f6-5ee8-4cde-a1e7-f266507ce08e', 'temp_user_for_res', 'temp_user_for_res@example.in', 'scrypt:32768:8:1$Canko5QD54LLKO11$16375046f64aed14c323dc6ed3295463874a4a80bbe9593ec8d4fca0a218cc58d7f195df3b965baa447df30779b9cc1306fe78923e8a0747507a7a2efa4f0892', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777189 + reqtimestampmock: 2025-11-10T12:19:49.569581944Z + restimestampmock: 2025-11-10T12:19:49.57005455Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-975 +spec: + metadata: + connID: "320" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777189 + reqtimestampmock: 2025-11-10T12:19:49.570168728Z + restimestampmock: 2025-11-10T12:19:49.583763994Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-976 +spec: + metadata: + connID: "322" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [219, 229, 157, 204, 4, 88, 90, 63, 195, 44, 33, 236, 112, 45, 121, 143, 61, 119, 251, 133, 57, 104, 83, 237, 231, 142, 53, 133, 43, 45, 26, 92] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 297 + auth_plugin_data: [75, 38, 8, 105, 79, 41, 80, 97, 75, 79, 111, 10, 87, 90, 60, 61, 76, 87, 64, 34, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777190 + reqtimestampmock: 2025-11-10T12:19:50.260963578Z + restimestampmock: 2025-11-10T12:19:50.267483612Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-977 +spec: + metadata: + connID: "322" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777190 + reqtimestampmock: 2025-11-10T12:19:50.267633881Z + restimestampmock: 2025-11-10T12:19:50.2678088Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-978 +spec: + metadata: + connID: "322" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777190 + reqtimestampmock: 2025-11-10T12:19:50.267911219Z + restimestampmock: 2025-11-10T12:19:50.268037618Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-979 +spec: + metadata: + connID: "322" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777190 + reqtimestampmock: 2025-11-10T12:19:50.268137277Z + restimestampmock: 2025-11-10T12:19:50.268236456Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-980 +spec: + metadata: + connID: "322" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 90 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='temp_user_for_res' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 247 + sequence_id: 6 + values: + - type: 253 + name: id + value: 148553f6-5ee8-4cde-a1e7-f266507ce08e + unsigned: false + - type: 253 + name: username + value: temp_user_for_res + unsigned: false + - type: 253 + name: email + value: temp_user_for_res@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Canko5QD54LLKO11$16375046f64aed14c323dc6ed3295463874a4a80bbe9593ec8d4fca0a218cc58d7f195df3b965baa447df30779b9cc1306fe78923e8a0747507a7a2efa4f0892 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777190 + reqtimestampmock: 2025-11-10T12:19:50.268430035Z + restimestampmock: 2025-11-10T12:19:50.268827561Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-981 +spec: + metadata: + connID: "324" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [42, 66, 143, 103, 87, 32, 218, 247, 248, 42, 17, 3, 154, 71, 78, 81, 85, 156, 208, 189, 100, 152, 17, 165, 186, 221, 247, 209, 89, 136, 94, 189] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 298 + auth_plugin_data: [25, 13, 6, 15, 11, 57, 39, 16, 48, 121, 92, 100, 41, 49, 108, 99, 71, 32, 86, 55, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777191 + reqtimestampmock: 2025-11-10T12:19:51.840026542Z + restimestampmock: 2025-11-10T12:19:51.846768006Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-982 +spec: + metadata: + connID: "324" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777191 + reqtimestampmock: 2025-11-10T12:19:51.846961263Z + restimestampmock: 2025-11-10T12:19:51.847162152Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-983 +spec: + metadata: + connID: "324" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777191 + reqtimestampmock: 2025-11-10T12:19:51.84734093Z + restimestampmock: 2025-11-10T12:19:51.847518399Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-984 +spec: + metadata: + connID: "324" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777191 + reqtimestampmock: 2025-11-10T12:19:51.847638738Z + restimestampmock: 2025-11-10T12:19:51.847746197Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-985 +spec: + metadata: + connID: "324" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='148553f6-5ee8-4cde-a1e7-f266507ce08e' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 148553f6-5ee8-4cde-a1e7-f266507ce08e + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777191 + reqtimestampmock: 2025-11-10T12:19:51.847942045Z + restimestampmock: 2025-11-10T12:19:51.848321822Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-986 +spec: + metadata: + connID: "324" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='148553f6-5ee8-4cde-a1e7-f266507ce08e' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777191 + reqtimestampmock: 2025-11-10T12:19:51.84845513Z + restimestampmock: 2025-11-10T12:19:51.848699658Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-987 +spec: + metadata: + connID: "324" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='148553f6-5ee8-4cde-a1e7-f266507ce08e' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777191 + reqtimestampmock: 2025-11-10T12:19:51.848820687Z + restimestampmock: 2025-11-10T12:19:51.849143745Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-988 +spec: + metadata: + connID: "324" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777191 + reqtimestampmock: 2025-11-10T12:19:51.849243174Z + restimestampmock: 2025-11-10T12:19:51.860878596Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-989 +spec: + metadata: + connID: "326" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [152, 178, 70, 12, 54, 64, 14, 237, 138, 104, 145, 115, 128, 232, 120, 204, 97, 18, 29, 196, 73, 142, 154, 189, 245, 115, 133, 35, 3, 48, 102, 208] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 299 + auth_plugin_data: [42, 7, 15, 49, 117, 96, 34, 54, 76, 85, 21, 89, 78, 47, 56, 60, 34, 76, 1, 48, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777192 + reqtimestampmock: 2025-11-10T12:19:52.650625731Z + restimestampmock: 2025-11-10T12:19:52.656941429Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-990 +spec: + metadata: + connID: "326" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777192 + reqtimestampmock: 2025-11-10T12:19:52.657084388Z + restimestampmock: 2025-11-10T12:19:52.657268626Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-991 +spec: + metadata: + connID: "326" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777192 + reqtimestampmock: 2025-11-10T12:19:52.657397204Z + restimestampmock: 2025-11-10T12:19:52.657569323Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-992 +spec: + metadata: + connID: "326" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777192 + reqtimestampmock: 2025-11-10T12:19:52.657706282Z + restimestampmock: 2025-11-10T12:19:52.657801581Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-993 +spec: + metadata: + connID: "326" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 337 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('042f99f1-0e36-466b-a245-da494416f551', 'temp_user_for_rel', 'temp_user_for_rel@example.in', 'scrypt:32768:8:1$Ft3ECOpI8jHeATsU$6423a2d51e6a4f4a439420287cec2d24b17010fa26a53838242c08c9786f9818623b4a4ae6e95f1aa5f31435fd08feea13001e1756f1afdc0f7693d61be1c019', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777192 + reqtimestampmock: 2025-11-10T12:19:52.7065389Z + restimestampmock: 2025-11-10T12:19:52.706994497Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-994 +spec: + metadata: + connID: "326" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777192 + reqtimestampmock: 2025-11-10T12:19:52.707184235Z + restimestampmock: 2025-11-10T12:19:52.719742749Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-995 +spec: + metadata: + connID: "328" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [60, 108, 60, 113, 184, 248, 50, 57, 30, 176, 142, 81, 73, 99, 104, 88, 32, 21, 183, 198, 4, 41, 244, 153, 180, 6, 121, 199, 234, 115, 26, 32] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 300 + auth_plugin_data: [16, 71, 35, 27, 28, 61, 53, 73, 19, 86, 80, 96, 109, 46, 78, 14, 56, 116, 75, 103, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777193 + reqtimestampmock: 2025-11-10T12:19:53.430993806Z + restimestampmock: 2025-11-10T12:19:53.437112235Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-996 +spec: + metadata: + connID: "328" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777193 + reqtimestampmock: 2025-11-10T12:19:53.437268463Z + restimestampmock: 2025-11-10T12:19:53.437448032Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-997 +spec: + metadata: + connID: "328" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777193 + reqtimestampmock: 2025-11-10T12:19:53.437582801Z + restimestampmock: 2025-11-10T12:19:53.43771752Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-998 +spec: + metadata: + connID: "328" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777193 + reqtimestampmock: 2025-11-10T12:19:53.437818539Z + restimestampmock: 2025-11-10T12:19:53.437918258Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-999 +spec: + metadata: + connID: "328" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 90 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='temp_user_for_rel' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 247 + sequence_id: 6 + values: + - type: 253 + name: id + value: 042f99f1-0e36-466b-a245-da494416f551 + unsigned: false + - type: 253 + name: username + value: temp_user_for_rel + unsigned: false + - type: 253 + name: email + value: temp_user_for_rel@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Ft3ECOpI8jHeATsU$6423a2d51e6a4f4a439420287cec2d24b17010fa26a53838242c08c9786f9818623b4a4ae6e95f1aa5f31435fd08feea13001e1756f1afdc0f7693d61be1c019 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777193 + reqtimestampmock: 2025-11-10T12:19:53.438138766Z + restimestampmock: 2025-11-10T12:19:53.438540553Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1000 +spec: + metadata: + connID: "330" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [208, 121, 12, 216, 231, 46, 233, 26, 239, 14, 29, 77, 5, 216, 65, 208, 134, 237, 2, 117, 1, 52, 64, 237, 226, 31, 5, 175, 34, 18, 30, 41] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 302 + auth_plugin_data: [69, 35, 81, 28, 107, 2, 56, 46, 70, 100, 56, 60, 61, 3, 23, 123, 47, 13, 110, 1, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777195 + reqtimestampmock: 2025-11-10T12:19:54.99384479Z + restimestampmock: 2025-11-10T12:19:55.000031488Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1001 +spec: + metadata: + connID: "330" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777195 + reqtimestampmock: 2025-11-10T12:19:55.000248466Z + restimestampmock: 2025-11-10T12:19:55.000489274Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1002 +spec: + metadata: + connID: "330" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777195 + reqtimestampmock: 2025-11-10T12:19:55.000647533Z + restimestampmock: 2025-11-10T12:19:55.000805231Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1003 +spec: + metadata: + connID: "330" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777195 + reqtimestampmock: 2025-11-10T12:19:55.00096811Z + restimestampmock: 2025-11-10T12:19:55.001080399Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1004 +spec: + metadata: + connID: "330" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='042f99f1-0e36-466b-a245-da494416f551' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 042f99f1-0e36-466b-a245-da494416f551 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777195 + reqtimestampmock: 2025-11-10T12:19:55.001302697Z + restimestampmock: 2025-11-10T12:19:55.001674013Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1005 +spec: + metadata: + connID: "330" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='042f99f1-0e36-466b-a245-da494416f551' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777195 + reqtimestampmock: 2025-11-10T12:19:55.001884153Z + restimestampmock: 2025-11-10T12:19:55.00211898Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1006 +spec: + metadata: + connID: "330" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='042f99f1-0e36-466b-a245-da494416f551' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777195 + reqtimestampmock: 2025-11-10T12:19:55.002304638Z + restimestampmock: 2025-11-10T12:19:55.002657406Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1007 +spec: + metadata: + connID: "330" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777195 + reqtimestampmock: 2025-11-10T12:19:55.002770015Z + restimestampmock: 2025-11-10T12:19:55.017753238Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1008 +spec: + metadata: + connID: "332" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [124, 138, 112, 43, 111, 92, 190, 37, 199, 69, 5, 222, 93, 3, 254, 67, 74, 10, 28, 189, 152, 123, 106, 65, 200, 116, 40, 113, 51, 172, 124, 254] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 303 + auth_plugin_data: [11, 113, 37, 45, 27, 51, 46, 34, 6, 105, 51, 17, 72, 56, 82, 114, 28, 85, 86, 93, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777197 + reqtimestampmock: 2025-11-10T12:19:57.402771217Z + restimestampmock: 2025-11-10T12:19:57.412336947Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1009 +spec: + metadata: + connID: "332" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777197 + reqtimestampmock: 2025-11-10T12:19:57.412601865Z + restimestampmock: 2025-11-10T12:19:57.412809193Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1010 +spec: + metadata: + connID: "332" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777197 + reqtimestampmock: 2025-11-10T12:19:57.412939872Z + restimestampmock: 2025-11-10T12:19:57.41310633Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1011 +spec: + metadata: + connID: "332" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777197 + reqtimestampmock: 2025-11-10T12:19:57.413257469Z + restimestampmock: 2025-11-10T12:19:57.413380097Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1012 +spec: + metadata: + connID: "332" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 325 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('254e2932-7422-46fb-81f5-9f9c3b854f14', 'kavita_pune', 'kavita_pune@example.in', 'scrypt:32768:8:1$AyS5f49TGIKiTEbo$0bb5bec2227f8f1feff70fdd048285b289e6944425143a4f16426e6f0d2746660d9b14e3c6b2888d45ef0f6b28f4bb77828eb414afa06855e5424f92785fd9f5', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777197 + reqtimestampmock: 2025-11-10T12:19:57.465464509Z + restimestampmock: 2025-11-10T12:19:57.465943465Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1013 +spec: + metadata: + connID: "332" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777197 + reqtimestampmock: 2025-11-10T12:19:57.466063004Z + restimestampmock: 2025-11-10T12:19:57.477814246Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1014 +spec: + metadata: + connID: "334" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [201, 173, 241, 75, 110, 180, 48, 24, 84, 228, 94, 172, 148, 219, 232, 26, 8, 93, 18, 138, 70, 48, 49, 109, 43, 199, 62, 152, 143, 212, 237, 132] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 304 + auth_plugin_data: [32, 112, 17, 65, 107, 18, 6, 80, 14, 6, 75, 81, 61, 123, 8, 127, 86, 54, 86, 89, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777198 + reqtimestampmock: 2025-11-10T12:19:58.367429262Z + restimestampmock: 2025-11-10T12:19:58.37361275Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1015 +spec: + metadata: + connID: "334" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777198 + reqtimestampmock: 2025-11-10T12:19:58.373746499Z + restimestampmock: 2025-11-10T12:19:58.373908247Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1016 +spec: + metadata: + connID: "334" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777198 + reqtimestampmock: 2025-11-10T12:19:58.374015716Z + restimestampmock: 2025-11-10T12:19:58.374266294Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1017 +spec: + metadata: + connID: "334" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777198 + reqtimestampmock: 2025-11-10T12:19:58.374353243Z + restimestampmock: 2025-11-10T12:19:58.374489663Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1018 +spec: + metadata: + connID: "334" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 84 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='kavita_pune' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 235 + sequence_id: 6 + values: + - type: 253 + name: id + value: 254e2932-7422-46fb-81f5-9f9c3b854f14 + unsigned: false + - type: 253 + name: username + value: kavita_pune + unsigned: false + - type: 253 + name: email + value: kavita_pune@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$AyS5f49TGIKiTEbo$0bb5bec2227f8f1feff70fdd048285b289e6944425143a4f16426e6f0d2746660d9b14e3c6b2888d45ef0f6b28f4bb77828eb414afa06855e5424f92785fd9f5 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777198 + reqtimestampmock: 2025-11-10T12:19:58.374629041Z + restimestampmock: 2025-11-10T12:19:58.375064708Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1019 +spec: + metadata: + connID: "336" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [182, 33, 174, 38, 116, 94, 68, 217, 195, 149, 156, 38, 173, 231, 240, 114, 173, 12, 60, 252, 110, 73, 139, 69, 53, 33, 184, 6, 191, 195, 225, 63] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 306 + auth_plugin_data: [75, 49, 95, 124, 42, 62, 91, 80, 28, 114, 7, 115, 110, 33, 57, 103, 14, 44, 5, 70, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.167705712Z + restimestampmock: 2025-11-10T12:19:59.177037634Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1020 +spec: + metadata: + connID: "336" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.177253282Z + restimestampmock: 2025-11-10T12:19:59.177418201Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1021 +spec: + metadata: + connID: "336" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.177556399Z + restimestampmock: 2025-11-10T12:19:59.177702769Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1022 +spec: + metadata: + connID: "336" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.177809317Z + restimestampmock: 2025-11-10T12:19:59.177904607Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1023 +spec: + metadata: + connID: "336" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='254e2932-7422-46fb-81f5-9f9c3b854f14' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 254e2932-7422-46fb-81f5-9f9c3b854f14 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.178073216Z + restimestampmock: 2025-11-10T12:19:59.178357633Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1024 +spec: + metadata: + connID: "336" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 275 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('7804329b-59a5-4b0b-bc59-386a890cb09d','254e2932-7422-46fb-81f5-9f9c3b854f14','Flat 5, Koregaon Park',NULL,'Pune','Maharashtra','411001','IN','+919890098900',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.17862145Z + restimestampmock: 2025-11-10T12:19:59.178928728Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1025 +spec: + metadata: + connID: "336" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='254e2932-7422-46fb-81f5-9f9c3b854f14' AND id<>'7804329b-59a5-4b0b-bc59-386a890cb09d' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.179099057Z + restimestampmock: 2025-11-10T12:19:59.179376424Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1026 +spec: + metadata: + connID: "336" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.179493543Z + restimestampmock: 2025-11-10T12:19:59.19174172Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1027 +spec: + metadata: + connID: "338" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [28, 171, 216, 218, 126, 38, 104, 198, 33, 97, 22, 248, 245, 20, 133, 15, 189, 93, 149, 115, 55, 59, 169, 255, 60, 79, 31, 50, 244, 134, 131, 148] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 307 + auth_plugin_data: [5, 37, 105, 56, 71, 100, 68, 1, 83, 95, 103, 105, 19, 65, 117, 61, 114, 85, 121, 98, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.906914136Z + restimestampmock: 2025-11-10T12:19:59.91355089Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1028 +spec: + metadata: + connID: "338" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.913741319Z + restimestampmock: 2025-11-10T12:19:59.913933367Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1029 +spec: + metadata: + connID: "338" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.914053756Z + restimestampmock: 2025-11-10T12:19:59.914233055Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1030 +spec: + metadata: + connID: "338" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.914349284Z + restimestampmock: 2025-11-10T12:19:59.914458902Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1031 +spec: + metadata: + connID: "338" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='254e2932-7422-46fb-81f5-9f9c3b854f14' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 254e2932-7422-46fb-81f5-9f9c3b854f14 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.914880069Z + restimestampmock: 2025-11-10T12:19:59.914972428Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1032 +spec: + metadata: + connID: "338" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='254e2932-7422-46fb-81f5-9f9c3b854f14' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.915216906Z + restimestampmock: 2025-11-10T12:19:59.915645324Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1033 +spec: + metadata: + connID: "338" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='254e2932-7422-46fb-81f5-9f9c3b854f14' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.915793811Z + restimestampmock: 2025-11-10T12:19:59.916103989Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1034 +spec: + metadata: + connID: "338" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777199 + reqtimestampmock: 2025-11-10T12:19:59.916237838Z + restimestampmock: 2025-11-10T12:19:59.929633845Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1035 +spec: + metadata: + connID: "340" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [119, 34, 122, 108, 137, 80, 225, 4, 121, 23, 169, 251, 152, 33, 35, 185, 48, 216, 164, 15, 52, 93, 231, 130, 154, 163, 248, 81, 89, 247, 74, 151] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 308 + auth_plugin_data: [51, 70, 26, 24, 8, 75, 9, 119, 56, 93, 127, 124, 126, 103, 34, 45, 107, 78, 25, 96, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777201 + reqtimestampmock: 2025-11-10T12:20:01.939906484Z + restimestampmock: 2025-11-10T12:20:01.946332331Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1036 +spec: + metadata: + connID: "340" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777201 + reqtimestampmock: 2025-11-10T12:20:01.94650666Z + restimestampmock: 2025-11-10T12:20:01.946672499Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1037 +spec: + metadata: + connID: "340" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777201 + reqtimestampmock: 2025-11-10T12:20:01.946781538Z + restimestampmock: 2025-11-10T12:20:01.946916396Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1038 +spec: + metadata: + connID: "340" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777201 + reqtimestampmock: 2025-11-10T12:20:01.947010755Z + restimestampmock: 2025-11-10T12:20:01.947110364Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1039 +spec: + metadata: + connID: "340" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 327 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('17dfef3d-5760-456f-a044-ca2226645b00', 'amit_kolkata', 'amit_kolkata@example.in', 'scrypt:32768:8:1$REOf4fvi2sGQdNNe$325cff5bcc52498aa618de817f7c77f590ed66b9751e07848eba1371abf39a50f6c74b0868daa06642f0ed7541e3dba962d5d81cdec55c4d165b93d4e53a3f7c', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777202 + reqtimestampmock: 2025-11-10T12:20:01.996364899Z + restimestampmock: 2025-11-10T12:20:02.001614955Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1040 +spec: + metadata: + connID: "340" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777202 + reqtimestampmock: 2025-11-10T12:20:02.001853263Z + restimestampmock: 2025-11-10T12:20:02.013675153Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1041 +spec: + metadata: + connID: "342" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [21, 65, 5, 92, 131, 142, 50, 243, 85, 203, 131, 237, 241, 134, 100, 78, 228, 193, 184, 113, 61, 57, 214, 51, 76, 99, 124, 50, 117, 230, 135, 131] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 309 + auth_plugin_data: [32, 11, 98, 43, 5, 67, 71, 121, 65, 66, 106, 122, 101, 50, 32, 69, 74, 109, 21, 9, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777202 + reqtimestampmock: 2025-11-10T12:20:02.822495563Z + restimestampmock: 2025-11-10T12:20:02.829527603Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1042 +spec: + metadata: + connID: "342" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777202 + reqtimestampmock: 2025-11-10T12:20:02.829679802Z + restimestampmock: 2025-11-10T12:20:02.82989147Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1043 +spec: + metadata: + connID: "342" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777202 + reqtimestampmock: 2025-11-10T12:20:02.830015119Z + restimestampmock: 2025-11-10T12:20:02.830165758Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1044 +spec: + metadata: + connID: "342" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777202 + reqtimestampmock: 2025-11-10T12:20:02.830311647Z + restimestampmock: 2025-11-10T12:20:02.830411956Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1045 +spec: + metadata: + connID: "342" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='amit_kolkata' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 237 + sequence_id: 6 + values: + - type: 253 + name: id + value: 17dfef3d-5760-456f-a044-ca2226645b00 + unsigned: false + - type: 253 + name: username + value: amit_kolkata + unsigned: false + - type: 253 + name: email + value: amit_kolkata@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$REOf4fvi2sGQdNNe$325cff5bcc52498aa618de817f7c77f590ed66b9751e07848eba1371abf39a50f6c74b0868daa06642f0ed7541e3dba962d5d81cdec55c4d165b93d4e53a3f7c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777202 + reqtimestampmock: 2025-11-10T12:20:02.830583085Z + restimestampmock: 2025-11-10T12:20:02.830945362Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1046 +spec: + metadata: + connID: "344" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [9, 10, 166, 87, 17, 214, 87, 244, 113, 56, 233, 104, 26, 106, 89, 139, 197, 72, 17, 115, 178, 129, 163, 49, 67, 13, 139, 57, 129, 180, 39, 109] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 310 + auth_plugin_data: [121, 86, 127, 53, 42, 99, 16, 17, 11, 126, 66, 12, 19, 1, 80, 123, 57, 60, 33, 81, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777203 + reqtimestampmock: 2025-11-10T12:20:03.584785104Z + restimestampmock: 2025-11-10T12:20:03.591656266Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1047 +spec: + metadata: + connID: "344" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777203 + reqtimestampmock: 2025-11-10T12:20:03.591869825Z + restimestampmock: 2025-11-10T12:20:03.592039743Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1048 +spec: + metadata: + connID: "344" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777203 + reqtimestampmock: 2025-11-10T12:20:03.592120092Z + restimestampmock: 2025-11-10T12:20:03.59227757Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1049 +spec: + metadata: + connID: "344" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777203 + reqtimestampmock: 2025-11-10T12:20:03.592382801Z + restimestampmock: 2025-11-10T12:20:03.592481259Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1050 +spec: + metadata: + connID: "344" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='17dfef3d-5760-456f-a044-ca2226645b00' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 17dfef3d-5760-456f-a044-ca2226645b00 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777203 + reqtimestampmock: 2025-11-10T12:20:03.592678438Z + restimestampmock: 2025-11-10T12:20:03.593062724Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1051 +spec: + metadata: + connID: "344" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 272 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('9c7ca41a-0722-4b62-ae20-0ec8d3ce0ce5','17dfef3d-5760-456f-a044-ca2226645b00','15, Park Street',NULL,'Kolkata','West Bengal','700016','IN','+919830098300',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777203 + reqtimestampmock: 2025-11-10T12:20:03.593250663Z + restimestampmock: 2025-11-10T12:20:03.59356277Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1052 +spec: + metadata: + connID: "344" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='17dfef3d-5760-456f-a044-ca2226645b00' AND id<>'9c7ca41a-0722-4b62-ae20-0ec8d3ce0ce5' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777203 + reqtimestampmock: 2025-11-10T12:20:03.593690019Z + restimestampmock: 2025-11-10T12:20:03.593953417Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1053 +spec: + metadata: + connID: "344" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777203 + reqtimestampmock: 2025-11-10T12:20:03.594058576Z + restimestampmock: 2025-11-10T12:20:03.605630529Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1054 +spec: + metadata: + connID: "346" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [157, 240, 250, 164, 220, 195, 50, 141, 174, 65, 28, 189, 43, 133, 40, 211, 214, 118, 250, 40, 44, 135, 42, 5, 142, 98, 34, 77, 248, 95, 1, 80] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 312 + auth_plugin_data: [12, 72, 12, 115, 69, 62, 66, 66, 87, 81, 77, 123, 11, 28, 79, 105, 28, 100, 24, 90, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777204 + reqtimestampmock: 2025-11-10T12:20:04.403088754Z + restimestampmock: 2025-11-10T12:20:04.409199513Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1055 +spec: + metadata: + connID: "346" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777204 + reqtimestampmock: 2025-11-10T12:20:04.409385811Z + restimestampmock: 2025-11-10T12:20:04.4095494Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1056 +spec: + metadata: + connID: "346" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777204 + reqtimestampmock: 2025-11-10T12:20:04.409712178Z + restimestampmock: 2025-11-10T12:20:04.409837827Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1057 +spec: + metadata: + connID: "346" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777204 + reqtimestampmock: 2025-11-10T12:20:04.409952136Z + restimestampmock: 2025-11-10T12:20:04.410087445Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1058 +spec: + metadata: + connID: "346" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='17dfef3d-5760-456f-a044-ca2226645b00' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 17dfef3d-5760-456f-a044-ca2226645b00 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777204 + reqtimestampmock: 2025-11-10T12:20:04.410282884Z + restimestampmock: 2025-11-10T12:20:04.41073006Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1059 +spec: + metadata: + connID: "346" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='17dfef3d-5760-456f-a044-ca2226645b00' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777204 + reqtimestampmock: 2025-11-10T12:20:04.410881419Z + restimestampmock: 2025-11-10T12:20:04.411276685Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1060 +spec: + metadata: + connID: "346" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='17dfef3d-5760-456f-a044-ca2226645b00' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777204 + reqtimestampmock: 2025-11-10T12:20:04.411407644Z + restimestampmock: 2025-11-10T12:20:04.411655112Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1061 +spec: + metadata: + connID: "346" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777204 + reqtimestampmock: 2025-11-10T12:20:04.411768631Z + restimestampmock: 2025-11-10T12:20:04.42368529Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1062 +spec: + metadata: + connID: "348" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [50, 145, 37, 40, 118, 107, 127, 81, 22, 6, 17, 117, 160, 187, 30, 94, 99, 51, 186, 129, 207, 23, 131, 214, 157, 35, 124, 66, 68, 213, 61, 192] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 313 + auth_plugin_data: [56, 92, 28, 10, 39, 114, 127, 94, 8, 61, 65, 89, 57, 117, 20, 39, 46, 26, 124, 82, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777206 + reqtimestampmock: 2025-11-10T12:20:06.263346032Z + restimestampmock: 2025-11-10T12:20:06.269592259Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1063 +spec: + metadata: + connID: "348" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777206 + reqtimestampmock: 2025-11-10T12:20:06.269800146Z + restimestampmock: 2025-11-10T12:20:06.269970376Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1064 +spec: + metadata: + connID: "348" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777206 + reqtimestampmock: 2025-11-10T12:20:06.270107704Z + restimestampmock: 2025-11-10T12:20:06.270297253Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1065 +spec: + metadata: + connID: "348" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777206 + reqtimestampmock: 2025-11-10T12:20:06.270412101Z + restimestampmock: 2025-11-10T12:20:06.270511451Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1066 +spec: + metadata: + connID: "348" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 333 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('25712cc5-6d68-48ea-8437-333cf1ad5cb2', 'lakshmi_chennai', 'lakshmi_chennai@example.in', 'scrypt:32768:8:1$OAmVpujq0k9C6sAj$22953a7d7a15978128b13c1c85c73bc20d9b50ade2e87fde87d5b4d76be0e81fb95f973dceeee08f7bf1351a96012eb97650f709b9da9f2886bf0341ba1521cc', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777206 + reqtimestampmock: 2025-11-10T12:20:06.31932218Z + restimestampmock: 2025-11-10T12:20:06.319798245Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1067 +spec: + metadata: + connID: "348" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777206 + reqtimestampmock: 2025-11-10T12:20:06.319951734Z + restimestampmock: 2025-11-10T12:20:06.331693856Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1068 +spec: + metadata: + connID: "350" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [212, 173, 98, 225, 198, 220, 181, 161, 182, 127, 8, 143, 146, 76, 216, 162, 200, 52, 82, 75, 94, 134, 53, 125, 197, 203, 225, 132, 167, 136, 112, 139] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 314 + auth_plugin_data: [116, 8, 112, 46, 40, 121, 48, 59, 113, 70, 90, 83, 67, 90, 37, 53, 1, 13, 89, 115, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.028095073Z + restimestampmock: 2025-11-10T12:20:07.034646058Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1069 +spec: + metadata: + connID: "350" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.034824327Z + restimestampmock: 2025-11-10T12:20:07.035005005Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1070 +spec: + metadata: + connID: "350" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.035126804Z + restimestampmock: 2025-11-10T12:20:07.035285453Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1071 +spec: + metadata: + connID: "350" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.035415501Z + restimestampmock: 2025-11-10T12:20:07.03552874Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1072 +spec: + metadata: + connID: "350" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 88 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='lakshmi_chennai' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 243 + sequence_id: 6 + values: + - type: 253 + name: id + value: 25712cc5-6d68-48ea-8437-333cf1ad5cb2 + unsigned: false + - type: 253 + name: username + value: lakshmi_chennai + unsigned: false + - type: 253 + name: email + value: lakshmi_chennai@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$OAmVpujq0k9C6sAj$22953a7d7a15978128b13c1c85c73bc20d9b50ade2e87fde87d5b4d76be0e81fb95f973dceeee08f7bf1351a96012eb97650f709b9da9f2886bf0341ba1521cc + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.035729479Z + restimestampmock: 2025-11-10T12:20:07.037806262Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1073 +spec: + metadata: + connID: "352" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [197, 58, 224, 114, 238, 47, 187, 79, 59, 212, 132, 167, 160, 6, 192, 227, 149, 155, 236, 70, 105, 113, 88, 118, 208, 124, 139, 249, 150, 96, 190, 204] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 315 + auth_plugin_data: [13, 17, 119, 46, 55, 27, 66, 62, 49, 1, 11, 44, 14, 12, 87, 111, 16, 104, 72, 118, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.77679062Z + restimestampmock: 2025-11-10T12:20:07.783315205Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1074 +spec: + metadata: + connID: "352" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.783511283Z + restimestampmock: 2025-11-10T12:20:07.783683783Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1075 +spec: + metadata: + connID: "352" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.783883471Z + restimestampmock: 2025-11-10T12:20:07.784067699Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1076 +spec: + metadata: + connID: "352" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.784211818Z + restimestampmock: 2025-11-10T12:20:07.784324277Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1077 +spec: + metadata: + connID: "352" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='25712cc5-6d68-48ea-8437-333cf1ad5cb2' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 25712cc5-6d68-48ea-8437-333cf1ad5cb2 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.784487106Z + restimestampmock: 2025-11-10T12:20:07.784880202Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1078 +spec: + metadata: + connID: "352" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 270 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('5d4c1ed2-bfe3-48b3-9941-c099827911e3','25712cc5-6d68-48ea-8437-333cf1ad5cb2','No 10, T Nagar',NULL,'Chennai','Tamil Nadu','600017','IN','+919840098400',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.785063021Z + restimestampmock: 2025-11-10T12:20:07.785399168Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1079 +spec: + metadata: + connID: "352" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='25712cc5-6d68-48ea-8437-333cf1ad5cb2' AND id<>'5d4c1ed2-bfe3-48b3-9941-c099827911e3' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.785620236Z + restimestampmock: 2025-11-10T12:20:07.785898284Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1080 +spec: + metadata: + connID: "352" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777207 + reqtimestampmock: 2025-11-10T12:20:07.786038712Z + restimestampmock: 2025-11-10T12:20:07.79463405Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1081 +spec: + metadata: + connID: "354" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [112, 41, 7, 62, 132, 244, 254, 221, 43, 5, 17, 27, 192, 203, 220, 247, 96, 22, 150, 101, 180, 74, 89, 165, 93, 174, 245, 79, 157, 57, 59, 145] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 316 + auth_plugin_data: [79, 114, 89, 101, 115, 46, 59, 121, 51, 32, 75, 113, 58, 114, 83, 37, 115, 1, 47, 92, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777208 + reqtimestampmock: 2025-11-10T12:20:08.619564885Z + restimestampmock: 2025-11-10T12:20:08.625681694Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1082 +spec: + metadata: + connID: "354" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777208 + reqtimestampmock: 2025-11-10T12:20:08.625821813Z + restimestampmock: 2025-11-10T12:20:08.625996641Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1083 +spec: + metadata: + connID: "354" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777208 + reqtimestampmock: 2025-11-10T12:20:08.626171869Z + restimestampmock: 2025-11-10T12:20:08.626309579Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1084 +spec: + metadata: + connID: "354" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777208 + reqtimestampmock: 2025-11-10T12:20:08.626411699Z + restimestampmock: 2025-11-10T12:20:08.626501867Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1085 +spec: + metadata: + connID: "354" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='25712cc5-6d68-48ea-8437-333cf1ad5cb2' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 25712cc5-6d68-48ea-8437-333cf1ad5cb2 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777208 + reqtimestampmock: 2025-11-10T12:20:08.626708416Z + restimestampmock: 2025-11-10T12:20:08.627064862Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1086 +spec: + metadata: + connID: "354" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='25712cc5-6d68-48ea-8437-333cf1ad5cb2' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777208 + reqtimestampmock: 2025-11-10T12:20:08.627209551Z + restimestampmock: 2025-11-10T12:20:08.627678867Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1087 +spec: + metadata: + connID: "354" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='25712cc5-6d68-48ea-8437-333cf1ad5cb2' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777208 + reqtimestampmock: 2025-11-10T12:20:08.627928395Z + restimestampmock: 2025-11-10T12:20:08.628208243Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1088 +spec: + metadata: + connID: "354" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777208 + reqtimestampmock: 2025-11-10T12:20:08.628364832Z + restimestampmock: 2025-11-10T12:20:08.640732558Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1089 +spec: + metadata: + connID: "356" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [95, 247, 181, 133, 96, 181, 171, 204, 142, 127, 141, 190, 14, 92, 195, 121, 150, 63, 26, 35, 99, 234, 246, 107, 8, 54, 212, 98, 162, 108, 10, 247] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 318 + auth_plugin_data: [59, 82, 94, 93, 1, 88, 91, 53, 37, 47, 17, 41, 91, 18, 63, 126, 92, 27, 61, 30, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777210 + reqtimestampmock: 2025-11-10T12:20:10.698760424Z + restimestampmock: 2025-11-10T12:20:10.70525967Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1090 +spec: + metadata: + connID: "356" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777210 + reqtimestampmock: 2025-11-10T12:20:10.705418568Z + restimestampmock: 2025-11-10T12:20:10.705586777Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1091 +spec: + metadata: + connID: "356" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777210 + reqtimestampmock: 2025-11-10T12:20:10.705687046Z + restimestampmock: 2025-11-10T12:20:10.705829405Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1092 +spec: + metadata: + connID: "356" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777210 + reqtimestampmock: 2025-11-10T12:20:10.705937934Z + restimestampmock: 2025-11-10T12:20:10.706036543Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1093 +spec: + metadata: + connID: "356" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 323 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('e2fdc4cd-cd10-43c3-b083-5b4050c1ab43', 'rajesh_hyd', 'rajesh_hyd@example.in', 'scrypt:32768:8:1$oirPyMNLjiB6HN8d$0a6225e526b2033e8d525819b4e9acbf97f31cd360bb2fd4b13adad46d5f599032543f2d6ff0be078114c28eaa5db56c87351cef7545e217a8ed45a14b505219', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777210 + reqtimestampmock: 2025-11-10T12:20:10.754286628Z + restimestampmock: 2025-11-10T12:20:10.754747613Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1094 +spec: + metadata: + connID: "356" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777210 + reqtimestampmock: 2025-11-10T12:20:10.754893462Z + restimestampmock: 2025-11-10T12:20:10.767622405Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1095 +spec: + metadata: + connID: "358" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [14, 153, 25, 125, 141, 57, 63, 63, 74, 165, 84, 219, 216, 17, 163, 173, 57, 160, 84, 181, 144, 207, 4, 50, 100, 210, 96, 100, 208, 62, 141, 25] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 319 + auth_plugin_data: [10, 25, 33, 6, 55, 32, 6, 93, 47, 97, 38, 65, 83, 70, 39, 79, 109, 55, 50, 72, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777211 + reqtimestampmock: 2025-11-10T12:20:11.460666402Z + restimestampmock: 2025-11-10T12:20:11.466679221Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1096 +spec: + metadata: + connID: "358" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777211 + reqtimestampmock: 2025-11-10T12:20:11.46685089Z + restimestampmock: 2025-11-10T12:20:11.467064658Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1097 +spec: + metadata: + connID: "358" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777211 + reqtimestampmock: 2025-11-10T12:20:11.467188607Z + restimestampmock: 2025-11-10T12:20:11.467366296Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1098 +spec: + metadata: + connID: "358" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777211 + reqtimestampmock: 2025-11-10T12:20:11.467473034Z + restimestampmock: 2025-11-10T12:20:11.467605753Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1099 +spec: + metadata: + connID: "358" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 83 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='rajesh_hyd' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 233 + sequence_id: 6 + values: + - type: 253 + name: id + value: e2fdc4cd-cd10-43c3-b083-5b4050c1ab43 + unsigned: false + - type: 253 + name: username + value: rajesh_hyd + unsigned: false + - type: 253 + name: email + value: rajesh_hyd@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$oirPyMNLjiB6HN8d$0a6225e526b2033e8d525819b4e9acbf97f31cd360bb2fd4b13adad46d5f599032543f2d6ff0be078114c28eaa5db56c87351cef7545e217a8ed45a14b505219 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777211 + reqtimestampmock: 2025-11-10T12:20:11.467792782Z + restimestampmock: 2025-11-10T12:20:11.468191208Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1100 +spec: + metadata: + connID: "360" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [88, 101, 38, 124, 16, 197, 194, 99, 2, 198, 229, 74, 236, 175, 176, 120, 189, 103, 246, 206, 196, 59, 78, 108, 161, 23, 13, 35, 76, 18, 216, 9] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 320 + auth_plugin_data: [115, 59, 35, 25, 51, 62, 39, 20, 30, 56, 26, 119, 94, 125, 106, 81, 27, 44, 120, 19, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777212 + reqtimestampmock: 2025-11-10T12:20:12.268048287Z + restimestampmock: 2025-11-10T12:20:12.274338944Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1101 +spec: + metadata: + connID: "360" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777212 + reqtimestampmock: 2025-11-10T12:20:12.274515982Z + restimestampmock: 2025-11-10T12:20:12.274719181Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1102 +spec: + metadata: + connID: "360" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777212 + reqtimestampmock: 2025-11-10T12:20:12.27484341Z + restimestampmock: 2025-11-10T12:20:12.275008308Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1103 +spec: + metadata: + connID: "360" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777212 + reqtimestampmock: 2025-11-10T12:20:12.275164007Z + restimestampmock: 2025-11-10T12:20:12.275263706Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1104 +spec: + metadata: + connID: "360" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='e2fdc4cd-cd10-43c3-b083-5b4050c1ab43' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: e2fdc4cd-cd10-43c3-b083-5b4050c1ab43 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777212 + reqtimestampmock: 2025-11-10T12:20:12.275435284Z + restimestampmock: 2025-11-10T12:20:12.275740383Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1105 +spec: + metadata: + connID: "360" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 281 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('1320d911-df66-4ba2-9487-538acb3f8028','e2fdc4cd-cd10-43c3-b083-5b4050c1ab43','Banjara Hills, Road No 1',NULL,'Hyderabad','Telangana','500034','IN','+919848012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777212 + reqtimestampmock: 2025-11-10T12:20:12.27592477Z + restimestampmock: 2025-11-10T12:20:12.276243678Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1106 +spec: + metadata: + connID: "360" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='e2fdc4cd-cd10-43c3-b083-5b4050c1ab43' AND id<>'1320d911-df66-4ba2-9487-538acb3f8028' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777212 + reqtimestampmock: 2025-11-10T12:20:12.276345927Z + restimestampmock: 2025-11-10T12:20:12.276641384Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1107 +spec: + metadata: + connID: "360" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777212 + reqtimestampmock: 2025-11-10T12:20:12.276744264Z + restimestampmock: 2025-11-10T12:20:12.289579725Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1108 +spec: + metadata: + connID: "362" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [226, 179, 31, 149, 18, 58, 178, 165, 110, 24, 93, 1, 150, 84, 1, 172, 184, 197, 239, 244, 109, 193, 124, 246, 238, 236, 138, 35, 204, 243, 34, 148] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 321 + auth_plugin_data: [37, 59, 63, 24, 1, 53, 100, 22, 127, 59, 39, 15, 3, 68, 58, 20, 9, 66, 110, 110, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777213 + reqtimestampmock: 2025-11-10T12:20:13.020131417Z + restimestampmock: 2025-11-10T12:20:13.026201566Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1109 +spec: + metadata: + connID: "362" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777213 + reqtimestampmock: 2025-11-10T12:20:13.026342106Z + restimestampmock: 2025-11-10T12:20:13.026499654Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1110 +spec: + metadata: + connID: "362" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777213 + reqtimestampmock: 2025-11-10T12:20:13.026671092Z + restimestampmock: 2025-11-10T12:20:13.026801721Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1111 +spec: + metadata: + connID: "362" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777213 + reqtimestampmock: 2025-11-10T12:20:13.02690956Z + restimestampmock: 2025-11-10T12:20:13.02699083Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1112 +spec: + metadata: + connID: "362" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='e2fdc4cd-cd10-43c3-b083-5b4050c1ab43' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: e2fdc4cd-cd10-43c3-b083-5b4050c1ab43 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777213 + reqtimestampmock: 2025-11-10T12:20:13.027132559Z + restimestampmock: 2025-11-10T12:20:13.027483526Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1113 +spec: + metadata: + connID: "362" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='e2fdc4cd-cd10-43c3-b083-5b4050c1ab43' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777213 + reqtimestampmock: 2025-11-10T12:20:13.027622085Z + restimestampmock: 2025-11-10T12:20:13.028347218Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1114 +spec: + metadata: + connID: "362" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='e2fdc4cd-cd10-43c3-b083-5b4050c1ab43' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777213 + reqtimestampmock: 2025-11-10T12:20:13.028491027Z + restimestampmock: 2025-11-10T12:20:13.028767005Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1115 +spec: + metadata: + connID: "362" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777213 + reqtimestampmock: 2025-11-10T12:20:13.028870144Z + restimestampmock: 2025-11-10T12:20:13.040495377Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1116 +spec: + metadata: + connID: "364" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [19, 239, 71, 20, 13, 187, 156, 5, 226, 173, 160, 231, 57, 172, 135, 116, 30, 36, 142, 30, 247, 78, 156, 187, 163, 13, 28, 51, 173, 139, 127, 24] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 324 + auth_plugin_data: [34, 66, 38, 94, 113, 11, 67, 5, 1, 98, 59, 51, 69, 108, 27, 13, 35, 40, 113, 57, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777220 + reqtimestampmock: 2025-11-10T12:20:20.773590718Z + restimestampmock: 2025-11-10T12:20:20.779578448Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1117 +spec: + metadata: + connID: "364" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777220 + reqtimestampmock: 2025-11-10T12:20:20.779715036Z + restimestampmock: 2025-11-10T12:20:20.779923255Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1118 +spec: + metadata: + connID: "364" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777220 + reqtimestampmock: 2025-11-10T12:20:20.780068593Z + restimestampmock: 2025-11-10T12:20:20.780257511Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1119 +spec: + metadata: + connID: "364" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777220 + reqtimestampmock: 2025-11-10T12:20:20.780357031Z + restimestampmock: 2025-11-10T12:20:20.78040627Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1120 +spec: + metadata: + connID: "364" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('a60a25ea-934f-48c7-85f1-ea694cbcbac6', 'bad_addr_order', 'bad_addr_order@example.in', 'scrypt:32768:8:1$pbh0axjdsSZs8UFc$9420b2f7f9295ca48b64369d00bd44c856dd134898c8e85c66b2f909d01da53360ecf7e010ab0f07e3e87dfbee6c33f4aef98793f097eb6ec4e19eb05d30689e', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777220 + reqtimestampmock: 2025-11-10T12:20:20.828188368Z + restimestampmock: 2025-11-10T12:20:20.828598455Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1121 +spec: + metadata: + connID: "364" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777220 + reqtimestampmock: 2025-11-10T12:20:20.828722064Z + restimestampmock: 2025-11-10T12:20:20.83751578Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1122 +spec: + metadata: + connID: "366" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [124, 56, 219, 38, 205, 255, 245, 241, 249, 16, 194, 117, 71, 113, 186, 198, 34, 249, 44, 227, 63, 80, 209, 144, 155, 203, 100, 110, 127, 39, 18, 56] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 325 + auth_plugin_data: [7, 26, 78, 35, 67, 46, 51, 42, 63, 124, 95, 54, 75, 58, 75, 122, 76, 18, 127, 63, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777221 + reqtimestampmock: 2025-11-10T12:20:21.513525774Z + restimestampmock: 2025-11-10T12:20:21.519654022Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1123 +spec: + metadata: + connID: "366" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777221 + reqtimestampmock: 2025-11-10T12:20:21.51983801Z + restimestampmock: 2025-11-10T12:20:21.520053469Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1124 +spec: + metadata: + connID: "366" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777221 + reqtimestampmock: 2025-11-10T12:20:21.520168048Z + restimestampmock: 2025-11-10T12:20:21.520291857Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1125 +spec: + metadata: + connID: "366" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777221 + reqtimestampmock: 2025-11-10T12:20:21.520412776Z + restimestampmock: 2025-11-10T12:20:21.520490495Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1126 +spec: + metadata: + connID: "366" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='bad_addr_order' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: a60a25ea-934f-48c7-85f1-ea694cbcbac6 + unsigned: false + - type: 253 + name: username + value: bad_addr_order + unsigned: false + - type: 253 + name: email + value: bad_addr_order@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$pbh0axjdsSZs8UFc$9420b2f7f9295ca48b64369d00bd44c856dd134898c8e85c66b2f909d01da53360ecf7e010ab0f07e3e87dfbee6c33f4aef98793f097eb6ec4e19eb05d30689e + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777221 + reqtimestampmock: 2025-11-10T12:20:21.520736843Z + restimestampmock: 2025-11-10T12:20:21.521098759Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1127 +spec: + metadata: + connID: "368" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [250, 216, 143, 232, 153, 63, 247, 68, 163, 165, 111, 82, 87, 166, 1, 99, 221, 219, 245, 66, 157, 153, 108, 65, 11, 23, 243, 113, 32, 158, 81, 148] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 326 + auth_plugin_data: [85, 104, 37, 107, 1, 32, 93, 63, 95, 114, 83, 3, 45, 51, 105, 26, 65, 104, 77, 114, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777222 + reqtimestampmock: 2025-11-10T12:20:22.991249944Z + restimestampmock: 2025-11-10T12:20:22.999860891Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1128 +spec: + metadata: + connID: "368" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777223 + reqtimestampmock: 2025-11-10T12:20:23.000077598Z + restimestampmock: 2025-11-10T12:20:23.000241888Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1129 +spec: + metadata: + connID: "368" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777223 + reqtimestampmock: 2025-11-10T12:20:23.000356587Z + restimestampmock: 2025-11-10T12:20:23.000481466Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1130 +spec: + metadata: + connID: "368" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777223 + reqtimestampmock: 2025-11-10T12:20:23.000610815Z + restimestampmock: 2025-11-10T12:20:23.000696044Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1131 +spec: + metadata: + connID: "368" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a60a25ea-934f-48c7-85f1-ea694cbcbac6' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a60a25ea-934f-48c7-85f1-ea694cbcbac6 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777223 + reqtimestampmock: 2025-11-10T12:20:23.000912932Z + restimestampmock: 2025-11-10T12:20:23.001323899Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1132 +spec: + metadata: + connID: "368" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='a60a25ea-934f-48c7-85f1-ea694cbcbac6' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777223 + reqtimestampmock: 2025-11-10T12:20:23.001708216Z + restimestampmock: 2025-11-10T12:20:23.001768915Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1133 +spec: + metadata: + connID: "368" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='a60a25ea-934f-48c7-85f1-ea694cbcbac6' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777223 + reqtimestampmock: 2025-11-10T12:20:23.001921954Z + restimestampmock: 2025-11-10T12:20:23.002268271Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1134 +spec: + metadata: + connID: "368" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777223 + reqtimestampmock: 2025-11-10T12:20:23.002387899Z + restimestampmock: 2025-11-10T12:20:23.012507565Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1135 +spec: + metadata: + connID: "370" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [90, 216, 39, 57, 141, 34, 110, 242, 135, 182, 173, 114, 171, 63, 126, 207, 30, 38, 182, 241, 99, 125, 248, 165, 13, 183, 167, 91, 42, 211, 221, 207] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 328 + auth_plugin_data: [84, 53, 12, 43, 113, 25, 117, 1, 4, 107, 22, 24, 31, 75, 19, 115, 17, 37, 42, 5, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.121383427Z + restimestampmock: 2025-11-10T12:20:25.127966562Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1136 +spec: + metadata: + connID: "370" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.128210179Z + restimestampmock: 2025-11-10T12:20:25.128412657Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1137 +spec: + metadata: + connID: "370" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.128607316Z + restimestampmock: 2025-11-10T12:20:25.128797784Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1138 +spec: + metadata: + connID: "370" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.128924073Z + restimestampmock: 2025-11-10T12:20:25.129945884Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1139 +spec: + metadata: + connID: "370" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 341 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('08d3e990-230d-42c4-afa7-b7582bcec893', 'auth_user_for_order', 'auth_user_for_order@example.in', 'scrypt:32768:8:1$7MuhvoaK9PfHmSJg$3f7a6a0a25b3191e12819e62bfe77cbddb0bfa8ddf3f544e77021e59397e3cc7bac4b977d3f4bc39b2f7eba89cd4bdbb6feccb56e5729361dedc90c9979efecc', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.179501478Z + restimestampmock: 2025-11-10T12:20:25.179874864Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1140 +spec: + metadata: + connID: "370" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.180013604Z + restimestampmock: 2025-11-10T12:20:25.191509547Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1141 +spec: + metadata: + connID: "372" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [103, 93, 174, 40, 252, 115, 15, 132, 196, 47, 41, 6, 54, 157, 247, 84, 15, 182, 161, 170, 150, 225, 19, 221, 107, 43, 31, 181, 227, 7, 73, 154] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 329 + auth_plugin_data: [27, 55, 32, 105, 93, 18, 55, 48, 111, 10, 8, 10, 18, 59, 49, 45, 27, 5, 4, 2, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.940335259Z + restimestampmock: 2025-11-10T12:20:25.946636676Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1142 +spec: + metadata: + connID: "372" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.946776225Z + restimestampmock: 2025-11-10T12:20:25.946965513Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1143 +spec: + metadata: + connID: "372" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.947095741Z + restimestampmock: 2025-11-10T12:20:25.94727186Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1144 +spec: + metadata: + connID: "372" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.947487759Z + restimestampmock: 2025-11-10T12:20:25.947582707Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1145 +spec: + metadata: + connID: "372" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 92 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='auth_user_for_order' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 251 + sequence_id: 6 + values: + - type: 253 + name: id + value: 08d3e990-230d-42c4-afa7-b7582bcec893 + unsigned: false + - type: 253 + name: username + value: auth_user_for_order + unsigned: false + - type: 253 + name: email + value: auth_user_for_order@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$7MuhvoaK9PfHmSJg$3f7a6a0a25b3191e12819e62bfe77cbddb0bfa8ddf3f544e77021e59397e3cc7bac4b977d3f4bc39b2f7eba89cd4bdbb6feccb56e5729361dedc90c9979efecc + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777225 + reqtimestampmock: 2025-11-10T12:20:25.947755286Z + restimestampmock: 2025-11-10T12:20:25.948256402Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1146 +spec: + metadata: + connID: "374" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [81, 19, 48, 92, 255, 229, 77, 112, 93, 37, 218, 101, 9, 228, 241, 161, 41, 200, 63, 165, 42, 43, 110, 160, 174, 223, 134, 230, 27, 247, 12, 166] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 330 + auth_plugin_data: [121, 100, 27, 94, 90, 27, 74, 45, 32, 32, 43, 9, 64, 48, 23, 110, 27, 17, 66, 24, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777227 + reqtimestampmock: 2025-11-10T12:20:27.478864769Z + restimestampmock: 2025-11-10T12:20:27.487405397Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1147 +spec: + metadata: + connID: "374" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777227 + reqtimestampmock: 2025-11-10T12:20:27.487597455Z + restimestampmock: 2025-11-10T12:20:27.487758325Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1148 +spec: + metadata: + connID: "374" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777227 + reqtimestampmock: 2025-11-10T12:20:27.487922923Z + restimestampmock: 2025-11-10T12:20:27.488186671Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1149 +spec: + metadata: + connID: "374" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777227 + reqtimestampmock: 2025-11-10T12:20:27.48832763Z + restimestampmock: 2025-11-10T12:20:27.488439058Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1150 +spec: + metadata: + connID: "374" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='08d3e990-230d-42c4-afa7-b7582bcec893' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 08d3e990-230d-42c4-afa7-b7582bcec893 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777227 + reqtimestampmock: 2025-11-10T12:20:27.488614067Z + restimestampmock: 2025-11-10T12:20:27.489025783Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1151 +spec: + metadata: + connID: "374" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='08d3e990-230d-42c4-afa7-b7582bcec893' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777227 + reqtimestampmock: 2025-11-10T12:20:27.489295691Z + restimestampmock: 2025-11-10T12:20:27.489670798Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1152 +spec: + metadata: + connID: "374" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='08d3e990-230d-42c4-afa7-b7582bcec893' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777227 + reqtimestampmock: 2025-11-10T12:20:27.489884076Z + restimestampmock: 2025-11-10T12:20:27.490263384Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1153 +spec: + metadata: + connID: "374" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777227 + reqtimestampmock: 2025-11-10T12:20:27.490353783Z + restimestampmock: 2025-11-10T12:20:27.499327087Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1154 +spec: + metadata: + connID: "376" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [152, 171, 46, 251, 41, 253, 119, 103, 245, 72, 26, 120, 19, 100, 112, 241, 33, 35, 61, 188, 251, 177, 186, 61, 8, 107, 183, 219, 31, 85, 209, 183] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 332 + auth_plugin_data: [49, 9, 9, 119, 18, 49, 99, 54, 1, 81, 62, 123, 113, 55, 55, 38, 64, 98, 45, 78, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.093910988Z + restimestampmock: 2025-11-10T12:20:30.100237316Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1155 +spec: + metadata: + connID: "376" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.100407493Z + restimestampmock: 2025-11-10T12:20:30.100626801Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1156 +spec: + metadata: + connID: "376" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.100733001Z + restimestampmock: 2025-11-10T12:20:30.100966389Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1157 +spec: + metadata: + connID: "376" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.101095608Z + restimestampmock: 2025-11-10T12:20:30.101206057Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1158 +spec: + metadata: + connID: "376" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('84fe4104-56ff-478f-a4eb-aa1c7d9833dd', 'neg_release_user', 'neg_release_user@example.in', 'scrypt:32768:8:1$ADWaINfaU5AyobTM$97f3b7e55dafcbb17c3b21857db4e5916956092c205ed7b7b322d30905e7824b02d47a8840caab005c315cd0144c5cbfc7be0792ae1d6d36c68fd9a9f0bbca23', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.151716382Z + restimestampmock: 2025-11-10T12:20:30.152222708Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1159 +spec: + metadata: + connID: "376" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.152423986Z + restimestampmock: 2025-11-10T12:20:30.164132018Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1160 +spec: + metadata: + connID: "378" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [156, 197, 113, 131, 154, 232, 132, 162, 71, 102, 45, 140, 11, 42, 102, 46, 72, 178, 251, 227, 225, 210, 235, 102, 124, 14, 176, 84, 245, 50, 57, 44] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 333 + auth_plugin_data: [86, 6, 120, 111, 95, 118, 46, 7, 41, 68, 90, 118, 79, 96, 92, 15, 125, 65, 112, 109, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.896693998Z + restimestampmock: 2025-11-10T12:20:30.904231636Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1161 +spec: + metadata: + connID: "378" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.904419553Z + restimestampmock: 2025-11-10T12:20:30.904591212Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1162 +spec: + metadata: + connID: "378" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.904696641Z + restimestampmock: 2025-11-10T12:20:30.90485059Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1163 +spec: + metadata: + connID: "378" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.904958549Z + restimestampmock: 2025-11-10T12:20:30.905055597Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1164 +spec: + metadata: + connID: "378" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='neg_release_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: 84fe4104-56ff-478f-a4eb-aa1c7d9833dd + unsigned: false + - type: 253 + name: username + value: neg_release_user + unsigned: false + - type: 253 + name: email + value: neg_release_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$ADWaINfaU5AyobTM$97f3b7e55dafcbb17c3b21857db4e5916956092c205ed7b7b322d30905e7824b02d47a8840caab005c315cd0144c5cbfc7be0792ae1d6d36c68fd9a9f0bbca23 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777230 + reqtimestampmock: 2025-11-10T12:20:30.905229436Z + restimestampmock: 2025-11-10T12:20:30.905691042Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1165 +spec: + metadata: + connID: "380" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [248, 250, 102, 53, 52, 87, 184, 4, 26, 209, 140, 93, 131, 155, 96, 99, 41, 163, 241, 45, 204, 53, 76, 32, 126, 76, 135, 186, 10, 169, 26, 79] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 334 + auth_plugin_data: [57, 37, 1, 89, 112, 19, 33, 121, 102, 53, 57, 96, 104, 105, 78, 86, 125, 12, 114, 91, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777232 + reqtimestampmock: 2025-11-10T12:20:32.420973953Z + restimestampmock: 2025-11-10T12:20:32.427079951Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1166 +spec: + metadata: + connID: "380" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777232 + reqtimestampmock: 2025-11-10T12:20:32.427275759Z + restimestampmock: 2025-11-10T12:20:32.427460348Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1167 +spec: + metadata: + connID: "380" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777232 + reqtimestampmock: 2025-11-10T12:20:32.427560468Z + restimestampmock: 2025-11-10T12:20:32.427698496Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1168 +spec: + metadata: + connID: "380" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777232 + reqtimestampmock: 2025-11-10T12:20:32.427858105Z + restimestampmock: 2025-11-10T12:20:32.427993293Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1169 +spec: + metadata: + connID: "380" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='84fe4104-56ff-478f-a4eb-aa1c7d9833dd' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 84fe4104-56ff-478f-a4eb-aa1c7d9833dd + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777232 + reqtimestampmock: 2025-11-10T12:20:32.428180402Z + restimestampmock: 2025-11-10T12:20:32.428524719Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1170 +spec: + metadata: + connID: "380" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='84fe4104-56ff-478f-a4eb-aa1c7d9833dd' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777232 + reqtimestampmock: 2025-11-10T12:20:32.428728927Z + restimestampmock: 2025-11-10T12:20:32.428962855Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1171 +spec: + metadata: + connID: "380" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='84fe4104-56ff-478f-a4eb-aa1c7d9833dd' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777232 + reqtimestampmock: 2025-11-10T12:20:32.429111254Z + restimestampmock: 2025-11-10T12:20:32.429459372Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1172 +spec: + metadata: + connID: "380" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777232 + reqtimestampmock: 2025-11-10T12:20:32.429584681Z + restimestampmock: 2025-11-10T12:20:32.438256488Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1173 +spec: + metadata: + connID: "382" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [13, 214, 162, 147, 77, 45, 51, 18, 69, 46, 250, 207, 58, 236, 89, 133, 88, 137, 189, 218, 130, 58, 17, 51, 133, 232, 69, 228, 203, 157, 221, 244] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 336 + auth_plugin_data: [110, 1, 18, 71, 121, 5, 84, 123, 116, 59, 83, 89, 30, 56, 23, 118, 1, 103, 85, 115, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777234 + reqtimestampmock: 2025-11-10T12:20:34.817083739Z + restimestampmock: 2025-11-10T12:20:34.823365016Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1174 +spec: + metadata: + connID: "382" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777234 + reqtimestampmock: 2025-11-10T12:20:34.823550825Z + restimestampmock: 2025-11-10T12:20:34.823757393Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1175 +spec: + metadata: + connID: "382" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777234 + reqtimestampmock: 2025-11-10T12:20:34.823936901Z + restimestampmock: 2025-11-10T12:20:34.82407961Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1176 +spec: + metadata: + connID: "382" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777234 + reqtimestampmock: 2025-11-10T12:20:34.824216069Z + restimestampmock: 2025-11-10T12:20:34.824328639Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1177 +spec: + metadata: + connID: "382" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('868072e7-73dc-4416-a6ea-28c2819950b2', 'neg_reserve_user', 'neg_reserve_user@example.in', 'scrypt:32768:8:1$jX4BWoelyrPLJ0CM$ebf85f97931a35663e1d14bd7506dde857ddec2048e62a104601dcb512266fe292f448abeb2cca9be4571bb93acc98f93b179e101ba411fa950bcb60b916f392', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777234 + reqtimestampmock: 2025-11-10T12:20:34.877264323Z + restimestampmock: 2025-11-10T12:20:34.877721079Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1178 +spec: + metadata: + connID: "382" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777234 + reqtimestampmock: 2025-11-10T12:20:34.877853408Z + restimestampmock: 2025-11-10T12:20:34.887369398Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1179 +spec: + metadata: + connID: "384" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [114, 220, 219, 17, 162, 27, 77, 10, 252, 138, 228, 205, 56, 255, 28, 119, 226, 51, 234, 149, 219, 150, 83, 94, 126, 149, 197, 149, 128, 92, 131, 222] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 337 + auth_plugin_data: [85, 40, 80, 81, 46, 79, 104, 78, 25, 8, 74, 92, 106, 96, 39, 44, 30, 107, 67, 119, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777235 + reqtimestampmock: 2025-11-10T12:20:35.623715529Z + restimestampmock: 2025-11-10T12:20:35.630038827Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1180 +spec: + metadata: + connID: "384" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777235 + reqtimestampmock: 2025-11-10T12:20:35.630194654Z + restimestampmock: 2025-11-10T12:20:35.630378563Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1181 +spec: + metadata: + connID: "384" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777235 + reqtimestampmock: 2025-11-10T12:20:35.630478932Z + restimestampmock: 2025-11-10T12:20:35.630603391Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1182 +spec: + metadata: + connID: "384" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777235 + reqtimestampmock: 2025-11-10T12:20:35.63068398Z + restimestampmock: 2025-11-10T12:20:35.63076114Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1183 +spec: + metadata: + connID: "384" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='neg_reserve_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: 868072e7-73dc-4416-a6ea-28c2819950b2 + unsigned: false + - type: 253 + name: username + value: neg_reserve_user + unsigned: false + - type: 253 + name: email + value: neg_reserve_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$jX4BWoelyrPLJ0CM$ebf85f97931a35663e1d14bd7506dde857ddec2048e62a104601dcb512266fe292f448abeb2cca9be4571bb93acc98f93b179e101ba411fa950bcb60b916f392 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777235 + reqtimestampmock: 2025-11-10T12:20:35.630962148Z + restimestampmock: 2025-11-10T12:20:35.631261465Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1184 +spec: + metadata: + connID: "386" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [222, 47, 104, 165, 140, 70, 102, 156, 97, 253, 163, 149, 196, 43, 80, 46, 42, 14, 86, 190, 45, 230, 3, 121, 76, 85, 139, 207, 46, 253, 97, 204] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 338 + auth_plugin_data: [107, 16, 54, 87, 123, 9, 39, 109, 116, 5, 70, 34, 118, 7, 84, 96, 46, 11, 114, 17, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777237 + reqtimestampmock: 2025-11-10T12:20:37.150358746Z + restimestampmock: 2025-11-10T12:20:37.157384817Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1185 +spec: + metadata: + connID: "386" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777237 + reqtimestampmock: 2025-11-10T12:20:37.157577875Z + restimestampmock: 2025-11-10T12:20:37.157754424Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1186 +spec: + metadata: + connID: "386" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777237 + reqtimestampmock: 2025-11-10T12:20:37.157914552Z + restimestampmock: 2025-11-10T12:20:37.158123451Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1187 +spec: + metadata: + connID: "386" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777237 + reqtimestampmock: 2025-11-10T12:20:37.15823451Z + restimestampmock: 2025-11-10T12:20:37.158352719Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1188 +spec: + metadata: + connID: "386" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='868072e7-73dc-4416-a6ea-28c2819950b2' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 868072e7-73dc-4416-a6ea-28c2819950b2 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777237 + reqtimestampmock: 2025-11-10T12:20:37.158560538Z + restimestampmock: 2025-11-10T12:20:37.158934614Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1189 +spec: + metadata: + connID: "386" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='868072e7-73dc-4416-a6ea-28c2819950b2' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777237 + reqtimestampmock: 2025-11-10T12:20:37.159082763Z + restimestampmock: 2025-11-10T12:20:37.159354711Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1190 +spec: + metadata: + connID: "386" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='868072e7-73dc-4416-a6ea-28c2819950b2' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777237 + reqtimestampmock: 2025-11-10T12:20:37.159463579Z + restimestampmock: 2025-11-10T12:20:37.159734457Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1191 +spec: + metadata: + connID: "386" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777237 + reqtimestampmock: 2025-11-10T12:20:37.159880717Z + restimestampmock: 2025-11-10T12:20:37.169297608Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1192 +spec: + metadata: + connID: "388" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [79, 42, 218, 37, 34, 123, 209, 218, 183, 30, 16, 226, 122, 8, 144, 144, 34, 99, 141, 180, 129, 234, 159, 82, 102, 113, 56, 252, 3, 188, 76, 23] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 340 + auth_plugin_data: [118, 66, 99, 107, 58, 95, 114, 127, 37, 43, 1, 88, 100, 65, 108, 22, 124, 116, 37, 116, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777239 + reqtimestampmock: 2025-11-10T12:20:39.767836986Z + restimestampmock: 2025-11-10T12:20:39.774287052Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1193 +spec: + metadata: + connID: "388" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777239 + reqtimestampmock: 2025-11-10T12:20:39.774431871Z + restimestampmock: 2025-11-10T12:20:39.774670499Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1194 +spec: + metadata: + connID: "388" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777239 + reqtimestampmock: 2025-11-10T12:20:39.774900727Z + restimestampmock: 2025-11-10T12:20:39.774962307Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1195 +spec: + metadata: + connID: "388" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777239 + reqtimestampmock: 2025-11-10T12:20:39.775055296Z + restimestampmock: 2025-11-10T12:20:39.775181865Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1196 +spec: + metadata: + connID: "388" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 325 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('5d6a84ae-2dbf-422c-9bbd-134aa47f0127', 'enrich_user', 'enrich_user@example.in', 'scrypt:32768:8:1$EBiaXDPnijbNEn2e$e20af787760e73fb570ddf1d9bf8f4153c44f7c7bd4c10debe56d0df0b427dc5856e404c2ccab4314b5cb8ce12b23c5a803fd39715a067d22f563d55c4a0c57a', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777239 + reqtimestampmock: 2025-11-10T12:20:39.824663439Z + restimestampmock: 2025-11-10T12:20:39.825034556Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1197 +spec: + metadata: + connID: "388" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777239 + reqtimestampmock: 2025-11-10T12:20:39.825165875Z + restimestampmock: 2025-11-10T12:20:39.837276083Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1198 +spec: + metadata: + connID: "390" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [28, 254, 15, 117, 171, 148, 213, 19, 36, 61, 57, 223, 155, 180, 71, 42, 179, 21, 193, 184, 7, 121, 37, 207, 172, 147, 118, 72, 116, 127, 92, 253] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 341 + auth_plugin_data: [92, 113, 93, 93, 90, 28, 126, 48, 77, 20, 87, 71, 35, 55, 15, 98, 45, 88, 82, 65, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777240 + reqtimestampmock: 2025-11-10T12:20:40.585792182Z + restimestampmock: 2025-11-10T12:20:40.592275909Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1199 +spec: + metadata: + connID: "390" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777240 + reqtimestampmock: 2025-11-10T12:20:40.592481776Z + restimestampmock: 2025-11-10T12:20:40.592648586Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1200 +spec: + metadata: + connID: "390" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777240 + reqtimestampmock: 2025-11-10T12:20:40.592752694Z + restimestampmock: 2025-11-10T12:20:40.592903013Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1201 +spec: + metadata: + connID: "390" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777240 + reqtimestampmock: 2025-11-10T12:20:40.593001062Z + restimestampmock: 2025-11-10T12:20:40.593122141Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1202 +spec: + metadata: + connID: "390" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 84 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='enrich_user' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 235 + sequence_id: 6 + values: + - type: 253 + name: id + value: 5d6a84ae-2dbf-422c-9bbd-134aa47f0127 + unsigned: false + - type: 253 + name: username + value: enrich_user + unsigned: false + - type: 253 + name: email + value: enrich_user@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$EBiaXDPnijbNEn2e$e20af787760e73fb570ddf1d9bf8f4153c44f7c7bd4c10debe56d0df0b427dc5856e404c2ccab4314b5cb8ce12b23c5a803fd39715a067d22f563d55c4a0c57a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777240 + reqtimestampmock: 2025-11-10T12:20:40.59329747Z + restimestampmock: 2025-11-10T12:20:40.593749626Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1203 +spec: + metadata: + connID: "392" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [34, 42, 173, 153, 233, 41, 244, 201, 125, 102, 181, 36, 15, 39, 122, 39, 93, 181, 142, 211, 85, 74, 82, 225, 17, 79, 115, 49, 144, 0, 47, 88] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 342 + auth_plugin_data: [26, 32, 87, 46, 123, 13, 42, 92, 112, 29, 1, 16, 64, 60, 18, 123, 39, 60, 125, 29, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777241 + reqtimestampmock: 2025-11-10T12:20:41.331711705Z + restimestampmock: 2025-11-10T12:20:41.338479697Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1204 +spec: + metadata: + connID: "392" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777241 + reqtimestampmock: 2025-11-10T12:20:41.338690345Z + restimestampmock: 2025-11-10T12:20:41.338944393Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1205 +spec: + metadata: + connID: "392" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777241 + reqtimestampmock: 2025-11-10T12:20:41.339072032Z + restimestampmock: 2025-11-10T12:20:41.339275921Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1206 +spec: + metadata: + connID: "392" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777241 + reqtimestampmock: 2025-11-10T12:20:41.33935369Z + restimestampmock: 2025-11-10T12:20:41.33942984Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1207 +spec: + metadata: + connID: "392" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5d6a84ae-2dbf-422c-9bbd-134aa47f0127' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5d6a84ae-2dbf-422c-9bbd-134aa47f0127 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777241 + reqtimestampmock: 2025-11-10T12:20:41.339605218Z + restimestampmock: 2025-11-10T12:20:41.340034465Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1208 +spec: + metadata: + connID: "392" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 268 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('c0f33e54-1f63-4c31-bc63-5dd4f8683457','5d6a84ae-2dbf-422c-9bbd-134aa47f0127','123 MG Road',NULL,'Bengaluru','Karnataka','560001','IN','+919876543210',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777241 + reqtimestampmock: 2025-11-10T12:20:41.340214962Z + restimestampmock: 2025-11-10T12:20:41.34058162Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1209 +spec: + metadata: + connID: "392" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='5d6a84ae-2dbf-422c-9bbd-134aa47f0127' AND id<>'c0f33e54-1f63-4c31-bc63-5dd4f8683457' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777241 + reqtimestampmock: 2025-11-10T12:20:41.340725469Z + restimestampmock: 2025-11-10T12:20:41.341046486Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1210 +spec: + metadata: + connID: "392" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777241 + reqtimestampmock: 2025-11-10T12:20:41.341186175Z + restimestampmock: 2025-11-10T12:20:41.349241638Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1211 +spec: + metadata: + connID: "394" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [252, 94, 58, 72, 224, 38, 115, 196, 218, 61, 232, 149, 83, 71, 206, 14, 59, 189, 41, 94, 139, 18, 116, 201, 189, 217, 33, 222, 209, 46, 128, 111] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 343 + auth_plugin_data: [22, 32, 127, 28, 99, 33, 2, 79, 6, 45, 51, 26, 106, 62, 94, 14, 72, 101, 46, 81, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777242 + reqtimestampmock: 2025-11-10T12:20:42.904473278Z + restimestampmock: 2025-11-10T12:20:42.911562699Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1212 +spec: + metadata: + connID: "394" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777242 + reqtimestampmock: 2025-11-10T12:20:42.911792326Z + restimestampmock: 2025-11-10T12:20:42.911969986Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1213 +spec: + metadata: + connID: "394" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777242 + reqtimestampmock: 2025-11-10T12:20:42.912072724Z + restimestampmock: 2025-11-10T12:20:42.912215093Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1214 +spec: + metadata: + connID: "394" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777242 + reqtimestampmock: 2025-11-10T12:20:42.912321372Z + restimestampmock: 2025-11-10T12:20:42.912422431Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1215 +spec: + metadata: + connID: "394" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5d6a84ae-2dbf-422c-9bbd-134aa47f0127' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5d6a84ae-2dbf-422c-9bbd-134aa47f0127 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777242 + reqtimestampmock: 2025-11-10T12:20:42.91258802Z + restimestampmock: 2025-11-10T12:20:42.912899468Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1216 +spec: + metadata: + connID: "394" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='5d6a84ae-2dbf-422c-9bbd-134aa47f0127' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777242 + reqtimestampmock: 2025-11-10T12:20:42.913069016Z + restimestampmock: 2025-11-10T12:20:42.913475513Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1217 +spec: + metadata: + connID: "394" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='5d6a84ae-2dbf-422c-9bbd-134aa47f0127' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777242 + reqtimestampmock: 2025-11-10T12:20:42.913716241Z + restimestampmock: 2025-11-10T12:20:42.914042197Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1218 +spec: + metadata: + connID: "394" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777242 + reqtimestampmock: 2025-11-10T12:20:42.914430354Z + restimestampmock: 2025-11-10T12:20:42.924789998Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1219 +spec: + metadata: + connID: "396" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [206, 254, 81, 89, 47, 216, 234, 190, 234, 57, 127, 44, 6, 111, 43, 213, 218, 241, 41, 120, 219, 133, 124, 41, 37, 128, 97, 227, 210, 218, 19, 172] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 345 + auth_plugin_data: [85, 112, 73, 102, 47, 17, 85, 11, 99, 79, 43, 118, 28, 22, 110, 70, 32, 51, 5, 75, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777245 + reqtimestampmock: 2025-11-10T12:20:45.526555246Z + restimestampmock: 2025-11-10T12:20:45.533819236Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1220 +spec: + metadata: + connID: "396" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777245 + reqtimestampmock: 2025-11-10T12:20:45.534046463Z + restimestampmock: 2025-11-10T12:20:45.534265371Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1221 +spec: + metadata: + connID: "396" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777245 + reqtimestampmock: 2025-11-10T12:20:45.53439089Z + restimestampmock: 2025-11-10T12:20:45.534539419Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1222 +spec: + metadata: + connID: "396" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777245 + reqtimestampmock: 2025-11-10T12:20:45.534658578Z + restimestampmock: 2025-11-10T12:20:45.534774987Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1223 +spec: + metadata: + connID: "396" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 333 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('f8de48e0-82cc-4004-8fb3-3639ebb3be22', 'immediate_login', 'immediate_login@example.in', 'scrypt:32768:8:1$6JSdHFArT7FG30gB$5b7d428eb9e134e13ac05862383ad8543f610ccebab80012060e37ffd99731f514cc4000434dd3e396753e2ab4aea8986e2dd14f68111bb9f26b4b9a9dd95cf9', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777245 + reqtimestampmock: 2025-11-10T12:20:45.581803042Z + restimestampmock: 2025-11-10T12:20:45.582256408Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1224 +spec: + metadata: + connID: "396" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777245 + reqtimestampmock: 2025-11-10T12:20:45.582388157Z + restimestampmock: 2025-11-10T12:20:45.591270673Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1225 +spec: + metadata: + connID: "398" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [170, 214, 13, 25, 178, 26, 136, 86, 120, 99, 73, 19, 175, 85, 108, 231, 186, 227, 40, 36, 8, 146, 219, 239, 62, 153, 236, 3, 58, 99, 45, 145] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 346 + auth_plugin_data: [95, 84, 58, 75, 12, 9, 77, 114, 32, 77, 70, 67, 123, 1, 51, 78, 89, 57, 11, 102, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777246 + reqtimestampmock: 2025-11-10T12:20:46.348265132Z + restimestampmock: 2025-11-10T12:20:46.354954196Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1226 +spec: + metadata: + connID: "398" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777246 + reqtimestampmock: 2025-11-10T12:20:46.355174434Z + restimestampmock: 2025-11-10T12:20:46.355378542Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1227 +spec: + metadata: + connID: "398" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777246 + reqtimestampmock: 2025-11-10T12:20:46.355511801Z + restimestampmock: 2025-11-10T12:20:46.35566392Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1228 +spec: + metadata: + connID: "398" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777246 + reqtimestampmock: 2025-11-10T12:20:46.355822169Z + restimestampmock: 2025-11-10T12:20:46.355919018Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1229 +spec: + metadata: + connID: "398" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 88 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='immediate_login' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 243 + sequence_id: 6 + values: + - type: 253 + name: id + value: f8de48e0-82cc-4004-8fb3-3639ebb3be22 + unsigned: false + - type: 253 + name: username + value: immediate_login + unsigned: false + - type: 253 + name: email + value: immediate_login@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$6JSdHFArT7FG30gB$5b7d428eb9e134e13ac05862383ad8543f610ccebab80012060e37ffd99731f514cc4000434dd3e396753e2ab4aea8986e2dd14f68111bb9f26b4b9a9dd95cf9 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777246 + reqtimestampmock: 2025-11-10T12:20:46.356133066Z + restimestampmock: 2025-11-10T12:20:46.356629362Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1230 +spec: + metadata: + connID: "400" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [232, 2, 16, 237, 3, 4, 129, 61, 195, 22, 100, 157, 129, 210, 10, 110, 160, 151, 95, 39, 194, 192, 208, 43, 121, 53, 129, 202, 27, 12, 118, 181] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 347 + auth_plugin_data: [70, 70, 73, 67, 41, 80, 53, 61, 29, 27, 115, 43, 4, 106, 96, 91, 55, 20, 18, 32, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.139226367Z + restimestampmock: 2025-11-10T12:20:47.146551725Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1231 +spec: + metadata: + connID: "400" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.146716124Z + restimestampmock: 2025-11-10T12:20:47.146909193Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1232 +spec: + metadata: + connID: "400" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.147021412Z + restimestampmock: 2025-11-10T12:20:47.14723964Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1233 +spec: + metadata: + connID: "400" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.147325859Z + restimestampmock: 2025-11-10T12:20:47.147431007Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1234 +spec: + metadata: + connID: "400" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='f8de48e0-82cc-4004-8fb3-3639ebb3be22' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: f8de48e0-82cc-4004-8fb3-3639ebb3be22 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.147572427Z + restimestampmock: 2025-11-10T12:20:47.147956253Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1235 +spec: + metadata: + connID: "400" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='f8de48e0-82cc-4004-8fb3-3639ebb3be22' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.148032384Z + restimestampmock: 2025-11-10T12:20:47.148295051Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1236 +spec: + metadata: + connID: "400" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='f8de48e0-82cc-4004-8fb3-3639ebb3be22' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.14840222Z + restimestampmock: 2025-11-10T12:20:47.148747987Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1237 +spec: + metadata: + connID: "400" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.148831596Z + restimestampmock: 2025-11-10T12:20:47.16023914Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1238 +spec: + metadata: + connID: "402" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [100, 137, 109, 102, 33, 132, 140, 119, 204, 247, 47, 212, 67, 254, 229, 236, 80, 85, 124, 170, 144, 137, 248, 113, 93, 175, 50, 126, 162, 17, 58, 229] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 348 + auth_plugin_data: [49, 3, 77, 26, 84, 114, 16, 91, 76, 13, 92, 110, 38, 20, 55, 102, 80, 11, 15, 31, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.975493331Z + restimestampmock: 2025-11-10T12:20:47.98163003Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1239 +spec: + metadata: + connID: "402" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.981794438Z + restimestampmock: 2025-11-10T12:20:47.981965178Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1240 +spec: + metadata: + connID: "402" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.982093776Z + restimestampmock: 2025-11-10T12:20:47.982238785Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1241 +spec: + metadata: + connID: "402" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777247 + reqtimestampmock: 2025-11-10T12:20:47.982329254Z + restimestampmock: 2025-11-10T12:20:47.982430693Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1242 +spec: + metadata: + connID: "402" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('f820469b-ddda-4dd0-866d-f240033e6b6a', 'delete_verify', 'delete_verify@example.in', 'scrypt:32768:8:1$BRRh3Py73GI81lEZ$548e54d3e357193352958389484371f50e9dcbaef5a8a276d9ea53eeff41b9366a198329276e83072b4cc82d94b122b56d3ff2840f1ab4d2e0b4a89d15f1277c', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777248 + reqtimestampmock: 2025-11-10T12:20:48.033349295Z + restimestampmock: 2025-11-10T12:20:48.033820272Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1243 +spec: + metadata: + connID: "402" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777248 + reqtimestampmock: 2025-11-10T12:20:48.03394974Z + restimestampmock: 2025-11-10T12:20:48.049242272Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1244 +spec: + metadata: + connID: "404" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [66, 143, 145, 211, 10, 254, 214, 126, 179, 186, 10, 17, 48, 143, 54, 48, 6, 87, 57, 166, 6, 173, 17, 207, 3, 87, 83, 221, 166, 79, 49, 50] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 349 + auth_plugin_data: [59, 66, 91, 90, 77, 79, 47, 57, 52, 102, 96, 127, 60, 68, 40, 112, 89, 95, 92, 102, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777248 + reqtimestampmock: 2025-11-10T12:20:48.72565714Z + restimestampmock: 2025-11-10T12:20:48.731939176Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1245 +spec: + metadata: + connID: "404" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777248 + reqtimestampmock: 2025-11-10T12:20:48.732129665Z + restimestampmock: 2025-11-10T12:20:48.732306033Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1246 +spec: + metadata: + connID: "404" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777248 + reqtimestampmock: 2025-11-10T12:20:48.732433462Z + restimestampmock: 2025-11-10T12:20:48.732647491Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1247 +spec: + metadata: + connID: "404" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777248 + reqtimestampmock: 2025-11-10T12:20:48.73276362Z + restimestampmock: 2025-11-10T12:20:48.732873169Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1248 +spec: + metadata: + connID: "404" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='delete_verify' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: f820469b-ddda-4dd0-866d-f240033e6b6a + unsigned: false + - type: 253 + name: username + value: delete_verify + unsigned: false + - type: 253 + name: email + value: delete_verify@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$BRRh3Py73GI81lEZ$548e54d3e357193352958389484371f50e9dcbaef5a8a276d9ea53eeff41b9366a198329276e83072b4cc82d94b122b56d3ff2840f1ab4d2e0b4a89d15f1277c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777248 + reqtimestampmock: 2025-11-10T12:20:48.733066077Z + restimestampmock: 2025-11-10T12:20:48.733508763Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1249 +spec: + metadata: + connID: "406" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [17, 79, 237, 122, 124, 58, 210, 157, 56, 21, 139, 130, 184, 101, 165, 244, 247, 67, 215, 105, 166, 145, 197, 126, 156, 236, 190, 133, 121, 181, 238, 243] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 351 + auth_plugin_data: [58, 66, 84, 112, 27, 14, 93, 9, 66, 52, 41, 109, 3, 72, 96, 112, 82, 85, 99, 13, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777249 + reqtimestampmock: 2025-11-10T12:20:49.498052531Z + restimestampmock: 2025-11-10T12:20:49.504381118Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1250 +spec: + metadata: + connID: "406" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777249 + reqtimestampmock: 2025-11-10T12:20:49.504547936Z + restimestampmock: 2025-11-10T12:20:49.504795664Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1251 +spec: + metadata: + connID: "406" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777249 + reqtimestampmock: 2025-11-10T12:20:49.504919913Z + restimestampmock: 2025-11-10T12:20:49.505074332Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1252 +spec: + metadata: + connID: "406" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777249 + reqtimestampmock: 2025-11-10T12:20:49.505187971Z + restimestampmock: 2025-11-10T12:20:49.505281159Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1253 +spec: + metadata: + connID: "406" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='f820469b-ddda-4dd0-866d-f240033e6b6a' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: f820469b-ddda-4dd0-866d-f240033e6b6a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777249 + reqtimestampmock: 2025-11-10T12:20:49.505457469Z + restimestampmock: 2025-11-10T12:20:49.505859616Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1254 +spec: + metadata: + connID: "406" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='f820469b-ddda-4dd0-866d-f240033e6b6a' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777249 + reqtimestampmock: 2025-11-10T12:20:49.506066264Z + restimestampmock: 2025-11-10T12:20:49.506335742Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1255 +spec: + metadata: + connID: "406" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='f820469b-ddda-4dd0-866d-f240033e6b6a' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777249 + reqtimestampmock: 2025-11-10T12:20:49.50649732Z + restimestampmock: 2025-11-10T12:20:49.506845297Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1256 +spec: + metadata: + connID: "406" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777249 + reqtimestampmock: 2025-11-10T12:20:49.506949996Z + restimestampmock: 2025-11-10T12:20:49.514223436Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1257 +spec: + metadata: + connID: "408" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [25, 187, 41, 40, 175, 22, 8, 162, 11, 95, 182, 58, 11, 146, 116, 18, 22, 12, 45, 94, 114, 113, 228, 137, 183, 28, 119, 176, 46, 57, 120, 63] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 352 + auth_plugin_data: [38, 91, 73, 41, 98, 54, 37, 52, 69, 126, 113, 37, 76, 94, 117, 79, 120, 116, 96, 20, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777250 + reqtimestampmock: 2025-11-10T12:20:50.235897142Z + restimestampmock: 2025-11-10T12:20:50.242051571Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1258 +spec: + metadata: + connID: "408" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777250 + reqtimestampmock: 2025-11-10T12:20:50.242244129Z + restimestampmock: 2025-11-10T12:20:50.242442398Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1259 +spec: + metadata: + connID: "408" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777250 + reqtimestampmock: 2025-11-10T12:20:50.242607067Z + restimestampmock: 2025-11-10T12:20:50.242757855Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1260 +spec: + metadata: + connID: "408" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777250 + reqtimestampmock: 2025-11-10T12:20:50.242863755Z + restimestampmock: 2025-11-10T12:20:50.242946523Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1261 +spec: + metadata: + connID: "408" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 109 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, phone, created_at FROM users WHERE id = 'f820469b-ddda-4dd0-866d-f240033e6b6a' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 5 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 59 + sequence_id: 6 + catalog: def + schema: user_db + table: users + org_table: users + name: created_at + org_name: created_at + fixed_length: 12 + character_set: 63 + column_length: 19 + type: 7 + flags: 1153 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777250 + reqtimestampmock: 2025-11-10T12:20:50.243201672Z + restimestampmock: 2025-11-10T12:20:50.243691117Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1262 +spec: + metadata: + connID: "410" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [182, 45, 27, 226, 247, 174, 17, 150, 59, 220, 162, 58, 87, 45, 64, 72, 98, 95, 156, 145, 12, 102, 186, 244, 33, 132, 10, 76, 200, 207, 169, 252] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 353 + auth_plugin_data: [63, 34, 60, 63, 40, 12, 118, 89, 51, 50, 112, 8, 115, 20, 84, 72, 66, 35, 29, 100, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.068715107Z + restimestampmock: 2025-11-10T12:20:51.082714909Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1263 +spec: + metadata: + connID: "410" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.082958038Z + restimestampmock: 2025-11-10T12:20:51.083129276Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1264 +spec: + metadata: + connID: "410" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.083308224Z + restimestampmock: 2025-11-10T12:20:51.083438983Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1265 +spec: + metadata: + connID: "410" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.083584162Z + restimestampmock: 2025-11-10T12:20:51.083696932Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1266 +spec: + metadata: + connID: "410" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='delete_verify' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 6 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.083906179Z + restimestampmock: 2025-11-10T12:20:51.084340656Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1267 +spec: + metadata: + connID: "412" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [137, 130, 153, 56, 199, 105, 59, 152, 46, 222, 182, 36, 162, 197, 216, 99, 206, 68, 168, 236, 17, 74, 180, 18, 218, 38, 89, 164, 162, 79, 29, 22] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 354 + auth_plugin_data: [37, 55, 92, 42, 50, 44, 52, 52, 46, 6, 23, 45, 77, 110, 6, 1, 4, 29, 86, 108, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.91004017Z + restimestampmock: 2025-11-10T12:20:51.916439857Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1268 +spec: + metadata: + connID: "412" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.916649935Z + restimestampmock: 2025-11-10T12:20:51.916841623Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1269 +spec: + metadata: + connID: "412" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.916949552Z + restimestampmock: 2025-11-10T12:20:51.917092932Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1270 +spec: + metadata: + connID: "412" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.917184771Z + restimestampmock: 2025-11-10T12:20:51.917300579Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1271 +spec: + metadata: + connID: "412" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('5be5ca18-7185-4cc1-847b-7a56eab9c349', 'non_default_addr', 'non_default_addr@example.in', 'scrypt:32768:8:1$A72Ak0KvHlo3ABDb$29e3cbb7e96508f19bce870ffc9bc3ef7ab473b8c93b5d2ed5db51f061b50ae4be7f0940b5da7ed1abf2dcf7156aabb78c27426b5e98af44c37792606cef9750', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.968808367Z + restimestampmock: 2025-11-10T12:20:51.969177254Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1272 +spec: + metadata: + connID: "412" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777251 + reqtimestampmock: 2025-11-10T12:20:51.969336222Z + restimestampmock: 2025-11-10T12:20:51.978181948Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1273 +spec: + metadata: + connID: "414" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [232, 235, 217, 229, 237, 84, 163, 101, 101, 132, 125, 29, 163, 173, 224, 126, 208, 105, 251, 75, 16, 229, 27, 165, 136, 4, 146, 130, 87, 181, 217, 76] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 355 + auth_plugin_data: [85, 13, 108, 9, 39, 115, 96, 83, 102, 10, 109, 31, 76, 31, 75, 17, 115, 37, 17, 51, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777252 + reqtimestampmock: 2025-11-10T12:20:52.666490117Z + restimestampmock: 2025-11-10T12:20:52.672915193Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1274 +spec: + metadata: + connID: "414" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777252 + reqtimestampmock: 2025-11-10T12:20:52.673088322Z + restimestampmock: 2025-11-10T12:20:52.673308309Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1275 +spec: + metadata: + connID: "414" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777252 + reqtimestampmock: 2025-11-10T12:20:52.673406689Z + restimestampmock: 2025-11-10T12:20:52.673548498Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1276 +spec: + metadata: + connID: "414" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777252 + reqtimestampmock: 2025-11-10T12:20:52.673697796Z + restimestampmock: 2025-11-10T12:20:52.673822176Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1277 +spec: + metadata: + connID: "414" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='non_default_addr' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: 5be5ca18-7185-4cc1-847b-7a56eab9c349 + unsigned: false + - type: 253 + name: username + value: non_default_addr + unsigned: false + - type: 253 + name: email + value: non_default_addr@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$A72Ak0KvHlo3ABDb$29e3cbb7e96508f19bce870ffc9bc3ef7ab473b8c93b5d2ed5db51f061b50ae4be7f0940b5da7ed1abf2dcf7156aabb78c27426b5e98af44c37792606cef9750 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777252 + reqtimestampmock: 2025-11-10T12:20:52.674032134Z + restimestampmock: 2025-11-10T12:20:52.674509989Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1278 +spec: + metadata: + connID: "416" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [174, 215, 191, 145, 129, 184, 200, 96, 94, 227, 184, 0, 38, 55, 71, 218, 225, 51, 49, 12, 189, 234, 41, 32, 7, 134, 210, 162, 244, 85, 115, 233] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 356 + auth_plugin_data: [119, 37, 53, 25, 73, 66, 20, 42, 1, 5, 65, 102, 50, 19, 91, 92, 1, 126, 104, 57, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777253 + reqtimestampmock: 2025-11-10T12:20:53.39822258Z + restimestampmock: 2025-11-10T12:20:53.404624167Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1279 +spec: + metadata: + connID: "416" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777253 + reqtimestampmock: 2025-11-10T12:20:53.404776876Z + restimestampmock: 2025-11-10T12:20:53.404961635Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1280 +spec: + metadata: + connID: "416" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777253 + reqtimestampmock: 2025-11-10T12:20:53.405059274Z + restimestampmock: 2025-11-10T12:20:53.405239652Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1281 +spec: + metadata: + connID: "416" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777253 + reqtimestampmock: 2025-11-10T12:20:53.405364061Z + restimestampmock: 2025-11-10T12:20:53.40545865Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1282 +spec: + metadata: + connID: "416" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5be5ca18-7185-4cc1-847b-7a56eab9c349' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5be5ca18-7185-4cc1-847b-7a56eab9c349 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777253 + reqtimestampmock: 2025-11-10T12:20:53.405621209Z + restimestampmock: 2025-11-10T12:20:53.406025765Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1283 +spec: + metadata: + connID: "416" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 271 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('afcc695f-bd55-46df-aff7-2687d3bf1e56','5be5ca18-7185-4cc1-847b-7a56eab9c349','Work Office, Infopark',NULL,'Kochi','Kerala','682030','IN','+919847012345',0) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777253 + reqtimestampmock: 2025-11-10T12:20:53.406162824Z + restimestampmock: 2025-11-10T12:20:53.406479391Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1284 +spec: + metadata: + connID: "416" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777253 + reqtimestampmock: 2025-11-10T12:20:53.406581061Z + restimestampmock: 2025-11-10T12:20:53.41618654Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1285 +spec: + metadata: + connID: "418" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [130, 220, 147, 68, 62, 130, 33, 114, 62, 39, 234, 53, 124, 34, 236, 114, 182, 224, 90, 116, 7, 22, 174, 76, 203, 157, 22, 1, 233, 112, 159, 85] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 357 + auth_plugin_data: [89, 82, 122, 100, 87, 8, 58, 99, 72, 85, 67, 2, 113, 41, 73, 37, 73, 79, 64, 29, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.251231027Z + restimestampmock: 2025-11-10T12:20:54.257373156Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1286 +spec: + metadata: + connID: "418" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.257533745Z + restimestampmock: 2025-11-10T12:20:54.257734223Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1287 +spec: + metadata: + connID: "418" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.257851502Z + restimestampmock: 2025-11-10T12:20:54.258005551Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1288 +spec: + metadata: + connID: "418" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.25811831Z + restimestampmock: 2025-11-10T12:20:54.258227429Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1289 +spec: + metadata: + connID: "418" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5be5ca18-7185-4cc1-847b-7a56eab9c349' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5be5ca18-7185-4cc1-847b-7a56eab9c349 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.258406237Z + restimestampmock: 2025-11-10T12:20:54.258746994Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1290 +spec: + metadata: + connID: "418" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 190 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, line1, line2, city, state, postal_code, country, phone, is_default FROM addresses WHERE user_id='5be5ca18-7185-4cc1-847b-7a56eab9c349' ORDER BY is_default DESC, created_at DESC + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 9 + columns: + - header: + payload_length: 51 + sequence_id: 2 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 3 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line1 + org_name: line1 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 4 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line2 + org_name: line2 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 5 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: city + org_name: city + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 6 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: state + org_name: state + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 69 + sequence_id: 7 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: postal_code + org_name: postal_code + fixed_length: 12 + character_set: 255 + column_length: 80 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 61 + sequence_id: 8 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: country + org_name: country + fixed_length: 12 + character_set: 255 + column_length: 8 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 9 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 67 + sequence_id: 10 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: is_default + org_name: is_default + fixed_length: 12 + character_set: 63 + column_length: 1 + type: 1 + flags: 1 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 99 + sequence_id: 11 + values: + - type: 253 + name: id + value: afcc695f-bd55-46df-aff7-2687d3bf1e56 + unsigned: false + - type: 253 + name: line1 + value: Work Office, Infopark + unsigned: false + - type: 253 + name: line2 + value: null + unsigned: false + - type: 253 + name: city + value: Kochi + unsigned: false + - type: 253 + name: state + value: Kerala + unsigned: false + - type: 253 + name: postal_code + value: "682030" + unsigned: false + - type: 253 + name: country + value: IN + unsigned: false + - type: 253 + name: phone + value: "+919847012345" + unsigned: false + - type: 1 + name: is_default + value: "0" + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 12 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.258910843Z + restimestampmock: 2025-11-10T12:20:54.259714515Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1291 +spec: + metadata: + connID: "420" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [195, 192, 130, 252, 201, 219, 34, 142, 253, 230, 53, 47, 253, 39, 147, 217, 56, 89, 82, 107, 102, 85, 27, 138, 196, 136, 23, 19, 32, 220, 253, 238] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 359 + auth_plugin_data: [82, 124, 96, 103, 125, 117, 1, 23, 25, 102, 1, 45, 23, 61, 121, 123, 57, 92, 28, 73, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.971282281Z + restimestampmock: 2025-11-10T12:20:54.977871005Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1292 +spec: + metadata: + connID: "420" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.978031883Z + restimestampmock: 2025-11-10T12:20:54.978208882Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1293 +spec: + metadata: + connID: "420" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.978324801Z + restimestampmock: 2025-11-10T12:20:54.97848269Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1294 +spec: + metadata: + connID: "420" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.978623879Z + restimestampmock: 2025-11-10T12:20:54.978711628Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1295 +spec: + metadata: + connID: "420" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5be5ca18-7185-4cc1-847b-7a56eab9c349' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5be5ca18-7185-4cc1-847b-7a56eab9c349 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.978901077Z + restimestampmock: 2025-11-10T12:20:54.979279933Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1296 +spec: + metadata: + connID: "420" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='5be5ca18-7185-4cc1-847b-7a56eab9c349' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.979474922Z + restimestampmock: 2025-11-10T12:20:54.979788058Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1297 +spec: + metadata: + connID: "420" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='5be5ca18-7185-4cc1-847b-7a56eab9c349' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.979957488Z + restimestampmock: 2025-11-10T12:20:54.980227555Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1298 +spec: + metadata: + connID: "420" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777254 + reqtimestampmock: 2025-11-10T12:20:54.980353434Z + restimestampmock: 2025-11-10T12:20:54.993109087Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1299 +spec: + metadata: + connID: "422" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [15, 227, 117, 43, 53, 49, 243, 245, 71, 242, 33, 19, 119, 189, 160, 201, 120, 205, 172, 160, 132, 136, 128, 117, 15, 87, 45, 111, 186, 230, 165, 95] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 360 + auth_plugin_data: [78, 37, 113, 113, 69, 81, 107, 33, 112, 127, 40, 102, 123, 98, 123, 26, 94, 34, 67, 73, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777258 + reqtimestampmock: 2025-11-10T12:20:58.638641074Z + restimestampmock: 2025-11-10T12:20:58.645239688Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1300 +spec: + metadata: + connID: "422" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777258 + reqtimestampmock: 2025-11-10T12:20:58.645424797Z + restimestampmock: 2025-11-10T12:20:58.645611815Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1301 +spec: + metadata: + connID: "422" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777258 + reqtimestampmock: 2025-11-10T12:20:58.645711064Z + restimestampmock: 2025-11-10T12:20:58.645877603Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1302 +spec: + metadata: + connID: "422" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777258 + reqtimestampmock: 2025-11-10T12:20:58.645962402Z + restimestampmock: 2025-11-10T12:20:58.646123091Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1303 +spec: + metadata: + connID: "422" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 147 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='userwithaveryverylongandunnecessarilycomplexusernamethatshouldstillbevalid' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 6 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777258 + reqtimestampmock: 2025-11-10T12:20:58.64629174Z + restimestampmock: 2025-11-10T12:20:58.646675466Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1304 +spec: + metadata: + connID: "424" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [106, 129, 79, 227, 190, 241, 148, 193, 73, 109, 199, 16, 101, 173, 129, 192, 157, 132, 192, 51, 235, 121, 82, 221, 9, 190, 123, 84, 70, 230, 218, 221] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 362 + auth_plugin_data: [1, 4, 27, 15, 37, 82, 21, 52, 28, 9, 122, 58, 12, 121, 91, 111, 122, 112, 59, 57, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777261 + reqtimestampmock: 2025-11-10T12:21:01.949733833Z + restimestampmock: 2025-11-10T12:21:01.956465856Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1305 +spec: + metadata: + connID: "424" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777261 + reqtimestampmock: 2025-11-10T12:21:01.956625084Z + restimestampmock: 2025-11-10T12:21:01.956817833Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1306 +spec: + metadata: + connID: "424" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777261 + reqtimestampmock: 2025-11-10T12:21:01.956918042Z + restimestampmock: 2025-11-10T12:21:01.95708168Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1307 +spec: + metadata: + connID: "424" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777261 + reqtimestampmock: 2025-11-10T12:21:01.95720291Z + restimestampmock: 2025-11-10T12:21:01.957357688Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1308 +spec: + metadata: + connID: "424" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 325 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('a96df4d6-c21f-47ec-956f-48b8e7afe77b', 'user_-.123', 'user.special@example.in', 'scrypt:32768:8:1$LyYsFPHw7JRWensE$aa74e9b40626889fe2932f30d6f33a41709f71148a2831a2deb50dee1974c546f5377595151d8f54240882ca3a8adc3989a0320b56a213b5f38117005c264af3', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777262 + reqtimestampmock: 2025-11-10T12:21:02.007713925Z + restimestampmock: 2025-11-10T12:21:02.008186531Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1309 +spec: + metadata: + connID: "424" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777262 + reqtimestampmock: 2025-11-10T12:21:02.00833982Z + restimestampmock: 2025-11-10T12:21:02.017216126Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1310 +spec: + metadata: + connID: "426" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [69, 52, 205, 107, 121, 154, 194, 157, 223, 76, 248, 157, 0, 107, 50, 202, 116, 190, 190, 28, 126, 158, 159, 86, 111, 141, 1, 133, 181, 146, 28, 168] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 363 + auth_plugin_data: [76, 122, 98, 37, 28, 116, 41, 63, 114, 12, 78, 87, 18, 42, 16, 69, 102, 124, 23, 70, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777262 + reqtimestampmock: 2025-11-10T12:21:02.846318826Z + restimestampmock: 2025-11-10T12:21:02.852485563Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1311 +spec: + metadata: + connID: "426" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777262 + reqtimestampmock: 2025-11-10T12:21:02.852638583Z + restimestampmock: 2025-11-10T12:21:02.852796921Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1312 +spec: + metadata: + connID: "426" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777262 + reqtimestampmock: 2025-11-10T12:21:02.85289588Z + restimestampmock: 2025-11-10T12:21:02.853042999Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1313 +spec: + metadata: + connID: "426" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777262 + reqtimestampmock: 2025-11-10T12:21:02.853160748Z + restimestampmock: 2025-11-10T12:21:02.853247058Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1314 +spec: + metadata: + connID: "426" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 83 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='user_-.123' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 235 + sequence_id: 6 + values: + - type: 253 + name: id + value: a96df4d6-c21f-47ec-956f-48b8e7afe77b + unsigned: false + - type: 253 + name: username + value: user_-.123 + unsigned: false + - type: 253 + name: email + value: user.special@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$LyYsFPHw7JRWensE$aa74e9b40626889fe2932f30d6f33a41709f71148a2831a2deb50dee1974c546f5377595151d8f54240882ca3a8adc3989a0320b56a213b5f38117005c264af3 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777262 + reqtimestampmock: 2025-11-10T12:21:02.853452417Z + restimestampmock: 2025-11-10T12:21:02.853877302Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1315 +spec: + metadata: + connID: "428" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [232, 86, 208, 233, 94, 8, 157, 45, 67, 132, 221, 46, 13, 158, 184, 2, 193, 138, 210, 170, 127, 204, 255, 246, 22, 91, 186, 233, 193, 204, 36, 51] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 364 + auth_plugin_data: [109, 119, 13, 112, 56, 60, 65, 95, 105, 21, 43, 32, 10, 75, 80, 71, 86, 2, 100, 113, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777263 + reqtimestampmock: 2025-11-10T12:21:03.711022178Z + restimestampmock: 2025-11-10T12:21:03.717423753Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1316 +spec: + metadata: + connID: "428" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777263 + reqtimestampmock: 2025-11-10T12:21:03.717612871Z + restimestampmock: 2025-11-10T12:21:03.71778734Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1317 +spec: + metadata: + connID: "428" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777263 + reqtimestampmock: 2025-11-10T12:21:03.717928959Z + restimestampmock: 2025-11-10T12:21:03.718079878Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1318 +spec: + metadata: + connID: "428" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777263 + reqtimestampmock: 2025-11-10T12:21:03.718220107Z + restimestampmock: 2025-11-10T12:21:03.718329285Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1319 +spec: + metadata: + connID: "428" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a96df4d6-c21f-47ec-956f-48b8e7afe77b' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a96df4d6-c21f-47ec-956f-48b8e7afe77b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777263 + reqtimestampmock: 2025-11-10T12:21:03.718496394Z + restimestampmock: 2025-11-10T12:21:03.718823152Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1320 +spec: + metadata: + connID: "428" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='a96df4d6-c21f-47ec-956f-48b8e7afe77b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777263 + reqtimestampmock: 2025-11-10T12:21:03.71900951Z + restimestampmock: 2025-11-10T12:21:03.719271687Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1321 +spec: + metadata: + connID: "428" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='a96df4d6-c21f-47ec-956f-48b8e7afe77b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777263 + reqtimestampmock: 2025-11-10T12:21:03.719723584Z + restimestampmock: 2025-11-10T12:21:03.720065071Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1322 +spec: + metadata: + connID: "428" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777263 + reqtimestampmock: 2025-11-10T12:21:03.72018592Z + restimestampmock: 2025-11-10T12:21:03.730015807Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1323 +spec: + metadata: + connID: "430" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [202, 45, 43, 132, 191, 147, 93, 30, 77, 106, 130, 253, 179, 78, 172, 245, 107, 233, 42, 134, 210, 127, 228, 116, 163, 155, 53, 54, 114, 111, 226, 136] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 366 + auth_plugin_data: [102, 111, 25, 40, 123, 73, 24, 29, 108, 39, 22, 118, 109, 114, 123, 42, 103, 116, 114, 35, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777264 + reqtimestampmock: 2025-11-10T12:21:04.525186151Z + restimestampmock: 2025-11-10T12:21:04.53130326Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1324 +spec: + metadata: + connID: "430" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777264 + reqtimestampmock: 2025-11-10T12:21:04.531469218Z + restimestampmock: 2025-11-10T12:21:04.531658848Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1325 +spec: + metadata: + connID: "430" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777264 + reqtimestampmock: 2025-11-10T12:21:04.531750887Z + restimestampmock: 2025-11-10T12:21:04.531911745Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1326 +spec: + metadata: + connID: "430" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777264 + reqtimestampmock: 2025-11-10T12:21:04.532013424Z + restimestampmock: 2025-11-10T12:21:04.532107203Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1327 +spec: + metadata: + connID: "430" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 341 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('777b90d1-71b1-4983-942d-656250aac340', 'auth_user_for_other', 'auth_user_for_other@example.in', 'scrypt:32768:8:1$hevjeMRkhBKIkxD7$49104df42aa01a53559987d12cf199f2772afad34640c5142f21c6844c3d8db312d0df806600a93a8bf8e56020577fcfe0c4056fa631132919f78e78447e1f21', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777264 + reqtimestampmock: 2025-11-10T12:21:04.57892595Z + restimestampmock: 2025-11-10T12:21:04.579343177Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1328 +spec: + metadata: + connID: "430" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777264 + reqtimestampmock: 2025-11-10T12:21:04.579497456Z + restimestampmock: 2025-11-10T12:21:04.596993829Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1329 +spec: + metadata: + connID: "432" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [252, 15, 84, 231, 139, 73, 143, 21, 53, 126, 68, 139, 102, 55, 57, 81, 242, 165, 215, 175, 53, 45, 221, 36, 90, 195, 8, 231, 84, 3, 110, 88] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 367 + auth_plugin_data: [53, 80, 121, 30, 74, 90, 23, 117, 81, 67, 78, 3, 18, 107, 17, 104, 78, 6, 7, 30, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777265 + reqtimestampmock: 2025-11-10T12:21:05.344336896Z + restimestampmock: 2025-11-10T12:21:05.350910252Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1330 +spec: + metadata: + connID: "432" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777265 + reqtimestampmock: 2025-11-10T12:21:05.35106654Z + restimestampmock: 2025-11-10T12:21:05.351262508Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1331 +spec: + metadata: + connID: "432" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777265 + reqtimestampmock: 2025-11-10T12:21:05.351472476Z + restimestampmock: 2025-11-10T12:21:05.351607215Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1332 +spec: + metadata: + connID: "432" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777265 + reqtimestampmock: 2025-11-10T12:21:05.351749674Z + restimestampmock: 2025-11-10T12:21:05.351837503Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1333 +spec: + metadata: + connID: "432" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('87d0e4a5-0b24-4869-bf5d-258cd2a17ff5', 'order_user_other', 'order_user_other@example.in', 'scrypt:32768:8:1$djGCVj7cMBvutvTt$15b89daaaefd85716aae7802c876ee79f8add72d0b74f3532c184bb3438d5da9845c528b495bb52545a294c952a5588d96a7bec286b5bab6d1ffbea811cd7676', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777265 + reqtimestampmock: 2025-11-10T12:21:05.399109487Z + restimestampmock: 2025-11-10T12:21:05.399584932Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1334 +spec: + metadata: + connID: "432" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777265 + reqtimestampmock: 2025-11-10T12:21:05.399738122Z + restimestampmock: 2025-11-10T12:21:05.412091867Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1335 +spec: + metadata: + connID: "434" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [254, 122, 84, 5, 145, 230, 149, 197, 139, 34, 157, 123, 61, 31, 189, 121, 189, 255, 151, 58, 206, 119, 40, 72, 159, 250, 134, 169, 151, 152, 106, 193] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 368 + auth_plugin_data: [62, 113, 17, 115, 41, 26, 23, 33, 89, 6, 91, 65, 113, 78, 82, 100, 26, 61, 2, 14, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777266 + reqtimestampmock: 2025-11-10T12:21:06.256184982Z + restimestampmock: 2025-11-10T12:21:06.262252421Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1336 +spec: + metadata: + connID: "434" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777266 + reqtimestampmock: 2025-11-10T12:21:06.26240683Z + restimestampmock: 2025-11-10T12:21:06.262578488Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1337 +spec: + metadata: + connID: "434" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777266 + reqtimestampmock: 2025-11-10T12:21:06.262683348Z + restimestampmock: 2025-11-10T12:21:06.262829686Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1338 +spec: + metadata: + connID: "434" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777266 + reqtimestampmock: 2025-11-10T12:21:06.262919716Z + restimestampmock: 2025-11-10T12:21:06.263002775Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1339 +spec: + metadata: + connID: "434" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 92 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='auth_user_for_other' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 251 + sequence_id: 6 + values: + - type: 253 + name: id + value: 777b90d1-71b1-4983-942d-656250aac340 + unsigned: false + - type: 253 + name: username + value: auth_user_for_other + unsigned: false + - type: 253 + name: email + value: auth_user_for_other@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$hevjeMRkhBKIkxD7$49104df42aa01a53559987d12cf199f2772afad34640c5142f21c6844c3d8db312d0df806600a93a8bf8e56020577fcfe0c4056fa631132919f78e78447e1f21 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777266 + reqtimestampmock: 2025-11-10T12:21:06.263180144Z + restimestampmock: 2025-11-10T12:21:06.26354704Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1340 +spec: + metadata: + connID: "436" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [81, 112, 167, 153, 171, 0, 111, 81, 209, 207, 144, 81, 187, 222, 76, 55, 151, 210, 3, 107, 244, 105, 224, 64, 132, 150, 161, 195, 191, 86, 240, 157] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 369 + auth_plugin_data: [113, 19, 91, 71, 76, 82, 79, 95, 70, 105, 33, 26, 126, 98, 107, 72, 37, 16, 8, 57, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777267 + reqtimestampmock: 2025-11-10T12:21:07.838312132Z + restimestampmock: 2025-11-10T12:21:07.844767938Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1341 +spec: + metadata: + connID: "436" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777267 + reqtimestampmock: 2025-11-10T12:21:07.844988936Z + restimestampmock: 2025-11-10T12:21:07.845249174Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1342 +spec: + metadata: + connID: "436" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777267 + reqtimestampmock: 2025-11-10T12:21:07.845364242Z + restimestampmock: 2025-11-10T12:21:07.845565561Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1343 +spec: + metadata: + connID: "436" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777267 + reqtimestampmock: 2025-11-10T12:21:07.845678301Z + restimestampmock: 2025-11-10T12:21:07.845796739Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1344 +spec: + metadata: + connID: "436" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='777b90d1-71b1-4983-942d-656250aac340' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 777b90d1-71b1-4983-942d-656250aac340 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777267 + reqtimestampmock: 2025-11-10T12:21:07.845968598Z + restimestampmock: 2025-11-10T12:21:07.846425664Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1345 +spec: + metadata: + connID: "436" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='777b90d1-71b1-4983-942d-656250aac340' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777267 + reqtimestampmock: 2025-11-10T12:21:07.846625942Z + restimestampmock: 2025-11-10T12:21:07.84686263Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1346 +spec: + metadata: + connID: "436" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='777b90d1-71b1-4983-942d-656250aac340' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777267 + reqtimestampmock: 2025-11-10T12:21:07.847032908Z + restimestampmock: 2025-11-10T12:21:07.847437225Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1347 +spec: + metadata: + connID: "436" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777267 + reqtimestampmock: 2025-11-10T12:21:07.847887522Z + restimestampmock: 2025-11-10T12:21:07.858007847Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1348 +spec: + metadata: + connID: "438" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [198, 89, 100, 49, 221, 155, 29, 176, 77, 93, 157, 215, 68, 158, 28, 171, 130, 30, 27, 36, 2, 136, 106, 170, 199, 138, 202, 5, 55, 77, 8, 231] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 370 + auth_plugin_data: [109, 51, 10, 29, 4, 35, 16, 86, 61, 22, 75, 94, 111, 7, 111, 21, 105, 21, 86, 98, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777268 + reqtimestampmock: 2025-11-10T12:21:08.593636353Z + restimestampmock: 2025-11-10T12:21:08.600029839Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1349 +spec: + metadata: + connID: "438" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777268 + reqtimestampmock: 2025-11-10T12:21:08.600195807Z + restimestampmock: 2025-11-10T12:21:08.600371046Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1350 +spec: + metadata: + connID: "438" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777268 + reqtimestampmock: 2025-11-10T12:21:08.600485875Z + restimestampmock: 2025-11-10T12:21:08.600736443Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1351 +spec: + metadata: + connID: "438" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777268 + reqtimestampmock: 2025-11-10T12:21:08.600882502Z + restimestampmock: 2025-11-10T12:21:08.600989471Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1352 +spec: + metadata: + connID: "438" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='order_user_other' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: 87d0e4a5-0b24-4869-bf5d-258cd2a17ff5 + unsigned: false + - type: 253 + name: username + value: order_user_other + unsigned: false + - type: 253 + name: email + value: order_user_other@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$djGCVj7cMBvutvTt$15b89daaaefd85716aae7802c876ee79f8add72d0b74f3532c184bb3438d5da9845c528b495bb52545a294c952a5588d96a7bec286b5bab6d1ffbea811cd7676 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777268 + reqtimestampmock: 2025-11-10T12:21:08.601191339Z + restimestampmock: 2025-11-10T12:21:08.601613866Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1353 +spec: + metadata: + connID: "440" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [227, 77, 26, 205, 139, 52, 22, 168, 185, 168, 109, 242, 140, 76, 49, 134, 211, 33, 189, 234, 80, 198, 12, 214, 150, 185, 80, 230, 68, 109, 154, 168] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 372 + auth_plugin_data: [7, 12, 116, 63, 99, 15, 37, 18, 114, 83, 29, 105, 47, 22, 93, 88, 14, 74, 108, 62, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777269 + reqtimestampmock: 2025-11-10T12:21:09.427582144Z + restimestampmock: 2025-11-10T12:21:09.434038589Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1354 +spec: + metadata: + connID: "440" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777269 + reqtimestampmock: 2025-11-10T12:21:09.434212378Z + restimestampmock: 2025-11-10T12:21:09.434383017Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1355 +spec: + metadata: + connID: "440" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777269 + reqtimestampmock: 2025-11-10T12:21:09.434503265Z + restimestampmock: 2025-11-10T12:21:09.434723413Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1356 +spec: + metadata: + connID: "440" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777269 + reqtimestampmock: 2025-11-10T12:21:09.434871182Z + restimestampmock: 2025-11-10T12:21:09.435018051Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1357 +spec: + metadata: + connID: "440" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='87d0e4a5-0b24-4869-bf5d-258cd2a17ff5' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 87d0e4a5-0b24-4869-bf5d-258cd2a17ff5 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777269 + reqtimestampmock: 2025-11-10T12:21:09.435247749Z + restimestampmock: 2025-11-10T12:21:09.435621386Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1358 +spec: + metadata: + connID: "440" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='87d0e4a5-0b24-4869-bf5d-258cd2a17ff5' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777269 + reqtimestampmock: 2025-11-10T12:21:09.435745565Z + restimestampmock: 2025-11-10T12:21:09.435944262Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1359 +spec: + metadata: + connID: "440" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='87d0e4a5-0b24-4869-bf5d-258cd2a17ff5' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777269 + reqtimestampmock: 2025-11-10T12:21:09.436363669Z + restimestampmock: 2025-11-10T12:21:09.436398329Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1360 +spec: + metadata: + connID: "440" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777269 + reqtimestampmock: 2025-11-10T12:21:09.436541999Z + restimestampmock: 2025-11-10T12:21:09.447891613Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1361 +spec: + metadata: + connID: "442" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [239, 107, 63, 220, 241, 196, 128, 152, 141, 106, 43, 22, 187, 76, 155, 126, 12, 196, 148, 92, 235, 140, 251, 3, 87, 66, 19, 163, 227, 129, 15, 119] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 373 + auth_plugin_data: [111, 77, 117, 71, 24, 125, 109, 37, 81, 53, 125, 27, 126, 69, 54, 88, 55, 2, 67, 1, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.035388866Z + restimestampmock: 2025-11-10T12:21:12.041484416Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1362 +spec: + metadata: + connID: "442" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.041651153Z + restimestampmock: 2025-11-10T12:21:12.041838562Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1363 +spec: + metadata: + connID: "442" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.041950281Z + restimestampmock: 2025-11-10T12:21:12.04209029Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1364 +spec: + metadata: + connID: "442" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.042200129Z + restimestampmock: 2025-11-10T12:21:12.042319748Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1365 +spec: + metadata: + connID: "442" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 337 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('531eb4e5-9d74-4017-a6ff-a400e1a169c8', 'user_no_addresses', 'user_no_addresses@example.in', 'scrypt:32768:8:1$sf01yWa9KDkpyuWV$c374c1e3e79b070943708f241c2fc18e67c913c972298317ec11243844fc51c8859b7cff68f98469b22ac83de253178d79b1be5ab9511e7f7f9cc5ed65dbe434', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.091507725Z + restimestampmock: 2025-11-10T12:21:12.091949401Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1366 +spec: + metadata: + connID: "442" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.092052671Z + restimestampmock: 2025-11-10T12:21:12.101036666Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1367 +spec: + metadata: + connID: "444" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [17, 241, 4, 17, 83, 233, 177, 138, 134, 35, 130, 185, 144, 9, 37, 15, 61, 99, 225, 115, 107, 104, 132, 239, 34, 172, 46, 104, 217, 222, 58, 250] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 374 + auth_plugin_data: [1, 116, 125, 118, 24, 41, 67, 2, 39, 105, 9, 50, 92, 119, 37, 64, 9, 118, 111, 94, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.787466065Z + restimestampmock: 2025-11-10T12:21:12.793754642Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1368 +spec: + metadata: + connID: "444" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.79395151Z + restimestampmock: 2025-11-10T12:21:12.794102609Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1369 +spec: + metadata: + connID: "444" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.794297958Z + restimestampmock: 2025-11-10T12:21:12.794461606Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1370 +spec: + metadata: + connID: "444" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.794605795Z + restimestampmock: 2025-11-10T12:21:12.794750074Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1371 +spec: + metadata: + connID: "444" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 90 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='user_no_addresses' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 247 + sequence_id: 6 + values: + - type: 253 + name: id + value: 531eb4e5-9d74-4017-a6ff-a400e1a169c8 + unsigned: false + - type: 253 + name: username + value: user_no_addresses + unsigned: false + - type: 253 + name: email + value: user_no_addresses@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$sf01yWa9KDkpyuWV$c374c1e3e79b070943708f241c2fc18e67c913c972298317ec11243844fc51c8859b7cff68f98469b22ac83de253178d79b1be5ab9511e7f7f9cc5ed65dbe434 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777272 + reqtimestampmock: 2025-11-10T12:21:12.795120451Z + restimestampmock: 2025-11-10T12:21:12.795661176Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1372 +spec: + metadata: + connID: "446" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [68, 176, 97, 236, 93, 200, 235, 3, 1, 168, 246, 87, 71, 39, 54, 2, 62, 126, 144, 144, 36, 47, 141, 173, 67, 53, 250, 41, 254, 26, 157, 135] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 375 + auth_plugin_data: [64, 91, 42, 1, 6, 58, 48, 5, 125, 98, 106, 32, 80, 1, 11, 72, 24, 111, 3, 120, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777273 + reqtimestampmock: 2025-11-10T12:21:13.627414507Z + restimestampmock: 2025-11-10T12:21:13.633652805Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1373 +spec: + metadata: + connID: "446" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777273 + reqtimestampmock: 2025-11-10T12:21:13.633783823Z + restimestampmock: 2025-11-10T12:21:13.633967372Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1374 +spec: + metadata: + connID: "446" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777273 + reqtimestampmock: 2025-11-10T12:21:13.634056231Z + restimestampmock: 2025-11-10T12:21:13.634217979Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1375 +spec: + metadata: + connID: "446" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777273 + reqtimestampmock: 2025-11-10T12:21:13.63432061Z + restimestampmock: 2025-11-10T12:21:13.634403048Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1376 +spec: + metadata: + connID: "446" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='531eb4e5-9d74-4017-a6ff-a400e1a169c8' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 531eb4e5-9d74-4017-a6ff-a400e1a169c8 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777273 + reqtimestampmock: 2025-11-10T12:21:13.634572307Z + restimestampmock: 2025-11-10T12:21:13.634862034Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1377 +spec: + metadata: + connID: "446" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 190 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, line1, line2, city, state, postal_code, country, phone, is_default FROM addresses WHERE user_id='531eb4e5-9d74-4017-a6ff-a400e1a169c8' ORDER BY is_default DESC, created_at DESC + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 9 + columns: + - header: + payload_length: 51 + sequence_id: 2 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 3 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line1 + org_name: line1 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 4 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: line2 + org_name: line2 + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 5 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: city + org_name: city + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 6 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: state + org_name: state + fixed_length: 12 + character_set: 255 + column_length: 400 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 69 + sequence_id: 7 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: postal_code + org_name: postal_code + fixed_length: 12 + character_set: 255 + column_length: 80 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 61 + sequence_id: 8 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: country + org_name: country + fixed_length: 12 + character_set: 255 + column_length: 8 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 57 + sequence_id: 9 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: phone + org_name: phone + fixed_length: 12 + character_set: 255 + column_length: 128 + type: 253 + flags: 0 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 67 + sequence_id: 10 + catalog: def + schema: user_db + table: addresses + org_table: addresses + name: is_default + org_name: is_default + fixed_length: 12 + character_set: 63 + column_length: 1 + type: 1 + flags: 1 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: [] + FinalResponse: + data: + - 7 + - 0 + - 0 + - 11 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777273 + reqtimestampmock: 2025-11-10T12:21:13.635043743Z + restimestampmock: 2025-11-10T12:21:13.635488399Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1378 +spec: + metadata: + connID: "448" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [196, 213, 205, 106, 107, 202, 242, 248, 90, 103, 141, 127, 173, 172, 72, 170, 253, 160, 49, 10, 30, 158, 167, 133, 207, 195, 45, 77, 39, 196, 23, 132] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 376 + auth_plugin_data: [20, 82, 116, 118, 14, 103, 71, 60, 124, 15, 3, 41, 84, 44, 33, 1, 54, 45, 83, 126, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777274 + reqtimestampmock: 2025-11-10T12:21:14.359531043Z + restimestampmock: 2025-11-10T12:21:14.366090508Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1379 +spec: + metadata: + connID: "448" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777274 + reqtimestampmock: 2025-11-10T12:21:14.366293555Z + restimestampmock: 2025-11-10T12:21:14.366487665Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1380 +spec: + metadata: + connID: "448" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777274 + reqtimestampmock: 2025-11-10T12:21:14.366592864Z + restimestampmock: 2025-11-10T12:21:14.366738242Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1381 +spec: + metadata: + connID: "448" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777274 + reqtimestampmock: 2025-11-10T12:21:14.366862542Z + restimestampmock: 2025-11-10T12:21:14.366950781Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1382 +spec: + metadata: + connID: "448" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='531eb4e5-9d74-4017-a6ff-a400e1a169c8' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 531eb4e5-9d74-4017-a6ff-a400e1a169c8 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777274 + reqtimestampmock: 2025-11-10T12:21:14.367119289Z + restimestampmock: 2025-11-10T12:21:14.367438526Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1383 +spec: + metadata: + connID: "448" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='531eb4e5-9d74-4017-a6ff-a400e1a169c8' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777274 + reqtimestampmock: 2025-11-10T12:21:14.367921752Z + restimestampmock: 2025-11-10T12:21:14.36816161Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1384 +spec: + metadata: + connID: "448" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='531eb4e5-9d74-4017-a6ff-a400e1a169c8' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777274 + reqtimestampmock: 2025-11-10T12:21:14.368290439Z + restimestampmock: 2025-11-10T12:21:14.368575437Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1385 +spec: + metadata: + connID: "448" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777274 + reqtimestampmock: 2025-11-10T12:21:14.368665247Z + restimestampmock: 2025-11-10T12:21:14.379944021Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1386 +spec: + metadata: + connID: "450" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [220, 146, 251, 169, 123, 178, 65, 123, 52, 173, 12, 216, 42, 205, 153, 184, 48, 3, 242, 45, 177, 118, 94, 59, 110, 195, 248, 56, 71, 165, 195, 122] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 378 + auth_plugin_data: [27, 46, 77, 122, 71, 91, 72, 116, 43, 77, 42, 83, 27, 88, 111, 120, 47, 12, 101, 125, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.187412395Z + restimestampmock: 2025-11-10T12:21:15.193811662Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1387 +spec: + metadata: + connID: "450" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.193966791Z + restimestampmock: 2025-11-10T12:21:15.19416392Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1388 +spec: + metadata: + connID: "450" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.194260028Z + restimestampmock: 2025-11-10T12:21:15.194403518Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1389 +spec: + metadata: + connID: "450" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.194503357Z + restimestampmock: 2025-11-10T12:21:15.194597516Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1390 +spec: + metadata: + connID: "450" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('d9d26f64-586a-45bc-b398-4ae9c5a4d9c2', 'user_no_orders', 'user_no_orders@example.in', 'scrypt:32768:8:1$OZLCDNynuJLP6t9e$b936d98e65758bd241b27c1d8c1ceab26c7cc38086efc2e43981010666c527fe201c306473268de73211cb2a958ebcadd0f945d1d83021d1c4d6a55862607dda', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.242437894Z + restimestampmock: 2025-11-10T12:21:15.24294061Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1391 +spec: + metadata: + connID: "450" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.243091598Z + restimestampmock: 2025-11-10T12:21:15.25592753Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1392 +spec: + metadata: + connID: "452" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [90, 71, 242, 237, 86, 117, 170, 188, 135, 224, 115, 27, 199, 194, 175, 255, 52, 1, 89, 4, 111, 140, 207, 132, 241, 149, 112, 251, 128, 95, 196, 151] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 379 + auth_plugin_data: [23, 111, 92, 14, 116, 125, 13, 122, 103, 56, 54, 118, 116, 108, 1, 49, 121, 85, 20, 68, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.942840118Z + restimestampmock: 2025-11-10T12:21:15.953957965Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1393 +spec: + metadata: + connID: "452" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.954127163Z + restimestampmock: 2025-11-10T12:21:15.954326391Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1394 +spec: + metadata: + connID: "452" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.954432971Z + restimestampmock: 2025-11-10T12:21:15.95458077Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1395 +spec: + metadata: + connID: "452" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.954683918Z + restimestampmock: 2025-11-10T12:21:15.954806717Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1396 +spec: + metadata: + connID: "452" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='user_no_orders' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: d9d26f64-586a-45bc-b398-4ae9c5a4d9c2 + unsigned: false + - type: 253 + name: username + value: user_no_orders + unsigned: false + - type: 253 + name: email + value: user_no_orders@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$OZLCDNynuJLP6t9e$b936d98e65758bd241b27c1d8c1ceab26c7cc38086efc2e43981010666c527fe201c306473268de73211cb2a958ebcadd0f945d1d83021d1c4d6a55862607dda + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777275 + reqtimestampmock: 2025-11-10T12:21:15.954965996Z + restimestampmock: 2025-11-10T12:21:15.955484102Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1397 +spec: + metadata: + connID: "454" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [84, 226, 31, 85, 167, 18, 253, 100, 123, 158, 52, 230, 230, 1, 251, 216, 189, 205, 180, 190, 234, 144, 155, 181, 61, 133, 124, 241, 191, 86, 175, 106] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 380 + auth_plugin_data: [116, 62, 1, 71, 15, 83, 37, 89, 72, 87, 110, 31, 30, 94, 31, 8, 104, 82, 51, 61, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777277 + reqtimestampmock: 2025-11-10T12:21:17.505239099Z + restimestampmock: 2025-11-10T12:21:17.512100742Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1398 +spec: + metadata: + connID: "454" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777277 + reqtimestampmock: 2025-11-10T12:21:17.512434249Z + restimestampmock: 2025-11-10T12:21:17.51345688Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1399 +spec: + metadata: + connID: "454" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777277 + reqtimestampmock: 2025-11-10T12:21:17.513600249Z + restimestampmock: 2025-11-10T12:21:17.513751848Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1400 +spec: + metadata: + connID: "454" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777277 + reqtimestampmock: 2025-11-10T12:21:17.513877147Z + restimestampmock: 2025-11-10T12:21:17.513992136Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1401 +spec: + metadata: + connID: "454" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='d9d26f64-586a-45bc-b398-4ae9c5a4d9c2' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: d9d26f64-586a-45bc-b398-4ae9c5a4d9c2 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777277 + reqtimestampmock: 2025-11-10T12:21:17.514199444Z + restimestampmock: 2025-11-10T12:21:17.514621111Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1402 +spec: + metadata: + connID: "454" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='d9d26f64-586a-45bc-b398-4ae9c5a4d9c2' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777277 + reqtimestampmock: 2025-11-10T12:21:17.51478448Z + restimestampmock: 2025-11-10T12:21:17.515046328Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1403 +spec: + metadata: + connID: "454" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='d9d26f64-586a-45bc-b398-4ae9c5a4d9c2' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777277 + reqtimestampmock: 2025-11-10T12:21:17.515225545Z + restimestampmock: 2025-11-10T12:21:17.515571012Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1404 +spec: + metadata: + connID: "454" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777277 + reqtimestampmock: 2025-11-10T12:21:17.515644182Z + restimestampmock: 2025-11-10T12:21:17.526937647Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1405 +spec: + metadata: + connID: "456" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [75, 99, 75, 57, 107, 104, 119, 63, 27, 18, 168, 165, 3, 122, 164, 126, 28, 188, 156, 1, 71, 22, 81, 104, 12, 209, 46, 175, 98, 154, 182, 135] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 382 + auth_plugin_data: [17, 10, 64, 34, 43, 112, 29, 67, 27, 65, 71, 88, 90, 52, 94, 104, 57, 70, 5, 66, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777281 + reqtimestampmock: 2025-11-10T12:21:21.294724649Z + restimestampmock: 2025-11-10T12:21:21.301112375Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1406 +spec: + metadata: + connID: "456" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777281 + reqtimestampmock: 2025-11-10T12:21:21.301278074Z + restimestampmock: 2025-11-10T12:21:21.301485832Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1407 +spec: + metadata: + connID: "456" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777281 + reqtimestampmock: 2025-11-10T12:21:21.301610381Z + restimestampmock: 2025-11-10T12:21:21.30179131Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1408 +spec: + metadata: + connID: "456" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777281 + reqtimestampmock: 2025-11-10T12:21:21.301893749Z + restimestampmock: 2025-11-10T12:21:21.302016908Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1409 +spec: + metadata: + connID: "456" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('f77a805c-a2c8-4646-a16b-1e1cd58249ff', 'addr_bad_json', 'addr_bad_json@example.in', 'scrypt:32768:8:1$aoH11mV085I7c8Ld$2203b3271c757b198a22892848b06df64930c1b09edcb1339112ab41bddb8171a89b1da361544a891790d2a83fd300c60b5dc4557d97e71b5563860dd68bb7dc', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777281 + reqtimestampmock: 2025-11-10T12:21:21.351729701Z + restimestampmock: 2025-11-10T12:21:21.352254746Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1410 +spec: + metadata: + connID: "456" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777281 + reqtimestampmock: 2025-11-10T12:21:21.352434285Z + restimestampmock: 2025-11-10T12:21:21.36492282Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1411 +spec: + metadata: + connID: "458" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [56, 113, 61, 191, 78, 223, 128, 175, 78, 8, 228, 236, 194, 97, 145, 149, 14, 63, 13, 13, 35, 131, 48, 137, 94, 110, 147, 242, 122, 238, 165, 221] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 383 + auth_plugin_data: [8, 21, 47, 72, 2, 4, 67, 1, 46, 67, 76, 71, 75, 62, 120, 93, 87, 118, 41, 8, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777282 + reqtimestampmock: 2025-11-10T12:21:22.133575063Z + restimestampmock: 2025-11-10T12:21:22.139588902Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1412 +spec: + metadata: + connID: "458" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777282 + reqtimestampmock: 2025-11-10T12:21:22.139745071Z + restimestampmock: 2025-11-10T12:21:22.139933338Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1413 +spec: + metadata: + connID: "458" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777282 + reqtimestampmock: 2025-11-10T12:21:22.140071088Z + restimestampmock: 2025-11-10T12:21:22.140293735Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1414 +spec: + metadata: + connID: "458" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777282 + reqtimestampmock: 2025-11-10T12:21:22.140394716Z + restimestampmock: 2025-11-10T12:21:22.140496445Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1415 +spec: + metadata: + connID: "458" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='addr_bad_json' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: f77a805c-a2c8-4646-a16b-1e1cd58249ff + unsigned: false + - type: 253 + name: username + value: addr_bad_json + unsigned: false + - type: 253 + name: email + value: addr_bad_json@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$aoH11mV085I7c8Ld$2203b3271c757b198a22892848b06df64930c1b09edcb1339112ab41bddb8171a89b1da361544a891790d2a83fd300c60b5dc4557d97e71b5563860dd68bb7dc + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777282 + reqtimestampmock: 2025-11-10T12:21:22.140751702Z + restimestampmock: 2025-11-10T12:21:22.141293648Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1416 +spec: + metadata: + connID: "460" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [107, 65, 101, 55, 242, 89, 13, 32, 20, 116, 75, 119, 87, 82, 148, 91, 38, 155, 223, 114, 27, 121, 209, 96, 203, 33, 174, 166, 221, 158, 173, 28] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 384 + auth_plugin_data: [99, 104, 2, 16, 51, 109, 6, 99, 98, 33, 85, 33, 21, 8, 125, 115, 99, 74, 61, 43, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777283 + reqtimestampmock: 2025-11-10T12:21:23.794027235Z + restimestampmock: 2025-11-10T12:21:23.800898418Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1417 +spec: + metadata: + connID: "460" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777283 + reqtimestampmock: 2025-11-10T12:21:23.801067676Z + restimestampmock: 2025-11-10T12:21:23.801288074Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1418 +spec: + metadata: + connID: "460" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777283 + reqtimestampmock: 2025-11-10T12:21:23.801404734Z + restimestampmock: 2025-11-10T12:21:23.801567832Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1419 +spec: + metadata: + connID: "460" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777283 + reqtimestampmock: 2025-11-10T12:21:23.801663961Z + restimestampmock: 2025-11-10T12:21:23.801760711Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1420 +spec: + metadata: + connID: "460" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='f77a805c-a2c8-4646-a16b-1e1cd58249ff' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: f77a805c-a2c8-4646-a16b-1e1cd58249ff + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777283 + reqtimestampmock: 2025-11-10T12:21:23.801940309Z + restimestampmock: 2025-11-10T12:21:23.802329186Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1421 +spec: + metadata: + connID: "460" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='f77a805c-a2c8-4646-a16b-1e1cd58249ff' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777283 + reqtimestampmock: 2025-11-10T12:21:23.802457724Z + restimestampmock: 2025-11-10T12:21:23.802695852Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1422 +spec: + metadata: + connID: "460" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='f77a805c-a2c8-4646-a16b-1e1cd58249ff' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777283 + reqtimestampmock: 2025-11-10T12:21:23.802839441Z + restimestampmock: 2025-11-10T12:21:23.803170929Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1423 +spec: + metadata: + connID: "460" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777283 + reqtimestampmock: 2025-11-10T12:21:23.803275138Z + restimestampmock: 2025-11-10T12:21:23.81484981Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1424 +spec: + metadata: + connID: "462" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [41, 140, 177, 70, 244, 81, 50, 26, 99, 86, 49, 78, 237, 85, 135, 213, 74, 236, 149, 77, 110, 252, 108, 216, 232, 223, 207, 80, 76, 245, 4, 250] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 386 + auth_plugin_data: [18, 2, 104, 67, 46, 62, 104, 96, 119, 78, 116, 41, 33, 113, 13, 84, 26, 22, 1, 97, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777284 + reqtimestampmock: 2025-11-10T12:21:24.618905786Z + restimestampmock: 2025-11-10T12:21:24.625330363Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1425 +spec: + metadata: + connID: "462" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777284 + reqtimestampmock: 2025-11-10T12:21:24.625515931Z + restimestampmock: 2025-11-10T12:21:24.625672859Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1426 +spec: + metadata: + connID: "462" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777284 + reqtimestampmock: 2025-11-10T12:21:24.625866818Z + restimestampmock: 2025-11-10T12:21:24.626094056Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1427 +spec: + metadata: + connID: "462" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777284 + reqtimestampmock: 2025-11-10T12:21:24.626257865Z + restimestampmock: 2025-11-10T12:21:24.626346564Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1428 +spec: + metadata: + connID: "462" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('bc280dae-7857-442d-ad52-1883bd5394f6', 'order_bad_json', 'order_bad_json@example.in', 'scrypt:32768:8:1$Imj8TvNOp1ZPSXJp$563d57685696d2f795679b19299123addbdec8fc7ef0f69585c3f1d7cfb40f789e12e51b3a0cb8f2d60c12d7a58c92995b96676827bae99ed1537c4e24833b31', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777284 + reqtimestampmock: 2025-11-10T12:21:24.676230966Z + restimestampmock: 2025-11-10T12:21:24.676649871Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1429 +spec: + metadata: + connID: "462" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777284 + reqtimestampmock: 2025-11-10T12:21:24.676771771Z + restimestampmock: 2025-11-10T12:21:24.68883885Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1430 +spec: + metadata: + connID: "464" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [248, 227, 26, 202, 71, 116, 175, 71, 82, 162, 37, 75, 13, 89, 103, 145, 236, 39, 122, 102, 247, 46, 231, 54, 175, 50, 107, 4, 141, 158, 144, 34] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 387 + auth_plugin_data: [105, 25, 33, 53, 126, 46, 103, 3, 108, 47, 71, 104, 13, 94, 98, 79, 37, 76, 33, 108, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777285 + reqtimestampmock: 2025-11-10T12:21:25.368717427Z + restimestampmock: 2025-11-10T12:21:25.374809486Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1431 +spec: + metadata: + connID: "464" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777285 + reqtimestampmock: 2025-11-10T12:21:25.375006164Z + restimestampmock: 2025-11-10T12:21:25.375237583Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1432 +spec: + metadata: + connID: "464" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777285 + reqtimestampmock: 2025-11-10T12:21:25.375349543Z + restimestampmock: 2025-11-10T12:21:25.37549784Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1433 +spec: + metadata: + connID: "464" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777285 + reqtimestampmock: 2025-11-10T12:21:25.37560693Z + restimestampmock: 2025-11-10T12:21:25.375745259Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1434 +spec: + metadata: + connID: "464" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='order_bad_json' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: bc280dae-7857-442d-ad52-1883bd5394f6 + unsigned: false + - type: 253 + name: username + value: order_bad_json + unsigned: false + - type: 253 + name: email + value: order_bad_json@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Imj8TvNOp1ZPSXJp$563d57685696d2f795679b19299123addbdec8fc7ef0f69585c3f1d7cfb40f789e12e51b3a0cb8f2d60c12d7a58c92995b96676827bae99ed1537c4e24833b31 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777285 + reqtimestampmock: 2025-11-10T12:21:25.375965267Z + restimestampmock: 2025-11-10T12:21:25.376450313Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1435 +spec: + metadata: + connID: "466" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [152, 99, 11, 219, 17, 50, 46, 24, 112, 200, 178, 87, 190, 144, 93, 188, 125, 216, 17, 2, 70, 67, 51, 229, 214, 52, 122, 101, 91, 247, 140, 248] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 388 + auth_plugin_data: [47, 65, 70, 5, 124, 118, 19, 56, 88, 10, 80, 77, 57, 81, 74, 22, 109, 66, 104, 120, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777286 + reqtimestampmock: 2025-11-10T12:21:26.938963178Z + restimestampmock: 2025-11-10T12:21:26.9494574Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1436 +spec: + metadata: + connID: "466" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777286 + reqtimestampmock: 2025-11-10T12:21:26.949656979Z + restimestampmock: 2025-11-10T12:21:26.949847867Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1437 +spec: + metadata: + connID: "466" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777286 + reqtimestampmock: 2025-11-10T12:21:26.949969686Z + restimestampmock: 2025-11-10T12:21:26.950131245Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1438 +spec: + metadata: + connID: "466" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777286 + reqtimestampmock: 2025-11-10T12:21:26.950239644Z + restimestampmock: 2025-11-10T12:21:26.950357413Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1439 +spec: + metadata: + connID: "466" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='bc280dae-7857-442d-ad52-1883bd5394f6' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: bc280dae-7857-442d-ad52-1883bd5394f6 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777286 + reqtimestampmock: 2025-11-10T12:21:26.950506561Z + restimestampmock: 2025-11-10T12:21:26.950916238Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1440 +spec: + metadata: + connID: "466" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='bc280dae-7857-442d-ad52-1883bd5394f6' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777286 + reqtimestampmock: 2025-11-10T12:21:26.951032837Z + restimestampmock: 2025-11-10T12:21:26.951385524Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1441 +spec: + metadata: + connID: "466" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='bc280dae-7857-442d-ad52-1883bd5394f6' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777286 + reqtimestampmock: 2025-11-10T12:21:26.951651722Z + restimestampmock: 2025-11-10T12:21:26.951992869Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1442 +spec: + metadata: + connID: "466" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777286 + reqtimestampmock: 2025-11-10T12:21:26.952119977Z + restimestampmock: 2025-11-10T12:21:26.963815179Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1443 +spec: + metadata: + connID: "468" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [75, 222, 125, 3, 120, 245, 231, 223, 126, 225, 121, 135, 61, 211, 114, 33, 159, 4, 32, 90, 150, 182, 2, 141, 246, 0, 160, 63, 242, 15, 156, 100] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 389 + auth_plugin_data: [91, 40, 97, 56, 78, 57, 96, 98, 43, 32, 62, 4, 109, 86, 40, 111, 102, 86, 13, 60, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777289 + reqtimestampmock: 2025-11-10T12:21:29.167404331Z + restimestampmock: 2025-11-10T12:21:29.173743398Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1444 +spec: + metadata: + connID: "468" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777289 + reqtimestampmock: 2025-11-10T12:21:29.173958896Z + restimestampmock: 2025-11-10T12:21:29.174127355Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1445 +spec: + metadata: + connID: "468" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777289 + reqtimestampmock: 2025-11-10T12:21:29.174260454Z + restimestampmock: 2025-11-10T12:21:29.174438142Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1446 +spec: + metadata: + connID: "468" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777289 + reqtimestampmock: 2025-11-10T12:21:29.174932649Z + restimestampmock: 2025-11-10T12:21:29.175059758Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1447 +spec: + metadata: + connID: "468" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('a8f32eb8-2e36-4603-8ba1-182b5a91d48b', 'reserve_bad_json', 'reserve_bad_json@example.in', 'scrypt:32768:8:1$QN9eYMnVwlCv2KWp$81017dcdd8ac9056c5270c2f23e7929955286bce1a6e69a7b1596bd1f3f199ede45873b2d567598df07570a4cd96c4d9a49f7e86e9225fe7914e7ac86f96895c', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777289 + reqtimestampmock: 2025-11-10T12:21:29.225244326Z + restimestampmock: 2025-11-10T12:21:29.225716102Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1448 +spec: + metadata: + connID: "468" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777289 + reqtimestampmock: 2025-11-10T12:21:29.225843791Z + restimestampmock: 2025-11-10T12:21:29.237807221Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1449 +spec: + metadata: + connID: "470" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [82, 170, 203, 49, 171, 89, 196, 132, 62, 173, 134, 41, 35, 153, 82, 15, 202, 214, 217, 207, 38, 10, 40, 84, 134, 122, 241, 41, 121, 25, 119, 240] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 391 + auth_plugin_data: [99, 101, 48, 104, 54, 13, 90, 34, 115, 93, 99, 90, 67, 98, 60, 77, 79, 47, 19, 40, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777290 + reqtimestampmock: 2025-11-10T12:21:30.001288369Z + restimestampmock: 2025-11-10T12:21:30.012099928Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1450 +spec: + metadata: + connID: "470" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777290 + reqtimestampmock: 2025-11-10T12:21:30.012320226Z + restimestampmock: 2025-11-10T12:21:30.012498615Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1451 +spec: + metadata: + connID: "470" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777290 + reqtimestampmock: 2025-11-10T12:21:30.012596864Z + restimestampmock: 2025-11-10T12:21:30.012751763Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1452 +spec: + metadata: + connID: "470" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777290 + reqtimestampmock: 2025-11-10T12:21:30.012871332Z + restimestampmock: 2025-11-10T12:21:30.01297015Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1453 +spec: + metadata: + connID: "470" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='reserve_bad_json' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: a8f32eb8-2e36-4603-8ba1-182b5a91d48b + unsigned: false + - type: 253 + name: username + value: reserve_bad_json + unsigned: false + - type: 253 + name: email + value: reserve_bad_json@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$QN9eYMnVwlCv2KWp$81017dcdd8ac9056c5270c2f23e7929955286bce1a6e69a7b1596bd1f3f199ede45873b2d567598df07570a4cd96c4d9a49f7e86e9225fe7914e7ac86f96895c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777290 + reqtimestampmock: 2025-11-10T12:21:30.01316317Z + restimestampmock: 2025-11-10T12:21:30.013567806Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1454 +spec: + metadata: + connID: "472" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [101, 107, 121, 98, 42, 77, 56, 207, 159, 83, 108, 50, 164, 251, 214, 173, 116, 23, 123, 237, 61, 173, 205, 147, 62, 53, 116, 199, 37, 250, 54, 200] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 392 + auth_plugin_data: [43, 86, 53, 24, 85, 106, 90, 73, 56, 86, 24, 71, 127, 31, 126, 85, 67, 93, 120, 62, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777291 + reqtimestampmock: 2025-11-10T12:21:31.665894329Z + restimestampmock: 2025-11-10T12:21:31.672719712Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1455 +spec: + metadata: + connID: "472" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777291 + reqtimestampmock: 2025-11-10T12:21:31.67298388Z + restimestampmock: 2025-11-10T12:21:31.673190798Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1456 +spec: + metadata: + connID: "472" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777291 + reqtimestampmock: 2025-11-10T12:21:31.673350547Z + restimestampmock: 2025-11-10T12:21:31.673504766Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1457 +spec: + metadata: + connID: "472" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777291 + reqtimestampmock: 2025-11-10T12:21:31.673631705Z + restimestampmock: 2025-11-10T12:21:31.673743124Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1458 +spec: + metadata: + connID: "472" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a8f32eb8-2e36-4603-8ba1-182b5a91d48b' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a8f32eb8-2e36-4603-8ba1-182b5a91d48b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777291 + reqtimestampmock: 2025-11-10T12:21:31.673955762Z + restimestampmock: 2025-11-10T12:21:31.674370479Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1459 +spec: + metadata: + connID: "472" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='a8f32eb8-2e36-4603-8ba1-182b5a91d48b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777291 + reqtimestampmock: 2025-11-10T12:21:31.674549917Z + restimestampmock: 2025-11-10T12:21:31.675261811Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1460 +spec: + metadata: + connID: "472" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='a8f32eb8-2e36-4603-8ba1-182b5a91d48b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777291 + reqtimestampmock: 2025-11-10T12:21:31.67541249Z + restimestampmock: 2025-11-10T12:21:31.675744637Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1461 +spec: + metadata: + connID: "472" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777291 + reqtimestampmock: 2025-11-10T12:21:31.675863916Z + restimestampmock: 2025-11-10T12:21:31.687008013Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1462 +spec: + metadata: + connID: "474" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [229, 142, 102, 142, 139, 114, 155, 58, 172, 154, 113, 107, 209, 234, 118, 192, 231, 46, 224, 156, 30, 182, 240, 220, 135, 91, 230, 225, 57, 39, 139, 183] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 393 + auth_plugin_data: [32, 42, 114, 124, 101, 55, 1, 101, 76, 25, 5, 78, 98, 79, 21, 112, 92, 65, 87, 52, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777293 + reqtimestampmock: 2025-11-10T12:21:33.765483424Z + restimestampmock: 2025-11-10T12:21:33.772433366Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1463 +spec: + metadata: + connID: "474" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777293 + reqtimestampmock: 2025-11-10T12:21:33.772688734Z + restimestampmock: 2025-11-10T12:21:33.772894351Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1464 +spec: + metadata: + connID: "474" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777293 + reqtimestampmock: 2025-11-10T12:21:33.77302987Z + restimestampmock: 2025-11-10T12:21:33.773210249Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1465 +spec: + metadata: + connID: "474" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777293 + reqtimestampmock: 2025-11-10T12:21:33.773306688Z + restimestampmock: 2025-11-10T12:21:33.773414877Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1466 +spec: + metadata: + connID: "474" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 335 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('eeae17ed-c614-4cb0-bf39-c9dd9062ed3a', 'release_bad_json', 'release_bad_json@example.in', 'scrypt:32768:8:1$bBT3qSXKJfFaI9WB$34c54855700076bdbc0ab4c02e71672f0b84122887d26072142f8c26e97c50115dbdaea942eb0e745c6de9bfa93850b6b32b7eaa57ce0f73ebf102a62165c5e2', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777293 + reqtimestampmock: 2025-11-10T12:21:33.823916014Z + restimestampmock: 2025-11-10T12:21:33.824407049Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1467 +spec: + metadata: + connID: "474" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777293 + reqtimestampmock: 2025-11-10T12:21:33.824574718Z + restimestampmock: 2025-11-10T12:21:33.836868676Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1468 +spec: + metadata: + connID: "476" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [142, 155, 173, 21, 148, 106, 97, 200, 196, 83, 216, 213, 95, 185, 82, 144, 108, 132, 84, 184, 211, 207, 157, 131, 235, 158, 187, 10, 73, 70, 218, 91] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 395 + auth_plugin_data: [126, 127, 106, 50, 114, 3, 93, 63, 85, 111, 71, 48, 84, 30, 120, 1, 47, 92, 73, 113, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777294 + reqtimestampmock: 2025-11-10T12:21:34.587528422Z + restimestampmock: 2025-11-10T12:21:34.595878091Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1469 +spec: + metadata: + connID: "476" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777294 + reqtimestampmock: 2025-11-10T12:21:34.59606696Z + restimestampmock: 2025-11-10T12:21:34.596251608Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1470 +spec: + metadata: + connID: "476" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777294 + reqtimestampmock: 2025-11-10T12:21:34.596380717Z + restimestampmock: 2025-11-10T12:21:34.596506096Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1471 +spec: + metadata: + connID: "476" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777294 + reqtimestampmock: 2025-11-10T12:21:34.596598945Z + restimestampmock: 2025-11-10T12:21:34.596690785Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1472 +spec: + metadata: + connID: "476" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 89 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='release_bad_json' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 245 + sequence_id: 6 + values: + - type: 253 + name: id + value: eeae17ed-c614-4cb0-bf39-c9dd9062ed3a + unsigned: false + - type: 253 + name: username + value: release_bad_json + unsigned: false + - type: 253 + name: email + value: release_bad_json@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$bBT3qSXKJfFaI9WB$34c54855700076bdbc0ab4c02e71672f0b84122887d26072142f8c26e97c50115dbdaea942eb0e745c6de9bfa93850b6b32b7eaa57ce0f73ebf102a62165c5e2 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777294 + reqtimestampmock: 2025-11-10T12:21:34.596855473Z + restimestampmock: 2025-11-10T12:21:34.597328949Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1473 +spec: + metadata: + connID: "478" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [86, 168, 36, 64, 21, 71, 80, 3, 173, 45, 230, 162, 190, 78, 63, 106, 106, 231, 86, 222, 179, 89, 56, 160, 176, 16, 102, 85, 202, 125, 142, 93] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 396 + auth_plugin_data: [1, 110, 72, 57, 102, 26, 91, 121, 64, 7, 56, 64, 115, 82, 82, 85, 123, 118, 98, 11, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777296 + reqtimestampmock: 2025-11-10T12:21:36.173798692Z + restimestampmock: 2025-11-10T12:21:36.180143929Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1474 +spec: + metadata: + connID: "478" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777296 + reqtimestampmock: 2025-11-10T12:21:36.180334068Z + restimestampmock: 2025-11-10T12:21:36.180510506Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1475 +spec: + metadata: + connID: "478" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777296 + reqtimestampmock: 2025-11-10T12:21:36.180643185Z + restimestampmock: 2025-11-10T12:21:36.180843513Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1476 +spec: + metadata: + connID: "478" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777296 + reqtimestampmock: 2025-11-10T12:21:36.181004152Z + restimestampmock: 2025-11-10T12:21:36.18110031Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1477 +spec: + metadata: + connID: "478" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='eeae17ed-c614-4cb0-bf39-c9dd9062ed3a' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: eeae17ed-c614-4cb0-bf39-c9dd9062ed3a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777296 + reqtimestampmock: 2025-11-10T12:21:36.181292829Z + restimestampmock: 2025-11-10T12:21:36.181633436Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1478 +spec: + metadata: + connID: "478" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='eeae17ed-c614-4cb0-bf39-c9dd9062ed3a' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777296 + reqtimestampmock: 2025-11-10T12:21:36.181774635Z + restimestampmock: 2025-11-10T12:21:36.181972844Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1479 +spec: + metadata: + connID: "478" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='eeae17ed-c614-4cb0-bf39-c9dd9062ed3a' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777296 + reqtimestampmock: 2025-11-10T12:21:36.182171472Z + restimestampmock: 2025-11-10T12:21:36.18253545Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1480 +spec: + metadata: + connID: "478" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777296 + reqtimestampmock: 2025-11-10T12:21:36.182639948Z + restimestampmock: 2025-11-10T12:21:36.195658939Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1481 +spec: + metadata: + connID: "480" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [252, 212, 25, 103, 194, 158, 141, 228, 201, 64, 176, 56, 53, 93, 227, 26, 131, 97, 64, 78, 120, 32, 170, 215, 149, 130, 201, 121, 17, 195, 31, 92] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 397 + auth_plugin_data: [78, 86, 122, 19, 108, 94, 120, 124, 85, 53, 125, 93, 98, 90, 124, 24, 3, 90, 107, 49, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777298 + reqtimestampmock: 2025-11-10T12:21:38.247701516Z + restimestampmock: 2025-11-10T12:21:38.254162192Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1482 +spec: + metadata: + connID: "480" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777298 + reqtimestampmock: 2025-11-10T12:21:38.254312402Z + restimestampmock: 2025-11-10T12:21:38.25447927Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1483 +spec: + metadata: + connID: "480" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777298 + reqtimestampmock: 2025-11-10T12:21:38.254591569Z + restimestampmock: 2025-11-10T12:21:38.254718538Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1484 +spec: + metadata: + connID: "480" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777298 + reqtimestampmock: 2025-11-10T12:21:38.254815527Z + restimestampmock: 2025-11-10T12:21:38.254968006Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1485 +spec: + metadata: + connID: "480" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 321 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('f0e44763-84a3-4d1b-b31d-c2bde7baaaef', 'maria_goa', 'maria_goa@example.in', 'scrypt:32768:8:1$EhG0ocI1sXLnvQGL$d1cbd0c4a9d6e91ba0d0461cd7fc35c858b404c1c914c6fd96e6a3f36b35904971dffdbc6ba7ec9db8761a9c2cfa6191caf07c5e6a76a9500ed89b75c472bbdd', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777298 + reqtimestampmock: 2025-11-10T12:21:38.304923077Z + restimestampmock: 2025-11-10T12:21:38.305389273Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1486 +spec: + metadata: + connID: "480" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777298 + reqtimestampmock: 2025-11-10T12:21:38.305522832Z + restimestampmock: 2025-11-10T12:21:38.317625261Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1487 +spec: + metadata: + connID: "482" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [135, 1, 248, 178, 12, 170, 252, 208, 227, 87, 157, 194, 104, 209, 150, 190, 53, 191, 186, 201, 222, 233, 76, 241, 2, 6, 133, 128, 17, 39, 251, 170] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 398 + auth_plugin_data: [33, 61, 84, 1, 120, 95, 118, 70, 39, 8, 6, 103, 116, 49, 70, 35, 65, 101, 55, 96, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.013725756Z + restimestampmock: 2025-11-10T12:21:39.020061193Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1488 +spec: + metadata: + connID: "482" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.020250561Z + restimestampmock: 2025-11-10T12:21:39.02044774Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1489 +spec: + metadata: + connID: "482" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.020632707Z + restimestampmock: 2025-11-10T12:21:39.020795306Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1490 +spec: + metadata: + connID: "482" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.020932475Z + restimestampmock: 2025-11-10T12:21:39.021033295Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1491 +spec: + metadata: + connID: "482" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 82 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='maria_goa' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 231 + sequence_id: 6 + values: + - type: 253 + name: id + value: f0e44763-84a3-4d1b-b31d-c2bde7baaaef + unsigned: false + - type: 253 + name: username + value: maria_goa + unsigned: false + - type: 253 + name: email + value: maria_goa@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$EhG0ocI1sXLnvQGL$d1cbd0c4a9d6e91ba0d0461cd7fc35c858b404c1c914c6fd96e6a3f36b35904971dffdbc6ba7ec9db8761a9c2cfa6191caf07c5e6a76a9500ed89b75c472bbdd + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.021255443Z + restimestampmock: 2025-11-10T12:21:39.02161741Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1492 +spec: + metadata: + connID: "484" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [245, 176, 203, 112, 198, 165, 133, 110, 39, 252, 220, 194, 109, 214, 38, 191, 107, 208, 221, 109, 137, 16, 50, 127, 156, 78, 103, 29, 65, 2, 113, 115] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 400 + auth_plugin_data: [37, 78, 3, 20, 35, 50, 35, 35, 106, 85, 58, 119, 125, 14, 28, 106, 14, 19, 103, 104, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.833210708Z + restimestampmock: 2025-11-10T12:21:39.839613814Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1493 +spec: + metadata: + connID: "484" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.839765252Z + restimestampmock: 2025-11-10T12:21:39.839952101Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1494 +spec: + metadata: + connID: "484" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.84008315Z + restimestampmock: 2025-11-10T12:21:39.840235069Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1495 +spec: + metadata: + connID: "484" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.840358397Z + restimestampmock: 2025-11-10T12:21:39.840453027Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1496 +spec: + metadata: + connID: "484" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='f0e44763-84a3-4d1b-b31d-c2bde7baaaef' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: f0e44763-84a3-4d1b-b31d-c2bde7baaaef + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.840633615Z + restimestampmock: 2025-11-10T12:21:39.840976122Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1497 +spec: + metadata: + connID: "484" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 271 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('578822f8-7c40-4316-bd19-b9fbb6e27992','f0e44763-84a3-4d1b-b31d-c2bde7baaaef','House 123, Calangute',NULL,'North Goa','Goa','403516','IN','+919822123456',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.841187841Z + restimestampmock: 2025-11-10T12:21:39.841448009Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1498 +spec: + metadata: + connID: "484" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='f0e44763-84a3-4d1b-b31d-c2bde7baaaef' AND id<>'578822f8-7c40-4316-bd19-b9fbb6e27992' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.841571467Z + restimestampmock: 2025-11-10T12:21:39.841798086Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1499 +spec: + metadata: + connID: "484" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777299 + reqtimestampmock: 2025-11-10T12:21:39.841915255Z + restimestampmock: 2025-11-10T12:21:39.851649543Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1500 +spec: + metadata: + connID: "486" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [159, 60, 199, 56, 175, 192, 175, 176, 86, 170, 76, 65, 159, 251, 225, 195, 103, 254, 183, 156, 216, 173, 138, 6, 131, 61, 31, 246, 70, 25, 119, 88] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 401 + auth_plugin_data: [121, 120, 51, 95, 31, 122, 123, 59, 66, 112, 121, 26, 43, 23, 60, 116, 58, 87, 60, 108, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777300 + reqtimestampmock: 2025-11-10T12:21:40.662353037Z + restimestampmock: 2025-11-10T12:21:40.669750276Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1501 +spec: + metadata: + connID: "486" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777300 + reqtimestampmock: 2025-11-10T12:21:40.669921914Z + restimestampmock: 2025-11-10T12:21:40.670124193Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1502 +spec: + metadata: + connID: "486" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777300 + reqtimestampmock: 2025-11-10T12:21:40.670249601Z + restimestampmock: 2025-11-10T12:21:40.67039735Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1503 +spec: + metadata: + connID: "486" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777300 + reqtimestampmock: 2025-11-10T12:21:40.670506349Z + restimestampmock: 2025-11-10T12:21:40.670594788Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1504 +spec: + metadata: + connID: "486" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='f0e44763-84a3-4d1b-b31d-c2bde7baaaef' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: f0e44763-84a3-4d1b-b31d-c2bde7baaaef + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777300 + reqtimestampmock: 2025-11-10T12:21:40.670765387Z + restimestampmock: 2025-11-10T12:21:40.671078444Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1505 +spec: + metadata: + connID: "486" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='f0e44763-84a3-4d1b-b31d-c2bde7baaaef' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777300 + reqtimestampmock: 2025-11-10T12:21:40.671297563Z + restimestampmock: 2025-11-10T12:21:40.67164489Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1506 +spec: + metadata: + connID: "486" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='f0e44763-84a3-4d1b-b31d-c2bde7baaaef' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777300 + reqtimestampmock: 2025-11-10T12:21:40.671853448Z + restimestampmock: 2025-11-10T12:21:40.672092096Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1507 +spec: + metadata: + connID: "486" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777300 + reqtimestampmock: 2025-11-10T12:21:40.672207535Z + restimestampmock: 2025-11-10T12:21:40.685745341Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1508 +spec: + metadata: + connID: "488" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [109, 52, 198, 77, 0, 248, 148, 75, 63, 187, 14, 61, 60, 251, 110, 228, 253, 178, 143, 136, 172, 43, 235, 6, 139, 236, 61, 35, 84, 102, 69, 228] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 402 + auth_plugin_data: [62, 32, 19, 73, 46, 80, 61, 13, 19, 126, 45, 21, 89, 37, 71, 111, 81, 58, 28, 77, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777303 + reqtimestampmock: 2025-11-10T12:21:43.495444641Z + restimestampmock: 2025-11-10T12:21:43.501988197Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1509 +spec: + metadata: + connID: "488" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777303 + reqtimestampmock: 2025-11-10T12:21:43.502143635Z + restimestampmock: 2025-11-10T12:21:43.502383743Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1510 +spec: + metadata: + connID: "488" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777303 + reqtimestampmock: 2025-11-10T12:21:43.502483733Z + restimestampmock: 2025-11-10T12:21:43.502613681Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1511 +spec: + metadata: + connID: "488" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777303 + reqtimestampmock: 2025-11-10T12:21:43.5027233Z + restimestampmock: 2025-11-10T12:21:43.5028367Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1512 +spec: + metadata: + connID: "488" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 325 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('91eb8fad-7de2-4795-98dc-913fa5a7eece', 'yash_jaipur', 'yash_jaipur@example.in', 'scrypt:32768:8:1$rCuGOTD376maETE8$f562f3530828f493b385776624f1f3946b13857dac262751fb6976aa0bb32610ed6a1e2651bfd2aa2701289eac3c0ad903ac4b0dc560bfa9629213a5fd9c75b4', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777303 + reqtimestampmock: 2025-11-10T12:21:43.554818194Z + restimestampmock: 2025-11-10T12:21:43.55536031Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1513 +spec: + metadata: + connID: "488" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777303 + reqtimestampmock: 2025-11-10T12:21:43.555483018Z + restimestampmock: 2025-11-10T12:21:43.568652588Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1514 +spec: + metadata: + connID: "490" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [93, 76, 110, 55, 102, 222, 28, 132, 190, 131, 131, 67, 12, 9, 42, 122, 69, 164, 2, 239, 191, 246, 188, 18, 114, 81, 90, 223, 147, 197, 122, 192] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 403 + auth_plugin_data: [27, 26, 82, 60, 107, 120, 12, 45, 6, 112, 14, 87, 55, 41, 125, 109, 7, 90, 26, 40, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777304 + reqtimestampmock: 2025-11-10T12:21:44.276260919Z + restimestampmock: 2025-11-10T12:21:44.283244299Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1515 +spec: + metadata: + connID: "490" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777304 + reqtimestampmock: 2025-11-10T12:21:44.283529987Z + restimestampmock: 2025-11-10T12:21:44.283817655Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1516 +spec: + metadata: + connID: "490" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777304 + reqtimestampmock: 2025-11-10T12:21:44.283944294Z + restimestampmock: 2025-11-10T12:21:44.284098552Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1517 +spec: + metadata: + connID: "490" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777304 + reqtimestampmock: 2025-11-10T12:21:44.284231522Z + restimestampmock: 2025-11-10T12:21:44.284352641Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1518 +spec: + metadata: + connID: "490" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 84 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='yash_jaipur' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 235 + sequence_id: 6 + values: + - type: 253 + name: id + value: 91eb8fad-7de2-4795-98dc-913fa5a7eece + unsigned: false + - type: 253 + name: username + value: yash_jaipur + unsigned: false + - type: 253 + name: email + value: yash_jaipur@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$rCuGOTD376maETE8$f562f3530828f493b385776624f1f3946b13857dac262751fb6976aa0bb32610ed6a1e2651bfd2aa2701289eac3c0ad903ac4b0dc560bfa9629213a5fd9c75b4 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777304 + reqtimestampmock: 2025-11-10T12:21:44.284562428Z + restimestampmock: 2025-11-10T12:21:44.285055404Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1519 +spec: + metadata: + connID: "492" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [9, 56, 190, 97, 238, 157, 60, 59, 240, 235, 99, 195, 35, 20, 80, 8, 243, 100, 19, 23, 89, 174, 45, 115, 41, 139, 155, 66, 80, 97, 222, 134] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 405 + auth_plugin_data: [106, 15, 76, 92, 49, 21, 30, 78, 48, 95, 37, 76, 26, 73, 83, 4, 114, 25, 101, 59, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.08030947Z + restimestampmock: 2025-11-10T12:21:45.08742956Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1520 +spec: + metadata: + connID: "492" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.087670268Z + restimestampmock: 2025-11-10T12:21:45.087890735Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1521 +spec: + metadata: + connID: "492" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.088008985Z + restimestampmock: 2025-11-10T12:21:45.088181013Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1522 +spec: + metadata: + connID: "492" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.088283012Z + restimestampmock: 2025-11-10T12:21:45.088376952Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1523 +spec: + metadata: + connID: "492" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='91eb8fad-7de2-4795-98dc-913fa5a7eece' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 91eb8fad-7de2-4795-98dc-913fa5a7eece + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.08854129Z + restimestampmock: 2025-11-10T12:21:45.088960358Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1524 +spec: + metadata: + connID: "492" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 269 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('2aafcfd3-ebe2-405f-90c5-a59fef147006','91eb8fad-7de2-4795-98dc-913fa5a7eece','Hawa Mahal Road',NULL,'Jaipur','Rajasthan','302002','IN','+919829098290',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.089137375Z + restimestampmock: 2025-11-10T12:21:45.089491812Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1525 +spec: + metadata: + connID: "492" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='91eb8fad-7de2-4795-98dc-913fa5a7eece' AND id<>'2aafcfd3-ebe2-405f-90c5-a59fef147006' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.089662181Z + restimestampmock: 2025-11-10T12:21:45.089972228Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1526 +spec: + metadata: + connID: "492" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.090065748Z + restimestampmock: 2025-11-10T12:21:45.101744159Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1527 +spec: + metadata: + connID: "494" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [255, 38, 181, 38, 63, 83, 56, 177, 103, 146, 226, 235, 19, 102, 119, 225, 93, 82, 136, 221, 129, 71, 94, 3, 67, 211, 141, 47, 114, 53, 204, 71] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 406 + auth_plugin_data: [55, 39, 70, 59, 119, 107, 14, 27, 114, 75, 15, 2, 104, 7, 106, 113, 5, 47, 102, 40, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.817255684Z + restimestampmock: 2025-11-10T12:21:45.824017106Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1528 +spec: + metadata: + connID: "494" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.824286294Z + restimestampmock: 2025-11-10T12:21:45.824488712Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1529 +spec: + metadata: + connID: "494" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.824622571Z + restimestampmock: 2025-11-10T12:21:45.82480684Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1530 +spec: + metadata: + connID: "494" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.824880449Z + restimestampmock: 2025-11-10T12:21:45.824922879Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1531 +spec: + metadata: + connID: "494" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='91eb8fad-7de2-4795-98dc-913fa5a7eece' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 91eb8fad-7de2-4795-98dc-913fa5a7eece + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.825184196Z + restimestampmock: 2025-11-10T12:21:45.825540913Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1532 +spec: + metadata: + connID: "494" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='91eb8fad-7de2-4795-98dc-913fa5a7eece' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.825789972Z + restimestampmock: 2025-11-10T12:21:45.826251668Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1533 +spec: + metadata: + connID: "494" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='91eb8fad-7de2-4795-98dc-913fa5a7eece' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.826415057Z + restimestampmock: 2025-11-10T12:21:45.826708114Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1534 +spec: + metadata: + connID: "494" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777305 + reqtimestampmock: 2025-11-10T12:21:45.826844253Z + restimestampmock: 2025-11-10T12:21:45.843604282Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1535 +spec: + metadata: + connID: "496" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [154, 79, 226, 219, 116, 130, 179, 11, 147, 8, 4, 23, 68, 183, 35, 26, 111, 203, 247, 245, 43, 226, 143, 68, 133, 184, 53, 223, 232, 140, 189, 182] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 407 + auth_plugin_data: [48, 10, 127, 32, 22, 39, 115, 64, 64, 118, 111, 114, 92, 31, 89, 33, 19, 93, 94, 55, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777307 + reqtimestampmock: 2025-11-10T12:21:47.740620578Z + restimestampmock: 2025-11-10T12:21:47.750555624Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1536 +spec: + metadata: + connID: "496" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777307 + reqtimestampmock: 2025-11-10T12:21:47.750855621Z + restimestampmock: 2025-11-10T12:21:47.75109183Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1537 +spec: + metadata: + connID: "496" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777307 + reqtimestampmock: 2025-11-10T12:21:47.751317928Z + restimestampmock: 2025-11-10T12:21:47.751481706Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1538 +spec: + metadata: + connID: "496" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777307 + reqtimestampmock: 2025-11-10T12:21:47.751688325Z + restimestampmock: 2025-11-10T12:21:47.751817853Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1539 +spec: + metadata: + connID: "496" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('44708021-ee36-4225-8633-f6d9b6c3a32d', 'isha_ahmedabad', 'isha_ahmedabad@example.in', 'scrypt:32768:8:1$tgcjgwFRCrArry4D$6845a0d336eed8338b59f41a9e9954aa826e0c065e7e948ad2eb33b0be0c7638d2efc3b5081626bda1ab82bab4089601365e11bcbba38826dd579042e1d8f798', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777307 + reqtimestampmock: 2025-11-10T12:21:47.820576457Z + restimestampmock: 2025-11-10T12:21:47.821314181Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1540 +spec: + metadata: + connID: "496" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777307 + reqtimestampmock: 2025-11-10T12:21:47.821522849Z + restimestampmock: 2025-11-10T12:21:47.837882732Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1541 +spec: + metadata: + connID: "498" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [202, 163, 2, 83, 255, 142, 90, 65, 96, 149, 40, 43, 20, 173, 7, 124, 174, 59, 15, 81, 0, 107, 14, 17, 247, 64, 135, 174, 187, 77, 129, 93] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 408 + auth_plugin_data: [23, 26, 43, 3, 39, 50, 12, 56, 98, 99, 75, 62, 2, 121, 52, 116, 108, 31, 46, 40, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777308 + reqtimestampmock: 2025-11-10T12:21:48.516492084Z + restimestampmock: 2025-11-10T12:21:48.524093341Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1542 +spec: + metadata: + connID: "498" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777308 + reqtimestampmock: 2025-11-10T12:21:48.524383659Z + restimestampmock: 2025-11-10T12:21:48.524625527Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1543 +spec: + metadata: + connID: "498" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777308 + reqtimestampmock: 2025-11-10T12:21:48.524772556Z + restimestampmock: 2025-11-10T12:21:48.524944594Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1544 +spec: + metadata: + connID: "498" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777308 + reqtimestampmock: 2025-11-10T12:21:48.525079203Z + restimestampmock: 2025-11-10T12:21:48.525205112Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1545 +spec: + metadata: + connID: "498" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='isha_ahmedabad' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: 44708021-ee36-4225-8633-f6d9b6c3a32d + unsigned: false + - type: 253 + name: username + value: isha_ahmedabad + unsigned: false + - type: 253 + name: email + value: isha_ahmedabad@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$tgcjgwFRCrArry4D$6845a0d336eed8338b59f41a9e9954aa826e0c065e7e948ad2eb33b0be0c7638d2efc3b5081626bda1ab82bab4089601365e11bcbba38826dd579042e1d8f798 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777308 + reqtimestampmock: 2025-11-10T12:21:48.525533499Z + restimestampmock: 2025-11-10T12:21:48.526036845Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1546 +spec: + metadata: + connID: "500" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [245, 144, 212, 85, 5, 84, 162, 51, 30, 187, 211, 207, 91, 66, 13, 123, 184, 234, 5, 212, 151, 61, 59, 46, 79, 187, 82, 5, 175, 16, 187, 158] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 409 + auth_plugin_data: [56, 111, 33, 48, 72, 78, 33, 34, 49, 9, 49, 106, 120, 29, 48, 61, 21, 4, 28, 40, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777309 + reqtimestampmock: 2025-11-10T12:21:49.270708685Z + restimestampmock: 2025-11-10T12:21:49.28089451Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1547 +spec: + metadata: + connID: "500" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777309 + reqtimestampmock: 2025-11-10T12:21:49.281190368Z + restimestampmock: 2025-11-10T12:21:49.281392456Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1548 +spec: + metadata: + connID: "500" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777309 + reqtimestampmock: 2025-11-10T12:21:49.281522095Z + restimestampmock: 2025-11-10T12:21:49.281813552Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1549 +spec: + metadata: + connID: "500" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777309 + reqtimestampmock: 2025-11-10T12:21:49.281967191Z + restimestampmock: 2025-11-10T12:21:49.282185529Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1550 +spec: + metadata: + connID: "500" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='44708021-ee36-4225-8633-f6d9b6c3a32d' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 44708021-ee36-4225-8633-f6d9b6c3a32d + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777309 + reqtimestampmock: 2025-11-10T12:21:49.282373619Z + restimestampmock: 2025-11-10T12:21:49.282789294Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1551 +spec: + metadata: + connID: "500" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 262 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('af27df60-c68f-4667-94e8-5be0b463f296','44708021-ee36-4225-8633-f6d9b6c3a32d','CG Road',NULL,'Ahmedabad','Gujarat','380006','IN','+919825012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777309 + reqtimestampmock: 2025-11-10T12:21:49.282952723Z + restimestampmock: 2025-11-10T12:21:49.283343499Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1552 +spec: + metadata: + connID: "500" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='44708021-ee36-4225-8633-f6d9b6c3a32d' AND id<>'af27df60-c68f-4667-94e8-5be0b463f296' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777309 + reqtimestampmock: 2025-11-10T12:21:49.283647857Z + restimestampmock: 2025-11-10T12:21:49.283984875Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1553 +spec: + metadata: + connID: "500" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777309 + reqtimestampmock: 2025-11-10T12:21:49.284144143Z + restimestampmock: 2025-11-10T12:21:49.305710472Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1554 +spec: + metadata: + connID: "502" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [160, 86, 183, 59, 219, 91, 197, 88, 207, 133, 93, 71, 237, 216, 59, 199, 234, 0, 106, 28, 252, 68, 251, 142, 228, 197, 50, 28, 239, 92, 231, 167] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 411 + auth_plugin_data: [82, 49, 94, 63, 102, 77, 49, 65, 117, 121, 107, 88, 125, 2, 24, 114, 87, 124, 16, 21, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777310 + reqtimestampmock: 2025-11-10T12:21:50.008181185Z + restimestampmock: 2025-11-10T12:21:50.015210038Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1555 +spec: + metadata: + connID: "502" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777310 + reqtimestampmock: 2025-11-10T12:21:50.015391265Z + restimestampmock: 2025-11-10T12:21:50.015612824Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1556 +spec: + metadata: + connID: "502" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777310 + reqtimestampmock: 2025-11-10T12:21:50.015744183Z + restimestampmock: 2025-11-10T12:21:50.015903321Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1557 +spec: + metadata: + connID: "502" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777310 + reqtimestampmock: 2025-11-10T12:21:50.016009911Z + restimestampmock: 2025-11-10T12:21:50.016174339Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1558 +spec: + metadata: + connID: "502" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='44708021-ee36-4225-8633-f6d9b6c3a32d' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 44708021-ee36-4225-8633-f6d9b6c3a32d + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777310 + reqtimestampmock: 2025-11-10T12:21:50.016471756Z + restimestampmock: 2025-11-10T12:21:50.016931253Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1559 +spec: + metadata: + connID: "502" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='44708021-ee36-4225-8633-f6d9b6c3a32d' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777310 + reqtimestampmock: 2025-11-10T12:21:50.017065211Z + restimestampmock: 2025-11-10T12:21:50.017447568Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1560 +spec: + metadata: + connID: "502" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='44708021-ee36-4225-8633-f6d9b6c3a32d' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777310 + reqtimestampmock: 2025-11-10T12:21:50.017614627Z + restimestampmock: 2025-11-10T12:21:50.017871605Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1561 +spec: + metadata: + connID: "502" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777310 + reqtimestampmock: 2025-11-10T12:21:50.017980744Z + restimestampmock: 2025-11-10T12:21:50.027469794Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1562 +spec: + metadata: + connID: "504" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [137, 233, 46, 69, 128, 51, 182, 221, 176, 205, 160, 185, 237, 11, 253, 96, 85, 109, 153, 129, 178, 186, 64, 213, 65, 220, 247, 197, 19, 84, 181, 37] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 412 + auth_plugin_data: [111, 57, 19, 3, 64, 29, 114, 23, 19, 104, 95, 111, 116, 30, 67, 75, 96, 121, 67, 38, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777311 + reqtimestampmock: 2025-11-10T12:21:51.885013983Z + restimestampmock: 2025-11-10T12:21:51.891952485Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1563 +spec: + metadata: + connID: "504" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777311 + reqtimestampmock: 2025-11-10T12:21:51.892208493Z + restimestampmock: 2025-11-10T12:21:51.89242524Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1564 +spec: + metadata: + connID: "504" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777311 + reqtimestampmock: 2025-11-10T12:21:51.892582119Z + restimestampmock: 2025-11-10T12:21:51.892772128Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1565 +spec: + metadata: + connID: "504" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777311 + reqtimestampmock: 2025-11-10T12:21:51.892864027Z + restimestampmock: 2025-11-10T12:21:51.892998165Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1566 +spec: + metadata: + connID: "504" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('a4290205-4d0c-4577-a66c-6b180d9e87bc', 'aditya_lucknow', 'aditya_lucknow@example.in', 'scrypt:32768:8:1$feWK93udWkmxdWk8$879ef14ce147b31889e34900f0e474e71b21b17ca67d9fe03189b50443d19223be22cb65bd493e5a35ad663e73fde635e92e1ea9749294f36dc88c30b2f4ac42', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777311 + reqtimestampmock: 2025-11-10T12:21:51.944580173Z + restimestampmock: 2025-11-10T12:21:51.945058719Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1567 +spec: + metadata: + connID: "504" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777311 + reqtimestampmock: 2025-11-10T12:21:51.945208678Z + restimestampmock: 2025-11-10T12:21:51.957729143Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1568 +spec: + metadata: + connID: "506" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [199, 191, 13, 186, 13, 237, 127, 204, 240, 29, 95, 78, 99, 6, 121, 135, 28, 119, 23, 161, 204, 186, 220, 115, 228, 217, 233, 126, 31, 130, 254, 93] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 413 + auth_plugin_data: [109, 53, 67, 38, 69, 26, 89, 21, 111, 42, 11, 54, 55, 22, 52, 52, 11, 23, 42, 113, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777312 + reqtimestampmock: 2025-11-10T12:21:52.643960874Z + restimestampmock: 2025-11-10T12:21:52.651142024Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1569 +spec: + metadata: + connID: "506" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777312 + reqtimestampmock: 2025-11-10T12:21:52.651496302Z + restimestampmock: 2025-11-10T12:21:52.65172686Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1570 +spec: + metadata: + connID: "506" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777312 + reqtimestampmock: 2025-11-10T12:21:52.651857219Z + restimestampmock: 2025-11-10T12:21:52.652022356Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1571 +spec: + metadata: + connID: "506" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777312 + reqtimestampmock: 2025-11-10T12:21:52.652186885Z + restimestampmock: 2025-11-10T12:21:52.652301734Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1572 +spec: + metadata: + connID: "506" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='aditya_lucknow' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: a4290205-4d0c-4577-a66c-6b180d9e87bc + unsigned: false + - type: 253 + name: username + value: aditya_lucknow + unsigned: false + - type: 253 + name: email + value: aditya_lucknow@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$feWK93udWkmxdWk8$879ef14ce147b31889e34900f0e474e71b21b17ca67d9fe03189b50443d19223be22cb65bd493e5a35ad663e73fde635e92e1ea9749294f36dc88c30b2f4ac42 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777312 + reqtimestampmock: 2025-11-10T12:21:52.652457723Z + restimestampmock: 2025-11-10T12:21:52.652913679Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1573 +spec: + metadata: + connID: "508" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [249, 39, 56, 250, 241, 147, 142, 30, 37, 247, 160, 69, 131, 246, 152, 47, 191, 175, 46, 85, 71, 141, 218, 92, 74, 160, 179, 119, 140, 7, 192, 80] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 414 + auth_plugin_data: [28, 7, 95, 44, 120, 27, 117, 61, 83, 114, 22, 63, 4, 86, 76, 55, 98, 115, 90, 124, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777313 + reqtimestampmock: 2025-11-10T12:21:53.503278225Z + restimestampmock: 2025-11-10T12:21:53.510091919Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1574 +spec: + metadata: + connID: "508" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777313 + reqtimestampmock: 2025-11-10T12:21:53.510280897Z + restimestampmock: 2025-11-10T12:21:53.510500465Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1575 +spec: + metadata: + connID: "508" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777313 + reqtimestampmock: 2025-11-10T12:21:53.510625184Z + restimestampmock: 2025-11-10T12:21:53.510922641Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1576 +spec: + metadata: + connID: "508" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777313 + reqtimestampmock: 2025-11-10T12:21:53.511048181Z + restimestampmock: 2025-11-10T12:21:53.51114266Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1577 +spec: + metadata: + connID: "508" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a4290205-4d0c-4577-a66c-6b180d9e87bc' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a4290205-4d0c-4577-a66c-6b180d9e87bc + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777313 + reqtimestampmock: 2025-11-10T12:21:53.511323527Z + restimestampmock: 2025-11-10T12:21:53.511689354Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1578 +spec: + metadata: + connID: "508" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 270 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('c2479e30-8c19-4779-922e-1f96cb1797fe','a4290205-4d0c-4577-a66c-6b180d9e87bc','Gomti Nagar',NULL,'Lucknow','Uttar Pradesh','226010','IN','+919415112345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777313 + reqtimestampmock: 2025-11-10T12:21:53.511816083Z + restimestampmock: 2025-11-10T12:21:53.51220208Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1579 +spec: + metadata: + connID: "508" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='a4290205-4d0c-4577-a66c-6b180d9e87bc' AND id<>'c2479e30-8c19-4779-922e-1f96cb1797fe' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777313 + reqtimestampmock: 2025-11-10T12:21:53.512370149Z + restimestampmock: 2025-11-10T12:21:53.512625787Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1580 +spec: + metadata: + connID: "508" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777313 + reqtimestampmock: 2025-11-10T12:21:53.512746416Z + restimestampmock: 2025-11-10T12:21:53.521442583Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1581 +spec: + metadata: + connID: "510" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [237, 119, 190, 17, 241, 143, 166, 20, 193, 51, 106, 220, 15, 194, 37, 16, 168, 227, 154, 183, 167, 155, 250, 121, 154, 116, 51, 178, 228, 100, 20, 55] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 415 + auth_plugin_data: [23, 114, 57, 81, 109, 103, 35, 92, 88, 72, 10, 63, 81, 106, 10, 54, 38, 126, 110, 8, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777314 + reqtimestampmock: 2025-11-10T12:21:54.232829963Z + restimestampmock: 2025-11-10T12:21:54.240554688Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1582 +spec: + metadata: + connID: "510" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777314 + reqtimestampmock: 2025-11-10T12:21:54.240799117Z + restimestampmock: 2025-11-10T12:21:54.240992434Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1583 +spec: + metadata: + connID: "510" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777314 + reqtimestampmock: 2025-11-10T12:21:54.241139303Z + restimestampmock: 2025-11-10T12:21:54.241297112Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1584 +spec: + metadata: + connID: "510" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777314 + reqtimestampmock: 2025-11-10T12:21:54.241384001Z + restimestampmock: 2025-11-10T12:21:54.24150544Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1585 +spec: + metadata: + connID: "510" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a4290205-4d0c-4577-a66c-6b180d9e87bc' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a4290205-4d0c-4577-a66c-6b180d9e87bc + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777314 + reqtimestampmock: 2025-11-10T12:21:54.241693229Z + restimestampmock: 2025-11-10T12:21:54.242051456Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1586 +spec: + metadata: + connID: "510" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='a4290205-4d0c-4577-a66c-6b180d9e87bc' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777314 + reqtimestampmock: 2025-11-10T12:21:54.242199975Z + restimestampmock: 2025-11-10T12:21:54.242537761Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1587 +spec: + metadata: + connID: "510" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='a4290205-4d0c-4577-a66c-6b180d9e87bc' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777314 + reqtimestampmock: 2025-11-10T12:21:54.24267971Z + restimestampmock: 2025-11-10T12:21:54.242933808Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1588 +spec: + metadata: + connID: "510" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777314 + reqtimestampmock: 2025-11-10T12:21:54.243076157Z + restimestampmock: 2025-11-10T12:21:54.255636451Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1589 +spec: + metadata: + connID: "512" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [137, 187, 169, 34, 88, 135, 157, 97, 97, 219, 254, 5, 16, 14, 28, 198, 186, 85, 230, 118, 153, 168, 74, 191, 132, 186, 206, 115, 49, 62, 236, 161] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 417 + auth_plugin_data: [63, 86, 84, 98, 108, 50, 109, 121, 78, 17, 107, 73, 50, 17, 114, 97, 47, 16, 11, 1, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777316 + reqtimestampmock: 2025-11-10T12:21:56.60265759Z + restimestampmock: 2025-11-10T12:21:56.609446284Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1590 +spec: + metadata: + connID: "512" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777316 + reqtimestampmock: 2025-11-10T12:21:56.609643822Z + restimestampmock: 2025-11-10T12:21:56.609818211Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1591 +spec: + metadata: + connID: "512" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777316 + reqtimestampmock: 2025-11-10T12:21:56.60995364Z + restimestampmock: 2025-11-10T12:21:56.610119458Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1592 +spec: + metadata: + connID: "512" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777316 + reqtimestampmock: 2025-11-10T12:21:56.610238727Z + restimestampmock: 2025-11-10T12:21:56.610338777Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1593 +spec: + metadata: + connID: "512" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 323 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('1dd33e6c-1eda-484f-9ac0-25323324cd5e', 'simran_chd', 'simran_chd@example.in', 'scrypt:32768:8:1$dCOQjDkgMLnaqzB4$a45722db8b0242f16ef412e4799ab16072eaaa5e29cf053f2a07cd20ba47acc80083fbfd990ea7fa52ad116348599784723135eef6e00f8af966df5eb6903d02', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777316 + reqtimestampmock: 2025-11-10T12:21:56.661838764Z + restimestampmock: 2025-11-10T12:21:56.662199392Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1594 +spec: + metadata: + connID: "512" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777316 + reqtimestampmock: 2025-11-10T12:21:56.662366031Z + restimestampmock: 2025-11-10T12:21:56.671466604Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1595 +spec: + metadata: + connID: "514" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [204, 52, 55, 31, 201, 236, 220, 91, 226, 141, 249, 180, 204, 8, 178, 160, 225, 179, 136, 40, 118, 97, 218, 181, 230, 246, 209, 236, 182, 1, 68, 177] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 418 + auth_plugin_data: [54, 94, 101, 109, 120, 94, 68, 22, 50, 16, 42, 39, 93, 101, 123, 121, 106, 12, 56, 8, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777317 + reqtimestampmock: 2025-11-10T12:21:57.3474561Z + restimestampmock: 2025-11-10T12:21:57.35464985Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1596 +spec: + metadata: + connID: "514" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777317 + reqtimestampmock: 2025-11-10T12:21:57.354815369Z + restimestampmock: 2025-11-10T12:21:57.355009097Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1597 +spec: + metadata: + connID: "514" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777317 + reqtimestampmock: 2025-11-10T12:21:57.355115136Z + restimestampmock: 2025-11-10T12:21:57.355268915Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1598 +spec: + metadata: + connID: "514" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777317 + reqtimestampmock: 2025-11-10T12:21:57.355365114Z + restimestampmock: 2025-11-10T12:21:57.355450964Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1599 +spec: + metadata: + connID: "514" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 83 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='simran_chd' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 233 + sequence_id: 6 + values: + - type: 253 + name: id + value: 1dd33e6c-1eda-484f-9ac0-25323324cd5e + unsigned: false + - type: 253 + name: username + value: simran_chd + unsigned: false + - type: 253 + name: email + value: simran_chd@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$dCOQjDkgMLnaqzB4$a45722db8b0242f16ef412e4799ab16072eaaa5e29cf053f2a07cd20ba47acc80083fbfd990ea7fa52ad116348599784723135eef6e00f8af966df5eb6903d02 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777317 + reqtimestampmock: 2025-11-10T12:21:57.355623422Z + restimestampmock: 2025-11-10T12:21:57.356045748Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1600 +spec: + metadata: + connID: "516" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [206, 230, 72, 103, 255, 26, 64, 26, 185, 47, 224, 127, 120, 252, 191, 120, 175, 162, 100, 111, 127, 141, 107, 80, 151, 53, 12, 190, 1, 157, 7, 16] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 419 + auth_plugin_data: [26, 125, 106, 62, 40, 69, 66, 60, 62, 126, 34, 95, 86, 126, 109, 81, 19, 88, 120, 102, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.20819993Z + restimestampmock: 2025-11-10T12:21:58.217122385Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1601 +spec: + metadata: + connID: "516" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.217323163Z + restimestampmock: 2025-11-10T12:21:58.217512712Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1602 +spec: + metadata: + connID: "516" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.217664051Z + restimestampmock: 2025-11-10T12:21:58.217830499Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1603 +spec: + metadata: + connID: "516" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.217960538Z + restimestampmock: 2025-11-10T12:21:58.218065148Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1604 +spec: + metadata: + connID: "516" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='1dd33e6c-1eda-484f-9ac0-25323324cd5e' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 1dd33e6c-1eda-484f-9ac0-25323324cd5e + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.218268615Z + restimestampmock: 2025-11-10T12:21:58.218625183Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1605 +spec: + metadata: + connID: "516" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 268 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('b9fe35b4-6ed9-48c0-bf77-49648e79f9c2','1dd33e6c-1eda-484f-9ac0-25323324cd5e','Sector 22',NULL,'Chandigarh','Chandigarh','160022','IN','+919814012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.218793921Z + restimestampmock: 2025-11-10T12:21:58.219084599Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1606 +spec: + metadata: + connID: "516" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='1dd33e6c-1eda-484f-9ac0-25323324cd5e' AND id<>'b9fe35b4-6ed9-48c0-bf77-49648e79f9c2' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.219254797Z + restimestampmock: 2025-11-10T12:21:58.219505035Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1607 +spec: + metadata: + connID: "516" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.219647284Z + restimestampmock: 2025-11-10T12:21:58.231383926Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1608 +spec: + metadata: + connID: "518" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [129, 204, 243, 208, 166, 96, 62, 244, 157, 24, 30, 64, 31, 172, 70, 221, 184, 77, 49, 181, 27, 100, 33, 94, 124, 12, 157, 214, 2, 26, 212, 155] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 420 + auth_plugin_data: [110, 43, 115, 61, 98, 33, 113, 111, 45, 93, 74, 62, 82, 111, 35, 97, 54, 120, 1, 2, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.927979171Z + restimestampmock: 2025-11-10T12:21:58.938953108Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1609 +spec: + metadata: + connID: "518" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.939291225Z + restimestampmock: 2025-11-10T12:21:58.939610923Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1610 +spec: + metadata: + connID: "518" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.939773691Z + restimestampmock: 2025-11-10T12:21:58.940020269Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1611 +spec: + metadata: + connID: "518" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.940159088Z + restimestampmock: 2025-11-10T12:21:58.940327316Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1612 +spec: + metadata: + connID: "518" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='1dd33e6c-1eda-484f-9ac0-25323324cd5e' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 1dd33e6c-1eda-484f-9ac0-25323324cd5e + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.940561004Z + restimestampmock: 2025-11-10T12:21:58.941002371Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1613 +spec: + metadata: + connID: "518" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='1dd33e6c-1eda-484f-9ac0-25323324cd5e' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.941245679Z + restimestampmock: 2025-11-10T12:21:58.941655986Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1614 +spec: + metadata: + connID: "518" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='1dd33e6c-1eda-484f-9ac0-25323324cd5e' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.941913103Z + restimestampmock: 2025-11-10T12:21:58.94221102Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1615 +spec: + metadata: + connID: "518" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777318 + reqtimestampmock: 2025-11-10T12:21:58.942324659Z + restimestampmock: 2025-11-10T12:21:58.961733417Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1616 +spec: + metadata: + connID: "520" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [85, 32, 202, 62, 83, 250, 69, 214, 60, 246, 13, 5, 89, 25, 125, 92, 183, 215, 238, 130, 81, 155, 238, 207, 222, 58, 250, 16, 31, 233, 139, 92] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 422 + auth_plugin_data: [91, 102, 104, 87, 1, 26, 32, 83, 105, 37, 15, 53, 95, 89, 59, 86, 116, 8, 116, 2, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777320 + reqtimestampmock: 2025-11-10T12:22:00.925606748Z + restimestampmock: 2025-11-10T12:22:00.933325894Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1617 +spec: + metadata: + connID: "520" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777320 + reqtimestampmock: 2025-11-10T12:22:00.933587991Z + restimestampmock: 2025-11-10T12:22:00.933865809Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1618 +spec: + metadata: + connID: "520" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777320 + reqtimestampmock: 2025-11-10T12:22:00.934011678Z + restimestampmock: 2025-11-10T12:22:00.934204236Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1619 +spec: + metadata: + connID: "520" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777320 + reqtimestampmock: 2025-11-10T12:22:00.934343835Z + restimestampmock: 2025-11-10T12:22:00.934550953Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1620 +spec: + metadata: + connID: "520" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 327 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('b8a7fc1d-a07c-4225-bf5b-8e486aecaae6', 'george_kochi', 'george_kochi@example.in', 'scrypt:32768:8:1$V7HhFJcRbEDWqUZp$4fd669ea0d8fde07f3faf1b5597498acad0fa0adfc8029572a1211fe4eecfb9796c153ea41f8cdce88b28965533b61c9727faf7100dbfc1655b2d942ed350be6', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777320 + reqtimestampmock: 2025-11-10T12:22:00.98748417Z + restimestampmock: 2025-11-10T12:22:00.988061985Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1621 +spec: + metadata: + connID: "520" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777320 + reqtimestampmock: 2025-11-10T12:22:00.988276873Z + restimestampmock: 2025-11-10T12:22:00.997553165Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1622 +spec: + metadata: + connID: "522" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [8, 77, 172, 175, 161, 6, 11, 25, 182, 148, 108, 141, 11, 66, 47, 69, 165, 229, 104, 241, 34, 127, 189, 179, 81, 20, 83, 186, 230, 154, 83, 44] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 423 + auth_plugin_data: [101, 72, 41, 81, 91, 73, 86, 81, 118, 54, 50, 107, 97, 15, 9, 65, 53, 102, 119, 122, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777321 + reqtimestampmock: 2025-11-10T12:22:01.841476937Z + restimestampmock: 2025-11-10T12:22:01.84845087Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1623 +spec: + metadata: + connID: "522" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777321 + reqtimestampmock: 2025-11-10T12:22:01.848698268Z + restimestampmock: 2025-11-10T12:22:01.848910427Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1624 +spec: + metadata: + connID: "522" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777321 + reqtimestampmock: 2025-11-10T12:22:01.849211053Z + restimestampmock: 2025-11-10T12:22:01.849464271Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1625 +spec: + metadata: + connID: "522" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777321 + reqtimestampmock: 2025-11-10T12:22:01.84962628Z + restimestampmock: 2025-11-10T12:22:01.849752129Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1626 +spec: + metadata: + connID: "522" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='george_kochi' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 237 + sequence_id: 6 + values: + - type: 253 + name: id + value: b8a7fc1d-a07c-4225-bf5b-8e486aecaae6 + unsigned: false + - type: 253 + name: username + value: george_kochi + unsigned: false + - type: 253 + name: email + value: george_kochi@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$V7HhFJcRbEDWqUZp$4fd669ea0d8fde07f3faf1b5597498acad0fa0adfc8029572a1211fe4eecfb9796c153ea41f8cdce88b28965533b61c9727faf7100dbfc1655b2d942ed350be6 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777321 + reqtimestampmock: 2025-11-10T12:22:01.850005927Z + restimestampmock: 2025-11-10T12:22:01.850511382Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1627 +spec: + metadata: + connID: "524" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [68, 172, 191, 126, 245, 64, 149, 6, 231, 102, 21, 179, 229, 76, 162, 16, 95, 9, 175, 174, 107, 97, 110, 232, 113, 193, 65, 159, 180, 110, 102, 183] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 424 + auth_plugin_data: [83, 11, 100, 5, 66, 94, 74, 27, 42, 100, 92, 58, 8, 65, 42, 105, 98, 113, 52, 41, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777322 + reqtimestampmock: 2025-11-10T12:22:02.584500685Z + restimestampmock: 2025-11-10T12:22:02.591288249Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1628 +spec: + metadata: + connID: "524" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777322 + reqtimestampmock: 2025-11-10T12:22:02.591441977Z + restimestampmock: 2025-11-10T12:22:02.591646756Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1629 +spec: + metadata: + connID: "524" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777322 + reqtimestampmock: 2025-11-10T12:22:02.591763814Z + restimestampmock: 2025-11-10T12:22:02.591918004Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1630 +spec: + metadata: + connID: "524" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777322 + reqtimestampmock: 2025-11-10T12:22:02.592031993Z + restimestampmock: 2025-11-10T12:22:02.592127411Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1631 +spec: + metadata: + connID: "524" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='b8a7fc1d-a07c-4225-bf5b-8e486aecaae6' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: b8a7fc1d-a07c-4225-bf5b-8e486aecaae6 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777322 + reqtimestampmock: 2025-11-10T12:22:02.592312631Z + restimestampmock: 2025-11-10T12:22:02.592690897Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1632 +spec: + metadata: + connID: "524" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 262 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('30a320aa-287e-46c6-b1ba-255350d701bd','b8a7fc1d-a07c-4225-bf5b-8e486aecaae6','Marine Drive',NULL,'Kochi','Kerala','682011','IN','+919846012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777322 + reqtimestampmock: 2025-11-10T12:22:02.592853636Z + restimestampmock: 2025-11-10T12:22:02.593163853Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1633 +spec: + metadata: + connID: "524" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='b8a7fc1d-a07c-4225-bf5b-8e486aecaae6' AND id<>'30a320aa-287e-46c6-b1ba-255350d701bd' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777322 + reqtimestampmock: 2025-11-10T12:22:02.593303902Z + restimestampmock: 2025-11-10T12:22:02.593593399Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1634 +spec: + metadata: + connID: "524" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777322 + reqtimestampmock: 2025-11-10T12:22:02.593720928Z + restimestampmock: 2025-11-10T12:22:02.603451856Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1635 +spec: + metadata: + connID: "526" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [105, 232, 72, 38, 75, 118, 177, 114, 1, 155, 80, 104, 240, 202, 113, 85, 78, 186, 109, 31, 81, 54, 214, 100, 123, 47, 68, 38, 81, 253, 99, 93] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 425 + auth_plugin_data: [32, 23, 91, 38, 10, 99, 86, 47, 60, 42, 67, 111, 28, 17, 93, 9, 83, 5, 121, 118, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777323 + reqtimestampmock: 2025-11-10T12:22:03.414135037Z + restimestampmock: 2025-11-10T12:22:03.421113199Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1636 +spec: + metadata: + connID: "526" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777323 + reqtimestampmock: 2025-11-10T12:22:03.421323327Z + restimestampmock: 2025-11-10T12:22:03.421550196Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1637 +spec: + metadata: + connID: "526" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777323 + reqtimestampmock: 2025-11-10T12:22:03.421670055Z + restimestampmock: 2025-11-10T12:22:03.421938532Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1638 +spec: + metadata: + connID: "526" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777323 + reqtimestampmock: 2025-11-10T12:22:03.422031322Z + restimestampmock: 2025-11-10T12:22:03.4221617Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1639 +spec: + metadata: + connID: "526" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='b8a7fc1d-a07c-4225-bf5b-8e486aecaae6' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: b8a7fc1d-a07c-4225-bf5b-8e486aecaae6 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777323 + reqtimestampmock: 2025-11-10T12:22:03.422362398Z + restimestampmock: 2025-11-10T12:22:03.422816615Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1640 +spec: + metadata: + connID: "526" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='b8a7fc1d-a07c-4225-bf5b-8e486aecaae6' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777323 + reqtimestampmock: 2025-11-10T12:22:03.422993343Z + restimestampmock: 2025-11-10T12:22:03.4234132Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1641 +spec: + metadata: + connID: "526" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='b8a7fc1d-a07c-4225-bf5b-8e486aecaae6' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777323 + reqtimestampmock: 2025-11-10T12:22:03.423600967Z + restimestampmock: 2025-11-10T12:22:03.423922375Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1642 +spec: + metadata: + connID: "526" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777323 + reqtimestampmock: 2025-11-10T12:22:03.424027804Z + restimestampmock: 2025-11-10T12:22:03.433478755Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1643 +spec: + metadata: + connID: "528" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [196, 17, 92, 95, 88, 241, 254, 51, 53, 2, 22, 63, 167, 65, 168, 79, 217, 160, 24, 195, 46, 170, 207, 141, 144, 182, 60, 153, 223, 248, 30, 165] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 427 + auth_plugin_data: [38, 35, 122, 122, 16, 59, 114, 4, 97, 82, 29, 96, 23, 74, 114, 124, 14, 84, 121, 53, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.055663676Z + restimestampmock: 2025-11-10T12:22:06.066320887Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1644 +spec: + metadata: + connID: "528" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.066547875Z + restimestampmock: 2025-11-10T12:22:06.066776703Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1645 +spec: + metadata: + connID: "528" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.066966002Z + restimestampmock: 2025-11-10T12:22:06.067108331Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1646 +spec: + metadata: + connID: "528" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.067276068Z + restimestampmock: 2025-11-10T12:22:06.067375139Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1647 +spec: + metadata: + connID: "528" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 325 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('4d375648-f4ab-4661-90e8-d4265b1afcc4', 'neha_indore', 'neha_indore@example.in', 'scrypt:32768:8:1$FWleo7020Rcq0TBx$af63d853213363e2d78aca92e4cbcf874d7801d84ea2e86515f4bee20e784219189d9e51bb9a3ff667e2b97aaa5d51453b4a825e5572b358746709a3ff5811c0', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.120779711Z + restimestampmock: 2025-11-10T12:22:06.121356207Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1648 +spec: + metadata: + connID: "528" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.121524755Z + restimestampmock: 2025-11-10T12:22:06.131451372Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1649 +spec: + metadata: + connID: "530" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [86, 137, 16, 167, 140, 116, 18, 82, 110, 169, 97, 16, 11, 107, 219, 46, 106, 111, 82, 244, 105, 232, 143, 54, 177, 43, 59, 190, 146, 10, 155, 102] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 428 + auth_plugin_data: [42, 5, 113, 85, 24, 56, 18, 30, 126, 102, 9, 13, 30, 101, 82, 103, 99, 66, 111, 30, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.808578832Z + restimestampmock: 2025-11-10T12:22:06.815412475Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1650 +spec: + metadata: + connID: "530" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.815637002Z + restimestampmock: 2025-11-10T12:22:06.81589277Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1651 +spec: + metadata: + connID: "530" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.81608551Z + restimestampmock: 2025-11-10T12:22:06.816305847Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1652 +spec: + metadata: + connID: "530" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.816458816Z + restimestampmock: 2025-11-10T12:22:06.816570415Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1653 +spec: + metadata: + connID: "530" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 84 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='neha_indore' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 235 + sequence_id: 6 + values: + - type: 253 + name: id + value: 4d375648-f4ab-4661-90e8-d4265b1afcc4 + unsigned: false + - type: 253 + name: username + value: neha_indore + unsigned: false + - type: 253 + name: email + value: neha_indore@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$FWleo7020Rcq0TBx$af63d853213363e2d78aca92e4cbcf874d7801d84ea2e86515f4bee20e784219189d9e51bb9a3ff667e2b97aaa5d51453b4a825e5572b358746709a3ff5811c0 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777326 + reqtimestampmock: 2025-11-10T12:22:06.816782653Z + restimestampmock: 2025-11-10T12:22:06.817401338Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1654 +spec: + metadata: + connID: "532" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [140, 96, 246, 25, 27, 22, 177, 7, 121, 12, 116, 250, 46, 48, 92, 157, 230, 128, 68, 64, 7, 76, 147, 57, 239, 29, 52, 193, 18, 45, 103, 245] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 429 + auth_plugin_data: [32, 8, 1, 31, 21, 52, 47, 13, 106, 114, 53, 61, 82, 5, 95, 88, 16, 35, 98, 26, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777327 + reqtimestampmock: 2025-11-10T12:22:07.606350282Z + restimestampmock: 2025-11-10T12:22:07.616438057Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1655 +spec: + metadata: + connID: "532" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777327 + reqtimestampmock: 2025-11-10T12:22:07.616715365Z + restimestampmock: 2025-11-10T12:22:07.616908003Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1656 +spec: + metadata: + connID: "532" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777327 + reqtimestampmock: 2025-11-10T12:22:07.617053722Z + restimestampmock: 2025-11-10T12:22:07.617241281Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1657 +spec: + metadata: + connID: "532" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777327 + reqtimestampmock: 2025-11-10T12:22:07.617358779Z + restimestampmock: 2025-11-10T12:22:07.617478959Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1658 +spec: + metadata: + connID: "532" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='4d375648-f4ab-4661-90e8-d4265b1afcc4' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 4d375648-f4ab-4661-90e8-d4265b1afcc4 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777327 + reqtimestampmock: 2025-11-10T12:22:07.617664976Z + restimestampmock: 2025-11-10T12:22:07.618043063Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1659 +spec: + metadata: + connID: "532" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 267 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('46e38be7-5454-45a3-a5d1-c0ed3c09b145','4d375648-f4ab-4661-90e8-d4265b1afcc4','56 Dukan',NULL,'Indore','Madhya Pradesh','452001','IN','+919826012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777327 + reqtimestampmock: 2025-11-10T12:22:07.618296121Z + restimestampmock: 2025-11-10T12:22:07.618697388Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1660 +spec: + metadata: + connID: "532" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='4d375648-f4ab-4661-90e8-d4265b1afcc4' AND id<>'46e38be7-5454-45a3-a5d1-c0ed3c09b145' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777327 + reqtimestampmock: 2025-11-10T12:22:07.618834276Z + restimestampmock: 2025-11-10T12:22:07.619607251Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1661 +spec: + metadata: + connID: "532" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777327 + reqtimestampmock: 2025-11-10T12:22:07.619733269Z + restimestampmock: 2025-11-10T12:22:07.629478528Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1662 +spec: + metadata: + connID: "534" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [34, 14, 79, 110, 182, 182, 104, 130, 204, 137, 126, 13, 196, 22, 233, 66, 104, 212, 15, 64, 108, 100, 161, 187, 186, 239, 254, 156, 150, 198, 77, 144] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 430 + auth_plugin_data: [8, 28, 52, 7, 62, 100, 24, 97, 77, 91, 59, 124, 57, 57, 87, 75, 26, 55, 80, 29, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777328 + reqtimestampmock: 2025-11-10T12:22:08.368765187Z + restimestampmock: 2025-11-10T12:22:08.375772218Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1663 +spec: + metadata: + connID: "534" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777328 + reqtimestampmock: 2025-11-10T12:22:08.375967688Z + restimestampmock: 2025-11-10T12:22:08.376259485Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1664 +spec: + metadata: + connID: "534" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777328 + reqtimestampmock: 2025-11-10T12:22:08.376369623Z + restimestampmock: 2025-11-10T12:22:08.376566102Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1665 +spec: + metadata: + connID: "534" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777328 + reqtimestampmock: 2025-11-10T12:22:08.376655331Z + restimestampmock: 2025-11-10T12:22:08.3767805Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1666 +spec: + metadata: + connID: "534" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='4d375648-f4ab-4661-90e8-d4265b1afcc4' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 4d375648-f4ab-4661-90e8-d4265b1afcc4 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777328 + reqtimestampmock: 2025-11-10T12:22:08.376962669Z + restimestampmock: 2025-11-10T12:22:08.377390755Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1667 +spec: + metadata: + connID: "534" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='4d375648-f4ab-4661-90e8-d4265b1afcc4' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777328 + reqtimestampmock: 2025-11-10T12:22:08.377663853Z + restimestampmock: 2025-11-10T12:22:08.378028799Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1668 +spec: + metadata: + connID: "534" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='4d375648-f4ab-4661-90e8-d4265b1afcc4' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777328 + reqtimestampmock: 2025-11-10T12:22:08.378290058Z + restimestampmock: 2025-11-10T12:22:08.378843693Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1669 +spec: + metadata: + connID: "534" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777328 + reqtimestampmock: 2025-11-10T12:22:08.379005552Z + restimestampmock: 2025-11-10T12:22:08.389477775Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1670 +spec: + metadata: + connID: "536" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [175, 28, 76, 113, 195, 0, 55, 250, 6, 143, 222, 83, 195, 241, 250, 32, 221, 44, 193, 68, 141, 85, 144, 189, 34, 31, 111, 109, 248, 18, 35, 184] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 432 + auth_plugin_data: [63, 72, 30, 76, 28, 61, 47, 8, 27, 32, 68, 29, 8, 51, 63, 111, 58, 97, 96, 8, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777330 + reqtimestampmock: 2025-11-10T12:22:10.491899061Z + restimestampmock: 2025-11-10T12:22:10.500466949Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1671 +spec: + metadata: + connID: "536" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777330 + reqtimestampmock: 2025-11-10T12:22:10.500700198Z + restimestampmock: 2025-11-10T12:22:10.500949236Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1672 +spec: + metadata: + connID: "536" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777330 + reqtimestampmock: 2025-11-10T12:22:10.501077735Z + restimestampmock: 2025-11-10T12:22:10.501241782Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1673 +spec: + metadata: + connID: "536" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777330 + reqtimestampmock: 2025-11-10T12:22:10.501347802Z + restimestampmock: 2025-11-10T12:22:10.501447092Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1674 +spec: + metadata: + connID: "536" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 327 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('cd231165-d42c-4937-8415-50d80f3cfbf2', 'vivek_bhopal', 'vivek_bhopal@example.in', 'scrypt:32768:8:1$tPirPR2JCoasqRdO$9279de0255bec58e045a4a19acdd64c299ed1b3d0b8aba3294936b9ff904405508b738f106ed9847648aaaddbf2dee52b1a761467773d8674ef7a7ceb8d11a21', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777330 + reqtimestampmock: 2025-11-10T12:22:10.557263374Z + restimestampmock: 2025-11-10T12:22:10.557725501Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1675 +spec: + metadata: + connID: "536" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777330 + reqtimestampmock: 2025-11-10T12:22:10.557952779Z + restimestampmock: 2025-11-10T12:22:10.577467805Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1676 +spec: + metadata: + connID: "538" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [192, 148, 209, 7, 189, 73, 115, 111, 100, 235, 208, 194, 50, 137, 250, 86, 54, 99, 64, 58, 155, 136, 192, 112, 23, 76, 71, 126, 250, 38, 0, 242] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 433 + auth_plugin_data: [126, 57, 66, 65, 118, 89, 14, 123, 120, 122, 5, 60, 117, 51, 76, 42, 32, 4, 70, 28, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777331 + reqtimestampmock: 2025-11-10T12:22:11.284269818Z + restimestampmock: 2025-11-10T12:22:11.292732766Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1677 +spec: + metadata: + connID: "538" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777331 + reqtimestampmock: 2025-11-10T12:22:11.292961694Z + restimestampmock: 2025-11-10T12:22:11.293181432Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1678 +spec: + metadata: + connID: "538" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777331 + reqtimestampmock: 2025-11-10T12:22:11.293335622Z + restimestampmock: 2025-11-10T12:22:11.29349701Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1679 +spec: + metadata: + connID: "538" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777331 + reqtimestampmock: 2025-11-10T12:22:11.293639239Z + restimestampmock: 2025-11-10T12:22:11.293749348Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1680 +spec: + metadata: + connID: "538" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='vivek_bhopal' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 237 + sequence_id: 6 + values: + - type: 253 + name: id + value: cd231165-d42c-4937-8415-50d80f3cfbf2 + unsigned: false + - type: 253 + name: username + value: vivek_bhopal + unsigned: false + - type: 253 + name: email + value: vivek_bhopal@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$tPirPR2JCoasqRdO$9279de0255bec58e045a4a19acdd64c299ed1b3d0b8aba3294936b9ff904405508b738f106ed9847648aaaddbf2dee52b1a761467773d8674ef7a7ceb8d11a21 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777331 + reqtimestampmock: 2025-11-10T12:22:11.294074824Z + restimestampmock: 2025-11-10T12:22:11.294714509Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1681 +spec: + metadata: + connID: "540" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [26, 201, 132, 141, 146, 147, 88, 173, 233, 128, 70, 84, 132, 81, 116, 116, 225, 136, 10, 110, 217, 189, 153, 171, 221, 104, 191, 216, 128, 153, 140, 42] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 434 + auth_plugin_data: [28, 69, 10, 51, 120, 23, 47, 55, 38, 99, 74, 71, 70, 90, 16, 6, 117, 58, 65, 72, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.03633405Z + restimestampmock: 2025-11-10T12:22:12.045489605Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1682 +spec: + metadata: + connID: "540" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.045812011Z + restimestampmock: 2025-11-10T12:22:12.04603462Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1683 +spec: + metadata: + connID: "540" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.046225418Z + restimestampmock: 2025-11-10T12:22:12.046411637Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1684 +spec: + metadata: + connID: "540" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.046530506Z + restimestampmock: 2025-11-10T12:22:12.046626335Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1685 +spec: + metadata: + connID: "540" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='cd231165-d42c-4937-8415-50d80f3cfbf2' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: cd231165-d42c-4937-8415-50d80f3cfbf2 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.046828343Z + restimestampmock: 2025-11-10T12:22:12.04723926Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1686 +spec: + metadata: + connID: "540" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 271 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('9e1de261-0fe2-4a26-abbb-6903869a2e78','cd231165-d42c-4937-8415-50d80f3cfbf2','Arera Colony',NULL,'Bhopal','Madhya Pradesh','462016','IN','+919425012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.047479847Z + restimestampmock: 2025-11-10T12:22:12.047830254Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1687 +spec: + metadata: + connID: "540" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='cd231165-d42c-4937-8415-50d80f3cfbf2' AND id<>'9e1de261-0fe2-4a26-abbb-6903869a2e78' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.048089012Z + restimestampmock: 2025-11-10T12:22:12.048496719Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1688 +spec: + metadata: + connID: "540" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.048643547Z + restimestampmock: 2025-11-10T12:22:12.057388815Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1689 +spec: + metadata: + connID: "542" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [242, 230, 52, 67, 49, 186, 45, 246, 133, 169, 46, 158, 85, 66, 101, 235, 1, 135, 174, 248, 110, 57, 89, 144, 216, 144, 63, 150, 162, 121, 214, 76] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 435 + auth_plugin_data: [72, 81, 106, 48, 96, 71, 47, 90, 88, 60, 93, 25, 93, 88, 49, 48, 120, 4, 13, 2, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.850222387Z + restimestampmock: 2025-11-10T12:22:12.860185843Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1690 +spec: + metadata: + connID: "542" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.860435871Z + restimestampmock: 2025-11-10T12:22:12.860657829Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1691 +spec: + metadata: + connID: "542" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.860800858Z + restimestampmock: 2025-11-10T12:22:12.860981687Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1692 +spec: + metadata: + connID: "542" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.861138406Z + restimestampmock: 2025-11-10T12:22:12.861267304Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1693 +spec: + metadata: + connID: "542" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='cd231165-d42c-4937-8415-50d80f3cfbf2' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: cd231165-d42c-4937-8415-50d80f3cfbf2 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.861505342Z + restimestampmock: 2025-11-10T12:22:12.861947798Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1694 +spec: + metadata: + connID: "542" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='cd231165-d42c-4937-8415-50d80f3cfbf2' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.862193516Z + restimestampmock: 2025-11-10T12:22:12.862791911Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1695 +spec: + metadata: + connID: "542" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='cd231165-d42c-4937-8415-50d80f3cfbf2' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.863075219Z + restimestampmock: 2025-11-10T12:22:12.863462356Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1696 +spec: + metadata: + connID: "542" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777332 + reqtimestampmock: 2025-11-10T12:22:12.863617294Z + restimestampmock: 2025-11-10T12:22:12.873464912Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1697 +spec: + metadata: + connID: "544" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [168, 179, 127, 86, 4, 69, 209, 236, 53, 180, 77, 84, 14, 172, 18, 218, 94, 101, 94, 175, 216, 59, 24, 18, 222, 32, 150, 48, 83, 112, 88, 164] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 436 + auth_plugin_data: [61, 23, 115, 26, 95, 122, 106, 42, 122, 68, 84, 51, 9, 3, 112, 53, 24, 122, 88, 76, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777334 + reqtimestampmock: 2025-11-10T12:22:14.656866063Z + restimestampmock: 2025-11-10T12:22:14.663461837Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1698 +spec: + metadata: + connID: "544" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777334 + reqtimestampmock: 2025-11-10T12:22:14.663635386Z + restimestampmock: 2025-11-10T12:22:14.663881834Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1699 +spec: + metadata: + connID: "544" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777334 + reqtimestampmock: 2025-11-10T12:22:14.664025003Z + restimestampmock: 2025-11-10T12:22:14.66426089Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1700 +spec: + metadata: + connID: "544" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777334 + reqtimestampmock: 2025-11-10T12:22:14.66439624Z + restimestampmock: 2025-11-10T12:22:14.664495728Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1701 +spec: + metadata: + connID: "544" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 327 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('9c9e241f-ff14-4eac-8296-5622d8fe401b', 'pooja_nagpur', 'pooja_nagpur@example.in', 'scrypt:32768:8:1$Qcpmub6wrIG1xjED$3d5eb31561030934fa4ef69914c7844fdcdd93e00ec3155162f57cc425df185cfc1e7962c7e11d434b64383ed03c1ab9277a69d9e67592c26ceccd5266c201ac', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777334 + reqtimestampmock: 2025-11-10T12:22:14.719520748Z + restimestampmock: 2025-11-10T12:22:14.720014904Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1702 +spec: + metadata: + connID: "544" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777334 + reqtimestampmock: 2025-11-10T12:22:14.720231412Z + restimestampmock: 2025-11-10T12:22:14.734429253Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1703 +spec: + metadata: + connID: "546" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [254, 222, 24, 130, 154, 102, 9, 75, 216, 20, 196, 123, 59, 175, 45, 202, 232, 149, 235, 165, 64, 34, 153, 91, 42, 87, 239, 87, 122, 21, 178, 154] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 438 + auth_plugin_data: [94, 39, 124, 122, 122, 90, 47, 91, 95, 27, 46, 112, 122, 99, 59, 54, 106, 9, 79, 119, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777335 + reqtimestampmock: 2025-11-10T12:22:15.485193028Z + restimestampmock: 2025-11-10T12:22:15.491771903Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1704 +spec: + metadata: + connID: "546" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777335 + reqtimestampmock: 2025-11-10T12:22:15.491934372Z + restimestampmock: 2025-11-10T12:22:15.492184129Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1705 +spec: + metadata: + connID: "546" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777335 + reqtimestampmock: 2025-11-10T12:22:15.492316339Z + restimestampmock: 2025-11-10T12:22:15.492481748Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1706 +spec: + metadata: + connID: "546" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777335 + reqtimestampmock: 2025-11-10T12:22:15.492608907Z + restimestampmock: 2025-11-10T12:22:15.492699275Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1707 +spec: + metadata: + connID: "546" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='pooja_nagpur' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 237 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9c9e241f-ff14-4eac-8296-5622d8fe401b + unsigned: false + - type: 253 + name: username + value: pooja_nagpur + unsigned: false + - type: 253 + name: email + value: pooja_nagpur@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Qcpmub6wrIG1xjED$3d5eb31561030934fa4ef69914c7844fdcdd93e00ec3155162f57cc425df185cfc1e7962c7e11d434b64383ed03c1ab9277a69d9e67592c26ceccd5266c201ac + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777335 + reqtimestampmock: 2025-11-10T12:22:15.492859604Z + restimestampmock: 2025-11-10T12:22:15.49328991Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1708 +spec: + metadata: + connID: "548" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [7, 188, 246, 132, 26, 179, 11, 156, 17, 152, 146, 147, 67, 171, 165, 213, 136, 165, 38, 153, 214, 79, 202, 131, 73, 124, 109, 194, 73, 63, 249, 215] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 439 + auth_plugin_data: [95, 94, 32, 60, 85, 25, 107, 125, 116, 43, 32, 124, 72, 30, 11, 34, 68, 84, 107, 11, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777336 + reqtimestampmock: 2025-11-10T12:22:16.229971983Z + restimestampmock: 2025-11-10T12:22:16.237663638Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1709 +spec: + metadata: + connID: "548" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777336 + reqtimestampmock: 2025-11-10T12:22:16.237854588Z + restimestampmock: 2025-11-10T12:22:16.238037965Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1710 +spec: + metadata: + connID: "548" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777336 + reqtimestampmock: 2025-11-10T12:22:16.238171994Z + restimestampmock: 2025-11-10T12:22:16.238405712Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1711 +spec: + metadata: + connID: "548" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777336 + reqtimestampmock: 2025-11-10T12:22:16.238623302Z + restimestampmock: 2025-11-10T12:22:16.23875284Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1712 +spec: + metadata: + connID: "548" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='9c9e241f-ff14-4eac-8296-5622d8fe401b' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 9c9e241f-ff14-4eac-8296-5622d8fe401b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777336 + reqtimestampmock: 2025-11-10T12:22:16.238975658Z + restimestampmock: 2025-11-10T12:22:16.239466734Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1713 +spec: + metadata: + connID: "548" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 265 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('df81c806-72b6-4a92-9f14-5fbeaa0c00ce','9c9e241f-ff14-4eac-8296-5622d8fe401b','Sitabuldi',NULL,'Nagpur','Maharashtra','440012','IN','+919823012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777336 + reqtimestampmock: 2025-11-10T12:22:16.239623913Z + restimestampmock: 2025-11-10T12:22:16.24000471Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1714 +spec: + metadata: + connID: "548" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='9c9e241f-ff14-4eac-8296-5622d8fe401b' AND id<>'df81c806-72b6-4a92-9f14-5fbeaa0c00ce' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777336 + reqtimestampmock: 2025-11-10T12:22:16.240173377Z + restimestampmock: 2025-11-10T12:22:16.240594414Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1715 +spec: + metadata: + connID: "548" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777336 + reqtimestampmock: 2025-11-10T12:22:16.241256829Z + restimestampmock: 2025-11-10T12:22:16.250412712Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1716 +spec: + metadata: + connID: "550" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [159, 127, 196, 14, 174, 147, 153, 33, 202, 2, 177, 162, 126, 168, 147, 177, 175, 134, 5, 2, 92, 129, 56, 172, 222, 119, 106, 241, 194, 140, 238, 196] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 440 + auth_plugin_data: [49, 106, 77, 95, 86, 124, 71, 121, 25, 123, 55, 30, 124, 44, 76, 124, 126, 83, 55, 93, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777337 + reqtimestampmock: 2025-11-10T12:22:17.040960995Z + restimestampmock: 2025-11-10T12:22:17.048369873Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1717 +spec: + metadata: + connID: "550" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777337 + reqtimestampmock: 2025-11-10T12:22:17.04864558Z + restimestampmock: 2025-11-10T12:22:17.048852988Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1718 +spec: + metadata: + connID: "550" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777337 + reqtimestampmock: 2025-11-10T12:22:17.049040627Z + restimestampmock: 2025-11-10T12:22:17.049302815Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1719 +spec: + metadata: + connID: "550" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777337 + reqtimestampmock: 2025-11-10T12:22:17.049466463Z + restimestampmock: 2025-11-10T12:22:17.049606492Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1720 +spec: + metadata: + connID: "550" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='9c9e241f-ff14-4eac-8296-5622d8fe401b' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 9c9e241f-ff14-4eac-8296-5622d8fe401b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777337 + reqtimestampmock: 2025-11-10T12:22:17.04984828Z + restimestampmock: 2025-11-10T12:22:17.050249287Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1721 +spec: + metadata: + connID: "550" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='9c9e241f-ff14-4eac-8296-5622d8fe401b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777337 + reqtimestampmock: 2025-11-10T12:22:17.050483684Z + restimestampmock: 2025-11-10T12:22:17.050904742Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1722 +spec: + metadata: + connID: "550" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='9c9e241f-ff14-4eac-8296-5622d8fe401b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777337 + reqtimestampmock: 2025-11-10T12:22:17.05106973Z + restimestampmock: 2025-11-10T12:22:17.051407437Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1723 +spec: + metadata: + connID: "550" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777337 + reqtimestampmock: 2025-11-10T12:22:17.051532916Z + restimestampmock: 2025-11-10T12:22:17.060391552Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1724 +spec: + metadata: + connID: "552" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [51, 44, 101, 161, 88, 46, 163, 134, 112, 35, 176, 189, 137, 140, 69, 56, 9, 77, 87, 92, 40, 15, 32, 151, 32, 127, 13, 143, 135, 201, 158, 121] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 441 + auth_plugin_data: [42, 68, 61, 97, 44, 108, 89, 57, 125, 35, 61, 79, 117, 16, 67, 20, 27, 67, 23, 47, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.23362354Z + restimestampmock: 2025-11-10T12:22:19.242146008Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1725 +spec: + metadata: + connID: "552" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.242416627Z + restimestampmock: 2025-11-10T12:22:19.242624204Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1726 +spec: + metadata: + connID: "552" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.242756213Z + restimestampmock: 2025-11-10T12:22:19.242932982Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1727 +spec: + metadata: + connID: "552" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.243067751Z + restimestampmock: 2025-11-10T12:22:19.243256129Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1728 +spec: + metadata: + connID: "552" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 327 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('9901edf5-b076-4850-9ada-b9c75949d59c', 'suresh_vizag', 'suresh_vizag@example.in', 'scrypt:32768:8:1$hWu3jNPzsKTqvyFS$f1c298721b78ec2cf15f0a1aaffb7193aa22e0cc90d390cb47be9ac3661c7a6b7acd0bb6cb73c6b62551d78be5228a4152fc8d5b655dc5618b533132c3896a24', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.299009172Z + restimestampmock: 2025-11-10T12:22:19.299663937Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1729 +spec: + metadata: + connID: "552" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.299825066Z + restimestampmock: 2025-11-10T12:22:19.314440683Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1730 +spec: + metadata: + connID: "554" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [220, 196, 18, 223, 168, 45, 92, 79, 22, 175, 25, 81, 245, 76, 152, 247, 222, 165, 100, 129, 74, 13, 173, 125, 84, 212, 146, 84, 108, 87, 152, 207] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 443 + auth_plugin_data: [21, 93, 14, 102, 5, 75, 116, 37, 54, 126, 126, 6, 87, 75, 32, 53, 80, 32, 105, 85, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.989398274Z + restimestampmock: 2025-11-10T12:22:19.99826487Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1731 +spec: + metadata: + connID: "554" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.998483498Z + restimestampmock: 2025-11-10T12:22:19.998681036Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1732 +spec: + metadata: + connID: "554" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.998801815Z + restimestampmock: 2025-11-10T12:22:19.998923574Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1733 +spec: + metadata: + connID: "554" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.999042223Z + restimestampmock: 2025-11-10T12:22:19.999178482Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1734 +spec: + metadata: + connID: "554" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='suresh_vizag' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 237 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9901edf5-b076-4850-9ada-b9c75949d59c + unsigned: false + - type: 253 + name: username + value: suresh_vizag + unsigned: false + - type: 253 + name: email + value: suresh_vizag@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$hWu3jNPzsKTqvyFS$f1c298721b78ec2cf15f0a1aaffb7193aa22e0cc90d390cb47be9ac3661c7a6b7acd0bb6cb73c6b62551d78be5228a4152fc8d5b655dc5618b533132c3896a24 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777339 + reqtimestampmock: 2025-11-10T12:22:19.9994145Z + restimestampmock: 2025-11-10T12:22:19.999972456Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1735 +spec: + metadata: + connID: "556" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [147, 187, 149, 101, 24, 108, 9, 251, 110, 29, 36, 147, 107, 212, 177, 236, 203, 96, 93, 168, 47, 18, 3, 168, 90, 174, 84, 10, 104, 151, 137, 237] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 444 + auth_plugin_data: [45, 35, 120, 105, 34, 116, 83, 37, 92, 117, 30, 39, 30, 49, 105, 37, 10, 75, 39, 54, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777340 + reqtimestampmock: 2025-11-10T12:22:20.737173105Z + restimestampmock: 2025-11-10T12:22:20.744535244Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1736 +spec: + metadata: + connID: "556" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777340 + reqtimestampmock: 2025-11-10T12:22:20.744736471Z + restimestampmock: 2025-11-10T12:22:20.744915861Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1737 +spec: + metadata: + connID: "556" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777340 + reqtimestampmock: 2025-11-10T12:22:20.74503654Z + restimestampmock: 2025-11-10T12:22:20.745181648Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1738 +spec: + metadata: + connID: "556" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777340 + reqtimestampmock: 2025-11-10T12:22:20.745294458Z + restimestampmock: 2025-11-10T12:22:20.745386826Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1739 +spec: + metadata: + connID: "556" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='9901edf5-b076-4850-9ada-b9c75949d59c' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 9901edf5-b076-4850-9ada-b9c75949d59c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777340 + reqtimestampmock: 2025-11-10T12:22:20.745796913Z + restimestampmock: 2025-11-10T12:22:20.746201189Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1740 +spec: + metadata: + connID: "556" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 279 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('04a4c42e-2bc4-4614-af7f-b84c7e7b598c','9901edf5-b076-4850-9ada-b9c75949d59c','RK Beach Road',NULL,'Visakhapatnam','Andhra Pradesh','530003','IN','+919849012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777340 + reqtimestampmock: 2025-11-10T12:22:20.746393138Z + restimestampmock: 2025-11-10T12:22:20.746707835Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1741 +spec: + metadata: + connID: "556" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='9901edf5-b076-4850-9ada-b9c75949d59c' AND id<>'04a4c42e-2bc4-4614-af7f-b84c7e7b598c' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777340 + reqtimestampmock: 2025-11-10T12:22:20.746833994Z + restimestampmock: 2025-11-10T12:22:20.747091702Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1742 +spec: + metadata: + connID: "556" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777340 + reqtimestampmock: 2025-11-10T12:22:20.747300321Z + restimestampmock: 2025-11-10T12:22:20.759352609Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1743 +spec: + metadata: + connID: "558" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [38, 89, 84, 19, 140, 202, 77, 54, 10, 33, 130, 73, 134, 58, 46, 56, 55, 246, 32, 30, 203, 46, 209, 110, 27, 173, 125, 83, 165, 109, 119, 126] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 445 + auth_plugin_data: [58, 115, 120, 86, 40, 71, 43, 34, 44, 49, 11, 23, 63, 23, 94, 55, 73, 110, 20, 84, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777341 + reqtimestampmock: 2025-11-10T12:22:21.463660474Z + restimestampmock: 2025-11-10T12:22:21.470993472Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1744 +spec: + metadata: + connID: "558" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777341 + reqtimestampmock: 2025-11-10T12:22:21.471317821Z + restimestampmock: 2025-11-10T12:22:21.471654267Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1745 +spec: + metadata: + connID: "558" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777341 + reqtimestampmock: 2025-11-10T12:22:21.471791737Z + restimestampmock: 2025-11-10T12:22:21.472040764Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1746 +spec: + metadata: + connID: "558" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777341 + reqtimestampmock: 2025-11-10T12:22:21.472190313Z + restimestampmock: 2025-11-10T12:22:21.472321722Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1747 +spec: + metadata: + connID: "558" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='9901edf5-b076-4850-9ada-b9c75949d59c' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 9901edf5-b076-4850-9ada-b9c75949d59c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777341 + reqtimestampmock: 2025-11-10T12:22:21.47253777Z + restimestampmock: 2025-11-10T12:22:21.472965687Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1748 +spec: + metadata: + connID: "558" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='9901edf5-b076-4850-9ada-b9c75949d59c' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777341 + reqtimestampmock: 2025-11-10T12:22:21.473194715Z + restimestampmock: 2025-11-10T12:22:21.476895874Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1749 +spec: + metadata: + connID: "558" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='9901edf5-b076-4850-9ada-b9c75949d59c' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777341 + reqtimestampmock: 2025-11-10T12:22:21.477048052Z + restimestampmock: 2025-11-10T12:22:21.47737438Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1750 +spec: + metadata: + connID: "558" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777341 + reqtimestampmock: 2025-11-10T12:22:21.477477409Z + restimestampmock: 2025-11-10T12:22:21.487323247Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1751 +spec: + metadata: + connID: "560" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [18, 231, 117, 158, 92, 10, 164, 44, 169, 233, 227, 147, 23, 45, 207, 86, 72, 255, 96, 189, 3, 138, 95, 249, 199, 159, 197, 47, 213, 131, 229, 57] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 446 + auth_plugin_data: [32, 33, 82, 30, 77, 123, 76, 80, 110, 13, 95, 22, 37, 85, 48, 120, 34, 111, 67, 38, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777343 + reqtimestampmock: 2025-11-10T12:22:23.898749774Z + restimestampmock: 2025-11-10T12:22:23.905270949Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1752 +spec: + metadata: + connID: "560" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777343 + reqtimestampmock: 2025-11-10T12:22:23.905522347Z + restimestampmock: 2025-11-10T12:22:23.905763666Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1753 +spec: + metadata: + connID: "560" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777343 + reqtimestampmock: 2025-11-10T12:22:23.905906504Z + restimestampmock: 2025-11-10T12:22:23.906059443Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1754 +spec: + metadata: + connID: "560" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777343 + reqtimestampmock: 2025-11-10T12:22:23.906200762Z + restimestampmock: 2025-11-10T12:22:23.90642192Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1755 +spec: + metadata: + connID: "560" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 325 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('86d9e8a4-6a00-46c9-9077-6362c89a686f', 'manoj_patna', 'manoj_patna@example.in', 'scrypt:32768:8:1$rQbNsBvJZUgQnbNf$ce561f843f8bab0b6f8f0da97db89b0094c0f4dd3d9714ec2ce2f012918701787ab85c149ce9bffd66682afc4b91de34d985d939c5efcaca70b7ed8ec85f838f', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777343 + reqtimestampmock: 2025-11-10T12:22:23.958023489Z + restimestampmock: 2025-11-10T12:22:23.958530494Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1756 +spec: + metadata: + connID: "560" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777343 + reqtimestampmock: 2025-11-10T12:22:23.958673892Z + restimestampmock: 2025-11-10T12:22:23.971346676Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1757 +spec: + metadata: + connID: "562" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [181, 9, 161, 6, 18, 69, 241, 46, 171, 112, 108, 179, 46, 40, 3, 64, 3, 135, 228, 108, 247, 63, 139, 79, 215, 33, 24, 23, 53, 237, 181, 171] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 447 + auth_plugin_data: [22, 77, 111, 2, 104, 10, 22, 27, 79, 122, 113, 100, 49, 112, 15, 72, 19, 7, 85, 84, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777344 + reqtimestampmock: 2025-11-10T12:22:24.650001528Z + restimestampmock: 2025-11-10T12:22:24.657962261Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1758 +spec: + metadata: + connID: "562" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777344 + reqtimestampmock: 2025-11-10T12:22:24.658175839Z + restimestampmock: 2025-11-10T12:22:24.658422417Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1759 +spec: + metadata: + connID: "562" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777344 + reqtimestampmock: 2025-11-10T12:22:24.658528906Z + restimestampmock: 2025-11-10T12:22:24.658702354Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1760 +spec: + metadata: + connID: "562" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777344 + reqtimestampmock: 2025-11-10T12:22:24.658806084Z + restimestampmock: 2025-11-10T12:22:24.658926893Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1761 +spec: + metadata: + connID: "562" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 84 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='manoj_patna' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 235 + sequence_id: 6 + values: + - type: 253 + name: id + value: 86d9e8a4-6a00-46c9-9077-6362c89a686f + unsigned: false + - type: 253 + name: username + value: manoj_patna + unsigned: false + - type: 253 + name: email + value: manoj_patna@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$rQbNsBvJZUgQnbNf$ce561f843f8bab0b6f8f0da97db89b0094c0f4dd3d9714ec2ce2f012918701787ab85c149ce9bffd66682afc4b91de34d985d939c5efcaca70b7ed8ec85f838f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777344 + reqtimestampmock: 2025-11-10T12:22:24.659082502Z + restimestampmock: 2025-11-10T12:22:24.659602098Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1762 +spec: + metadata: + connID: "564" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [244, 112, 126, 200, 152, 2, 83, 194, 69, 81, 238, 39, 83, 245, 204, 79, 229, 215, 6, 175, 134, 35, 40, 31, 160, 5, 67, 216, 152, 38, 242, 14] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 449 + auth_plugin_data: [60, 110, 67, 102, 22, 127, 96, 102, 25, 56, 12, 34, 64, 116, 19, 70, 124, 119, 23, 118, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777345 + reqtimestampmock: 2025-11-10T12:22:25.455049929Z + restimestampmock: 2025-11-10T12:22:25.463446179Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1763 +spec: + metadata: + connID: "564" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777345 + reqtimestampmock: 2025-11-10T12:22:25.463659648Z + restimestampmock: 2025-11-10T12:22:25.463860096Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1764 +spec: + metadata: + connID: "564" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777345 + reqtimestampmock: 2025-11-10T12:22:25.464033114Z + restimestampmock: 2025-11-10T12:22:25.464222123Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1765 +spec: + metadata: + connID: "564" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777345 + reqtimestampmock: 2025-11-10T12:22:25.464355002Z + restimestampmock: 2025-11-10T12:22:25.46447469Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1766 +spec: + metadata: + connID: "564" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='86d9e8a4-6a00-46c9-9077-6362c89a686f' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 86d9e8a4-6a00-46c9-9077-6362c89a686f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777345 + reqtimestampmock: 2025-11-10T12:22:25.464697759Z + restimestampmock: 2025-11-10T12:22:25.465137365Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1767 +spec: + metadata: + connID: "564" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 260 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('06437b9d-d8b1-4db6-b86c-f2430b82c704','86d9e8a4-6a00-46c9-9077-6362c89a686f','Boring Road',NULL,'Patna','Bihar','800001','IN','+919431012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777345 + reqtimestampmock: 2025-11-10T12:22:25.465355914Z + restimestampmock: 2025-11-10T12:22:25.465795439Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1768 +spec: + metadata: + connID: "564" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='86d9e8a4-6a00-46c9-9077-6362c89a686f' AND id<>'06437b9d-d8b1-4db6-b86c-f2430b82c704' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777345 + reqtimestampmock: 2025-11-10T12:22:25.466006238Z + restimestampmock: 2025-11-10T12:22:25.466345795Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1769 +spec: + metadata: + connID: "564" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777345 + reqtimestampmock: 2025-11-10T12:22:25.466551912Z + restimestampmock: 2025-11-10T12:22:25.477333013Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1770 +spec: + metadata: + connID: "566" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [0, 34, 132, 45, 95, 176, 231, 230, 233, 92, 211, 27, 62, 34, 1, 220, 177, 203, 245, 124, 18, 5, 87, 70, 129, 33, 234, 51, 205, 188, 191, 206] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 450 + auth_plugin_data: [33, 1, 12, 52, 67, 42, 124, 52, 50, 102, 60, 56, 24, 106, 35, 101, 125, 42, 89, 58, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777346 + reqtimestampmock: 2025-11-10T12:22:26.191218368Z + restimestampmock: 2025-11-10T12:22:26.20066323Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1771 +spec: + metadata: + connID: "566" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777346 + reqtimestampmock: 2025-11-10T12:22:26.200908988Z + restimestampmock: 2025-11-10T12:22:26.201122286Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1772 +spec: + metadata: + connID: "566" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777346 + reqtimestampmock: 2025-11-10T12:22:26.201269325Z + restimestampmock: 2025-11-10T12:22:26.201425444Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1773 +spec: + metadata: + connID: "566" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777346 + reqtimestampmock: 2025-11-10T12:22:26.201542363Z + restimestampmock: 2025-11-10T12:22:26.201648321Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1774 +spec: + metadata: + connID: "566" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='86d9e8a4-6a00-46c9-9077-6362c89a686f' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 86d9e8a4-6a00-46c9-9077-6362c89a686f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777346 + reqtimestampmock: 2025-11-10T12:22:26.2018294Z + restimestampmock: 2025-11-10T12:22:26.202228467Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1775 +spec: + metadata: + connID: "566" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='86d9e8a4-6a00-46c9-9077-6362c89a686f' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777346 + reqtimestampmock: 2025-11-10T12:22:26.202390786Z + restimestampmock: 2025-11-10T12:22:26.202773812Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1776 +spec: + metadata: + connID: "566" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='86d9e8a4-6a00-46c9-9077-6362c89a686f' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777346 + reqtimestampmock: 2025-11-10T12:22:26.202916991Z + restimestampmock: 2025-11-10T12:22:26.203249218Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1777 +spec: + metadata: + connID: "566" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777346 + reqtimestampmock: 2025-11-10T12:22:26.203342158Z + restimestampmock: 2025-11-10T12:22:26.219134566Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1778 +spec: + metadata: + connID: "568" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [246, 91, 17, 175, 96, 171, 245, 117, 45, 47, 192, 73, 250, 227, 190, 95, 137, 89, 158, 156, 232, 154, 192, 107, 159, 62, 189, 143, 147, 26, 92, 83] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 451 + auth_plugin_data: [21, 23, 57, 29, 101, 34, 79, 126, 85, 82, 67, 84, 63, 37, 52, 65, 24, 75, 91, 75, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777348 + reqtimestampmock: 2025-11-10T12:22:28.684251567Z + restimestampmock: 2025-11-10T12:22:28.691738684Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1779 +spec: + metadata: + connID: "568" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777348 + reqtimestampmock: 2025-11-10T12:22:28.691952432Z + restimestampmock: 2025-11-10T12:22:28.692170061Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1780 +spec: + metadata: + connID: "568" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777348 + reqtimestampmock: 2025-11-10T12:22:28.692331029Z + restimestampmock: 2025-11-10T12:22:28.692484828Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1781 +spec: + metadata: + connID: "568" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777348 + reqtimestampmock: 2025-11-10T12:22:28.692624567Z + restimestampmock: 2025-11-10T12:22:28.692732946Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1782 +spec: + metadata: + connID: "568" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 327 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('9c22935f-1a65-4d1a-9064-c702ca10afc9', 'harpreet_ldh', 'harpreet_ldh@example.in', 'scrypt:32768:8:1$YE5HfZtjgApGT4iW$77dc414d897e35fe765b12203a651cc190517d202054ed05a0aaa3b26790c90eeccfeeb0e4a36adee7afe70b7e79d6fbf8cae241ffccff027ca42a70ab92ee06', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777348 + reqtimestampmock: 2025-11-10T12:22:28.749127684Z + restimestampmock: 2025-11-10T12:22:28.749702639Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1783 +spec: + metadata: + connID: "568" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777348 + reqtimestampmock: 2025-11-10T12:22:28.749918077Z + restimestampmock: 2025-11-10T12:22:28.761292272Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1784 +spec: + metadata: + connID: "570" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [148, 108, 35, 51, 168, 74, 66, 103, 117, 185, 213, 80, 137, 141, 253, 77, 114, 56, 188, 209, 101, 116, 45, 144, 153, 62, 160, 255, 85, 181, 198, 193] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 452 + auth_plugin_data: [47, 95, 114, 88, 30, 50, 9, 69, 48, 82, 31, 59, 43, 81, 101, 38, 28, 11, 92, 95, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777349 + reqtimestampmock: 2025-11-10T12:22:29.454466132Z + restimestampmock: 2025-11-10T12:22:29.462006929Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1785 +spec: + metadata: + connID: "570" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777349 + reqtimestampmock: 2025-11-10T12:22:29.462384435Z + restimestampmock: 2025-11-10T12:22:29.462601814Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1786 +spec: + metadata: + connID: "570" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777349 + reqtimestampmock: 2025-11-10T12:22:29.462762792Z + restimestampmock: 2025-11-10T12:22:29.46293459Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1787 +spec: + metadata: + connID: "570" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777349 + reqtimestampmock: 2025-11-10T12:22:29.463057799Z + restimestampmock: 2025-11-10T12:22:29.463231048Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1788 +spec: + metadata: + connID: "570" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='harpreet_ldh' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 237 + sequence_id: 6 + values: + - type: 253 + name: id + value: 9c22935f-1a65-4d1a-9064-c702ca10afc9 + unsigned: false + - type: 253 + name: username + value: harpreet_ldh + unsigned: false + - type: 253 + name: email + value: harpreet_ldh@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$YE5HfZtjgApGT4iW$77dc414d897e35fe765b12203a651cc190517d202054ed05a0aaa3b26790c90eeccfeeb0e4a36adee7afe70b7e79d6fbf8cae241ffccff027ca42a70ab92ee06 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777349 + reqtimestampmock: 2025-11-10T12:22:29.463454737Z + restimestampmock: 2025-11-10T12:22:29.463887303Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1789 +spec: + metadata: + connID: "572" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [130, 243, 161, 66, 146, 145, 230, 209, 143, 248, 121, 96, 61, 208, 220, 81, 17, 201, 173, 14, 150, 230, 45, 231, 7, 117, 145, 109, 161, 187, 64, 68] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 454 + auth_plugin_data: [21, 61, 37, 46, 27, 113, 19, 65, 38, 61, 126, 103, 37, 103, 106, 88, 26, 92, 64, 80, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777350 + reqtimestampmock: 2025-11-10T12:22:30.280901586Z + restimestampmock: 2025-11-10T12:22:30.288741111Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1790 +spec: + metadata: + connID: "572" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777350 + reqtimestampmock: 2025-11-10T12:22:30.288987558Z + restimestampmock: 2025-11-10T12:22:30.289209497Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1791 +spec: + metadata: + connID: "572" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777350 + reqtimestampmock: 2025-11-10T12:22:30.289411675Z + restimestampmock: 2025-11-10T12:22:30.289574104Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1792 +spec: + metadata: + connID: "572" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777350 + reqtimestampmock: 2025-11-10T12:22:30.289706733Z + restimestampmock: 2025-11-10T12:22:30.289833842Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1793 +spec: + metadata: + connID: "572" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='9c22935f-1a65-4d1a-9064-c702ca10afc9' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 9c22935f-1a65-4d1a-9064-c702ca10afc9 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777350 + reqtimestampmock: 2025-11-10T12:22:30.2901069Z + restimestampmock: 2025-11-10T12:22:30.290556195Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1794 +spec: + metadata: + connID: "572" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 266 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('751a63b7-51ee-4790-b9e3-e93190ce458f','9c22935f-1a65-4d1a-9064-c702ca10afc9','Sarabha Nagar',NULL,'Ludhiana','Punjab','141001','IN','+919815012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777350 + reqtimestampmock: 2025-11-10T12:22:30.290770823Z + restimestampmock: 2025-11-10T12:22:30.29116722Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1795 +spec: + metadata: + connID: "572" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='9c22935f-1a65-4d1a-9064-c702ca10afc9' AND id<>'751a63b7-51ee-4790-b9e3-e93190ce458f' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777350 + reqtimestampmock: 2025-11-10T12:22:30.291350189Z + restimestampmock: 2025-11-10T12:22:30.291787574Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1796 +spec: + metadata: + connID: "572" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777350 + reqtimestampmock: 2025-11-10T12:22:30.291908133Z + restimestampmock: 2025-11-10T12:22:30.305470251Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1797 +spec: + metadata: + connID: "574" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [17, 130, 110, 101, 173, 199, 173, 62, 9, 51, 216, 220, 25, 189, 67, 239, 15, 112, 17, 106, 102, 182, 126, 238, 132, 37, 100, 190, 69, 72, 178, 229] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 455 + auth_plugin_data: [40, 55, 9, 62, 3, 106, 105, 124, 103, 86, 1, 110, 38, 7, 96, 48, 41, 27, 2, 97, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777351 + reqtimestampmock: 2025-11-10T12:22:31.015918757Z + restimestampmock: 2025-11-10T12:22:31.024820672Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1798 +spec: + metadata: + connID: "574" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777351 + reqtimestampmock: 2025-11-10T12:22:31.025116929Z + restimestampmock: 2025-11-10T12:22:31.025382517Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1799 +spec: + metadata: + connID: "574" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777351 + reqtimestampmock: 2025-11-10T12:22:31.025521996Z + restimestampmock: 2025-11-10T12:22:31.025728654Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1800 +spec: + metadata: + connID: "574" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777351 + reqtimestampmock: 2025-11-10T12:22:31.025866384Z + restimestampmock: 2025-11-10T12:22:31.026009972Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1801 +spec: + metadata: + connID: "574" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='9c22935f-1a65-4d1a-9064-c702ca10afc9' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 9c22935f-1a65-4d1a-9064-c702ca10afc9 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777351 + reqtimestampmock: 2025-11-10T12:22:31.02624952Z + restimestampmock: 2025-11-10T12:22:31.026716345Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1802 +spec: + metadata: + connID: "574" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='9c22935f-1a65-4d1a-9064-c702ca10afc9' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777351 + reqtimestampmock: 2025-11-10T12:22:31.026952924Z + restimestampmock: 2025-11-10T12:22:31.027437629Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1803 +spec: + metadata: + connID: "574" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='9c22935f-1a65-4d1a-9064-c702ca10afc9' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777351 + reqtimestampmock: 2025-11-10T12:22:31.027676399Z + restimestampmock: 2025-11-10T12:22:31.028192104Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1804 +spec: + metadata: + connID: "574" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777351 + reqtimestampmock: 2025-11-10T12:22:31.028338642Z + restimestampmock: 2025-11-10T12:22:31.042295705Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1805 +spec: + metadata: + connID: "576" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [105, 92, 135, 35, 228, 12, 8, 168, 83, 133, 213, 119, 225, 94, 150, 144, 93, 188, 165, 134, 246, 67, 103, 63, 114, 154, 37, 14, 109, 16, 38, 14] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 456 + auth_plugin_data: [119, 13, 105, 82, 22, 21, 33, 112, 41, 59, 95, 30, 47, 48, 37, 32, 58, 20, 77, 15, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777352 + reqtimestampmock: 2025-11-10T12:22:32.986664708Z + restimestampmock: 2025-11-10T12:22:32.996824473Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1806 +spec: + metadata: + connID: "576" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777352 + reqtimestampmock: 2025-11-10T12:22:32.997086391Z + restimestampmock: 2025-11-10T12:22:32.997358878Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1807 +spec: + metadata: + connID: "576" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777352 + reqtimestampmock: 2025-11-10T12:22:32.997504497Z + restimestampmock: 2025-11-10T12:22:32.997685575Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1808 +spec: + metadata: + connID: "576" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777352 + reqtimestampmock: 2025-11-10T12:22:32.997816974Z + restimestampmock: 2025-11-10T12:22:32.998005772Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1809 +spec: + metadata: + connID: "576" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 323 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a', 'aisha_agra', 'aisha_agra@example.in', 'scrypt:32768:8:1$Ah1ScQWcEVoEoykx$0eca4ea8dcdb5d0cfe698973f914ea04eb23061df8f1a862a65e915589f37a7d738ab1c49793f4eeafd9112efad673eb1bbeed0009ef73b8c806e103742bbe46', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777353 + reqtimestampmock: 2025-11-10T12:22:33.057378316Z + restimestampmock: 2025-11-10T12:22:33.057914942Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1810 +spec: + metadata: + connID: "576" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777353 + reqtimestampmock: 2025-11-10T12:22:33.058091381Z + restimestampmock: 2025-11-10T12:22:33.069286767Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1811 +spec: + metadata: + connID: "578" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [177, 158, 197, 192, 182, 0, 4, 183, 52, 89, 64, 178, 109, 104, 84, 22, 152, 214, 129, 250, 144, 95, 125, 86, 157, 197, 255, 86, 92, 14, 1, 225] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 457 + auth_plugin_data: [57, 5, 124, 69, 84, 67, 76, 68, 127, 109, 56, 104, 6, 116, 3, 43, 109, 50, 126, 40, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777353 + reqtimestampmock: 2025-11-10T12:22:33.829715564Z + restimestampmock: 2025-11-10T12:22:33.838446691Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1812 +spec: + metadata: + connID: "578" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777353 + reqtimestampmock: 2025-11-10T12:22:33.83868472Z + restimestampmock: 2025-11-10T12:22:33.838864918Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1813 +spec: + metadata: + connID: "578" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777353 + reqtimestampmock: 2025-11-10T12:22:33.838976466Z + restimestampmock: 2025-11-10T12:22:33.839117375Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1814 +spec: + metadata: + connID: "578" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777353 + reqtimestampmock: 2025-11-10T12:22:33.839273814Z + restimestampmock: 2025-11-10T12:22:33.839371023Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1815 +spec: + metadata: + connID: "578" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 83 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='aisha_agra' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 233 + sequence_id: 6 + values: + - type: 253 + name: id + value: 5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a + unsigned: false + - type: 253 + name: username + value: aisha_agra + unsigned: false + - type: 253 + name: email + value: aisha_agra@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Ah1ScQWcEVoEoykx$0eca4ea8dcdb5d0cfe698973f914ea04eb23061df8f1a862a65e915589f37a7d738ab1c49793f4eeafd9112efad673eb1bbeed0009ef73b8c806e103742bbe46 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777353 + reqtimestampmock: 2025-11-10T12:22:33.839542102Z + restimestampmock: 2025-11-10T12:22:33.839904779Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1816 +spec: + metadata: + connID: "580" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [208, 127, 21, 202, 82, 0, 118, 244, 74, 200, 168, 226, 156, 171, 17, 93, 142, 92, 1, 46, 245, 105, 126, 46, 224, 134, 169, 237, 162, 80, 30, 85] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 458 + auth_plugin_data: [86, 53, 40, 64, 58, 114, 78, 48, 61, 62, 112, 102, 55, 24, 120, 124, 72, 99, 98, 114, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777354 + reqtimestampmock: 2025-11-10T12:22:34.625814493Z + restimestampmock: 2025-11-10T12:22:34.632425209Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1817 +spec: + metadata: + connID: "580" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777354 + reqtimestampmock: 2025-11-10T12:22:34.632632636Z + restimestampmock: 2025-11-10T12:22:34.632797785Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1818 +spec: + metadata: + connID: "580" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777354 + reqtimestampmock: 2025-11-10T12:22:34.632888114Z + restimestampmock: 2025-11-10T12:22:34.633143772Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1819 +spec: + metadata: + connID: "580" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777354 + reqtimestampmock: 2025-11-10T12:22:34.633243752Z + restimestampmock: 2025-11-10T12:22:34.633348561Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1820 +spec: + metadata: + connID: "580" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777354 + reqtimestampmock: 2025-11-10T12:22:34.633785926Z + restimestampmock: 2025-11-10T12:22:34.633917836Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1821 +spec: + metadata: + connID: "580" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 264 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('ea294c9b-cec5-4bb4-aca0-c963574e5179','5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a','Taj Ganj',NULL,'Agra','Uttar Pradesh','282001','IN','+919837012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777354 + reqtimestampmock: 2025-11-10T12:22:34.634090884Z + restimestampmock: 2025-11-10T12:22:34.634401701Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1822 +spec: + metadata: + connID: "580" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a' AND id<>'ea294c9b-cec5-4bb4-aca0-c963574e5179' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777354 + reqtimestampmock: 2025-11-10T12:22:34.6345297Z + restimestampmock: 2025-11-10T12:22:34.634814138Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1823 +spec: + metadata: + connID: "580" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777354 + reqtimestampmock: 2025-11-10T12:22:34.634930247Z + restimestampmock: 2025-11-10T12:22:34.644181439Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1824 +spec: + metadata: + connID: "582" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [145, 139, 31, 131, 105, 26, 144, 244, 105, 121, 152, 138, 142, 109, 66, 39, 11, 208, 128, 239, 243, 91, 77, 105, 207, 158, 68, 97, 152, 14, 186, 251] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 460 + auth_plugin_data: [40, 99, 107, 75, 77, 33, 65, 84, 38, 46, 124, 127, 123, 24, 118, 87, 82, 83, 63, 92, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777355 + reqtimestampmock: 2025-11-10T12:22:35.421368758Z + restimestampmock: 2025-11-10T12:22:35.431482623Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1825 +spec: + metadata: + connID: "582" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777355 + reqtimestampmock: 2025-11-10T12:22:35.431788781Z + restimestampmock: 2025-11-10T12:22:35.431972309Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1826 +spec: + metadata: + connID: "582" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777355 + reqtimestampmock: 2025-11-10T12:22:35.432206107Z + restimestampmock: 2025-11-10T12:22:35.432370256Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1827 +spec: + metadata: + connID: "582" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777355 + reqtimestampmock: 2025-11-10T12:22:35.432501085Z + restimestampmock: 2025-11-10T12:22:35.432620424Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1828 +spec: + metadata: + connID: "582" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777355 + reqtimestampmock: 2025-11-10T12:22:35.432974621Z + restimestampmock: 2025-11-10T12:22:35.433443447Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1829 +spec: + metadata: + connID: "582" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777355 + reqtimestampmock: 2025-11-10T12:22:35.433715105Z + restimestampmock: 2025-11-10T12:22:35.43426147Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1830 +spec: + metadata: + connID: "582" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777355 + reqtimestampmock: 2025-11-10T12:22:35.434457409Z + restimestampmock: 2025-11-10T12:22:35.434850575Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1831 +spec: + metadata: + connID: "582" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777355 + reqtimestampmock: 2025-11-10T12:22:35.435077874Z + restimestampmock: 2025-11-10T12:22:35.448237593Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1832 +spec: + metadata: + connID: "584" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [175, 44, 102, 198, 36, 100, 62, 161, 121, 171, 3, 244, 136, 48, 142, 168, 201, 36, 243, 252, 238, 149, 106, 46, 18, 226, 114, 194, 252, 166, 16, 241] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 461 + auth_plugin_data: [10, 55, 88, 126, 71, 123, 105, 117, 91, 43, 50, 75, 63, 59, 17, 113, 73, 12, 118, 1, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777357 + reqtimestampmock: 2025-11-10T12:22:37.197491678Z + restimestampmock: 2025-11-10T12:22:37.204921097Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1833 +spec: + metadata: + connID: "584" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777357 + reqtimestampmock: 2025-11-10T12:22:37.205240394Z + restimestampmock: 2025-11-10T12:22:37.205465652Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1834 +spec: + metadata: + connID: "584" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777357 + reqtimestampmock: 2025-11-10T12:22:37.205625301Z + restimestampmock: 2025-11-10T12:22:37.206497304Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1835 +spec: + metadata: + connID: "584" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777357 + reqtimestampmock: 2025-11-10T12:22:37.206646642Z + restimestampmock: 2025-11-10T12:22:37.206807211Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1836 +spec: + metadata: + connID: "584" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('cde545bf-bacd-4efb-891d-b197fc7e0e6c', 'parth_vadodara', 'parth_vadodara@example.in', 'scrypt:32768:8:1$HwF56AUmG6g1kg7K$2a0a2b32314a8a62fd061e5430606b67d7595b4e4f1540e0cadcd91787a8ae892347bb5c56454bacea616a6a176716a72335c71cf9e905648ac7386af5fcf46f', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777357 + reqtimestampmock: 2025-11-10T12:22:37.265306042Z + restimestampmock: 2025-11-10T12:22:37.265866057Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1837 +spec: + metadata: + connID: "584" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777357 + reqtimestampmock: 2025-11-10T12:22:37.266126405Z + restimestampmock: 2025-11-10T12:22:37.280201107Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1838 +spec: + metadata: + connID: "586" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [17, 111, 195, 146, 164, 46, 110, 209, 138, 158, 149, 28, 72, 131, 22, 95, 94, 77, 205, 225, 219, 35, 170, 140, 87, 213, 36, 79, 59, 110, 84, 150] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 462 + auth_plugin_data: [16, 22, 126, 62, 42, 86, 6, 54, 49, 28, 39, 103, 1, 18, 92, 75, 70, 85, 90, 6, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.018159334Z + restimestampmock: 2025-11-10T12:22:38.025182046Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1839 +spec: + metadata: + connID: "586" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.025381504Z + restimestampmock: 2025-11-10T12:22:38.025635862Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1840 +spec: + metadata: + connID: "586" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.025801061Z + restimestampmock: 2025-11-10T12:22:38.026003608Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1841 +spec: + metadata: + connID: "586" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.026118887Z + restimestampmock: 2025-11-10T12:22:38.026269817Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1842 +spec: + metadata: + connID: "586" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='parth_vadodara' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: cde545bf-bacd-4efb-891d-b197fc7e0e6c + unsigned: false + - type: 253 + name: username + value: parth_vadodara + unsigned: false + - type: 253 + name: email + value: parth_vadodara@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$HwF56AUmG6g1kg7K$2a0a2b32314a8a62fd061e5430606b67d7595b4e4f1540e0cadcd91787a8ae892347bb5c56454bacea616a6a176716a72335c71cf9e905648ac7386af5fcf46f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.026434665Z + restimestampmock: 2025-11-10T12:22:38.026929441Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1843 +spec: + metadata: + connID: "588" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [136, 141, 54, 41, 239, 147, 112, 49, 167, 247, 8, 4, 191, 243, 205, 172, 189, 223, 53, 44, 199, 53, 192, 115, 63, 52, 155, 231, 70, 209, 123, 96] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 463 + auth_plugin_data: [92, 41, 34, 85, 75, 6, 31, 105, 37, 23, 9, 74, 57, 81, 35, 86, 29, 92, 91, 116, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.765551753Z + restimestampmock: 2025-11-10T12:22:38.773550966Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1844 +spec: + metadata: + connID: "588" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.773774193Z + restimestampmock: 2025-11-10T12:22:38.773973501Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1845 +spec: + metadata: + connID: "588" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.77410318Z + restimestampmock: 2025-11-10T12:22:38.774268529Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1846 +spec: + metadata: + connID: "588" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.774379468Z + restimestampmock: 2025-11-10T12:22:38.774450987Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1847 +spec: + metadata: + connID: "588" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='cde545bf-bacd-4efb-891d-b197fc7e0e6c' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: cde545bf-bacd-4efb-891d-b197fc7e0e6c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.774618676Z + restimestampmock: 2025-11-10T12:22:38.775011442Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1848 +spec: + metadata: + connID: "588" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 262 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('5e2876ef-bc0a-4d94-b867-bad0945c4275','cde545bf-bacd-4efb-891d-b197fc7e0e6c','Alkapuri',NULL,'Vadodara','Gujarat','390007','IN','+919824012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.775262511Z + restimestampmock: 2025-11-10T12:22:38.775612957Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1849 +spec: + metadata: + connID: "588" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='cde545bf-bacd-4efb-891d-b197fc7e0e6c' AND id<>'5e2876ef-bc0a-4d94-b867-bad0945c4275' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.775776827Z + restimestampmock: 2025-11-10T12:22:38.776088105Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1850 +spec: + metadata: + connID: "588" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777358 + reqtimestampmock: 2025-11-10T12:22:38.776224032Z + restimestampmock: 2025-11-10T12:22:38.786135869Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1851 +spec: + metadata: + connID: "590" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [137, 216, 137, 73, 114, 149, 131, 47, 231, 177, 15, 52, 42, 145, 113, 235, 29, 15, 91, 2, 195, 148, 136, 48, 182, 65, 113, 157, 9, 163, 168, 115] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 464 + auth_plugin_data: [81, 1, 59, 63, 115, 29, 80, 74, 1, 46, 51, 57, 35, 79, 12, 16, 30, 4, 65, 123, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777359 + reqtimestampmock: 2025-11-10T12:22:39.592934752Z + restimestampmock: 2025-11-10T12:22:39.602823419Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1852 +spec: + metadata: + connID: "590" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777359 + reqtimestampmock: 2025-11-10T12:22:39.603049846Z + restimestampmock: 2025-11-10T12:22:39.603291055Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1853 +spec: + metadata: + connID: "590" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777359 + reqtimestampmock: 2025-11-10T12:22:39.603442664Z + restimestampmock: 2025-11-10T12:22:39.603595962Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1854 +spec: + metadata: + connID: "590" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777359 + reqtimestampmock: 2025-11-10T12:22:39.603717151Z + restimestampmock: 2025-11-10T12:22:39.603834101Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1855 +spec: + metadata: + connID: "590" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='cde545bf-bacd-4efb-891d-b197fc7e0e6c' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: cde545bf-bacd-4efb-891d-b197fc7e0e6c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777359 + reqtimestampmock: 2025-11-10T12:22:39.604030448Z + restimestampmock: 2025-11-10T12:22:39.604511484Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1856 +spec: + metadata: + connID: "590" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='cde545bf-bacd-4efb-891d-b197fc7e0e6c' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777359 + reqtimestampmock: 2025-11-10T12:22:39.604642913Z + restimestampmock: 2025-11-10T12:22:39.605122069Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1857 +spec: + metadata: + connID: "590" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='cde545bf-bacd-4efb-891d-b197fc7e0e6c' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777359 + reqtimestampmock: 2025-11-10T12:22:39.605452717Z + restimestampmock: 2025-11-10T12:22:39.605908132Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1858 +spec: + metadata: + connID: "590" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777359 + reqtimestampmock: 2025-11-10T12:22:39.606157621Z + restimestampmock: 2025-11-10T12:22:39.616160016Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1859 +spec: + metadata: + connID: "592" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [182, 157, 128, 210, 18, 109, 38, 209, 204, 50, 118, 6, 146, 86, 213, 240, 214, 115, 99, 180, 13, 92, 35, 249, 48, 191, 92, 139, 71, 99, 210, 46] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 466 + auth_plugin_data: [101, 37, 68, 49, 100, 18, 24, 95, 78, 110, 46, 100, 24, 63, 8, 32, 114, 78, 39, 43, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777361 + reqtimestampmock: 2025-11-10T12:22:41.427909563Z + restimestampmock: 2025-11-10T12:22:41.435624038Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1860 +spec: + metadata: + connID: "592" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777361 + reqtimestampmock: 2025-11-10T12:22:41.435848436Z + restimestampmock: 2025-11-10T12:22:41.436040984Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1861 +spec: + metadata: + connID: "592" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777361 + reqtimestampmock: 2025-11-10T12:22:41.436180063Z + restimestampmock: 2025-11-10T12:22:41.436387021Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1862 +spec: + metadata: + connID: "592" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777361 + reqtimestampmock: 2025-11-10T12:22:41.4365261Z + restimestampmock: 2025-11-10T12:22:41.43665221Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1863 +spec: + metadata: + connID: "592" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('a22e67bf-eae3-4636-8ef7-0a9772a483d9', 'bipul_guwahati', 'bipul_guwahati@example.in', 'scrypt:32768:8:1$sFD1Q5Mofpzd6xWQ$a210482e3e631d2ff5d30de5c403f399f2affdac186a42dc1b0848483a855063af8386e7131ca3e45d57fc0bf3549ad2a94ed2b83d34e14a85e57a4fa78f8212', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777361 + reqtimestampmock: 2025-11-10T12:22:41.492988788Z + restimestampmock: 2025-11-10T12:22:41.493557813Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1864 +spec: + metadata: + connID: "592" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777361 + reqtimestampmock: 2025-11-10T12:22:41.493688242Z + restimestampmock: 2025-11-10T12:22:41.507112869Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1865 +spec: + metadata: + connID: "594" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [48, 196, 120, 37, 237, 155, 139, 184, 197, 35, 62, 106, 212, 210, 5, 17, 153, 82, 163, 225, 185, 133, 205, 172, 22, 6, 134, 67, 110, 187, 53, 207] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 467 + auth_plugin_data: [72, 18, 120, 100, 106, 5, 75, 62, 50, 16, 119, 65, 30, 26, 123, 7, 88, 119, 39, 110, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.20533353Z + restimestampmock: 2025-11-10T12:22:42.212305611Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1866 +spec: + metadata: + connID: "594" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.212471361Z + restimestampmock: 2025-11-10T12:22:42.212650959Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1867 +spec: + metadata: + connID: "594" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.212778807Z + restimestampmock: 2025-11-10T12:22:42.213007686Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1868 +spec: + metadata: + connID: "594" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.213224034Z + restimestampmock: 2025-11-10T12:22:42.213384783Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1869 +spec: + metadata: + connID: "594" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='bipul_guwahati' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: a22e67bf-eae3-4636-8ef7-0a9772a483d9 + unsigned: false + - type: 253 + name: username + value: bipul_guwahati + unsigned: false + - type: 253 + name: email + value: bipul_guwahati@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$sFD1Q5Mofpzd6xWQ$a210482e3e631d2ff5d30de5c403f399f2affdac186a42dc1b0848483a855063af8386e7131ca3e45d57fc0bf3549ad2a94ed2b83d34e14a85e57a4fa78f8212 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.213656131Z + restimestampmock: 2025-11-10T12:22:42.214061107Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1870 +spec: + metadata: + connID: "596" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [76, 213, 40, 223, 186, 204, 107, 37, 245, 138, 232, 149, 200, 148, 185, 198, 204, 28, 78, 166, 225, 8, 221, 20, 135, 5, 179, 166, 94, 177, 211, 223] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 468 + auth_plugin_data: [106, 51, 67, 80, 37, 73, 69, 34, 121, 9, 21, 72, 64, 62, 117, 14, 1, 95, 14, 2, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.962308199Z + restimestampmock: 2025-11-10T12:22:42.96925022Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1871 +spec: + metadata: + connID: "596" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.969569358Z + restimestampmock: 2025-11-10T12:22:42.969798206Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1872 +spec: + metadata: + connID: "596" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.969975825Z + restimestampmock: 2025-11-10T12:22:42.970169453Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1873 +spec: + metadata: + connID: "596" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.970273212Z + restimestampmock: 2025-11-10T12:22:42.970377892Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1874 +spec: + metadata: + connID: "596" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a22e67bf-eae3-4636-8ef7-0a9772a483d9' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a22e67bf-eae3-4636-8ef7-0a9772a483d9 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.970565Z + restimestampmock: 2025-11-10T12:22:42.970951516Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1875 +spec: + metadata: + connID: "596" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 259 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('aa45d8d6-302e-4967-b351-256da7a22a64','a22e67bf-eae3-4636-8ef7-0a9772a483d9','GS Road',NULL,'Guwahati','Assam','781005','IN','+919864012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.971161535Z + restimestampmock: 2025-11-10T12:22:42.971509613Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1876 +spec: + metadata: + connID: "596" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='a22e67bf-eae3-4636-8ef7-0a9772a483d9' AND id<>'aa45d8d6-302e-4967-b351-256da7a22a64' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.97166291Z + restimestampmock: 2025-11-10T12:22:42.972001618Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1877 +spec: + metadata: + connID: "596" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777362 + reqtimestampmock: 2025-11-10T12:22:42.972136507Z + restimestampmock: 2025-11-10T12:22:42.982096493Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1878 +spec: + metadata: + connID: "598" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [156, 245, 32, 201, 99, 20, 58, 75, 75, 198, 41, 159, 66, 15, 105, 229, 154, 172, 146, 94, 169, 116, 243, 31, 212, 14, 99, 197, 66, 143, 174, 161] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 469 + auth_plugin_data: [41, 112, 54, 73, 16, 2, 127, 43, 92, 55, 97, 4, 84, 41, 52, 92, 26, 79, 89, 114, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777363 + reqtimestampmock: 2025-11-10T12:22:43.785778211Z + restimestampmock: 2025-11-10T12:22:43.794351859Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1879 +spec: + metadata: + connID: "598" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777363 + reqtimestampmock: 2025-11-10T12:22:43.794576778Z + restimestampmock: 2025-11-10T12:22:43.794788116Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1880 +spec: + metadata: + connID: "598" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777363 + reqtimestampmock: 2025-11-10T12:22:43.794985685Z + restimestampmock: 2025-11-10T12:22:43.795231442Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1881 +spec: + metadata: + connID: "598" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777363 + reqtimestampmock: 2025-11-10T12:22:43.795353691Z + restimestampmock: 2025-11-10T12:22:43.795463851Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1882 +spec: + metadata: + connID: "598" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a22e67bf-eae3-4636-8ef7-0a9772a483d9' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a22e67bf-eae3-4636-8ef7-0a9772a483d9 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777363 + reqtimestampmock: 2025-11-10T12:22:43.795648979Z + restimestampmock: 2025-11-10T12:22:43.796103925Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1883 +spec: + metadata: + connID: "598" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='a22e67bf-eae3-4636-8ef7-0a9772a483d9' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777363 + reqtimestampmock: 2025-11-10T12:22:43.796293714Z + restimestampmock: 2025-11-10T12:22:43.79665669Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1884 +spec: + metadata: + connID: "598" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='a22e67bf-eae3-4636-8ef7-0a9772a483d9' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777363 + reqtimestampmock: 2025-11-10T12:22:43.796874878Z + restimestampmock: 2025-11-10T12:22:43.797277395Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1885 +spec: + metadata: + connID: "598" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777363 + reqtimestampmock: 2025-11-10T12:22:43.797405024Z + restimestampmock: 2025-11-10T12:22:43.810104908Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1886 +spec: + metadata: + connID: "600" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [136, 97, 109, 60, 41, 13, 114, 208, 214, 11, 228, 79, 161, 212, 222, 125, 3, 129, 151, 204, 224, 242, 169, 172, 194, 216, 39, 255, 130, 100, 96, 78] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 471 + auth_plugin_data: [74, 126, 103, 16, 90, 9, 80, 112, 47, 99, 98, 77, 7, 69, 73, 109, 96, 60, 20, 103, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777365 + reqtimestampmock: 2025-11-10T12:22:45.717670124Z + restimestampmock: 2025-11-10T12:22:45.727202554Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1887 +spec: + metadata: + connID: "600" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777365 + reqtimestampmock: 2025-11-10T12:22:45.727400733Z + restimestampmock: 2025-11-10T12:22:45.727612011Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1888 +spec: + metadata: + connID: "600" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777365 + reqtimestampmock: 2025-11-10T12:22:45.727847259Z + restimestampmock: 2025-11-10T12:22:45.728008038Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1889 +spec: + metadata: + connID: "600" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777365 + reqtimestampmock: 2025-11-10T12:22:45.728113907Z + restimestampmock: 2025-11-10T12:22:45.728306665Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1890 +spec: + metadata: + connID: "600" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 323 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('151f3c3f-ac17-4f3d-afe9-22f899357d9d', 'smita_bbsr', 'smita_bbsr@example.in', 'scrypt:32768:8:1$jypSV0zSFdWqovlR$8aba32f40ee9323bba0f4b0984c7f80adac657b1694acd87ed27960785796bf378825c0cb29f2121c0119d3a48fe7a1cdb1c55d8f7e0aa77c58958a41a485c41', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777365 + reqtimestampmock: 2025-11-10T12:22:45.783241426Z + restimestampmock: 2025-11-10T12:22:45.783816871Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1891 +spec: + metadata: + connID: "600" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777365 + reqtimestampmock: 2025-11-10T12:22:45.784047489Z + restimestampmock: 2025-11-10T12:22:45.799133433Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1892 +spec: + metadata: + connID: "602" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [90, 238, 181, 152, 245, 176, 215, 161, 110, 177, 101, 114, 42, 178, 61, 187, 252, 41, 51, 21, 31, 247, 235, 173, 191, 251, 215, 156, 152, 250, 125, 206] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 472 + auth_plugin_data: [116, 28, 65, 106, 37, 58, 45, 116, 54, 107, 48, 30, 10, 21, 57, 26, 7, 38, 49, 85, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777366 + reqtimestampmock: 2025-11-10T12:22:46.624362091Z + restimestampmock: 2025-11-10T12:22:46.635876864Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1893 +spec: + metadata: + connID: "602" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777366 + reqtimestampmock: 2025-11-10T12:22:46.636165692Z + restimestampmock: 2025-11-10T12:22:46.63638064Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1894 +spec: + metadata: + connID: "602" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777366 + reqtimestampmock: 2025-11-10T12:22:46.636557519Z + restimestampmock: 2025-11-10T12:22:46.636777227Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1895 +spec: + metadata: + connID: "602" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777366 + reqtimestampmock: 2025-11-10T12:22:46.636930706Z + restimestampmock: 2025-11-10T12:22:46.637030795Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1896 +spec: + metadata: + connID: "602" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 83 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='smita_bbsr' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 233 + sequence_id: 6 + values: + - type: 253 + name: id + value: 151f3c3f-ac17-4f3d-afe9-22f899357d9d + unsigned: false + - type: 253 + name: username + value: smita_bbsr + unsigned: false + - type: 253 + name: email + value: smita_bbsr@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$jypSV0zSFdWqovlR$8aba32f40ee9323bba0f4b0984c7f80adac657b1694acd87ed27960785796bf378825c0cb29f2121c0119d3a48fe7a1cdb1c55d8f7e0aa77c58958a41a485c41 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777366 + reqtimestampmock: 2025-11-10T12:22:46.637314602Z + restimestampmock: 2025-11-10T12:22:46.637914468Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1897 +spec: + metadata: + connID: "604" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [78, 175, 176, 200, 210, 150, 215, 223, 69, 153, 140, 109, 131, 9, 224, 180, 140, 221, 57, 96, 110, 42, 169, 125, 51, 9, 22, 35, 24, 20, 129, 0] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 473 + auth_plugin_data: [75, 95, 7, 42, 71, 118, 68, 86, 90, 42, 84, 62, 8, 110, 64, 25, 46, 27, 95, 70, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777367 + reqtimestampmock: 2025-11-10T12:22:47.453903772Z + restimestampmock: 2025-11-10T12:22:47.462111784Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1898 +spec: + metadata: + connID: "604" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777367 + reqtimestampmock: 2025-11-10T12:22:47.462362723Z + restimestampmock: 2025-11-10T12:22:47.462604831Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1899 +spec: + metadata: + connID: "604" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777367 + reqtimestampmock: 2025-11-10T12:22:47.462813698Z + restimestampmock: 2025-11-10T12:22:47.462978728Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1900 +spec: + metadata: + connID: "604" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777367 + reqtimestampmock: 2025-11-10T12:22:47.463112466Z + restimestampmock: 2025-11-10T12:22:47.463233836Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1901 +spec: + metadata: + connID: "604" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='151f3c3f-ac17-4f3d-afe9-22f899357d9d' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 151f3c3f-ac17-4f3d-afe9-22f899357d9d + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777367 + reqtimestampmock: 2025-11-10T12:22:47.463497804Z + restimestampmock: 2025-11-10T12:22:47.464025498Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1902 +spec: + metadata: + connID: "604" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 268 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('3c7d34c2-64ed-4579-9bb4-72a576ab2e47','151f3c3f-ac17-4f3d-afe9-22f899357d9d','Saheed Nagar',NULL,'Bhubaneswar','Odisha','751007','IN','+919437012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777367 + reqtimestampmock: 2025-11-10T12:22:47.464287516Z + restimestampmock: 2025-11-10T12:22:47.464635374Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1903 +spec: + metadata: + connID: "604" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='151f3c3f-ac17-4f3d-afe9-22f899357d9d' AND id<>'3c7d34c2-64ed-4579-9bb4-72a576ab2e47' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777367 + reqtimestampmock: 2025-11-10T12:22:47.464784452Z + restimestampmock: 2025-11-10T12:22:47.465107639Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1904 +spec: + metadata: + connID: "604" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777367 + reqtimestampmock: 2025-11-10T12:22:47.465275099Z + restimestampmock: 2025-11-10T12:22:47.4770365Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1905 +spec: + metadata: + connID: "606" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [246, 46, 12, 186, 166, 225, 33, 79, 86, 31, 174, 29, 6, 155, 133, 179, 49, 90, 58, 46, 28, 156, 151, 145, 44, 225, 73, 138, 89, 194, 91, 162] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 474 + auth_plugin_data: [100, 25, 60, 61, 51, 54, 25, 21, 112, 83, 127, 60, 126, 126, 124, 115, 31, 9, 109, 126, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777368 + reqtimestampmock: 2025-11-10T12:22:48.202609813Z + restimestampmock: 2025-11-10T12:22:48.209943451Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1906 +spec: + metadata: + connID: "606" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777368 + reqtimestampmock: 2025-11-10T12:22:48.210235358Z + restimestampmock: 2025-11-10T12:22:48.210438486Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1907 +spec: + metadata: + connID: "606" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777368 + reqtimestampmock: 2025-11-10T12:22:48.210621345Z + restimestampmock: 2025-11-10T12:22:48.210799544Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1908 +spec: + metadata: + connID: "606" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777368 + reqtimestampmock: 2025-11-10T12:22:48.211005761Z + restimestampmock: 2025-11-10T12:22:48.211182301Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1909 +spec: + metadata: + connID: "606" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='151f3c3f-ac17-4f3d-afe9-22f899357d9d' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 151f3c3f-ac17-4f3d-afe9-22f899357d9d + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777368 + reqtimestampmock: 2025-11-10T12:22:48.211749395Z + restimestampmock: 2025-11-10T12:22:48.212162982Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1910 +spec: + metadata: + connID: "606" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='151f3c3f-ac17-4f3d-afe9-22f899357d9d' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777368 + reqtimestampmock: 2025-11-10T12:22:48.21238039Z + restimestampmock: 2025-11-10T12:22:48.212710728Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1911 +spec: + metadata: + connID: "606" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='151f3c3f-ac17-4f3d-afe9-22f899357d9d' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777368 + reqtimestampmock: 2025-11-10T12:22:48.212882865Z + restimestampmock: 2025-11-10T12:22:48.213190233Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1912 +spec: + metadata: + connID: "606" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777368 + reqtimestampmock: 2025-11-10T12:22:48.213298742Z + restimestampmock: 2025-11-10T12:22:48.229209549Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1913 +spec: + metadata: + connID: "608" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [22, 236, 231, 96, 160, 26, 220, 228, 143, 45, 82, 184, 112, 132, 46, 126, 185, 149, 26, 161, 35, 174, 49, 109, 90, 113, 18, 236, 117, 176, 17, 183] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 476 + auth_plugin_data: [75, 77, 4, 99, 30, 71, 38, 113, 60, 91, 24, 116, 43, 12, 72, 118, 81, 98, 64, 65, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777370 + reqtimestampmock: 2025-11-10T12:22:50.233371069Z + restimestampmock: 2025-11-10T12:22:50.240266702Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1914 +spec: + metadata: + connID: "608" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777370 + reqtimestampmock: 2025-11-10T12:22:50.240491829Z + restimestampmock: 2025-11-10T12:22:50.240706787Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1915 +spec: + metadata: + connID: "608" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777370 + reqtimestampmock: 2025-11-10T12:22:50.240866836Z + restimestampmock: 2025-11-10T12:22:50.241040685Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1916 +spec: + metadata: + connID: "608" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777370 + reqtimestampmock: 2025-11-10T12:22:50.241194334Z + restimestampmock: 2025-11-10T12:22:50.241307683Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1917 +spec: + metadata: + connID: "608" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('7c9c0bb4-569d-483f-9ed8-86cb9add8591', 'deepak_ranchi', 'deepak_ranchi@example.in', 'scrypt:32768:8:1$TDCVtMQphGja6YY4$ed6155048bae53771500c836d1472724521de363eda2053056d0c12c05fe73068d54c1506a42a71dbe98228340a3714b487517ec947345298e52f29890d1358b', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777370 + reqtimestampmock: 2025-11-10T12:22:50.298736182Z + restimestampmock: 2025-11-10T12:22:50.299458766Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1918 +spec: + metadata: + connID: "608" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777370 + reqtimestampmock: 2025-11-10T12:22:50.299634534Z + restimestampmock: 2025-11-10T12:22:50.310173457Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1919 +spec: + metadata: + connID: "610" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [140, 107, 165, 145, 214, 254, 44, 184, 130, 162, 194, 139, 103, 132, 52, 4, 145, 4, 210, 26, 238, 247, 183, 199, 224, 81, 248, 20, 86, 217, 172, 33] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 477 + auth_plugin_data: [6, 80, 14, 33, 102, 43, 74, 23, 80, 44, 40, 4, 51, 19, 118, 64, 92, 23, 65, 85, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.131727837Z + restimestampmock: 2025-11-10T12:22:51.145471802Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1920 +spec: + metadata: + connID: "610" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.145761629Z + restimestampmock: 2025-11-10T12:22:51.146000857Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1921 +spec: + metadata: + connID: "610" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.146161276Z + restimestampmock: 2025-11-10T12:22:51.146358445Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1922 +spec: + metadata: + connID: "610" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.146494003Z + restimestampmock: 2025-11-10T12:22:51.146697651Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1923 +spec: + metadata: + connID: "610" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='deepak_ranchi' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: 7c9c0bb4-569d-483f-9ed8-86cb9add8591 + unsigned: false + - type: 253 + name: username + value: deepak_ranchi + unsigned: false + - type: 253 + name: email + value: deepak_ranchi@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$TDCVtMQphGja6YY4$ed6155048bae53771500c836d1472724521de363eda2053056d0c12c05fe73068d54c1506a42a71dbe98228340a3714b487517ec947345298e52f29890d1358b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.146951409Z + restimestampmock: 2025-11-10T12:22:51.147446405Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1924 +spec: + metadata: + connID: "612" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [113, 197, 25, 207, 49, 154, 173, 240, 238, 120, 62, 214, 106, 52, 131, 195, 160, 172, 192, 159, 63, 4, 137, 177, 51, 211, 197, 32, 94, 134, 231, 85] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 478 + auth_plugin_data: [78, 6, 2, 43, 102, 108, 21, 28, 114, 6, 7, 92, 119, 19, 39, 124, 86, 53, 56, 49, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.888295041Z + restimestampmock: 2025-11-10T12:22:51.895344691Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1925 +spec: + metadata: + connID: "612" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.895766138Z + restimestampmock: 2025-11-10T12:22:51.895831357Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1926 +spec: + metadata: + connID: "612" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.895971656Z + restimestampmock: 2025-11-10T12:22:51.896141155Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1927 +spec: + metadata: + connID: "612" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.896270634Z + restimestampmock: 2025-11-10T12:22:51.896384542Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1928 +spec: + metadata: + connID: "612" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='7c9c0bb4-569d-483f-9ed8-86cb9add8591' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 7c9c0bb4-569d-483f-9ed8-86cb9add8591 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.896603121Z + restimestampmock: 2025-11-10T12:22:51.897061507Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1929 +spec: + metadata: + connID: "612" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 264 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('6733d6ab-8f6c-44c2-93fc-3f7cb8211ad7','7c9c0bb4-569d-483f-9ed8-86cb9add8591','Kanke Road',NULL,'Ranchi','Jharkhand','834008','IN','+919431112345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.897284406Z + restimestampmock: 2025-11-10T12:22:51.897649942Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1930 +spec: + metadata: + connID: "612" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='7c9c0bb4-569d-483f-9ed8-86cb9add8591' AND id<>'6733d6ab-8f6c-44c2-93fc-3f7cb8211ad7' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.897796971Z + restimestampmock: 2025-11-10T12:22:51.898078589Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1931 +spec: + metadata: + connID: "612" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777371 + reqtimestampmock: 2025-11-10T12:22:51.898213548Z + restimestampmock: 2025-11-10T12:22:51.907998746Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1932 +spec: + metadata: + connID: "614" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [69, 74, 68, 74, 183, 176, 174, 234, 190, 221, 66, 232, 244, 42, 137, 150, 117, 62, 208, 199, 125, 160, 101, 19, 194, 189, 58, 186, 53, 61, 216, 112] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 479 + auth_plugin_data: [19, 33, 18, 96, 33, 119, 26, 1, 49, 109, 74, 46, 91, 27, 52, 1, 66, 11, 84, 120, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777372 + reqtimestampmock: 2025-11-10T12:22:52.691991641Z + restimestampmock: 2025-11-10T12:22:52.70040203Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1933 +spec: + metadata: + connID: "614" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777372 + reqtimestampmock: 2025-11-10T12:22:52.700698238Z + restimestampmock: 2025-11-10T12:22:52.700999624Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1934 +spec: + metadata: + connID: "614" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777372 + reqtimestampmock: 2025-11-10T12:22:52.701166284Z + restimestampmock: 2025-11-10T12:22:52.701375403Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1935 +spec: + metadata: + connID: "614" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777372 + reqtimestampmock: 2025-11-10T12:22:52.701523361Z + restimestampmock: 2025-11-10T12:22:52.701741819Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1936 +spec: + metadata: + connID: "614" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='7c9c0bb4-569d-483f-9ed8-86cb9add8591' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 7c9c0bb4-569d-483f-9ed8-86cb9add8591 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777372 + reqtimestampmock: 2025-11-10T12:22:52.702067346Z + restimestampmock: 2025-11-10T12:22:52.702529441Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1937 +spec: + metadata: + connID: "614" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='7c9c0bb4-569d-483f-9ed8-86cb9add8591' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777372 + reqtimestampmock: 2025-11-10T12:22:52.70279127Z + restimestampmock: 2025-11-10T12:22:52.703281765Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1938 +spec: + metadata: + connID: "614" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='7c9c0bb4-569d-483f-9ed8-86cb9add8591' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777372 + reqtimestampmock: 2025-11-10T12:22:52.703575653Z + restimestampmock: 2025-11-10T12:22:52.70401681Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1939 +spec: + metadata: + connID: "614" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777372 + reqtimestampmock: 2025-11-10T12:22:52.704217988Z + restimestampmock: 2025-11-10T12:22:52.724004062Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1940 +spec: + metadata: + connID: "616" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [193, 162, 123, 98, 235, 123, 207, 234, 155, 109, 230, 9, 47, 199, 30, 62, 166, 134, 133, 66, 170, 155, 200, 158, 10, 80, 245, 236, 91, 116, 59, 58] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 480 + auth_plugin_data: [29, 93, 64, 63, 33, 100, 78, 6, 39, 94, 56, 80, 124, 17, 62, 107, 54, 29, 18, 114, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777374 + reqtimestampmock: 2025-11-10T12:22:54.611219402Z + restimestampmock: 2025-11-10T12:22:54.620372116Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1941 +spec: + metadata: + connID: "616" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777374 + reqtimestampmock: 2025-11-10T12:22:54.620664994Z + restimestampmock: 2025-11-10T12:22:54.620919701Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1942 +spec: + metadata: + connID: "616" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777374 + reqtimestampmock: 2025-11-10T12:22:54.621133899Z + restimestampmock: 2025-11-10T12:22:54.621325538Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1943 +spec: + metadata: + connID: "616" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777374 + reqtimestampmock: 2025-11-10T12:22:54.621479906Z + restimestampmock: 2025-11-10T12:22:54.621675554Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1944 +spec: + metadata: + connID: "616" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 323 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('a95e1d27-6e25-4d2e-afdf-c6756e7a6b26', 'gaurav_ddn', 'gaurav_ddn@example.in', 'scrypt:32768:8:1$1rpN04H7vWYdPvJ8$ba776fb232cce9d9cb609dc71d664ab27f8ffaa413945402cac8097089273b54105931aa8f80f1b9bb5572c1dd5aed5a046919165e5ef3550532a3017b328fd5', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777374 + reqtimestampmock: 2025-11-10T12:22:54.68086618Z + restimestampmock: 2025-11-10T12:22:54.681578984Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1945 +spec: + metadata: + connID: "616" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777374 + reqtimestampmock: 2025-11-10T12:22:54.681832851Z + restimestampmock: 2025-11-10T12:22:54.694086539Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1946 +spec: + metadata: + connID: "618" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [240, 44, 222, 101, 7, 237, 120, 108, 183, 172, 160, 251, 5, 214, 183, 181, 200, 223, 2, 138, 115, 227, 58, 107, 93, 77, 191, 111, 176, 12, 234, 147] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 482 + auth_plugin_data: [52, 86, 6, 15, 45, 105, 54, 46, 7, 66, 88, 108, 14, 61, 11, 127, 110, 125, 5, 70, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777375 + reqtimestampmock: 2025-11-10T12:22:55.444183707Z + restimestampmock: 2025-11-10T12:22:55.451978603Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1947 +spec: + metadata: + connID: "618" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777375 + reqtimestampmock: 2025-11-10T12:22:55.45226901Z + restimestampmock: 2025-11-10T12:22:55.452482178Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1948 +spec: + metadata: + connID: "618" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777375 + reqtimestampmock: 2025-11-10T12:22:55.452634558Z + restimestampmock: 2025-11-10T12:22:55.452798385Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1949 +spec: + metadata: + connID: "618" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777375 + reqtimestampmock: 2025-11-10T12:22:55.452950884Z + restimestampmock: 2025-11-10T12:22:55.453057283Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1950 +spec: + metadata: + connID: "618" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 83 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='gaurav_ddn' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 233 + sequence_id: 6 + values: + - type: 253 + name: id + value: a95e1d27-6e25-4d2e-afdf-c6756e7a6b26 + unsigned: false + - type: 253 + name: username + value: gaurav_ddn + unsigned: false + - type: 253 + name: email + value: gaurav_ddn@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$1rpN04H7vWYdPvJ8$ba776fb232cce9d9cb609dc71d664ab27f8ffaa413945402cac8097089273b54105931aa8f80f1b9bb5572c1dd5aed5a046919165e5ef3550532a3017b328fd5 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777375 + reqtimestampmock: 2025-11-10T12:22:55.453282442Z + restimestampmock: 2025-11-10T12:22:55.453746898Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1951 +spec: + metadata: + connID: "620" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [52, 83, 7, 157, 242, 52, 221, 34, 53, 233, 116, 36, 78, 27, 186, 49, 52, 81, 58, 246, 83, 160, 238, 10, 179, 123, 69, 238, 43, 196, 179, 178] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 483 + auth_plugin_data: [10, 24, 31, 18, 115, 63, 80, 1, 84, 40, 108, 12, 1, 92, 7, 32, 58, 9, 97, 46, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777376 + reqtimestampmock: 2025-11-10T12:22:56.356073523Z + restimestampmock: 2025-11-10T12:22:56.363573141Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1952 +spec: + metadata: + connID: "620" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777376 + reqtimestampmock: 2025-11-10T12:22:56.363780059Z + restimestampmock: 2025-11-10T12:22:56.363971058Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1953 +spec: + metadata: + connID: "620" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777376 + reqtimestampmock: 2025-11-10T12:22:56.364092326Z + restimestampmock: 2025-11-10T12:22:56.364256015Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1954 +spec: + metadata: + connID: "620" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777376 + reqtimestampmock: 2025-11-10T12:22:56.364358734Z + restimestampmock: 2025-11-10T12:22:56.364454023Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1955 +spec: + metadata: + connID: "620" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a95e1d27-6e25-4d2e-afdf-c6756e7a6b26' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a95e1d27-6e25-4d2e-afdf-c6756e7a6b26 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777376 + reqtimestampmock: 2025-11-10T12:22:56.364614072Z + restimestampmock: 2025-11-10T12:22:56.365181437Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1956 +spec: + metadata: + connID: "620" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 269 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('8bd5d485-3e66-43e1-9d78-35736f37b0c4','a95e1d27-6e25-4d2e-afdf-c6756e7a6b26','Rajpur Road',NULL,'Dehradun','Uttarakhand','248001','IN','+919412012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777376 + reqtimestampmock: 2025-11-10T12:22:56.365525115Z + restimestampmock: 2025-11-10T12:22:56.36603281Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1957 +spec: + metadata: + connID: "620" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='a95e1d27-6e25-4d2e-afdf-c6756e7a6b26' AND id<>'8bd5d485-3e66-43e1-9d78-35736f37b0c4' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777376 + reqtimestampmock: 2025-11-10T12:22:56.366197528Z + restimestampmock: 2025-11-10T12:22:56.366621945Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1958 +spec: + metadata: + connID: "620" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777376 + reqtimestampmock: 2025-11-10T12:22:56.366765334Z + restimestampmock: 2025-11-10T12:22:56.37784431Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1959 +spec: + metadata: + connID: "622" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [64, 204, 2, 61, 2, 43, 11, 144, 120, 72, 127, 159, 215, 167, 94, 61, 188, 85, 85, 75, 160, 230, 1, 77, 140, 13, 180, 76, 67, 196, 251, 116] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 484 + auth_plugin_data: [10, 3, 7, 41, 102, 39, 20, 102, 92, 43, 5, 20, 1, 28, 20, 118, 113, 68, 48, 5, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777377 + reqtimestampmock: 2025-11-10T12:22:57.116705263Z + restimestampmock: 2025-11-10T12:22:57.129257278Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1960 +spec: + metadata: + connID: "622" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777377 + reqtimestampmock: 2025-11-10T12:22:57.129541616Z + restimestampmock: 2025-11-10T12:22:57.129752384Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1961 +spec: + metadata: + connID: "622" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777377 + reqtimestampmock: 2025-11-10T12:22:57.129910022Z + restimestampmock: 2025-11-10T12:22:57.13021014Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1962 +spec: + metadata: + connID: "622" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777377 + reqtimestampmock: 2025-11-10T12:22:57.130420068Z + restimestampmock: 2025-11-10T12:22:57.130515347Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1963 +spec: + metadata: + connID: "622" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a95e1d27-6e25-4d2e-afdf-c6756e7a6b26' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a95e1d27-6e25-4d2e-afdf-c6756e7a6b26 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777377 + reqtimestampmock: 2025-11-10T12:22:57.130760185Z + restimestampmock: 2025-11-10T12:22:57.131207372Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1964 +spec: + metadata: + connID: "622" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='a95e1d27-6e25-4d2e-afdf-c6756e7a6b26' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777377 + reqtimestampmock: 2025-11-10T12:22:57.13141568Z + restimestampmock: 2025-11-10T12:22:57.131850207Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1965 +spec: + metadata: + connID: "622" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='a95e1d27-6e25-4d2e-afdf-c6756e7a6b26' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777377 + reqtimestampmock: 2025-11-10T12:22:57.131999785Z + restimestampmock: 2025-11-10T12:22:57.132356362Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1966 +spec: + metadata: + connID: "622" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777377 + reqtimestampmock: 2025-11-10T12:22:57.132444172Z + restimestampmock: 2025-11-10T12:22:57.145209165Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1967 +spec: + metadata: + connID: "624" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [91, 192, 77, 102, 33, 81, 130, 42, 89, 158, 215, 97, 106, 176, 161, 113, 60, 127, 41, 65, 182, 202, 69, 90, 161, 213, 21, 237, 67, 142, 72, 120] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 485 + auth_plugin_data: [92, 8, 93, 100, 10, 70, 126, 39, 1, 47, 112, 16, 66, 82, 64, 70, 28, 9, 39, 119, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.032486427Z + restimestampmock: 2025-11-10T12:22:59.039634367Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1968 +spec: + metadata: + connID: "624" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.039869984Z + restimestampmock: 2025-11-10T12:22:59.040091143Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1969 +spec: + metadata: + connID: "624" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.040271071Z + restimestampmock: 2025-11-10T12:22:59.04042716Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1970 +spec: + metadata: + connID: "624" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.040550489Z + restimestampmock: 2025-11-10T12:22:59.040670638Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1971 +spec: + metadata: + connID: "624" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 331 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('0b152bc2-443c-424f-a1eb-5e872f244af0', 'chaitra_mysuru', 'chaitra_mysuru@example.in', 'scrypt:32768:8:1$srU8EmOVOi44ohtZ$c639032438f779feac2eb79b6a32e1dd85de04245d7b219d0589da3c47c46eac5706fbe0f57d7e4b06c599663f0f2de056bf2129f302ba3b6048263b860836ad', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.098119387Z + restimestampmock: 2025-11-10T12:22:59.098715713Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1972 +spec: + metadata: + connID: "624" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.098908751Z + restimestampmock: 2025-11-10T12:22:59.108950567Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1973 +spec: + metadata: + connID: "626" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [106, 175, 7, 247, 113, 144, 179, 146, 172, 243, 61, 125, 123, 74, 24, 9, 119, 249, 148, 97, 85, 233, 52, 253, 121, 255, 214, 220, 238, 150, 200, 138] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 486 + auth_plugin_data: [37, 97, 26, 34, 64, 59, 40, 18, 54, 58, 77, 9, 108, 73, 119, 98, 91, 64, 49, 46, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.808011533Z + restimestampmock: 2025-11-10T12:22:59.814504009Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1974 +spec: + metadata: + connID: "626" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.814704176Z + restimestampmock: 2025-11-10T12:22:59.814902185Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1975 +spec: + metadata: + connID: "626" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.815031435Z + restimestampmock: 2025-11-10T12:22:59.815231812Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1976 +spec: + metadata: + connID: "626" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.815367762Z + restimestampmock: 2025-11-10T12:22:59.81550694Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1977 +spec: + metadata: + connID: "626" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 87 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='chaitra_mysuru' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 241 + sequence_id: 6 + values: + - type: 253 + name: id + value: 0b152bc2-443c-424f-a1eb-5e872f244af0 + unsigned: false + - type: 253 + name: username + value: chaitra_mysuru + unsigned: false + - type: 253 + name: email + value: chaitra_mysuru@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$srU8EmOVOi44ohtZ$c639032438f779feac2eb79b6a32e1dd85de04245d7b219d0589da3c47c46eac5706fbe0f57d7e4b06c599663f0f2de056bf2129f302ba3b6048263b860836ad + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777379 + reqtimestampmock: 2025-11-10T12:22:59.81567888Z + restimestampmock: 2025-11-10T12:22:59.816193255Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1978 +spec: + metadata: + connID: "628" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [179, 79, 144, 25, 221, 78, 185, 113, 51, 65, 20, 229, 184, 68, 192, 217, 232, 171, 3, 163, 34, 213, 102, 47, 36, 157, 203, 180, 90, 108, 159, 171] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 488 + auth_plugin_data: [97, 76, 106, 6, 114, 93, 15, 124, 111, 47, 85, 9, 51, 57, 112, 120, 75, 122, 1, 117, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777380 + reqtimestampmock: 2025-11-10T12:23:00.569883574Z + restimestampmock: 2025-11-10T12:23:00.577577799Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1979 +spec: + metadata: + connID: "628" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777380 + reqtimestampmock: 2025-11-10T12:23:00.577900236Z + restimestampmock: 2025-11-10T12:23:00.578090525Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1980 +spec: + metadata: + connID: "628" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777380 + reqtimestampmock: 2025-11-10T12:23:00.578261054Z + restimestampmock: 2025-11-10T12:23:00.578419042Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1981 +spec: + metadata: + connID: "628" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777380 + reqtimestampmock: 2025-11-10T12:23:00.578545111Z + restimestampmock: 2025-11-10T12:23:00.578851569Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1982 +spec: + metadata: + connID: "628" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='0b152bc2-443c-424f-a1eb-5e872f244af0' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 0b152bc2-443c-424f-a1eb-5e872f244af0 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777380 + reqtimestampmock: 2025-11-10T12:23:00.579100197Z + restimestampmock: 2025-11-10T12:23:00.579487434Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1983 +spec: + metadata: + connID: "628" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 266 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('f8737d26-8010-4838-9d0d-faee0c60c74f','0b152bc2-443c-424f-a1eb-5e872f244af0','Kuvempunagar',NULL,'Mysuru','Karnataka','570023','IN','+919845012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777380 + reqtimestampmock: 2025-11-10T12:23:00.57995088Z + restimestampmock: 2025-11-10T12:23:00.580307616Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1984 +spec: + metadata: + connID: "628" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='0b152bc2-443c-424f-a1eb-5e872f244af0' AND id<>'f8737d26-8010-4838-9d0d-faee0c60c74f' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777380 + reqtimestampmock: 2025-11-10T12:23:00.580555694Z + restimestampmock: 2025-11-10T12:23:00.580876883Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1985 +spec: + metadata: + connID: "628" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777380 + reqtimestampmock: 2025-11-10T12:23:00.580979021Z + restimestampmock: 2025-11-10T12:23:00.5929524Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1986 +spec: + metadata: + connID: "630" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [192, 85, 215, 188, 169, 49, 99, 253, 197, 130, 229, 181, 223, 73, 53, 121, 23, 238, 30, 254, 3, 59, 32, 104, 110, 32, 245, 92, 127, 250, 225, 199] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 489 + auth_plugin_data: [100, 26, 29, 79, 112, 127, 1, 48, 61, 56, 69, 4, 33, 51, 54, 80, 51, 45, 56, 70, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777381 + reqtimestampmock: 2025-11-10T12:23:01.307946314Z + restimestampmock: 2025-11-10T12:23:01.316478933Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1987 +spec: + metadata: + connID: "630" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777381 + reqtimestampmock: 2025-11-10T12:23:01.316671611Z + restimestampmock: 2025-11-10T12:23:01.316902938Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1988 +spec: + metadata: + connID: "630" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777381 + reqtimestampmock: 2025-11-10T12:23:01.317021708Z + restimestampmock: 2025-11-10T12:23:01.317206576Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1989 +spec: + metadata: + connID: "630" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777381 + reqtimestampmock: 2025-11-10T12:23:01.317367476Z + restimestampmock: 2025-11-10T12:23:01.317471654Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1990 +spec: + metadata: + connID: "630" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='0b152bc2-443c-424f-a1eb-5e872f244af0' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 0b152bc2-443c-424f-a1eb-5e872f244af0 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777381 + reqtimestampmock: 2025-11-10T12:23:01.317691153Z + restimestampmock: 2025-11-10T12:23:01.31804608Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1991 +spec: + metadata: + connID: "630" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='0b152bc2-443c-424f-a1eb-5e872f244af0' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777381 + reqtimestampmock: 2025-11-10T12:23:01.318309847Z + restimestampmock: 2025-11-10T12:23:01.318690884Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1992 +spec: + metadata: + connID: "630" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='0b152bc2-443c-424f-a1eb-5e872f244af0' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777381 + reqtimestampmock: 2025-11-10T12:23:01.318901993Z + restimestampmock: 2025-11-10T12:23:01.31921354Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1993 +spec: + metadata: + connID: "630" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777381 + reqtimestampmock: 2025-11-10T12:23:01.319337219Z + restimestampmock: 2025-11-10T12:23:01.33596933Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1994 +spec: + metadata: + connID: "632" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [61, 87, 255, 96, 161, 5, 30, 252, 51, 95, 203, 45, 52, 203, 219, 225, 114, 79, 236, 53, 248, 3, 163, 160, 159, 175, 18, 40, 168, 64, 65, 129] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 490 + auth_plugin_data: [58, 49, 25, 42, 29, 8, 83, 21, 100, 127, 9, 21, 65, 106, 69, 86, 95, 118, 12, 18, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777383 + reqtimestampmock: 2025-11-10T12:23:03.30253682Z + restimestampmock: 2025-11-10T12:23:03.3158026Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1995 +spec: + metadata: + connID: "632" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777383 + reqtimestampmock: 2025-11-10T12:23:03.316041858Z + restimestampmock: 2025-11-10T12:23:03.316255966Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1996 +spec: + metadata: + connID: "632" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777383 + reqtimestampmock: 2025-11-10T12:23:03.316444194Z + restimestampmock: 2025-11-10T12:23:03.316620242Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1997 +spec: + metadata: + connID: "632" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777383 + reqtimestampmock: 2025-11-10T12:23:03.316761721Z + restimestampmock: 2025-11-10T12:23:03.316895011Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1998 +spec: + metadata: + connID: "632" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 333 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('86dd9463-20ce-4da2-b106-2cf1077bda0b', 'farooq_srinagar', 'farooq_srinagar@example.in', 'scrypt:32768:8:1$RbsGuMuCVv85xDXS$e6f4a714cceec789c9eb2af999d5be615429d53391a6e93c52d7529367ce62f09996732ff857cb95c25701da5692a8d032cf5778faff4af4820b978f50a67e5f', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777383 + reqtimestampmock: 2025-11-10T12:23:03.370229525Z + restimestampmock: 2025-11-10T12:23:03.37078974Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-1999 +spec: + metadata: + connID: "632" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777383 + reqtimestampmock: 2025-11-10T12:23:03.370959628Z + restimestampmock: 2025-11-10T12:23:03.38399949Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2000 +spec: + metadata: + connID: "634" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [137, 17, 37, 197, 176, 163, 65, 75, 35, 153, 18, 206, 116, 90, 64, 163, 58, 96, 200, 136, 110, 250, 46, 154, 2, 165, 111, 20, 209, 58, 121, 14] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 491 + auth_plugin_data: [68, 87, 67, 11, 66, 55, 92, 85, 86, 10, 7, 83, 94, 54, 103, 31, 82, 11, 42, 43, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777384 + reqtimestampmock: 2025-11-10T12:23:04.263342039Z + restimestampmock: 2025-11-10T12:23:04.271827288Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2001 +spec: + metadata: + connID: "634" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777384 + reqtimestampmock: 2025-11-10T12:23:04.272074936Z + restimestampmock: 2025-11-10T12:23:04.272296615Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2002 +spec: + metadata: + connID: "634" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777384 + reqtimestampmock: 2025-11-10T12:23:04.272502903Z + restimestampmock: 2025-11-10T12:23:04.272683141Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2003 +spec: + metadata: + connID: "634" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777384 + reqtimestampmock: 2025-11-10T12:23:04.27281878Z + restimestampmock: 2025-11-10T12:23:04.272938438Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2004 +spec: + metadata: + connID: "634" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 88 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='farooq_srinagar' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 243 + sequence_id: 6 + values: + - type: 253 + name: id + value: 86dd9463-20ce-4da2-b106-2cf1077bda0b + unsigned: false + - type: 253 + name: username + value: farooq_srinagar + unsigned: false + - type: 253 + name: email + value: farooq_srinagar@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$RbsGuMuCVv85xDXS$e6f4a714cceec789c9eb2af999d5be615429d53391a6e93c52d7529367ce62f09996732ff857cb95c25701da5692a8d032cf5778faff4af4820b978f50a67e5f + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777384 + reqtimestampmock: 2025-11-10T12:23:04.273281986Z + restimestampmock: 2025-11-10T12:23:04.273839841Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2005 +spec: + metadata: + connID: "636" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [231, 49, 131, 75, 212, 25, 178, 104, 160, 209, 114, 180, 75, 91, 148, 155, 242, 179, 49, 156, 39, 109, 197, 255, 84, 201, 161, 97, 20, 42, 53, 129] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 492 + auth_plugin_data: [93, 29, 89, 10, 124, 60, 69, 32, 84, 20, 35, 2, 69, 98, 8, 41, 11, 127, 99, 29, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.016335974Z + restimestampmock: 2025-11-10T12:23:05.0252073Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2006 +spec: + metadata: + connID: "636" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.025422239Z + restimestampmock: 2025-11-10T12:23:05.025608437Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2007 +spec: + metadata: + connID: "636" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.025747886Z + restimestampmock: 2025-11-10T12:23:05.025897575Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2008 +spec: + metadata: + connID: "636" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.026015194Z + restimestampmock: 2025-11-10T12:23:05.026105283Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2009 +spec: + metadata: + connID: "636" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='86dd9463-20ce-4da2-b106-2cf1077bda0b' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 86dd9463-20ce-4da2-b106-2cf1077bda0b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.026296572Z + restimestampmock: 2025-11-10T12:23:05.026679619Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2010 +spec: + metadata: + connID: "636" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 273 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('8ca3d49c-0888-4b28-9987-d25e74f145e6','86dd9463-20ce-4da2-b106-2cf1077bda0b','Lal Chowk',NULL,'Srinagar','Jammu and Kashmir','190001','IN','+919419012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.026922667Z + restimestampmock: 2025-11-10T12:23:05.027236883Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2011 +spec: + metadata: + connID: "636" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='86dd9463-20ce-4da2-b106-2cf1077bda0b' AND id<>'8ca3d49c-0888-4b28-9987-d25e74f145e6' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.027476301Z + restimestampmock: 2025-11-10T12:23:05.027767059Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2012 +spec: + metadata: + connID: "636" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.027888258Z + restimestampmock: 2025-11-10T12:23:05.04077716Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2013 +spec: + metadata: + connID: "638" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [2, 217, 78, 127, 103, 116, 155, 171, 16, 87, 214, 145, 184, 153, 164, 131, 244, 18, 237, 253, 0, 226, 93, 183, 86, 4, 244, 189, 0, 21, 63, 106] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 494 + auth_plugin_data: [42, 72, 55, 74, 93, 27, 76, 9, 27, 72, 24, 115, 37, 5, 46, 31, 81, 52, 27, 113, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.816543896Z + restimestampmock: 2025-11-10T12:23:05.823439909Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2014 +spec: + metadata: + connID: "638" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.823660737Z + restimestampmock: 2025-11-10T12:23:05.823895395Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2015 +spec: + metadata: + connID: "638" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.824094653Z + restimestampmock: 2025-11-10T12:23:05.824268082Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2016 +spec: + metadata: + connID: "638" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.824450291Z + restimestampmock: 2025-11-10T12:23:05.824566429Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2017 +spec: + metadata: + connID: "638" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='86dd9463-20ce-4da2-b106-2cf1077bda0b' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 86dd9463-20ce-4da2-b106-2cf1077bda0b + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.824803957Z + restimestampmock: 2025-11-10T12:23:05.825227793Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2018 +spec: + metadata: + connID: "638" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='86dd9463-20ce-4da2-b106-2cf1077bda0b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.825382002Z + restimestampmock: 2025-11-10T12:23:05.825726379Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2019 +spec: + metadata: + connID: "638" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='86dd9463-20ce-4da2-b106-2cf1077bda0b' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.825939198Z + restimestampmock: 2025-11-10T12:23:05.826211836Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2020 +spec: + metadata: + connID: "638" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777385 + reqtimestampmock: 2025-11-10T12:23:05.826289775Z + restimestampmock: 2025-11-10T12:23:05.839069668Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2021 +spec: + metadata: + connID: "640" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [27, 175, 34, 120, 193, 123, 208, 42, 175, 69, 199, 25, 242, 195, 246, 166, 27, 233, 189, 184, 102, 99, 9, 48, 108, 97, 234, 83, 243, 237, 18, 221] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 495 + auth_plugin_data: [69, 48, 54, 90, 69, 8, 58, 1, 75, 101, 41, 19, 124, 88, 97, 14, 9, 67, 51, 63, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777387 + reqtimestampmock: 2025-11-10T12:23:07.774633631Z + restimestampmock: 2025-11-10T12:23:07.782263016Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2022 +spec: + metadata: + connID: "640" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777387 + reqtimestampmock: 2025-11-10T12:23:07.782473005Z + restimestampmock: 2025-11-10T12:23:07.782715852Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2023 +spec: + metadata: + connID: "640" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777387 + reqtimestampmock: 2025-11-10T12:23:07.782830802Z + restimestampmock: 2025-11-10T12:23:07.78299163Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2024 +spec: + metadata: + connID: "640" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777387 + reqtimestampmock: 2025-11-10T12:23:07.783099309Z + restimestampmock: 2025-11-10T12:23:07.783193839Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2025 +spec: + metadata: + connID: "640" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 327 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('2d5b0ab1-6b3a-4b31-a0c1-78d853542c98', 'ankit_shimla', 'ankit_shimla@example.in', 'scrypt:32768:8:1$hq3RS4sq4EyArcdu$722faa2907224897e3fb22cdd8a675b18a749bbd762bd60d6ddddad84fad04c617b599d8768111c13f697b777a17ac12e557236b578d71b06189ace37998b93a', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777387 + reqtimestampmock: 2025-11-10T12:23:07.839872045Z + restimestampmock: 2025-11-10T12:23:07.840386751Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2026 +spec: + metadata: + connID: "640" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777387 + reqtimestampmock: 2025-11-10T12:23:07.840533529Z + restimestampmock: 2025-11-10T12:23:07.852806106Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2027 +spec: + metadata: + connID: "642" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [157, 76, 0, 141, 9, 252, 172, 228, 188, 98, 164, 118, 216, 114, 110, 140, 248, 251, 188, 66, 105, 205, 200, 137, 127, 54, 225, 170, 152, 125, 7, 4] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 496 + auth_plugin_data: [12, 100, 13, 89, 17, 5, 120, 42, 116, 86, 125, 25, 121, 80, 72, 125, 98, 62, 46, 68, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777388 + reqtimestampmock: 2025-11-10T12:23:08.643459729Z + restimestampmock: 2025-11-10T12:23:08.65159474Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2028 +spec: + metadata: + connID: "642" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777388 + reqtimestampmock: 2025-11-10T12:23:08.651847048Z + restimestampmock: 2025-11-10T12:23:08.652071807Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2029 +spec: + metadata: + connID: "642" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777388 + reqtimestampmock: 2025-11-10T12:23:08.652228925Z + restimestampmock: 2025-11-10T12:23:08.652368654Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2030 +spec: + metadata: + connID: "642" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777388 + reqtimestampmock: 2025-11-10T12:23:08.652574622Z + restimestampmock: 2025-11-10T12:23:08.652670141Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2031 +spec: + metadata: + connID: "642" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 85 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='ankit_shimla' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 237 + sequence_id: 6 + values: + - type: 253 + name: id + value: 2d5b0ab1-6b3a-4b31-a0c1-78d853542c98 + unsigned: false + - type: 253 + name: username + value: ankit_shimla + unsigned: false + - type: 253 + name: email + value: ankit_shimla@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$hq3RS4sq4EyArcdu$722faa2907224897e3fb22cdd8a675b18a749bbd762bd60d6ddddad84fad04c617b599d8768111c13f697b777a17ac12e557236b578d71b06189ace37998b93a + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777388 + reqtimestampmock: 2025-11-10T12:23:08.652991578Z + restimestampmock: 2025-11-10T12:23:08.653573294Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2032 +spec: + metadata: + connID: "644" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [148, 0, 254, 93, 105, 201, 105, 42, 203, 6, 60, 99, 240, 72, 155, 98, 187, 103, 101, 100, 111, 101, 113, 160, 151, 42, 230, 162, 107, 87, 138, 223] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 497 + auth_plugin_data: [94, 2, 88, 58, 83, 60, 19, 107, 74, 1, 125, 22, 80, 83, 93, 69, 18, 53, 4, 54, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777389 + reqtimestampmock: 2025-11-10T12:23:09.470604035Z + restimestampmock: 2025-11-10T12:23:09.47722602Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2033 +spec: + metadata: + connID: "644" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777389 + reqtimestampmock: 2025-11-10T12:23:09.477413778Z + restimestampmock: 2025-11-10T12:23:09.477595606Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2034 +spec: + metadata: + connID: "644" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777389 + reqtimestampmock: 2025-11-10T12:23:09.477777484Z + restimestampmock: 2025-11-10T12:23:09.477991254Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2035 +spec: + metadata: + connID: "644" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777389 + reqtimestampmock: 2025-11-10T12:23:09.478216792Z + restimestampmock: 2025-11-10T12:23:09.478275961Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2036 +spec: + metadata: + connID: "644" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='2d5b0ab1-6b3a-4b31-a0c1-78d853542c98' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 2d5b0ab1-6b3a-4b31-a0c1-78d853542c98 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777389 + reqtimestampmock: 2025-11-10T12:23:09.47849058Z + restimestampmock: 2025-11-10T12:23:09.478859016Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2037 +spec: + metadata: + connID: "644" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 274 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('60846d3f-1c66-44e2-bd08-d0cc0b390e18','2d5b0ab1-6b3a-4b31-a0c1-78d853542c98','The Mall Road',NULL,'Shimla','Himachal Pradesh','171001','IN','+919418012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777389 + reqtimestampmock: 2025-11-10T12:23:09.479084934Z + restimestampmock: 2025-11-10T12:23:09.479436401Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2038 +spec: + metadata: + connID: "644" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='2d5b0ab1-6b3a-4b31-a0c1-78d853542c98' AND id<>'60846d3f-1c66-44e2-bd08-d0cc0b390e18' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777389 + reqtimestampmock: 2025-11-10T12:23:09.47960958Z + restimestampmock: 2025-11-10T12:23:09.479911318Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2039 +spec: + metadata: + connID: "644" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777389 + reqtimestampmock: 2025-11-10T12:23:09.480029066Z + restimestampmock: 2025-11-10T12:23:09.491799148Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2040 +spec: + metadata: + connID: "646" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [46, 174, 105, 101, 91, 67, 98, 249, 81, 43, 157, 147, 85, 227, 231, 8, 167, 253, 19, 21, 2, 242, 23, 41, 148, 81, 76, 19, 46, 254, 178, 45] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 498 + auth_plugin_data: [67, 44, 127, 63, 15, 3, 120, 29, 67, 62, 1, 55, 44, 18, 22, 31, 31, 45, 59, 18, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777390 + reqtimestampmock: 2025-11-10T12:23:10.236957589Z + restimestampmock: 2025-11-10T12:23:10.246584579Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2041 +spec: + metadata: + connID: "646" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777390 + reqtimestampmock: 2025-11-10T12:23:10.246844387Z + restimestampmock: 2025-11-10T12:23:10.247035556Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2042 +spec: + metadata: + connID: "646" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777390 + reqtimestampmock: 2025-11-10T12:23:10.247173525Z + restimestampmock: 2025-11-10T12:23:10.247300474Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2043 +spec: + metadata: + connID: "646" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777390 + reqtimestampmock: 2025-11-10T12:23:10.247410123Z + restimestampmock: 2025-11-10T12:23:10.247502122Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2044 +spec: + metadata: + connID: "646" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='2d5b0ab1-6b3a-4b31-a0c1-78d853542c98' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 2d5b0ab1-6b3a-4b31-a0c1-78d853542c98 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777390 + reqtimestampmock: 2025-11-10T12:23:10.247691241Z + restimestampmock: 2025-11-10T12:23:10.248057417Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2045 +spec: + metadata: + connID: "646" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='2d5b0ab1-6b3a-4b31-a0c1-78d853542c98' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777390 + reqtimestampmock: 2025-11-10T12:23:10.248163606Z + restimestampmock: 2025-11-10T12:23:10.248619132Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2046 +spec: + metadata: + connID: "646" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='2d5b0ab1-6b3a-4b31-a0c1-78d853542c98' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777390 + reqtimestampmock: 2025-11-10T12:23:10.249090139Z + restimestampmock: 2025-11-10T12:23:10.249178658Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2047 +spec: + metadata: + connID: "646" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777390 + reqtimestampmock: 2025-11-10T12:23:10.249443966Z + restimestampmock: 2025-11-10T12:23:10.265877218Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2048 +spec: + metadata: + connID: "648" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [235, 180, 87, 142, 116, 239, 142, 12, 149, 175, 116, 36, 5, 214, 227, 36, 63, 122, 138, 130, 4, 133, 3, 215, 60, 29, 130, 20, 173, 98, 127, 121] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 500 + auth_plugin_data: [11, 7, 30, 14, 48, 127, 117, 53, 24, 34, 64, 26, 89, 115, 77, 116, 73, 43, 84, 113, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.141581361Z + restimestampmock: 2025-11-10T12:23:12.149434606Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2049 +spec: + metadata: + connID: "648" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.149621734Z + restimestampmock: 2025-11-10T12:23:12.149814573Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2050 +spec: + metadata: + connID: "648" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.149920641Z + restimestampmock: 2025-11-10T12:23:12.15006216Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2051 +spec: + metadata: + connID: "648" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.15018218Z + restimestampmock: 2025-11-10T12:23:12.150299828Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2052 +spec: + metadata: + connID: "648" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('c6513285-696c-4f39-ac7a-116bfb97a272', 'shreya_raipur', 'shreya_raipur@example.in', 'scrypt:32768:8:1$jvpbiVZ3Wy8ptMLu$8ae1aa4fda0cddd64136982b5c8b6fd12790ac446dbe0915eb470ca2495a97f4ce02687d9e7021b190d09553c9938789427479ff540e26c3d5d10541848501e4', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.206122931Z + restimestampmock: 2025-11-10T12:23:12.206619328Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2053 +spec: + metadata: + connID: "648" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.206841456Z + restimestampmock: 2025-11-10T12:23:12.215723422Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2054 +spec: + metadata: + connID: "650" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [83, 74, 106, 68, 4, 217, 98, 121, 15, 217, 100, 167, 36, 3, 26, 7, 32, 251, 134, 140, 117, 223, 153, 155, 54, 123, 29, 242, 248, 127, 51, 49] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 501 + auth_plugin_data: [25, 21, 95, 117, 46, 103, 34, 50, 118, 50, 70, 26, 48, 56, 44, 78, 51, 118, 121, 26, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.903114007Z + restimestampmock: 2025-11-10T12:23:12.910793544Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2055 +spec: + metadata: + connID: "650" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.910970271Z + restimestampmock: 2025-11-10T12:23:12.911231599Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2056 +spec: + metadata: + connID: "650" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.911344928Z + restimestampmock: 2025-11-10T12:23:12.911516207Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2057 +spec: + metadata: + connID: "650" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.911611426Z + restimestampmock: 2025-11-10T12:23:12.911722135Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2058 +spec: + metadata: + connID: "650" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='shreya_raipur' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: c6513285-696c-4f39-ac7a-116bfb97a272 + unsigned: false + - type: 253 + name: username + value: shreya_raipur + unsigned: false + - type: 253 + name: email + value: shreya_raipur@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$jvpbiVZ3Wy8ptMLu$8ae1aa4fda0cddd64136982b5c8b6fd12790ac446dbe0915eb470ca2495a97f4ce02687d9e7021b190d09553c9938789427479ff540e26c3d5d10541848501e4 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777392 + reqtimestampmock: 2025-11-10T12:23:12.911889334Z + restimestampmock: 2025-11-10T12:23:12.912518609Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2059 +spec: + metadata: + connID: "652" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [33, 61, 10, 4, 219, 252, 180, 201, 53, 69, 186, 11, 160, 210, 150, 222, 240, 4, 71, 143, 3, 224, 237, 199, 57, 246, 197, 210, 18, 48, 163, 82] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 502 + auth_plugin_data: [57, 31, 50, 51, 28, 115, 126, 63, 111, 3, 123, 105, 23, 13, 46, 124, 61, 74, 44, 1, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777393 + reqtimestampmock: 2025-11-10T12:23:13.669644441Z + restimestampmock: 2025-11-10T12:23:13.676590713Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2060 +spec: + metadata: + connID: "652" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777393 + reqtimestampmock: 2025-11-10T12:23:13.676799071Z + restimestampmock: 2025-11-10T12:23:13.67696591Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2061 +spec: + metadata: + connID: "652" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777393 + reqtimestampmock: 2025-11-10T12:23:13.677468225Z + restimestampmock: 2025-11-10T12:23:13.677664034Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2062 +spec: + metadata: + connID: "652" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777393 + reqtimestampmock: 2025-11-10T12:23:13.677785912Z + restimestampmock: 2025-11-10T12:23:13.677878461Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2063 +spec: + metadata: + connID: "652" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='c6513285-696c-4f39-ac7a-116bfb97a272' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: c6513285-696c-4f39-ac7a-116bfb97a272 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777393 + reqtimestampmock: 2025-11-10T12:23:13.67802482Z + restimestampmock: 2025-11-10T12:23:13.678461888Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2064 +spec: + metadata: + connID: "652" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 268 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('8fe9dd7c-b34d-4b02-a06a-7d566401dec7','c6513285-696c-4f39-ac7a-116bfb97a272','Civil Lines',NULL,'Raipur','Chhattisgarh','492001','IN','+919425212345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777393 + reqtimestampmock: 2025-11-10T12:23:13.678604616Z + restimestampmock: 2025-11-10T12:23:13.678879333Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2065 +spec: + metadata: + connID: "652" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='c6513285-696c-4f39-ac7a-116bfb97a272' AND id<>'8fe9dd7c-b34d-4b02-a06a-7d566401dec7' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777393 + reqtimestampmock: 2025-11-10T12:23:13.678978143Z + restimestampmock: 2025-11-10T12:23:13.67926922Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2066 +spec: + metadata: + connID: "652" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777393 + reqtimestampmock: 2025-11-10T12:23:13.67935777Z + restimestampmock: 2025-11-10T12:23:13.68769059Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2067 +spec: + metadata: + connID: "654" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [149, 212, 175, 137, 157, 228, 174, 92, 101, 237, 99, 229, 195, 74, 47, 152, 233, 16, 249, 160, 157, 8, 183, 213, 35, 150, 84, 58, 71, 57, 6, 255] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 503 + auth_plugin_data: [24, 65, 39, 122, 56, 10, 123, 114, 6, 66, 75, 25, 62, 60, 97, 59, 118, 41, 98, 10, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777394 + reqtimestampmock: 2025-11-10T12:23:14.441841828Z + restimestampmock: 2025-11-10T12:23:14.450860403Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2068 +spec: + metadata: + connID: "654" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777394 + reqtimestampmock: 2025-11-10T12:23:14.451096061Z + restimestampmock: 2025-11-10T12:23:14.451345429Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2069 +spec: + metadata: + connID: "654" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777394 + reqtimestampmock: 2025-11-10T12:23:14.451533916Z + restimestampmock: 2025-11-10T12:23:14.451718485Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2070 +spec: + metadata: + connID: "654" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777394 + reqtimestampmock: 2025-11-10T12:23:14.451872764Z + restimestampmock: 2025-11-10T12:23:14.451990134Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2071 +spec: + metadata: + connID: "654" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='c6513285-696c-4f39-ac7a-116bfb97a272' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: c6513285-696c-4f39-ac7a-116bfb97a272 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777394 + reqtimestampmock: 2025-11-10T12:23:14.452234791Z + restimestampmock: 2025-11-10T12:23:14.452819456Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2072 +spec: + metadata: + connID: "654" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='c6513285-696c-4f39-ac7a-116bfb97a272' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777394 + reqtimestampmock: 2025-11-10T12:23:14.452989045Z + restimestampmock: 2025-11-10T12:23:14.453410181Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2073 +spec: + metadata: + connID: "654" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='c6513285-696c-4f39-ac7a-116bfb97a272' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777394 + reqtimestampmock: 2025-11-10T12:23:14.45358333Z + restimestampmock: 2025-11-10T12:23:14.453921688Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2074 +spec: + metadata: + connID: "654" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777394 + reqtimestampmock: 2025-11-10T12:23:14.454028447Z + restimestampmock: 2025-11-10T12:23:14.470859686Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2075 +spec: + metadata: + connID: "656" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [90, 28, 139, 172, 209, 31, 92, 70, 160, 43, 231, 157, 194, 47, 103, 94, 48, 241, 45, 108, 177, 124, 180, 34, 234, 5, 107, 50, 211, 168, 104, 11] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 505 + auth_plugin_data: [64, 11, 115, 126, 64, 80, 113, 87, 121, 26, 44, 116, 95, 25, 72, 92, 115, 11, 100, 73, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777396 + reqtimestampmock: 2025-11-10T12:23:16.86007994Z + restimestampmock: 2025-11-10T12:23:16.866834612Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2076 +spec: + metadata: + connID: "656" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777396 + reqtimestampmock: 2025-11-10T12:23:16.86709649Z + restimestampmock: 2025-11-10T12:23:16.867331648Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2077 +spec: + metadata: + connID: "656" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777396 + reqtimestampmock: 2025-11-10T12:23:16.867504327Z + restimestampmock: 2025-11-10T12:23:16.867726365Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2078 +spec: + metadata: + connID: "656" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777396 + reqtimestampmock: 2025-11-10T12:23:16.867896515Z + restimestampmock: 2025-11-10T12:23:16.868006223Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2079 +spec: + metadata: + connID: "656" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 325 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('c451d32e-5cdf-4ca8-aeeb-0442518719a7', 'karthik_cbe', 'karthik_cbe@example.in', 'scrypt:32768:8:1$Rp0BSwAMCteY2d34$bbf3cda42f1076b5e139ce61fbecc204b5c8824534879de3fe00b19d1307a217b31c16714f60c482d37b49b73f249e5aac36920d8176af8311fc19d17b6e9349', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777396 + reqtimestampmock: 2025-11-10T12:23:16.925478824Z + restimestampmock: 2025-11-10T12:23:16.926048328Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2080 +spec: + metadata: + connID: "656" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777396 + reqtimestampmock: 2025-11-10T12:23:16.926250267Z + restimestampmock: 2025-11-10T12:23:16.938796092Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2081 +spec: + metadata: + connID: "658" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [247, 11, 10, 113, 49, 144, 189, 212, 110, 140, 69, 170, 221, 109, 245, 52, 7, 173, 156, 93, 74, 55, 37, 67, 177, 49, 60, 116, 37, 26, 216, 136] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 506 + auth_plugin_data: [16, 34, 102, 53, 97, 43, 78, 127, 55, 65, 47, 77, 114, 26, 21, 35, 47, 115, 44, 51, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777397 + reqtimestampmock: 2025-11-10T12:23:17.666605181Z + restimestampmock: 2025-11-10T12:23:17.673552152Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2082 +spec: + metadata: + connID: "658" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777397 + reqtimestampmock: 2025-11-10T12:23:17.673836301Z + restimestampmock: 2025-11-10T12:23:17.674189367Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2083 +spec: + metadata: + connID: "658" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777397 + reqtimestampmock: 2025-11-10T12:23:17.674362706Z + restimestampmock: 2025-11-10T12:23:17.674582094Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2084 +spec: + metadata: + connID: "658" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777397 + reqtimestampmock: 2025-11-10T12:23:17.675278509Z + restimestampmock: 2025-11-10T12:23:17.675377388Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2085 +spec: + metadata: + connID: "658" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 84 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='karthik_cbe' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 235 + sequence_id: 6 + values: + - type: 253 + name: id + value: c451d32e-5cdf-4ca8-aeeb-0442518719a7 + unsigned: false + - type: 253 + name: username + value: karthik_cbe + unsigned: false + - type: 253 + name: email + value: karthik_cbe@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$Rp0BSwAMCteY2d34$bbf3cda42f1076b5e139ce61fbecc204b5c8824534879de3fe00b19d1307a217b31c16714f60c482d37b49b73f249e5aac36920d8176af8311fc19d17b6e9349 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777397 + reqtimestampmock: 2025-11-10T12:23:17.675628526Z + restimestampmock: 2025-11-10T12:23:17.67619539Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2086 +spec: + metadata: + connID: "660" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [232, 224, 124, 95, 203, 4, 6, 242, 241, 53, 122, 121, 248, 34, 213, 54, 234, 179, 27, 202, 112, 131, 139, 111, 252, 21, 231, 90, 194, 176, 238, 255] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 507 + auth_plugin_data: [65, 7, 39, 16, 117, 14, 6, 103, 19, 71, 44, 48, 69, 21, 71, 95, 94, 50, 82, 27, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777398 + reqtimestampmock: 2025-11-10T12:23:18.416361686Z + restimestampmock: 2025-11-10T12:23:18.425360821Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2087 +spec: + metadata: + connID: "660" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777398 + reqtimestampmock: 2025-11-10T12:23:18.425658328Z + restimestampmock: 2025-11-10T12:23:18.425914625Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2088 +spec: + metadata: + connID: "660" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777398 + reqtimestampmock: 2025-11-10T12:23:18.426086584Z + restimestampmock: 2025-11-10T12:23:18.426332592Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2089 +spec: + metadata: + connID: "660" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777398 + reqtimestampmock: 2025-11-10T12:23:18.426470551Z + restimestampmock: 2025-11-10T12:23:18.42661457Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2090 +spec: + metadata: + connID: "660" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='c451d32e-5cdf-4ca8-aeeb-0442518719a7' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: c451d32e-5cdf-4ca8-aeeb-0442518719a7 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777398 + reqtimestampmock: 2025-11-10T12:23:18.426846998Z + restimestampmock: 2025-11-10T12:23:18.427289235Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2091 +spec: + metadata: + connID: "660" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 267 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('4f16c0dc-ad54-493d-9caa-7f07512f702c','c451d32e-5cdf-4ca8-aeeb-0442518719a7','RS Puram',NULL,'Coimbatore','Tamil Nadu','641002','IN','+919843012345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777398 + reqtimestampmock: 2025-11-10T12:23:18.427654992Z + restimestampmock: 2025-11-10T12:23:18.428050069Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2092 +spec: + metadata: + connID: "660" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='c451d32e-5cdf-4ca8-aeeb-0442518719a7' AND id<>'4f16c0dc-ad54-493d-9caa-7f07512f702c' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777398 + reqtimestampmock: 2025-11-10T12:23:18.428235786Z + restimestampmock: 2025-11-10T12:23:18.428584133Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2093 +spec: + metadata: + connID: "660" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777398 + reqtimestampmock: 2025-11-10T12:23:18.428768382Z + restimestampmock: 2025-11-10T12:23:18.436810334Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2094 +spec: + metadata: + connID: "662" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [7, 44, 81, 123, 231, 173, 23, 29, 142, 203, 50, 7, 25, 105, 178, 218, 210, 16, 164, 192, 148, 152, 195, 75, 204, 79, 133, 204, 181, 43, 171, 43] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 508 + auth_plugin_data: [94, 81, 33, 16, 57, 12, 29, 27, 40, 126, 114, 109, 52, 9, 81, 28, 11, 37, 18, 15, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777399 + reqtimestampmock: 2025-11-10T12:23:19.159545026Z + restimestampmock: 2025-11-10T12:23:19.166892665Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2095 +spec: + metadata: + connID: "662" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777399 + reqtimestampmock: 2025-11-10T12:23:19.167118872Z + restimestampmock: 2025-11-10T12:23:19.167346961Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2096 +spec: + metadata: + connID: "662" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777399 + reqtimestampmock: 2025-11-10T12:23:19.16749042Z + restimestampmock: 2025-11-10T12:23:19.167638518Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2097 +spec: + metadata: + connID: "662" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777399 + reqtimestampmock: 2025-11-10T12:23:19.167805597Z + restimestampmock: 2025-11-10T12:23:19.167911106Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2098 +spec: + metadata: + connID: "662" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='c451d32e-5cdf-4ca8-aeeb-0442518719a7' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: c451d32e-5cdf-4ca8-aeeb-0442518719a7 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777399 + reqtimestampmock: 2025-11-10T12:23:19.168188034Z + restimestampmock: 2025-11-10T12:23:19.168610491Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2099 +spec: + metadata: + connID: "662" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='c451d32e-5cdf-4ca8-aeeb-0442518719a7' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777399 + reqtimestampmock: 2025-11-10T12:23:19.168778378Z + restimestampmock: 2025-11-10T12:23:19.169205156Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2100 +spec: + metadata: + connID: "662" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='c451d32e-5cdf-4ca8-aeeb-0442518719a7' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777399 + reqtimestampmock: 2025-11-10T12:23:19.169452133Z + restimestampmock: 2025-11-10T12:23:19.169751282Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2101 +spec: + metadata: + connID: "662" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777399 + reqtimestampmock: 2025-11-10T12:23:19.16988981Z + restimestampmock: 2025-11-10T12:23:19.178742436Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2102 +spec: + metadata: + connID: "664" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [33, 135, 184, 230, 191, 40, 131, 86, 40, 240, 33, 54, 36, 237, 166, 153, 156, 142, 169, 46, 86, 33, 53, 35, 94, 140, 26, 196, 85, 139, 40, 88] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 510 + auth_plugin_data: [91, 116, 32, 91, 39, 123, 113, 124, 37, 108, 91, 28, 20, 7, 88, 91, 71, 54, 121, 125, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777401 + reqtimestampmock: 2025-11-10T12:23:21.706585725Z + restimestampmock: 2025-11-10T12:23:21.716319315Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2103 +spec: + metadata: + connID: "664" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777401 + reqtimestampmock: 2025-11-10T12:23:21.716618192Z + restimestampmock: 2025-11-10T12:23:21.716905739Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2104 +spec: + metadata: + connID: "664" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777401 + reqtimestampmock: 2025-11-10T12:23:21.717070959Z + restimestampmock: 2025-11-10T12:23:21.717306336Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2105 +spec: + metadata: + connID: "664" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777401 + reqtimestampmock: 2025-11-10T12:23:21.717453725Z + restimestampmock: 2025-11-10T12:23:21.717584124Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2106 +spec: + metadata: + connID: "664" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('7589f9d1-ff0f-4de5-8826-94e379e80c71', 'meena_madurai', 'meena_madurai@example.in', 'scrypt:32768:8:1$TRgGg5FBZwzkxcAx$05186131e0b5bacfcc5620fc1bf5a53ad414d1d2bafd5859db8c09d2c9840a3079194c3bdcfcf93cbbd29b206af1fe3e0e480714dcfda931c211bb5236fcf44c', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777401 + reqtimestampmock: 2025-11-10T12:23:21.788023335Z + restimestampmock: 2025-11-10T12:23:21.78861547Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2107 +spec: + metadata: + connID: "664" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777401 + reqtimestampmock: 2025-11-10T12:23:21.788824259Z + restimestampmock: 2025-11-10T12:23:21.798786545Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2108 +spec: + metadata: + connID: "666" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [168, 145, 232, 192, 74, 74, 176, 7, 50, 42, 113, 201, 82, 122, 65, 183, 248, 6, 75, 248, 50, 87, 166, 138, 86, 191, 117, 48, 126, 95, 213, 170] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 511 + auth_plugin_data: [121, 127, 108, 59, 117, 81, 73, 102, 2, 84, 64, 126, 94, 118, 22, 47, 30, 28, 121, 49, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777402 + reqtimestampmock: 2025-11-10T12:23:22.577225721Z + restimestampmock: 2025-11-10T12:23:22.589939625Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2109 +spec: + metadata: + connID: "666" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777402 + reqtimestampmock: 2025-11-10T12:23:22.590171583Z + restimestampmock: 2025-11-10T12:23:22.590451641Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2110 +spec: + metadata: + connID: "666" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777402 + reqtimestampmock: 2025-11-10T12:23:22.590600839Z + restimestampmock: 2025-11-10T12:23:22.590830118Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2111 +spec: + metadata: + connID: "666" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777402 + reqtimestampmock: 2025-11-10T12:23:22.591016675Z + restimestampmock: 2025-11-10T12:23:22.591182904Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2112 +spec: + metadata: + connID: "666" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='meena_madurai' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: 7589f9d1-ff0f-4de5-8826-94e379e80c71 + unsigned: false + - type: 253 + name: username + value: meena_madurai + unsigned: false + - type: 253 + name: email + value: meena_madurai@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$TRgGg5FBZwzkxcAx$05186131e0b5bacfcc5620fc1bf5a53ad414d1d2bafd5859db8c09d2c9840a3079194c3bdcfcf93cbbd29b206af1fe3e0e480714dcfda931c211bb5236fcf44c + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777402 + reqtimestampmock: 2025-11-10T12:23:22.591399253Z + restimestampmock: 2025-11-10T12:23:22.591855589Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2113 +spec: + metadata: + connID: "668" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [16, 118, 23, 34, 124, 244, 197, 110, 198, 244, 111, 175, 169, 103, 50, 65, 131, 134, 193, 77, 183, 5, 165, 105, 1, 20, 78, 36, 74, 115, 131, 183] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 512 + auth_plugin_data: [98, 37, 72, 1, 89, 47, 102, 89, 75, 106, 55, 87, 70, 92, 81, 108, 7, 41, 17, 66, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777403 + reqtimestampmock: 2025-11-10T12:23:23.323626715Z + restimestampmock: 2025-11-10T12:23:23.333123345Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2114 +spec: + metadata: + connID: "668" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777403 + reqtimestampmock: 2025-11-10T12:23:23.333414423Z + restimestampmock: 2025-11-10T12:23:23.333618351Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2115 +spec: + metadata: + connID: "668" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777403 + reqtimestampmock: 2025-11-10T12:23:23.33373294Z + restimestampmock: 2025-11-10T12:23:23.333884939Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2116 +spec: + metadata: + connID: "668" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777403 + reqtimestampmock: 2025-11-10T12:23:23.333983878Z + restimestampmock: 2025-11-10T12:23:23.334084388Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2117 +spec: + metadata: + connID: "668" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='7589f9d1-ff0f-4de5-8826-94e379e80c71' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 7589f9d1-ff0f-4de5-8826-94e379e80c71 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777403 + reqtimestampmock: 2025-11-10T12:23:23.334322286Z + restimestampmock: 2025-11-10T12:23:23.334687073Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2118 +spec: + metadata: + connID: "668" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 266 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('ce31628e-b33b-4dff-8662-bc96f9929894','7589f9d1-ff0f-4de5-8826-94e379e80c71','Anna Nagar',NULL,'Madurai','Tamil Nadu','625020','IN','+919842112345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777403 + reqtimestampmock: 2025-11-10T12:23:23.334882761Z + restimestampmock: 2025-11-10T12:23:23.335221188Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2119 +spec: + metadata: + connID: "668" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='7589f9d1-ff0f-4de5-8826-94e379e80c71' AND id<>'ce31628e-b33b-4dff-8662-bc96f9929894' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777403 + reqtimestampmock: 2025-11-10T12:23:23.335389026Z + restimestampmock: 2025-11-10T12:23:23.335722114Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2120 +spec: + metadata: + connID: "668" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777403 + reqtimestampmock: 2025-11-10T12:23:23.335849613Z + restimestampmock: 2025-11-10T12:23:23.344847278Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2121 +spec: + metadata: + connID: "670" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [134, 144, 105, 120, 98, 16, 136, 145, 114, 185, 191, 105, 17, 57, 206, 39, 244, 161, 10, 27, 74, 86, 114, 116, 73, 125, 84, 155, 236, 60, 155, 41] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 513 + auth_plugin_data: [33, 97, 112, 10, 101, 111, 58, 80, 65, 11, 51, 20, 49, 125, 11, 18, 8, 1, 19, 5, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777404 + reqtimestampmock: 2025-11-10T12:23:24.152205962Z + restimestampmock: 2025-11-10T12:23:24.159783238Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2122 +spec: + metadata: + connID: "670" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777404 + reqtimestampmock: 2025-11-10T12:23:24.159998007Z + restimestampmock: 2025-11-10T12:23:24.160213226Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2123 +spec: + metadata: + connID: "670" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777404 + reqtimestampmock: 2025-11-10T12:23:24.160354684Z + restimestampmock: 2025-11-10T12:23:24.160515883Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2124 +spec: + metadata: + connID: "670" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777404 + reqtimestampmock: 2025-11-10T12:23:24.160642252Z + restimestampmock: 2025-11-10T12:23:24.160757331Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2125 +spec: + metadata: + connID: "670" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='7589f9d1-ff0f-4de5-8826-94e379e80c71' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 7589f9d1-ff0f-4de5-8826-94e379e80c71 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777404 + reqtimestampmock: 2025-11-10T12:23:24.160943149Z + restimestampmock: 2025-11-10T12:23:24.161393246Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2126 +spec: + metadata: + connID: "670" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='7589f9d1-ff0f-4de5-8826-94e379e80c71' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777404 + reqtimestampmock: 2025-11-10T12:23:24.161577814Z + restimestampmock: 2025-11-10T12:23:24.16196722Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2127 +spec: + metadata: + connID: "670" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='7589f9d1-ff0f-4de5-8826-94e379e80c71' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777404 + reqtimestampmock: 2025-11-10T12:23:24.162117069Z + restimestampmock: 2025-11-10T12:23:24.162433727Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2128 +spec: + metadata: + connID: "670" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777404 + reqtimestampmock: 2025-11-10T12:23:24.162534996Z + restimestampmock: 2025-11-10T12:23:24.172700481Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2129 +spec: + metadata: + connID: "672" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [80, 40, 246, 91, 209, 235, 113, 60, 3, 25, 85, 216, 67, 174, 175, 159, 233, 154, 246, 87, 198, 198, 234, 122, 190, 42, 134, 227, 145, 242, 251, 173] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 515 + auth_plugin_data: [3, 30, 83, 21, 15, 116, 111, 123, 114, 94, 103, 44, 2, 71, 109, 7, 1, 55, 111, 52, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777406 + reqtimestampmock: 2025-11-10T12:23:26.591422016Z + restimestampmock: 2025-11-10T12:23:26.602318995Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2130 +spec: + metadata: + connID: "672" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777406 + reqtimestampmock: 2025-11-10T12:23:26.602659962Z + restimestampmock: 2025-11-10T12:23:26.60288946Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2131 +spec: + metadata: + connID: "672" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777406 + reqtimestampmock: 2025-11-10T12:23:26.603072749Z + restimestampmock: 2025-11-10T12:23:26.603291817Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2132 +spec: + metadata: + connID: "672" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777406 + reqtimestampmock: 2025-11-10T12:23:26.603430096Z + restimestampmock: 2025-11-10T12:23:26.603595255Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2133 +spec: + metadata: + connID: "672" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 329 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('a1276f9a-46d4-471e-8067-75cb6ea37800', 'sunil_jodhpur', 'sunil_jodhpur@example.in', 'scrypt:32768:8:1$t4Z9r11GEsE4OEqB$38f72321119cbce5f7af7b41732e1e1e2b40c02e70a4d125365e1d84472ed09915a80da2b35792a2f1443473aab6cc552693338a5282fbeaf9523541f0dceed0', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777406 + reqtimestampmock: 2025-11-10T12:23:26.66274475Z + restimestampmock: 2025-11-10T12:23:26.663386035Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2134 +spec: + metadata: + connID: "672" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777406 + reqtimestampmock: 2025-11-10T12:23:26.663528843Z + restimestampmock: 2025-11-10T12:23:26.673848787Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2135 +spec: + metadata: + connID: "674" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [244, 220, 203, 134, 78, 2, 47, 253, 162, 232, 155, 112, 246, 217, 73, 238, 73, 254, 53, 58, 182, 48, 66, 36, 86, 239, 124, 215, 115, 55, 88, 78] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 516 + auth_plugin_data: [37, 39, 2, 58, 4, 110, 35, 75, 123, 99, 127, 112, 38, 26, 88, 18, 32, 9, 124, 13, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777407 + reqtimestampmock: 2025-11-10T12:23:27.52705411Z + restimestampmock: 2025-11-10T12:23:27.540801346Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2136 +spec: + metadata: + connID: "674" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777407 + reqtimestampmock: 2025-11-10T12:23:27.541200761Z + restimestampmock: 2025-11-10T12:23:27.541495879Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2137 +spec: + metadata: + connID: "674" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777407 + reqtimestampmock: 2025-11-10T12:23:27.541683808Z + restimestampmock: 2025-11-10T12:23:27.541920966Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2138 +spec: + metadata: + connID: "674" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777407 + reqtimestampmock: 2025-11-10T12:23:27.542106474Z + restimestampmock: 2025-11-10T12:23:27.542293323Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2139 +spec: + metadata: + connID: "674" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 86 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='sunil_jodhpur' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 239 + sequence_id: 6 + values: + - type: 253 + name: id + value: a1276f9a-46d4-471e-8067-75cb6ea37800 + unsigned: false + - type: 253 + name: username + value: sunil_jodhpur + unsigned: false + - type: 253 + name: email + value: sunil_jodhpur@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$t4Z9r11GEsE4OEqB$38f72321119cbce5f7af7b41732e1e1e2b40c02e70a4d125365e1d84472ed09915a80da2b35792a2f1443473aab6cc552693338a5282fbeaf9523541f0dceed0 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777407 + reqtimestampmock: 2025-11-10T12:23:27.54256982Z + restimestampmock: 2025-11-10T12:23:27.543132465Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2140 +spec: + metadata: + connID: "676" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [189, 144, 21, 114, 154, 71, 29, 216, 195, 68, 184, 61, 221, 155, 229, 168, 119, 197, 197, 108, 58, 128, 91, 24, 96, 2, 197, 232, 206, 160, 196, 105] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 517 + auth_plugin_data: [115, 44, 81, 55, 6, 100, 94, 3, 100, 114, 127, 10, 115, 85, 88, 51, 48, 16, 72, 75, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777408 + reqtimestampmock: 2025-11-10T12:23:28.35533791Z + restimestampmock: 2025-11-10T12:23:28.365797163Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2141 +spec: + metadata: + connID: "676" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777408 + reqtimestampmock: 2025-11-10T12:23:28.366132081Z + restimestampmock: 2025-11-10T12:23:28.366459657Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2142 +spec: + metadata: + connID: "676" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777408 + reqtimestampmock: 2025-11-10T12:23:28.366631456Z + restimestampmock: 2025-11-10T12:23:28.366892654Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2143 +spec: + metadata: + connID: "676" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777408 + reqtimestampmock: 2025-11-10T12:23:28.367041862Z + restimestampmock: 2025-11-10T12:23:28.367201511Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2144 +spec: + metadata: + connID: "676" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a1276f9a-46d4-471e-8067-75cb6ea37800' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a1276f9a-46d4-471e-8067-75cb6ea37800 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777408 + reqtimestampmock: 2025-11-10T12:23:28.367454889Z + restimestampmock: 2025-11-10T12:23:28.367959895Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2145 +spec: + metadata: + connID: "676" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 265 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('57e896c2-18ea-4aa2-9703-f064429a1b72','a1276f9a-46d4-471e-8067-75cb6ea37800','Sardarpura',NULL,'Jodhpur','Rajasthan','342003','IN','+919829112345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777408 + reqtimestampmock: 2025-11-10T12:23:28.368217863Z + restimestampmock: 2025-11-10T12:23:28.36866326Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2146 +spec: + metadata: + connID: "676" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='a1276f9a-46d4-471e-8067-75cb6ea37800' AND id<>'57e896c2-18ea-4aa2-9703-f064429a1b72' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777408 + reqtimestampmock: 2025-11-10T12:23:28.368988836Z + restimestampmock: 2025-11-10T12:23:28.369399843Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2147 +spec: + metadata: + connID: "676" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777408 + reqtimestampmock: 2025-11-10T12:23:28.369571792Z + restimestampmock: 2025-11-10T12:23:28.386862347Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2148 +spec: + metadata: + connID: "678" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [150, 134, 34, 182, 34, 4, 240, 17, 206, 133, 3, 48, 74, 158, 72, 197, 7, 210, 130, 195, 143, 77, 123, 60, 98, 58, 95, 123, 191, 109, 225, 231] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 518 + auth_plugin_data: [126, 63, 80, 10, 115, 75, 110, 5, 53, 83, 29, 120, 32, 19, 53, 111, 59, 78, 127, 126, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777409 + reqtimestampmock: 2025-11-10T12:23:29.130682644Z + restimestampmock: 2025-11-10T12:23:29.138015142Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2149 +spec: + metadata: + connID: "678" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777409 + reqtimestampmock: 2025-11-10T12:23:29.138346469Z + restimestampmock: 2025-11-10T12:23:29.138583527Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2150 +spec: + metadata: + connID: "678" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777409 + reqtimestampmock: 2025-11-10T12:23:29.138698726Z + restimestampmock: 2025-11-10T12:23:29.138896055Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2151 +spec: + metadata: + connID: "678" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777409 + reqtimestampmock: 2025-11-10T12:23:29.139024494Z + restimestampmock: 2025-11-10T12:23:29.139374141Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2152 +spec: + metadata: + connID: "678" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='a1276f9a-46d4-471e-8067-75cb6ea37800' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: a1276f9a-46d4-471e-8067-75cb6ea37800 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777409 + reqtimestampmock: 2025-11-10T12:23:29.13955681Z + restimestampmock: 2025-11-10T12:23:29.139972536Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2153 +spec: + metadata: + connID: "678" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='a1276f9a-46d4-471e-8067-75cb6ea37800' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777409 + reqtimestampmock: 2025-11-10T12:23:29.140247984Z + restimestampmock: 2025-11-10T12:23:29.140576062Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2154 +spec: + metadata: + connID: "678" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='a1276f9a-46d4-471e-8067-75cb6ea37800' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777409 + reqtimestampmock: 2025-11-10T12:23:29.140768559Z + restimestampmock: 2025-11-10T12:23:29.141227755Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2155 +spec: + metadata: + connID: "678" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777409 + reqtimestampmock: 2025-11-10T12:23:29.141386834Z + restimestampmock: 2025-11-10T12:23:29.151759407Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2156 +spec: + metadata: + connID: "680" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [118, 175, 242, 49, 100, 67, 124, 55, 255, 33, 30, 106, 14, 109, 89, 119, 233, 160, 52, 214, 11, 101, 7, 161, 128, 247, 218, 245, 64, 54, 42, 71] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 520 + auth_plugin_data: [84, 113, 112, 28, 34, 19, 44, 15, 42, 96, 123, 21, 99, 77, 34, 117, 86, 93, 25, 126, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777411 + reqtimestampmock: 2025-11-10T12:23:31.273098127Z + restimestampmock: 2025-11-10T12:23:31.283662999Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2157 +spec: + metadata: + connID: "680" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777411 + reqtimestampmock: 2025-11-10T12:23:31.283963246Z + restimestampmock: 2025-11-10T12:23:31.284240074Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2158 +spec: + metadata: + connID: "680" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777411 + reqtimestampmock: 2025-11-10T12:23:31.284380183Z + restimestampmock: 2025-11-10T12:23:31.284650091Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2159 +spec: + metadata: + connID: "680" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777411 + reqtimestampmock: 2025-11-10T12:23:31.28479519Z + restimestampmock: 2025-11-10T12:23:31.284935539Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2160 +spec: + metadata: + connID: "680" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 319 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('96b38460-486f-42f1-8251-da28d33c8749', 'vani_vja', 'vani_vja@example.in', 'scrypt:32768:8:1$iOiHb9d9qO9bsXCo$e34f375562674f95891233fb375bae82502a803ab8daf0638e2405bb445c81dfae437b2ce0aef1daf36b07b71a47a3a13ea6a96c887faafb2a7a19c80bf5801e', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777411 + reqtimestampmock: 2025-11-10T12:23:31.355092892Z + restimestampmock: 2025-11-10T12:23:31.355770787Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2161 +spec: + metadata: + connID: "680" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777411 + reqtimestampmock: 2025-11-10T12:23:31.355972555Z + restimestampmock: 2025-11-10T12:23:31.368792878Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2162 +spec: + metadata: + connID: "682" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [115, 40, 226, 210, 34, 7, 70, 128, 155, 168, 129, 98, 112, 195, 155, 43, 3, 252, 50, 31, 120, 116, 208, 217, 78, 146, 183, 182, 167, 136, 130, 206] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 521 + auth_plugin_data: [94, 30, 125, 64, 37, 119, 13, 122, 46, 91, 14, 102, 54, 15, 46, 8, 74, 46, 103, 74, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.133790906Z + restimestampmock: 2025-11-10T12:23:32.14059598Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2163 +spec: + metadata: + connID: "682" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.140866079Z + restimestampmock: 2025-11-10T12:23:32.141143145Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2164 +spec: + metadata: + connID: "682" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.141269304Z + restimestampmock: 2025-11-10T12:23:32.141459603Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2165 +spec: + metadata: + connID: "682" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.141589672Z + restimestampmock: 2025-11-10T12:23:32.14177002Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2166 +spec: + metadata: + connID: "682" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 81 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='vani_vja' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 229 + sequence_id: 6 + values: + - type: 253 + name: id + value: 96b38460-486f-42f1-8251-da28d33c8749 + unsigned: false + - type: 253 + name: username + value: vani_vja + unsigned: false + - type: 253 + name: email + value: vani_vja@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$iOiHb9d9qO9bsXCo$e34f375562674f95891233fb375bae82502a803ab8daf0638e2405bb445c81dfae437b2ce0aef1daf36b07b71a47a3a13ea6a96c887faafb2a7a19c80bf5801e + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.142012098Z + restimestampmock: 2025-11-10T12:23:32.142603713Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2167 +spec: + metadata: + connID: "684" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [168, 110, 169, 111, 207, 10, 216, 0, 79, 26, 26, 207, 157, 74, 96, 160, 209, 64, 26, 21, 92, 203, 142, 179, 30, 252, 133, 15, 143, 212, 176, 177] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 522 + auth_plugin_data: [59, 33, 56, 111, 8, 88, 85, 126, 7, 20, 21, 4, 8, 107, 19, 19, 58, 37, 35, 10, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.90323198Z + restimestampmock: 2025-11-10T12:23:32.916563749Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2168 +spec: + metadata: + connID: "684" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.916848926Z + restimestampmock: 2025-11-10T12:23:32.917115623Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2169 +spec: + metadata: + connID: "684" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.917354222Z + restimestampmock: 2025-11-10T12:23:32.91756149Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2170 +spec: + metadata: + connID: "684" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.917696919Z + restimestampmock: 2025-11-10T12:23:32.917817648Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2171 +spec: + metadata: + connID: "684" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='96b38460-486f-42f1-8251-da28d33c8749' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 96b38460-486f-42f1-8251-da28d33c8749 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.918057336Z + restimestampmock: 2025-11-10T12:23:32.918428952Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2172 +spec: + metadata: + connID: "684" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 274 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('22677a17-3088-4ec3-ad4c-cb8dec1d6cf0','96b38460-486f-42f1-8251-da28d33c8749','Benz Circle',NULL,'Vijayawada','Andhra Pradesh','520010','IN','+919848112345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.91872452Z + restimestampmock: 2025-11-10T12:23:32.919140596Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2173 +spec: + metadata: + connID: "684" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='96b38460-486f-42f1-8251-da28d33c8749' AND id<>'22677a17-3088-4ec3-ad4c-cb8dec1d6cf0' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.919383395Z + restimestampmock: 2025-11-10T12:23:32.919748002Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2174 +spec: + metadata: + connID: "684" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777412 + reqtimestampmock: 2025-11-10T12:23:32.919890101Z + restimestampmock: 2025-11-10T12:23:32.92944363Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2175 +spec: + metadata: + connID: "686" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [207, 151, 27, 121, 88, 111, 65, 79, 229, 179, 230, 178, 38, 138, 107, 84, 47, 17, 201, 1, 181, 91, 246, 98, 11, 66, 187, 44, 29, 194, 219, 120] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 523 + auth_plugin_data: [70, 108, 96, 89, 22, 114, 23, 54, 117, 74, 100, 103, 48, 127, 125, 28, 13, 101, 11, 51, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777413 + reqtimestampmock: 2025-11-10T12:23:33.81913482Z + restimestampmock: 2025-11-10T12:23:33.827036903Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2176 +spec: + metadata: + connID: "686" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777413 + reqtimestampmock: 2025-11-10T12:23:33.827298331Z + restimestampmock: 2025-11-10T12:23:33.82750682Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2177 +spec: + metadata: + connID: "686" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777413 + reqtimestampmock: 2025-11-10T12:23:33.827672858Z + restimestampmock: 2025-11-10T12:23:33.827854956Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2178 +spec: + metadata: + connID: "686" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777413 + reqtimestampmock: 2025-11-10T12:23:33.827993765Z + restimestampmock: 2025-11-10T12:23:33.828096185Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2179 +spec: + metadata: + connID: "686" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='96b38460-486f-42f1-8251-da28d33c8749' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 96b38460-486f-42f1-8251-da28d33c8749 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777413 + reqtimestampmock: 2025-11-10T12:23:33.828318712Z + restimestampmock: 2025-11-10T12:23:33.828749659Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2180 +spec: + metadata: + connID: "686" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='96b38460-486f-42f1-8251-da28d33c8749' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777413 + reqtimestampmock: 2025-11-10T12:23:33.828928617Z + restimestampmock: 2025-11-10T12:23:33.829309714Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2181 +spec: + metadata: + connID: "686" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='96b38460-486f-42f1-8251-da28d33c8749' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777413 + reqtimestampmock: 2025-11-10T12:23:33.829435193Z + restimestampmock: 2025-11-10T12:23:33.829719691Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2182 +spec: + metadata: + connID: "686" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777413 + reqtimestampmock: 2025-11-10T12:23:33.82985144Z + restimestampmock: 2025-11-10T12:23:33.843760043Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2183 +spec: + metadata: + connID: "688" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [183, 41, 79, 85, 93, 24, 44, 15, 66, 13, 75, 4, 99, 53, 33, 164, 31, 61, 246, 116, 53, 38, 118, 69, 229, 65, 177, 87, 248, 101, 71, 87] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 525 + auth_plugin_data: [85, 13, 53, 80, 15, 100, 92, 33, 94, 86, 121, 51, 88, 23, 109, 88, 107, 121, 45, 111, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777415 + reqtimestampmock: 2025-11-10T12:23:35.796250795Z + restimestampmock: 2025-11-10T12:23:35.810581756Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2184 +spec: + metadata: + connID: "688" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777415 + reqtimestampmock: 2025-11-10T12:23:35.810935083Z + restimestampmock: 2025-11-10T12:23:35.81125735Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2185 +spec: + metadata: + connID: "688" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777415 + reqtimestampmock: 2025-11-10T12:23:35.811476798Z + restimestampmock: 2025-11-10T12:23:35.811708236Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2186 +spec: + metadata: + connID: "688" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777415 + reqtimestampmock: 2025-11-10T12:23:35.811880515Z + restimestampmock: 2025-11-10T12:23:35.812029175Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2187 +spec: + metadata: + connID: "688" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 333 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO users (id, username, email, password_hash, phone) VALUES ('7a640bce-243a-429e-907b-feb7b10dc1fb', 'nair_trivandrum', 'nair_trivandrum@example.in', 'scrypt:32768:8:1$UQdto6JWLaP5j8zI$a4ecd12db0536309f390a744677c2d1c69876e38c09618e425565cb952b6b61758fb73b850fcbe16eb1e3d9b3072705956a4827242ac0cdf39353a45c03e71e5', NULL) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777415 + reqtimestampmock: 2025-11-10T12:23:35.870263228Z + restimestampmock: 2025-11-10T12:23:35.870895483Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2188 +spec: + metadata: + connID: "688" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777415 + reqtimestampmock: 2025-11-10T12:23:35.871105081Z + restimestampmock: 2025-11-10T12:23:35.884701078Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2189 +spec: + metadata: + connID: "690" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [70, 1, 252, 42, 101, 84, 36, 18, 44, 166, 45, 235, 95, 68, 122, 35, 167, 108, 0, 140, 47, 238, 55, 40, 125, 181, 239, 129, 147, 21, 242, 29] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 526 + auth_plugin_data: [32, 49, 87, 121, 11, 1, 101, 64, 123, 1, 59, 9, 105, 92, 61, 28, 81, 68, 3, 23, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777416 + reqtimestampmock: 2025-11-10T12:23:36.751189171Z + restimestampmock: 2025-11-10T12:23:36.761482195Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2190 +spec: + metadata: + connID: "690" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777416 + reqtimestampmock: 2025-11-10T12:23:36.761779021Z + restimestampmock: 2025-11-10T12:23:36.762022369Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2191 +spec: + metadata: + connID: "690" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777416 + reqtimestampmock: 2025-11-10T12:23:36.762208128Z + restimestampmock: 2025-11-10T12:23:36.762417496Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2192 +spec: + metadata: + connID: "690" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777416 + reqtimestampmock: 2025-11-10T12:23:36.762572825Z + restimestampmock: 2025-11-10T12:23:36.762694124Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2193 +spec: + metadata: + connID: "690" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 88 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id, username, email, password_hash FROM users WHERE username='nair_trivandrum' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 4 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 55 + sequence_id: 3 + catalog: def + schema: user_db + table: users + org_table: users + name: username + org_name: username + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 49 + sequence_id: 4 + catalog: def + schema: user_db + table: users + org_table: users + name: email + org_name: email + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 20485 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + - header: + payload_length: 65 + sequence_id: 5 + catalog: def + schema: user_db + table: users + org_table: users + name: password_hash + org_name: password_hash + fixed_length: 12 + character_set: 255 + column_length: 1020 + type: 253 + flags: 4097 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 243 + sequence_id: 6 + values: + - type: 253 + name: id + value: 7a640bce-243a-429e-907b-feb7b10dc1fb + unsigned: false + - type: 253 + name: username + value: nair_trivandrum + unsigned: false + - type: 253 + name: email + value: nair_trivandrum@example.in + unsigned: false + - type: 253 + name: password_hash + value: scrypt:32768:8:1$UQdto6JWLaP5j8zI$a4ecd12db0536309f390a744677c2d1c69876e38c09618e425565cb952b6b61758fb73b850fcbe16eb1e3d9b3072705956a4827242ac0cdf39353a45c03e71e5 + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 7 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777416 + reqtimestampmock: 2025-11-10T12:23:36.763031941Z + restimestampmock: 2025-11-10T12:23:36.763686645Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2194 +spec: + metadata: + connID: "692" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [133, 199, 191, 186, 216, 21, 234, 161, 146, 109, 135, 107, 58, 190, 222, 56, 116, 172, 241, 250, 84, 77, 239, 163, 89, 10, 158, 46, 126, 188, 19, 197] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 527 + auth_plugin_data: [57, 13, 88, 52, 52, 86, 92, 105, 53, 116, 4, 110, 58, 60, 87, 55, 13, 108, 113, 37, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777417 + reqtimestampmock: 2025-11-10T12:23:37.532940242Z + restimestampmock: 2025-11-10T12:23:37.539856883Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2195 +spec: + metadata: + connID: "692" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777417 + reqtimestampmock: 2025-11-10T12:23:37.540089942Z + restimestampmock: 2025-11-10T12:23:37.5403213Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2196 +spec: + metadata: + connID: "692" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777417 + reqtimestampmock: 2025-11-10T12:23:37.540476018Z + restimestampmock: 2025-11-10T12:23:37.540643077Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2197 +spec: + metadata: + connID: "692" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777417 + reqtimestampmock: 2025-11-10T12:23:37.540780666Z + restimestampmock: 2025-11-10T12:23:37.540886955Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2198 +spec: + metadata: + connID: "692" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='7a640bce-243a-429e-907b-feb7b10dc1fb' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 7a640bce-243a-429e-907b-feb7b10dc1fb + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777417 + reqtimestampmock: 2025-11-10T12:23:37.541116573Z + restimestampmock: 2025-11-10T12:23:37.5414936Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2199 +spec: + metadata: + connID: "692" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 269 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: INSERT INTO addresses (id, user_id, line1, line2, city, state, postal_code, country, phone, is_default) VALUES ('d95b3363-0f2f-4d70-98ca-02d2b6576427','7a640bce-243a-429e-907b-feb7b10dc1fb','Pattom',NULL,'Thiruvananthapuram','Kerala','695004','IN','+919847112345',1) + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777417 + reqtimestampmock: 2025-11-10T12:23:37.541758067Z + restimestampmock: 2025-11-10T12:23:37.542491471Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2200 +spec: + metadata: + connID: "692" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 136 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: UPDATE addresses SET is_default=0 WHERE user_id='7a640bce-243a-429e-907b-feb7b10dc1fb' AND id<>'d95b3363-0f2f-4d70-98ca-02d2b6576427' + responses: + - header: + header: + payload_length: 48 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: '(Rows matched: 0 Changed: 0 Warnings: 0' + created: 1762777417 + reqtimestampmock: 2025-11-10T12:23:37.542786939Z + restimestampmock: 2025-11-10T12:23:37.543234525Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2201 +spec: + metadata: + connID: "692" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777417 + reqtimestampmock: 2025-11-10T12:23:37.543396384Z + restimestampmock: 2025-11-10T12:23:37.553726618Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2202 +spec: + metadata: + connID: "694" + requestOperation: HandshakeV10 + responseOperation: OK + type: config + requests: + - header: + header: + payload_length: 32 + sequence_id: 1 + packet_type: SSLRequest + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + - header: + header: + payload_length: 299 + sequence_id: 2 + packet_type: HandshakeResponse41 + message: + capability_flags: 431991437 + max_packet_size: 1073741824 + character_set: 255 + filler: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + username: user + auth_response: [233, 160, 213, 204, 15, 249, 211, 102, 115, 91, 140, 188, 254, 216, 101, 214, 204, 224, 247, 247, 254, 65, 230, 153, 18, 50, 104, 197, 117, 26, 99, 243] + database: user_db + auth_plugin_name: caching_sha2_password + connection_attributes: + _client_name: libmysql + _client_version: 9.0.0 + _connector_license: GPL-2.0 + _connector_name: mysql-connector-python + _connector_version: 9.0.0 + _os: Linux + _pid: "1060" + _platform: x86_64 + _source_host: d5823dc59ac7 + zstdcompressionlevel: 0 + responses: + - header: + header: + payload_length: 74 + sequence_id: 0 + packet_type: HandshakeV10 + message: + protocol_version: 10 + server_version: 8.0.43 + connection_id: 528 + auth_plugin_data: [109, 13, 112, 92, 114, 25, 33, 85, 98, 30, 105, 33, 56, 65, 47, 75, 54, 97, 35, 126, 0] + filler: 0 + capability_flags: 3758096383 + character_set: 255 + status_flags: 2 + auth_plugin_name: caching_sha2_password + - header: + header: + payload_length: 2 + sequence_id: 3 + packet_type: AuthMoreData + message: + status_tag: 1 + data: FastAuthSuccess + - header: + header: + payload_length: 19 + sequence_id: 4 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0\n\x01\b\auser_db" + created: 1762777418 + reqtimestampmock: 2025-11-10T12:23:38.321258408Z + restimestampmock: 2025-11-10T12:23:38.330629029Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2203 +spec: + metadata: + connID: "694" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 51 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SET NAMES 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci' + responses: + - header: + header: + payload_length: 107 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16386 + warnings: 0 + info: "\0b\0\x1D\x14character_set_client\autf8mb4\0!\x18character_set_connection\autf8mb4\0\x1E\x15character_set_results\autf8mb4" + created: 1762777418 + reqtimestampmock: 2025-11-10T12:23:38.330855997Z + restimestampmock: 2025-11-10T12:23:38.331092785Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2204 +spec: + metadata: + connID: "694" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 19 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: set autocommit=0 + responses: + - header: + header: + payload_length: 26 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 16384 + warnings: 0 + info: "\0\x11\0\x0F\nautocommit\x03OFF" + created: 1762777418 + reqtimestampmock: 2025-11-10T12:23:38.331263954Z + restimestampmock: 2025-11-10T12:23:38.331439343Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2205 +spec: + metadata: + connID: "694" + requestOperation: COM_PING + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 1 + sequence_id: 0 + packet_type: COM_PING + message: + command: 14 + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777418 + reqtimestampmock: 2025-11-10T12:23:38.331563642Z + restimestampmock: 2025-11-10T12:23:38.33173263Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2206 +spec: + metadata: + connID: "694" + requestOperation: COM_QUERY + responseOperation: TextResultSet + type: mocks + requests: + - header: + header: + payload_length: 71 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: SELECT id FROM users WHERE id='7a640bce-243a-429e-907b-feb7b10dc1fb' + responses: + - header: + header: + payload_length: 1 + sequence_id: 1 + packet_type: TextResultSet + message: + columnCount: 1 + columns: + - header: + payload_length: 43 + sequence_id: 2 + catalog: def + schema: user_db + table: users + org_table: users + name: id + org_name: id + fixed_length: 12 + character_set: 255 + column_length: 144 + type: 253 + flags: 20483 + decimals: 0 + filler: + - 0 + - 0 + defaultValue: "" + eofAfterColumns: [] + rows: + - header: + payload_length: 37 + sequence_id: 3 + values: + - type: 253 + name: id + value: 7a640bce-243a-429e-907b-feb7b10dc1fb + unsigned: false + FinalResponse: + data: + - 7 + - 0 + - 0 + - 4 + - 254 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + type: EOF + created: 1762777418 + reqtimestampmock: 2025-11-10T12:23:38.331916069Z + restimestampmock: 2025-11-10T12:23:38.332688742Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2207 +spec: + metadata: + connID: "694" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 77 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM addresses WHERE user_id='7a640bce-243a-429e-907b-feb7b10dc1fb' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777418 + reqtimestampmock: 2025-11-10T12:23:38.33289171Z + restimestampmock: 2025-11-10T12:23:38.333273758Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2208 +spec: + metadata: + connID: "694" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 68 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: DELETE FROM users WHERE id='7a640bce-243a-429e-907b-feb7b10dc1fb' + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 1 + last_insert_id: 0 + status_flags: 1 + warnings: 0 + info: "" + created: 1762777418 + reqtimestampmock: 2025-11-10T12:23:38.333481875Z + restimestampmock: 2025-11-10T12:23:38.333964812Z +--- +version: api.keploy.io/v1beta1 +kind: MySQL +name: mock-2209 +spec: + metadata: + connID: "694" + requestOperation: COM_QUERY + responseOperation: OK + type: mocks + requests: + - header: + header: + payload_length: 9 + sequence_id: 0 + packet_type: COM_QUERY + message: + command: 3 + parameter_count: 0 + null_bitmap: [] + new_params_bind_flag: 0 + parameters: [] + query: commit + responses: + - header: + header: + payload_length: 7 + sequence_id: 1 + packet_type: OK + message: + header: 0 + affected_rows: 0 + last_insert_id: 0 + status_flags: 0 + warnings: 0 + info: "" + created: 1762777418 + reqtimestampmock: 2025-11-10T12:23:38.334120079Z + restimestampmock: 2025-11-10T12:23:38.344725782Z diff --git a/user_service/keploy/atg/tests/test-1.yaml b/user_service/keploy/atg/tests/test-1.yaml new file mode 100755 index 0000000..0f14eed --- /dev/null +++ b/user_service/keploy/atg/tests/test-1.yaml @@ -0,0 +1,46 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-1 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/ + header: + Accept-Encoding: gzip + Content-Length: "0" + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:09:55.028848867Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:09:55 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:09:55.03060231Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776595 +curl: | + curl --request POST \ + --url http://localhost:8082/ \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-10.yaml b/user_service/keploy/atg/tests/test-10.yaml new file mode 100755 index 0000000..e3bc917 --- /dev/null +++ b/user_service/keploy/atg/tests/test-10.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-10 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "rohan_order_01", "email": "rohan.order.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:31.948931465Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:31.950170274Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776811 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"rohan_order_01\", \"email\": \"rohan.order.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-100.yaml b/user_service/keploy/atg/tests/test-100.yaml new file mode 100755 index 0000000..ad09ca3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-100.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-100 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:53.981937215Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:54 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTU0LCJleHAiOjE3NjUzNjg5NTR9.SrS7i9Uzj4uZ6fFXOhkMP60w1b6OZX5cwLir92NaKIU","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:54.045705305Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776954 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-101.yaml b/user_service/keploy/atg/tests/test-101.yaml new file mode 100755 index 0000000..84ca7a8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-101.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-101 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "94" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "twoitem_user_01", "email": "twoitem.user.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:54.837285831Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:54 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:54.83856152Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776954 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"twoitem_user_01\", \"email\": \"twoitem.user.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-102.yaml b/user_service/keploy/atg/tests/test-102.yaml new file mode 100755 index 0000000..b26f432 --- /dev/null +++ b/user_service/keploy/atg/tests/test-102.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-102 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:15:55.592191272Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:55 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:55.593317193Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776955 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-103.yaml b/user_service/keploy/atg/tests/test-103.yaml new file mode 100755 index 0000000..952ae6e --- /dev/null +++ b/user_service/keploy/atg/tests/test-103.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-103 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "2" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{}' + timestamp: 2025-11-10T12:16:00.119121745Z + resp: + status_code: 400 + header: + Content-Length: "36" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:16:00 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Missing required fields"} + status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:16:00.120313366Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776960 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{}" diff --git a/user_service/keploy/atg/tests/test-104.yaml b/user_service/keploy/atg/tests/test-104.yaml new file mode 100755 index 0000000..daa6683 --- /dev/null +++ b/user_service/keploy/atg/tests/test-104.yaml @@ -0,0 +1,46 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-104 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/ + header: + Accept-Encoding: gzip + Content-Length: "0" + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:16:34.897444462Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:16:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:16:34.898599511Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776994 +curl: | + curl --request POST \ + --url http://localhost:8082/ \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-105.yaml b/user_service/keploy/atg/tests/test-105.yaml new file mode 100755 index 0000000..2e4b19f --- /dev/null +++ b/user_service/keploy/atg/tests/test-105.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-105 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "88" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "priya_e2e_01", "email": "priya_e2e_01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:00.357918778Z + resp: + status_code: 201 + header: + Content-Length: "119" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:00 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"priya_e2e_01@example.in","id":"16be51aa-dcaf-4681-b125-ccffcc64e663","phone":null,"username":"priya_e2e_01"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:00.429226178Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777020 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"priya_e2e_01\", \"email\": \"priya_e2e_01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-106.yaml b/user_service/keploy/atg/tests/test-106.yaml new file mode 100755 index 0000000..61cecda --- /dev/null +++ b/user_service/keploy/atg/tests/test-106.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-106 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "priya_e2e_01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:01.139408008Z + resp: + status_code: 200 + header: + Content-Length: "340" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"priya_e2e_01@example.in","id":"16be51aa-dcaf-4681-b125-ccffcc64e663","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNmJlNTFhYS1kY2FmLTQ2ODEtYjEyNS1jY2ZmY2M2NGU2NjMiLCJ1c2VybmFtZSI6InByaXlhX2UyZV8wMSIsImlhdCI6MTc2Mjc3NzAyMSwiZXhwIjoxNzY1MzY5MDIxfQ.GtljdTRYooYtflXt0gptDBHy5DoN7GP03VM86CluguU","username":"priya_e2e_01"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:01.199819313Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777021 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"priya_e2e_01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-107.yaml b/user_service/keploy/atg/tests/test-107.yaml new file mode 100755 index 0000000..bdf54e8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-107.yaml @@ -0,0 +1,44 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-107 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/16be51aa-dcaf-4681-b125-ccffcc64e663 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:01.959224792Z + resp: + status_code: 200 + header: + Content-Length: "179" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"addresses":[],"created_at":"Mon, 10 Nov 2025 12:17:00 GMT","email":"priya_e2e_01@example.in","id":"16be51aa-dcaf-4681-b125-ccffcc64e663","phone":null,"username":"priya_e2e_01"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:01.974947577Z + objects: [] + assertions: + noise: + body.created_at: [] + header.Date: [] + created: 1762777021 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/16be51aa-dcaf-4681-b125-ccffcc64e663 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-108.yaml b/user_service/keploy/atg/tests/test-108.yaml new file mode 100755 index 0000000..41dec04 --- /dev/null +++ b/user_service/keploy/atg/tests/test-108.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-108 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/16be51aa-dcaf-4681-b125-ccffcc64e663/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "155" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "123 MG Road", "city": "Bengaluru", "state": "Karnataka", "postal_code": "560001", "country": "IN", "phone": "+919876543210", "is_default": true}' + timestamp: 2025-11-10T12:17:02.706459176Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:02 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"63f3fd41-0c2d-4609-9934-999884526bda"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:02.734902993Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777022 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/16be51aa-dcaf-4681-b125-ccffcc64e663/addresses \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"line1\": \"123 MG Road\", \"city\": \"Bengaluru\", \"state\": \"Karnataka\", \"postal_code\": \"560001\", \"country\": \"IN\", \"phone\": \"+919876543210\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-109.yaml b/user_service/keploy/atg/tests/test-109.yaml new file mode 100755 index 0000000..f88651e --- /dev/null +++ b/user_service/keploy/atg/tests/test-109.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-109 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/16be51aa-dcaf-4681-b125-ccffcc64e663/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:03.519930355Z + resp: + status_code: 200 + header: + Content-Length: "199" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:03 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + [{"city":"Bengaluru","country":"IN","id":"63f3fd41-0c2d-4609-9934-999884526bda","is_default":1,"line1":"123 MG Road","line2":null,"phone":"+919876543210","postal_code":"560001","state":"Karnataka"}] + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:03.535535891Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777023 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/16be51aa-dcaf-4681-b125-ccffcc64e663/addresses \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-11.yaml b/user_service/keploy/atg/tests/test-11.yaml new file mode 100755 index 0000000..3ed7e9f --- /dev/null +++ b/user_service/keploy/atg/tests/test-11.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-11 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:13:32.749899449Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:13:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:32.752570196Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776812 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-110.yaml b/user_service/keploy/atg/tests/test-110.yaml new file mode 100755 index 0000000..ef06167 --- /dev/null +++ b/user_service/keploy/atg/tests/test-110.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-110 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/16be51aa-dcaf-4681-b125-ccffcc64e663 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:04.278237366Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:04 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:04.304933448Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777024 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/16be51aa-dcaf-4681-b125-ccffcc64e663 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-111.yaml b/user_service/keploy/atg/tests/test-111.yaml new file mode 100755 index 0000000..638b1db --- /dev/null +++ b/user_service/keploy/atg/tests/test-111.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-111 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "rohan_order_01", "email": "rohan_order_01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:06.372833352Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"rohan_order_01@example.in","id":"18ae4cc0-1e25-49fd-bb2a-1002466783c4","phone":null,"username":"rohan_order_01"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:06.450708076Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777026 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"rohan_order_01\", \"email\": \"rohan_order_01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-112.yaml b/user_service/keploy/atg/tests/test-112.yaml new file mode 100755 index 0000000..e727c52 --- /dev/null +++ b/user_service/keploy/atg/tests/test-112.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-112 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "rohan_order_01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:07.20671563Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"rohan_order_01@example.in","id":"18ae4cc0-1e25-49fd-bb2a-1002466783c4","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxOGFlNGNjMC0xZTI1LTQ5ZmQtYmIyYS0xMDAyNDY2NzgzYzQiLCJ1c2VybmFtZSI6InJvaGFuX29yZGVyXzAxIiwiaWF0IjoxNzYyNzc3MDI3LCJleHAiOjE3NjUzNjkwMjd9.5axYkZCFd0fCeDqKZXru73f9zKMYx7WHUh7T-5ii1m0","username":"rohan_order_01"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:07.266904745Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777027 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"rohan_order_01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-113.yaml b/user_service/keploy/atg/tests/test-113.yaml new file mode 100755 index 0000000..619ca26 --- /dev/null +++ b/user_service/keploy/atg/tests/test-113.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-113 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/18ae4cc0-1e25-49fd-bb2a-1002466783c4/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "157" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "45 BTM Layout", "city": "Bengaluru", "state": "Karnataka", "postal_code": "560076", "country": "IN", "phone": "+918765432109", "is_default": true}' + timestamp: 2025-11-10T12:17:07.943187389Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"e19a385a-5d5c-4e5c-8022-32988d92a112"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:07.965502208Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777027 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/18ae4cc0-1e25-49fd-bb2a-1002466783c4/addresses \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"line1\": \"45 BTM Layout\", \"city\": \"Bengaluru\", \"state\": \"Karnataka\", \"postal_code\": \"560076\", \"country\": \"IN\", \"phone\": \"+918765432109\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-114.yaml b/user_service/keploy/atg/tests/test-114.yaml new file mode 100755 index 0000000..c5c4a95 --- /dev/null +++ b/user_service/keploy/atg/tests/test-114.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-114 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:08.784428205Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:17:08 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:08.785577955Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777028 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-115.yaml b/user_service/keploy/atg/tests/test-115.yaml new file mode 100755 index 0000000..44dbba5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-115.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-115 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/18ae4cc0-1e25-49fd-bb2a-1002466783c4 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:09.527231982Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:09 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:09.54982385Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777029 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/18ae4cc0-1e25-49fd-bb2a-1002466783c4 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-116.yaml b/user_service/keploy/atg/tests/test-116.yaml new file mode 100755 index 0000000..e343ee6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-116.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-116 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "anika_login_fail", "email": "anika_login_fail@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:12.697614286Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"anika_login_fail@example.in","id":"46442b8f-133b-4c4a-beca-096cc6a034cd","phone":null,"username":"anika_login_fail"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:12.767811476Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777032 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"anika_login_fail\", \"email\": \"anika_login_fail@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-117.yaml b/user_service/keploy/atg/tests/test-117.yaml new file mode 100755 index 0000000..b569c80 --- /dev/null +++ b/user_service/keploy/atg/tests/test-117.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-117 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "61" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "anika_login_fail", "password": "wrongpassword"}' + timestamp: 2025-11-10T12:17:13.502194958Z + resp: + status_code: 401 + header: + Content-Length: "32" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:13 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Invalid credentials"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:13.560709878Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777033 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"anika_login_fail\", \"password\": \"wrongpassword\"}" diff --git a/user_service/keploy/atg/tests/test-118.yaml b/user_service/keploy/atg/tests/test-118.yaml new file mode 100755 index 0000000..7a1da1f --- /dev/null +++ b/user_service/keploy/atg/tests/test-118.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-118 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "anika_login_fail", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:14.226717654Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:14 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"anika_login_fail@example.in","id":"46442b8f-133b-4c4a-beca-096cc6a034cd","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0NjQ0MmI4Zi0xMzNiLTRjNGEtYmVjYS0wOTZjYzZhMDM0Y2QiLCJ1c2VybmFtZSI6ImFuaWthX2xvZ2luX2ZhaWwiLCJpYXQiOjE3NjI3NzcwMzQsImV4cCI6MTc2NTM2OTAzNH0.ikqJsAMQAl1iAGwOhMhY_sYpI_cbiIDvT6s52jAu6Lc","username":"anika_login_fail"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:14.284915607Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777034 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"anika_login_fail\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-119.yaml b/user_service/keploy/atg/tests/test-119.yaml new file mode 100755 index 0000000..531b2a3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-119.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-119 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/46442b8f-133b-4c4a-beca-096cc6a034cd + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:15.044120187Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:15 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:15.069848368Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777035 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/46442b8f-133b-4c4a-beca-096cc6a034cd \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-12.yaml b/user_service/keploy/atg/tests/test-12.yaml new file mode 100755 index 0000000..da4f3e0 --- /dev/null +++ b/user_service/keploy/atg/tests/test-12.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-12 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:37.139581329Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:37 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODE3LCJleHAiOjE3NjUzNjg4MTd9.mOf2rllkQ-whHamjVZBgrnUYSMovGDZ__S699IN23UI","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:37.199978145Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776817 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-120.yaml b/user_service/keploy/atg/tests/test-120.yaml new file mode 100755 index 0000000..4f66fdf --- /dev/null +++ b/user_service/keploy/atg/tests/test-120.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-120 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "58" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "ghost_user_999", "password": "somepassword"}' + timestamp: 2025-11-10T12:17:15.884265719Z + resp: + status_code: 401 + header: + Content-Length: "32" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:15 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Invalid credentials"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:15.895626153Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777035 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"ghost_user_999\", \"password\": \"somepassword\"}" diff --git a/user_service/keploy/atg/tests/test-121.yaml b/user_service/keploy/atg/tests/test-121.yaml new file mode 100755 index 0000000..7dcc962 --- /dev/null +++ b/user_service/keploy/atg/tests/test-121.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-121 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "vikram_duplicate", "email": "vikram_duplicate@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:16.70933608Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:16 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"vikram_duplicate@example.in","id":"5c02e676-4397-466c-ad59-7a47f2026a13","phone":null,"username":"vikram_duplicate"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:16.780962748Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777036 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"vikram_duplicate\", \"email\": \"vikram_duplicate@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-122.yaml b/user_service/keploy/atg/tests/test-122.yaml new file mode 100755 index 0000000..13f6a1b --- /dev/null +++ b/user_service/keploy/atg/tests/test-122.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-122 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "93" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "vikram_duplicate", "email": "another_email@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:17.454916629Z + resp: + status_code: 500 + header: + Content-Length: "118" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:17 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Failed to create user: 1062 (23000): Duplicate entry 'vikram_duplicate' for key 'users.uq_users_username'"} + status_message: Internal Server Error + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:17.535847368Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777037 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"vikram_duplicate\", \"email\": \"another_email@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-123.yaml b/user_service/keploy/atg/tests/test-123.yaml new file mode 100755 index 0000000..fff4b18 --- /dev/null +++ b/user_service/keploy/atg/tests/test-123.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-123 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "vikram_duplicate", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:18.225119688Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:18 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"vikram_duplicate@example.in","id":"5c02e676-4397-466c-ad59-7a47f2026a13","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YzAyZTY3Ni00Mzk3LTQ2NmMtYWQ1OS03YTQ3ZjIwMjZhMTMiLCJ1c2VybmFtZSI6InZpa3JhbV9kdXBsaWNhdGUiLCJpYXQiOjE3NjI3NzcwMzgsImV4cCI6MTc2NTM2OTAzOH0.wky89p1hkvWXH8WPGE0lQ1URShBY7dqApr2ptG9lEaQ","username":"vikram_duplicate"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:18.285681911Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777038 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"vikram_duplicate\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-124.yaml b/user_service/keploy/atg/tests/test-124.yaml new file mode 100755 index 0000000..73df1e3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-124.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-124 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5c02e676-4397-466c-ad59-7a47f2026a13 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:18.977899877Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:18 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:18.999498563Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777038 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/5c02e676-4397-466c-ad59-7a47f2026a13 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-125.yaml b/user_service/keploy/atg/tests/test-125.yaml new file mode 100755 index 0000000..eb6c10b --- /dev/null +++ b/user_service/keploy/atg/tests/test-125.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-125 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "sunita_email", "email": "sunita_duplicate@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:21.447574535Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"sunita_duplicate@example.in","id":"ce7a9d8f-2c45-4db1-a930-b46dcc8ffb2c","phone":null,"username":"sunita_email"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:21.51494935Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777041 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"sunita_email\", \"email\": \"sunita_duplicate@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-126.yaml b/user_service/keploy/atg/tests/test-126.yaml new file mode 100755 index 0000000..d54e66c --- /dev/null +++ b/user_service/keploy/atg/tests/test-126.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-126 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "94" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "another_sunita", "email": "sunita_duplicate@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:22.195979682Z + resp: + status_code: 500 + header: + Content-Length: "126" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:22 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Failed to create user: 1062 (23000): Duplicate entry 'sunita_duplicate@example.in' for key 'users.uq_users_email'"} + status_message: Internal Server Error + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:22.262915042Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777042 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"another_sunita\", \"email\": \"sunita_duplicate@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-127.yaml b/user_service/keploy/atg/tests/test-127.yaml new file mode 100755 index 0000000..06c4d5d --- /dev/null +++ b/user_service/keploy/atg/tests/test-127.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-127 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "sunita_email", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:22.956504738Z + resp: + status_code: 200 + header: + Content-Length: "344" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"sunita_duplicate@example.in","id":"ce7a9d8f-2c45-4db1-a930-b46dcc8ffb2c","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjZTdhOWQ4Zi0yYzQ1LTRkYjEtYTkzMC1iNDZkY2M4ZmZiMmMiLCJ1c2VybmFtZSI6InN1bml0YV9lbWFpbCIsImlhdCI6MTc2Mjc3NzA0MywiZXhwIjoxNzY1MzY5MDQzfQ.eNS90d_UGAhB35tZqgFaaJMSy3aeGffMgFx1lMU95Is","username":"sunita_email"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:23.016951702Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777043 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"sunita_email\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-128.yaml b/user_service/keploy/atg/tests/test-128.yaml new file mode 100755 index 0000000..bc11842 --- /dev/null +++ b/user_service/keploy/atg/tests/test-128.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-128 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/ce7a9d8f-2c45-4db1-a930-b46dcc8ffb2c + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:23.700931761Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:23.724809687Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777043 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/ce7a9d8f-2c45-4db1-a930-b46dcc8ffb2c \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-129.yaml b/user_service/keploy/atg/tests/test-129.yaml new file mode 100755 index 0000000..a4db7b1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-129.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-129 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "86" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "noauth_user", "email": "noauth_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:26.276485294Z + resp: + status_code: 201 + header: + Content-Length: "117" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"noauth_user@example.in","id":"a7ef1d19-4a3a-4a51-ab42-038fd59367b3","phone":null,"username":"noauth_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:26.347735028Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777046 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"noauth_user\", \"email\": \"noauth_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-13.yaml b/user_service/keploy/atg/tests/test-13.yaml new file mode 100755 index 0000000..166fdbb --- /dev/null +++ b/user_service/keploy/atg/tests/test-13.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-13 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "sunita_cancel_01", "email": "sunita.cancel.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:37.984136937Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:37 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:37.985491695Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776817 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"sunita_cancel_01\", \"email\": \"sunita.cancel.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-130.yaml b/user_service/keploy/atg/tests/test-130.yaml new file mode 100755 index 0000000..01d97e8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-130.yaml @@ -0,0 +1,44 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-130 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a7ef1d19-4a3a-4a51-ab42-038fd59367b3 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:27.118734367Z + resp: + status_code: 200 + header: + Content-Length: "177" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:27 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"addresses":[],"created_at":"Mon, 10 Nov 2025 12:17:26 GMT","email":"noauth_user@example.in","id":"a7ef1d19-4a3a-4a51-ab42-038fd59367b3","phone":null,"username":"noauth_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:27.131468538Z + objects: [] + assertions: + noise: + body.created_at: [] + header.Date: [] + created: 1762777047 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/a7ef1d19-4a3a-4a51-ab42-038fd59367b3 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-131.yaml b/user_service/keploy/atg/tests/test-131.yaml new file mode 100755 index 0000000..dc957f9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-131.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-131 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "51" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "noauth_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:27.875669016Z + resp: + status_code: 200 + header: + Content-Length: "336" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:27 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"noauth_user@example.in","id":"a7ef1d19-4a3a-4a51-ab42-038fd59367b3","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhN2VmMWQxOS00YTNhLTRhNTEtYWI0Mi0wMzhmZDU5MzY3YjMiLCJ1c2VybmFtZSI6Im5vYXV0aF91c2VyIiwiaWF0IjoxNzYyNzc3MDQ3LCJleHAiOjE3NjUzNjkwNDd9.MA3HZz1UAdj5dY6U1JBLKFnBl8MZfqJxK9-Gb4ri_tE","username":"noauth_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:27.9373012Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777047 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"noauth_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-132.yaml b/user_service/keploy/atg/tests/test-132.yaml new file mode 100755 index 0000000..adb4d37 --- /dev/null +++ b/user_service/keploy/atg/tests/test-132.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-132 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a7ef1d19-4a3a-4a51-ab42-038fd59367b3 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:28.696590908Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:28 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:28.720760134Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777048 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/a7ef1d19-4a3a-4a51-ab42-038fd59367b3 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-133.yaml b/user_service/keploy/atg/tests/test-133.yaml new file mode 100755 index 0000000..0f909b2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-133.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-133 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "100" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_auth", "email": "temp_user_for_auth@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:30.507376598Z + resp: + status_code: 201 + header: + Content-Length: "131" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:30 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_auth@example.in","id":"753085ca-8366-43b0-bfd1-3c017574933e","phone":null,"username":"temp_user_for_auth"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:30.577034265Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777050 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"temp_user_for_auth\", \"email\": \"temp_user_for_auth@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-134.yaml b/user_service/keploy/atg/tests/test-134.yaml new file mode 100755 index 0000000..ffad845 --- /dev/null +++ b/user_service/keploy/atg/tests/test-134.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-134 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "58" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_auth", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:31.322468425Z + resp: + status_code: 200 + header: + Content-Length: "360" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_auth@example.in","id":"753085ca-8366-43b0-bfd1-3c017574933e","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NTMwODVjYS04MzY2LTQzYjAtYmZkMS0zYzAxNzU3NDkzM2UiLCJ1c2VybmFtZSI6InRlbXBfdXNlcl9mb3JfYXV0aCIsImlhdCI6MTc2Mjc3NzA1MSwiZXhwIjoxNzY1MzY5MDUxfQ.HRpI67V1WHqlNK7d_G9EF0pWqSGGeNfoC4O_qcE61wI","username":"temp_user_for_auth"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:31.382996429Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777051 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"temp_user_for_auth\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-135.yaml b/user_service/keploy/atg/tests/test-135.yaml new file mode 100755 index 0000000..cab86ca --- /dev/null +++ b/user_service/keploy/atg/tests/test-135.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-135 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/00000000-0000-0000-0000-000000000000 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:32.078686353Z + resp: + status_code: 404 + header: + Content-Length: "27" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"User not found"} + status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:32.089647369Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777052 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/00000000-0000-0000-0000-000000000000 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-136.yaml b/user_service/keploy/atg/tests/test-136.yaml new file mode 100755 index 0000000..881c032 --- /dev/null +++ b/user_service/keploy/atg/tests/test-136.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-136 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/753085ca-8366-43b0-bfd1-3c017574933e + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:32.890599438Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:32.915646754Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777052 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/753085ca-8366-43b0-bfd1-3c017574933e \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-137.yaml b/user_service/keploy/atg/tests/test-137.yaml new file mode 100755 index 0000000..1027e09 --- /dev/null +++ b/user_service/keploy/atg/tests/test-137.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-137 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "108" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_auth_del", "email": "temp_user_for_auth_del@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:33.716700551Z + resp: + status_code: 201 + header: + Content-Length: "139" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:33 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_auth_del@example.in","id":"3b923b66-00fd-4468-ba80-4925b266fe45","phone":null,"username":"temp_user_for_auth_del"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:33.786861873Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777053 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"temp_user_for_auth_del\", \"email\": \"temp_user_for_auth_del@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-138.yaml b/user_service/keploy/atg/tests/test-138.yaml new file mode 100755 index 0000000..8877440 --- /dev/null +++ b/user_service/keploy/atg/tests/test-138.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-138 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "62" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_auth_del", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:34.483941547Z + resp: + status_code: 200 + header: + Content-Length: "373" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_auth_del@example.in","id":"3b923b66-00fd-4468-ba80-4925b266fe45","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzYjkyM2I2Ni0wMGZkLTQ0NjgtYmE4MC00OTI1YjI2NmZlNDUiLCJ1c2VybmFtZSI6InRlbXBfdXNlcl9mb3JfYXV0aF9kZWwiLCJpYXQiOjE3NjI3NzcwNTQsImV4cCI6MTc2NTM2OTA1NH0.zieLjFbvo6GNNKwLWx-83dCXMzyDDBi_5jPFSiGQNbI","username":"temp_user_for_auth_del"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:34.542318139Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777054 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"temp_user_for_auth_del\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-139.yaml b/user_service/keploy/atg/tests/test-139.yaml new file mode 100755 index 0000000..41531f6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-139.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-139 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/00000000-0000-0000-0000-000000000000 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:35.221829673Z + resp: + status_code: 404 + header: + Content-Length: "27" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:35 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"User not found"} + status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:35.233390285Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777055 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/00000000-0000-0000-0000-000000000000 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-14.yaml b/user_service/keploy/atg/tests/test-14.yaml new file mode 100755 index 0000000..381dbd1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-14.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-14 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:13:38.758277337Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:13:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:38.759351128Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776818 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-140.yaml b/user_service/keploy/atg/tests/test-140.yaml new file mode 100755 index 0000000..236afd0 --- /dev/null +++ b/user_service/keploy/atg/tests/test-140.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-140 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/3b923b66-00fd-4468-ba80-4925b266fe45 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:36.020768529Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:36 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:36.046316572Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777056 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/3b923b66-00fd-4468-ba80-4925b266fe45 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-141.yaml b/user_service/keploy/atg/tests/test-141.yaml new file mode 100755 index 0000000..5f552ba --- /dev/null +++ b/user_service/keploy/atg/tests/test-141.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-141 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "product_tester", "email": "product_tester@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:36.866121831Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:36 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"product_tester@example.in","id":"76e07df1-2f87-4705-89fe-c61e78001e39","phone":null,"username":"product_tester"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:36.937933889Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777056 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"product_tester\", \"email\": \"product_tester@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-142.yaml b/user_service/keploy/atg/tests/test-142.yaml new file mode 100755 index 0000000..66628f0 --- /dev/null +++ b/user_service/keploy/atg/tests/test-142.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-142 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "product_tester", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:37.611576874Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:37 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"product_tester@example.in","id":"76e07df1-2f87-4705-89fe-c61e78001e39","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NmUwN2RmMS0yZjg3LTQ3MDUtODlmZS1jNjFlNzgwMDFlMzkiLCJ1c2VybmFtZSI6InByb2R1Y3RfdGVzdGVyIiwiaWF0IjoxNzYyNzc3MDU3LCJleHAiOjE3NjUzNjkwNTd9.of6ew7hqU7nHbEglnetGD47LJLjMFOWII7KERIjtOyk","username":"product_tester"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:37.67436127Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777057 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"product_tester\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-143.yaml b/user_service/keploy/atg/tests/test-143.yaml new file mode 100755 index 0000000..c0feb3d --- /dev/null +++ b/user_service/keploy/atg/tests/test-143.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-143 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:38.354616057Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:17:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:38.355669638Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777058 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-144.yaml b/user_service/keploy/atg/tests/test-144.yaml new file mode 100755 index 0000000..f8baecc --- /dev/null +++ b/user_service/keploy/atg/tests/test-144.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-144 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/76e07df1-2f87-4705-89fe-c61e78001e39 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:39.179939762Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:39.205138647Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777059 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/76e07df1-2f87-4705-89fe-c61e78001e39 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-145.yaml b/user_service/keploy/atg/tests/test-145.yaml new file mode 100755 index 0000000..bf4ae00 --- /dev/null +++ b/user_service/keploy/atg/tests/test-145.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-145 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "98" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "inventory_manager", "email": "inventory_manager@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:41.537418625Z + resp: + status_code: 201 + header: + Content-Length: "129" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:41 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"inventory_manager@example.in","id":"c4f415bd-b650-453c-852e-592ef525edac","phone":null,"username":"inventory_manager"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:41.608904766Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777061 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"inventory_manager\", \"email\": \"inventory_manager@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-146.yaml b/user_service/keploy/atg/tests/test-146.yaml new file mode 100755 index 0000000..67a43c1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-146.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-146 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "57" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "inventory_manager", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:42.326697587Z + resp: + status_code: 200 + header: + Content-Length: "356" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:42 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"inventory_manager@example.in","id":"c4f415bd-b650-453c-852e-592ef525edac","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjNGY0MTViZC1iNjUwLTQ1M2MtODUyZS01OTJlZjUyNWVkYWMiLCJ1c2VybmFtZSI6ImludmVudG9yeV9tYW5hZ2VyIiwiaWF0IjoxNzYyNzc3MDYyLCJleHAiOjE3NjUzNjkwNjJ9.rnC_wZNIFX5D_5_H63nnSbInu-Xmofb2xPcb8SbA878","username":"inventory_manager"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:42.387531958Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777062 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"inventory_manager\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-147.yaml b/user_service/keploy/atg/tests/test-147.yaml new file mode 100755 index 0000000..c289627 --- /dev/null +++ b/user_service/keploy/atg/tests/test-147.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-147 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:43.075838482Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:17:43 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:43.077312519Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777063 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-148.yaml b/user_service/keploy/atg/tests/test-148.yaml new file mode 100755 index 0000000..d28b89c --- /dev/null +++ b/user_service/keploy/atg/tests/test-148.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-148 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/c4f415bd-b650-453c-852e-592ef525edac + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:43.911131654Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:43 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:43.936673276Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777063 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/c4f415bd-b650-453c-852e-592ef525edac \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-149.yaml b/user_service/keploy/atg/tests/test-149.yaml new file mode 100755 index 0000000..c79fb05 --- /dev/null +++ b/user_service/keploy/atg/tests/test-149.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-149 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "stock_checker", "email": "stock_checker@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:46.515607287Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:46 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"stock_checker@example.in","id":"79211abf-4c4b-4339-a13c-ee6106a32e6b","phone":null,"username":"stock_checker"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:46.589865146Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777066 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"stock_checker\", \"email\": \"stock_checker@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-15.yaml b/user_service/keploy/atg/tests/test-15.yaml new file mode 100755 index 0000000..5cfc8ed --- /dev/null +++ b/user_service/keploy/atg/tests/test-15.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-15 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "50" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "wrongpassword"}' + timestamp: 2025-11-10T12:13:43.213689215Z + resp: + status_code: 401 + header: + Content-Length: "32" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:43 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Invalid credentials"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:43.273496056Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776823 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"alice\", \"password\": \"wrongpassword\"}" diff --git a/user_service/keploy/atg/tests/test-150.yaml b/user_service/keploy/atg/tests/test-150.yaml new file mode 100755 index 0000000..81c9f8b --- /dev/null +++ b/user_service/keploy/atg/tests/test-150.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-150 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "stock_checker", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:47.293651749Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"stock_checker@example.in","id":"79211abf-4c4b-4339-a13c-ee6106a32e6b","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3OTIxMWFiZi00YzRiLTQzMzktYTEzYy1lZTYxMDZhMzJlNmIiLCJ1c2VybmFtZSI6InN0b2NrX2NoZWNrZXIiLCJpYXQiOjE3NjI3NzcwNjcsImV4cCI6MTc2NTM2OTA2N30.iiW0V78uukvqFuy4GgIhXYvQjfsSaB884tOLq-qpiK4","username":"stock_checker"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:47.353921136Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777067 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"stock_checker\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-151.yaml b/user_service/keploy/atg/tests/test-151.yaml new file mode 100755 index 0000000..81b3f12 --- /dev/null +++ b/user_service/keploy/atg/tests/test-151.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-151 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:48.094213369Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:17:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:48.09539259Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777068 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-152.yaml b/user_service/keploy/atg/tests/test-152.yaml new file mode 100755 index 0000000..098c50d --- /dev/null +++ b/user_service/keploy/atg/tests/test-152.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-152 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/79211abf-4c4b-4339-a13c-ee6106a32e6b + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:48.836616247Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:48.860531212Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777068 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/79211abf-4c4b-4339-a13c-ee6106a32e6b \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-153.yaml b/user_service/keploy/atg/tests/test-153.yaml new file mode 100755 index 0000000..9a271d3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-153.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-153 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "deepa_idem_01", "email": "deepa_idem_01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:51.066074198Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"deepa_idem_01@example.in","id":"bb49cd03-583c-4f27-a003-e2050840ab3b","phone":null,"username":"deepa_idem_01"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:51.141463747Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777071 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"deepa_idem_01\", \"email\": \"deepa_idem_01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-154.yaml b/user_service/keploy/atg/tests/test-154.yaml new file mode 100755 index 0000000..b302019 --- /dev/null +++ b/user_service/keploy/atg/tests/test-154.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-154 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "deepa_idem_01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:51.97960266Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:52 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"deepa_idem_01@example.in","id":"bb49cd03-583c-4f27-a003-e2050840ab3b","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiYjQ5Y2QwMy01ODNjLTRmMjctYTAwMy1lMjA1MDg0MGFiM2IiLCJ1c2VybmFtZSI6ImRlZXBhX2lkZW1fMDEiLCJpYXQiOjE3NjI3NzcwNzIsImV4cCI6MTc2NTM2OTA3Mn0.GdcBF1NQm5J90pqOt0vZP9oKBaxRBmHH4GSp6E2MzIk","username":"deepa_idem_01"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:52.039787789Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777072 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"deepa_idem_01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-155.yaml b/user_service/keploy/atg/tests/test-155.yaml new file mode 100755 index 0000000..de725e9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-155.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-155 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/bb49cd03-583c-4f27-a003-e2050840ab3b/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "154" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "22 Connaught Place", "city": "Delhi", "state": "Delhi", "postal_code": "110001", "country": "IN", "phone": "+919812345678", "is_default": true}' + timestamp: 2025-11-10T12:17:52.813289763Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:52 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"8ce1b5f8-3b4d-427f-8bf5-e9703ff9df27"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:52.836177168Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777072 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/bb49cd03-583c-4f27-a003-e2050840ab3b/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"22 Connaught Place\", \"city\": \"Delhi\", \"state\": \"Delhi\", \"postal_code\": \"110001\", \"country\": \"IN\", \"phone\": \"+919812345678\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-156.yaml b/user_service/keploy/atg/tests/test-156.yaml new file mode 100755 index 0000000..add96fb --- /dev/null +++ b/user_service/keploy/atg/tests/test-156.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-156 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:53.613936767Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:17:53 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:53.615427775Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777073 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-157.yaml b/user_service/keploy/atg/tests/test-157.yaml new file mode 100755 index 0000000..8df941a --- /dev/null +++ b/user_service/keploy/atg/tests/test-157.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-157 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/bb49cd03-583c-4f27-a003-e2050840ab3b + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:54.442575053Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:54 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:54.467109655Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777074 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/bb49cd03-583c-4f27-a003-e2050840ab3b \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-158.yaml b/user_service/keploy/atg/tests/test-158.yaml new file mode 100755 index 0000000..aa88d0e --- /dev/null +++ b/user_service/keploy/atg/tests/test-158.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-158 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "cancel_user_01", "email": "cancel_user_01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:57.17250484Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:57 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"cancel_user_01@example.in","id":"9a082290-3fed-4a09-ac50-d25e81f0222c","phone":null,"username":"cancel_user_01"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:57.246018485Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777077 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"cancel_user_01\", \"email\": \"cancel_user_01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-159.yaml b/user_service/keploy/atg/tests/test-159.yaml new file mode 100755 index 0000000..bb2dafc --- /dev/null +++ b/user_service/keploy/atg/tests/test-159.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-159 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "cancel_user_01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:17:58.099168096Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:58 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"cancel_user_01@example.in","id":"9a082290-3fed-4a09-ac50-d25e81f0222c","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5YTA4MjI5MC0zZmVkLTRhMDktYWM1MC1kMjVlODFmMDIyMmMiLCJ1c2VybmFtZSI6ImNhbmNlbF91c2VyXzAxIiwiaWF0IjoxNzYyNzc3MDc4LCJleHAiOjE3NjUzNjkwNzh9.ZpgHuOS2yh9KReMjMbdBMa1jhhsumdcXbzsLApWGlSw","username":"cancel_user_01"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:58.161631846Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777078 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"cancel_user_01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-16.yaml b/user_service/keploy/atg/tests/test-16.yaml new file mode 100755 index 0000000..6dcbe00 --- /dev/null +++ b/user_service/keploy/atg/tests/test-16.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-16 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "70" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "nonexistentuser_{{$randomInt}}", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:44.065937095Z + resp: + status_code: 401 + header: + Content-Length: "32" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:44 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Invalid credentials"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:44.078421004Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776824 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"nonexistentuser_{{$randomInt}}\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-160.yaml b/user_service/keploy/atg/tests/test-160.yaml new file mode 100755 index 0000000..b3c4528 --- /dev/null +++ b/user_service/keploy/atg/tests/test-160.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-160 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/9a082290-3fed-4a09-ac50-d25e81f0222c/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "151" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "33 FC Road", "city": "Pune", "state": "Maharashtra", "postal_code": "411004", "country": "IN", "phone": "+919876501234", "is_default": true}' + timestamp: 2025-11-10T12:17:58.961045683Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:17:58 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"942d93ed-78d4-4e03-9d60-4931b6ab2e88"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:58.985482115Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777078 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/9a082290-3fed-4a09-ac50-d25e81f0222c/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"33 FC Road\", \"city\": \"Pune\", \"state\": \"Maharashtra\", \"postal_code\": \"411004\", \"country\": \"IN\", \"phone\": \"+919876501234\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-161.yaml b/user_service/keploy/atg/tests/test-161.yaml new file mode 100755 index 0000000..847c5ba --- /dev/null +++ b/user_service/keploy/atg/tests/test-161.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-161 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:17:59.923925112Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:17:59 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:17:59.925041393Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777079 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-162.yaml b/user_service/keploy/atg/tests/test-162.yaml new file mode 100755 index 0000000..bf9f4cd --- /dev/null +++ b/user_service/keploy/atg/tests/test-162.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-162 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/9a082290-3fed-4a09-ac50-d25e81f0222c + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:00.762099331Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:00 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:00.790519339Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777080 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/9a082290-3fed-4a09-ac50-d25e81f0222c \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-163.yaml b/user_service/keploy/atg/tests/test-163.yaml new file mode 100755 index 0000000..e6ff5a6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-163.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-163 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "paid_cancel_fail", "email": "paid_cancel_fail@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:03.417369327Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:03 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"paid_cancel_fail@example.in","id":"be716f2f-f071-42fe-8a85-a92c8a1c1b68","phone":null,"username":"paid_cancel_fail"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:03.488399143Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777083 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"paid_cancel_fail\", \"email\": \"paid_cancel_fail@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-164.yaml b/user_service/keploy/atg/tests/test-164.yaml new file mode 100755 index 0000000..3413be3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-164.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-164 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "paid_cancel_fail", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:04.632974233Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:04 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"paid_cancel_fail@example.in","id":"be716f2f-f071-42fe-8a85-a92c8a1c1b68","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiZTcxNmYyZi1mMDcxLTQyZmUtOGE4NS1hOTJjOGExYzFiNjgiLCJ1c2VybmFtZSI6InBhaWRfY2FuY2VsX2ZhaWwiLCJpYXQiOjE3NjI3NzcwODQsImV4cCI6MTc2NTM2OTA4NH0.n8FD-NR_aXgOXwgLFQ_h_Z6cdELd4z4ZtRFF-VamyiE","username":"paid_cancel_fail"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:04.692622396Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777084 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"paid_cancel_fail\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-165.yaml b/user_service/keploy/atg/tests/test-165.yaml new file mode 100755 index 0000000..106d637 --- /dev/null +++ b/user_service/keploy/atg/tests/test-165.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-165 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/be716f2f-f071-42fe-8a85-a92c8a1c1b68/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "158" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "55 Park Street", "city": "Kolkata", "state": "West Bengal", "postal_code": "700016", "country": "IN", "phone": "+919000011111", "is_default": true}' + timestamp: 2025-11-10T12:18:05.436254221Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:05 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"7aac94f7-3f51-4ad1-b262-d2acdd7429f2"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:05.461209379Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777085 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/be716f2f-f071-42fe-8a85-a92c8a1c1b68/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"55 Park Street\", \"city\": \"Kolkata\", \"state\": \"West Bengal\", \"postal_code\": \"700016\", \"country\": \"IN\", \"phone\": \"+919000011111\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-166.yaml b/user_service/keploy/atg/tests/test-166.yaml new file mode 100755 index 0000000..d80eee7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-166.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-166 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:06.187808509Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:06.188875211Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777086 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-167.yaml b/user_service/keploy/atg/tests/test-167.yaml new file mode 100755 index 0000000..8873ca1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-167.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-167 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/be716f2f-f071-42fe-8a85-a92c8a1c1b68 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:06.995160754Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:07.017982631Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777087 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/be716f2f-f071-42fe-8a85-a92c8a1c1b68 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-168.yaml b/user_service/keploy/atg/tests/test-168.yaml new file mode 100755 index 0000000..3a10f0c --- /dev/null +++ b/user_service/keploy/atg/tests/test-168.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-168 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "94" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "list_order_user", "email": "list_order_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:10.11722249Z + resp: + status_code: 201 + header: + Content-Length: "125" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:10 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"list_order_user@example.in","id":"4dec0e0b-8950-49ab-aefb-acea23952e05","phone":null,"username":"list_order_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:10.187578124Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777090 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"list_order_user\", \"email\": \"list_order_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-169.yaml b/user_service/keploy/atg/tests/test-169.yaml new file mode 100755 index 0000000..361b7c5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-169.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-169 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "55" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "list_order_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:10.956529056Z + resp: + status_code: 200 + header: + Content-Length: "350" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:11 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"list_order_user@example.in","id":"4dec0e0b-8950-49ab-aefb-acea23952e05","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0ZGVjMGUwYi04OTUwLTQ5YWItYWVmYi1hY2VhMjM5NTJlMDUiLCJ1c2VybmFtZSI6Imxpc3Rfb3JkZXJfdXNlciIsImlhdCI6MTc2Mjc3NzA5MSwiZXhwIjoxNzY1MzY5MDkxfQ.vngRxKSmZqG5kFGtD846fuHbpFilltmw_tQYeVwzrRg","username":"list_order_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:11.019803439Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777091 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"list_order_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-17.yaml b/user_service/keploy/atg/tests/test-17.yaml new file mode 100755 index 0000000..5d081a1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-17.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-17 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:44.870391247Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:44 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODI0LCJleHAiOjE3NjUzNjg4MjR9.pEaw7nY1cfyN2wfWIk_SRc6DsKM1oRItCxHcc8PIpyk","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:44.927810041Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776824 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-170.yaml b/user_service/keploy/atg/tests/test-170.yaml new file mode 100755 index 0000000..5bf5d66 --- /dev/null +++ b/user_service/keploy/atg/tests/test-170.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-170 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/4dec0e0b-8950-49ab-aefb-acea23952e05/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "159" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "7 Jubilee Hills", "city": "Hyderabad", "state": "Telangana", "postal_code": "500033", "country": "IN", "phone": "+919123456789", "is_default": true}' + timestamp: 2025-11-10T12:18:11.716445396Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:11 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"149209af-8c0e-440f-99fb-704a2209cf84"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:11.741693293Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777091 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/4dec0e0b-8950-49ab-aefb-acea23952e05/addresses \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"line1\": \"7 Jubilee Hills\", \"city\": \"Hyderabad\", \"state\": \"Telangana\", \"postal_code\": \"500033\", \"country\": \"IN\", \"phone\": \"+919123456789\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-171.yaml b/user_service/keploy/atg/tests/test-171.yaml new file mode 100755 index 0000000..a4dc11b --- /dev/null +++ b/user_service/keploy/atg/tests/test-171.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-171 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:12.460372083Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:12.461690691Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777092 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-172.yaml b/user_service/keploy/atg/tests/test-172.yaml new file mode 100755 index 0000000..96826d7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-172.yaml @@ -0,0 +1,50 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-172 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders?limit=5&userId=4dec0e0b-8950-49ab-aefb-acea23952e05 + url_params: + limit: "5" + userId: 4dec0e0b-8950-49ab-aefb-acea23952e05 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:13.277109641Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:13 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:13.278189413Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777093 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/orders?limit=5&userId=4dec0e0b-8950-49ab-aefb-acea23952e05 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-173.yaml b/user_service/keploy/atg/tests/test-173.yaml new file mode 100755 index 0000000..ff01742 --- /dev/null +++ b/user_service/keploy/atg/tests/test-173.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-173 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/4dec0e0b-8950-49ab-aefb-acea23952e05 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:14.00324888Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:14 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:14.025508771Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777094 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/4dec0e0b-8950-49ab-aefb-acea23952e05 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-174.yaml b/user_service/keploy/atg/tests/test-174.yaml new file mode 100755 index 0000000..e46536c --- /dev/null +++ b/user_service/keploy/atg/tests/test-174.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-174 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "98" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "default_addr_user", "email": "default_addr_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:17.458328285Z + resp: + status_code: 201 + header: + Content-Length: "129" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:17 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"default_addr_user@example.in","id":"2a16540a-f916-4b11-b715-7636c0946697","phone":null,"username":"default_addr_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:17.533726246Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777097 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"default_addr_user\", \"email\": \"default_addr_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-175.yaml b/user_service/keploy/atg/tests/test-175.yaml new file mode 100755 index 0000000..9267584 --- /dev/null +++ b/user_service/keploy/atg/tests/test-175.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-175 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "57" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "default_addr_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:18.203340675Z + resp: + status_code: 200 + header: + Content-Length: "356" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:18 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"default_addr_user@example.in","id":"2a16540a-f916-4b11-b715-7636c0946697","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyYTE2NTQwYS1mOTE2LTRiMTEtYjcxNS03NjM2YzA5NDY2OTciLCJ1c2VybmFtZSI6ImRlZmF1bHRfYWRkcl91c2VyIiwiaWF0IjoxNzYyNzc3MDk4LCJleHAiOjE3NjUzNjkwOTh9.9vYHsR4VRnSX1kRhgkqWc9nAOGN-K22Y9v48FRQF-vU","username":"default_addr_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:18.270221819Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777098 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"default_addr_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-176.yaml b/user_service/keploy/atg/tests/test-176.yaml new file mode 100755 index 0000000..bca2df2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-176.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-176 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/2a16540a-f916-4b11-b715-7636c0946697/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "155" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "9 Anna Salai", "city": "Chennai", "state": "Tamil Nadu", "postal_code": "600002", "country": "IN", "phone": "+919998887776", "is_default": true}' + timestamp: 2025-11-10T12:18:19.031547061Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:19 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"d54f10f7-0200-468b-9b9f-e90a9c484959"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:19.0565023Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777099 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/2a16540a-f916-4b11-b715-7636c0946697/addresses \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"line1\": \"9 Anna Salai\", \"city\": \"Chennai\", \"state\": \"Tamil Nadu\", \"postal_code\": \"600002\", \"country\": \"IN\", \"phone\": \"+919998887776\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-177.yaml b/user_service/keploy/atg/tests/test-177.yaml new file mode 100755 index 0000000..ed271cc --- /dev/null +++ b/user_service/keploy/atg/tests/test-177.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-177 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:19.786404339Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:19 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:19.787657918Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777099 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-178.yaml b/user_service/keploy/atg/tests/test-178.yaml new file mode 100755 index 0000000..ec5ef5b --- /dev/null +++ b/user_service/keploy/atg/tests/test-178.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-178 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/2a16540a-f916-4b11-b715-7636c0946697 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:20.614291898Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:20 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:20.638032177Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777100 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/2a16540a-f916-4b11-b715-7636c0946697 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-179.yaml b/user_service/keploy/atg/tests/test-179.yaml new file mode 100755 index 0000000..3c126b6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-179.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-179 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "88" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "no_addr_user", "email": "no_addr_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:23.003994714Z + resp: + status_code: 201 + header: + Content-Length: "119" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"no_addr_user@example.in","id":"3afeaa74-af93-4845-a994-2421dfbb9178","phone":null,"username":"no_addr_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:23.074531545Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777103 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"no_addr_user\", \"email\": \"no_addr_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-18.yaml b/user_service/keploy/atg/tests/test-18.yaml new file mode 100755 index 0000000..1e6a84f --- /dev/null +++ b/user_service/keploy/atg/tests/test-18.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-18 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "78" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "email": "new.alice@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:45.613797782Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:45 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:45.615001281Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776825 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"alice\", \"email\": \"new.alice@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-180.yaml b/user_service/keploy/atg/tests/test-180.yaml new file mode 100755 index 0000000..bb38036 --- /dev/null +++ b/user_service/keploy/atg/tests/test-180.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-180 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "no_addr_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:23.767542119Z + resp: + status_code: 200 + header: + Content-Length: "340" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"no_addr_user@example.in","id":"3afeaa74-af93-4845-a994-2421dfbb9178","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzYWZlYWE3NC1hZjkzLTQ4NDUtYTk5NC0yNDIxZGZiYjkxNzgiLCJ1c2VybmFtZSI6Im5vX2FkZHJfdXNlciIsImlhdCI6MTc2Mjc3NzEwMywiZXhwIjoxNzY1MzY5MTAzfQ.uQffEgNrdYgJo-6TQae8Taoz9hcpc7uBARz_VPlOt58","username":"no_addr_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:23.82518081Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777103 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"no_addr_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-181.yaml b/user_service/keploy/atg/tests/test-181.yaml new file mode 100755 index 0000000..fb7bc03 --- /dev/null +++ b/user_service/keploy/atg/tests/test-181.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-181 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:24.486555783Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:24 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:24.487720473Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777104 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-182.yaml b/user_service/keploy/atg/tests/test-182.yaml new file mode 100755 index 0000000..67e5ea3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-182.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-182 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/3afeaa74-af93-4845-a994-2421dfbb9178 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:25.301413224Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:25 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:25.325730039Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777105 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/3afeaa74-af93-4845-a994-2421dfbb9178 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-183.yaml b/user_service/keploy/atg/tests/test-183.yaml new file mode 100755 index 0000000..bd5cb55 --- /dev/null +++ b/user_service/keploy/atg/tests/test-183.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-183 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "108" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_auth_get", "email": "temp_user_for_auth_get@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:29.478548529Z + resp: + status_code: 201 + header: + Content-Length: "139" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:29 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_auth_get@example.in","id":"b002b30e-2c02-4c4e-afc6-0f374d902571","phone":null,"username":"temp_user_for_auth_get"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:29.551156943Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777109 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"temp_user_for_auth_get\", \"email\": \"temp_user_for_auth_get@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-184.yaml b/user_service/keploy/atg/tests/test-184.yaml new file mode 100755 index 0000000..dac4f05 --- /dev/null +++ b/user_service/keploy/atg/tests/test-184.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-184 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "62" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_auth_get", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:30.254465693Z + resp: + status_code: 200 + header: + Content-Length: "373" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:30 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_auth_get@example.in","id":"b002b30e-2c02-4c4e-afc6-0f374d902571","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiMDAyYjMwZS0yYzAyLTRjNGUtYWZjNi0wZjM3NGQ5MDI1NzEiLCJ1c2VybmFtZSI6InRlbXBfdXNlcl9mb3JfYXV0aF9nZXQiLCJpYXQiOjE3NjI3NzcxMTAsImV4cCI6MTc2NTM2OTExMH0.T7uBeqwCkTMA_625JgyrXoTMZ-DupFTU8paLEU9cSvM","username":"temp_user_for_auth_get"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:30.314434365Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777110 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"temp_user_for_auth_get\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-185.yaml b/user_service/keploy/atg/tests/test-185.yaml new file mode 100755 index 0000000..0d6cb6f --- /dev/null +++ b/user_service/keploy/atg/tests/test-185.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-185 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders/00000000-0000-0000-0000-000000000000 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:31.08316836Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:31.084265612Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777111 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/orders/00000000-0000-0000-0000-000000000000 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-186.yaml b/user_service/keploy/atg/tests/test-186.yaml new file mode 100755 index 0000000..d7147eb --- /dev/null +++ b/user_service/keploy/atg/tests/test-186.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-186 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/b002b30e-2c02-4c4e-afc6-0f374d902571 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:31.825326962Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:31.849945094Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777111 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/b002b30e-2c02-4c4e-afc6-0f374d902571 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-187.yaml b/user_service/keploy/atg/tests/test-187.yaml new file mode 100755 index 0000000..b0b807c --- /dev/null +++ b/user_service/keploy/atg/tests/test-187.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-187 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "108" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_auth_pay", "email": "temp_user_for_auth_pay@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:32.656673868Z + resp: + status_code: 201 + header: + Content-Length: "139" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_auth_pay@example.in","id":"595b6f58-990a-47b2-8245-e03d98d9a2df","phone":null,"username":"temp_user_for_auth_pay"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:32.728141912Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777112 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"temp_user_for_auth_pay\", \"email\": \"temp_user_for_auth_pay@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-188.yaml b/user_service/keploy/atg/tests/test-188.yaml new file mode 100755 index 0000000..67a4431 --- /dev/null +++ b/user_service/keploy/atg/tests/test-188.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-188 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "62" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_auth_pay", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:33.407480167Z + resp: + status_code: 200 + header: + Content-Length: "373" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:33 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_auth_pay@example.in","id":"595b6f58-990a-47b2-8245-e03d98d9a2df","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1OTViNmY1OC05OTBhLTQ3YjItODI0NS1lMDNkOThkOWEyZGYiLCJ1c2VybmFtZSI6InRlbXBfdXNlcl9mb3JfYXV0aF9wYXkiLCJpYXQiOjE3NjI3NzcxMTMsImV4cCI6MTc2NTM2OTExM30.doywp3Ab4WYeWNPCGgpxudMGOrXL7BRC_i0xsLFfmyo","username":"temp_user_for_auth_pay"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:33.469331543Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777113 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"temp_user_for_auth_pay\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-189.yaml b/user_service/keploy/atg/tests/test-189.yaml new file mode 100755 index 0000000..42366b1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-189.yaml @@ -0,0 +1,48 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-189 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders/00000000-0000-0000-0000-000000000000/pay + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "0" + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:34.237879303Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:34.239032643Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777114 +curl: | + curl --request POST \ + --url http://localhost:8082/api/v1/orders/00000000-0000-0000-0000-000000000000/pay \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-19.yaml b/user_service/keploy/atg/tests/test-19.yaml new file mode 100755 index 0000000..18c4b3b --- /dev/null +++ b/user_service/keploy/atg/tests/test-19.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-19 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:47.600560527Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODI3LCJleHAiOjE3NjUzNjg4Mjd9.OT-ZO05GbGXy6qvmSIaI8THhnvY54itNSCVeAVzH128","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:47.664637811Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776827 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-190.yaml b/user_service/keploy/atg/tests/test-190.yaml new file mode 100755 index 0000000..ade67ce --- /dev/null +++ b/user_service/keploy/atg/tests/test-190.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-190 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/595b6f58-990a-47b2-8245-e03d98d9a2df + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:34.975603962Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:34.995582403Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777114 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/595b6f58-990a-47b2-8245-e03d98d9a2df \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-191.yaml b/user_service/keploy/atg/tests/test-191.yaml new file mode 100755 index 0000000..d0141f7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-191.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-191 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "114" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_auth_cancel", "email": "temp_user_for_auth_cancel@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:35.797761378Z + resp: + status_code: 201 + header: + Content-Length: "145" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:35 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_auth_cancel@example.in","id":"54158f89-4d6f-4b9e-a7f1-10ac725e2da5","phone":null,"username":"temp_user_for_auth_cancel"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:35.867443548Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777115 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"temp_user_for_auth_cancel\", \"email\": \"temp_user_for_auth_cancel@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-192.yaml b/user_service/keploy/atg/tests/test-192.yaml new file mode 100755 index 0000000..ad589db --- /dev/null +++ b/user_service/keploy/atg/tests/test-192.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-192 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "65" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_auth_cancel", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:36.558463566Z + resp: + status_code: 200 + header: + Content-Length: "383" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:36 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_auth_cancel@example.in","id":"54158f89-4d6f-4b9e-a7f1-10ac725e2da5","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NDE1OGY4OS00ZDZmLTRiOWUtYTdmMS0xMGFjNzI1ZTJkYTUiLCJ1c2VybmFtZSI6InRlbXBfdXNlcl9mb3JfYXV0aF9jYW5jZWwiLCJpYXQiOjE3NjI3NzcxMTYsImV4cCI6MTc2NTM2OTExNn0.mq-d5GhUDneBlLIfM2aovJ63vITPCSHoJShdB3FY1sw","username":"temp_user_for_auth_cancel"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:36.616938311Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777116 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"temp_user_for_auth_cancel\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-193.yaml b/user_service/keploy/atg/tests/test-193.yaml new file mode 100755 index 0000000..f22151f --- /dev/null +++ b/user_service/keploy/atg/tests/test-193.yaml @@ -0,0 +1,48 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-193 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders/00000000-0000-0000-0000-000000000000/cancel + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "0" + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:37.384807296Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:37 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:37.386035017Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777117 +curl: | + curl --request POST \ + --url http://localhost:8082/api/v1/orders/00000000-0000-0000-0000-000000000000/cancel \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-194.yaml b/user_service/keploy/atg/tests/test-194.yaml new file mode 100755 index 0000000..48639fe --- /dev/null +++ b/user_service/keploy/atg/tests/test-194.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-194 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/54158f89-4d6f-4b9e-a7f1-10ac725e2da5 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:38.119047598Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:38.140626836Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777118 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/54158f89-4d6f-4b9e-a7f1-10ac725e2da5 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-195.yaml b/user_service/keploy/atg/tests/test-195.yaml new file mode 100755 index 0000000..d940133 --- /dev/null +++ b/user_service/keploy/atg/tests/test-195.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-195 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "59" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"email": "missinguser@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:38.949572276Z + resp: + status_code: 400 + header: + Content-Length: "36" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Missing required fields"} + status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:38.950873024Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777118 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"email\": \"missinguser@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-196.yaml b/user_service/keploy/atg/tests/test-196.yaml new file mode 100755 index 0000000..a967743 --- /dev/null +++ b/user_service/keploy/atg/tests/test-196.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-196 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "62" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "missingpass", "email": "missingpass@example.in"}' + timestamp: 2025-11-10T12:18:39.781302833Z + resp: + status_code: 400 + header: + Content-Length: "36" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Missing required fields"} + status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:39.782585222Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777119 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"missingpass\", \"email\": \"missingpass@example.in\"}" diff --git a/user_service/keploy/atg/tests/test-197.yaml b/user_service/keploy/atg/tests/test-197.yaml new file mode 100755 index 0000000..3ce8188 --- /dev/null +++ b/user_service/keploy/atg/tests/test-197.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-197 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "missingemail", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:40.576698618Z + resp: + status_code: 400 + header: + Content-Length: "36" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:40 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Missing required fields"} + status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:40.578050206Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777120 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"missingemail\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-198.yaml b/user_service/keploy/atg/tests/test-198.yaml new file mode 100755 index 0000000..efa01ee --- /dev/null +++ b/user_service/keploy/atg/tests/test-198.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-198 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "noauth_addr_user", "email": "noauth_addr_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:41.36769923Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:41 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"noauth_addr_user@example.in","id":"3aa6b50d-82a0-45d8-b37c-d82fa83742fb","phone":null,"username":"noauth_addr_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:41.43617456Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777121 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"noauth_addr_user\", \"email\": \"noauth_addr_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-199.yaml b/user_service/keploy/atg/tests/test-199.yaml new file mode 100755 index 0000000..d09da1b --- /dev/null +++ b/user_service/keploy/atg/tests/test-199.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-199 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/3aa6b50d-82a0-45d8-b37c-d82fa83742fb/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "155" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "123 MG Road", "city": "Bengaluru", "state": "Karnataka", "postal_code": "560001", "country": "IN", "phone": "+919876543210", "is_default": true}' + timestamp: 2025-11-10T12:18:42.216054267Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:42 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"8cd8a687-17ae-433c-9822-068378f0076f"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:42.237955652Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777122 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/3aa6b50d-82a0-45d8-b37c-d82fa83742fb/addresses \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"line1\": \"123 MG Road\", \"city\": \"Bengaluru\", \"state\": \"Karnataka\", \"postal_code\": \"560001\", \"country\": \"IN\", \"phone\": \"+919876543210\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-2.yaml b/user_service/keploy/atg/tests/test-2.yaml new file mode 100755 index 0000000..2494cb3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-2.yaml @@ -0,0 +1,45 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-2 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Content-Length: "118" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "priya_sharma_{{$randomInt}}", "email": "priya.sharma.{{$randomInt}}@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:11:09.67247478Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:11:09 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:11:09.673743448Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776669 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"priya_sharma_{{$randomInt}}\", \"email\": \"priya.sharma.{{$randomInt}}@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-20.yaml b/user_service/keploy/atg/tests/test-20.yaml new file mode 100755 index 0000000..42ec217 --- /dev/null +++ b/user_service/keploy/atg/tests/test-20.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-20 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/99999999-9999-9999-9999-999999999999 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:13:48.487458499Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:48.488871526Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776828 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/99999999-9999-9999-9999-999999999999 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ diff --git a/user_service/keploy/atg/tests/test-200.yaml b/user_service/keploy/atg/tests/test-200.yaml new file mode 100755 index 0000000..9bc9268 --- /dev/null +++ b/user_service/keploy/atg/tests/test-200.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-200 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "noauth_addr_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:42.945251954Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:43 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"noauth_addr_user@example.in","id":"3aa6b50d-82a0-45d8-b37c-d82fa83742fb","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzYWE2YjUwZC04MmEwLTQ1ZDgtYjM3Yy1kODJmYTgzNzQyZmIiLCJ1c2VybmFtZSI6Im5vYXV0aF9hZGRyX3VzZXIiLCJpYXQiOjE3NjI3NzcxMjMsImV4cCI6MTc2NTM2OTEyM30.2D3v9vrEEChLsmyk7S7PfSDd-Ore2EUd8mEXPjakrLg","username":"noauth_addr_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:43.004571842Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777123 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"noauth_addr_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-201.yaml b/user_service/keploy/atg/tests/test-201.yaml new file mode 100755 index 0000000..eb9f4c4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-201.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-201 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/3aa6b50d-82a0-45d8-b37c-d82fa83742fb + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:43.686511429Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:43 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:43.714844859Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777123 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/3aa6b50d-82a0-45d8-b37c-d82fa83742fb \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-202.yaml b/user_service/keploy/atg/tests/test-202.yaml new file mode 100755 index 0000000..d6023db --- /dev/null +++ b/user_service/keploy/atg/tests/test-202.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-202 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "noauth_list_addr", "email": "noauth_list_addr@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:45.609567322Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:45 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"noauth_list_addr@example.in","id":"e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6","phone":null,"username":"noauth_list_addr"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:45.682393865Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777125 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"noauth_list_addr\", \"email\": \"noauth_list_addr@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-203.yaml b/user_service/keploy/atg/tests/test-203.yaml new file mode 100755 index 0000000..b1844fe --- /dev/null +++ b/user_service/keploy/atg/tests/test-203.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-203 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:46.408836856Z + resp: + status_code: 200 + header: + Content-Length: "3" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:46 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + [] + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:46.419753105Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777126 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-204.yaml b/user_service/keploy/atg/tests/test-204.yaml new file mode 100755 index 0000000..15ede72 --- /dev/null +++ b/user_service/keploy/atg/tests/test-204.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-204 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "noauth_list_addr", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:18:47.143671178Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"noauth_list_addr@example.in","id":"e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlMDRhODJlMi04NTM0LTRiYTgtOGFhZi03OGU1M2YyYzZmYzYiLCJ1c2VybmFtZSI6Im5vYXV0aF9saXN0X2FkZHIiLCJpYXQiOjE3NjI3NzcxMjcsImV4cCI6MTc2NTM2OTEyN30.QV_CHN9fG6sbC69ejswDViUZHgtqQpGEsYDiVRMpWyw","username":"noauth_list_addr"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:47.206327168Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777127 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"noauth_list_addr\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-205.yaml b/user_service/keploy/atg/tests/test-205.yaml new file mode 100755 index 0000000..ecc01a4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-205.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-205 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:47.970128824Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:18:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:47.991927689Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777127 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/e04a82e2-8534-4ba8-8aaf-78e53f2c6fc6 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-206.yaml b/user_service/keploy/atg/tests/test-206.yaml new file mode 100755 index 0000000..f551302 --- /dev/null +++ b/user_service/keploy/atg/tests/test-206.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-206 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:49.963794045Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:49 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:49.964943294Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777129 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-207.yaml b/user_service/keploy/atg/tests/test-207.yaml new file mode 100755 index 0000000..e27c039 --- /dev/null +++ b/user_service/keploy/atg/tests/test-207.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-207 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/some-product-id + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:51.926534319Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:51.928078195Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777131 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products/some-product-id \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-208.yaml b/user_service/keploy/atg/tests/test-208.yaml new file mode 100755 index 0000000..829a7c3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-208.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-208 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/some-product-id/reserve + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "15" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1}' + timestamp: 2025-11-10T12:18:53.934472263Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:53 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:53.935746172Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777133 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/products/some-product-id/reserve \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"quantity\": 1}" diff --git a/user_service/keploy/atg/tests/test-209.yaml b/user_service/keploy/atg/tests/test-209.yaml new file mode 100755 index 0000000..42ef789 --- /dev/null +++ b/user_service/keploy/atg/tests/test-209.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-209 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/some-product-id/release + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "15" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1}' + timestamp: 2025-11-10T12:18:55.802450193Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:55 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:55.803685183Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777135 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/products/some-product-id/release \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"quantity\": 1}" diff --git a/user_service/keploy/atg/tests/test-21.yaml b/user_service/keploy/atg/tests/test-21.yaml new file mode 100755 index 0000000..d5eafb6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-21.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-21 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:50.206463359Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:50 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODMwLCJleHAiOjE3NjUzNjg4MzB9.cTGZgbzuwkgZjJ0mvymGSyztmBd0ciQLtMJkv3v4hPM","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:50.266720427Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776830 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-210.yaml b/user_service/keploy/atg/tests/test-210.yaml new file mode 100755 index 0000000..62bad39 --- /dev/null +++ b/user_service/keploy/atg/tests/test-210.yaml @@ -0,0 +1,53 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-210 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + Idempotency-Key: '{{$guid}}' + User-Agent: Go-http-client/1.1 + body: '{"userId": "some-user-id", "items": [ { "productId": "some-product-id", "quantity": 1 } ]}' + timestamp: 2025-11-10T12:18:57.78580305Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:57 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:57.78705993Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777137 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/orders \ + --header 'Idempotency-Key: {{$guid}}' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"userId\": \"some-user-id\", \"items\": [ { \"productId\": \"some-product-id\", \"quantity\": 1 } ]}" diff --git a/user_service/keploy/atg/tests/test-211.yaml b/user_service/keploy/atg/tests/test-211.yaml new file mode 100755 index 0000000..4d5e994 --- /dev/null +++ b/user_service/keploy/atg/tests/test-211.yaml @@ -0,0 +1,49 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-211 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders?userId=some-user-id + url_params: + userId: some-user-id + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:18:59.679073912Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:18:59 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:18:59.680194312Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777139 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/orders?userId=some-user-id \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-212.yaml b/user_service/keploy/atg/tests/test-212.yaml new file mode 100755 index 0000000..6ca4e1b --- /dev/null +++ b/user_service/keploy/atg/tests/test-212.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-212 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders/some-order-id + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:01.602200164Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:01.603521112Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777141 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/orders/some-order-id \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-213.yaml b/user_service/keploy/atg/tests/test-213.yaml new file mode 100755 index 0000000..d352028 --- /dev/null +++ b/user_service/keploy/atg/tests/test-213.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-213 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders/some-order-id/details + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:03.856054791Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:03 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:03.85739103Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777143 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/orders/some-order-id/details \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-214.yaml b/user_service/keploy/atg/tests/test-214.yaml new file mode 100755 index 0000000..87f3bfc --- /dev/null +++ b/user_service/keploy/atg/tests/test-214.yaml @@ -0,0 +1,48 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-214 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders/some-order-id/pay + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "0" + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:05.888591313Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:05 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:05.889751293Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777145 +curl: | + curl --request POST \ + --url http://localhost:8082/api/v1/orders/some-order-id/pay \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-215.yaml b/user_service/keploy/atg/tests/test-215.yaml new file mode 100755 index 0000000..eb35ffa --- /dev/null +++ b/user_service/keploy/atg/tests/test-215.yaml @@ -0,0 +1,48 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-215 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders/some-order-id/cancel + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "0" + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:07.99765419Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:07.998727911Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777147 +curl: | + curl --request POST \ + --url http://localhost:8082/api/v1/orders/some-order-id/cancel \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-216.yaml b/user_service/keploy/atg/tests/test-216.yaml new file mode 100755 index 0000000..3d7d60c --- /dev/null +++ b/user_service/keploy/atg/tests/test-216.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-216 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "88" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "rahul_mumbai", "email": "rahul_mumbai@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:09.88073066Z + resp: + status_code: 201 + header: + Content-Length: "119" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:09 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"rahul_mumbai@example.in","id":"3363bd32-cba3-4bb9-b6b9-3257a46415fa","phone":null,"username":"rahul_mumbai"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:09.954702404Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777149 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"rahul_mumbai\", \"email\": \"rahul_mumbai@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-217.yaml b/user_service/keploy/atg/tests/test-217.yaml new file mode 100755 index 0000000..74003e1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-217.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-217 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "rahul_mumbai", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:10.634083864Z + resp: + status_code: 200 + header: + Content-Length: "340" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:10 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"rahul_mumbai@example.in","id":"3363bd32-cba3-4bb9-b6b9-3257a46415fa","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzMzYzYmQzMi1jYmEzLTRiYjktYjZiOS0zMjU3YTQ2NDE1ZmEiLCJ1c2VybmFtZSI6InJhaHVsX211bWJhaSIsImlhdCI6MTc2Mjc3NzE1MCwiZXhwIjoxNzY1MzY5MTUwfQ.svoqj5Y-CxQjizUcNJbIZ1Wch0jdoeqiEfMWeVwLC5Q","username":"rahul_mumbai"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:10.694872541Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777150 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"rahul_mumbai\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-218.yaml b/user_service/keploy/atg/tests/test-218.yaml new file mode 100755 index 0000000..76e8319 --- /dev/null +++ b/user_service/keploy/atg/tests/test-218.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-218 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/3363bd32-cba3-4bb9-b6b9-3257a46415fa/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "158" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "101 Bandra West", "city": "Mumbai", "state": "Maharashtra", "postal_code": "400050", "country": "IN", "phone": "+919820098200", "is_default": true}' + timestamp: 2025-11-10T12:19:11.454380364Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:11 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"b1a0dd95-5f84-435b-a6ee-59dfe1aa6155"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:11.479018416Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777151 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/3363bd32-cba3-4bb9-b6b9-3257a46415fa/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"101 Bandra West\", \"city\": \"Mumbai\", \"state\": \"Maharashtra\", \"postal_code\": \"400050\", \"country\": \"IN\", \"phone\": \"+919820098200\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-219.yaml b/user_service/keploy/atg/tests/test-219.yaml new file mode 100755 index 0000000..eda82c5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-219.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-219 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/3363bd32-cba3-4bb9-b6b9-3257a46415fa/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:12.202344085Z + resp: + status_code: 200 + header: + Content-Length: "202" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + [{"city":"Mumbai","country":"IN","id":"b1a0dd95-5f84-435b-a6ee-59dfe1aa6155","is_default":1,"line1":"101 Bandra West","line2":null,"phone":"+919820098200","postal_code":"400050","state":"Maharashtra"}] + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:12.213754039Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777152 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/3363bd32-cba3-4bb9-b6b9-3257a46415fa/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-22.yaml b/user_service/keploy/atg/tests/test-22.yaml new file mode 100755 index 0000000..5704207 --- /dev/null +++ b/user_service/keploy/atg/tests/test-22.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-22 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/99999999-9999-9999-9999-999999999999 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:13:51.094247716Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:51.095481755Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776831 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/99999999-9999-9999-9999-999999999999 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-220.yaml b/user_service/keploy/atg/tests/test-220.yaml new file mode 100755 index 0000000..a9aeac1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-220.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-220 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/3363bd32-cba3-4bb9-b6b9-3257a46415fa + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:13.027989551Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:13 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:13.053489006Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777153 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/3363bd32-cba3-4bb9-b6b9-3257a46415fa \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-221.yaml b/user_service/keploy/atg/tests/test-221.yaml new file mode 100755 index 0000000..1c0053e --- /dev/null +++ b/user_service/keploy/atg/tests/test-221.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-221 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "86" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "sneha_delhi", "email": "sneha_delhi@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:15.211424789Z + resp: + status_code: 201 + header: + Content-Length: "117" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:15 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"sneha_delhi@example.in","id":"5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a","phone":null,"username":"sneha_delhi"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:15.28358329Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777155 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"sneha_delhi\", \"email\": \"sneha_delhi@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-222.yaml b/user_service/keploy/atg/tests/test-222.yaml new file mode 100755 index 0000000..eeb4c53 --- /dev/null +++ b/user_service/keploy/atg/tests/test-222.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-222 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "51" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "sneha_delhi", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:16.157091833Z + resp: + status_code: 200 + header: + Content-Length: "336" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:16 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"sneha_delhi@example.in","id":"5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YTJjYmFiNS00OTFhLTRkZmUtOWUzYy1jYjUzODdlOGE5M2EiLCJ1c2VybmFtZSI6InNuZWhhX2RlbGhpIiwiaWF0IjoxNzYyNzc3MTU2LCJleHAiOjE3NjUzNjkxNTZ9.RuILZRWbkWhbihXaYXxZ7CPEeuiQ1ngM7u4WLOtxcY0","username":"sneha_delhi"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:16.216285623Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777156 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"sneha_delhi\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-223.yaml b/user_service/keploy/atg/tests/test-223.yaml new file mode 100755 index 0000000..8496c12 --- /dev/null +++ b/user_service/keploy/atg/tests/test-223.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-223 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "161" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "25 Hauz Khas Village", "city": "New Delhi", "state": "Delhi", "postal_code": "110016", "country": "IN", "phone": "+919810098100", "is_default": false}' + timestamp: 2025-11-10T12:19:16.933289527Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:16 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"223a9700-5da2-454b-a432-8de30c59cf5d"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:16.957326085Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777156 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"line1\": \"25 Hauz Khas Village\", \"city\": \"New Delhi\", \"state\": \"Delhi\", \"postal_code\": \"110016\", \"country\": \"IN\", \"phone\": \"+919810098100\", \"is_default\": false}" diff --git a/user_service/keploy/atg/tests/test-224.yaml b/user_service/keploy/atg/tests/test-224.yaml new file mode 100755 index 0000000..50c3a4c --- /dev/null +++ b/user_service/keploy/atg/tests/test-224.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-224 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:17.736954252Z + resp: + status_code: 200 + header: + Content-Length: "204" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:17 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + [{"city":"New Delhi","country":"IN","id":"223a9700-5da2-454b-a432-8de30c59cf5d","is_default":0,"line1":"25 Hauz Khas Village","line2":null,"phone":"+919810098100","postal_code":"110016","state":"Delhi"}] + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:17.751984315Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777157 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-225.yaml b/user_service/keploy/atg/tests/test-225.yaml new file mode 100755 index 0000000..bde65e0 --- /dev/null +++ b/user_service/keploy/atg/tests/test-225.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-225 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:18.494121099Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:18 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:18.518119125Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777158 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/5a2cbab5-491a-4dfe-9e3c-cb5387e8a93a \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-226.yaml b/user_service/keploy/atg/tests/test-226.yaml new file mode 100755 index 0000000..9ce4d54 --- /dev/null +++ b/user_service/keploy/atg/tests/test-226.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-226 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "bad_prod_user", "email": "bad_prod_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:20.38789479Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:20 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"bad_prod_user@example.in","id":"80b19253-5d7a-4d05-9036-bff647acbaaa","phone":null,"username":"bad_prod_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:20.460716084Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777160 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"bad_prod_user\", \"email\": \"bad_prod_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-227.yaml b/user_service/keploy/atg/tests/test-227.yaml new file mode 100755 index 0000000..157b8db --- /dev/null +++ b/user_service/keploy/atg/tests/test-227.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-227 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "bad_prod_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:21.134196698Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"bad_prod_user@example.in","id":"80b19253-5d7a-4d05-9036-bff647acbaaa","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4MGIxOTI1My01ZDdhLTRkMDUtOTAzNi1iZmY2NDdhY2JhYWEiLCJ1c2VybmFtZSI6ImJhZF9wcm9kX3VzZXIiLCJpYXQiOjE3NjI3NzcxNjEsImV4cCI6MTc2NTM2OTE2MX0.TgpWx5zWO7bYn674LIxiZSBgV1NBnqKbcjJsEUYnz5M","username":"bad_prod_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:21.196229574Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777161 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"bad_prod_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-228.yaml b/user_service/keploy/atg/tests/test-228.yaml new file mode 100755 index 0000000..d72d1f1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-228.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-228 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/80b19253-5d7a-4d05-9036-bff647acbaaa/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "159" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "123 Sector 17", "city": "Chandigarh", "state": "Chandigarh", "postal_code": "160017", "country": "IN", "phone": "+919876512345", "is_default": true}' + timestamp: 2025-11-10T12:19:21.934303444Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"9b315486-d4a1-4e7f-a2b0-9dd8736af86c"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:21.961395415Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777161 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/80b19253-5d7a-4d05-9036-bff647acbaaa/addresses \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"line1\": \"123 Sector 17\", \"city\": \"Chandigarh\", \"state\": \"Chandigarh\", \"postal_code\": \"160017\", \"country\": \"IN\", \"phone\": \"+919876512345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-229.yaml b/user_service/keploy/atg/tests/test-229.yaml new file mode 100755 index 0000000..bc7dbc3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-229.yaml @@ -0,0 +1,53 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-229 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "183" + Content-Type: application/json + Host: localhost:8082 + Idempotency-Key: '{{$guid}}' + User-Agent: Go-http-client/1.1 + body: '{"userId": "80b19253-5d7a-4d05-9036-bff647acbaaa", "items": [ { "productId": "non-existent-product-id", "quantity": 1 } ], "shippingAddressId": "9b315486-d4a1-4e7f-a2b0-9dd8736af86c"}' + timestamp: 2025-11-10T12:19:22.681537566Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:22 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:22.682805515Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777162 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/orders \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'Idempotency-Key: {{$guid}}' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"userId\": \"80b19253-5d7a-4d05-9036-bff647acbaaa\", \"items\": [ { \"productId\": \"non-existent-product-id\", \"quantity\": 1 } ], \"shippingAddressId\": \"9b315486-d4a1-4e7f-a2b0-9dd8736af86c\"}" diff --git a/user_service/keploy/atg/tests/test-23.yaml b/user_service/keploy/atg/tests/test-23.yaml new file mode 100755 index 0000000..ef598f5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-23.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-23 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:52.851722817Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:52 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODMyLCJleHAiOjE3NjUzNjg4MzJ9.Wy81AUf9LdhynGhAKJZCwyc6GjT1whNwpkXQuFXBksc","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:52.911376031Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776832 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-230.yaml b/user_service/keploy/atg/tests/test-230.yaml new file mode 100755 index 0000000..1d81564 --- /dev/null +++ b/user_service/keploy/atg/tests/test-230.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-230 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/80b19253-5d7a-4d05-9036-bff647acbaaa + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:23.515341558Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:23.542899825Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777163 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/80b19253-5d7a-4d05-9036-bff647acbaaa \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-231.yaml b/user_service/keploy/atg/tests/test-231.yaml new file mode 100755 index 0000000..b5b4c8e --- /dev/null +++ b/user_service/keploy/atg/tests/test-231.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-231 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "94" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "zero_quant_user", "email": "zero_quant_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:25.944699933Z + resp: + status_code: 201 + header: + Content-Length: "125" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"zero_quant_user@example.in","id":"d2e41f94-240a-471b-a0fb-78c3380c60c2","phone":null,"username":"zero_quant_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:26.016654576Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777166 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"zero_quant_user\", \"email\": \"zero_quant_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-232.yaml b/user_service/keploy/atg/tests/test-232.yaml new file mode 100755 index 0000000..375463d --- /dev/null +++ b/user_service/keploy/atg/tests/test-232.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-232 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "55" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "zero_quant_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:26.876556119Z + resp: + status_code: 200 + header: + Content-Length: "350" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"zero_quant_user@example.in","id":"d2e41f94-240a-471b-a0fb-78c3380c60c2","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMmU0MWY5NC0yNDBhLTQ3MWItYTBmYi03OGMzMzgwYzYwYzIiLCJ1c2VybmFtZSI6Inplcm9fcXVhbnRfdXNlciIsImlhdCI6MTc2Mjc3NzE2NiwiZXhwIjoxNzY1MzY5MTY2fQ.gYXZOQo5Cy82wXqfSLm1vclrZS8aIhfoQFKr3m7BDps","username":"zero_quant_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:26.936298405Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777166 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"zero_quant_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-233.yaml b/user_service/keploy/atg/tests/test-233.yaml new file mode 100755 index 0000000..f81b2ff --- /dev/null +++ b/user_service/keploy/atg/tests/test-233.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-233 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/d2e41f94-240a-471b-a0fb-78c3380c60c2/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "156" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "45 Ashram Road", "city": "Ahmedabad", "state": "Gujarat", "postal_code": "380009", "country": "IN", "phone": "+919988776655", "is_default": true}' + timestamp: 2025-11-10T12:19:27.699870002Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:27 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"16862c52-8fa2-4677-81ba-cc26143ddddc"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:27.72734391Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777167 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/d2e41f94-240a-471b-a0fb-78c3380c60c2/addresses \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"line1\": \"45 Ashram Road\", \"city\": \"Ahmedabad\", \"state\": \"Gujarat\", \"postal_code\": \"380009\", \"country\": \"IN\", \"phone\": \"+919988776655\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-234.yaml b/user_service/keploy/atg/tests/test-234.yaml new file mode 100755 index 0000000..9ba5187 --- /dev/null +++ b/user_service/keploy/atg/tests/test-234.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-234 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:28.446832178Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:28 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:28.448324346Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777168 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-235.yaml b/user_service/keploy/atg/tests/test-235.yaml new file mode 100755 index 0000000..0205fce --- /dev/null +++ b/user_service/keploy/atg/tests/test-235.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-235 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/d2e41f94-240a-471b-a0fb-78c3380c60c2 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:29.268740823Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:29 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:29.292054907Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777169 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/d2e41f94-240a-471b-a0fb-78c3380c60c2 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-236.yaml b/user_service/keploy/atg/tests/test-236.yaml new file mode 100755 index 0000000..139302b --- /dev/null +++ b/user_service/keploy/atg/tests/test-236.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-236 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "no_items_user", "email": "no_items_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:31.648236327Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"no_items_user@example.in","id":"c6906a40-98ca-4161-9d35-da12bc129a7f","phone":null,"username":"no_items_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:31.718547564Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777171 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"no_items_user\", \"email\": \"no_items_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-237.yaml b/user_service/keploy/atg/tests/test-237.yaml new file mode 100755 index 0000000..2690fdf --- /dev/null +++ b/user_service/keploy/atg/tests/test-237.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-237 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "no_items_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:32.424288609Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"no_items_user@example.in","id":"c6906a40-98ca-4161-9d35-da12bc129a7f","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjNjkwNmE0MC05OGNhLTQxNjEtOWQzNS1kYTEyYmMxMjlhN2YiLCJ1c2VybmFtZSI6Im5vX2l0ZW1zX3VzZXIiLCJpYXQiOjE3NjI3NzcxNzIsImV4cCI6MTc2NTM2OTE3Mn0.0nMexZrGDqF7cUzFetSOAcqvSlQnjnMlUoqcb5DJAC8","username":"no_items_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:32.482945595Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777172 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"no_items_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-238.yaml b/user_service/keploy/atg/tests/test-238.yaml new file mode 100755 index 0000000..a2bcb24 --- /dev/null +++ b/user_service/keploy/atg/tests/test-238.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-238 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/c6906a40-98ca-4161-9d35-da12bc129a7f/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "151" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "67 MI Road", "city": "Jaipur", "state": "Rajasthan", "postal_code": "302001", "country": "IN", "phone": "+919829012345", "is_default": true}' + timestamp: 2025-11-10T12:19:33.170921652Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:33 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"4fa7bac9-4929-4b7c-9809-47681ee46b29"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:33.195206476Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777173 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/c6906a40-98ca-4161-9d35-da12bc129a7f/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"line1\": \"67 MI Road\", \"city\": \"Jaipur\", \"state\": \"Rajasthan\", \"postal_code\": \"302001\", \"country\": \"IN\", \"phone\": \"+919829012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-239.yaml b/user_service/keploy/atg/tests/test-239.yaml new file mode 100755 index 0000000..f366596 --- /dev/null +++ b/user_service/keploy/atg/tests/test-239.yaml @@ -0,0 +1,53 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-239 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "124" + Content-Type: application/json + Host: localhost:8082 + Idempotency-Key: '{{$guid}}' + User-Agent: Go-http-client/1.1 + body: '{"userId": "c6906a40-98ca-4161-9d35-da12bc129a7f", "items": [], "shippingAddressId": "4fa7bac9-4929-4b7c-9809-47681ee46b29"}' + timestamp: 2025-11-10T12:19:33.984734676Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:33 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:33.985896317Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777173 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/orders \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'Idempotency-Key: {{$guid}}' \ + --data "{\"userId\": \"c6906a40-98ca-4161-9d35-da12bc129a7f\", \"items\": [], \"shippingAddressId\": \"4fa7bac9-4929-4b7c-9809-47681ee46b29\"}" diff --git a/user_service/keploy/atg/tests/test-24.yaml b/user_service/keploy/atg/tests/test-24.yaml new file mode 100755 index 0000000..202a098 --- /dev/null +++ b/user_service/keploy/atg/tests/test-24.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-24 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "vikram_addr_01", "email": "vikram.addr.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:53.731313123Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:53 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:53.732576551Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776833 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"vikram_addr_01\", \"email\": \"vikram.addr.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-240.yaml b/user_service/keploy/atg/tests/test-240.yaml new file mode 100755 index 0000000..eb72064 --- /dev/null +++ b/user_service/keploy/atg/tests/test-240.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-240 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/c6906a40-98ca-4161-9d35-da12bc129a7f + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:34.733278022Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:34.756766844Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777174 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/c6906a40-98ca-4161-9d35-da12bc129a7f \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-241.yaml b/user_service/keploy/atg/tests/test-241.yaml new file mode 100755 index 0000000..38bb59a --- /dev/null +++ b/user_service/keploy/atg/tests/test-241.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-241 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "pay_twice_user", "email": "pay_twice_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:36.770608459Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:36 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"pay_twice_user@example.in","id":"05b4f265-384a-4be5-b72f-b3281743867c","phone":null,"username":"pay_twice_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:36.838330347Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777176 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"pay_twice_user\", \"email\": \"pay_twice_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-242.yaml b/user_service/keploy/atg/tests/test-242.yaml new file mode 100755 index 0000000..fc427e3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-242.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-242 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "pay_twice_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:37.656956573Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:37 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"pay_twice_user@example.in","id":"05b4f265-384a-4be5-b72f-b3281743867c","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwNWI0ZjI2NS0zODRhLTRiZTUtYjcyZi1iMzI4MTc0Mzg2N2MiLCJ1c2VybmFtZSI6InBheV90d2ljZV91c2VyIiwiaWF0IjoxNzYyNzc3MTc3LCJleHAiOjE3NjUzNjkxNzd9.EBId_nT1TYH39hktG-NHtERA0BmqNL3KyVHH7CJkB8w","username":"pay_twice_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:37.718699233Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777177 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"pay_twice_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-243.yaml b/user_service/keploy/atg/tests/test-243.yaml new file mode 100755 index 0000000..b69f911 --- /dev/null +++ b/user_service/keploy/atg/tests/test-243.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-243 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/05b4f265-384a-4be5-b72f-b3281743867c/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "158" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "8 Hazratganj", "city": "Lucknow", "state": "Uttar Pradesh", "postal_code": "226001", "country": "IN", "phone": "+919415012345", "is_default": true}' + timestamp: 2025-11-10T12:19:38.41627298Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"a0789c4d-45b2-486a-86ea-ae6f06226459"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:38.438920229Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777178 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/05b4f265-384a-4be5-b72f-b3281743867c/addresses \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"line1\": \"8 Hazratganj\", \"city\": \"Lucknow\", \"state\": \"Uttar Pradesh\", \"postal_code\": \"226001\", \"country\": \"IN\", \"phone\": \"+919415012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-244.yaml b/user_service/keploy/atg/tests/test-244.yaml new file mode 100755 index 0000000..579f7f2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-244.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-244 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:39.289436896Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:39.290571426Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777179 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-245.yaml b/user_service/keploy/atg/tests/test-245.yaml new file mode 100755 index 0000000..6655033 --- /dev/null +++ b/user_service/keploy/atg/tests/test-245.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-245 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/05b4f265-384a-4be5-b72f-b3281743867c + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:40.009036878Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:40 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:40.0289607Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777180 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/05b4f265-384a-4be5-b72f-b3281743867c \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-246.yaml b/user_service/keploy/atg/tests/test-246.yaml new file mode 100755 index 0000000..26bdc86 --- /dev/null +++ b/user_service/keploy/atg/tests/test-246.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-246 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "bad_addr_user", "email": "bad_addr_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:43.226321753Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:43 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"bad_addr_user@example.in","id":"c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a","phone":null,"username":"bad_addr_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:43.297914829Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777183 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"bad_addr_user\", \"email\": \"bad_addr_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-247.yaml b/user_service/keploy/atg/tests/test-247.yaml new file mode 100755 index 0000000..3e61e03 --- /dev/null +++ b/user_service/keploy/atg/tests/test-247.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-247 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "bad_addr_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:44.037243125Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:44 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"bad_addr_user@example.in","id":"c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjMmZmMWNmNi0yYWE5LTQ0NmItYmI3YS01OTQ0ZWQ3YmYxNmEiLCJ1c2VybmFtZSI6ImJhZF9hZGRyX3VzZXIiLCJpYXQiOjE3NjI3NzcxODQsImV4cCI6MTc2NTM2OTE4NH0.UbFvpLQWobnB1SRzocrfJGtpfZZ2EU1Lfz_p6hyDx4Y","username":"bad_addr_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:44.098204491Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777184 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"bad_addr_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-248.yaml b/user_service/keploy/atg/tests/test-248.yaml new file mode 100755 index 0000000..a965947 --- /dev/null +++ b/user_service/keploy/atg/tests/test-248.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-248 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "49" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "123 Some Street", "city": "Some City"}' + timestamp: 2025-11-10T12:19:44.786088453Z + resp: + status_code: 400 + header: + Content-Length: "36" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:44 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Missing required fields"} + status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:44.787406902Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777184 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a/addresses \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"line1\": \"123 Some Street\", \"city\": \"Some City\"}" diff --git a/user_service/keploy/atg/tests/test-249.yaml b/user_service/keploy/atg/tests/test-249.yaml new file mode 100755 index 0000000..46fc71d --- /dev/null +++ b/user_service/keploy/atg/tests/test-249.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-249 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:45.544543219Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:45 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:45.569054393Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777185 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/c2ff1cf6-2aa9-446b-bb7a-5944ed7bf16a \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-25.yaml b/user_service/keploy/atg/tests/test-25.yaml new file mode 100755 index 0000000..f3ea21a --- /dev/null +++ b/user_service/keploy/atg/tests/test-25.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-25 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:56.210383555Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:56 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODM2LCJleHAiOjE3NjUzNjg4MzZ9.vXL-B5iPPnoyNRTOTB6gPLfqhc7NCJwlf3uPjqCaEx4","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:56.270579254Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776836 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-250.yaml b/user_service/keploy/atg/tests/test-250.yaml new file mode 100755 index 0000000..f4f3faa --- /dev/null +++ b/user_service/keploy/atg/tests/test-250.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-250 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "100" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_prod", "email": "temp_user_for_prod@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:46.35960993Z + resp: + status_code: 201 + header: + Content-Length: "131" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:46 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_prod@example.in","id":"0381a46c-7bc7-4054-b914-322bb96c2cbf","phone":null,"username":"temp_user_for_prod"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:46.437465004Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777186 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"temp_user_for_prod\", \"email\": \"temp_user_for_prod@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-251.yaml b/user_service/keploy/atg/tests/test-251.yaml new file mode 100755 index 0000000..ae50fb8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-251.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-251 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "58" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_prod", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:47.113364046Z + resp: + status_code: 200 + header: + Content-Length: "360" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_prod@example.in","id":"0381a46c-7bc7-4054-b914-322bb96c2cbf","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwMzgxYTQ2Yy03YmM3LTQwNTQtYjkxNC0zMjJiYjk2YzJjYmYiLCJ1c2VybmFtZSI6InRlbXBfdXNlcl9mb3JfcHJvZCIsImlhdCI6MTc2Mjc3NzE4NywiZXhwIjoxNzY1MzY5MTg3fQ.qaEatLtaz2IH893NdOuujOFMZDQqHLN1aTLtS8dgZHI","username":"temp_user_for_prod"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:47.172867014Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777187 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"temp_user_for_prod\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-252.yaml b/user_service/keploy/atg/tests/test-252.yaml new file mode 100755 index 0000000..4b6705f --- /dev/null +++ b/user_service/keploy/atg/tests/test-252.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-252 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/00000000-0000-0000-0000-000000000000 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:47.864287057Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:47.865538396Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777187 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products/00000000-0000-0000-0000-000000000000 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-253.yaml b/user_service/keploy/atg/tests/test-253.yaml new file mode 100755 index 0000000..9a048b8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-253.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-253 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/0381a46c-7bc7-4054-b914-322bb96c2cbf + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:48.672742563Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:48.697061897Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777188 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/0381a46c-7bc7-4054-b914-322bb96c2cbf \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-254.yaml b/user_service/keploy/atg/tests/test-254.yaml new file mode 100755 index 0000000..d178afb --- /dev/null +++ b/user_service/keploy/atg/tests/test-254.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-254 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "98" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_res", "email": "temp_user_for_res@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:49.511117426Z + resp: + status_code: 201 + header: + Content-Length: "129" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:49 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_res@example.in","id":"148553f6-5ee8-4cde-a1e7-f266507ce08e","phone":null,"username":"temp_user_for_res"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:49.585371151Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777189 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"temp_user_for_res\", \"email\": \"temp_user_for_res@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-255.yaml b/user_service/keploy/atg/tests/test-255.yaml new file mode 100755 index 0000000..964eb94 --- /dev/null +++ b/user_service/keploy/atg/tests/test-255.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-255 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "57" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_res", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:50.259256902Z + resp: + status_code: 200 + header: + Content-Length: "356" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:50 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_res@example.in","id":"148553f6-5ee8-4cde-a1e7-f266507ce08e","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNDg1NTNmNi01ZWU4LTRjZGUtYTFlNy1mMjY2NTA3Y2UwOGUiLCJ1c2VybmFtZSI6InRlbXBfdXNlcl9mb3JfcmVzIiwiaWF0IjoxNzYyNzc3MTkwLCJleHAiOjE3NjUzNjkxOTB9.b-UVdUVwMxkoh4MIJRRKtVemhHx1ePuGhp0w3XsWx88","username":"temp_user_for_res"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:50.317331702Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777190 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"temp_user_for_res\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-256.yaml b/user_service/keploy/atg/tests/test-256.yaml new file mode 100755 index 0000000..a95df18 --- /dev/null +++ b/user_service/keploy/atg/tests/test-256.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-256 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/00000000-0000-0000-0000-000000000000/reserve + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "15" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1}' + timestamp: 2025-11-10T12:19:51.087647791Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:51.08899886Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777191 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/products/00000000-0000-0000-0000-000000000000/reserve \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"quantity\": 1}" diff --git a/user_service/keploy/atg/tests/test-257.yaml b/user_service/keploy/atg/tests/test-257.yaml new file mode 100755 index 0000000..a86a412 --- /dev/null +++ b/user_service/keploy/atg/tests/test-257.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-257 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/148553f6-5ee8-4cde-a1e7-f266507ce08e + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:51.838118218Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:51.862708371Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777191 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/148553f6-5ee8-4cde-a1e7-f266507ce08e \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-258.yaml b/user_service/keploy/atg/tests/test-258.yaml new file mode 100755 index 0000000..3eab915 --- /dev/null +++ b/user_service/keploy/atg/tests/test-258.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-258 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "98" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_rel", "email": "temp_user_for_rel@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:52.648531209Z + resp: + status_code: 201 + header: + Content-Length: "129" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:52 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_rel@example.in","id":"042f99f1-0e36-466b-a245-da494416f551","phone":null,"username":"temp_user_for_rel"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:52.721842051Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777192 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"temp_user_for_rel\", \"email\": \"temp_user_for_rel@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-259.yaml b/user_service/keploy/atg/tests/test-259.yaml new file mode 100755 index 0000000..f76c1d7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-259.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-259 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "57" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_for_rel", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:53.429104123Z + resp: + status_code: 200 + header: + Content-Length: "356" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:53 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"temp_user_for_rel@example.in","id":"042f99f1-0e36-466b-a245-da494416f551","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwNDJmOTlmMS0wZTM2LTQ2NmItYTI0NS1kYTQ5NDQxNmY1NTEiLCJ1c2VybmFtZSI6InRlbXBfdXNlcl9mb3JfcmVsIiwiaWF0IjoxNzYyNzc3MTkzLCJleHAiOjE3NjUzNjkxOTN9.kbmH52KgFH4gWc1jNnOdGxYuwuYxK1a_VPJNbCvVPkc","username":"temp_user_for_rel"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:53.488559862Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777193 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"temp_user_for_rel\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-26.yaml b/user_service/keploy/atg/tests/test-26.yaml new file mode 100755 index 0000000..42846c9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-26.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-26 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "anjali_noaddr_01", "email": "anjali.noaddr.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:57.109438214Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:57 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:57.110704424Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776837 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"anjali_noaddr_01\", \"email\": \"anjali.noaddr.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-260.yaml b/user_service/keploy/atg/tests/test-260.yaml new file mode 100755 index 0000000..ce105ba --- /dev/null +++ b/user_service/keploy/atg/tests/test-260.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-260 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/00000000-0000-0000-0000-000000000000/release + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "15" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1}' + timestamp: 2025-11-10T12:19:54.171112061Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:19:54 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:54.172206372Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777194 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/products/00000000-0000-0000-0000-000000000000/release \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"quantity\": 1}" diff --git a/user_service/keploy/atg/tests/test-261.yaml b/user_service/keploy/atg/tests/test-261.yaml new file mode 100755 index 0000000..c600e84 --- /dev/null +++ b/user_service/keploy/atg/tests/test-261.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-261 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/042f99f1-0e36-466b-a245-da494416f551 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:54.991946586Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:55 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:55.019094918Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777195 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/042f99f1-0e36-466b-a245-da494416f551 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-262.yaml b/user_service/keploy/atg/tests/test-262.yaml new file mode 100755 index 0000000..2eecec2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-262.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-262 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "2" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{}' + timestamp: 2025-11-10T12:19:55.819555604Z + resp: + status_code: 400 + header: + Content-Length: "36" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:55 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Missing required fields"} + status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:55.820723263Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777195 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{}" diff --git a/user_service/keploy/atg/tests/test-263.yaml b/user_service/keploy/atg/tests/test-263.yaml new file mode 100755 index 0000000..1d726a9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-263.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-263 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "82" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "invalidemailuser", "email": "invalid-email", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:56.628815957Z + resp: + status_code: 400 + header: + Content-Length: "26" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:56 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"invalid email"} + status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:56.630135445Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777196 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"invalidemailuser\", \"email\": \"invalid-email\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-264.yaml b/user_service/keploy/atg/tests/test-264.yaml new file mode 100755 index 0000000..86911cb --- /dev/null +++ b/user_service/keploy/atg/tests/test-264.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-264 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "86" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "kavita_pune", "email": "kavita_pune@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:57.400499617Z + resp: + status_code: 201 + header: + Content-Length: "117" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:57 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"kavita_pune@example.in","id":"254e2932-7422-46fb-81f5-9f9c3b854f14","phone":null,"username":"kavita_pune"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:57.479467542Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777197 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"kavita_pune\", \"email\": \"kavita_pune@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-265.yaml b/user_service/keploy/atg/tests/test-265.yaml new file mode 100755 index 0000000..447fd8f --- /dev/null +++ b/user_service/keploy/atg/tests/test-265.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-265 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "51" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "kavita_pune", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:19:58.365607437Z + resp: + status_code: 200 + header: + Content-Length: "336" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:58 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"kavita_pune@example.in","id":"254e2932-7422-46fb-81f5-9f9c3b854f14","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyNTRlMjkzMi03NDIyLTQ2ZmItODFmNS05ZjljM2I4NTRmMTQiLCJ1c2VybmFtZSI6Imthdml0YV9wdW5lIiwiaWF0IjoxNzYyNzc3MTk4LCJleHAiOjE3NjUzNjkxOTh9.rxnUZJBRbp1rmmU9LwTP6ZzC0vak1lpaJGzr3ZtM1vg","username":"kavita_pune"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:58.425561972Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777198 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"kavita_pune\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-266.yaml b/user_service/keploy/atg/tests/test-266.yaml new file mode 100755 index 0000000..1e96855 --- /dev/null +++ b/user_service/keploy/atg/tests/test-266.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-266 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/254e2932-7422-46fb-81f5-9f9c3b854f14/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "162" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Flat 5, Koregaon Park", "city": "Pune", "state": "Maharashtra", "postal_code": "411001", "country": "IN", "phone": "+919890098900", "is_default": true}' + timestamp: 2025-11-10T12:19:59.165457621Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:59 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"7804329b-59a5-4b0b-bc59-386a890cb09d"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:59.193263547Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777199 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/254e2932-7422-46fb-81f5-9f9c3b854f14/addresses \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"line1\": \"Flat 5, Koregaon Park\", \"city\": \"Pune\", \"state\": \"Maharashtra\", \"postal_code\": \"411001\", \"country\": \"IN\", \"phone\": \"+919890098900\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-267.yaml b/user_service/keploy/atg/tests/test-267.yaml new file mode 100755 index 0000000..306ea38 --- /dev/null +++ b/user_service/keploy/atg/tests/test-267.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-267 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/254e2932-7422-46fb-81f5-9f9c3b854f14 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:19:59.905076782Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:19:59 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:19:59.931007583Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777199 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/254e2932-7422-46fb-81f5-9f9c3b854f14 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-268.yaml b/user_service/keploy/atg/tests/test-268.yaml new file mode 100755 index 0000000..d0cf6cd --- /dev/null +++ b/user_service/keploy/atg/tests/test-268.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-268 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "88" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "amit_kolkata", "email": "amit_kolkata@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:01.937989292Z + resp: + status_code: 201 + header: + Content-Length: "119" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:02 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"amit_kolkata@example.in","id":"17dfef3d-5760-456f-a044-ca2226645b00","phone":null,"username":"amit_kolkata"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:02.015133371Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777202 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"amit_kolkata\", \"email\": \"amit_kolkata@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-269.yaml b/user_service/keploy/atg/tests/test-269.yaml new file mode 100755 index 0000000..d2aab49 --- /dev/null +++ b/user_service/keploy/atg/tests/test-269.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-269 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "amit_kolkata", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:02.820629718Z + resp: + status_code: 200 + header: + Content-Length: "340" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:02 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"amit_kolkata@example.in","id":"17dfef3d-5760-456f-a044-ca2226645b00","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxN2RmZWYzZC01NzYwLTQ1NmYtYTA0NC1jYTIyMjY2NDViMDAiLCJ1c2VybmFtZSI6ImFtaXRfa29sa2F0YSIsImlhdCI6MTc2Mjc3NzIwMiwiZXhwIjoxNzY1MzY5MjAyfQ.gYldK80Y6P4-P4y70SxpG2uBUGsF5vk_kJM9K1fJUFQ","username":"amit_kolkata"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:02.882628357Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777202 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"amit_kolkata\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-27.yaml b/user_service/keploy/atg/tests/test-27.yaml new file mode 100755 index 0000000..7a4ff67 --- /dev/null +++ b/user_service/keploy/atg/tests/test-27.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-27 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:59.496257555Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:59 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODM5LCJleHAiOjE3NjUzNjg4Mzl9.EdlZ6AKQQEqccoI4FMOdUYrGbF0B5_sHYntMddZN8RE","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:59.553974926Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776839 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-270.yaml b/user_service/keploy/atg/tests/test-270.yaml new file mode 100755 index 0000000..57d66ae --- /dev/null +++ b/user_service/keploy/atg/tests/test-270.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-270 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/17dfef3d-5760-456f-a044-ca2226645b00/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "159" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "15, Park Street", "city": "Kolkata", "state": "West Bengal", "postal_code": "700016", "country": "IN", "phone": "+919830098300", "is_default": true}' + timestamp: 2025-11-10T12:20:03.582757751Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:03 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"9c7ca41a-0722-4b62-ae20-0ec8d3ce0ce5"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:03.607359824Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777203 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/17dfef3d-5760-456f-a044-ca2226645b00/addresses \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"line1\": \"15, Park Street\", \"city\": \"Kolkata\", \"state\": \"West Bengal\", \"postal_code\": \"700016\", \"country\": \"IN\", \"phone\": \"+919830098300\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-271.yaml b/user_service/keploy/atg/tests/test-271.yaml new file mode 100755 index 0000000..1344bc6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-271.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-271 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/17dfef3d-5760-456f-a044-ca2226645b00 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:04.401426838Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:04 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:04.425048659Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777204 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/17dfef3d-5760-456f-a044-ca2226645b00 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-272.yaml b/user_service/keploy/atg/tests/test-272.yaml new file mode 100755 index 0000000..62f52df --- /dev/null +++ b/user_service/keploy/atg/tests/test-272.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-272 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "94" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "lakshmi_chennai", "email": "lakshmi_chennai@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:06.26111013Z + resp: + status_code: 201 + header: + Content-Length: "125" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"lakshmi_chennai@example.in","id":"25712cc5-6d68-48ea-8437-333cf1ad5cb2","phone":null,"username":"lakshmi_chennai"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:06.333209244Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777206 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"lakshmi_chennai\", \"email\": \"lakshmi_chennai@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-273.yaml b/user_service/keploy/atg/tests/test-273.yaml new file mode 100755 index 0000000..3c1aba6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-273.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-273 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "55" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "lakshmi_chennai", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:07.026192419Z + resp: + status_code: 200 + header: + Content-Length: "350" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"lakshmi_chennai@example.in","id":"25712cc5-6d68-48ea-8437-333cf1ad5cb2","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyNTcxMmNjNS02ZDY4LTQ4ZWEtODQzNy0zMzNjZjFhZDVjYjIiLCJ1c2VybmFtZSI6Imxha3NobWlfY2hlbm5haSIsImlhdCI6MTc2Mjc3NzIwNywiZXhwIjoxNzY1MzY5MjA3fQ.VPty348Ux41Ii4qSvzsmqnvMRiUBE5smFB2Jg5w_MhY","username":"lakshmi_chennai"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:07.087483863Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777207 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"lakshmi_chennai\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-274.yaml b/user_service/keploy/atg/tests/test-274.yaml new file mode 100755 index 0000000..bc263ed --- /dev/null +++ b/user_service/keploy/atg/tests/test-274.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-274 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/25712cc5-6d68-48ea-8437-333cf1ad5cb2/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "157" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "No 10, T Nagar", "city": "Chennai", "state": "Tamil Nadu", "postal_code": "600017", "country": "IN", "phone": "+919840098400", "is_default": true}' + timestamp: 2025-11-10T12:20:07.774756887Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"5d4c1ed2-bfe3-48b3-9941-c099827911e3"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:07.796735243Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777207 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/25712cc5-6d68-48ea-8437-333cf1ad5cb2/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"No 10, T Nagar\", \"city\": \"Chennai\", \"state\": \"Tamil Nadu\", \"postal_code\": \"600017\", \"country\": \"IN\", \"phone\": \"+919840098400\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-275.yaml b/user_service/keploy/atg/tests/test-275.yaml new file mode 100755 index 0000000..88bc645 --- /dev/null +++ b/user_service/keploy/atg/tests/test-275.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-275 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/25712cc5-6d68-48ea-8437-333cf1ad5cb2 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:08.617701351Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:08 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:08.642254364Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777208 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/25712cc5-6d68-48ea-8437-333cf1ad5cb2 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-276.yaml b/user_service/keploy/atg/tests/test-276.yaml new file mode 100755 index 0000000..20253dd --- /dev/null +++ b/user_service/keploy/atg/tests/test-276.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-276 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "84" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "rajesh_hyd", "email": "rajesh_hyd@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:10.69690258Z + resp: + status_code: 201 + header: + Content-Length: "115" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:10 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"rajesh_hyd@example.in","id":"e2fdc4cd-cd10-43c3-b083-5b4050c1ab43","phone":null,"username":"rajesh_hyd"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:10.769236771Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777210 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"rajesh_hyd\", \"email\": \"rajesh_hyd@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-277.yaml b/user_service/keploy/atg/tests/test-277.yaml new file mode 100755 index 0000000..ff7424d --- /dev/null +++ b/user_service/keploy/atg/tests/test-277.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-277 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "50" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "rajesh_hyd", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:11.458828107Z + resp: + status_code: 200 + header: + Content-Length: "333" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:11 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"rajesh_hyd@example.in","id":"e2fdc4cd-cd10-43c3-b083-5b4050c1ab43","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlMmZkYzRjZC1jZDEwLTQzYzMtYjA4My01YjQwNTBjMWFiNDMiLCJ1c2VybmFtZSI6InJhamVzaF9oeWQiLCJpYXQiOjE3NjI3NzcyMTEsImV4cCI6MTc2NTM2OTIxMX0.keeFD3HcINyn5WxOx5w7aOJA-3clgRhXXoufU6oEGUE","username":"rajesh_hyd"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:11.519101479Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777211 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"rajesh_hyd\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-278.yaml b/user_service/keploy/atg/tests/test-278.yaml new file mode 100755 index 0000000..000b599 --- /dev/null +++ b/user_service/keploy/atg/tests/test-278.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-278 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/e2fdc4cd-cd10-43c3-b083-5b4050c1ab43/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "168" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Banjara Hills, Road No 1", "city": "Hyderabad", "state": "Telangana", "postal_code": "500034", "country": "IN", "phone": "+919848012345", "is_default": true}' + timestamp: 2025-11-10T12:20:12.266200702Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"1320d911-df66-4ba2-9487-538acb3f8028"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:12.290827766Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777212 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/e2fdc4cd-cd10-43c3-b083-5b4050c1ab43/addresses \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"line1\": \"Banjara Hills, Road No 1\", \"city\": \"Hyderabad\", \"state\": \"Telangana\", \"postal_code\": \"500034\", \"country\": \"IN\", \"phone\": \"+919848012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-279.yaml b/user_service/keploy/atg/tests/test-279.yaml new file mode 100755 index 0000000..e10a372 --- /dev/null +++ b/user_service/keploy/atg/tests/test-279.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-279 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/e2fdc4cd-cd10-43c3-b083-5b4050c1ab43 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:13.018367552Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:13 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:13.041799695Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777213 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/e2fdc4cd-cd10-43c3-b083-5b4050c1ab43 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-28.yaml b/user_service/keploy/atg/tests/test-28.yaml new file mode 100755 index 0000000..5d51009 --- /dev/null +++ b/user_service/keploy/atg/tests/test-28.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-28 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:00.230179184Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:00 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:00.231454012Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776840 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-280.yaml b/user_service/keploy/atg/tests/test-280.yaml new file mode 100755 index 0000000..e7a568c --- /dev/null +++ b/user_service/keploy/atg/tests/test-280.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-280 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "bad_addr_order", "email": "bad_addr_order@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:20.771626245Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:20 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"bad_addr_order@example.in","id":"a60a25ea-934f-48c7-85f1-ea694cbcbac6","phone":null,"username":"bad_addr_order"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:20.839018457Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777220 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"bad_addr_order\", \"email\": \"bad_addr_order@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-281.yaml b/user_service/keploy/atg/tests/test-281.yaml new file mode 100755 index 0000000..c2a14b5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-281.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-281 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "bad_addr_order", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:21.511661969Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"bad_addr_order@example.in","id":"a60a25ea-934f-48c7-85f1-ea694cbcbac6","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhNjBhMjVlYS05MzRmLTQ4YzctODVmMS1lYTY5NGNiY2JhYzYiLCJ1c2VybmFtZSI6ImJhZF9hZGRyX29yZGVyIiwiaWF0IjoxNzYyNzc3MjIxLCJleHAiOjE3NjUzNjkyMjF9.jSrgKuq3MEwrBUqM0-8p-sFQLGrrOW5NqAeDQCQPegI","username":"bad_addr_order"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:21.572537278Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777221 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"bad_addr_order\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-282.yaml b/user_service/keploy/atg/tests/test-282.yaml new file mode 100755 index 0000000..843abba --- /dev/null +++ b/user_service/keploy/atg/tests/test-282.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-282 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:22.251752814Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:20:22 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:22.253047323Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777222 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-283.yaml b/user_service/keploy/atg/tests/test-283.yaml new file mode 100755 index 0000000..c1d1df4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-283.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-283 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a60a25ea-934f-48c7-85f1-ea694cbcbac6 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:22.989311049Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:23.013921262Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777223 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/a60a25ea-934f-48c7-85f1-ea694cbcbac6 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-284.yaml b/user_service/keploy/atg/tests/test-284.yaml new file mode 100755 index 0000000..f670dc9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-284.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-284 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "102" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "auth_user_for_order", "email": "auth_user_for_order@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:25.119383104Z + resp: + status_code: 201 + header: + Content-Length: "133" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:25 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"auth_user_for_order@example.in","id":"08d3e990-230d-42c4-afa7-b7582bcec893","phone":null,"username":"auth_user_for_order"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:25.193057905Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777225 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"auth_user_for_order\", \"email\": \"auth_user_for_order@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-285.yaml b/user_service/keploy/atg/tests/test-285.yaml new file mode 100755 index 0000000..4fbfaf4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-285.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-285 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "59" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "auth_user_for_order", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:25.938439825Z + resp: + status_code: 200 + header: + Content-Length: "363" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:25 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"auth_user_for_order@example.in","id":"08d3e990-230d-42c4-afa7-b7582bcec893","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwOGQzZTk5MC0yMzBkLTQyYzQtYWZhNy1iNzU4MmJjZWM4OTMiLCJ1c2VybmFtZSI6ImF1dGhfdXNlcl9mb3Jfb3JkZXIiLCJpYXQiOjE3NjI3NzcyMjUsImV4cCI6MTc2NTM2OTIyNX0.vG96d8ROQx5m6vwar6RMGgbuYzkdgqB94Yv1QDTQ85E","username":"auth_user_for_order"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:25.998852887Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777225 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"auth_user_for_order\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-286.yaml b/user_service/keploy/atg/tests/test-286.yaml new file mode 100755 index 0000000..ca7e445 --- /dev/null +++ b/user_service/keploy/atg/tests/test-286.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-286 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:26.669014321Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:20:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:26.670336289Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777226 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-287.yaml b/user_service/keploy/atg/tests/test-287.yaml new file mode 100755 index 0000000..da163c7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-287.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-287 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/08d3e990-230d-42c4-afa7-b7582bcec893 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:27.476660628Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:27 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:27.500726945Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777227 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/08d3e990-230d-42c4-afa7-b7582bcec893 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-288.yaml b/user_service/keploy/atg/tests/test-288.yaml new file mode 100755 index 0000000..c955373 --- /dev/null +++ b/user_service/keploy/atg/tests/test-288.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-288 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "neg_release_user", "email": "neg_release_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:30.092023704Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:30 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"neg_release_user@example.in","id":"84fe4104-56ff-478f-a4eb-aa1c7d9833dd","phone":null,"username":"neg_release_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:30.165782413Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777230 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"neg_release_user\", \"email\": \"neg_release_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-289.yaml b/user_service/keploy/atg/tests/test-289.yaml new file mode 100755 index 0000000..eade045 --- /dev/null +++ b/user_service/keploy/atg/tests/test-289.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-289 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "neg_release_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:30.894652096Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:30 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"neg_release_user@example.in","id":"84fe4104-56ff-478f-a4eb-aa1c7d9833dd","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4NGZlNDEwNC01NmZmLTQ3OGYtYTRlYi1hYTFjN2Q5ODMzZGQiLCJ1c2VybmFtZSI6Im5lZ19yZWxlYXNlX3VzZXIiLCJpYXQiOjE3NjI3NzcyMzAsImV4cCI6MTc2NTM2OTIzMH0.yg4HJnKDXUMDWX55r3GR5B7QQ8JydYgRrp4pcclCfgA","username":"neg_release_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:30.957534646Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777230 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"neg_release_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-29.yaml b/user_service/keploy/atg/tests/test-29.yaml new file mode 100755 index 0000000..9724b5c --- /dev/null +++ b/user_service/keploy/atg/tests/test-29.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-29 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:02.617040769Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:02 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODQyLCJleHAiOjE3NjUzNjg4NDJ9.jjTk3VC10dQywX7yCGONG9e6wVQMHWoBcXDv0QuXhuQ","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:02.677764345Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776842 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-290.yaml b/user_service/keploy/atg/tests/test-290.yaml new file mode 100755 index 0000000..4236a9b --- /dev/null +++ b/user_service/keploy/atg/tests/test-290.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-290 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:31.68385952Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:20:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:31.68500708Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777231 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-291.yaml b/user_service/keploy/atg/tests/test-291.yaml new file mode 100755 index 0000000..9cbcee2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-291.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-291 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/84fe4104-56ff-478f-a4eb-aa1c7d9833dd + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:32.419083308Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:32.439644666Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777232 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/84fe4104-56ff-478f-a4eb-aa1c7d9833dd \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-292.yaml b/user_service/keploy/atg/tests/test-292.yaml new file mode 100755 index 0000000..ed760ee --- /dev/null +++ b/user_service/keploy/atg/tests/test-292.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-292 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "neg_reserve_user", "email": "neg_reserve_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:34.815116876Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"neg_reserve_user@example.in","id":"868072e7-73dc-4416-a6ea-28c2819950b2","phone":null,"username":"neg_reserve_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:34.888933145Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777234 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"neg_reserve_user\", \"email\": \"neg_reserve_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-293.yaml b/user_service/keploy/atg/tests/test-293.yaml new file mode 100755 index 0000000..4ef0f76 --- /dev/null +++ b/user_service/keploy/atg/tests/test-293.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-293 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "neg_reserve_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:35.621954724Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:35 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"neg_reserve_user@example.in","id":"868072e7-73dc-4416-a6ea-28c2819950b2","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4NjgwNzJlNy03M2RjLTQ0MTYtYTZlYS0yOGMyODE5OTUwYjIiLCJ1c2VybmFtZSI6Im5lZ19yZXNlcnZlX3VzZXIiLCJpYXQiOjE3NjI3NzcyMzUsImV4cCI6MTc2NTM2OTIzNX0.uObWgKNPjsloUUypko5n7uX8R6acylYLC7tJJjMgvTc","username":"neg_reserve_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:35.681752171Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777235 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"neg_reserve_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-294.yaml b/user_service/keploy/atg/tests/test-294.yaml new file mode 100755 index 0000000..ddfa679 --- /dev/null +++ b/user_service/keploy/atg/tests/test-294.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-294 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:36.390693352Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:20:36 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:36.391906901Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777236 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-295.yaml b/user_service/keploy/atg/tests/test-295.yaml new file mode 100755 index 0000000..9bd505b --- /dev/null +++ b/user_service/keploy/atg/tests/test-295.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-295 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/868072e7-73dc-4416-a6ea-28c2819950b2 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:37.148481522Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:37 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:37.170619725Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777237 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/868072e7-73dc-4416-a6ea-28c2819950b2 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-296.yaml b/user_service/keploy/atg/tests/test-296.yaml new file mode 100755 index 0000000..ddbbb5d --- /dev/null +++ b/user_service/keploy/atg/tests/test-296.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-296 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "86" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "enrich_user", "email": "enrich_user@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:39.765907582Z + resp: + status_code: 201 + header: + Content-Length: "117" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"enrich_user@example.in","id":"5d6a84ae-2dbf-422c-9bbd-134aa47f0127","phone":null,"username":"enrich_user"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:39.83880966Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777239 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"enrich_user\", \"email\": \"enrich_user@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-297.yaml b/user_service/keploy/atg/tests/test-297.yaml new file mode 100755 index 0000000..70e1ca0 --- /dev/null +++ b/user_service/keploy/atg/tests/test-297.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-297 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "51" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "enrich_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:40.583947928Z + resp: + status_code: 200 + header: + Content-Length: "336" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:40 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"enrich_user@example.in","id":"5d6a84ae-2dbf-422c-9bbd-134aa47f0127","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZDZhODRhZS0yZGJmLTQyMmMtOWJiZC0xMzRhYTQ3ZjAxMjciLCJ1c2VybmFtZSI6ImVucmljaF91c2VyIiwiaWF0IjoxNzYyNzc3MjQwLCJleHAiOjE3NjUzNjkyNDB9.4abZz39u7-6z1Tf8rIZrQ-xM87eUP-SG-_PB1_j8BF8","username":"enrich_user"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:40.646606052Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777240 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"enrich_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-298.yaml b/user_service/keploy/atg/tests/test-298.yaml new file mode 100755 index 0000000..be7f2ce --- /dev/null +++ b/user_service/keploy/atg/tests/test-298.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-298 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5d6a84ae-2dbf-422c-9bbd-134aa47f0127/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "155" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "123 MG Road", "city": "Bengaluru", "state": "Karnataka", "postal_code": "560001", "country": "IN", "phone": "+919876543210", "is_default": true}' + timestamp: 2025-11-10T12:20:41.32976053Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:41 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"c0f33e54-1f63-4c31-bc63-5dd4f8683457"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:41.350882574Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777241 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/5d6a84ae-2dbf-422c-9bbd-134aa47f0127/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"123 MG Road\", \"city\": \"Bengaluru\", \"state\": \"Karnataka\", \"postal_code\": \"560001\", \"country\": \"IN\", \"phone\": \"+919876543210\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-299.yaml b/user_service/keploy/atg/tests/test-299.yaml new file mode 100755 index 0000000..964fb72 --- /dev/null +++ b/user_service/keploy/atg/tests/test-299.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-299 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:42.149887539Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:20:42 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:42.151111949Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777242 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-3.yaml b/user_service/keploy/atg/tests/test-3.yaml new file mode 100755 index 0000000..58b4ae7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-3.yaml @@ -0,0 +1,45 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-3 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Content-Length: "116" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "rohan_mehta_{{$randomInt}}", "email": "rohan.mehta.{{$randomInt}}@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:11:14.694278623Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:11:14 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:11:14.695461321Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776674 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"rohan_mehta_{{$randomInt}}\", \"email\": \"rohan.mehta.{{$randomInt}}@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-30.yaml b/user_service/keploy/atg/tests/test-30.yaml new file mode 100755 index 0000000..b3a25cc --- /dev/null +++ b/user_service/keploy/atg/tests/test-30.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-30 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:03.3818604Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:03 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:03.383201669Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776843 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-300.yaml b/user_service/keploy/atg/tests/test-300.yaml new file mode 100755 index 0000000..e6ce2d0 --- /dev/null +++ b/user_service/keploy/atg/tests/test-300.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-300 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5d6a84ae-2dbf-422c-9bbd-134aa47f0127 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:42.902228327Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:42 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:42.926655881Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777242 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/5d6a84ae-2dbf-422c-9bbd-134aa47f0127 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-301.yaml b/user_service/keploy/atg/tests/test-301.yaml new file mode 100755 index 0000000..6c6842b --- /dev/null +++ b/user_service/keploy/atg/tests/test-301.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-301 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "94" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "immediate_login", "email": "immediate_login@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:45.524507274Z + resp: + status_code: 201 + header: + Content-Length: "125" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:45 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"immediate_login@example.in","id":"f8de48e0-82cc-4004-8fb3-3639ebb3be22","phone":null,"username":"immediate_login"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:45.593074398Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777245 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"immediate_login\", \"email\": \"immediate_login@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-302.yaml b/user_service/keploy/atg/tests/test-302.yaml new file mode 100755 index 0000000..4fbd979 --- /dev/null +++ b/user_service/keploy/atg/tests/test-302.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-302 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "55" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "immediate_login", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:46.346542946Z + resp: + status_code: 200 + header: + Content-Length: "350" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:46 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"immediate_login@example.in","id":"f8de48e0-82cc-4004-8fb3-3639ebb3be22","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmOGRlNDhlMC04MmNjLTQwMDQtOGZiMy0zNjM5ZWJiM2JlMjIiLCJ1c2VybmFtZSI6ImltbWVkaWF0ZV9sb2dpbiIsImlhdCI6MTc2Mjc3NzI0NiwiZXhwIjoxNzY1MzY5MjQ2fQ.UttTVzMbQFvf6YA4BXBIxMXB8dI36vLk4VaqSn38KJc","username":"immediate_login"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:46.406480784Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777246 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"immediate_login\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-303.yaml b/user_service/keploy/atg/tests/test-303.yaml new file mode 100755 index 0000000..6c29768 --- /dev/null +++ b/user_service/keploy/atg/tests/test-303.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-303 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/f8de48e0-82cc-4004-8fb3-3639ebb3be22 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:47.136994206Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:47.161417281Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777247 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/f8de48e0-82cc-4004-8fb3-3639ebb3be22 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-304.yaml b/user_service/keploy/atg/tests/test-304.yaml new file mode 100755 index 0000000..0d158e3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-304.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-304 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "delete_verify", "email": "delete_verify@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:47.97339001Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"delete_verify@example.in","id":"f820469b-ddda-4dd0-866d-f240033e6b6a","phone":null,"username":"delete_verify"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:48.051123407Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777248 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"delete_verify\", \"email\": \"delete_verify@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-305.yaml b/user_service/keploy/atg/tests/test-305.yaml new file mode 100755 index 0000000..64275bb --- /dev/null +++ b/user_service/keploy/atg/tests/test-305.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-305 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "delete_verify", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:48.723898374Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"delete_verify@example.in","id":"f820469b-ddda-4dd0-866d-f240033e6b6a","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmODIwNDY5Yi1kZGRhLTRkZDAtODY2ZC1mMjQwMDMzZTZiNmEiLCJ1c2VybmFtZSI6ImRlbGV0ZV92ZXJpZnkiLCJpYXQiOjE3NjI3NzcyNDgsImV4cCI6MTc2NTM2OTI0OH0.Rq08PZDTdBAmZM4BRJXmQKxEhmRX8mF1nDFu5l7ZEHo","username":"delete_verify"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:48.783401885Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777248 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"delete_verify\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-306.yaml b/user_service/keploy/atg/tests/test-306.yaml new file mode 100755 index 0000000..d8d52b3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-306.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-306 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/f820469b-ddda-4dd0-866d-f240033e6b6a + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:49.496275826Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:49 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:49.515523984Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777249 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/f820469b-ddda-4dd0-866d-f240033e6b6a \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-307.yaml b/user_service/keploy/atg/tests/test-307.yaml new file mode 100755 index 0000000..85fd740 --- /dev/null +++ b/user_service/keploy/atg/tests/test-307.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-307 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/f820469b-ddda-4dd0-866d-f240033e6b6a + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:50.23386575Z + resp: + status_code: 404 + header: + Content-Length: "27" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:50 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"User not found"} + status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:50.245646351Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777250 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/f820469b-ddda-4dd0-866d-f240033e6b6a \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-308.yaml b/user_service/keploy/atg/tests/test-308.yaml new file mode 100755 index 0000000..fe2e317 --- /dev/null +++ b/user_service/keploy/atg/tests/test-308.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-308 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "delete_verify", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:51.066435986Z + resp: + status_code: 401 + header: + Content-Length: "32" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Invalid credentials"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:51.085738054Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777251 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"delete_verify\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-309.yaml b/user_service/keploy/atg/tests/test-309.yaml new file mode 100755 index 0000000..b938d2a --- /dev/null +++ b/user_service/keploy/atg/tests/test-309.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-309 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "non_default_addr", "email": "non_default_addr@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:51.908022587Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"non_default_addr@example.in","id":"5be5ca18-7185-4cc1-847b-7a56eab9c349","phone":null,"username":"non_default_addr"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:51.979691836Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777251 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"non_default_addr\", \"email\": \"non_default_addr@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-31.yaml b/user_service/keploy/atg/tests/test-31.yaml new file mode 100755 index 0000000..8d31886 --- /dev/null +++ b/user_service/keploy/atg/tests/test-31.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-31 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:05.633904745Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:05 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODQ1LCJleHAiOjE3NjUzNjg4NDV9.9jIQkE6boqJK4ufa2E6AcjKb6lK8ncFzr-kIQu_FPlY","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:05.692291322Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776845 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-310.yaml b/user_service/keploy/atg/tests/test-310.yaml new file mode 100755 index 0000000..adbc7af --- /dev/null +++ b/user_service/keploy/atg/tests/test-310.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-310 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "non_default_addr", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:52.664626053Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:52 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"non_default_addr@example.in","id":"5be5ca18-7185-4cc1-847b-7a56eab9c349","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YmU1Y2ExOC03MTg1LTRjYzEtODQ3Yi03YTU2ZWFiOWMzNDkiLCJ1c2VybmFtZSI6Im5vbl9kZWZhdWx0X2FkZHIiLCJpYXQiOjE3NjI3NzcyNTIsImV4cCI6MTc2NTM2OTI1Mn0.OHXuxUtLgN7iqgOoIFbbbSR6YjGQGAdelKyggeJYTnk","username":"non_default_addr"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:52.723183361Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777252 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"non_default_addr\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-311.yaml b/user_service/keploy/atg/tests/test-311.yaml new file mode 100755 index 0000000..36c9c15 --- /dev/null +++ b/user_service/keploy/atg/tests/test-311.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-311 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5be5ca18-7185-4cc1-847b-7a56eab9c349/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "159" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Work Office, Infopark", "city": "Kochi", "state": "Kerala", "postal_code": "682030", "country": "IN", "phone": "+919847012345", "is_default": false}' + timestamp: 2025-11-10T12:20:53.396282297Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:53 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"afcc695f-bd55-46df-aff7-2687d3bf1e56"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:53.41746189Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777253 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/5be5ca18-7185-4cc1-847b-7a56eab9c349/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"line1\": \"Work Office, Infopark\", \"city\": \"Kochi\", \"state\": \"Kerala\", \"postal_code\": \"682030\", \"country\": \"IN\", \"phone\": \"+919847012345\", \"is_default\": false}" diff --git a/user_service/keploy/atg/tests/test-312.yaml b/user_service/keploy/atg/tests/test-312.yaml new file mode 100755 index 0000000..dfabd61 --- /dev/null +++ b/user_service/keploy/atg/tests/test-312.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-312 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5be5ca18-7185-4cc1-847b-7a56eab9c349/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:54.249370842Z + resp: + status_code: 200 + header: + Content-Length: "202" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:54 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + [{"city":"Kochi","country":"IN","id":"afcc695f-bd55-46df-aff7-2687d3bf1e56","is_default":0,"line1":"Work Office, Infopark","line2":null,"phone":"+919847012345","postal_code":"682030","state":"Kerala"}] + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:54.261243133Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777254 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/5be5ca18-7185-4cc1-847b-7a56eab9c349/addresses \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-313.yaml b/user_service/keploy/atg/tests/test-313.yaml new file mode 100755 index 0000000..7ebecda --- /dev/null +++ b/user_service/keploy/atg/tests/test-313.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-313 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5be5ca18-7185-4cc1-847b-7a56eab9c349 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:20:54.969382156Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:54 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:54.994420766Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777254 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/5be5ca18-7185-4cc1-847b-7a56eab9c349 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-314.yaml b/user_service/keploy/atg/tests/test-314.yaml new file mode 100755 index 0000000..0b6d330 --- /dev/null +++ b/user_service/keploy/atg/tests/test-314.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-314 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "146" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "userwithaveryverylongandunnecessarilycomplexusernamethatshouldstillbevalid", "email": "longuser@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:57.882592093Z + resp: + status_code: 400 + header: + Content-Length: "40" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:57 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"username must be 3-50 chars"} + status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:57.883938151Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777257 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"userwithaveryverylongandunnecessarilycomplexusernamethatshouldstillbevalid\", \"email\": \"longuser@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-315.yaml b/user_service/keploy/atg/tests/test-315.yaml new file mode 100755 index 0000000..b0f7ff8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-315.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-315 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "114" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "userwithaveryverylongandunnecessarilycomplexusernamethatshouldstillbevalid", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:20:58.636769429Z + resp: + status_code: 401 + header: + Content-Length: "32" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:20:58 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Invalid credentials"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:20:58.648168224Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777258 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"userwithaveryverylongandunnecessarilycomplexusernamethatshouldstillbevalid\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-316.yaml b/user_service/keploy/atg/tests/test-316.yaml new file mode 100755 index 0000000..85ce438 --- /dev/null +++ b/user_service/keploy/atg/tests/test-316.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-316 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "86" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "user_-.123", "email": "user.special@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:01.947789988Z + resp: + status_code: 201 + header: + Content-Length: "117" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:02 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"user.special@example.in","id":"a96df4d6-c21f-47ec-956f-48b8e7afe77b","phone":null,"username":"user_-.123"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:02.01923174Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777262 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"user_-.123\", \"email\": \"user.special@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-317.yaml b/user_service/keploy/atg/tests/test-317.yaml new file mode 100755 index 0000000..78992f4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-317.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-317 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "50" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "user_-.123", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:02.84461095Z + resp: + status_code: 200 + header: + Content-Length: "335" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:02 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"user.special@example.in","id":"a96df4d6-c21f-47ec-956f-48b8e7afe77b","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhOTZkZjRkNi1jMjFmLTQ3ZWMtOTU2Zi00OGI4ZTdhZmU3N2IiLCJ1c2VybmFtZSI6InVzZXJfLS4xMjMiLCJpYXQiOjE3NjI3NzcyNjIsImV4cCI6MTc2NTM2OTI2Mn0.bVbYUCAcdcRlfe7iKTbKktA9IJcScCZnqukOF-6Znt4","username":"user_-.123"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:02.903469256Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777262 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"user_-.123\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-318.yaml b/user_service/keploy/atg/tests/test-318.yaml new file mode 100755 index 0000000..32ec440 --- /dev/null +++ b/user_service/keploy/atg/tests/test-318.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-318 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a96df4d6-c21f-47ec-956f-48b8e7afe77b + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:03.708917484Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:03 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:03.731588494Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777263 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/a96df4d6-c21f-47ec-956f-48b8e7afe77b \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-319.yaml b/user_service/keploy/atg/tests/test-319.yaml new file mode 100755 index 0000000..c97fbc0 --- /dev/null +++ b/user_service/keploy/atg/tests/test-319.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-319 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "102" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "auth_user_for_other", "email": "auth_user_for_other@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:04.523329057Z + resp: + status_code: 201 + header: + Content-Length: "133" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:04 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"auth_user_for_other@example.in","id":"777b90d1-71b1-4983-942d-656250aac340","phone":null,"username":"auth_user_for_other"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:04.598499016Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777264 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"auth_user_for_other\", \"email\": \"auth_user_for_other@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-32.yaml b/user_service/keploy/atg/tests/test-32.yaml new file mode 100755 index 0000000..d2e09b3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-32.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-32 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/99999999-9999-9999-9999-999999999999 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:06.534977634Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:06.536287593Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776846 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products/99999999-9999-9999-9999-999999999999 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-320.yaml b/user_service/keploy/atg/tests/test-320.yaml new file mode 100755 index 0000000..75e616b --- /dev/null +++ b/user_service/keploy/atg/tests/test-320.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-320 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "order_user_other", "email": "order_user_other@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:05.342340343Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:05 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"order_user_other@example.in","id":"87d0e4a5-0b24-4869-bf5d-258cd2a17ff5","phone":null,"username":"order_user_other"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:05.413589795Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777265 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"order_user_other\", \"email\": \"order_user_other@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-321.yaml b/user_service/keploy/atg/tests/test-321.yaml new file mode 100755 index 0000000..b76209b --- /dev/null +++ b/user_service/keploy/atg/tests/test-321.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-321 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "59" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "auth_user_for_other", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:06.254283538Z + resp: + status_code: 200 + header: + Content-Length: "363" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"auth_user_for_other@example.in","id":"777b90d1-71b1-4983-942d-656250aac340","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NzdiOTBkMS03MWIxLTQ5ODMtOTQyZC02NTYyNTBhYWMzNDAiLCJ1c2VybmFtZSI6ImF1dGhfdXNlcl9mb3Jfb3RoZXIiLCJpYXQiOjE3NjI3NzcyNjYsImV4cCI6MTc2NTM2OTI2Nn0.vBOev8m_0LNmMMCmLiV2teGI6sPTTvPf-EW8rhxtoOM","username":"auth_user_for_other"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:06.313446332Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777266 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"auth_user_for_other\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-322.yaml b/user_service/keploy/atg/tests/test-322.yaml new file mode 100755 index 0000000..26912c2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-322.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-322 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:07.01427433Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:21:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:07.015528309Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777267 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-323.yaml b/user_service/keploy/atg/tests/test-323.yaml new file mode 100755 index 0000000..9a4968c --- /dev/null +++ b/user_service/keploy/atg/tests/test-323.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-323 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/777b90d1-71b1-4983-942d-656250aac340 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:07.836313238Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:07.859875931Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777267 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/777b90d1-71b1-4983-942d-656250aac340 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-324.yaml b/user_service/keploy/atg/tests/test-324.yaml new file mode 100755 index 0000000..f49c947 --- /dev/null +++ b/user_service/keploy/atg/tests/test-324.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-324 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "order_user_other", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:08.591802458Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:08 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"order_user_other@example.in","id":"87d0e4a5-0b24-4869-bf5d-258cd2a17ff5","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4N2QwZTRhNS0wYjI0LTQ4NjktYmY1ZC0yNThjZDJhMTdmZjUiLCJ1c2VybmFtZSI6Im9yZGVyX3VzZXJfb3RoZXIiLCJpYXQiOjE3NjI3NzcyNjgsImV4cCI6MTc2NTM2OTI2OH0.Jfqc87i9yzU9ppBtJGVKxBD1U_vdyyxESFY_3BBOtg0","username":"order_user_other"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:08.654544791Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777268 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"order_user_other\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-325.yaml b/user_service/keploy/atg/tests/test-325.yaml new file mode 100755 index 0000000..1b9eb6e --- /dev/null +++ b/user_service/keploy/atg/tests/test-325.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-325 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/87d0e4a5-0b24-4869-bf5d-258cd2a17ff5 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:09.425328373Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:09 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:09.449294412Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777269 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/87d0e4a5-0b24-4869-bf5d-258cd2a17ff5 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-326.yaml b/user_service/keploy/atg/tests/test-326.yaml new file mode 100755 index 0000000..b762300 --- /dev/null +++ b/user_service/keploy/atg/tests/test-326.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-326 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "98" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "user_no_addresses", "email": "user_no_addresses@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:12.033505602Z + resp: + status_code: 201 + header: + Content-Length: "129" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"user_no_addresses@example.in","id":"531eb4e5-9d74-4017-a6ff-a400e1a169c8","phone":null,"username":"user_no_addresses"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:12.102462413Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777272 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"user_no_addresses\", \"email\": \"user_no_addresses@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-327.yaml b/user_service/keploy/atg/tests/test-327.yaml new file mode 100755 index 0000000..9f68cbe --- /dev/null +++ b/user_service/keploy/atg/tests/test-327.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-327 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "57" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "user_no_addresses", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:12.78563981Z + resp: + status_code: 200 + header: + Content-Length: "356" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"user_no_addresses@example.in","id":"531eb4e5-9d74-4017-a6ff-a400e1a169c8","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1MzFlYjRlNS05ZDc0LTQwMTctYTZmZi1hNDAwZTFhMTY5YzgiLCJ1c2VybmFtZSI6InVzZXJfbm9fYWRkcmVzc2VzIiwiaWF0IjoxNzYyNzc3MjcyLCJleHAiOjE3NjUzNjkyNzJ9.c_bPcCpKrMFcaA3FOqn6PWK6XucYjZVGKclKR8yuQT0","username":"user_no_addresses"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:12.845062931Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777272 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"user_no_addresses\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-328.yaml b/user_service/keploy/atg/tests/test-328.yaml new file mode 100755 index 0000000..321a70d --- /dev/null +++ b/user_service/keploy/atg/tests/test-328.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-328 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/531eb4e5-9d74-4017-a6ff-a400e1a169c8/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:13.625560312Z + resp: + status_code: 200 + header: + Content-Length: "3" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:13 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + [] + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:13.636698539Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777273 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/users/531eb4e5-9d74-4017-a6ff-a400e1a169c8/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-329.yaml b/user_service/keploy/atg/tests/test-329.yaml new file mode 100755 index 0000000..b4aeb2c --- /dev/null +++ b/user_service/keploy/atg/tests/test-329.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-329 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/531eb4e5-9d74-4017-a6ff-a400e1a169c8 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:14.35755177Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:14 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:14.381380319Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777274 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/531eb4e5-9d74-4017-a6ff-a400e1a169c8 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-33.yaml b/user_service/keploy/atg/tests/test-33.yaml new file mode 100755 index 0000000..251edb6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-33.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-33 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:07.443291891Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODQ3LCJleHAiOjE3NjUzNjg4NDd9.tJgru3FcBSL5PWf4cCRCwriDg1D2QknYOgBAB_jN62A","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:07.510851967Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776847 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-330.yaml b/user_service/keploy/atg/tests/test-330.yaml new file mode 100755 index 0000000..2066262 --- /dev/null +++ b/user_service/keploy/atg/tests/test-330.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-330 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "user_no_orders", "email": "user_no_orders@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:15.185405922Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:15 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"user_no_orders@example.in","id":"d9d26f64-586a-45bc-b398-4ae9c5a4d9c2","phone":null,"username":"user_no_orders"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:15.257549228Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777275 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"user_no_orders\", \"email\": \"user_no_orders@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-331.yaml b/user_service/keploy/atg/tests/test-331.yaml new file mode 100755 index 0000000..6ea4b78 --- /dev/null +++ b/user_service/keploy/atg/tests/test-331.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-331 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "user_no_orders", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:15.940829165Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:16 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"user_no_orders@example.in","id":"d9d26f64-586a-45bc-b398-4ae9c5a4d9c2","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkOWQyNmY2NC01ODZhLTQ1YmMtYjM5OC00YWU5YzVhNGQ5YzIiLCJ1c2VybmFtZSI6InVzZXJfbm9fb3JkZXJzIiwiaWF0IjoxNzYyNzc3Mjc2LCJleHAiOjE3NjUzNjkyNzZ9.D-n-sngMN9BQKWuLlYuY-NLLOjQPqiZxG3HY4-1Hlso","username":"user_no_orders"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:16.009060622Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777276 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"user_no_orders\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-332.yaml b/user_service/keploy/atg/tests/test-332.yaml new file mode 100755 index 0000000..4adc77d --- /dev/null +++ b/user_service/keploy/atg/tests/test-332.yaml @@ -0,0 +1,49 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-332 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders?userId=d9d26f64-586a-45bc-b398-4ae9c5a4d9c2 + url_params: + userId: d9d26f64-586a-45bc-b398-4ae9c5a4d9c2 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:16.770079167Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:21:16 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:16.771408956Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777276 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/orders?userId=d9d26f64-586a-45bc-b398-4ae9c5a4d9c2 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-333.yaml b/user_service/keploy/atg/tests/test-333.yaml new file mode 100755 index 0000000..ea555e3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-333.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-333 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/d9d26f64-586a-45bc-b398-4ae9c5a4d9c2 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:17.502990458Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:17 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:17.528769352Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777277 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/d9d26f64-586a-45bc-b398-4ae9c5a4d9c2 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-334.yaml b/user_service/keploy/atg/tests/test-334.yaml new file mode 100755 index 0000000..ddc5326 --- /dev/null +++ b/user_service/keploy/atg/tests/test-334.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-334 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "47" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "testuser", "password": "p@ssw0rd"' + timestamp: 2025-11-10T12:21:19.503773003Z + resp: + status_code: 400 + header: + Content-Length: "167" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:21:19 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 400 Bad Request +

Bad Request

+

The browser (or proxy) sent a request that this server could not understand.

+ status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:19.505083352Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777279 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"testuser\", \"password\": \"p@ssw0rd\"" diff --git a/user_service/keploy/atg/tests/test-335.yaml b/user_service/keploy/atg/tests/test-335.yaml new file mode 100755 index 0000000..5411a15 --- /dev/null +++ b/user_service/keploy/atg/tests/test-335.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-335 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "55" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "badjson", "email": "badjson@example.in", ' + timestamp: 2025-11-10T12:21:20.288935486Z + resp: + status_code: 400 + header: + Content-Length: "36" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:20 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Missing required fields"} + status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:20.290268675Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777280 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"badjson\", \"email\": \"badjson@example.in\", " diff --git a/user_service/keploy/atg/tests/test-336.yaml b/user_service/keploy/atg/tests/test-336.yaml new file mode 100755 index 0000000..9d205a8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-336.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-336 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "addr_bad_json", "email": "addr_bad_json@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:21.292704856Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"addr_bad_json@example.in","id":"f77a805c-a2c8-4646-a16b-1e1cd58249ff","phone":null,"username":"addr_bad_json"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:21.366571396Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777281 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"addr_bad_json\", \"email\": \"addr_bad_json@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-337.yaml b/user_service/keploy/atg/tests/test-337.yaml new file mode 100755 index 0000000..ee886ce --- /dev/null +++ b/user_service/keploy/atg/tests/test-337.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-337 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "addr_bad_json", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:22.131745857Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:22 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"addr_bad_json@example.in","id":"f77a805c-a2c8-4646-a16b-1e1cd58249ff","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmNzdhODA1Yy1hMmM4LTQ2NDYtYTE2Yi0xZTFjZDU4MjQ5ZmYiLCJ1c2VybmFtZSI6ImFkZHJfYmFkX2pzb24iLCJpYXQiOjE3NjI3NzcyODIsImV4cCI6MTc2NTM2OTI4Mn0.sHmvCpQ0M53KYNX5Z2SWxsSmdbmtyiKLjwri-DesbI0","username":"addr_bad_json"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:22.188940578Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777282 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"addr_bad_json\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-338.yaml b/user_service/keploy/atg/tests/test-338.yaml new file mode 100755 index 0000000..124aebd --- /dev/null +++ b/user_service/keploy/atg/tests/test-338.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-338 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/f77a805c-a2c8-4646-a16b-1e1cd58249ff/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "38" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "123 Street", "city": "City"' + timestamp: 2025-11-10T12:21:23.053893563Z + resp: + status_code: 400 + header: + Content-Length: "36" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Missing required fields"} + status_message: Bad Request + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:23.055785358Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777283 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/f77a805c-a2c8-4646-a16b-1e1cd58249ff/addresses \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"line1\": \"123 Street\", \"city\": \"City\"" diff --git a/user_service/keploy/atg/tests/test-339.yaml b/user_service/keploy/atg/tests/test-339.yaml new file mode 100755 index 0000000..1792685 --- /dev/null +++ b/user_service/keploy/atg/tests/test-339.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-339 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/f77a805c-a2c8-4646-a16b-1e1cd58249ff + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:23.7922379Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:23.816458036Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777283 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/f77a805c-a2c8-4646-a16b-1e1cd58249ff \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-34.yaml b/user_service/keploy/atg/tests/test-34.yaml new file mode 100755 index 0000000..4fe4551 --- /dev/null +++ b/user_service/keploy/atg/tests/test-34.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-34 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:08.192172459Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:08 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:08.193378128Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776848 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-340.yaml b/user_service/keploy/atg/tests/test-340.yaml new file mode 100755 index 0000000..b10baf4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-340.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-340 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "order_bad_json", "email": "order_bad_json@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:24.616859423Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:24 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"order_bad_json@example.in","id":"bc280dae-7857-442d-ad52-1883bd5394f6","phone":null,"username":"order_bad_json"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:24.690274948Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777284 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"order_bad_json\", \"email\": \"order_bad_json@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-341.yaml b/user_service/keploy/atg/tests/test-341.yaml new file mode 100755 index 0000000..990ca7c --- /dev/null +++ b/user_service/keploy/atg/tests/test-341.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-341 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "order_bad_json", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:25.366865984Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:25 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"order_bad_json@example.in","id":"bc280dae-7857-442d-ad52-1883bd5394f6","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiYzI4MGRhZS03ODU3LTQ0MmQtYWQ1Mi0xODgzYmQ1Mzk0ZjYiLCJ1c2VybmFtZSI6Im9yZGVyX2JhZF9qc29uIiwiaWF0IjoxNzYyNzc3Mjg1LCJleHAiOjE3NjUzNjkyODV9.uMcZ-vDlQja6Dasa5Z0pZgyn-Nj8l0odSl6dCzJ8Co0","username":"order_bad_json"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:25.424871217Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777285 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"order_bad_json\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-342.yaml b/user_service/keploy/atg/tests/test-342.yaml new file mode 100755 index 0000000..4a1c26a --- /dev/null +++ b/user_service/keploy/atg/tests/test-342.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-342 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "61" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"userId": "bc280dae-7857-442d-ad52-1883bd5394f6", "items": [' + timestamp: 2025-11-10T12:21:26.194410912Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:21:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:26.195510493Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777286 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/orders \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"userId\": \"bc280dae-7857-442d-ad52-1883bd5394f6\", \"items\": [" diff --git a/user_service/keploy/atg/tests/test-343.yaml b/user_service/keploy/atg/tests/test-343.yaml new file mode 100755 index 0000000..8d8d979 --- /dev/null +++ b/user_service/keploy/atg/tests/test-343.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-343 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/bc280dae-7857-442d-ad52-1883bd5394f6 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:26.936735137Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:26.965821174Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777286 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/bc280dae-7857-442d-ad52-1883bd5394f6 \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-344.yaml b/user_service/keploy/atg/tests/test-344.yaml new file mode 100755 index 0000000..54b7d0a --- /dev/null +++ b/user_service/keploy/atg/tests/test-344.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-344 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "reserve_bad_json", "email": "reserve_bad_json@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:29.165548977Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:29 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"reserve_bad_json@example.in","id":"a8f32eb8-2e36-4603-8ba1-182b5a91d48b","phone":null,"username":"reserve_bad_json"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:29.239376647Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777289 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"reserve_bad_json\", \"email\": \"reserve_bad_json@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-345.yaml b/user_service/keploy/atg/tests/test-345.yaml new file mode 100755 index 0000000..cacf489 --- /dev/null +++ b/user_service/keploy/atg/tests/test-345.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-345 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "reserve_bad_json", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:29.999309435Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:30 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"reserve_bad_json@example.in","id":"a8f32eb8-2e36-4603-8ba1-182b5a91d48b","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhOGYzMmViOC0yZTM2LTQ2MDMtOGJhMS0xODJiNWE5MWQ0OGIiLCJ1c2VybmFtZSI6InJlc2VydmVfYmFkX2pzb24iLCJpYXQiOjE3NjI3NzcyOTAsImV4cCI6MTc2NTM2OTI5MH0.ioCEcNeerJpG-rr5W7i6S1TrgOo1Y3rAR8CH5CSqyZw","username":"reserve_bad_json"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:30.064891106Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777290 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"reserve_bad_json\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-346.yaml b/user_service/keploy/atg/tests/test-346.yaml new file mode 100755 index 0000000..b6b9cde --- /dev/null +++ b/user_service/keploy/atg/tests/test-346.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-346 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/some-product-id/reserve + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "14" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1' + timestamp: 2025-11-10T12:21:30.928303794Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:21:30 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:30.929470484Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777290 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/products/some-product-id/reserve \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"quantity\": 1" diff --git a/user_service/keploy/atg/tests/test-347.yaml b/user_service/keploy/atg/tests/test-347.yaml new file mode 100755 index 0000000..f18d039 --- /dev/null +++ b/user_service/keploy/atg/tests/test-347.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-347 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a8f32eb8-2e36-4603-8ba1-182b5a91d48b + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:31.66344628Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:31.689294784Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777291 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/a8f32eb8-2e36-4603-8ba1-182b5a91d48b \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-348.yaml b/user_service/keploy/atg/tests/test-348.yaml new file mode 100755 index 0000000..706c2f0 --- /dev/null +++ b/user_service/keploy/atg/tests/test-348.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-348 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "release_bad_json", "email": "release_bad_json@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:33.763349852Z + resp: + status_code: 201 + header: + Content-Length: "127" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:33 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"release_bad_json@example.in","id":"eeae17ed-c614-4cb0-bf39-c9dd9062ed3a","phone":null,"username":"release_bad_json"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:33.839000888Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777293 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"release_bad_json\", \"email\": \"release_bad_json@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-349.yaml b/user_service/keploy/atg/tests/test-349.yaml new file mode 100755 index 0000000..720806c --- /dev/null +++ b/user_service/keploy/atg/tests/test-349.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-349 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "release_bad_json", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:34.585738837Z + resp: + status_code: 200 + header: + Content-Length: "353" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"release_bad_json@example.in","id":"eeae17ed-c614-4cb0-bf39-c9dd9062ed3a","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlZWFlMTdlZC1jNjE0LTRjYjAtYmYzOS1jOWRkOTA2MmVkM2EiLCJ1c2VybmFtZSI6InJlbGVhc2VfYmFkX2pzb24iLCJpYXQiOjE3NjI3NzcyOTQsImV4cCI6MTc2NTM2OTI5NH0.t-LsyLJxPcmIjeShSQ-O1UbY3CcbWEubXkg1H_W1j14","username":"release_bad_json"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:34.647216931Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777294 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"release_bad_json\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-35.yaml b/user_service/keploy/atg/tests/test-35.yaml new file mode 100755 index 0000000..113b46c --- /dev/null +++ b/user_service/keploy/atg/tests/test-35.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-35 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:11.154979082Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:11 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODUxLCJleHAiOjE3NjUzNjg4NTF9.tubXDF9ONEKbvNACUxDA6JU-uG_me82sfG5klGUcGPk","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:11.214036704Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776851 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-350.yaml b/user_service/keploy/atg/tests/test-350.yaml new file mode 100755 index 0000000..a002c45 --- /dev/null +++ b/user_service/keploy/atg/tests/test-350.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-350 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/some-product-id/release + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "14" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1' + timestamp: 2025-11-10T12:21:35.333641886Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:21:35 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:35.334856266Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777295 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/products/some-product-id/release \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"quantity\": 1" diff --git a/user_service/keploy/atg/tests/test-351.yaml b/user_service/keploy/atg/tests/test-351.yaml new file mode 100755 index 0000000..907640e --- /dev/null +++ b/user_service/keploy/atg/tests/test-351.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-351 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/eeae17ed-c614-4cb0-bf39-c9dd9062ed3a + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:36.1717927Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:36 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:36.197109947Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777296 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/eeae17ed-c614-4cb0-bf39-c9dd9062ed3a \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-352.yaml b/user_service/keploy/atg/tests/test-352.yaml new file mode 100755 index 0000000..363967b --- /dev/null +++ b/user_service/keploy/atg/tests/test-352.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-352 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "82" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "maria_goa", "email": "maria_goa@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:38.245882882Z + resp: + status_code: 201 + header: + Content-Length: "113" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"maria_goa@example.in","id":"f0e44763-84a3-4d1b-b31d-c2bde7baaaef","phone":null,"username":"maria_goa"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:38.319099278Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777298 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"maria_goa\", \"email\": \"maria_goa@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-353.yaml b/user_service/keploy/atg/tests/test-353.yaml new file mode 100755 index 0000000..34c0266 --- /dev/null +++ b/user_service/keploy/atg/tests/test-353.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-353 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "49" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "maria_goa", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:39.011850912Z + resp: + status_code: 200 + header: + Content-Length: "330" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"maria_goa@example.in","id":"f0e44763-84a3-4d1b-b31d-c2bde7baaaef","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmMGU0NDc2My04NGEzLTRkMWItYjMxZC1jMmJkZTdiYWFhZWYiLCJ1c2VybmFtZSI6Im1hcmlhX2dvYSIsImlhdCI6MTc2Mjc3NzI5OSwiZXhwIjoxNzY1MzY5Mjk5fQ.oLffE6JA-pv6A1uNVqCmqVNl54Y1ros0Dege6MRsIKg","username":"maria_goa"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:39.070439711Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777299 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"maria_goa\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-354.yaml b/user_service/keploy/atg/tests/test-354.yaml new file mode 100755 index 0000000..1c444dd --- /dev/null +++ b/user_service/keploy/atg/tests/test-354.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-354 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/f0e44763-84a3-4d1b-b31d-c2bde7baaaef/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "158" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "House 123, Calangute", "city": "North Goa", "state": "Goa", "postal_code": "403516", "country": "IN", "phone": "+919822123456", "is_default": true}' + timestamp: 2025-11-10T12:21:39.831223974Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"578822f8-7c40-4316-bd19-b9fbb6e27992"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:39.852993792Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777299 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/f0e44763-84a3-4d1b-b31d-c2bde7baaaef/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"House 123, Calangute\", \"city\": \"North Goa\", \"state\": \"Goa\", \"postal_code\": \"403516\", \"country\": \"IN\", \"phone\": \"+919822123456\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-355.yaml b/user_service/keploy/atg/tests/test-355.yaml new file mode 100755 index 0000000..1d43bb6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-355.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-355 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/f0e44763-84a3-4d1b-b31d-c2bde7baaaef + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:40.660422864Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:40 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:40.687287388Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777300 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/f0e44763-84a3-4d1b-b31d-c2bde7baaaef \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-356.yaml b/user_service/keploy/atg/tests/test-356.yaml new file mode 100755 index 0000000..31720d7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-356.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-356 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "86" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "yash_jaipur", "email": "yash_jaipur@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:43.493540488Z + resp: + status_code: 201 + header: + Content-Length: "117" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:43 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"yash_jaipur@example.in","id":"91eb8fad-7de2-4795-98dc-913fa5a7eece","phone":null,"username":"yash_jaipur"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:43.570795041Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777303 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"yash_jaipur\", \"email\": \"yash_jaipur@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-357.yaml b/user_service/keploy/atg/tests/test-357.yaml new file mode 100755 index 0000000..4202ab9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-357.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-357 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "51" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "yash_jaipur", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:44.274196635Z + resp: + status_code: 200 + header: + Content-Length: "336" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:44 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"yash_jaipur@example.in","id":"91eb8fad-7de2-4795-98dc-913fa5a7eece","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5MWViOGZhZC03ZGUyLTQ3OTUtOThkYy05MTNmYTVhN2VlY2UiLCJ1c2VybmFtZSI6Inlhc2hfamFpcHVyIiwiaWF0IjoxNzYyNzc3MzA0LCJleHAiOjE3NjUzNjkzMDR9.Q1hao-B3vCRBV4StHrHwcYIen21P9IzVVNkuo9o_EV4","username":"yash_jaipur"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:44.339886084Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777304 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"yash_jaipur\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-358.yaml b/user_service/keploy/atg/tests/test-358.yaml new file mode 100755 index 0000000..57609af --- /dev/null +++ b/user_service/keploy/atg/tests/test-358.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-358 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/91eb8fad-7de2-4795-98dc-913fa5a7eece/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "156" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Hawa Mahal Road", "city": "Jaipur", "state": "Rajasthan", "postal_code": "302002", "country": "IN", "phone": "+919829098290", "is_default": true}' + timestamp: 2025-11-10T12:21:45.0779138Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:45 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"2aafcfd3-ebe2-405f-90c5-a59fef147006"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:45.103645493Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777305 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/91eb8fad-7de2-4795-98dc-913fa5a7eece/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Hawa Mahal Road\", \"city\": \"Jaipur\", \"state\": \"Rajasthan\", \"postal_code\": \"302002\", \"country\": \"IN\", \"phone\": \"+919829098290\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-359.yaml b/user_service/keploy/atg/tests/test-359.yaml new file mode 100755 index 0000000..069cfd2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-359.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-359 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/91eb8fad-7de2-4795-98dc-913fa5a7eece + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:45.814652484Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:45 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:45.845311568Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777305 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/91eb8fad-7de2-4795-98dc-913fa5a7eece \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-36.yaml b/user_service/keploy/atg/tests/test-36.yaml new file mode 100755 index 0000000..f2fdaf3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-36.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-36 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:12.077857909Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:12.078954229Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776852 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-360.yaml b/user_service/keploy/atg/tests/test-360.yaml new file mode 100755 index 0000000..66f565a --- /dev/null +++ b/user_service/keploy/atg/tests/test-360.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-360 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "isha_ahmedabad", "email": "isha_ahmedabad@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:47.737699692Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"isha_ahmedabad@example.in","id":"44708021-ee36-4225-8633-f6d9b6c3a32d","phone":null,"username":"isha_ahmedabad"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:47.8405233Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777307 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"isha_ahmedabad\", \"email\": \"isha_ahmedabad@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-361.yaml b/user_service/keploy/atg/tests/test-361.yaml new file mode 100755 index 0000000..6706fe8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-361.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-361 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "isha_ahmedabad", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:48.514551362Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"isha_ahmedabad@example.in","id":"44708021-ee36-4225-8633-f6d9b6c3a32d","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0NDcwODAyMS1lZTM2LTQyMjUtODYzMy1mNmQ5YjZjM2EzMmQiLCJ1c2VybmFtZSI6ImlzaGFfYWhtZWRhYmFkIiwiaWF0IjoxNzYyNzc3MzA4LCJleHAiOjE3NjUzNjkzMDh9.NUlGAXzThxnBBW5scVA2PnaDyQ_cc0n21v_Bidvmd5U","username":"isha_ahmedabad"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:48.581681308Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777308 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"isha_ahmedabad\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-362.yaml b/user_service/keploy/atg/tests/test-362.yaml new file mode 100755 index 0000000..59d496b --- /dev/null +++ b/user_service/keploy/atg/tests/test-362.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-362 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/44708021-ee36-4225-8633-f6d9b6c3a32d/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "149" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "CG Road", "city": "Ahmedabad", "state": "Gujarat", "postal_code": "380006", "country": "IN", "phone": "+919825012345", "is_default": true}' + timestamp: 2025-11-10T12:21:49.268064368Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:49 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"af27df60-c68f-4667-94e8-5be0b463f296"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:49.308034033Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777309 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/44708021-ee36-4225-8633-f6d9b6c3a32d/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"CG Road\", \"city\": \"Ahmedabad\", \"state\": \"Gujarat\", \"postal_code\": \"380006\", \"country\": \"IN\", \"phone\": \"+919825012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-363.yaml b/user_service/keploy/atg/tests/test-363.yaml new file mode 100755 index 0000000..cc0e026 --- /dev/null +++ b/user_service/keploy/atg/tests/test-363.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-363 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/44708021-ee36-4225-8633-f6d9b6c3a32d + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:50.006037803Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:50 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:50.028815203Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777310 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/44708021-ee36-4225-8633-f6d9b6c3a32d \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-364.yaml b/user_service/keploy/atg/tests/test-364.yaml new file mode 100755 index 0000000..7614fc1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-364.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-364 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "aditya_lucknow", "email": "aditya_lucknow@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:51.882832771Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"aditya_lucknow@example.in","id":"a4290205-4d0c-4577-a66c-6b180d9e87bc","phone":null,"username":"aditya_lucknow"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:51.95936713Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777311 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"aditya_lucknow\", \"email\": \"aditya_lucknow@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-365.yaml b/user_service/keploy/atg/tests/test-365.yaml new file mode 100755 index 0000000..37ec58e --- /dev/null +++ b/user_service/keploy/atg/tests/test-365.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-365 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "aditya_lucknow", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:52.641993521Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:52 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"aditya_lucknow@example.in","id":"a4290205-4d0c-4577-a66c-6b180d9e87bc","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhNDI5MDIwNS00ZDBjLTQ1NzctYTY2Yy02YjE4MGQ5ZTg3YmMiLCJ1c2VybmFtZSI6ImFkaXR5YV9sdWNrbm93IiwiaWF0IjoxNzYyNzc3MzEyLCJleHAiOjE3NjUzNjkzMTJ9.8dpEBzlMQzu2qvieZScvyDAr8-vlvA5LT939eoybYLo","username":"aditya_lucknow"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:52.709365027Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777312 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"aditya_lucknow\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-366.yaml b/user_service/keploy/atg/tests/test-366.yaml new file mode 100755 index 0000000..0796b54 --- /dev/null +++ b/user_service/keploy/atg/tests/test-366.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-366 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a4290205-4d0c-4577-a66c-6b180d9e87bc/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "157" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Gomti Nagar", "city": "Lucknow", "state": "Uttar Pradesh", "postal_code": "226010", "country": "IN", "phone": "+919415112345", "is_default": true}' + timestamp: 2025-11-10T12:21:53.501127303Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:53 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"c2479e30-8c19-4779-922e-1f96cb1797fe"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:53.523176159Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777313 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/a4290205-4d0c-4577-a66c-6b180d9e87bc/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Gomti Nagar\", \"city\": \"Lucknow\", \"state\": \"Uttar Pradesh\", \"postal_code\": \"226010\", \"country\": \"IN\", \"phone\": \"+919415112345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-367.yaml b/user_service/keploy/atg/tests/test-367.yaml new file mode 100755 index 0000000..b9605f7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-367.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-367 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a4290205-4d0c-4577-a66c-6b180d9e87bc + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:54.23076126Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:54 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:54.257101869Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777314 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/a4290205-4d0c-4577-a66c-6b180d9e87bc \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-368.yaml b/user_service/keploy/atg/tests/test-368.yaml new file mode 100755 index 0000000..080f54b --- /dev/null +++ b/user_service/keploy/atg/tests/test-368.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-368 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "84" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "simran_chd", "email": "simran_chd@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:56.600441579Z + resp: + status_code: 201 + header: + Content-Length: "115" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:56 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"simran_chd@example.in","id":"1dd33e6c-1eda-484f-9ac0-25323324cd5e","phone":null,"username":"simran_chd"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:56.673025292Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777316 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"simran_chd\", \"email\": \"simran_chd@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-369.yaml b/user_service/keploy/atg/tests/test-369.yaml new file mode 100755 index 0000000..3113f99 --- /dev/null +++ b/user_service/keploy/atg/tests/test-369.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-369 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "50" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "simran_chd", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:21:57.345612106Z + resp: + status_code: 200 + header: + Content-Length: "333" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:57 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"simran_chd@example.in","id":"1dd33e6c-1eda-484f-9ac0-25323324cd5e","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxZGQzM2U2Yy0xZWRhLTQ4NGYtOWFjMC0yNTMyMzMyNGNkNWUiLCJ1c2VybmFtZSI6InNpbXJhbl9jaGQiLCJpYXQiOjE3NjI3NzczMTcsImV4cCI6MTc2NTM2OTMxN30.2JXBVp6I04XCGXiQ6PqXFevAUUgZ3Zk40_z68OtqRbs","username":"simran_chd"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:57.407960484Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777317 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"simran_chd\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-37.yaml b/user_service/keploy/atg/tests/test-37.yaml new file mode 100755 index 0000000..7aefab2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-37.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-37 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:15.239334082Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:15 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODU1LCJleHAiOjE3NjUzNjg4NTV9.p0iGpdnbFVv4Yx4XqzatnRC6h5oEMgsLhQRbFTbNFkU","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:15.298605632Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776855 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-370.yaml b/user_service/keploy/atg/tests/test-370.yaml new file mode 100755 index 0000000..467b393 --- /dev/null +++ b/user_service/keploy/atg/tests/test-370.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-370 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/1dd33e6c-1eda-484f-9ac0-25323324cd5e/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "155" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Sector 22", "city": "Chandigarh", "state": "Chandigarh", "postal_code": "160022", "country": "IN", "phone": "+919814012345", "is_default": true}' + timestamp: 2025-11-10T12:21:58.205915729Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:58 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"b9fe35b4-6ed9-48c0-bf77-49648e79f9c2"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:58.23336406Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777318 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/1dd33e6c-1eda-484f-9ac0-25323324cd5e/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Sector 22\", \"city\": \"Chandigarh\", \"state\": \"Chandigarh\", \"postal_code\": \"160022\", \"country\": \"IN\", \"phone\": \"+919814012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-371.yaml b/user_service/keploy/atg/tests/test-371.yaml new file mode 100755 index 0000000..9993730 --- /dev/null +++ b/user_service/keploy/atg/tests/test-371.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-371 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/1dd33e6c-1eda-484f-9ac0-25323324cd5e + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:21:58.925542511Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:21:58 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:21:58.9637096Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777318 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/1dd33e6c-1eda-484f-9ac0-25323324cd5e \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-372.yaml b/user_service/keploy/atg/tests/test-372.yaml new file mode 100755 index 0000000..2448741 --- /dev/null +++ b/user_service/keploy/atg/tests/test-372.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-372 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "88" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "george_kochi", "email": "george_kochi@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:00.923462656Z + resp: + status_code: 201 + header: + Content-Length: "119" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:00 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"george_kochi@example.in","id":"b8a7fc1d-a07c-4225-bf5b-8e486aecaae6","phone":null,"username":"george_kochi"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:00.999454479Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777320 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"george_kochi\", \"email\": \"george_kochi@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-373.yaml b/user_service/keploy/atg/tests/test-373.yaml new file mode 100755 index 0000000..a3f96a9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-373.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-373 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "george_kochi", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:01.839443395Z + resp: + status_code: 200 + header: + Content-Length: "340" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"george_kochi@example.in","id":"b8a7fc1d-a07c-4225-bf5b-8e486aecaae6","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiOGE3ZmMxZC1hMDdjLTQyMjUtYmY1Yi04ZTQ4NmFlY2FhZTYiLCJ1c2VybmFtZSI6Imdlb3JnZV9rb2NoaSIsImlhdCI6MTc2Mjc3NzMyMSwiZXhwIjoxNzY1MzY5MzIxfQ.irtO06sKj3FQ988Pumu2YW3zKOP5-0s95RxopPrka9s","username":"george_kochi"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:01.90339926Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777321 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"george_kochi\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-374.yaml b/user_service/keploy/atg/tests/test-374.yaml new file mode 100755 index 0000000..ca401bb --- /dev/null +++ b/user_service/keploy/atg/tests/test-374.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-374 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/b8a7fc1d-a07c-4225-bf5b-8e486aecaae6/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "149" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Marine Drive", "city": "Kochi", "state": "Kerala", "postal_code": "682011", "country": "IN", "phone": "+919846012345", "is_default": true}' + timestamp: 2025-11-10T12:22:02.582345543Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:02 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"30a320aa-287e-46c6-b1ba-255350d701bd"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:02.605686929Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777322 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/b8a7fc1d-a07c-4225-bf5b-8e486aecaae6/addresses \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"line1\": \"Marine Drive\", \"city\": \"Kochi\", \"state\": \"Kerala\", \"postal_code\": \"682011\", \"country\": \"IN\", \"phone\": \"+919846012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-375.yaml b/user_service/keploy/atg/tests/test-375.yaml new file mode 100755 index 0000000..8397bfb --- /dev/null +++ b/user_service/keploy/atg/tests/test-375.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-375 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/b8a7fc1d-a07c-4225-bf5b-8e486aecaae6 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:03.412091174Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:03 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:03.435423279Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777323 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/b8a7fc1d-a07c-4225-bf5b-8e486aecaae6 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-376.yaml b/user_service/keploy/atg/tests/test-376.yaml new file mode 100755 index 0000000..860cc72 --- /dev/null +++ b/user_service/keploy/atg/tests/test-376.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-376 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "86" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "neha_indore", "email": "neha_indore@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:06.053286146Z + resp: + status_code: 201 + header: + Content-Length: "117" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"neha_indore@example.in","id":"4d375648-f4ab-4661-90e8-d4265b1afcc4","phone":null,"username":"neha_indore"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:06.133446645Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777326 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"neha_indore\", \"email\": \"neha_indore@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-377.yaml b/user_service/keploy/atg/tests/test-377.yaml new file mode 100755 index 0000000..cf707c8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-377.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-377 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "51" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "neha_indore", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:06.806256501Z + resp: + status_code: 200 + header: + Content-Length: "336" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"neha_indore@example.in","id":"4d375648-f4ab-4661-90e8-d4265b1afcc4","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0ZDM3NTY0OC1mNGFiLTQ2NjEtOTBlOC1kNDI2NWIxYWZjYzQiLCJ1c2VybmFtZSI6Im5laGFfaW5kb3JlIiwiaWF0IjoxNzYyNzc3MzI2LCJleHAiOjE3NjUzNjkzMjZ9.7wy1PcFFWyzCUYA10e13UaO_-ToCMWyBcO9gPuUfx_8","username":"neha_indore"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:06.871004069Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777326 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"neha_indore\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-378.yaml b/user_service/keploy/atg/tests/test-378.yaml new file mode 100755 index 0000000..bfde55a --- /dev/null +++ b/user_service/keploy/atg/tests/test-378.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-378 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/4d375648-f4ab-4661-90e8-d4265b1afcc4/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "154" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "56 Dukan", "city": "Indore", "state": "Madhya Pradesh", "postal_code": "452001", "country": "IN", "phone": "+919826012345", "is_default": true}' + timestamp: 2025-11-10T12:22:07.603936252Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"46e38be7-5454-45a3-a5d1-c0ed3c09b145"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:07.631443761Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777327 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/4d375648-f4ab-4661-90e8-d4265b1afcc4/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"line1\": \"56 Dukan\", \"city\": \"Indore\", \"state\": \"Madhya Pradesh\", \"postal_code\": \"452001\", \"country\": \"IN\", \"phone\": \"+919826012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-379.yaml b/user_service/keploy/atg/tests/test-379.yaml new file mode 100755 index 0000000..f1a44b6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-379.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-379 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/4d375648-f4ab-4661-90e8-d4265b1afcc4 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:08.366513846Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:08 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:08.391644096Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777328 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/4d375648-f4ab-4661-90e8-d4265b1afcc4 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-38.yaml b/user_service/keploy/atg/tests/test-38.yaml new file mode 100755 index 0000000..42b9c12 --- /dev/null +++ b/user_service/keploy/atg/tests/test-38.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-38 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "arjun_idem_01", "email": "arjun.idem.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:15.983276305Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:15 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:15.984739193Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776855 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"arjun_idem_01\", \"email\": \"arjun.idem.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-380.yaml b/user_service/keploy/atg/tests/test-380.yaml new file mode 100755 index 0000000..86f22d6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-380.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-380 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "88" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "vivek_bhopal", "email": "vivek_bhopal@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:10.489721729Z + resp: + status_code: 201 + header: + Content-Length: "119" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:10 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"vivek_bhopal@example.in","id":"cd231165-d42c-4937-8415-50d80f3cfbf2","phone":null,"username":"vivek_bhopal"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:10.579580247Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777330 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"vivek_bhopal\", \"email\": \"vivek_bhopal@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-381.yaml b/user_service/keploy/atg/tests/test-381.yaml new file mode 100755 index 0000000..d36d78a --- /dev/null +++ b/user_service/keploy/atg/tests/test-381.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-381 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "vivek_bhopal", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:11.28155972Z + resp: + status_code: 200 + header: + Content-Length: "340" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:11 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"vivek_bhopal@example.in","id":"cd231165-d42c-4937-8415-50d80f3cfbf2","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjZDIzMTE2NS1kNDJjLTQ5MzctODQxNS01MGQ4MGYzY2ZiZjIiLCJ1c2VybmFtZSI6InZpdmVrX2Job3BhbCIsImlhdCI6MTc2Mjc3NzMzMSwiZXhwIjoxNzY1MzY5MzMxfQ.70ch2bypnWJohO-eIFlsSoW7wUzktfAnUzwxI_MA9Uk","username":"vivek_bhopal"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:11.353844115Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777331 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"vivek_bhopal\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-382.yaml b/user_service/keploy/atg/tests/test-382.yaml new file mode 100755 index 0000000..37e6cc9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-382.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-382 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/cd231165-d42c-4937-8415-50d80f3cfbf2/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "158" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Arera Colony", "city": "Bhopal", "state": "Madhya Pradesh", "postal_code": "462016", "country": "IN", "phone": "+919425012345", "is_default": true}' + timestamp: 2025-11-10T12:22:12.033737293Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"9e1de261-0fe2-4a26-abbb-6903869a2e78"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:12.059316149Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777332 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/cd231165-d42c-4937-8415-50d80f3cfbf2/addresses \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"line1\": \"Arera Colony\", \"city\": \"Bhopal\", \"state\": \"Madhya Pradesh\", \"postal_code\": \"462016\", \"country\": \"IN\", \"phone\": \"+919425012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-383.yaml b/user_service/keploy/atg/tests/test-383.yaml new file mode 100755 index 0000000..1cbe9ae --- /dev/null +++ b/user_service/keploy/atg/tests/test-383.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-383 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/cd231165-d42c-4937-8415-50d80f3cfbf2 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:12.847491769Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:12.875889812Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777332 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/cd231165-d42c-4937-8415-50d80f3cfbf2 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-384.yaml b/user_service/keploy/atg/tests/test-384.yaml new file mode 100755 index 0000000..41609c9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-384.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-384 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "88" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "pooja_nagpur", "email": "pooja_nagpur@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:14.654550152Z + resp: + status_code: 201 + header: + Content-Length: "119" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:14 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"pooja_nagpur@example.in","id":"9c9e241f-ff14-4eac-8296-5622d8fe401b","phone":null,"username":"pooja_nagpur"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:14.73608572Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777334 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"pooja_nagpur\", \"email\": \"pooja_nagpur@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-385.yaml b/user_service/keploy/atg/tests/test-385.yaml new file mode 100755 index 0000000..08d8630 --- /dev/null +++ b/user_service/keploy/atg/tests/test-385.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-385 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "pooja_nagpur", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:15.483333214Z + resp: + status_code: 200 + header: + Content-Length: "340" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:15 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"pooja_nagpur@example.in","id":"9c9e241f-ff14-4eac-8296-5622d8fe401b","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5YzllMjQxZi1mZjE0LTRlYWMtODI5Ni01NjIyZDhmZTQwMWIiLCJ1c2VybmFtZSI6InBvb2phX25hZ3B1ciIsImlhdCI6MTc2Mjc3NzMzNSwiZXhwIjoxNzY1MzY5MzM1fQ.6OdUNldCFaBFvdoAZXZ9Tyr6K76HX7p_oWsXKpqG_N8","username":"pooja_nagpur"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:15.548275591Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777335 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"pooja_nagpur\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-386.yaml b/user_service/keploy/atg/tests/test-386.yaml new file mode 100755 index 0000000..6ad4b94 --- /dev/null +++ b/user_service/keploy/atg/tests/test-386.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-386 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/9c9e241f-ff14-4eac-8296-5622d8fe401b/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "152" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Sitabuldi", "city": "Nagpur", "state": "Maharashtra", "postal_code": "440012", "country": "IN", "phone": "+919823012345", "is_default": true}' + timestamp: 2025-11-10T12:22:16.227597304Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:16 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"df81c806-72b6-4a92-9f14-5fbeaa0c00ce"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:16.252475855Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777336 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/9c9e241f-ff14-4eac-8296-5622d8fe401b/addresses \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"line1\": \"Sitabuldi\", \"city\": \"Nagpur\", \"state\": \"Maharashtra\", \"postal_code\": \"440012\", \"country\": \"IN\", \"phone\": \"+919823012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-387.yaml b/user_service/keploy/atg/tests/test-387.yaml new file mode 100755 index 0000000..a3c995a --- /dev/null +++ b/user_service/keploy/atg/tests/test-387.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-387 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/9c9e241f-ff14-4eac-8296-5622d8fe401b + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:17.038674604Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:17 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:17.062520764Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777337 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/9c9e241f-ff14-4eac-8296-5622d8fe401b \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-388.yaml b/user_service/keploy/atg/tests/test-388.yaml new file mode 100755 index 0000000..b8f1363 --- /dev/null +++ b/user_service/keploy/atg/tests/test-388.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-388 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "88" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "suresh_vizag", "email": "suresh_vizag@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:19.230628325Z + resp: + status_code: 201 + header: + Content-Length: "119" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:19 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"suresh_vizag@example.in","id":"9901edf5-b076-4850-9ada-b9c75949d59c","phone":null,"username":"suresh_vizag"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:19.316690695Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777339 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"suresh_vizag\", \"email\": \"suresh_vizag@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-389.yaml b/user_service/keploy/atg/tests/test-389.yaml new file mode 100755 index 0000000..809ca11 --- /dev/null +++ b/user_service/keploy/atg/tests/test-389.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-389 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "suresh_vizag", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:19.986891454Z + resp: + status_code: 200 + header: + Content-Length: "340" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:20 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"suresh_vizag@example.in","id":"9901edf5-b076-4850-9ada-b9c75949d59c","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5OTAxZWRmNS1iMDc2LTQ4NTAtOWFkYS1iOWM3NTk0OWQ1OWMiLCJ1c2VybmFtZSI6InN1cmVzaF92aXphZyIsImlhdCI6MTc2Mjc3NzM0MCwiZXhwIjoxNzY1MzY5MzQwfQ.qa2hmdVc_qaUcrr-8GHWXzfG-HU_VA9nItEYGqh2750","username":"suresh_vizag"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:20.060714467Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777340 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"suresh_vizag\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-39.yaml b/user_service/keploy/atg/tests/test-39.yaml new file mode 100755 index 0000000..1159bd4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-39.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-39 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:16.796851599Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:16 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:16.797980809Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776856 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-390.yaml b/user_service/keploy/atg/tests/test-390.yaml new file mode 100755 index 0000000..07489bc --- /dev/null +++ b/user_service/keploy/atg/tests/test-390.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-390 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/9901edf5-b076-4850-9ada-b9c75949d59c/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "166" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "RK Beach Road", "city": "Visakhapatnam", "state": "Andhra Pradesh", "postal_code": "530003", "country": "IN", "phone": "+919849012345", "is_default": true}' + timestamp: 2025-11-10T12:22:20.734896354Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:20 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"04a4c42e-2bc4-4614-af7f-b84c7e7b598c"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:20.761325433Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777340 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/9901edf5-b076-4850-9ada-b9c75949d59c/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"line1\": \"RK Beach Road\", \"city\": \"Visakhapatnam\", \"state\": \"Andhra Pradesh\", \"postal_code\": \"530003\", \"country\": \"IN\", \"phone\": \"+919849012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-391.yaml b/user_service/keploy/atg/tests/test-391.yaml new file mode 100755 index 0000000..91b07e9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-391.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-391 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/9901edf5-b076-4850-9ada-b9c75949d59c + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:21.461360904Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:21.489078971Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777341 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/9901edf5-b076-4850-9ada-b9c75949d59c \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-392.yaml b/user_service/keploy/atg/tests/test-392.yaml new file mode 100755 index 0000000..1e91a00 --- /dev/null +++ b/user_service/keploy/atg/tests/test-392.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-392 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "86" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "manoj_patna", "email": "manoj_patna@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:23.896721802Z + resp: + status_code: 201 + header: + Content-Length: "117" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"manoj_patna@example.in","id":"86d9e8a4-6a00-46c9-9077-6362c89a686f","phone":null,"username":"manoj_patna"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:23.97342719Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777343 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"manoj_patna\", \"email\": \"manoj_patna@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-393.yaml b/user_service/keploy/atg/tests/test-393.yaml new file mode 100755 index 0000000..e32a16e --- /dev/null +++ b/user_service/keploy/atg/tests/test-393.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-393 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "51" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "manoj_patna", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:24.647672046Z + resp: + status_code: 200 + header: + Content-Length: "336" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:24 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"manoj_patna@example.in","id":"86d9e8a4-6a00-46c9-9077-6362c89a686f","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4NmQ5ZThhNC02YTAwLTQ2YzktOTA3Ny02MzYyYzg5YTY4NmYiLCJ1c2VybmFtZSI6Im1hbm9qX3BhdG5hIiwiaWF0IjoxNzYyNzc3MzQ0LCJleHAiOjE3NjUzNjkzNDR9.Sd-N8Rbn9SJGOYS3vmct3Xf_kz0uVYXTmUGNrdmoDT4","username":"manoj_patna"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:24.714816884Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777344 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"manoj_patna\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-394.yaml b/user_service/keploy/atg/tests/test-394.yaml new file mode 100755 index 0000000..4ae09ec --- /dev/null +++ b/user_service/keploy/atg/tests/test-394.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-394 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/86d9e8a4-6a00-46c9-9077-6362c89a686f/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "147" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Boring Road", "city": "Patna", "state": "Bihar", "postal_code": "800001", "country": "IN", "phone": "+919431012345", "is_default": true}' + timestamp: 2025-11-10T12:22:25.45261695Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:25 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"06437b9d-d8b1-4db6-b86c-f2430b82c704"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:25.479076508Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777345 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/86d9e8a4-6a00-46c9-9077-6362c89a686f/addresses \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"line1\": \"Boring Road\", \"city\": \"Patna\", \"state\": \"Bihar\", \"postal_code\": \"800001\", \"country\": \"IN\", \"phone\": \"+919431012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-395.yaml b/user_service/keploy/atg/tests/test-395.yaml new file mode 100755 index 0000000..608822b --- /dev/null +++ b/user_service/keploy/atg/tests/test-395.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-395 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/86d9e8a4-6a00-46c9-9077-6362c89a686f + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:26.189256486Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:26.220613163Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777346 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/86d9e8a4-6a00-46c9-9077-6362c89a686f \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-396.yaml b/user_service/keploy/atg/tests/test-396.yaml new file mode 100755 index 0000000..225cb30 --- /dev/null +++ b/user_service/keploy/atg/tests/test-396.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-396 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "88" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "harpreet_ldh", "email": "harpreet_ldh@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:28.681587539Z + resp: + status_code: 201 + header: + Content-Length: "119" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:28 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"harpreet_ldh@example.in","id":"9c22935f-1a65-4d1a-9064-c702ca10afc9","phone":null,"username":"harpreet_ldh"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:28.763464104Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777348 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"harpreet_ldh\", \"email\": \"harpreet_ldh@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-397.yaml b/user_service/keploy/atg/tests/test-397.yaml new file mode 100755 index 0000000..cea7b37 --- /dev/null +++ b/user_service/keploy/atg/tests/test-397.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-397 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "harpreet_ldh", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:29.452091561Z + resp: + status_code: 200 + header: + Content-Length: "340" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:29 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"harpreet_ldh@example.in","id":"9c22935f-1a65-4d1a-9064-c702ca10afc9","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5YzIyOTM1Zi0xYTY1LTRkMWEtOTA2NC1jNzAyY2ExMGFmYzkiLCJ1c2VybmFtZSI6ImhhcnByZWV0X2xkaCIsImlhdCI6MTc2Mjc3NzM0OSwiZXhwIjoxNzY1MzY5MzQ5fQ.mBXi9aq6RYDfwqVeRR05Fos8w3_NAFwtqBjjFCMhrZ0","username":"harpreet_ldh"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:29.522475652Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777349 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"harpreet_ldh\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-398.yaml b/user_service/keploy/atg/tests/test-398.yaml new file mode 100755 index 0000000..fe9af7d --- /dev/null +++ b/user_service/keploy/atg/tests/test-398.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-398 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/9c22935f-1a65-4d1a-9064-c702ca10afc9/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "153" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Sarabha Nagar", "city": "Ludhiana", "state": "Punjab", "postal_code": "141001", "country": "IN", "phone": "+919815012345", "is_default": true}' + timestamp: 2025-11-10T12:22:30.278341158Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:30 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"751a63b7-51ee-4790-b9e3-e93190ce458f"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:30.307936351Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777350 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/9c22935f-1a65-4d1a-9064-c702ca10afc9/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Sarabha Nagar\", \"city\": \"Ludhiana\", \"state\": \"Punjab\", \"postal_code\": \"141001\", \"country\": \"IN\", \"phone\": \"+919815012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-399.yaml b/user_service/keploy/atg/tests/test-399.yaml new file mode 100755 index 0000000..672904c --- /dev/null +++ b/user_service/keploy/atg/tests/test-399.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-399 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/9c22935f-1a65-4d1a-9064-c702ca10afc9 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:31.013871194Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:31.044107231Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777351 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/9c22935f-1a65-4d1a-9064-c702ca10afc9 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-4.yaml b/user_service/keploy/atg/tests/test-4.yaml new file mode 100755 index 0000000..74de4e5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-4.yaml @@ -0,0 +1,45 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-4 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Content-Length: "114" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "sunita_rao_{{$randomInt}}", "email": "sunita.rao.{{$randomInt}}@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:11:21.550597948Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:11:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:11:21.551694968Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776681 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"sunita_rao_{{$randomInt}}\", \"email\": \"sunita.rao.{{$randomInt}}@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-40.yaml b/user_service/keploy/atg/tests/test-40.yaml new file mode 100755 index 0000000..45c8d8d --- /dev/null +++ b/user_service/keploy/atg/tests/test-40.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-40 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:20.567422511Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:20 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODYwLCJleHAiOjE3NjUzNjg4NjB9.2_NzcoQpgQUMzDlHQeEuEwCQ44wUimTaQ0TcrWe94e8","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:20.625421643Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776860 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-400.yaml b/user_service/keploy/atg/tests/test-400.yaml new file mode 100755 index 0000000..3a71acf --- /dev/null +++ b/user_service/keploy/atg/tests/test-400.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-400 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "84" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "aisha_agra", "email": "aisha_agra@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:32.983835821Z + resp: + status_code: 201 + header: + Content-Length: "115" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:33 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"aisha_agra@example.in","id":"5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a","phone":null,"username":"aisha_agra"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:33.07132743Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777353 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"aisha_agra\", \"email\": \"aisha_agra@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-401.yaml b/user_service/keploy/atg/tests/test-401.yaml new file mode 100755 index 0000000..97a6081 --- /dev/null +++ b/user_service/keploy/atg/tests/test-401.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-401 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "50" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "aisha_agra", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:33.827192555Z + resp: + status_code: 200 + header: + Content-Length: "333" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:33 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"aisha_agra@example.in","id":"5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWQyYWEyMy0zYzU5LTQwZDQtYjRkNy00YzY4NTEwYjVmOWEiLCJ1c2VybmFtZSI6ImFpc2hhX2FncmEiLCJpYXQiOjE3NjI3NzczNTMsImV4cCI6MTc2NTM2OTM1M30.-iDZx1BCMqfruBmx-5t0s5DE9oIsRKytdHTfPSoLrN4","username":"aisha_agra"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:33.892651977Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777353 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"aisha_agra\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-402.yaml b/user_service/keploy/atg/tests/test-402.yaml new file mode 100755 index 0000000..c3ffbcf --- /dev/null +++ b/user_service/keploy/atg/tests/test-402.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-402 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "151" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Taj Ganj", "city": "Agra", "state": "Uttar Pradesh", "postal_code": "282001", "country": "IN", "phone": "+919837012345", "is_default": true}' + timestamp: 2025-11-10T12:22:34.623698562Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"ea294c9b-cec5-4bb4-aca0-c963574e5179"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:34.645941105Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777354 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a/addresses \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"line1\": \"Taj Ganj\", \"city\": \"Agra\", \"state\": \"Uttar Pradesh\", \"postal_code\": \"282001\", \"country\": \"IN\", \"phone\": \"+919837012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-403.yaml b/user_service/keploy/atg/tests/test-403.yaml new file mode 100755 index 0000000..04ccae9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-403.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-403 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:35.418789379Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:35 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:35.450368986Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777355 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/5ed2aa23-3c59-40d4-b4d7-4c68510b5f9a \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-404.yaml b/user_service/keploy/atg/tests/test-404.yaml new file mode 100755 index 0000000..4ef4804 --- /dev/null +++ b/user_service/keploy/atg/tests/test-404.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-404 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "parth_vadodara", "email": "parth_vadodara@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:37.195071969Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:37 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"parth_vadodara@example.in","id":"cde545bf-bacd-4efb-891d-b197fc7e0e6c","phone":null,"username":"parth_vadodara"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:37.282247161Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777357 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"parth_vadodara\", \"email\": \"parth_vadodara@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-405.yaml b/user_service/keploy/atg/tests/test-405.yaml new file mode 100755 index 0000000..5212b19 --- /dev/null +++ b/user_service/keploy/atg/tests/test-405.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-405 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "parth_vadodara", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:38.015691505Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"parth_vadodara@example.in","id":"cde545bf-bacd-4efb-891d-b197fc7e0e6c","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjZGU1NDViZi1iYWNkLTRlZmItODkxZC1iMTk3ZmM3ZTBlNmMiLCJ1c2VybmFtZSI6InBhcnRoX3ZhZG9kYXJhIiwiaWF0IjoxNzYyNzc3MzU4LCJleHAiOjE3NjUzNjkzNTh9.G_htDUUnJ2yG-03W-hbSKNrZ8NWH7M7iX2mYJqskVcE","username":"parth_vadodara"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:38.083130091Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777358 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"parth_vadodara\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-406.yaml b/user_service/keploy/atg/tests/test-406.yaml new file mode 100755 index 0000000..6c83498 --- /dev/null +++ b/user_service/keploy/atg/tests/test-406.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-406 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/cde545bf-bacd-4efb-891d-b197fc7e0e6c/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "149" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Alkapuri", "city": "Vadodara", "state": "Gujarat", "postal_code": "390007", "country": "IN", "phone": "+919824012345", "is_default": true}' + timestamp: 2025-11-10T12:22:38.762985353Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"5e2876ef-bc0a-4d94-b867-bad0945c4275"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:38.78841358Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777358 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/cde545bf-bacd-4efb-891d-b197fc7e0e6c/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Alkapuri\", \"city\": \"Vadodara\", \"state\": \"Gujarat\", \"postal_code\": \"390007\", \"country\": \"IN\", \"phone\": \"+919824012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-407.yaml b/user_service/keploy/atg/tests/test-407.yaml new file mode 100755 index 0000000..17a3aee --- /dev/null +++ b/user_service/keploy/atg/tests/test-407.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-407 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/cde545bf-bacd-4efb-891d-b197fc7e0e6c + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:39.59064508Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:39.618404028Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777359 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/cde545bf-bacd-4efb-891d-b197fc7e0e6c \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-408.yaml b/user_service/keploy/atg/tests/test-408.yaml new file mode 100755 index 0000000..58a79bf --- /dev/null +++ b/user_service/keploy/atg/tests/test-408.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-408 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "bipul_guwahati", "email": "bipul_guwahati@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:41.425653982Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:41 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"bipul_guwahati@example.in","id":"a22e67bf-eae3-4636-8ef7-0a9772a483d9","phone":null,"username":"bipul_guwahati"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:41.509641669Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777361 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"bipul_guwahati\", \"email\": \"bipul_guwahati@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-409.yaml b/user_service/keploy/atg/tests/test-409.yaml new file mode 100755 index 0000000..d153888 --- /dev/null +++ b/user_service/keploy/atg/tests/test-409.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-409 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "bipul_guwahati", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:42.203137939Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:42 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"bipul_guwahati@example.in","id":"a22e67bf-eae3-4636-8ef7-0a9772a483d9","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMjJlNjdiZi1lYWUzLTQ2MzYtOGVmNy0wYTk3NzJhNDgzZDkiLCJ1c2VybmFtZSI6ImJpcHVsX2d1d2FoYXRpIiwiaWF0IjoxNzYyNzc3MzYyLCJleHAiOjE3NjUzNjkzNjJ9.J6kuwNDAa8AESJn1mXYZNMf8k2lxaPRjLuPy91BnGVc","username":"bipul_guwahati"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:42.273695998Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777362 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"bipul_guwahati\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-41.yaml b/user_service/keploy/atg/tests/test-41.yaml new file mode 100755 index 0000000..69dc32d --- /dev/null +++ b/user_service/keploy/atg/tests/test-41.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-41 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "neha_fallback_01", "email": "neha.fallback.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:21.32758235Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:21.328963008Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776861 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"neha_fallback_01\", \"email\": \"neha.fallback.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-410.yaml b/user_service/keploy/atg/tests/test-410.yaml new file mode 100755 index 0000000..7ac5fa9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-410.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-410 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a22e67bf-eae3-4636-8ef7-0a9772a483d9/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "146" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "GS Road", "city": "Guwahati", "state": "Assam", "postal_code": "781005", "country": "IN", "phone": "+919864012345", "is_default": true}' + timestamp: 2025-11-10T12:22:42.960173817Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:42 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"aa45d8d6-302e-4967-b351-256da7a22a64"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:42.983995317Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777362 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/a22e67bf-eae3-4636-8ef7-0a9772a483d9/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"GS Road\", \"city\": \"Guwahati\", \"state\": \"Assam\", \"postal_code\": \"781005\", \"country\": \"IN\", \"phone\": \"+919864012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-411.yaml b/user_service/keploy/atg/tests/test-411.yaml new file mode 100755 index 0000000..482f2d5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-411.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-411 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a22e67bf-eae3-4636-8ef7-0a9772a483d9 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:43.783250223Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:43 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:43.811816243Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777363 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/a22e67bf-eae3-4636-8ef7-0a9772a483d9 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-412.yaml b/user_service/keploy/atg/tests/test-412.yaml new file mode 100755 index 0000000..43c1174 --- /dev/null +++ b/user_service/keploy/atg/tests/test-412.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-412 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "84" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "smita_bbsr", "email": "smita_bbsr@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:45.715383112Z + resp: + status_code: 201 + header: + Content-Length: "115" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:45 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"smita_bbsr@example.in","id":"151f3c3f-ac17-4f3d-afe9-22f899357d9d","phone":null,"username":"smita_bbsr"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:45.800933828Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777365 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"smita_bbsr\", \"email\": \"smita_bbsr@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-413.yaml b/user_service/keploy/atg/tests/test-413.yaml new file mode 100755 index 0000000..f4c52fc --- /dev/null +++ b/user_service/keploy/atg/tests/test-413.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-413 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "50" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "smita_bbsr", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:46.621963991Z + resp: + status_code: 200 + header: + Content-Length: "333" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:46 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"smita_bbsr@example.in","id":"151f3c3f-ac17-4f3d-afe9-22f899357d9d","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNTFmM2MzZi1hYzE3LTRmM2QtYWZlOS0yMmY4OTkzNTdkOWQiLCJ1c2VybmFtZSI6InNtaXRhX2Jic3IiLCJpYXQiOjE3NjI3NzczNjYsImV4cCI6MTc2NTM2OTM2Nn0.Leg4M4qo3MHmP9Abm0o1qN24VzRrQ3cg2ZosvqVln44","username":"smita_bbsr"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:46.702934765Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777366 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"smita_bbsr\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-414.yaml b/user_service/keploy/atg/tests/test-414.yaml new file mode 100755 index 0000000..15af050 --- /dev/null +++ b/user_service/keploy/atg/tests/test-414.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-414 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/151f3c3f-ac17-4f3d-afe9-22f899357d9d/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "155" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Saheed Nagar", "city": "Bhubaneswar", "state": "Odisha", "postal_code": "751007", "country": "IN", "phone": "+919437012345", "is_default": true}' + timestamp: 2025-11-10T12:22:47.451396864Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"3c7d34c2-64ed-4579-9bb4-72a576ab2e47"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:47.479052373Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777367 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/151f3c3f-ac17-4f3d-afe9-22f899357d9d/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Saheed Nagar\", \"city\": \"Bhubaneswar\", \"state\": \"Odisha\", \"postal_code\": \"751007\", \"country\": \"IN\", \"phone\": \"+919437012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-415.yaml b/user_service/keploy/atg/tests/test-415.yaml new file mode 100755 index 0000000..4fcfb19 --- /dev/null +++ b/user_service/keploy/atg/tests/test-415.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-415 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/151f3c3f-ac17-4f3d-afe9-22f899357d9d + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:48.200407121Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:48.231034345Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777368 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/151f3c3f-ac17-4f3d-afe9-22f899357d9d \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-416.yaml b/user_service/keploy/atg/tests/test-416.yaml new file mode 100755 index 0000000..a2976e2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-416.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-416 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "deepak_ranchi", "email": "deepak_ranchi@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:50.231037289Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:50 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"deepak_ranchi@example.in","id":"7c9c0bb4-569d-483f-9ed8-86cb9add8591","phone":null,"username":"deepak_ranchi"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:50.312584727Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777370 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"deepak_ranchi\", \"email\": \"deepak_ranchi@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-417.yaml b/user_service/keploy/atg/tests/test-417.yaml new file mode 100755 index 0000000..b9ec3d5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-417.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-417 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "deepak_ranchi", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:51.129141708Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"deepak_ranchi@example.in","id":"7c9c0bb4-569d-483f-9ed8-86cb9add8591","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3YzljMGJiNC01NjlkLTQ4M2YtOWVkOC04NmNiOWFkZDg1OTEiLCJ1c2VybmFtZSI6ImRlZXBha19yYW5jaGkiLCJpYXQiOjE3NjI3NzczNzEsImV4cCI6MTc2NTM2OTM3MX0.XuuIalwUuzblhCC_NywQ6kUTVUzF-x9zJyUv4tniHBk","username":"deepak_ranchi"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:51.207061897Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777371 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"deepak_ranchi\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-418.yaml b/user_service/keploy/atg/tests/test-418.yaml new file mode 100755 index 0000000..ce4792e --- /dev/null +++ b/user_service/keploy/atg/tests/test-418.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-418 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/7c9c0bb4-569d-483f-9ed8-86cb9add8591/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "151" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Kanke Road", "city": "Ranchi", "state": "Jharkhand", "postal_code": "834008", "country": "IN", "phone": "+919431112345", "is_default": true}' + timestamp: 2025-11-10T12:22:51.885435244Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"6733d6ab-8f6c-44c2-93fc-3f7cb8211ad7"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:51.910046278Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777371 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/7c9c0bb4-569d-483f-9ed8-86cb9add8591/addresses \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"line1\": \"Kanke Road\", \"city\": \"Ranchi\", \"state\": \"Jharkhand\", \"postal_code\": \"834008\", \"country\": \"IN\", \"phone\": \"+919431112345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-419.yaml b/user_service/keploy/atg/tests/test-419.yaml new file mode 100755 index 0000000..af79e4b --- /dev/null +++ b/user_service/keploy/atg/tests/test-419.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-419 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/7c9c0bb4-569d-483f-9ed8-86cb9add8591 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:52.689511841Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:52 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:52.726274554Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777372 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/7c9c0bb4-569d-483f-9ed8-86cb9add8591 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-42.yaml b/user_service/keploy/atg/tests/test-42.yaml new file mode 100755 index 0000000..f6677e3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-42.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-42 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:22.064449213Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:22 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:22.065621743Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776862 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ diff --git a/user_service/keploy/atg/tests/test-420.yaml b/user_service/keploy/atg/tests/test-420.yaml new file mode 100755 index 0000000..d7ef0cf --- /dev/null +++ b/user_service/keploy/atg/tests/test-420.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-420 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "84" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "gaurav_ddn", "email": "gaurav_ddn@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:54.608445035Z + resp: + status_code: 201 + header: + Content-Length: "115" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:54 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"gaurav_ddn@example.in","id":"a95e1d27-6e25-4d2e-afdf-c6756e7a6b26","phone":null,"username":"gaurav_ddn"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:54.696639258Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777374 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"gaurav_ddn\", \"email\": \"gaurav_ddn@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-421.yaml b/user_service/keploy/atg/tests/test-421.yaml new file mode 100755 index 0000000..335122e --- /dev/null +++ b/user_service/keploy/atg/tests/test-421.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-421 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "50" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "gaurav_ddn", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:55.441760918Z + resp: + status_code: 200 + header: + Content-Length: "333" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:55 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"gaurav_ddn@example.in","id":"a95e1d27-6e25-4d2e-afdf-c6756e7a6b26","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhOTVlMWQyNy02ZTI1LTRkMmUtYWZkZi1jNjc1NmU3YTZiMjYiLCJ1c2VybmFtZSI6ImdhdXJhdl9kZG4iLCJpYXQiOjE3NjI3NzczNzUsImV4cCI6MTc2NTM2OTM3NX0.FI_ODnoO1P1hY5jEALsor1IId3z8O7SjRWOrdRbPWyE","username":"gaurav_ddn"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:55.512505606Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777375 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"gaurav_ddn\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-422.yaml b/user_service/keploy/atg/tests/test-422.yaml new file mode 100755 index 0000000..5d5b628 --- /dev/null +++ b/user_service/keploy/atg/tests/test-422.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-422 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a95e1d27-6e25-4d2e-afdf-c6756e7a6b26/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "156" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Rajpur Road", "city": "Dehradun", "state": "Uttarakhand", "postal_code": "248001", "country": "IN", "phone": "+919412012345", "is_default": true}' + timestamp: 2025-11-10T12:22:56.353851072Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:56 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"8bd5d485-3e66-43e1-9d78-35736f37b0c4"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:56.379869584Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777376 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/a95e1d27-6e25-4d2e-afdf-c6756e7a6b26/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Rajpur Road\", \"city\": \"Dehradun\", \"state\": \"Uttarakhand\", \"postal_code\": \"248001\", \"country\": \"IN\", \"phone\": \"+919412012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-423.yaml b/user_service/keploy/atg/tests/test-423.yaml new file mode 100755 index 0000000..d8cde06 --- /dev/null +++ b/user_service/keploy/atg/tests/test-423.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-423 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a95e1d27-6e25-4d2e-afdf-c6756e7a6b26 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:22:57.114465682Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:57 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:57.147195408Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777377 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/a95e1d27-6e25-4d2e-afdf-c6756e7a6b26 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-424.yaml b/user_service/keploy/atg/tests/test-424.yaml new file mode 100755 index 0000000..64d369a --- /dev/null +++ b/user_service/keploy/atg/tests/test-424.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-424 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "chaitra_mysuru", "email": "chaitra_mysuru@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:59.030246705Z + resp: + status_code: 201 + header: + Content-Length: "123" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:59 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"chaitra_mysuru@example.in","id":"0b152bc2-443c-424f-a1eb-5e872f244af0","phone":null,"username":"chaitra_mysuru"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:59.111062709Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777379 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"chaitra_mysuru\", \"email\": \"chaitra_mysuru@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-425.yaml b/user_service/keploy/atg/tests/test-425.yaml new file mode 100755 index 0000000..5d04a57 --- /dev/null +++ b/user_service/keploy/atg/tests/test-425.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-425 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "chaitra_mysuru", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:22:59.806026889Z + resp: + status_code: 200 + header: + Content-Length: "346" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:22:59 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"chaitra_mysuru@example.in","id":"0b152bc2-443c-424f-a1eb-5e872f244af0","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwYjE1MmJjMi00NDNjLTQyNGYtYTFlYi01ZTg3MmYyNDRhZjAiLCJ1c2VybmFtZSI6ImNoYWl0cmFfbXlzdXJ1IiwiaWF0IjoxNzYyNzc3Mzc5LCJleHAiOjE3NjUzNjkzNzl9.hQS6WLK4_IqYN0kVng6-GaxhLuwIFafmzBItO8R40ps","username":"chaitra_mysuru"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:22:59.871579001Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777379 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"chaitra_mysuru\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-426.yaml b/user_service/keploy/atg/tests/test-426.yaml new file mode 100755 index 0000000..02c6682 --- /dev/null +++ b/user_service/keploy/atg/tests/test-426.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-426 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/0b152bc2-443c-424f-a1eb-5e872f244af0/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "153" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Kuvempunagar", "city": "Mysuru", "state": "Karnataka", "postal_code": "570023", "country": "IN", "phone": "+919845012345", "is_default": true}' + timestamp: 2025-11-10T12:23:00.567672033Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:00 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"f8737d26-8010-4838-9d0d-faee0c60c74f"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:00.594791406Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777380 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/0b152bc2-443c-424f-a1eb-5e872f244af0/addresses \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"line1\": \"Kuvempunagar\", \"city\": \"Mysuru\", \"state\": \"Karnataka\", \"postal_code\": \"570023\", \"country\": \"IN\", \"phone\": \"+919845012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-427.yaml b/user_service/keploy/atg/tests/test-427.yaml new file mode 100755 index 0000000..ce6f098 --- /dev/null +++ b/user_service/keploy/atg/tests/test-427.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-427 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/0b152bc2-443c-424f-a1eb-5e872f244af0 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:23:01.305964201Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:01.337800264Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777381 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/0b152bc2-443c-424f-a1eb-5e872f244af0 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-428.yaml b/user_service/keploy/atg/tests/test-428.yaml new file mode 100755 index 0000000..d9d6fbc --- /dev/null +++ b/user_service/keploy/atg/tests/test-428.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-428 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "94" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "farooq_srinagar", "email": "farooq_srinagar@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:03.299977401Z + resp: + status_code: 201 + header: + Content-Length: "125" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:03 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"farooq_srinagar@example.in","id":"86dd9463-20ce-4da2-b106-2cf1077bda0b","phone":null,"username":"farooq_srinagar"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:03.385900673Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777383 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"farooq_srinagar\", \"email\": \"farooq_srinagar@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-429.yaml b/user_service/keploy/atg/tests/test-429.yaml new file mode 100755 index 0000000..cfa2bdd --- /dev/null +++ b/user_service/keploy/atg/tests/test-429.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-429 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "55" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "farooq_srinagar", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:04.261061468Z + resp: + status_code: 200 + header: + Content-Length: "350" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:04 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"farooq_srinagar@example.in","id":"86dd9463-20ce-4da2-b106-2cf1077bda0b","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4NmRkOTQ2My0yMGNlLTRkYTItYjEwNi0yY2YxMDc3YmRhMGIiLCJ1c2VybmFtZSI6ImZhcm9vcV9zcmluYWdhciIsImlhdCI6MTc2Mjc3NzM4NCwiZXhwIjoxNzY1MzY5Mzg0fQ.SaiL4pdliwHCUuL6pAHydDc5z8Shjl4tTGg44ICQYXQ","username":"farooq_srinagar"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:04.330814315Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777384 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"farooq_srinagar\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-43.yaml b/user_service/keploy/atg/tests/test-43.yaml new file mode 100755 index 0000000..a2a392b --- /dev/null +++ b/user_service/keploy/atg/tests/test-43.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-43 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:25.986586255Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODY2LCJleHAiOjE3NjUzNjg4NjZ9.PoAYpy195riRsnTwmOVJMjYTSR1y8Q3boyQ0Lrv13JE","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:26.04777853Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776866 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-430.yaml b/user_service/keploy/atg/tests/test-430.yaml new file mode 100755 index 0000000..08aabdc --- /dev/null +++ b/user_service/keploy/atg/tests/test-430.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-430 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/86dd9463-20ce-4da2-b106-2cf1077bda0b/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "160" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Lal Chowk", "city": "Srinagar", "state": "Jammu and Kashmir", "postal_code": "190001", "country": "IN", "phone": "+919419012345", "is_default": true}' + timestamp: 2025-11-10T12:23:05.014033523Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:05 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"8ca3d49c-0888-4b28-9987-d25e74f145e6"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:05.042394927Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777385 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/86dd9463-20ce-4da2-b106-2cf1077bda0b/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"line1\": \"Lal Chowk\", \"city\": \"Srinagar\", \"state\": \"Jammu and Kashmir\", \"postal_code\": \"190001\", \"country\": \"IN\", \"phone\": \"+919419012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-431.yaml b/user_service/keploy/atg/tests/test-431.yaml new file mode 100755 index 0000000..e45c569 --- /dev/null +++ b/user_service/keploy/atg/tests/test-431.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-431 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/86dd9463-20ce-4da2-b106-2cf1077bda0b + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:23:05.814257985Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:05 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:05.84129722Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777385 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/86dd9463-20ce-4da2-b106-2cf1077bda0b \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-432.yaml b/user_service/keploy/atg/tests/test-432.yaml new file mode 100755 index 0000000..c4d362e --- /dev/null +++ b/user_service/keploy/atg/tests/test-432.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-432 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "88" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "ankit_shimla", "email": "ankit_shimla@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:07.772539657Z + resp: + status_code: 201 + header: + Content-Length: "119" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"ankit_shimla@example.in","id":"2d5b0ab1-6b3a-4b31-a0c1-78d853542c98","phone":null,"username":"ankit_shimla"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:07.855015838Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777387 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"ankit_shimla\", \"email\": \"ankit_shimla@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-433.yaml b/user_service/keploy/atg/tests/test-433.yaml new file mode 100755 index 0000000..8e43ace --- /dev/null +++ b/user_service/keploy/atg/tests/test-433.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-433 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "ankit_shimla", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:08.641546705Z + resp: + status_code: 200 + header: + Content-Length: "340" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:08 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"ankit_shimla@example.in","id":"2d5b0ab1-6b3a-4b31-a0c1-78d853542c98","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyZDViMGFiMS02YjNhLTRiMzEtYTBjMS03OGQ4NTM1NDJjOTgiLCJ1c2VybmFtZSI6ImFua2l0X3NoaW1sYSIsImlhdCI6MTc2Mjc3NzM4OCwiZXhwIjoxNzY1MzY5Mzg4fQ.qbw0m_y8__F68VduPTTb1N9Q3IxewTU4eo0rIVIPMn4","username":"ankit_shimla"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:08.711757368Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777388 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"ankit_shimla\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-434.yaml b/user_service/keploy/atg/tests/test-434.yaml new file mode 100755 index 0000000..f4d99d3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-434.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-434 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/2d5b0ab1-6b3a-4b31-a0c1-78d853542c98/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "161" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "The Mall Road", "city": "Shimla", "state": "Himachal Pradesh", "postal_code": "171001", "country": "IN", "phone": "+919418012345", "is_default": true}' + timestamp: 2025-11-10T12:23:09.468308894Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:09 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"60846d3f-1c66-44e2-bd08-d0cc0b390e18"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:09.493514464Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777389 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/2d5b0ab1-6b3a-4b31-a0c1-78d853542c98/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"The Mall Road\", \"city\": \"Shimla\", \"state\": \"Himachal Pradesh\", \"postal_code\": \"171001\", \"country\": \"IN\", \"phone\": \"+919418012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-435.yaml b/user_service/keploy/atg/tests/test-435.yaml new file mode 100755 index 0000000..860b780 --- /dev/null +++ b/user_service/keploy/atg/tests/test-435.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-435 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/2d5b0ab1-6b3a-4b31-a0c1-78d853542c98 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:23:10.234286652Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:10 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:10.267983881Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777390 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/2d5b0ab1-6b3a-4b31-a0c1-78d853542c98 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-436.yaml b/user_service/keploy/atg/tests/test-436.yaml new file mode 100755 index 0000000..8d32a86 --- /dev/null +++ b/user_service/keploy/atg/tests/test-436.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-436 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "shreya_raipur", "email": "shreya_raipur@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:12.139574828Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"shreya_raipur@example.in","id":"c6513285-696c-4f39-ac7a-116bfb97a272","phone":null,"username":"shreya_raipur"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:12.217963203Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777392 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"shreya_raipur\", \"email\": \"shreya_raipur@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-437.yaml b/user_service/keploy/atg/tests/test-437.yaml new file mode 100755 index 0000000..80506b2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-437.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-437 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "shreya_raipur", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:12.900761327Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"shreya_raipur@example.in","id":"c6513285-696c-4f39-ac7a-116bfb97a272","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjNjUxMzI4NS02OTZjLTRmMzktYWM3YS0xMTZiZmI5N2EyNzIiLCJ1c2VybmFtZSI6InNocmV5YV9yYWlwdXIiLCJpYXQiOjE3NjI3NzczOTIsImV4cCI6MTc2NTM2OTM5Mn0.FSeyaJD0SGiSBWa6g072VLF5bjpxX1HA1CbGxEw2CV0","username":"shreya_raipur"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:12.968353191Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777392 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"shreya_raipur\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-438.yaml b/user_service/keploy/atg/tests/test-438.yaml new file mode 100755 index 0000000..8175186 --- /dev/null +++ b/user_service/keploy/atg/tests/test-438.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-438 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/c6513285-696c-4f39-ac7a-116bfb97a272/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "155" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Civil Lines", "city": "Raipur", "state": "Chhattisgarh", "postal_code": "492001", "country": "IN", "phone": "+919425212345", "is_default": true}' + timestamp: 2025-11-10T12:23:13.667549259Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:13 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"8fe9dd7c-b34d-4b02-a06a-7d566401dec7"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:13.688977139Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777393 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/c6513285-696c-4f39-ac7a-116bfb97a272/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Civil Lines\", \"city\": \"Raipur\", \"state\": \"Chhattisgarh\", \"postal_code\": \"492001\", \"country\": \"IN\", \"phone\": \"+919425212345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-439.yaml b/user_service/keploy/atg/tests/test-439.yaml new file mode 100755 index 0000000..f146893 --- /dev/null +++ b/user_service/keploy/atg/tests/test-439.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-439 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/c6513285-696c-4f39-ac7a-116bfb97a272 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:23:14.439397708Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:14 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:14.472720259Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777394 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/c6513285-696c-4f39-ac7a-116bfb97a272 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-44.yaml b/user_service/keploy/atg/tests/test-44.yaml new file mode 100755 index 0000000..0d3e42a --- /dev/null +++ b/user_service/keploy/atg/tests/test-44.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-44 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "kavita_list_01", "email": "kavita.list.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:26.773725105Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:26.775248991Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776866 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"kavita_list_01\", \"email\": \"kavita.list.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-440.yaml b/user_service/keploy/atg/tests/test-440.yaml new file mode 100755 index 0000000..191f62b --- /dev/null +++ b/user_service/keploy/atg/tests/test-440.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-440 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "86" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "karthik_cbe", "email": "karthik_cbe@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:16.857781349Z + resp: + status_code: 201 + header: + Content-Length: "117" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:16 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"karthik_cbe@example.in","id":"c451d32e-5cdf-4ca8-aeeb-0442518719a7","phone":null,"username":"karthik_cbe"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:16.940986344Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777396 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"karthik_cbe\", \"email\": \"karthik_cbe@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-441.yaml b/user_service/keploy/atg/tests/test-441.yaml new file mode 100755 index 0000000..7d35c04 --- /dev/null +++ b/user_service/keploy/atg/tests/test-441.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-441 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "51" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "karthik_cbe", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:17.663925853Z + resp: + status_code: 200 + header: + Content-Length: "336" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:17 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"karthik_cbe@example.in","id":"c451d32e-5cdf-4ca8-aeeb-0442518719a7","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjNDUxZDMyZS01Y2RmLTRjYTgtYWVlYi0wNDQyNTE4NzE5YTciLCJ1c2VybmFtZSI6ImthcnRoaWtfY2JlIiwiaWF0IjoxNzYyNzc3Mzk3LCJleHAiOjE3NjUzNjkzOTd9.SuiY6FuTkc5-93v_SE54nA0LlmVcIEjCgeCk3qrVtAI","username":"karthik_cbe"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:17.736569986Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777397 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"karthik_cbe\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-442.yaml b/user_service/keploy/atg/tests/test-442.yaml new file mode 100755 index 0000000..c452248 --- /dev/null +++ b/user_service/keploy/atg/tests/test-442.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-442 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/c451d32e-5cdf-4ca8-aeeb-0442518719a7/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "154" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "RS Puram", "city": "Coimbatore", "state": "Tamil Nadu", "postal_code": "641002", "country": "IN", "phone": "+919843012345", "is_default": true}' + timestamp: 2025-11-10T12:23:18.413504609Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:18 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"4f16c0dc-ad54-493d-9caa-7f07512f702c"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:18.439125005Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777398 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/c451d32e-5cdf-4ca8-aeeb-0442518719a7/addresses \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"line1\": \"RS Puram\", \"city\": \"Coimbatore\", \"state\": \"Tamil Nadu\", \"postal_code\": \"641002\", \"country\": \"IN\", \"phone\": \"+919843012345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-443.yaml b/user_service/keploy/atg/tests/test-443.yaml new file mode 100755 index 0000000..ee3c222 --- /dev/null +++ b/user_service/keploy/atg/tests/test-443.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-443 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/c451d32e-5cdf-4ca8-aeeb-0442518719a7 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:23:19.157342194Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:19 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:19.180477641Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777399 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/c451d32e-5cdf-4ca8-aeeb-0442518719a7 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-444.yaml b/user_service/keploy/atg/tests/test-444.yaml new file mode 100755 index 0000000..7ea8843 --- /dev/null +++ b/user_service/keploy/atg/tests/test-444.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-444 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "meena_madurai", "email": "meena_madurai@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:21.704076156Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"meena_madurai@example.in","id":"7589f9d1-ff0f-4de5-8826-94e379e80c71","phone":null,"username":"meena_madurai"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:21.800963847Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777401 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"meena_madurai\", \"email\": \"meena_madurai@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-445.yaml b/user_service/keploy/atg/tests/test-445.yaml new file mode 100755 index 0000000..c4ffbf8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-445.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-445 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "meena_madurai", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:22.574838361Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:22 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"meena_madurai@example.in","id":"7589f9d1-ff0f-4de5-8826-94e379e80c71","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NTg5ZjlkMS1mZjBmLTRkZTUtODgyNi05NGUzNzllODBjNzEiLCJ1c2VybmFtZSI6Im1lZW5hX21hZHVyYWkiLCJpYXQiOjE3NjI3Nzc0MDIsImV4cCI6MTc2NTM2OTQwMn0.KQ5i4q37OfQNVtTLPAotg4A8OJe2ATZOrz3zIK0m0kA","username":"meena_madurai"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:22.646628251Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777402 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"meena_madurai\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-446.yaml b/user_service/keploy/atg/tests/test-446.yaml new file mode 100755 index 0000000..eca2fb2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-446.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-446 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/7589f9d1-ff0f-4de5-8826-94e379e80c71/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "153" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Anna Nagar", "city": "Madurai", "state": "Tamil Nadu", "postal_code": "625020", "country": "IN", "phone": "+919842112345", "is_default": true}' + timestamp: 2025-11-10T12:23:23.321103256Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"ce31628e-b33b-4dff-8662-bc96f9929894"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:23.346728552Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777403 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/7589f9d1-ff0f-4de5-8826-94e379e80c71/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Anna Nagar\", \"city\": \"Madurai\", \"state\": \"Tamil Nadu\", \"postal_code\": \"625020\", \"country\": \"IN\", \"phone\": \"+919842112345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-447.yaml b/user_service/keploy/atg/tests/test-447.yaml new file mode 100755 index 0000000..2379abc --- /dev/null +++ b/user_service/keploy/atg/tests/test-447.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-447 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/7589f9d1-ff0f-4de5-8826-94e379e80c71 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:23:24.149883041Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:24 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:24.174362787Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777404 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/7589f9d1-ff0f-4de5-8826-94e379e80c71 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-448.yaml b/user_service/keploy/atg/tests/test-448.yaml new file mode 100755 index 0000000..b3becbc --- /dev/null +++ b/user_service/keploy/atg/tests/test-448.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-448 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "sunil_jodhpur", "email": "sunil_jodhpur@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:26.58856181Z + resp: + status_code: 201 + header: + Content-Length: "121" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"sunil_jodhpur@example.in","id":"a1276f9a-46d4-471e-8067-75cb6ea37800","phone":null,"username":"sunil_jodhpur"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:26.6759843Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777406 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"sunil_jodhpur\", \"email\": \"sunil_jodhpur@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-449.yaml b/user_service/keploy/atg/tests/test-449.yaml new file mode 100755 index 0000000..767f329 --- /dev/null +++ b/user_service/keploy/atg/tests/test-449.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-449 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "sunil_jodhpur", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:27.524144384Z + resp: + status_code: 200 + header: + Content-Length: "343" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:27 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"sunil_jodhpur@example.in","id":"a1276f9a-46d4-471e-8067-75cb6ea37800","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMTI3NmY5YS00NmQ0LTQ3MWUtODA2Ny03NWNiNmVhMzc4MDAiLCJ1c2VybmFtZSI6InN1bmlsX2pvZGhwdXIiLCJpYXQiOjE3NjI3Nzc0MDcsImV4cCI6MTc2NTM2OTQwN30.u2PejazngBrjSuB_EoeCZoBX7u7PhQ1gisDTSQ1ZJnM","username":"sunil_jodhpur"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:27.618999182Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777407 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"sunil_jodhpur\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-45.yaml b/user_service/keploy/atg/tests/test-45.yaml new file mode 100755 index 0000000..7ed25db --- /dev/null +++ b/user_service/keploy/atg/tests/test-45.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-45 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:27.50659179Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:27 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:27.508180827Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776867 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-450.yaml b/user_service/keploy/atg/tests/test-450.yaml new file mode 100755 index 0000000..794f87a --- /dev/null +++ b/user_service/keploy/atg/tests/test-450.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-450 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a1276f9a-46d4-471e-8067-75cb6ea37800/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "152" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Sardarpura", "city": "Jodhpur", "state": "Rajasthan", "postal_code": "342003", "country": "IN", "phone": "+919829112345", "is_default": true}' + timestamp: 2025-11-10T12:23:28.352197447Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:28 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"57e896c2-18ea-4aa2-9703-f064429a1b72"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:28.389165658Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777408 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/a1276f9a-46d4-471e-8067-75cb6ea37800/addresses \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"line1\": \"Sardarpura\", \"city\": \"Jodhpur\", \"state\": \"Rajasthan\", \"postal_code\": \"342003\", \"country\": \"IN\", \"phone\": \"+919829112345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-451.yaml b/user_service/keploy/atg/tests/test-451.yaml new file mode 100755 index 0000000..eb91a7b --- /dev/null +++ b/user_service/keploy/atg/tests/test-451.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-451 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/a1276f9a-46d4-471e-8067-75cb6ea37800 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:23:29.128570661Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:29 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:29.153954789Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777409 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/a1276f9a-46d4-471e-8067-75cb6ea37800 \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-452.yaml b/user_service/keploy/atg/tests/test-452.yaml new file mode 100755 index 0000000..830169b --- /dev/null +++ b/user_service/keploy/atg/tests/test-452.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-452 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "80" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "vani_vja", "email": "vani_vja@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:31.270283711Z + resp: + status_code: 201 + header: + Content-Length: "111" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"vani_vja@example.in","id":"96b38460-486f-42f1-8251-da28d33c8749","phone":null,"username":"vani_vja"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:31.371537026Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777411 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"vani_vja\", \"email\": \"vani_vja@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-453.yaml b/user_service/keploy/atg/tests/test-453.yaml new file mode 100755 index 0000000..e4ecdd4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-453.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-453 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "48" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "vani_vja", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:32.131843314Z + resp: + status_code: 200 + header: + Content-Length: "326" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"vani_vja@example.in","id":"96b38460-486f-42f1-8251-da28d33c8749","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5NmIzODQ2MC00ODZmLTQyZjEtODI1MS1kYTI4ZDMzYzg3NDkiLCJ1c2VybmFtZSI6InZhbmlfdmphIiwiaWF0IjoxNzYyNzc3NDEyLCJleHAiOjE3NjUzNjk0MTJ9.z9G1Fk1dkXPyFlhzjts4VysJuaj5Y-c6ZMsZDY3fGOc","username":"vani_vja"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:32.203972381Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777412 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"vani_vja\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-454.yaml b/user_service/keploy/atg/tests/test-454.yaml new file mode 100755 index 0000000..d6b26de --- /dev/null +++ b/user_service/keploy/atg/tests/test-454.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-454 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/96b38460-486f-42f1-8251-da28d33c8749/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "161" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Benz Circle", "city": "Vijayawada", "state": "Andhra Pradesh", "postal_code": "520010", "country": "IN", "phone": "+919848112345", "is_default": true}' + timestamp: 2025-11-10T12:23:32.900672561Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"22677a17-3088-4ec3-ad4c-cb8dec1d6cf0"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:32.930878469Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777412 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/96b38460-486f-42f1-8251-da28d33c8749/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Benz Circle\", \"city\": \"Vijayawada\", \"state\": \"Andhra Pradesh\", \"postal_code\": \"520010\", \"country\": \"IN\", \"phone\": \"+919848112345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-455.yaml b/user_service/keploy/atg/tests/test-455.yaml new file mode 100755 index 0000000..f864ad5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-455.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-455 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/96b38460-486f-42f1-8251-da28d33c8749 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:23:33.816473102Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:33 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:33.846456Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777413 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/96b38460-486f-42f1-8251-da28d33c8749 \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-456.yaml b/user_service/keploy/atg/tests/test-456.yaml new file mode 100755 index 0000000..d68159d --- /dev/null +++ b/user_service/keploy/atg/tests/test-456.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-456 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "94" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "nair_trivandrum", "email": "nair_trivandrum@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:35.793049993Z + resp: + status_code: 201 + header: + Content-Length: "125" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:35 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"nair_trivandrum@example.in","id":"7a640bce-243a-429e-907b-feb7b10dc1fb","phone":null,"username":"nair_trivandrum"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:35.887390234Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777415 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"nair_trivandrum\", \"email\": \"nair_trivandrum@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-457.yaml b/user_service/keploy/atg/tests/test-457.yaml new file mode 100755 index 0000000..8de0785 --- /dev/null +++ b/user_service/keploy/atg/tests/test-457.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-457 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "55" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "nair_trivandrum", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:23:36.748621041Z + resp: + status_code: 200 + header: + Content-Length: "350" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:36 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"nair_trivandrum@example.in","id":"7a640bce-243a-429e-907b-feb7b10dc1fb","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3YTY0MGJjZS0yNDNhLTQyOWUtOTA3Yi1mZWI3YjEwZGMxZmIiLCJ1c2VybmFtZSI6Im5haXJfdHJpdmFuZHJ1bSIsImlhdCI6MTc2Mjc3NzQxNiwiZXhwIjoxNzY1MzY5NDE2fQ.d9IjTj9nyB_Bhc1S9j_OJ8WvM5q1KhcEzDQlgor2Vhg","username":"nair_trivandrum"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:36.830353269Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777416 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"nair_trivandrum\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-458.yaml b/user_service/keploy/atg/tests/test-458.yaml new file mode 100755 index 0000000..5c6f635 --- /dev/null +++ b/user_service/keploy/atg/tests/test-458.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-458 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/7a640bce-243a-429e-907b-feb7b10dc1fb/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "156" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "Pattom", "city": "Thiruvananthapuram", "state": "Kerala", "postal_code": "695004", "country": "IN", "phone": "+919847112345", "is_default": true}' + timestamp: 2025-11-10T12:23:37.530075575Z + resp: + status_code: 201 + header: + Content-Length: "46" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:37 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"id":"d95b3363-0f2f-4d70-98ca-02d2b6576427"} + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:37.556020529Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777417 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/7a640bce-243a-429e-907b-feb7b10dc1fb/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"line1\": \"Pattom\", \"city\": \"Thiruvananthapuram\", \"state\": \"Kerala\", \"postal_code\": \"695004\", \"country\": \"IN\", \"phone\": \"+919847112345\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-459.yaml b/user_service/keploy/atg/tests/test-459.yaml new file mode 100755 index 0000000..b0af687 --- /dev/null +++ b/user_service/keploy/atg/tests/test-459.yaml @@ -0,0 +1,43 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-459 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/7a640bce-243a-429e-907b-feb7b10dc1fb + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:23:38.318432761Z + resp: + status_code: 200 + header: + Content-Length: "17" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:23:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"deleted":true} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:23:38.347255021Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777418 +curl: | + curl --request DELETE \ + --url http://localhost:8082/api/v1/users/7a640bce-243a-429e-907b-feb7b10dc1fb \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-46.yaml b/user_service/keploy/atg/tests/test-46.yaml new file mode 100755 index 0000000..3498a70 --- /dev/null +++ b/user_service/keploy/atg/tests/test-46.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-46 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:31.485052226Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODcxLCJleHAiOjE3NjUzNjg4NzF9.56N35-IhpfU1sVLJCTloQHLg88S0hvlyJ16R5YmU9l8","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:31.54398929Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776871 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-460.yaml b/user_service/keploy/atg/tests/test-460.yaml new file mode 100755 index 0000000..95f0771 --- /dev/null +++ b/user_service/keploy/atg/tests/test-460.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-460 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "93" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "productuser_01", "email": "productuser_01@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:01.987189813Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:01.988549052Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777501 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"productuser_01\", \"email\": \"productuser_01@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-461.yaml b/user_service/keploy/atg/tests/test-461.yaml new file mode 100755 index 0000000..b9152d1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-461.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-461 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "productuser_01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:02.731421608Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:02 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:02.732832637Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777502 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"productuser_01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-462.yaml b/user_service/keploy/atg/tests/test-462.yaml new file mode 100755 index 0000000..f1f3333 --- /dev/null +++ b/user_service/keploy/atg/tests/test-462.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-462 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:03.528281005Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:03 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:03.529603394Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777503 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-463.yaml b/user_service/keploy/atg/tests/test-463.yaml new file mode 100755 index 0000000..7d9662b --- /dev/null +++ b/user_service/keploy/atg/tests/test-463.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-463 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "93" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "productuser_02", "email": "productuser_02@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:06.54945855Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:06.550716609Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777506 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"productuser_02\", \"email\": \"productuser_02@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-464.yaml b/user_service/keploy/atg/tests/test-464.yaml new file mode 100755 index 0000000..6f2897c --- /dev/null +++ b/user_service/keploy/atg/tests/test-464.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-464 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "productuser_02", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:07.443883644Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:07.445356582Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777507 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"productuser_02\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-465.yaml b/user_service/keploy/atg/tests/test-465.yaml new file mode 100755 index 0000000..6f392f2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-465.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-465 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:08.197199495Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:08 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:08.198509914Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777508 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-466.yaml b/user_service/keploy/atg/tests/test-466.yaml new file mode 100755 index 0000000..77347ab --- /dev/null +++ b/user_service/keploy/atg/tests/test-466.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-466 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "93" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "productuser_03", "email": "productuser_03@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:12.136279931Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:12.13753343Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777512 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"productuser_03\", \"email\": \"productuser_03@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-467.yaml b/user_service/keploy/atg/tests/test-467.yaml new file mode 100755 index 0000000..66301d1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-467.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-467 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "productuser_03", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:12.899848856Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:12.901091135Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777512 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"productuser_03\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-468.yaml b/user_service/keploy/atg/tests/test-468.yaml new file mode 100755 index 0000000..3fa0337 --- /dev/null +++ b/user_service/keploy/atg/tests/test-468.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-468 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:13.718648051Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:13 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:13.71979975Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777513 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-469.yaml b/user_service/keploy/atg/tests/test-469.yaml new file mode 100755 index 0000000..5d687ef --- /dev/null +++ b/user_service/keploy/atg/tests/test-469.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-469 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "89" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_01", "email": "orderuser_01@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:16.935169202Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:16 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:16.936258493Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777516 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"orderuser_01\", \"email\": \"orderuser_01@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-47.yaml b/user_service/keploy/atg/tests/test-47.yaml new file mode 100755 index 0000000..f9a56f3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-47.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-47 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders/99999999-9999-9999-9999-999999999999 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:32.221246938Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:32.222619207Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776872 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/orders/99999999-9999-9999-9999-999999999999 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-470.yaml b/user_service/keploy/atg/tests/test-470.yaml new file mode 100755 index 0000000..68c620f --- /dev/null +++ b/user_service/keploy/atg/tests/test-470.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-470 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:17.653432196Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:17 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:17.655066071Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777517 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"orderuser_01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-471.yaml b/user_service/keploy/atg/tests/test-471.yaml new file mode 100755 index 0000000..ee79d74 --- /dev/null +++ b/user_service/keploy/atg/tests/test-471.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-471 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:18.452378477Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:18 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:18.453853184Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777518 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-472.yaml b/user_service/keploy/atg/tests/test-472.yaml new file mode 100755 index 0000000..1cca10f --- /dev/null +++ b/user_service/keploy/atg/tests/test-472.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-472 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "89" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_02", "email": "orderuser_02@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:22.40089925Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:22 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:22.402162579Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777522 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"orderuser_02\", \"email\": \"orderuser_02@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-473.yaml b/user_service/keploy/atg/tests/test-473.yaml new file mode 100755 index 0000000..a7aced7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-473.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-473 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_02", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:23.168334223Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:23.169450604Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777523 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"orderuser_02\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-474.yaml b/user_service/keploy/atg/tests/test-474.yaml new file mode 100755 index 0000000..8861b22 --- /dev/null +++ b/user_service/keploy/atg/tests/test-474.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-474 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:23.945960293Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:23.947084693Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777523 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-475.yaml b/user_service/keploy/atg/tests/test-475.yaml new file mode 100755 index 0000000..8338c09 --- /dev/null +++ b/user_service/keploy/atg/tests/test-475.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-475 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "89" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_03", "email": "orderuser_03@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:29.155477081Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:29 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:29.156757171Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777529 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"orderuser_03\", \"email\": \"orderuser_03@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-476.yaml b/user_service/keploy/atg/tests/test-476.yaml new file mode 100755 index 0000000..36e210c --- /dev/null +++ b/user_service/keploy/atg/tests/test-476.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-476 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_03", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:29.974756604Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:29 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:29.975881535Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777529 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"orderuser_03\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-477.yaml b/user_service/keploy/atg/tests/test-477.yaml new file mode 100755 index 0000000..6c937e6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-477.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-477 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:30.779719648Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:30 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:30.781198545Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777530 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-478.yaml b/user_service/keploy/atg/tests/test-478.yaml new file mode 100755 index 0000000..155815c --- /dev/null +++ b/user_service/keploy/atg/tests/test-478.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-478 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "89" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_04", "email": "orderuser_04@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:34.476164297Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:34.477249448Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777534 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"orderuser_04\", \"email\": \"orderuser_04@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-479.yaml b/user_service/keploy/atg/tests/test-479.yaml new file mode 100755 index 0000000..3f46573 --- /dev/null +++ b/user_service/keploy/atg/tests/test-479.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-479 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_04", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:35.23430259Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:35 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:35.235532969Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777535 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"orderuser_04\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-48.yaml b/user_service/keploy/atg/tests/test-48.yaml new file mode 100755 index 0000000..44654e4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-48.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-48 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:33.052952728Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:33 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODczLCJleHAiOjE3NjUzNjg4NzN9.9_aCTokUHod2Aj7Vi8nC3JDdl7aofzKdZNp-NNqaVIw","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:33.112609626Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776873 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-480.yaml b/user_service/keploy/atg/tests/test-480.yaml new file mode 100755 index 0000000..b3352f8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-480.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-480 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:36.078493906Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:36 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:36.079693026Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777536 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-481.yaml b/user_service/keploy/atg/tests/test-481.yaml new file mode 100755 index 0000000..8998c2d --- /dev/null +++ b/user_service/keploy/atg/tests/test-481.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-481 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "151" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "auserwithaveryverylongandunnecessarilycomplexusernamethatshouldnotbevalidatall", "email": "longuser@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:39.750947228Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:39.752106889Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777539 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"auserwithaveryverylongandunnecessarilycomplexusernamethatshouldnotbevalidatall\", \"email\": \"longuser@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-482.yaml b/user_service/keploy/atg/tests/test-482.yaml new file mode 100755 index 0000000..a213724 --- /dev/null +++ b/user_service/keploy/atg/tests/test-482.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-482 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "89" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_05", "email": "orderuser_05@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:42.046405724Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:42 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:42.047701244Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777542 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"orderuser_05\", \"email\": \"orderuser_05@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-483.yaml b/user_service/keploy/atg/tests/test-483.yaml new file mode 100755 index 0000000..d8f5c2c --- /dev/null +++ b/user_service/keploy/atg/tests/test-483.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-483 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_05", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:42.790046499Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:42 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:42.791372137Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777542 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"orderuser_05\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-484.yaml b/user_service/keploy/atg/tests/test-484.yaml new file mode 100755 index 0000000..5b904d2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-484.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-484 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:43.608003065Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:43 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:43.609173714Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777543 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-485.yaml b/user_service/keploy/atg/tests/test-485.yaml new file mode 100755 index 0000000..40a9197 --- /dev/null +++ b/user_service/keploy/atg/tests/test-485.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-485 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "89" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_06", "email": "orderuser_06@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:47.007132888Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:47.008650964Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777547 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"orderuser_06\", \"email\": \"orderuser_06@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-486.yaml b/user_service/keploy/atg/tests/test-486.yaml new file mode 100755 index 0000000..dec82b2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-486.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-486 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_06", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:47.808294232Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:47.809531383Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777547 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"orderuser_06\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-487.yaml b/user_service/keploy/atg/tests/test-487.yaml new file mode 100755 index 0000000..06aef19 --- /dev/null +++ b/user_service/keploy/atg/tests/test-487.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-487 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:48.567425689Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:48.568717278Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777548 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-488.yaml b/user_service/keploy/atg/tests/test-488.yaml new file mode 100755 index 0000000..64fd156 --- /dev/null +++ b/user_service/keploy/atg/tests/test-488.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-488 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "89" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_07", "email": "orderuser_07@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:51.636220525Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:51.637718962Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777551 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"orderuser_07\", \"email\": \"orderuser_07@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-489.yaml b/user_service/keploy/atg/tests/test-489.yaml new file mode 100755 index 0000000..d34d789 --- /dev/null +++ b/user_service/keploy/atg/tests/test-489.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-489 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "52" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_07", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:52.531432598Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:52 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:52.532594768Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777552 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"orderuser_07\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-49.yaml b/user_service/keploy/atg/tests/test-49.yaml new file mode 100755 index 0000000..01dd6f7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-49.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-49 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "deepak_pay_01", "email": "deepak.pay.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:33.786741943Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:33 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:33.787978903Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776873 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"deepak_pay_01\", \"email\": \"deepak.pay.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-490.yaml b/user_service/keploy/atg/tests/test-490.yaml new file mode 100755 index 0000000..fc577c8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-490.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-490 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:53.294455622Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:53 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:53.29587928Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777553 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-491.yaml b/user_service/keploy/atg/tests/test-491.yaml new file mode 100755 index 0000000..4c1c1c5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-491.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-491 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "93" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "productuser_04", "email": "productuser_04@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:56.289438224Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:56 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:56.290531466Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777556 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"productuser_04\", \"email\": \"productuser_04@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-492.yaml b/user_service/keploy/atg/tests/test-492.yaml new file mode 100755 index 0000000..8ca8848 --- /dev/null +++ b/user_service/keploy/atg/tests/test-492.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-492 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "productuser_04", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:25:56.974310549Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:56 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:56.975461331Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777556 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"productuser_04\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-493.yaml b/user_service/keploy/atg/tests/test-493.yaml new file mode 100755 index 0000000..c1b2e26 --- /dev/null +++ b/user_service/keploy/atg/tests/test-493.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-493 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:25:57.779865501Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:25:57 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:25:57.78104159Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777557 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-494.yaml b/user_service/keploy/atg/tests/test-494.yaml new file mode 100755 index 0000000..4a7833f --- /dev/null +++ b/user_service/keploy/atg/tests/test-494.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-494 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "93" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "productuser_05", "email": "productuser_05@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:01.248719288Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:01.250088477Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777561 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"productuser_05\", \"email\": \"productuser_05@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-495.yaml b/user_service/keploy/atg/tests/test-495.yaml new file mode 100755 index 0000000..92de3b6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-495.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-495 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "productuser_05", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:01.981678864Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:01.983027252Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777561 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"productuser_05\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-496.yaml b/user_service/keploy/atg/tests/test-496.yaml new file mode 100755 index 0000000..3a17eb6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-496.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-496 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:26:02.716117726Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:02 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:02.728186436Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777562 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-497.yaml b/user_service/keploy/atg/tests/test-497.yaml new file mode 100755 index 0000000..e09dc52 --- /dev/null +++ b/user_service/keploy/atg/tests/test-497.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-497 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "91" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_08a", "email": "orderuser_08a@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:05.927043375Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:05 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:05.92886575Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777565 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"orderuser_08a\", \"email\": \"orderuser_08a@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-498.yaml b/user_service/keploy/atg/tests/test-498.yaml new file mode 100755 index 0000000..bb81db4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-498.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-498 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "91" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_08b", "email": "orderuser_08b@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:06.676314675Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:06.677733333Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777566 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"orderuser_08b\", \"email\": \"orderuser_08b@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-499.yaml b/user_service/keploy/atg/tests/test-499.yaml new file mode 100755 index 0000000..cc202fc --- /dev/null +++ b/user_service/keploy/atg/tests/test-499.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-499 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "orderuser_08a", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:07.435300844Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:07.436493304Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777567 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"orderuser_08a\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-5.yaml b/user_service/keploy/atg/tests/test-5.yaml new file mode 100755 index 0000000..0181065 --- /dev/null +++ b/user_service/keploy/atg/tests/test-5.yaml @@ -0,0 +1,45 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-5 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Content-Length: "118" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "vikram_singh_{{$randomInt}}", "email": "vikram.singh.{{$randomInt}}@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:11:27.804788344Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:11:27 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:11:27.806117111Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776687 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"vikram_singh_{{$randomInt}}\", \"email\": \"vikram.singh.{{$randomInt}}@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-50.yaml b/user_service/keploy/atg/tests/test-50.yaml new file mode 100755 index 0000000..e8f4823 --- /dev/null +++ b/user_service/keploy/atg/tests/test-50.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-50 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:34.690502355Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:34.691724125Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776874 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-500.yaml b/user_service/keploy/atg/tests/test-500.yaml new file mode 100755 index 0000000..ac4a9c5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-500.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-500 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:26:08.239695135Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:08 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:08.241040294Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777568 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-501.yaml b/user_service/keploy/atg/tests/test-501.yaml new file mode 100755 index 0000000..73b3572 --- /dev/null +++ b/user_service/keploy/atg/tests/test-501.yaml @@ -0,0 +1,49 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-501 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:26:11.402017459Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:11 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:11.403231Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777571 +curl: | + curl --request GET \ + --url http://localhost:8082/products \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-502.yaml b/user_service/keploy/atg/tests/test-502.yaml new file mode 100755 index 0000000..11ac74d --- /dev/null +++ b/user_service/keploy/atg/tests/test-502.yaml @@ -0,0 +1,49 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-502 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products/some-product-id + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:26:13.168508469Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:13 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:13.169821228Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777573 +curl: | + curl --request GET \ + --url http://localhost:8082/products/some-product-id \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ diff --git a/user_service/keploy/atg/tests/test-503.yaml b/user_service/keploy/atg/tests/test-503.yaml new file mode 100755 index 0000000..a4aa2c4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-503.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-503 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products/some-product-id/reserve + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "15" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1}' + timestamp: 2025-11-10T12:26:15.193505866Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:15 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:15.194713186Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777575 +curl: |- + curl --request POST \ + --url http://localhost:8082/products/some-product-id/reserve \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"quantity\": 1}" diff --git a/user_service/keploy/atg/tests/test-504.yaml b/user_service/keploy/atg/tests/test-504.yaml new file mode 100755 index 0000000..19be05f --- /dev/null +++ b/user_service/keploy/atg/tests/test-504.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-504 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products/some-product-id/release + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "15" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1}' + timestamp: 2025-11-10T12:26:17.174318821Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:17 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:17.175597941Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777577 +curl: |- + curl --request POST \ + --url http://localhost:8082/products/some-product-id/release \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"quantity\": 1}" diff --git a/user_service/keploy/atg/tests/test-505.yaml b/user_service/keploy/atg/tests/test-505.yaml new file mode 100755 index 0000000..f9f3ee2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-505.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-505 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/orders + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"userId": "some-user-id", "items": [ { "productId": "some-product-id", "quantity": 1 } ]}' + timestamp: 2025-11-10T12:26:19.045203313Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:19 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:19.04672918Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777579 +curl: |- + curl --request POST \ + --url http://localhost:8082/orders \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"userId\": \"some-user-id\", \"items\": [ { \"productId\": \"some-product-id\", \"quantity\": 1 } ]}" diff --git a/user_service/keploy/atg/tests/test-506.yaml b/user_service/keploy/atg/tests/test-506.yaml new file mode 100755 index 0000000..615a9f4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-506.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-506 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/orders?userId=some-user-id + url_params: + userId: some-user-id + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:26:20.976024496Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:20 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:20.977496783Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777580 +curl: | + curl --request GET \ + --url http://localhost:8082/orders?userId=some-user-id \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-507.yaml b/user_service/keploy/atg/tests/test-507.yaml new file mode 100755 index 0000000..bf04326 --- /dev/null +++ b/user_service/keploy/atg/tests/test-507.yaml @@ -0,0 +1,49 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-507 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/orders/some-order-id + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:26:23.011882034Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:23.012960264Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777583 +curl: | + curl --request GET \ + --url http://localhost:8082/orders/some-order-id \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ diff --git a/user_service/keploy/atg/tests/test-508.yaml b/user_service/keploy/atg/tests/test-508.yaml new file mode 100755 index 0000000..2a7d5ce --- /dev/null +++ b/user_service/keploy/atg/tests/test-508.yaml @@ -0,0 +1,49 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-508 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/orders/some-order-id/details + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:26:25.029878743Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:25 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:25.031207371Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777585 +curl: | + curl --request GET \ + --url http://localhost:8082/orders/some-order-id/details \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-509.yaml b/user_service/keploy/atg/tests/test-509.yaml new file mode 100755 index 0000000..5dfab92 --- /dev/null +++ b/user_service/keploy/atg/tests/test-509.yaml @@ -0,0 +1,50 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-509 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/orders/some-order-id/pay + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "0" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:26:26.832519483Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:26.833811732Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777586 +curl: | + curl --request POST \ + --url http://localhost:8082/orders/some-order-id/pay \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-51.yaml b/user_service/keploy/atg/tests/test-51.yaml new file mode 100755 index 0000000..f93a2a7 --- /dev/null +++ b/user_service/keploy/atg/tests/test-51.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-51 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:38.548544113Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODc4LCJleHAiOjE3NjUzNjg4Nzh9.8ebZlP285GTvSzq63w1cxQSFHB6RUJCUmQVeiLzJkck","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:38.607383969Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776878 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-510.yaml b/user_service/keploy/atg/tests/test-510.yaml new file mode 100755 index 0000000..7c1bf15 --- /dev/null +++ b/user_service/keploy/atg/tests/test-510.yaml @@ -0,0 +1,50 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-510 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/orders/some-order-id/cancel + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "0" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:26:28.559749392Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:28 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:28.561012942Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777588 +curl: | + curl --request POST \ + --url http://localhost:8082/orders/some-order-id/cancel \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ diff --git a/user_service/keploy/atg/tests/test-511.yaml b/user_service/keploy/atg/tests/test-511.yaml new file mode 100755 index 0000000..b51d4e4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-511.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-511 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "91" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "badjsonuser01", "email": "badjsonuser01@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:30.54916554Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:30 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:30.550330229Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777590 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"badjsonuser01\", \"email\": \"badjsonuser01@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-512.yaml b/user_service/keploy/atg/tests/test-512.yaml new file mode 100755 index 0000000..f4b771d --- /dev/null +++ b/user_service/keploy/atg/tests/test-512.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-512 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "badjsonuser01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:31.333974657Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:31.335375944Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777591 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"badjsonuser01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-513.yaml b/user_service/keploy/atg/tests/test-513.yaml new file mode 100755 index 0000000..931a480 --- /dev/null +++ b/user_service/keploy/atg/tests/test-513.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-513 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/orders + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "37" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"userId": "some-user-id", "items": [' + timestamp: 2025-11-10T12:26:32.067515659Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:32.068690949Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777592 +curl: |- + curl --request POST \ + --url http://localhost:8082/orders \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"userId\": \"some-user-id\", \"items\": [" diff --git a/user_service/keploy/atg/tests/test-514.yaml b/user_service/keploy/atg/tests/test-514.yaml new file mode 100755 index 0000000..0d73734 --- /dev/null +++ b/user_service/keploy/atg/tests/test-514.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-514 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "91" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "badjsonuser02", "email": "badjsonuser02@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:34.674696144Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:34 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:34.675805284Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777594 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"badjsonuser02\", \"email\": \"badjsonuser02@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-515.yaml b/user_service/keploy/atg/tests/test-515.yaml new file mode 100755 index 0000000..13f24de --- /dev/null +++ b/user_service/keploy/atg/tests/test-515.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-515 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "badjsonuser02", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:35.514436373Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:35 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:35.515733811Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777595 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"badjsonuser02\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-516.yaml b/user_service/keploy/atg/tests/test-516.yaml new file mode 100755 index 0000000..88931ea --- /dev/null +++ b/user_service/keploy/atg/tests/test-516.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-516 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products/some-product-id/reserve + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "15" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1,' + timestamp: 2025-11-10T12:26:36.286208047Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:36 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:36.287341208Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777596 +curl: |- + curl --request POST \ + --url http://localhost:8082/products/some-product-id/reserve \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"quantity\": 1," diff --git a/user_service/keploy/atg/tests/test-517.yaml b/user_service/keploy/atg/tests/test-517.yaml new file mode 100755 index 0000000..7990e49 --- /dev/null +++ b/user_service/keploy/atg/tests/test-517.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-517 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "91" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "badjsonuser03", "email": "badjsonuser03@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:38.968567077Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:38.969753977Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777598 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"badjsonuser03\", \"email\": \"badjsonuser03@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-518.yaml b/user_service/keploy/atg/tests/test-518.yaml new file mode 100755 index 0000000..599907a --- /dev/null +++ b/user_service/keploy/atg/tests/test-518.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-518 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "badjsonuser03", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:39.719497015Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:39.720857354Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777599 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"badjsonuser03\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-519.yaml b/user_service/keploy/atg/tests/test-519.yaml new file mode 100755 index 0000000..5c5b333 --- /dev/null +++ b/user_service/keploy/atg/tests/test-519.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-519 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products/some-product-id/release + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "15" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1,' + timestamp: 2025-11-10T12:26:40.485633549Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:40 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:40.486846038Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777600 +curl: |- + curl --request POST \ + --url http://localhost:8082/products/some-product-id/release \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"quantity\": 1," diff --git a/user_service/keploy/atg/tests/test-52.yaml b/user_service/keploy/atg/tests/test-52.yaml new file mode 100755 index 0000000..b69af44 --- /dev/null +++ b/user_service/keploy/atg/tests/test-52.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-52 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "isha_cancel_01", "email": "isha.cancel.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:39.358466231Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:39.35969229Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776879 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"isha_cancel_01\", \"email\": \"isha.cancel.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-520.yaml b/user_service/keploy/atg/tests/test-520.yaml new file mode 100755 index 0000000..07e1148 --- /dev/null +++ b/user_service/keploy/atg/tests/test-520.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-520 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "99" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "lifecycle_user_01", "email": "lifecycle_user_01@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:42.969636559Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:42 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:42.970921339Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777602 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"lifecycle_user_01\", \"email\": \"lifecycle_user_01@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-521.yaml b/user_service/keploy/atg/tests/test-521.yaml new file mode 100755 index 0000000..cfc44f8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-521.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-521 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "57" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "lifecycle_user_01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:43.753244326Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:43 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:43.754507936Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777603 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"lifecycle_user_01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-522.yaml b/user_service/keploy/atg/tests/test-522.yaml new file mode 100755 index 0000000..7291812 --- /dev/null +++ b/user_service/keploy/atg/tests/test-522.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-522 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "99" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "wrongpass_user_01", "email": "wrongpass_user_01@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:47.648038007Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:47.649219918Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777607 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"wrongpass_user_01\", \"email\": \"wrongpass_user_01@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-523.yaml b/user_service/keploy/atg/tests/test-523.yaml new file mode 100755 index 0000000..ee77b6f --- /dev/null +++ b/user_service/keploy/atg/tests/test-523.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-523 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "62" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "wrongpass_user_01", "password": "wrongpassword"}' + timestamp: 2025-11-10T12:26:48.453462312Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:48.454724311Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777608 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"wrongpass_user_01\", \"password\": \"wrongpassword\"}" diff --git a/user_service/keploy/atg/tests/test-524.yaml b/user_service/keploy/atg/tests/test-524.yaml new file mode 100755 index 0000000..a481590 --- /dev/null +++ b/user_service/keploy/atg/tests/test-524.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-524 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "54" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "ghost_user_999", "password": "password"}' + timestamp: 2025-11-10T12:26:51.321852908Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:51.323061368Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777611 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"ghost_user_999\", \"password\": \"password\"}" diff --git a/user_service/keploy/atg/tests/test-525.yaml b/user_service/keploy/atg/tests/test-525.yaml new file mode 100755 index 0000000..2b237ab --- /dev/null +++ b/user_service/keploy/atg/tests/test-525.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-525 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "99" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "duplicate_user_01", "email": "duplicate_user_01@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:53.624908068Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:53 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:53.626312647Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777613 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"duplicate_user_01\", \"email\": \"duplicate_user_01@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-526.yaml b/user_service/keploy/atg/tests/test-526.yaml new file mode 100755 index 0000000..fc94cc1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-526.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-526 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "95" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "duplicate_user_01", "email": "another_email@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:54.405319963Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:54 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:54.406371795Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777614 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"duplicate_user_01\", \"email\": \"another_email@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-527.yaml b/user_service/keploy/atg/tests/test-527.yaml new file mode 100755 index 0000000..99cdf32 --- /dev/null +++ b/user_service/keploy/atg/tests/test-527.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-527 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "97" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "unique_user_02", "email": "duplicate_email_02@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:57.893325342Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:57 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:57.894466323Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777617 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"unique_user_02\", \"email\": \"duplicate_email_02@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-528.yaml b/user_service/keploy/atg/tests/test-528.yaml new file mode 100755 index 0000000..f165a9d --- /dev/null +++ b/user_service/keploy/atg/tests/test-528.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-528 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "98" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "another_user_02", "email": "duplicate_email_02@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:26:58.898654955Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:26:58 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:26:58.899810596Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777618 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"another_user_02\", \"email\": \"duplicate_email_02@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-529.yaml b/user_service/keploy/atg/tests/test-529.yaml new file mode 100755 index 0000000..8ea719f --- /dev/null +++ b/user_service/keploy/atg/tests/test-529.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-529 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "95" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "address_user_01", "email": "address_user_01@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:01.493888547Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:01.494982798Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777621 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"address_user_01\", \"email\": \"address_user_01@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-53.yaml b/user_service/keploy/atg/tests/test-53.yaml new file mode 100755 index 0000000..2e11410 --- /dev/null +++ b/user_service/keploy/atg/tests/test-53.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-53 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:14:40.08701679Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:14:40 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:40.0881729Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776880 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-530.yaml b/user_service/keploy/atg/tests/test-530.yaml new file mode 100755 index 0000000..e3c9158 --- /dev/null +++ b/user_service/keploy/atg/tests/test-530.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-530 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "55" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "address_user_01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:02.257565091Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:02 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:02.258641552Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777622 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"address_user_01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-531.yaml b/user_service/keploy/atg/tests/test-531.yaml new file mode 100755 index 0000000..075b3ad --- /dev/null +++ b/user_service/keploy/atg/tests/test-531.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-531 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "101" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "no_address_user_01", "email": "no_address_user_01@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:07.006020523Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:07.007531802Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777627 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"no_address_user_01\", \"email\": \"no_address_user_01@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-532.yaml b/user_service/keploy/atg/tests/test-532.yaml new file mode 100755 index 0000000..479879a --- /dev/null +++ b/user_service/keploy/atg/tests/test-532.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-532 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "58" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "no_address_user_01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:07.735075627Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:07 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:07.736594775Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777627 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"no_address_user_01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-533.yaml b/user_service/keploy/atg/tests/test-533.yaml new file mode 100755 index 0000000..2331e22 --- /dev/null +++ b/user_service/keploy/atg/tests/test-533.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-533 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "99" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "auth_only_user_01", "email": "auth_only_user_01@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:10.880087063Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:10 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:10.881362293Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777630 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"auth_only_user_01\", \"email\": \"auth_only_user_01@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-534.yaml b/user_service/keploy/atg/tests/test-534.yaml new file mode 100755 index 0000000..d2d03f3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-534.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-534 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "57" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "auth_only_user_01", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:11.698220526Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:11 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:11.699441746Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777631 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"auth_only_user_01\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-535.yaml b/user_service/keploy/atg/tests/test-535.yaml new file mode 100755 index 0000000..34fe318 --- /dev/null +++ b/user_service/keploy/atg/tests/test-535.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-535 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users/invalid-user-id-999/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "99" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "123 Main St", "city": "Anytown", "state": "CA", "postal_code": "12345", "country": "US"}' + timestamp: 2025-11-10T12:27:12.446220812Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:12.447782689Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777632 +curl: |- + curl --request POST \ + --url http://localhost:8082/users/invalid-user-id-999/addresses \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"line1\": \"123 Main St\", \"city\": \"Anytown\", \"state\": \"CA\", \"postal_code\": \"12345\", \"country\": \"US\"}" diff --git a/user_service/keploy/atg/tests/test-536.yaml b/user_service/keploy/atg/tests/test-536.yaml new file mode 100755 index 0000000..01eb12e --- /dev/null +++ b/user_service/keploy/atg/tests/test-536.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-536 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "56" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"email": "no_user@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:14.687613919Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:14 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:14.688926639Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777634 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"email\": \"no_user@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-537.yaml b/user_service/keploy/atg/tests/test-537.yaml new file mode 100755 index 0000000..22c2c8c --- /dev/null +++ b/user_service/keploy/atg/tests/test-537.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-537 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "60" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "no_pass_user", "email": "no_pass@example.com"}' + timestamp: 2025-11-10T12:27:17.060863221Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:17 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:17.062747625Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777637 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --data "{\"username\": \"no_pass_user\", \"email\": \"no_pass@example.com\"}" diff --git a/user_service/keploy/atg/tests/test-538.yaml b/user_service/keploy/atg/tests/test-538.yaml new file mode 100755 index 0000000..ba32a13 --- /dev/null +++ b/user_service/keploy/atg/tests/test-538.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-538 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "53" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "no_email_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:19.338692037Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:19 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:19.339967746Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777639 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"no_email_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-539.yaml b/user_service/keploy/atg/tests/test-539.yaml new file mode 100755 index 0000000..3b76abb --- /dev/null +++ b/user_service/keploy/atg/tests/test-539.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-539 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "107" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "product_notfound_user", "email": "product_notfound_user@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:21.739845628Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:21.740966728Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777641 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"product_notfound_user\", \"email\": \"product_notfound_user@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-54.yaml b/user_service/keploy/atg/tests/test-54.yaml new file mode 100755 index 0000000..c2a1048 --- /dev/null +++ b/user_service/keploy/atg/tests/test-54.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-54 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:44.318303458Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:44 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODg0LCJleHAiOjE3NjUzNjg4ODR9.Spk1AS1SaQPlIePntGKCvaJnFBOVw8iA2RRRqCkG8n4","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:44.379708272Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776884 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-540.yaml b/user_service/keploy/atg/tests/test-540.yaml new file mode 100755 index 0000000..373fa9e --- /dev/null +++ b/user_service/keploy/atg/tests/test-540.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-540 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "61" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "product_notfound_user", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:22.424243402Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:22 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:22.425456993Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777642 +curl: |- + curl --request POST \ + --url http://localhost:8082/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"product_notfound_user\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-541.yaml b/user_service/keploy/atg/tests/test-541.yaml new file mode 100755 index 0000000..8c15cfa --- /dev/null +++ b/user_service/keploy/atg/tests/test-541.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-541 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/products/non-existent-product-id-123 + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:27:23.217207426Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:23 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:23.218440265Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777643 +curl: | + curl --request GET \ + --url http://localhost:8082/products/non-existent-product-id-123 \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-542.yaml b/user_service/keploy/atg/tests/test-542.yaml new file mode 100755 index 0000000..fa9d628 --- /dev/null +++ b/user_service/keploy/atg/tests/test-542.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-542 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0 + Content-Length: "105" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "order_lifecycle_user", "email": "order_lifecycle_user@example.com", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:27:26.459926143Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:27:26 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:27:26.461106965Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762777646 +curl: |- + curl --request POST \ + --url http://localhost:8082/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"order_lifecycle_user\", \"email\": \"order_lifecycle_user@example.com\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-55.yaml b/user_service/keploy/atg/tests/test-55.yaml new file mode 100755 index 0000000..1ca54d0 --- /dev/null +++ b/user_service/keploy/atg/tests/test-55.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-55 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "rahul_invalid_01", "email": "rahul.invalid.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:45.114656821Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:45 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:45.115928961Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776885 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"rahul_invalid_01\", \"email\": \"rahul.invalid.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-56.yaml b/user_service/keploy/atg/tests/test-56.yaml new file mode 100755 index 0000000..390648c --- /dev/null +++ b/user_service/keploy/atg/tests/test-56.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-56 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:48.018627742Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODg4LCJleHAiOjE3NjUzNjg4ODh9.J1GHPEqaVUSPJNIPbRyqshi4-Kt6foZP7hBqBGTshU8","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:48.086246303Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776888 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-57.yaml b/user_service/keploy/atg/tests/test-57.yaml new file mode 100755 index 0000000..aff6c2c --- /dev/null +++ b/user_service/keploy/atg/tests/test-57.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-57 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "95" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "pooja_login_01", "email": "pooja.login.01@example.in", "password": "newP@ssw0rd"}' + timestamp: 2025-11-10T12:14:48.803188054Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:48.804600882Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776888 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"pooja_login_01\", \"email\": \"pooja.login.01@example.in\", \"password\": \"newP@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-58.yaml b/user_service/keploy/atg/tests/test-58.yaml new file mode 100755 index 0000000..948e7d5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-58.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-58 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "57" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "pooja_login_01", "password": "newP@ssw0rd"}' + timestamp: 2025-11-10T12:14:49.523399037Z + resp: + status_code: 401 + header: + Content-Length: "32" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:49 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Invalid credentials"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:49.537220097Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776889 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"pooja_login_01\", \"password\": \"newP@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-59.yaml b/user_service/keploy/atg/tests/test-59.yaml new file mode 100755 index 0000000..aae2b7a --- /dev/null +++ b/user_service/keploy/atg/tests/test-59.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-59 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:52.720053259Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:52 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODkyLCJleHAiOjE3NjUzNjg4OTJ9.IuTReg9hPvUh1datpSxsTZmxe8PMXg7SdeIeBvjhUM8","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:52.781299036Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776892 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-6.yaml b/user_service/keploy/atg/tests/test-6.yaml new file mode 100755 index 0000000..c858071 --- /dev/null +++ b/user_service/keploy/atg/tests/test-6.yaml @@ -0,0 +1,46 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-6 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/ + header: + Accept-Encoding: gzip + Content-Length: "0" + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:12:32.494279392Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:12:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:12:32.495428191Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776752 +curl: | + curl --request POST \ + --url http://localhost:8082/ \ + --header 'Accept-Encoding: gzip' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-60.yaml b/user_service/keploy/atg/tests/test-60.yaml new file mode 100755 index 0000000..a0d6d49 --- /dev/null +++ b/user_service/keploy/atg/tests/test-60.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-60 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "98" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "manish_multiaddr_01", "email": "manish.multi.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:53.540025717Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:53 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:53.541308607Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776893 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"manish_multiaddr_01\", \"email\": \"manish.multi.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-61.yaml b/user_service/keploy/atg/tests/test-61.yaml new file mode 100755 index 0000000..293f8b6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-61.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-61 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:56.677516746Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:56 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODk2LCJleHAiOjE3NjUzNjg4OTZ9.RswdUlKLNR3gdNCDtstYsuHkcJ2GGd9SrFXLvSPGTI4","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:56.737707832Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776896 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-62.yaml b/user_service/keploy/atg/tests/test-62.yaml new file mode 100755 index 0000000..8a632c4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-62.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-62 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "94" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "sneha_noitem_01", "email": "sneha.noitem.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:14:57.419093482Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:14:57 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:14:57.420388042Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776897 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"sneha_noitem_01\", \"email\": \"sneha.noitem.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-63.yaml b/user_service/keploy/atg/tests/test-63.yaml new file mode 100755 index 0000000..301e0c1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-63.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-63 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:00.314268445Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:00 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTAwLCJleHAiOjE3NjUzNjg5MDB9.TvfwHgFlbI5YX7TYnWPr1OoVRNX7Uj-uwDIlqv-fy0g","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:00.373434822Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776900 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-64.yaml b/user_service/keploy/atg/tests/test-64.yaml new file mode 100755 index 0000000..adc0034 --- /dev/null +++ b/user_service/keploy/atg/tests/test-64.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-64 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "92" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "vivek_zeroq_01", "email": "vivek.zeroq.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:01.075828563Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:01.077058063Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776901 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"vivek_zeroq_01\", \"email\": \"vivek.zeroq.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-65.yaml b/user_service/keploy/atg/tests/test-65.yaml new file mode 100755 index 0000000..ce7010a --- /dev/null +++ b/user_service/keploy/atg/tests/test-65.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-65 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:15:01.887202868Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:01 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:01.888397349Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776901 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-66.yaml b/user_service/keploy/atg/tests/test-66.yaml new file mode 100755 index 0000000..6026b27 --- /dev/null +++ b/user_service/keploy/atg/tests/test-66.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-66 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:05.182271508Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:05 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTA1LCJleHAiOjE3NjUzNjg5MDV9.I1lII4xSu3rNN8T9fyHj39O3HJ34zDEy5OPwb73-ZlI","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:05.240578891Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776905 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-67.yaml b/user_service/keploy/atg/tests/test-67.yaml new file mode 100755 index 0000000..c7e7f6b --- /dev/null +++ b/user_service/keploy/atg/tests/test-67.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-67 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "amit_limit_01", "email": "amit.limit.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:06.099313229Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:06.100714887Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776906 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"amit_limit_01\", \"email\": \"amit.limit.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-68.yaml b/user_service/keploy/atg/tests/test-68.yaml new file mode 100755 index 0000000..f08e968 --- /dev/null +++ b/user_service/keploy/atg/tests/test-68.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-68 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:15:06.841675909Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:06 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:06.842988089Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776906 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ diff --git a/user_service/keploy/atg/tests/test-69.yaml b/user_service/keploy/atg/tests/test-69.yaml new file mode 100755 index 0000000..ccdba4d --- /dev/null +++ b/user_service/keploy/atg/tests/test-69.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-69 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:11.074451704Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:11 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTExLCJleHAiOjE3NjUzNjg5MTF9.Fm7GOp3OT854kT4y-SadnUpGRfwD_Ctl_QScfzF0KSE","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:11.132717508Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776911 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-7.yaml b/user_service/keploy/atg/tests/test-7.yaml new file mode 100755 index 0000000..c1a05cd --- /dev/null +++ b/user_service/keploy/atg/tests/test-7.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-7 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:24.921347946Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:24 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODA0LCJleHAiOjE3NjUzNjg4MDR9.CazMym1y3A_PhEg8zRKzlGj7ZNhOv7IIfeo2HJQcvzo","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:24.99189222Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776804 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-70.yaml b/user_service/keploy/atg/tests/test-70.yaml new file mode 100755 index 0000000..3147795 --- /dev/null +++ b/user_service/keploy/atg/tests/test-70.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-70 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "96" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "geeta_details_01", "email": "geeta.details.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:11.876308314Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:11 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:11.877696991Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776911 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"geeta_details_01\", \"email\": \"geeta.details.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-71.yaml b/user_service/keploy/atg/tests/test-71.yaml new file mode 100755 index 0000000..d4de9ad --- /dev/null +++ b/user_service/keploy/atg/tests/test-71.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-71 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:15:12.606528766Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:12 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:12.607650925Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776912 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ diff --git a/user_service/keploy/atg/tests/test-72.yaml b/user_service/keploy/atg/tests/test-72.yaml new file mode 100755 index 0000000..64a09d3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-72.yaml @@ -0,0 +1,49 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-72 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:15:16.673281576Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:16 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:16.674392547Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776916 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ diff --git a/user_service/keploy/atg/tests/test-73.yaml b/user_service/keploy/atg/tests/test-73.yaml new file mode 100755 index 0000000..75f74d4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-73.yaml @@ -0,0 +1,49 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-73 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:15:18.653275348Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:18 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:18.654467357Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776918 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ diff --git a/user_service/keploy/atg/tests/test-74.yaml b/user_service/keploy/atg/tests/test-74.yaml new file mode 100755 index 0000000..39991c1 --- /dev/null +++ b/user_service/keploy/atg/tests/test-74.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-74 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:20.644167461Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:20 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTIwLCJleHAiOjE3NjUzNjg5MjB9.VWAfyfbuhW1P5NuR_zVn80W5S7nVTm9BAmmMlhvMO6c","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:20.704215411Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776920 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-75.yaml b/user_service/keploy/atg/tests/test-75.yaml new file mode 100755 index 0000000..6edf5d4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-75.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-75 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "91" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "temp_user_del_01", "email": "temp.del.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:21.413388595Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:21 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:21.414653374Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776921 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"temp_user_del_01\", \"email\": \"temp.del.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-76.yaml b/user_service/keploy/atg/tests/test-76.yaml new file mode 100755 index 0000000..dad91e8 --- /dev/null +++ b/user_service/keploy/atg/tests/test-76.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-76 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:24.601214918Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:24 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTI0LCJleHAiOjE3NjUzNjg5MjR9.q8y0hBCGs3NIvqzRMsLgzVhx_-nv0NCQJ1FJcM4fSb8","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:24.65990145Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776924 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-77.yaml b/user_service/keploy/atg/tests/test-77.yaml new file mode 100755 index 0000000..343cbe0 --- /dev/null +++ b/user_service/keploy/atg/tests/test-77.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-77 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users/99999999-9999-9999-9999-999999999999/addresses + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "149" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"line1": "123 Nowhere St", "city": "Nowhere", "state": "NA", "postal_code": "000000", "country": "IN", "phone": "+910000000000", "is_default": true}' + timestamp: 2025-11-10T12:15:25.477402001Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:25 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:25.478731149Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776925 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users/99999999-9999-9999-9999-999999999999/addresses \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"line1\": \"123 Nowhere St\", \"city\": \"Nowhere\", \"state\": \"NA\", \"postal_code\": \"000000\", \"country\": \"IN\", \"phone\": \"+910000000000\", \"is_default\": true}" diff --git a/user_service/keploy/atg/tests/test-78.yaml b/user_service/keploy/atg/tests/test-78.yaml new file mode 100755 index 0000000..8d63cf5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-78.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-78 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:27.300978441Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:27 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTI3LCJleHAiOjE3NjUzNjg5Mjd9.gC3-VTsS8cgAhvqFygdzaq7z1O1C23eg_3TyygGMrdw","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:27.361787624Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776927 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-79.yaml b/user_service/keploy/atg/tests/test-79.yaml new file mode 100755 index 0000000..9bd99b5 --- /dev/null +++ b/user_service/keploy/atg/tests/test-79.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-79 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/99999999-9999-9999-9999-999999999999/reserve + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "15" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1}' + timestamp: 2025-11-10T12:15:28.102575612Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:28 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:28.103721372Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776928 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/products/99999999-9999-9999-9999-999999999999/reserve \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"quantity\": 1}" diff --git a/user_service/keploy/atg/tests/test-8.yaml b/user_service/keploy/atg/tests/test-8.yaml new file mode 100755 index 0000000..fcf5752 --- /dev/null +++ b/user_service/keploy/atg/tests/test-8.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-8 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "priya_test_01", "email": "priya.test.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:25.717832137Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:25 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:25.719056026Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776805 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"priya_test_01\", \"email\": \"priya.test.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-80.yaml b/user_service/keploy/atg/tests/test-80.yaml new file mode 100755 index 0000000..bff508c --- /dev/null +++ b/user_service/keploy/atg/tests/test-80.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-80 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:28.93117902Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:28 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTI4LCJleHAiOjE3NjUzNjg5Mjh9.MB6009bZoT-uLgjMDzjJkfxJmVdxw_FM5WTa-NQkXl4","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:28.992226251Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776928 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-81.yaml b/user_service/keploy/atg/tests/test-81.yaml new file mode 100755 index 0000000..01d6086 --- /dev/null +++ b/user_service/keploy/atg/tests/test-81.yaml @@ -0,0 +1,51 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-81 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products/99999999-9999-9999-9999-999999999999/release + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "15" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"quantity": 1}' + timestamp: 2025-11-10T12:15:29.710771443Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:29 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:29.711971153Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776929 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/products/99999999-9999-9999-9999-999999999999/release \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --data "{\"quantity\": 1}" diff --git a/user_service/keploy/atg/tests/test-82.yaml b/user_service/keploy/atg/tests/test-82.yaml new file mode 100755 index 0000000..5088bc3 --- /dev/null +++ b/user_service/keploy/atg/tests/test-82.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-82 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:30.514382911Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:30 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTMwLCJleHAiOjE3NjUzNjg5MzB9.neCNh47y_tHyGhVdvAJ9UWuz4nQn03y3-yUtWc75BYU","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:30.574316032Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776930 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-83.yaml b/user_service/keploy/atg/tests/test-83.yaml new file mode 100755 index 0000000..c81ecd6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-83.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-83 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "94" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "ritu_cancel2_01", "email": "ritu.cancel2.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:31.281555922Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:31.282790052Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776931 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"ritu_cancel2_01\", \"email\": \"ritu.cancel2.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-84.yaml b/user_service/keploy/atg/tests/test-84.yaml new file mode 100755 index 0000000..2793ee9 --- /dev/null +++ b/user_service/keploy/atg/tests/test-84.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-84 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/products + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:15:32.007315324Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:32 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:32.008394135Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776932 +curl: | + curl --request GET \ + --url http://localhost:8082/api/v1/products \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ diff --git a/user_service/keploy/atg/tests/test-85.yaml b/user_service/keploy/atg/tests/test-85.yaml new file mode 100755 index 0000000..c3714d2 --- /dev/null +++ b/user_service/keploy/atg/tests/test-85.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-85 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:35.835508737Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:35 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTM1LCJleHAiOjE3NjUzNjg5MzV9.R29p56FDIN_eg8IWOr73XA8rXbRYPmRXIgbo0rzQRro","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:35.90010569Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776935 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-86.yaml b/user_service/keploy/atg/tests/test-86.yaml new file mode 100755 index 0000000..2b7a25e --- /dev/null +++ b/user_service/keploy/atg/tests/test-86.yaml @@ -0,0 +1,48 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-86 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders/99999999-9999-9999-9999-999999999999/pay + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "0" + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:15:36.714668558Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:36 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:36.716223324Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776936 +curl: | + curl --request POST \ + --url http://localhost:8082/api/v1/orders/99999999-9999-9999-9999-999999999999/pay \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-87.yaml b/user_service/keploy/atg/tests/test-87.yaml new file mode 100755 index 0000000..8635c22 --- /dev/null +++ b/user_service/keploy/atg/tests/test-87.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-87 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:37.543628062Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:37 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTM3LCJleHAiOjE3NjUzNjg5Mzd9.dLe6vYxI0QAbTySGH_9MAsTG3-au733ACyKWvoF_wXk","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:37.606751506Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776937 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-88.yaml b/user_service/keploy/atg/tests/test-88.yaml new file mode 100755 index 0000000..a253c66 --- /dev/null +++ b/user_service/keploy/atg/tests/test-88.yaml @@ -0,0 +1,48 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-88 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/orders/99999999-9999-9999-9999-999999999999/cancel + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "0" + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: "" + timestamp: 2025-11-10T12:15:38.29138558Z + resp: + status_code: 404 + header: + Content-Length: "207" + Content-Type: text/html; charset=utf-8 + Date: Mon, 10 Nov 2025 12:15:38 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ status_message: Not Found + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:38.292650629Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776938 +curl: | + curl --request POST \ + --url http://localhost:8082/api/v1/orders/99999999-9999-9999-9999-999999999999/cancel \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Host: localhost:8082' \ diff --git a/user_service/keploy/atg/tests/test-89.yaml b/user_service/keploy/atg/tests/test-89.yaml new file mode 100755 index 0000000..be2b9dd --- /dev/null +++ b/user_service/keploy/atg/tests/test-89.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-89 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:39.122162971Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTM5LCJleHAiOjE3NjUzNjg5Mzl9.0V7aS_vZFT5WyOz2l-se5An6oq18oyRjfn1IlFzcFPE","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:39.182527481Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776939 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-9.yaml b/user_service/keploy/atg/tests/test-9.yaml new file mode 100755 index 0000000..4a31e99 --- /dev/null +++ b/user_service/keploy/atg/tests/test-9.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-9 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:13:31.191420318Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:13:31 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2ODExLCJleHAiOjE3NjUzNjg4MTF9.9q9o-aZvv0OaKRwC0CdfSII109-V6-i3mFKRGwHetyE","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:13:31.251025191Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776811 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-90.yaml b/user_service/keploy/atg/tests/test-90.yaml new file mode 100755 index 0000000..5869d39 --- /dev/null +++ b/user_service/keploy/atg/tests/test-90.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-90 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "86" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "invalidemail_user_01", "email": "invalid-email", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:39.856763334Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:39 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:39.858049892Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776939 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"invalidemail_user_01\", \"email\": \"invalid-email\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-91.yaml b/user_service/keploy/atg/tests/test-91.yaml new file mode 100755 index 0000000..fdacb39 --- /dev/null +++ b/user_service/keploy/atg/tests/test-91.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-91 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:41.935577315Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:41 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTQxLCJleHAiOjE3NjUzNjg5NDF9.VhFe4lrJ1v9Vj16fta71JmFujZha0nT7QVmPTC45OQw","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:41.996120814Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776941 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-92.yaml b/user_service/keploy/atg/tests/test-92.yaml new file mode 100755 index 0000000..41af625 --- /dev/null +++ b/user_service/keploy/atg/tests/test-92.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-92 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "68" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "nopass_user_01", "email": "nopass.user.01@example.in"}' + timestamp: 2025-11-10T12:15:42.777508295Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:42 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:42.778813453Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776942 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"nopass_user_01\", \"email\": \"nopass.user.01@example.in\"}" diff --git a/user_service/keploy/atg/tests/test-93.yaml b/user_service/keploy/atg/tests/test-93.yaml new file mode 100755 index 0000000..3e43258 --- /dev/null +++ b/user_service/keploy/atg/tests/test-93.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-93 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:44.879734512Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:44 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTQ0LCJleHAiOjE3NjUzNjg5NDR9.wv9d-nvvDZu10rc9HpFoTfUblG1EmtGD4QiQzpQxats","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:44.941610808Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776944 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-94.yaml b/user_service/keploy/atg/tests/test-94.yaml new file mode 100755 index 0000000..5dcfb37 --- /dev/null +++ b/user_service/keploy/atg/tests/test-94.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-94 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "61" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"email": "nousername.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:45.737614126Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:45 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:45.738834716Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776945 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"email\": \"nousername.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-95.yaml b/user_service/keploy/atg/tests/test-95.yaml new file mode 100755 index 0000000..ff70f64 --- /dev/null +++ b/user_service/keploy/atg/tests/test-95.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-95 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:47.524367714Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:47 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTQ3LCJleHAiOjE3NjUzNjg5NDd9.VeU5bpJFps-EnyIXqYVARhN5XDiDydNmxMXJK8skSJY","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:47.587939505Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776947 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-96.yaml b/user_service/keploy/atg/tests/test-96.yaml new file mode 100755 index 0000000..b9ff0c4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-96.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-96 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "98" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "missingaddr_user_01", "email": "missing.addr.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:48.268791429Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:48 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:48.270361206Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776948 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --data "{\"username\": \"missingaddr_user_01\", \"email\": \"missing.addr.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-97.yaml b/user_service/keploy/atg/tests/test-97.yaml new file mode 100755 index 0000000..bfd5fd6 --- /dev/null +++ b/user_service/keploy/atg/tests/test-97.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-97 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "45" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "alice", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:50.82365678Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:50 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"alice@example.com","id":"9fdac0ef-7d77-4798-b07f-a7076b80aa63","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ZmRhYzBlZi03ZDc3LTQ3OTgtYjA3Zi1hNzA3NmI4MGFhNjMiLCJ1c2VybmFtZSI6ImFsaWNlIiwiaWF0IjoxNzYyNzc2OTUwLCJleHAiOjE3NjUzNjg5NTB9.KD8KR5hCQgwPaavzjbg_sJ54-F1atGoHrDJyxZodbws","username":"alice"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:50.886105541Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776950 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --data "{\"username\": \"alice\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-98.yaml b/user_service/keploy/atg/tests/test-98.yaml new file mode 100755 index 0000000..d4107e4 --- /dev/null +++ b/user_service/keploy/atg/tests/test-98.yaml @@ -0,0 +1,47 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-98 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/users + header: + Accept-Encoding: gzip + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q + Content-Length: "90" + Content-Type: application/json + Host: localhost:8082 + User-Agent: Go-http-client/1.1 + body: '{"username": "noorder_user_01", "email": "no.order.01@example.in", "password": "p@ssw0rd"}' + timestamp: 2025-11-10T12:15:51.706941812Z + resp: + status_code: 401 + header: + Content-Length: "25" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:51 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"error":"Unauthorized"} + status_message: Unauthorized + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:51.708244211Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776951 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/users \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: Go-http-client/1.1' \ + --header 'Accept-Encoding: gzip' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ZWY1ODg3Ni04NDE2LTExZjAtYjBhOC1mNmMyOTNhM2UxZWQiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzU2Mzk0MTQxLCJleHAiOjE3NTg5ODYxNDF9.AsSxN-qJ5fx7cff6D4fYqwH0Wd896-4EFHNAkSRQU4Q' \ + --data "{\"username\": \"noorder_user_01\", \"email\": \"no.order.01@example.in\", \"password\": \"p@ssw0rd\"}" diff --git a/user_service/keploy/atg/tests/test-99.yaml b/user_service/keploy/atg/tests/test-99.yaml new file mode 100755 index 0000000..89d008c --- /dev/null +++ b/user_service/keploy/atg/tests/test-99.yaml @@ -0,0 +1,49 @@ +# Generated by Keploy (2-dev) +version: api.keploy.io/v1beta1 +kind: Http +name: test-99 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:8082/api/v1/login + header: + Accept: '*/*' + Content-Length: "69" + Content-Type: application/json + Host: localhost:8082 + User-Agent: curl/8.5.0 + body: |- + { + "username": "admin", + "password": "admin123" + } + timestamp: 2025-11-10T12:15:53.673729922Z + resp: + status_code: 200 + header: + Content-Length: "317" + Content-Type: application/json + Date: Mon, 10 Nov 2025 12:15:53 GMT + Server: Werkzeug/3.0.3 Python/3.11.14 + body: | + {"email":"admin@example.com","id":"8bd161aa-bb12-11f0-982a-c678de5766ef","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc2OTUzLCJleHAiOjE3NjUzNjg5NTN9.gjfO5M0E5YzxM6QQLu6RShVQnEsRXUpHSKVExz5kRZ0","username":"admin"} + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2025-11-10T12:15:53.748760435Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1762776953 +curl: |- + curl --request POST \ + --url http://localhost:8082/api/v1/login \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:8082' \ + --header 'User-Agent: curl/8.5.0' \ + --header 'Accept: */*' \ + --data "{\n \"username\": \"admin\",\n \"password\": \"admin123\"\n }" diff --git a/user_service/keploy/load/reports/20251110_113538_suite-0.json b/user_service/keploy/load/reports/20251110_113538_suite-0.json new file mode 100644 index 0000000..470b3b8 --- /dev/null +++ b/user_service/keploy/load/reports/20251110_113538_suite-0.json @@ -0,0 +1,56 @@ +{ + "test_suite_file": "suite-0.yaml", + "vus": 30, + "duration": "3m", + "rps": 0, + "steps": [ + { + "step_name": "Create_todo", + "total_requests": 0, + "total_failures": 0, + "total_bytes_in": 0, + "total_bytes_out": 0, + "p95_latency": 0, + "thresholds": [ + { + "metric": "http_req_duration_p95", + "condition": "< 500ms", + "severity": "high", + "actual": "0s", + "pass": true + }, + { + "metric": "http_req_failed_rate", + "condition": "<= 1%", + "severity": "critical", + "actual": 0, + "pass": true + } + ] + }, + { + "step_name": "Get_todo", + "total_requests": 0, + "total_failures": 0, + "total_bytes_in": 0, + "total_bytes_out": 0, + "p95_latency": 0, + "thresholds": [ + { + "metric": "http_req_duration_p95", + "condition": "< 500ms", + "severity": "high", + "actual": "0s", + "pass": true + }, + { + "metric": "http_req_failed_rate", + "condition": "<= 1%", + "severity": "critical", + "actual": 0, + "pass": true + } + ] + } + ] +} diff --git a/user_service/keploy/load/reports/20251110_113651_suite-0.json b/user_service/keploy/load/reports/20251110_113651_suite-0.json new file mode 100644 index 0000000..5ca37b8 --- /dev/null +++ b/user_service/keploy/load/reports/20251110_113651_suite-0.json @@ -0,0 +1,56 @@ +{ + "test_suite_file": "suite-0.yaml", + "vus": 30, + "duration": "3m", + "rps": 0, + "steps": [ + { + "step_name": "Create_todo", + "total_requests": 2993, + "total_failures": 2993, + "total_bytes_in": 146657, + "total_bytes_out": 619551, + "p95_latency": 109742563, + "thresholds": [ + { + "metric": "http_req_duration_p95", + "condition": "< 500ms", + "severity": "high", + "actual": "109.742563ms", + "pass": true + }, + { + "metric": "http_req_failed_rate", + "condition": "<= 1%", + "severity": "critical", + "actual": 100, + "pass": false + } + ] + }, + { + "step_name": "Get_todo", + "total_requests": 2993, + "total_failures": 2993, + "total_bytes_in": 0, + "total_bytes_out": 619551, + "p95_latency": 94658354, + "thresholds": [ + { + "metric": "http_req_duration_p95", + "condition": "< 500ms", + "severity": "high", + "actual": "94.658354ms", + "pass": true + }, + { + "metric": "http_req_failed_rate", + "condition": "<= 1%", + "severity": "critical", + "actual": 100, + "pass": false + } + ] + } + ] +} diff --git a/user_service/keploy/load/reports/20251110_125123_suite-0.json b/user_service/keploy/load/reports/20251110_125123_suite-0.json new file mode 100644 index 0000000..34bea55 --- /dev/null +++ b/user_service/keploy/load/reports/20251110_125123_suite-0.json @@ -0,0 +1,128 @@ +{ + "test_suite_file": "suite-0.yaml", + "vus": 50, + "duration": "5m", + "rps": 0, + "steps": [ + { + "step_name": "Login_User", + "total_requests": 3759, + "total_failures": 3759, + "total_bytes_in": 221781, + "total_bytes_out": 120288, + "p95_latency": 34372348, + "thresholds": [ + { + "metric": "http_req_duration_p95", + "condition": "< 400ms", + "severity": "high", + "actual": "34.372348ms", + "pass": true + }, + { + "metric": "http_req_failed_rate", + "condition": "<= 0.5%", + "severity": "critical", + "actual": 100, + "pass": false + } + ] + }, + { + "step_name": "Create_Address", + "total_requests": 3759, + "total_failures": 3759, + "total_bytes_in": 578886, + "total_bytes_out": 93975, + "p95_latency": 13458436, + "thresholds": [ + { + "metric": "http_req_duration_p95", + "condition": "< 400ms", + "severity": "high", + "actual": "13.458436ms", + "pass": true + }, + { + "metric": "http_req_failed_rate", + "condition": "<= 0.5%", + "severity": "critical", + "actual": 100, + "pass": false + } + ] + }, + { + "step_name": "Create_Order", + "total_requests": 3759, + "total_failures": 3759, + "total_bytes_in": 593922, + "total_bytes_out": 778113, + "p95_latency": 13602788, + "thresholds": [ + { + "metric": "http_req_duration_p95", + "condition": "< 400ms", + "severity": "high", + "actual": "13.602788ms", + "pass": true + }, + { + "metric": "http_req_failed_rate", + "condition": "<= 0.5%", + "severity": "critical", + "actual": 100, + "pass": false + } + ] + }, + { + "step_name": "Get_Order_Details", + "total_requests": 3759, + "total_failures": 3759, + "total_bytes_in": 0, + "total_bytes_out": 778113, + "p95_latency": 14229255, + "thresholds": [ + { + "metric": "http_req_duration_p95", + "condition": "< 400ms", + "severity": "high", + "actual": "14.229255ms", + "pass": true + }, + { + "metric": "http_req_failed_rate", + "condition": "<= 0.5%", + "severity": "critical", + "actual": 100, + "pass": false + } + ] + }, + { + "step_name": "Delete_User", + "total_requests": 3759, + "total_failures": 3759, + "total_bytes_in": 0, + "total_bytes_out": 93975, + "p95_latency": 13584141, + "thresholds": [ + { + "metric": "http_req_duration_p95", + "condition": "< 400ms", + "severity": "high", + "actual": "13.584141ms", + "pass": true + }, + { + "metric": "http_req_failed_rate", + "condition": "<= 0.5%", + "severity": "critical", + "actual": 100, + "pass": false + } + ] + } + ] +} diff --git a/user_service/keploy/testsuite/keploy.yml b/user_service/keploy/testsuite/keploy.yml new file mode 100755 index 0000000..ffcee7e --- /dev/null +++ b/user_service/keploy/testsuite/keploy.yml @@ -0,0 +1,74 @@ +# Generated by Keploy (2-dev) +path: "" +appId: 0 +appName: testsuite +command: "" +templatize: + testSets: [] +port: 0 +e2e: false +dnsPort: 26789 +proxyPort: 16789 +debug: false +disableTele: false +disableANSI: false +containerName: "" +networkName: "" +buildDelay: 30 +test: + selectedTests: {} + globalNoise: + global: {} + test-sets: {} + delay: 5 + host: "" + port: 0 + apiTimeout: 5 + skipCoverage: false + coverageReportPath: "" + ignoreOrdering: true + mongoPassword: default@123 + language: "" + removeUnusedMocks: false + fallBackOnMiss: false + jacocoAgentPath: "" + basePath: "" + mocking: true + ignoredTests: {} + disableLineCoverage: false + disableMockUpload: true + useLocalMock: false + updateTemplate: false + mustPass: false + maxFailAttempts: 5 + maxFlakyChecks: 1 +record: + filters: [] + basePath: "" + recordTimer: 0s + metadata: "" +report: + selectedTestSets: {} +configPath: "" +bypassRules: [] +generateGithubActions: false +keployContainer: keploy-v2 +keployNetwork: keploy-network +cmdType: native +contract: + services: [] + tests: [] + path: "" + download: false + generate: false + driven: consumer + mappings: + servicesMapping: {} + self: s1 +testSuite: + tsPath: keploy/testsuite + tsFile: suite-0.yaml + baseUrl: "" +inCi: false + +# Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file. diff --git a/user_service/keploy/testsuite/suite-0.yaml b/user_service/keploy/testsuite/suite-0.yaml new file mode 100644 index 0000000..2fb235b --- /dev/null +++ b/user_service/keploy/testsuite/suite-0.yaml @@ -0,0 +1,122 @@ +# suite-1.yaml +version: api.keploy.io/v2beta1 +kind: TestSuite +name: API_Gateway_Ecommerce +spec: + metadata: + description: > + Test login, user address management, and order operations + for the E-commerce API Gateway. + + # Security Configuration + security: + ruleset: strict + severity_threshold: HIGH + allowlist: + headers: ["Server"] + keys: ["data.debug"] + + # Load Testing Configuration + load: + profile: ramping_vus + vus: 50 + duration: 5m + stages: + - duration: 1m + target: 10 + - duration: 2m + target: 30 + - duration: 2m + target: 50 + thresholds: + - metric: http_req_duration_p95 + condition: "< 400ms" + severity: high + - metric: http_req_failed_rate + condition: "<= 0.5%" + severity: critical + + # Test Steps + steps: + - name: Login_User + method: POST + url: /api/v1/login + headers: + Content-Type: application/json + body: | + { + "username": "{{username}}", + "password": "p@ssw0rd" + } + extract: + jwt: token + assert: + - type: status_code + expected_string: "200" + - type: json_path + expression: "$.token" + expected_not_empty: true + + - name: Create_Address + method: POST + url: /api/v1/users/{{user_id}}/addresses + headers: + Content-Type: application/json + Authorization: Bearer {{jwt}} + body: | + { + "line1": "1 Main St", + "city": "NYC", + "state": "NY", + "postal_code": "10001", + "country": "US", + "phone": "+1-555-0000", + "is_default": true + } + extract: + address_id: id + assert: + - type: status_code + expected_string: "201" + + - name: Create_Order + method: POST + url: /api/v1/orders + headers: + Content-Type: application/json + Idempotency-Key: "{{idempotency_key}}" + Authorization: Bearer {{jwt}} + body: | + { + "userId": "{{user_id}}", + "items": [ + { + "productId": "{{product_id}}", + "quantity": 1 + } + ], + "shippingAddressId": "{{address_id}}" + } + extract: + order_id: id + assert: + - type: status_code + expected_string: "201" + + - name: Get_Order_Details + method: GET + url: /api/v1/orders/{{order_id}}/details + headers: + Authorization: Bearer {{jwt}} + assert: + - type: status_code + expected_string: "200" + + - name: Delete_User + method: DELETE + url: /api/v1/users/{{user_id}} + headers: + Authorization: Bearer {{jwt}} + assert: + - type: status_code + expected_string: "204" diff --git a/user_service/keploy/testsuite/ts_reports/20251110112827_report_suite-0.yaml b/user_service/keploy/testsuite/ts_reports/20251110112827_report_suite-0.yaml new file mode 100644 index 0000000..69560a2 --- /dev/null +++ b/user_service/keploy/testsuite/ts_reports/20251110112827_report_suite-0.yaml @@ -0,0 +1,55 @@ +suitename: Todo_CRUD_Operations +totalsteps: 2 +failedsteps: 2 +stepsresult: + - stepname: Create_todo + method: POST + url: /todos + status: failed + statuscode: 404 + header: + Content-Length: + - "207" + Content-Type: + - text/html; charset=utf-8 + Date: + - Mon, 10 Nov 2025 11:28:27 GMT + Server: + - Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ responsetime: 11.777958ms + failurereason: expected status code 201 but got 404 + extractedvars: {} + reqbytes: 49 + resbytes: 207 + - stepname: Get_todo + method: GET + url: /todos/{{todo_id}} + status: failed + statuscode: 404 + header: + Content-Length: + - "207" + Content-Type: + - text/html; charset=utf-8 + Date: + - Mon, 10 Nov 2025 11:28:27 GMT + Server: + - Werkzeug/3.0.3 Python/3.11.14 + body: | + + + 404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+ responsetime: 1.930001ms + failurereason: expected status code 200 but got 404 + extractedvars: {} + reqbytes: 0 + resbytes: 207 +executiontime: 13.812667ms From acb2c00fa315c3dfecc15902c0bf61f1c4e1255d Mon Sep 17 00:00:00 2001 From: Sarthak160 Date: Mon, 10 Nov 2025 13:30:29 +0000 Subject: [PATCH 2/3] chore: remove keploy-folder Signed-off-by: Sarthak160 --- keploy/.cz.toml | 5 - keploy/.github/CLA.md | 25 - keploy/.github/CODEOWNERS | 1 - keploy/.github/CONTRIBUTING.md | 101 - keploy/.github/FUNDING.yml | 1 - .../.github/ISSUE_TEMPLATE/--bug-report.yaml | 167 -- .../--documentation-update.yaml | 48 - .../ISSUE_TEMPLATE/--feature-request.yaml | 60 - keploy/.github/ISSUE_TEMPLATE/config.yml | 7 - keploy/.github/License-Apache_2.0-blue.svg | 1 - keploy/.github/PULL_REQUEST_TEMPLATE.md | 70 - .../actions/download-binary/action.yml | 28 - .../.github/actions/download-image/action.yml | 12 - keploy/.github/actions/tester/action.yml | 112 - keploy/.github/actions/tester/install.sh | 172 -- keploy/.github/docs.svg | 21 - keploy/.github/slack.svg | 20 - keploy/.github/workflows/bug.yml | 38 - keploy/.github/workflows/cla.yml | 42 - keploy/.github/workflows/codeql.yml | 84 - keploy/.github/workflows/coverage_stage.yml | 57 - keploy/.github/workflows/docker-publish.yml | 96 - keploy/.github/workflows/docs.yml | 38 - keploy/.github/workflows/feat.yml | 38 - keploy/.github/workflows/go.yml | 31 - keploy/.github/workflows/go_macos.yaml | 25 - keploy/.github/workflows/go_windows.yml | 31 - .../workflows/golang_docker-compose.yml | 49 - keploy/.github/workflows/golang_docker.yml | 49 - .../.github/workflows/golang_http_linux.yml | 53 - keploy/.github/workflows/golang_linux.yml | 46 - .../.github/workflows/golang_mysql_linux.yml | 46 - keploy/.github/workflows/golangci-lint.yml | 48 - keploy/.github/workflows/greetings.yml | 17 - keploy/.github/workflows/java_linux.yml | 58 - keploy/.github/workflows/main.yml | 26 - keploy/.github/workflows/node_docker.yml | 50 - keploy/.github/workflows/node_encoding.yaml | 48 - keploy/.github/workflows/node_linux.yml | 47 - keploy/.github/workflows/prepare_and_run.yml | 74 - keploy/.github/workflows/python_docker.yml | 49 - keploy/.github/workflows/python_linux.yml | 45 - keploy/.github/workflows/release.yml | 101 - keploy/.github/workflows/sample-run.yml | 25 - keploy/.github/workflows/test-go-mongo-1.yml | 60 - keploy/.github/workflows/test-go-mongo-2.yml | 58 - .../check-deprecated-deps.sh | 25 - .../golang-docker-compose.sh | 127 -- .../test_workflow_scripts/golang-docker.sh | 134 -- .../golang-http-linux.sh | 134 -- .../test_workflow_scripts/golang-linux.sh | 144 -- .../golang-mysql-linux.sh | 178 -- .../test_workflow_scripts/java-linux.sh | 255 --- .../test_workflow_scripts/node-docker.sh | 127 -- .../test_workflow_scripts/node-encoding.sh | 165 -- .../test_workflow_scripts/node-linux.sh | 203 -- .../test_workflow_scripts/python-docker.sh | 118 -- .../test_workflow_scripts/python-linux.sh | 128 -- .../test_workflow_scripts/test-iid.sh | 4 - .../test_workflow_scripts/update-docker.sh | 38 - .../test_workflow_scripts/update-java.sh | 8 - keploy/.github/write-good.yml | 3 - keploy/.gitignore | 61 - keploy/.golangci.yml | 24 - keploy/.pre-commit-config.yaml | 7 - keploy/.releaserc.json | 13 - keploy/CITATION.cff | 7 - keploy/CODE_OF_CONDUCT.md | 126 -- keploy/CONTRIBUTING.md | 122 -- keploy/DEBUG.md | 79 - keploy/Dockerfile | 57 - keploy/HACKTOBERFEST_GUIDE.md | 82 - keploy/LICENSE | 201 -- keploy/README-UnitGen.md | 278 --- keploy/README.md | 172 -- keploy/READMEes-Es.md | 243 --- keploy/READMEja-JP.md | 160 -- keploy/SECURITY.md | 9 - keploy/cli/README.md | 5 - keploy/cli/cli.go | 22 - keploy/cli/config.go | 75 - keploy/cli/contract.go | 134 -- keploy/cli/examples.go | 47 - keploy/cli/export.go | 63 - keploy/cli/import.go | 72 - keploy/cli/load.go | 66 - keploy/cli/login.go | 41 - keploy/cli/mock.go | 94 - keploy/cli/normalise.go | 50 - keploy/cli/provider/cmd.go | 1079 ---------- keploy/cli/provider/common.go | 20 - keploy/cli/provider/compat_linux.go | 27 - keploy/cli/provider/compat_others.go | 11 - keploy/cli/provider/core_service_linux.go | 125 -- keploy/cli/provider/core_service_others.go | 83 - keploy/cli/provider/docker.go | 297 --- keploy/cli/provider/service.go | 60 - keploy/cli/provider/util.go | 101 - keploy/cli/record.go | 55 - keploy/cli/report.go | 55 - keploy/cli/rerecord.go | 56 - keploy/cli/root.go | 55 - keploy/cli/secure.go | 286 --- keploy/cli/service.go | 17 - keploy/cli/templatize.go | 53 - keploy/cli/test.go | 63 - keploy/cli/testsuite.go | 60 - keploy/cli/update.go | 51 - keploy/cli/utgen.go | 56 - keploy/config/config.go | 259 --- keploy/config/default.go | 134 -- keploy/entrypoint.sh | 7 - keploy/go.mod | 159 -- keploy/go.sum | 470 ----- keploy/gon.json | 13 - keploy/goreleaser.yaml | 60 - keploy/gsoc25_performance&security-testing.md | 558 ----- keploy/keploy.sh | 295 --- keploy/main.go | 117 -- keploy/oss-pledge.json | 14 - keploy/package-lock.json | 6 - keploy/pkg/README.md | 6 - keploy/pkg/core/app/app.go | 582 ------ keploy/pkg/core/app/util.go | 103 - keploy/pkg/core/core_linux.go | 279 --- keploy/pkg/core/core_others.go | 61 - keploy/pkg/core/core_others_test.go | 92 - keploy/pkg/core/hooks/README.md | 6 - keploy/pkg/core/hooks/bpf_arm64_bpfel.go | 289 --- keploy/pkg/core/hooks/bpf_arm64_bpfel.o | Bin 381808 -> 0 bytes keploy/pkg/core/hooks/bpf_x86_bpfel.go | 289 --- keploy/pkg/core/hooks/bpf_x86_bpfel.o | Bin 381144 -> 0 bytes keploy/pkg/core/hooks/conn/README.md | 5 - keploy/pkg/core/hooks/conn/conn.go | 125 -- keploy/pkg/core/hooks/conn/factory.go | 154 -- keploy/pkg/core/hooks/conn/socket.go | 264 --- keploy/pkg/core/hooks/conn/tracker.go | 571 ------ keploy/pkg/core/hooks/conn/util.go | 329 --- keploy/pkg/core/hooks/hooks.go | 713 ------- keploy/pkg/core/hooks/hooks_test.go | 273 --- keploy/pkg/core/hooks/kernelComm.go | 131 -- keploy/pkg/core/hooks/structs/structs.go | 78 - keploy/pkg/core/hooks/util.go | 126 -- keploy/pkg/core/proxy/README.md | 5 - keploy/pkg/core/proxy/dns.go | 345 ---- keploy/pkg/core/proxy/integrations/README.md | 3 - .../core/proxy/integrations/generic/README.md | 0 .../core/proxy/integrations/generic/decode.go | 124 -- .../core/proxy/integrations/generic/encode.go | 200 -- .../proxy/integrations/generic/generic.go | 69 - .../core/proxy/integrations/generic/match.go | 155 -- .../core/proxy/integrations/grpc/decode.go | 33 - .../core/proxy/integrations/grpc/encode.go | 86 - .../pkg/core/proxy/integrations/grpc/frame.go | 215 -- .../pkg/core/proxy/integrations/grpc/grpc.go | 71 - .../pkg/core/proxy/integrations/grpc/match.go | 227 --- .../core/proxy/integrations/grpc/stream.go | 160 -- .../proxy/integrations/grpc/transcoder.go | 379 ---- .../pkg/core/proxy/integrations/grpc/util.go | 10 - .../core/proxy/integrations/grpcV2/codec.go | 76 - .../core/proxy/integrations/grpcV2/grpc.go | 92 - .../core/proxy/integrations/grpcV2/helper.go | 46 - .../proxy/integrations/grpcV2/listener.go | 64 - .../core/proxy/integrations/grpcV2/match.go | 291 --- .../core/proxy/integrations/grpcV2/mock.go | 213 -- .../core/proxy/integrations/grpcV2/record.go | 428 ---- .../proxy/integrations/grpcV2/replayconn.go | 28 - .../core/proxy/integrations/http/README.md | 6 - .../pkg/core/proxy/integrations/http/chunk.go | 348 ---- .../core/proxy/integrations/http/decode.go | 206 -- .../core/proxy/integrations/http/encode.go | 262 --- .../pkg/core/proxy/integrations/http/http.go | 212 -- .../pkg/core/proxy/integrations/http/match.go | 349 ---- .../pkg/core/proxy/integrations/http/util.go | 46 - .../core/proxy/integrations/integrations.go | 54 - .../core/proxy/integrations/mongo/README.md | 6 - .../core/proxy/integrations/mongo/command.go | 98 - .../core/proxy/integrations/mongo/decode.go | 345 ---- .../core/proxy/integrations/mongo/encode.go | 288 --- .../core/proxy/integrations/mongo/match.go | 244 --- .../core/proxy/integrations/mongo/mongo.go | 128 -- .../proxy/integrations/mongo/operation.go | 1575 --------------- .../proxy/integrations/mongo/scramAuth.go | 512 ----- .../pkg/core/proxy/integrations/mongo/util.go | 36 - .../core/proxy/integrations/mysql/README.md | 52 - .../core/proxy/integrations/mysql/mysql.go | 62 - .../integrations/mysql/panic_fix_test.go | 192 -- .../proxy/integrations/mysql/recorder/conn.go | 730 ------- .../integrations/mysql/recorder/query.go | 559 ----- .../integrations/mysql/recorder/record.go | 131 -- .../proxy/integrations/mysql/replayer/conn.go | 719 ------- .../integrations/mysql/replayer/match.go | 749 ------- .../integrations/mysql/replayer/query.go | 184 -- .../integrations/mysql/replayer/replay.go | 124 -- .../proxy/integrations/mysql/utils/util.go | 360 ---- .../integrations/mysql/utils/util_test.go | 211 -- .../proxy/integrations/mysql/wire/decode.go | 433 ---- .../proxy/integrations/mysql/wire/encode.go | 179 -- .../wire/phase/conn/authMoreDataPacket.go | 43 - .../wire/phase/conn/authNextFactorPacket.go | 56 - .../phase/conn/authSwitchRequestPacket.go | 59 - .../phase/conn/authSwitchResponsePacket.go | 31 - .../phase/conn/handshakeResponse41Packet.go | 272 --- .../conn/handshakeResponse41Packet_test.go | 234 --- .../wire/phase/conn/handshakeV10Packet.go | 167 -- .../integrations/mysql/wire/phase/generic.go | 205 -- .../mysql/wire/phase/query/ResultSetPacket.go | 142 -- .../query/preparedstmt/StmtPrepareOkPacket.go | 153 -- .../query/preparedstmt/stmtClosePacket.go | 26 - .../query/preparedstmt/stmtExecutePacket.go | 237 --- .../preparedstmt/stmtExecutePacket_test.go | 110 - .../query/preparedstmt/stmtFetchPacket.go | 29 - .../query/preparedstmt/stmtPreparePacket.go | 22 - .../query/preparedstmt/stmtResetPacket.go | 25 - .../preparedstmt/stmtSendLongDataPacket.go | 28 - .../mysql/wire/phase/query/queryPacket.go | 225 --- .../query/rowscols/binaryProtocolRowPacket.go | 497 ----- .../phase/query/rowscols/columnCountPacket.go | 32 - .../wire/phase/query/rowscols/columnPacket.go | 201 -- .../phase/query/rowscols/textRowPacket.go | 105 - .../wire/phase/query/utility/initDbPacket.go | 20 - .../phase/query/utility/setOptionPacket.go | 26 - .../proxy/integrations/mysql/wire/util.go | 175 -- .../proxy/integrations/postgres/v1/decode.go | 152 -- .../proxy/integrations/postgres/v1/encode.go | 401 ---- .../proxy/integrations/postgres/v1/match.go | 867 -------- .../integrations/postgres/v1/postgres.go | 87 - .../integrations/postgres/v1/transcoder.go | 313 --- .../proxy/integrations/postgres/v1/util.go | 500 ----- .../core/proxy/integrations/redis/decode.go | 125 -- .../core/proxy/integrations/redis/encode.go | 188 -- .../core/proxy/integrations/redis/match.go | 151 -- .../core/proxy/integrations/redis/redis.go | 78 - .../core/proxy/integrations/scram/scram.go | 137 -- .../pkg/core/proxy/integrations/scram/util.go | 90 - .../pkg/core/proxy/integrations/util/util.go | 88 - keploy/pkg/core/proxy/mockmanager.go | 514 ----- keploy/pkg/core/proxy/options.go | 12 - keploy/pkg/core/proxy/parsers.go | 15 - keploy/pkg/core/proxy/proxy.go | 731 ------- keploy/pkg/core/proxy/tls/asset/ca.crt | 10 - keploy/pkg/core/proxy/tls/asset/ca.key | 5 - keploy/pkg/core/proxy/tls/asset/setup_ca.sh | 108 - keploy/pkg/core/proxy/tls/ca.go | 345 ---- keploy/pkg/core/proxy/tls/tls.go | 58 - keploy/pkg/core/proxy/treedb.go | 88 - keploy/pkg/core/proxy/util.go | 75 - keploy/pkg/core/proxy/util/util.go | 648 ------ keploy/pkg/core/record.go | 24 - keploy/pkg/core/replay.go | 19 - keploy/pkg/core/service.go | 142 -- keploy/pkg/core/tester/tester.go | 134 -- keploy/pkg/http2.go | 741 ------- keploy/pkg/matcher/grpc/canonical.go | 282 --- keploy/pkg/matcher/grpc/match.go | 234 --- keploy/pkg/matcher/http/absmatch.go | 472 ----- keploy/pkg/matcher/http/match.go | 536 ----- keploy/pkg/matcher/http/match_test.go | 236 --- keploy/pkg/matcher/http/utils.go | 103 - keploy/pkg/matcher/schema/match.go | 262 --- keploy/pkg/matcher/utils.go | 1207 ----------- keploy/pkg/models/README.md | 3 - keploy/pkg/models/assertions.go | 17 - keploy/pkg/models/auth.go | 14 - keploy/pkg/models/config.go | 33 - keploy/pkg/models/const.go | 128 -- keploy/pkg/models/errors.go | 27 - keploy/pkg/models/generic.go | 13 - keploy/pkg/models/github.go | 20 - keploy/pkg/models/grpc.go | 79 - keploy/pkg/models/http.go | 48 - keploy/pkg/models/instrument.go | 63 - keploy/pkg/models/mock.go | 92 - keploy/pkg/models/mock_Secret.go | 29 - keploy/pkg/models/mode.go | 52 - keploy/pkg/models/mongo.go | 285 --- keploy/pkg/models/mysql/comm.go | 228 --- keploy/pkg/models/mysql/conn.go | 68 - keploy/pkg/models/mysql/const.go | 267 --- keploy/pkg/models/mysql/generic.go | 30 - keploy/pkg/models/mysql/mysql.go | 61 - keploy/pkg/models/openapi.go | 142 -- keploy/pkg/models/postgres.go | 114 -- keploy/pkg/models/redis.go | 13 - keploy/pkg/models/tele.go | 14 - keploy/pkg/models/testcase.go | 73 - keploy/pkg/models/testcompare.go | 42 - keploy/pkg/models/testrun.go | 155 -- keploy/pkg/models/ut.go | 133 -- keploy/pkg/platform/README.md | 12 - keploy/pkg/platform/auth/auth.go | 237 --- keploy/pkg/platform/coverage/csharp/csharp.go | 130 -- keploy/pkg/platform/coverage/csharp/utils.go | 140 -- keploy/pkg/platform/coverage/golang/golang.go | 151 -- keploy/pkg/platform/coverage/golang/utils.go | 56 - keploy/pkg/platform/coverage/java/java.go | 145 -- keploy/pkg/platform/coverage/java/utils.go | 171 -- .../coverage/javascript/javascript.go | 174 -- .../pkg/platform/coverage/javascript/utils.go | 48 - keploy/pkg/platform/coverage/python/python.go | 173 -- keploy/pkg/platform/coverage/python/utils.go | 166 -- keploy/pkg/platform/coverage/service.go | 18 - keploy/pkg/platform/docker/docker.go | 585 ------ keploy/pkg/platform/docker/service.go | 38 - keploy/pkg/platform/docker/util.go | 53 - keploy/pkg/platform/storage/storage.go | 204 -- keploy/pkg/platform/telemetry/telemetry.go | 185 -- keploy/pkg/platform/telemetry/utils.go | 52 - .../pkg/platform/yaml/configdb/testset/db.go | 96 - keploy/pkg/platform/yaml/configdb/user/db.go | 107 - keploy/pkg/platform/yaml/mockdb/db.go | 319 --- keploy/pkg/platform/yaml/mockdb/util.go | 714 ------- keploy/pkg/platform/yaml/openapidb/db.go | 152 -- keploy/pkg/platform/yaml/reportdb/db.go | 138 -- keploy/pkg/platform/yaml/testdb/db.go | 262 --- keploy/pkg/platform/yaml/testdb/util.go | 384 ---- keploy/pkg/platform/yaml/utils.go | 403 ---- keploy/pkg/platform/yaml/yaml.go | 232 --- keploy/pkg/service/README.md | 5 - .../pkg/service/contract/consumer/consumer.go | 298 --- .../pkg/service/contract/consumer/service.go | 8 - keploy/pkg/service/contract/contract.go | 685 ------- .../pkg/service/contract/provider/provider.go | 127 -- .../pkg/service/contract/provider/service.go | 10 - keploy/pkg/service/contract/schema.go | 111 - keploy/pkg/service/contract/service.go | 30 - keploy/pkg/service/contract/utils.go | 261 --- keploy/pkg/service/export/export.go | 269 --- keploy/pkg/service/import/import.go | 569 ------ keploy/pkg/service/load/dashboard_exposer.go | 118 -- keploy/pkg/service/load/exporter.go | 205 -- keploy/pkg/service/load/load.go | 239 --- keploy/pkg/service/load/metrics_collector.go | 92 - keploy/pkg/service/load/out/404.html | 1 - keploy/pkg/service/load/out/404/index.html | 1 - .../S6A95ZgpgxS8RHL4D2qnv/_buildManifest.js | 1 - .../S6A95ZgpgxS8RHL4D2qnv/_ssgManifest.js | 1 - .../chunks/4bd1b696-cf72ae8a39fa05aa.js | 1 - .../static/chunks/547-586c3cf76649ec2c.js | 27 - .../static/chunks/964-69097a61540f27b4.js | 1 - .../app/_not-found/page-7c3ecdf160dc3360.js | 1 - .../chunks/app/layout-bc503d5738af696e.js | 1 - .../chunks/app/page-a3cd1a73fe99bbf3.js | 1 - .../chunks/framework-7c95b8e5103c9e90.js | 1 - .../static/chunks/main-a5da5fd7e32dc553.js | 1 - .../chunks/main-app-b3d2ebcb2857645a.js | 1 - .../chunks/pages/_app-0a0020ddd67f79cf.js | 1 - .../chunks/pages/_error-03529f2c21436739.js | 1 - .../chunks/polyfills-42372ed130431b0a.js | 1 - .../static/chunks/webpack-cb07c2fe276ae3fa.js | 1 - .../out/_next/static/css/bc1768f92951bee0.css | 3 - .../static/media/26a46d62cd723877-s.woff2 | Bin 18820 -> 0 bytes .../static/media/55c55f0601d81cf3-s.woff2 | Bin 25908 -> 0 bytes .../static/media/581909926a08bbc8-s.woff2 | Bin 19072 -> 0 bytes .../static/media/8e9860b6e62d6359-s.woff2 | Bin 85272 -> 0 bytes .../static/media/97e0cb1ae144a2a9-s.woff2 | Bin 11220 -> 0 bytes .../static/media/df0a9ae256c0569c-s.woff2 | Bin 10280 -> 0 bytes .../static/media/e4af272ccee01ff0-s.p.woff2 | Bin 48432 -> 0 bytes keploy/pkg/service/load/out/favicon.ico | Bin 243774 -> 0 bytes keploy/pkg/service/load/out/index.html | 1 - keploy/pkg/service/load/out/index.txt | 22 - keploy/pkg/service/load/out/manifest.json | 17 - keploy/pkg/service/load/out/sw.js | 188 -- keploy/pkg/service/load/scheduler.go | 138 -- keploy/pkg/service/load/service.go | 9 - .../pkg/service/load/threshold_evaluator.go | 299 --- keploy/pkg/service/load/utils.go | 1 - keploy/pkg/service/load/vu_worker.go | 118 -- .../pkg/service/orchestrator/orchestrator.go | 31 - keploy/pkg/service/orchestrator/rerecord.go | 334 --- keploy/pkg/service/orchestrator/service.go | 7 - keploy/pkg/service/record/record.go | 390 ---- keploy/pkg/service/record/service.go | 51 - keploy/pkg/service/record/utils.go | 3 - keploy/pkg/service/replay/hooks.go | 282 --- keploy/pkg/service/replay/integration_test.go | 272 --- keploy/pkg/service/replay/mock.go | 297 --- keploy/pkg/service/replay/replay.go | 1793 ----------------- keploy/pkg/service/replay/replay_test.go | 265 --- keploy/pkg/service/replay/service.go | 103 - keploy/pkg/service/replay/service_test.go | 267 --- keploy/pkg/service/replay/utils.go | 118 -- keploy/pkg/service/report/report.go | 283 --- keploy/pkg/service/report/service.go | 20 - keploy/pkg/service/secure/secure.go | 1222 ----------- keploy/pkg/service/secure/service.go | 11 - keploy/pkg/service/secure/utils.go | 337 ---- keploy/pkg/service/service.go | 9 - keploy/pkg/service/testsuite/service.go | 11 - keploy/pkg/service/testsuite/testsuite.go | 501 ----- keploy/pkg/service/testsuite/utils.go | 109 - keploy/pkg/service/tools/mock_Service.go | 149 -- keploy/pkg/service/tools/mock_TestDB.go | 143 -- .../pkg/service/tools/mock_TestSetConfig.go | 107 - keploy/pkg/service/tools/service.go | 37 - keploy/pkg/service/tools/templatize.go | 1106 ---------- keploy/pkg/service/tools/templatize_test.go | 162 -- keploy/pkg/service/tools/tools.go | 330 --- keploy/pkg/service/utgen/ai.go | 391 ---- keploy/pkg/service/utgen/assets/config.go | 58 - .../pkg/service/utgen/assets/indentation.toml | 41 - .../pkg/service/utgen/assets/insert_line.toml | 37 - keploy/pkg/service/utgen/assets/language.toml | 439 ---- .../service/utgen/assets/test_generation.toml | 355 ---- keploy/pkg/service/utgen/coverage.go | 249 --- keploy/pkg/service/utgen/gen.go | 813 -------- keploy/pkg/service/utgen/injector.go | 830 -------- keploy/pkg/service/utgen/prompt.go | 207 -- keploy/pkg/service/utgen/service.go | 13 - keploy/pkg/service/utgen/utils.go | 330 --- keploy/pkg/util.go | 637 ------ keploy/pkg/util_test.go | 247 --- keploy/utils/ctx.go | 69 - keploy/utils/inc.go | 23 - keploy/utils/log/colors.go | 48 - keploy/utils/log/logger.go | 99 - keploy/utils/log/time.go | 12 - keploy/utils/mask_others.go | 13 - keploy/utils/mask_windows.go | 11 - keploy/utils/signal_others.go | 104 - keploy/utils/signal_windows.go | 44 - keploy/utils/utils.go | 1292 ------------ keploy/utils/utils_test.go | 348 ---- 423 files changed, 68691 deletions(-) delete mode 100755 keploy/.cz.toml delete mode 100755 keploy/.github/CLA.md delete mode 100644 keploy/.github/CODEOWNERS delete mode 100755 keploy/.github/CONTRIBUTING.md delete mode 100755 keploy/.github/FUNDING.yml delete mode 100755 keploy/.github/ISSUE_TEMPLATE/--bug-report.yaml delete mode 100755 keploy/.github/ISSUE_TEMPLATE/--documentation-update.yaml delete mode 100755 keploy/.github/ISSUE_TEMPLATE/--feature-request.yaml delete mode 100755 keploy/.github/ISSUE_TEMPLATE/config.yml delete mode 100755 keploy/.github/License-Apache_2.0-blue.svg delete mode 100755 keploy/.github/PULL_REQUEST_TEMPLATE.md delete mode 100644 keploy/.github/actions/download-binary/action.yml delete mode 100644 keploy/.github/actions/download-image/action.yml delete mode 100644 keploy/.github/actions/tester/action.yml delete mode 100644 keploy/.github/actions/tester/install.sh delete mode 100755 keploy/.github/docs.svg delete mode 100755 keploy/.github/slack.svg delete mode 100755 keploy/.github/workflows/bug.yml delete mode 100755 keploy/.github/workflows/cla.yml delete mode 100755 keploy/.github/workflows/codeql.yml delete mode 100644 keploy/.github/workflows/coverage_stage.yml delete mode 100755 keploy/.github/workflows/docker-publish.yml delete mode 100755 keploy/.github/workflows/docs.yml delete mode 100755 keploy/.github/workflows/feat.yml delete mode 100755 keploy/.github/workflows/go.yml delete mode 100644 keploy/.github/workflows/go_macos.yaml delete mode 100644 keploy/.github/workflows/go_windows.yml delete mode 100644 keploy/.github/workflows/golang_docker-compose.yml delete mode 100755 keploy/.github/workflows/golang_docker.yml delete mode 100644 keploy/.github/workflows/golang_http_linux.yml delete mode 100644 keploy/.github/workflows/golang_linux.yml delete mode 100644 keploy/.github/workflows/golang_mysql_linux.yml delete mode 100644 keploy/.github/workflows/golangci-lint.yml delete mode 100755 keploy/.github/workflows/greetings.yml delete mode 100644 keploy/.github/workflows/java_linux.yml delete mode 100755 keploy/.github/workflows/main.yml delete mode 100755 keploy/.github/workflows/node_docker.yml delete mode 100644 keploy/.github/workflows/node_encoding.yaml delete mode 100644 keploy/.github/workflows/node_linux.yml delete mode 100644 keploy/.github/workflows/prepare_and_run.yml delete mode 100644 keploy/.github/workflows/python_docker.yml delete mode 100644 keploy/.github/workflows/python_linux.yml delete mode 100755 keploy/.github/workflows/release.yml delete mode 100755 keploy/.github/workflows/sample-run.yml delete mode 100644 keploy/.github/workflows/test-go-mongo-1.yml delete mode 100644 keploy/.github/workflows/test-go-mongo-2.yml delete mode 100644 keploy/.github/workflows/test_workflow_scripts/check-deprecated-deps.sh delete mode 100644 keploy/.github/workflows/test_workflow_scripts/golang-docker-compose.sh delete mode 100755 keploy/.github/workflows/test_workflow_scripts/golang-docker.sh delete mode 100644 keploy/.github/workflows/test_workflow_scripts/golang-http-linux.sh delete mode 100644 keploy/.github/workflows/test_workflow_scripts/golang-linux.sh delete mode 100644 keploy/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh delete mode 100644 keploy/.github/workflows/test_workflow_scripts/java-linux.sh delete mode 100755 keploy/.github/workflows/test_workflow_scripts/node-docker.sh delete mode 100644 keploy/.github/workflows/test_workflow_scripts/node-encoding.sh delete mode 100755 keploy/.github/workflows/test_workflow_scripts/node-linux.sh delete mode 100755 keploy/.github/workflows/test_workflow_scripts/python-docker.sh delete mode 100644 keploy/.github/workflows/test_workflow_scripts/python-linux.sh delete mode 100644 keploy/.github/workflows/test_workflow_scripts/test-iid.sh delete mode 100644 keploy/.github/workflows/test_workflow_scripts/update-docker.sh delete mode 100644 keploy/.github/workflows/test_workflow_scripts/update-java.sh delete mode 100755 keploy/.github/write-good.yml delete mode 100755 keploy/.gitignore delete mode 100644 keploy/.golangci.yml delete mode 100755 keploy/.pre-commit-config.yaml delete mode 100755 keploy/.releaserc.json delete mode 100755 keploy/CITATION.cff delete mode 100755 keploy/CODE_OF_CONDUCT.md delete mode 100755 keploy/CONTRIBUTING.md delete mode 100644 keploy/DEBUG.md delete mode 100755 keploy/Dockerfile delete mode 100644 keploy/HACKTOBERFEST_GUIDE.md delete mode 100755 keploy/LICENSE delete mode 100644 keploy/README-UnitGen.md delete mode 100755 keploy/README.md delete mode 100644 keploy/READMEes-Es.md delete mode 100644 keploy/READMEja-JP.md delete mode 100755 keploy/SECURITY.md delete mode 100755 keploy/cli/README.md delete mode 100644 keploy/cli/cli.go delete mode 100644 keploy/cli/config.go delete mode 100644 keploy/cli/contract.go delete mode 100755 keploy/cli/examples.go delete mode 100644 keploy/cli/export.go delete mode 100644 keploy/cli/import.go delete mode 100644 keploy/cli/load.go delete mode 100644 keploy/cli/login.go delete mode 100644 keploy/cli/mock.go delete mode 100644 keploy/cli/normalise.go delete mode 100644 keploy/cli/provider/cmd.go delete mode 100644 keploy/cli/provider/common.go delete mode 100644 keploy/cli/provider/compat_linux.go delete mode 100644 keploy/cli/provider/compat_others.go delete mode 100644 keploy/cli/provider/core_service_linux.go delete mode 100644 keploy/cli/provider/core_service_others.go delete mode 100644 keploy/cli/provider/docker.go delete mode 100644 keploy/cli/provider/service.go delete mode 100644 keploy/cli/provider/util.go delete mode 100755 keploy/cli/record.go delete mode 100644 keploy/cli/report.go delete mode 100644 keploy/cli/rerecord.go delete mode 100755 keploy/cli/root.go delete mode 100644 keploy/cli/secure.go delete mode 100644 keploy/cli/service.go delete mode 100644 keploy/cli/templatize.go delete mode 100755 keploy/cli/test.go delete mode 100644 keploy/cli/testsuite.go delete mode 100644 keploy/cli/update.go delete mode 100644 keploy/cli/utgen.go delete mode 100644 keploy/config/config.go delete mode 100644 keploy/config/default.go delete mode 100755 keploy/entrypoint.sh delete mode 100755 keploy/go.mod delete mode 100644 keploy/go.sum delete mode 100755 keploy/gon.json delete mode 100755 keploy/goreleaser.yaml delete mode 100644 keploy/gsoc25_performance&security-testing.md delete mode 100644 keploy/keploy.sh delete mode 100755 keploy/main.go delete mode 100644 keploy/oss-pledge.json delete mode 100644 keploy/package-lock.json delete mode 100755 keploy/pkg/README.md delete mode 100644 keploy/pkg/core/app/app.go delete mode 100644 keploy/pkg/core/app/util.go delete mode 100644 keploy/pkg/core/core_linux.go delete mode 100644 keploy/pkg/core/core_others.go delete mode 100644 keploy/pkg/core/core_others_test.go delete mode 100755 keploy/pkg/core/hooks/README.md delete mode 100644 keploy/pkg/core/hooks/bpf_arm64_bpfel.go delete mode 100644 keploy/pkg/core/hooks/bpf_arm64_bpfel.o delete mode 100644 keploy/pkg/core/hooks/bpf_x86_bpfel.go delete mode 100644 keploy/pkg/core/hooks/bpf_x86_bpfel.o delete mode 100755 keploy/pkg/core/hooks/conn/README.md delete mode 100644 keploy/pkg/core/hooks/conn/conn.go delete mode 100755 keploy/pkg/core/hooks/conn/factory.go delete mode 100644 keploy/pkg/core/hooks/conn/socket.go delete mode 100755 keploy/pkg/core/hooks/conn/tracker.go delete mode 100755 keploy/pkg/core/hooks/conn/util.go delete mode 100644 keploy/pkg/core/hooks/hooks.go delete mode 100644 keploy/pkg/core/hooks/hooks_test.go delete mode 100644 keploy/pkg/core/hooks/kernelComm.go delete mode 100755 keploy/pkg/core/hooks/structs/structs.go delete mode 100644 keploy/pkg/core/hooks/util.go delete mode 100755 keploy/pkg/core/proxy/README.md delete mode 100644 keploy/pkg/core/proxy/dns.go delete mode 100755 keploy/pkg/core/proxy/integrations/README.md delete mode 100644 keploy/pkg/core/proxy/integrations/generic/README.md delete mode 100644 keploy/pkg/core/proxy/integrations/generic/decode.go delete mode 100644 keploy/pkg/core/proxy/integrations/generic/encode.go delete mode 100755 keploy/pkg/core/proxy/integrations/generic/generic.go delete mode 100755 keploy/pkg/core/proxy/integrations/generic/match.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpc/decode.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpc/encode.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpc/frame.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpc/grpc.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpc/match.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpc/stream.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpc/transcoder.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpc/util.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/codec.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/grpc.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/helper.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/listener.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/match.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/mock.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/record.go delete mode 100644 keploy/pkg/core/proxy/integrations/grpcV2/replayconn.go delete mode 100755 keploy/pkg/core/proxy/integrations/http/README.md delete mode 100644 keploy/pkg/core/proxy/integrations/http/chunk.go delete mode 100644 keploy/pkg/core/proxy/integrations/http/decode.go delete mode 100644 keploy/pkg/core/proxy/integrations/http/encode.go delete mode 100755 keploy/pkg/core/proxy/integrations/http/http.go delete mode 100644 keploy/pkg/core/proxy/integrations/http/match.go delete mode 100644 keploy/pkg/core/proxy/integrations/http/util.go delete mode 100644 keploy/pkg/core/proxy/integrations/integrations.go delete mode 100644 keploy/pkg/core/proxy/integrations/mongo/README.md delete mode 100644 keploy/pkg/core/proxy/integrations/mongo/command.go delete mode 100644 keploy/pkg/core/proxy/integrations/mongo/decode.go delete mode 100644 keploy/pkg/core/proxy/integrations/mongo/encode.go delete mode 100644 keploy/pkg/core/proxy/integrations/mongo/match.go delete mode 100644 keploy/pkg/core/proxy/integrations/mongo/mongo.go delete mode 100644 keploy/pkg/core/proxy/integrations/mongo/operation.go delete mode 100644 keploy/pkg/core/proxy/integrations/mongo/scramAuth.go delete mode 100644 keploy/pkg/core/proxy/integrations/mongo/util.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/README.md delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/mysql.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/panic_fix_test.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/recorder/conn.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/recorder/query.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/recorder/record.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/replayer/conn.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/replayer/match.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/replayer/query.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/replayer/replay.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/utils/util.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/utils/util_test.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/decode.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/encode.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authMoreDataPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authNextFactorPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchRequestPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchResponsePacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet_test.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeV10Packet.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/generic.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/ResultSetPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/StmtPrepareOkPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtClosePacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket_test.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtFetchPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtPreparePacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtResetPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtSendLongDataPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/queryPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/columnCountPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/columnPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/textRowPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/initDbPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/setOptionPacket.go delete mode 100644 keploy/pkg/core/proxy/integrations/mysql/wire/util.go delete mode 100644 keploy/pkg/core/proxy/integrations/postgres/v1/decode.go delete mode 100755 keploy/pkg/core/proxy/integrations/postgres/v1/encode.go delete mode 100644 keploy/pkg/core/proxy/integrations/postgres/v1/match.go delete mode 100755 keploy/pkg/core/proxy/integrations/postgres/v1/postgres.go delete mode 100644 keploy/pkg/core/proxy/integrations/postgres/v1/transcoder.go delete mode 100755 keploy/pkg/core/proxy/integrations/postgres/v1/util.go delete mode 100644 keploy/pkg/core/proxy/integrations/redis/decode.go delete mode 100644 keploy/pkg/core/proxy/integrations/redis/encode.go delete mode 100755 keploy/pkg/core/proxy/integrations/redis/match.go delete mode 100755 keploy/pkg/core/proxy/integrations/redis/redis.go delete mode 100644 keploy/pkg/core/proxy/integrations/scram/scram.go delete mode 100644 keploy/pkg/core/proxy/integrations/scram/util.go delete mode 100644 keploy/pkg/core/proxy/integrations/util/util.go delete mode 100644 keploy/pkg/core/proxy/mockmanager.go delete mode 100755 keploy/pkg/core/proxy/options.go delete mode 100644 keploy/pkg/core/proxy/parsers.go delete mode 100755 keploy/pkg/core/proxy/proxy.go delete mode 100755 keploy/pkg/core/proxy/tls/asset/ca.crt delete mode 100755 keploy/pkg/core/proxy/tls/asset/ca.key delete mode 100755 keploy/pkg/core/proxy/tls/asset/setup_ca.sh delete mode 100644 keploy/pkg/core/proxy/tls/ca.go delete mode 100644 keploy/pkg/core/proxy/tls/tls.go delete mode 100644 keploy/pkg/core/proxy/treedb.go delete mode 100644 keploy/pkg/core/proxy/util.go delete mode 100755 keploy/pkg/core/proxy/util/util.go delete mode 100644 keploy/pkg/core/record.go delete mode 100644 keploy/pkg/core/replay.go delete mode 100644 keploy/pkg/core/service.go delete mode 100644 keploy/pkg/core/tester/tester.go delete mode 100644 keploy/pkg/http2.go delete mode 100644 keploy/pkg/matcher/grpc/canonical.go delete mode 100644 keploy/pkg/matcher/grpc/match.go delete mode 100644 keploy/pkg/matcher/http/absmatch.go delete mode 100644 keploy/pkg/matcher/http/match.go delete mode 100644 keploy/pkg/matcher/http/match_test.go delete mode 100644 keploy/pkg/matcher/http/utils.go delete mode 100644 keploy/pkg/matcher/schema/match.go delete mode 100644 keploy/pkg/matcher/utils.go delete mode 100755 keploy/pkg/models/README.md delete mode 100644 keploy/pkg/models/assertions.go delete mode 100644 keploy/pkg/models/auth.go delete mode 100644 keploy/pkg/models/config.go delete mode 100755 keploy/pkg/models/const.go delete mode 100644 keploy/pkg/models/errors.go delete mode 100644 keploy/pkg/models/generic.go delete mode 100644 keploy/pkg/models/github.go delete mode 100644 keploy/pkg/models/grpc.go delete mode 100755 keploy/pkg/models/http.go delete mode 100644 keploy/pkg/models/instrument.go delete mode 100755 keploy/pkg/models/mock.go delete mode 100644 keploy/pkg/models/mock_Secret.go delete mode 100755 keploy/pkg/models/mode.go delete mode 100755 keploy/pkg/models/mongo.go delete mode 100644 keploy/pkg/models/mysql/comm.go delete mode 100644 keploy/pkg/models/mysql/conn.go delete mode 100644 keploy/pkg/models/mysql/const.go delete mode 100644 keploy/pkg/models/mysql/generic.go delete mode 100644 keploy/pkg/models/mysql/mysql.go delete mode 100644 keploy/pkg/models/openapi.go delete mode 100755 keploy/pkg/models/postgres.go delete mode 100644 keploy/pkg/models/redis.go delete mode 100755 keploy/pkg/models/tele.go delete mode 100755 keploy/pkg/models/testcase.go delete mode 100644 keploy/pkg/models/testcompare.go delete mode 100755 keploy/pkg/models/testrun.go delete mode 100644 keploy/pkg/models/ut.go delete mode 100755 keploy/pkg/platform/README.md delete mode 100644 keploy/pkg/platform/auth/auth.go delete mode 100644 keploy/pkg/platform/coverage/csharp/csharp.go delete mode 100644 keploy/pkg/platform/coverage/csharp/utils.go delete mode 100644 keploy/pkg/platform/coverage/golang/golang.go delete mode 100644 keploy/pkg/platform/coverage/golang/utils.go delete mode 100644 keploy/pkg/platform/coverage/java/java.go delete mode 100644 keploy/pkg/platform/coverage/java/utils.go delete mode 100644 keploy/pkg/platform/coverage/javascript/javascript.go delete mode 100644 keploy/pkg/platform/coverage/javascript/utils.go delete mode 100644 keploy/pkg/platform/coverage/python/python.go delete mode 100644 keploy/pkg/platform/coverage/python/utils.go delete mode 100644 keploy/pkg/platform/coverage/service.go delete mode 100644 keploy/pkg/platform/docker/docker.go delete mode 100644 keploy/pkg/platform/docker/service.go delete mode 100644 keploy/pkg/platform/docker/util.go delete mode 100644 keploy/pkg/platform/storage/storage.go delete mode 100644 keploy/pkg/platform/telemetry/telemetry.go delete mode 100644 keploy/pkg/platform/telemetry/utils.go delete mode 100644 keploy/pkg/platform/yaml/configdb/testset/db.go delete mode 100644 keploy/pkg/platform/yaml/configdb/user/db.go delete mode 100644 keploy/pkg/platform/yaml/mockdb/db.go delete mode 100644 keploy/pkg/platform/yaml/mockdb/util.go delete mode 100644 keploy/pkg/platform/yaml/openapidb/db.go delete mode 100755 keploy/pkg/platform/yaml/reportdb/db.go delete mode 100644 keploy/pkg/platform/yaml/testdb/db.go delete mode 100644 keploy/pkg/platform/yaml/testdb/util.go delete mode 100755 keploy/pkg/platform/yaml/utils.go delete mode 100755 keploy/pkg/platform/yaml/yaml.go delete mode 100755 keploy/pkg/service/README.md delete mode 100644 keploy/pkg/service/contract/consumer/consumer.go delete mode 100644 keploy/pkg/service/contract/consumer/service.go delete mode 100644 keploy/pkg/service/contract/contract.go delete mode 100644 keploy/pkg/service/contract/provider/provider.go delete mode 100644 keploy/pkg/service/contract/provider/service.go delete mode 100644 keploy/pkg/service/contract/schema.go delete mode 100644 keploy/pkg/service/contract/service.go delete mode 100644 keploy/pkg/service/contract/utils.go delete mode 100644 keploy/pkg/service/export/export.go delete mode 100644 keploy/pkg/service/import/import.go delete mode 100644 keploy/pkg/service/load/dashboard_exposer.go delete mode 100644 keploy/pkg/service/load/exporter.go delete mode 100644 keploy/pkg/service/load/load.go delete mode 100644 keploy/pkg/service/load/metrics_collector.go delete mode 100644 keploy/pkg/service/load/out/404.html delete mode 100644 keploy/pkg/service/load/out/404/index.html delete mode 100644 keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_buildManifest.js delete mode 100644 keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_ssgManifest.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/547-586c3cf76649ec2c.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/964-69097a61540f27b4.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/app/_not-found/page-7c3ecdf160dc3360.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/app/layout-bc503d5738af696e.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/app/page-a3cd1a73fe99bbf3.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/framework-7c95b8e5103c9e90.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/main-a5da5fd7e32dc553.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/main-app-b3d2ebcb2857645a.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/pages/_app-0a0020ddd67f79cf.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/pages/_error-03529f2c21436739.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/polyfills-42372ed130431b0a.js delete mode 100644 keploy/pkg/service/load/out/_next/static/chunks/webpack-cb07c2fe276ae3fa.js delete mode 100644 keploy/pkg/service/load/out/_next/static/css/bc1768f92951bee0.css delete mode 100644 keploy/pkg/service/load/out/_next/static/media/26a46d62cd723877-s.woff2 delete mode 100644 keploy/pkg/service/load/out/_next/static/media/55c55f0601d81cf3-s.woff2 delete mode 100644 keploy/pkg/service/load/out/_next/static/media/581909926a08bbc8-s.woff2 delete mode 100644 keploy/pkg/service/load/out/_next/static/media/8e9860b6e62d6359-s.woff2 delete mode 100644 keploy/pkg/service/load/out/_next/static/media/97e0cb1ae144a2a9-s.woff2 delete mode 100644 keploy/pkg/service/load/out/_next/static/media/df0a9ae256c0569c-s.woff2 delete mode 100644 keploy/pkg/service/load/out/_next/static/media/e4af272ccee01ff0-s.p.woff2 delete mode 100644 keploy/pkg/service/load/out/favicon.ico delete mode 100644 keploy/pkg/service/load/out/index.html delete mode 100644 keploy/pkg/service/load/out/index.txt delete mode 100644 keploy/pkg/service/load/out/manifest.json delete mode 100644 keploy/pkg/service/load/out/sw.js delete mode 100644 keploy/pkg/service/load/scheduler.go delete mode 100644 keploy/pkg/service/load/service.go delete mode 100644 keploy/pkg/service/load/threshold_evaluator.go delete mode 100644 keploy/pkg/service/load/utils.go delete mode 100644 keploy/pkg/service/load/vu_worker.go delete mode 100644 keploy/pkg/service/orchestrator/orchestrator.go delete mode 100644 keploy/pkg/service/orchestrator/rerecord.go delete mode 100644 keploy/pkg/service/orchestrator/service.go delete mode 100755 keploy/pkg/service/record/record.go delete mode 100755 keploy/pkg/service/record/service.go delete mode 100644 keploy/pkg/service/record/utils.go delete mode 100644 keploy/pkg/service/replay/hooks.go delete mode 100644 keploy/pkg/service/replay/integration_test.go delete mode 100644 keploy/pkg/service/replay/mock.go delete mode 100644 keploy/pkg/service/replay/replay.go delete mode 100644 keploy/pkg/service/replay/replay_test.go delete mode 100644 keploy/pkg/service/replay/service.go delete mode 100644 keploy/pkg/service/replay/service_test.go delete mode 100644 keploy/pkg/service/replay/utils.go delete mode 100644 keploy/pkg/service/report/report.go delete mode 100644 keploy/pkg/service/report/service.go delete mode 100644 keploy/pkg/service/secure/secure.go delete mode 100644 keploy/pkg/service/secure/service.go delete mode 100644 keploy/pkg/service/secure/utils.go delete mode 100644 keploy/pkg/service/service.go delete mode 100644 keploy/pkg/service/testsuite/service.go delete mode 100644 keploy/pkg/service/testsuite/testsuite.go delete mode 100644 keploy/pkg/service/testsuite/utils.go delete mode 100644 keploy/pkg/service/tools/mock_Service.go delete mode 100644 keploy/pkg/service/tools/mock_TestDB.go delete mode 100644 keploy/pkg/service/tools/mock_TestSetConfig.go delete mode 100644 keploy/pkg/service/tools/service.go delete mode 100644 keploy/pkg/service/tools/templatize.go delete mode 100644 keploy/pkg/service/tools/templatize_test.go delete mode 100644 keploy/pkg/service/tools/tools.go delete mode 100644 keploy/pkg/service/utgen/ai.go delete mode 100644 keploy/pkg/service/utgen/assets/config.go delete mode 100644 keploy/pkg/service/utgen/assets/indentation.toml delete mode 100644 keploy/pkg/service/utgen/assets/insert_line.toml delete mode 100644 keploy/pkg/service/utgen/assets/language.toml delete mode 100644 keploy/pkg/service/utgen/assets/test_generation.toml delete mode 100644 keploy/pkg/service/utgen/coverage.go delete mode 100644 keploy/pkg/service/utgen/gen.go delete mode 100644 keploy/pkg/service/utgen/injector.go delete mode 100644 keploy/pkg/service/utgen/prompt.go delete mode 100644 keploy/pkg/service/utgen/service.go delete mode 100644 keploy/pkg/service/utgen/utils.go delete mode 100755 keploy/pkg/util.go delete mode 100644 keploy/pkg/util_test.go delete mode 100644 keploy/utils/ctx.go delete mode 100644 keploy/utils/inc.go delete mode 100644 keploy/utils/log/colors.go delete mode 100644 keploy/utils/log/logger.go delete mode 100644 keploy/utils/log/time.go delete mode 100644 keploy/utils/mask_others.go delete mode 100644 keploy/utils/mask_windows.go delete mode 100644 keploy/utils/signal_others.go delete mode 100644 keploy/utils/signal_windows.go delete mode 100644 keploy/utils/utils.go delete mode 100644 keploy/utils/utils_test.go diff --git a/keploy/.cz.toml b/keploy/.cz.toml deleted file mode 100755 index a574cf2..0000000 --- a/keploy/.cz.toml +++ /dev/null @@ -1,5 +0,0 @@ -[tool] -[tool.commitizen] -name = "cz_conventional_commits" -version = "0.2.4" -tag_format = "v$version" diff --git a/keploy/.github/CLA.md b/keploy/.github/CLA.md deleted file mode 100755 index 6302b5d..0000000 --- a/keploy/.github/CLA.md +++ /dev/null @@ -1,25 +0,0 @@ -# Contributor License Agreement (CLA) - -This license is for your protection as a Contributor as well as the protection of the maintainers of the Keploy software; it does not change your rights to use your own Contributions for any other purpose. In the following, the maintainers of Keploy are referred to as "Keploy". - -You accept and agree to the following terms and conditions for Your present and future Contributions submitted to "Keploy". Except for the license granted herein to Keploy and recipients of software distributed by "Keploy", You reserve all right, title, and interest in and to Your Contributions. - -1. Definitions. - - "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with "Keploy". For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. - - "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to "Keploy" for inclusion in, or documentation of, any of the products owned or managed by "Keploy" (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to "Keploy" or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, "Keploy" for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." - -2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You grant to "Keploy" and to recipients of software distributed by "Keploy" a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. - -3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You grant to "Keploy" and to recipients of software distributed by "Keploy" a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. - -4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to "Keploy", or that your employer has executed a separate Contributor License Agreement with "Keploy". - -5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. - -6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. - -7. Should You wish to submit work that is not Your original creation, You may submit it to "Keploy" separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". - -8. You agree to notify "Keploy" of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. \ No newline at end of file diff --git a/keploy/.github/CODEOWNERS b/keploy/.github/CODEOWNERS deleted file mode 100644 index edb2b0a..0000000 --- a/keploy/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @gouravkrosx diff --git a/keploy/.github/CONTRIBUTING.md b/keploy/.github/CONTRIBUTING.md deleted file mode 100755 index 91c4733..0000000 --- a/keploy/.github/CONTRIBUTING.md +++ /dev/null @@ -1,101 +0,0 @@ -# Keploy Record/Replay CI Guide - -This document describes the **one‑time build / one‑time download** strategy we use in all CI jobs that exercise Keploy, and how you can plug new sample workflows into the same system. - ---- - -## Table of contents - -1. Why we build the PR binary **once** and download the latest release **once**. -2. Why we also **build *one* Docker image** and reuse it via a temporary registry. -3. Key files that implement the mechanism. -4. Checklist – add a new language / sample workflow in < 2 mins. -5. Troubleshooting tips. - ---- - -## 1. Why one‑time *build + download* - -* **Speed** – compiling and downloading just once keeps the fan‑out jobs lean. -* **Consistency** – every downstream job gets the *exact same* PR binary and the *exact same* "latest" binary (avoids the "a new patch went live mid‑run" race). -* **Flexibility** – the 3‑row test‑matrix lets us cover all flows: - * record **latest** → replay **build** - * record **build** → replay **latest** - * record **build** → replay **build** *(the legacy flow)* - ---- - -## 2. Why one‑time *build & push Docker image* -Some samples (e.g. `gin‑mongo` in Docker‑mode) need a container image. Building it repeatedly inside every matrix row is wasteful, so we: - -1. **Build once** in `prepare_and_run.yml` → job `build‑docker‑image`. -2. **Push** the result to [`ttl.sh`](https://ttl.sh) with a 1‑hour TTL (`ttl.sh/keploy/keploy:1h`). -3. **Pull & re‑tag** it inside downstream jobs via the composite action `download‑image` so that the image name matches what the samples expect (`ghcr.io/keploy/keploy:v2‑dev`). - -Advantages are identical to the binary‑artifact strategy – plus we keep our public registries clean because the image auto‑expires. - ---- - -## 3. Key files - -| File / dir | Purpose | -| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `.github/workflows/prepare_and_run.yml` | The *aggregator* – builds the PR binary, downloads `latest`, uploads both as artifacts **and** builds + pushes the one Docker image. Then it fans‑out to language/sample workflows. | -| `.github/actions/download-binary/action.yml` | Composite action – downloads **one** of those two binary artifacts and outputs its absolute path. | -| `.github/actions/download-image/action.yml` | Composite action – pulls the temporary image from `ttl.sh`, re‑tags it to `ghcr.io/keploy/keploy:v2-dev`, and makes it available for the sample. | -| `.github/workflows/*_linux.yml`, `*_docker.yml`, … | Language/sample workflows. They declare the 3‑row matrix and obtain the two binaries (and, for Docker flows, the image) via the composite actions. | -| `.github/workflows/test_workflow_scripts/*.sh` | Bash helpers that run the sample under record / replay. All scripts use the two env vars **`$RECORD_BIN`** / **`$REPLAY_BIN`** that the workflow passes in. | - -### Why fail-fast: false in every matrix? - -We set fail-fast: false inside each strategy.matrix to ensure that all matrix permutations run to completion even if one of them fails. This gives us full visibility into the different record/replay combinations, helps surface flaky paths, and prevents a single early failure from masking other regressions. - ---- - -## 4. Adding a new workflow – checklist - -1. **Copy an existing sibling** (e.g. `golang_linux.yml`) ➜ rename it. -2. Keep the **matrix** block *as is* unless you truly need fewer combinations. -3. Add the two `download-binary` steps: - - ```yaml - - id: record - uses: ./.github/actions/download-binary - with: { src: ${{ matrix.record_src }} } - - - id: replay - uses: ./.github/actions/download-binary - with: { src: ${{ matrix.replay_src }} } - ``` -4. **Docker‑based sample?** Insert the `download-image` step *before* you start the app: - - ```yaml - - id: image - uses: ./.github/actions/download-image - ``` -5. Pass the paths into your run step: - - ```yaml - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - ``` -6. In the helper script you invoke, ensure every `keploy record|test` call uses `$RECORD_BIN` / `$REPLAY_BIN`. -7. **Wire the workflow** into `prepare_and_run.yml`: - - ```yaml - run_: - needs: [build-and-upload, upload-latest] - uses: ./.github/workflows/my_.yml - ``` - Replace `` with the name of your workflow. - **Note:** *If your sample needs the pre‑built Docker image add `build-docker-image` to the `needs` list.* - ---- - -## 5. Troubleshooting - -When a workflow fails, and log isn't visible, then it might be due to the error happening not in record/replay step, but somewhere else, could be that some referenced binary was not found (exit code 127), could be some permission issues, could be file not found. Look for the exit code to determine what failed (https://tldp.org/LDP/abs/html/exitcodes.html), and check the previous successful step to pipe out the line in bash script related to the workflow run. - ---- - diff --git a/keploy/.github/FUNDING.yml b/keploy/.github/FUNDING.yml deleted file mode 100755 index 24b566c..0000000 --- a/keploy/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: [keploy] diff --git a/keploy/.github/ISSUE_TEMPLATE/--bug-report.yaml b/keploy/.github/ISSUE_TEMPLATE/--bug-report.yaml deleted file mode 100755 index fc9d7bb..0000000 --- a/keploy/.github/ISSUE_TEMPLATE/--bug-report.yaml +++ /dev/null @@ -1,167 +0,0 @@ -name: Bug report -description: Create a bug report to help us improve Keploy -title: "[bug]: " -labels: [bug] -body: -- type: markdown - attributes: - value: | - Thanks for taking the time to fill out our bug report form 🙏 - -- type: checkboxes - attributes: - label: "👀 Is there an existing issue for this?" - description: Please search to see if an issue already exists for the bug you encountered - options: - - label: I have searched and didn't find similar issue - required: true - -- type: textarea - attributes: - label: 👍 Current behavior - description: A concise description of what you're experiencing and what you expect - placeholder: | - When I do , happens and I see the error message attached below: - ```...``` - What I expect is - validations: - required: true - -- type: textarea - attributes: - label: 👟 Steps to Replicate - description: How do you trigger this bug? Please walk us through it step by step. - placeholder: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error - validations: - required: true - -- type: textarea - id: logs - attributes: - label: 📜 Logs (if any) - description: Paste any relevant logs here. You can also upload `.log` files via drag & drop. - placeholder: | - ``` - paste stack trace, application log, or keploy logs here - ``` - validations: - required: false - -- type: dropdown - id: operating-system - attributes: - label: "💻 Operating system" - description: What OS is your server/device running on? - options: - - Linux - - MacOS - - Windows - - Something else - validations: - required: true - -- type: textarea - id: uname - attributes: - label: 🧾 System Info (`uname -a`) - description: Run "uname -a" on your terminal and paste the output (macOS/Linux only). - placeholder: | - Darwin MacBook-Pro.local 22.6.0 Darwin Kernel Version ... - validations: - required: false - -- type: textarea - id: os-release - attributes: - label: 📦 OS Release Info (`cat /etc/os-release`) - description: Run "cat /etc/os-release" (Linux only) and paste the full output here. - placeholder: | - NAME="Ubuntu" - VERSION="22.04.4 LTS (Jammy Jellyfish)" - ... - validations: - required: false - -- type: textarea - id: docker - attributes: - label: 🐳 Docker Info (if applicable) - description: | - Are you running inside Docker? If yes, please mention: - - Docker version - - Docker Desktop version (if used) - - Build image - - Runtime image - placeholder: | - Yes, running inside Docker. - - Docker version: 24.0.2 - - Docker Desktop version: 4.24.2 - - Build image: golang:1.21-alpine - - Runtime image: alpine:3.18 - validations: - required: false - -- type: textarea - id: environment - attributes: - label: "🧱 Your Environment" - description: Include any infrastructure info (e.g., local machine, VM, cloud, Kubernetes etc.) - placeholder: | - - Running on Kubernetes (GKE) - - 3-node cluster - validations: - required: false - -- type: textarea - id: version - attributes: - label: 🎲 Version - description: | - You can find your current Keploy version by running "keploy -v" or "keploy --version" in your terminal. - placeholder: | - I am using latest version v2.5.2 - validations: - required: true - -- type: dropdown - id: repository - attributes: - label: 📦 Repository - options: - - keploy - - go-sdk - - java-sdk - - python-sdk - - typescript-sdk - - docs - - website - - writers-program - - blog-website - - ui - - vscode-extension - - jetbrains-plugin - - samples-go - - samples-java - - samples-rust - - samples-python - - samples-csharp - - samples-typescript - validations: - required: true - -- type: textarea - id: use-case - attributes: - label: 🤔 What use case were you trying? (optional) - description: Tell us briefly what you were trying to achieve when this bug occurred. - placeholder: | - I was trying to capture test cases during a login API call... - -- type: markdown - attributes: - value: | - I have read the [Code of Conduct](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) and followed [Contribution Guide](https://keploy.io/docs/keploy-explained/contribution-guide/) \ No newline at end of file diff --git a/keploy/.github/ISSUE_TEMPLATE/--documentation-update.yaml b/keploy/.github/ISSUE_TEMPLATE/--documentation-update.yaml deleted file mode 100755 index 17b9460..0000000 --- a/keploy/.github/ISSUE_TEMPLATE/--documentation-update.yaml +++ /dev/null @@ -1,48 +0,0 @@ -name: Documentation Update📄 -description: Suggest an improvement/addition in the Keploy Server Docs. -title: "[docs]: " -labels: [Documentation] - -body: - - type: markdown - attributes: - value: Thank you for taking the time to our documentation better 🙏 - - type: checkboxes - attributes: - label: "👀 Is there an existing issue for this?" - description: Please search to see if an issue already exists for the bug you encountered - options: - - label: I have searched and didn't find similar issue - required: true - - type: textarea - attributes: - label: "💭 Description" - description: "A clear and concise description of what the issue is." - placeholder: "Documentation is ..." - - type: dropdown - id: repository - attributes: - label: 💻 Repository - options: - - keploy - - go-sdk - - java-sdk - - python-sdk - - typescript-sdk - - docs - - website - - writers-program - - blog-website - - ui - - samples-go - - samples-java - - samples-rust - - samples-python - - samples-csharp - - samples-typescript - validations: - required: true - - type: markdown - attributes: - value: | - I have read the [Code of Conduct](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) \ No newline at end of file diff --git a/keploy/.github/ISSUE_TEMPLATE/--feature-request.yaml b/keploy/.github/ISSUE_TEMPLATE/--feature-request.yaml deleted file mode 100755 index 272386c..0000000 --- a/keploy/.github/ISSUE_TEMPLATE/--feature-request.yaml +++ /dev/null @@ -1,60 +0,0 @@ -name: Feature request -description: Suggest a feature to improve Keploy -title: "[feature]: " -labels: [Enhancement] -body: -- type: markdown - attributes: - value: | - Thank you for taking the time to request a feature for Keploy🙏 -- type: checkboxes - attributes: - label: 👀 Is there an existing feature request for this? - description: Please search to see if an issue related to this feature request/feature request already exists - options: - - label: I have searched the existing issues - required: true -- type: textarea - attributes: - label: 🔖 Enhancement description - description: A clear and concise description of what the enhancement is. - placeholder: | - "I would like to see ..." - validations: - required: true -- type: textarea - attributes: - label: 🎤 Why should this be worked on? - description: A concise description of the problems or use cases for this feature request - placeholder: "In my use-case, ..." - validations: - required: true -- type: dropdown - id: repository - attributes: - label: 💻 Repository - options: - - keploy - - go-sdk - - java-sdk - - python-sdk - - typescript-sdk - - docs - - website - - writers-program - - blog-website - - ui - - vscode-extension - - jetbrains-plugin - - samples-go - - samples-java - - samples-rust - - samples-python - - samples-csharp - - samples-typescript - validations: - required: true -- type: markdown - attributes: - value: | - I have read the [Code of Conduct](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) diff --git a/keploy/.github/ISSUE_TEMPLATE/config.yml b/keploy/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100755 index 6d0c5b2..0000000 --- a/keploy/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,7 +0,0 @@ -contact_links: - - name: Help and support - url: https://github.com/keploy/keploy#community-support - about: Reach out to us on our Slack channel or Discourse discussions or GitHub discussions. - - name: Dedicated support - url: mailto:hello@keploy.io - about: Write to us if you'd like dedicated support using Keploy \ No newline at end of file diff --git a/keploy/.github/License-Apache_2.0-blue.svg b/keploy/.github/License-Apache_2.0-blue.svg deleted file mode 100755 index ca466ca..0000000 --- a/keploy/.github/License-Apache_2.0-blue.svg +++ /dev/null @@ -1 +0,0 @@ -License: Apache 2.0LicenseApache 2.0 \ No newline at end of file diff --git a/keploy/.github/PULL_REQUEST_TEMPLATE.md b/keploy/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100755 index 7fdf2b3..0000000 --- a/keploy/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,70 +0,0 @@ -## Describe the changes that are made -- - -## Links & References - -**Closes:** #[issue number that will be closed through this PR] -- NA (if very small change like typo, linting, etc.) - -### 🔗 Related PRs -- NA -### 🐞 Related Issues -- NA -### 📄 Related Documents -- NA - -## What type of PR is this? (check all applicable) -- [ ] 📦 Chore -- [ ] 🍕 Feature -- [ ] 🐞 Bug Fix -- [ ] 📝 Documentation Update -- [ ] 🎨 Style -- [ ] 🧑‍💻 Code Refactor -- [ ] 🔥 Performance Improvements -- [ ] ✅ Test -- [ ] 🔁 CI -- [ ] ⏩ Revert - -## Added e2e test pipeline? -- [ ] 👍 yes -- [ ] 🙅 no, because they aren't needed -- [ ] 🙋 no, because I need help - -## Added comments for hard-to-understand areas? -- [ ] 👍 yes -- [ ] 🙅 no, because the code is self-explanatory - -## Added to documentation? -- [ ] 📜 README.md -- [ ] 📓 Wiki -- [ ] 🙅 no documentation needed - -## Are there any sample code or steps to test the changes? -- [ ] 👍 yes, mentioned below -- [ ] 🙅 no, because it is not needed - -## Self Review done? -- [ ] ✅ yes -- [ ] ❌ no, because I need help - -## Any relevant screenshots, recordings or logs? -- NA - -## 🧠 Semantics for PR Title & Branch Name - -Please ensure your PR title and branch name follow the Keploy semantics: - -📌 [PR Semantics Guide](https://github.com/keploy/keploy/wiki/PR-Semantics) -📌 [Branch Semantics Guide](https://github.com/keploy/keploy/wiki/Branch-Semantics) - -**Examples:** - -- **PR Title**: `fix: patch MongoDB document update bug` -- **Branch Name**: `feat/#1-login-flow` (You may skip mentioning the issue number in the branch name if the change is small and the PR description clearly explains it.) - ---- - -## Additional checklist: -- [ ] Have you read the [Contributing Guidelines on issues](https://keploy.io/docs/keploy-explained/contribution-guide/)? -- [ ] Have you followed the [PR Semantics guide](https://github.com/keploy/keploy/wiki/PR-Semantics) for naming this PR? -- [ ] Have you followed the [Branch Semantics guide](https://github.com/keploy/keploy/wiki/Branch-Semantics) for naming your branch? \ No newline at end of file diff --git a/keploy/.github/actions/download-binary/action.yml b/keploy/.github/actions/download-binary/action.yml deleted file mode 100644 index 9ec0942..0000000 --- a/keploy/.github/actions/download-binary/action.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: get-binary -description: "Download build or latest Keploy and expose its absolute path" - -inputs: - src: - required: true - description: "build | latest" - -outputs: - path: - description: "binary path" - value: ${{ steps.set-path.outputs.path }} - -runs: - using: composite - steps: - - uses: actions/download-artifact@v4 - with: - name: ${{ inputs.src }} - path: ${{ inputs.src }} - - - name: Set binary path - id: set-path - shell: bash - run: | - ls -l - chmod +x ${{ inputs.src }}/keploy - echo "path=$PWD/${{ inputs.src }}/keploy" >> $GITHUB_OUTPUT diff --git a/keploy/.github/actions/download-image/action.yml b/keploy/.github/actions/download-image/action.yml deleted file mode 100644 index bccc056..0000000 --- a/keploy/.github/actions/download-image/action.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: pull-image -description: "Pull Image" -runs: - using: composite - steps: - - name: Pull Image - shell: bash - run: | - docker pull ttl.sh/keploy/keploy:1h - # since keploy expects the image to be tagged as ghcr.io/keploy/keploy:v2-dev so we need to rename it - # https://stackoverflow.com/questions/70531871/how-pull-a-docker-image-and-give-it-a-diferente-name - docker tag ttl.sh/keploy/keploy:1h ghcr.io/keploy/keploy:v2-dev \ No newline at end of file diff --git a/keploy/.github/actions/tester/action.yml b/keploy/.github/actions/tester/action.yml deleted file mode 100644 index 2ae1353..0000000 --- a/keploy/.github/actions/tester/action.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: 'Keploy test Keploy' -description: "An action to test keploy with keploy" -author: gouravkrosx -branding: - icon: 'refresh-cw' - color: 'orange' - -inputs: - working-directory: - description: Relative path where your application code is located - required: true - command: - description: Command to run the application - required: true - keploy-path: - description: Path to keploy pre-recorded test cases - required: true - default: . - config-path: - description: Path to the config file - required: true - default: . - delay: - description: Time to start application - required: true - default: 10 - keploy-record-bin: - description: Path to keploy record binary (Build or Released) - required: true - keploy-test-bin: - description: Path to keploy test binary (Build or Released) - required: true - mode: - description: Mode to identify the workflow type - required: true - -runs: - using: "composite" - steps: - - name: Print the input data. - run: | - echo "Working Directory: ${{ inputs.working-directory }}" - echo "Command: ${{ inputs.command }}" - echo "Keploy Path: ${{ inputs.keploy-path }}" - echo "Config Path: ${{ inputs.config-path }}" - echo "Delay: ${{ inputs.delay }}" - echo "Keploy Record Binary: ${{ inputs.keploy-record-bin }}" - echo "Keploy Test Binary: ${{ inputs.keploy-test-bin }}" - echo "Mode: ${{ inputs.mode }}" - shell: bash - - - name: Grant permissions - run: chmod +x ${GITHUB_ACTION_PATH}/install.sh - shell: sh - - id: keploy-test-keploy - name: Run Script - run: | - ${GITHUB_ACTION_PATH}/install.sh - - shell: bash - env: - WORKDIR: ${{ inputs.working-directory }} - COMMAND : ${{ inputs.command }} - KEPLOY_PATH: ${{inputs.keploy-path}} - CONFIG_PATH: ${{inputs.config-path}} - DELAY: ${{ inputs.delay }} - KEPLOY_RECORD_BIN: ${{ inputs.keploy-record-bin }} - KEPLOY_TEST_BIN: ${{ inputs.keploy-test-bin }} - MODE: ${{ inputs.mode }} - - - name: Get coverage data and zip it - if: success() - id: coverage-zip # Unique ID for the step - run: | - echo "Output from script: ${{ steps.keploy-test-keploy.outputs.script_output }}" - workdir="${{ inputs.working-directory }}/" - workdir="${workdir//\//-}" - workdir="${workdir%-}" - - coverage="coverage-reports-${workdir}-${{ inputs.mode }}" - mv ${{ inputs.working-directory }}/coverage-reports ./$coverage - echo "coverage=$coverage" - echo "::set-output name=coverage_name::$coverage" - zip -r ${coverage}.zip $coverage - shell: bash - - - name: Upload coverage zip as artifact - if: steps.coverage-zip.outputs.coverage_name != '' - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.coverage-zip.outputs.coverage_name }} - path: ${{ steps.coverage-zip.outputs.coverage_name }}.zip - - # - name: Comment on PR - # if: success() - # uses: actions/github-script@v6 - # env: - # KEPLOY_REPORT: ${{ steps.keploy-test-report.outputs.KEPLOY_REPORT }} - # with: - # github-token: ${{ github.token }} - # script: | - # if (!process.env.KEPLOY_REPORT) { - # console.error('Error: KEPLOY_REPORT not found.'); - # process.exit(1); - # } - # github.rest.issues.createComment({ - # issue_number: context.issue.number, - # owner: context.repo.owner, - # repo: context.repo.repo, - # body: process.env.KEPLOY_REPORT - # }) - # shell: bash \ No newline at end of file diff --git a/keploy/.github/actions/tester/install.sh b/keploy/.github/actions/tester/install.sh deleted file mode 100644 index 5bff962..0000000 --- a/keploy/.github/actions/tester/install.sh +++ /dev/null @@ -1,172 +0,0 @@ -#Print the current github workspace -echo Github_workspace "${GITHUB_WORKSPACE}" - -# Add fake installation-id for the workflow. -source ${GITHUB_WORKSPACE}/.github/workflows/test_workflow_scripts/test-iid.sh - -delete_if_exists() { - local path=$1 - if [ -e "$path" ]; then - sudo rm -rf "$path" - fi -} - - -check_test_status() { - local path=$1 - local fixed_index=$2 # Boolean to determine if index should be fixed to 0 - local overallStatus=1 # true - local idx=0 # Initialize index - for dir in $test_sets; do - if [ "$fixed_index" -eq 1 ]; then - local report_file="$path/keploy/reports/test-run-0/$dir-report.yaml" - else - local report_file="$path/keploy/reports/test-run-$idx/$dir-report.yaml" - idx=$((idx + 1)) - fi - - local test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') - - if [ "$test_status" != "PASSED" ]; then - overallStatus=0 # false - fi - done - echo $overallStatus -} - - -# Go to the working directory -echo "Working directory: ${WORKDIR}" -cd "${WORKDIR}" - -# Make a directory to dump coverage -mkdir coverage-reports - -# Set GOCOVERDIR to the coverage directory -export GOCOVERDIR="./coverage-reports" - -#### Recording Phase of test-bench #### -pre_rec="${KEPLOY_PATH}" - -# Delete the reports directory if it exists -delete_if_exists "$pre_rec/keploy/reports" - -# Get all directories except the 'reports' directory and maintain order -test_sets=$(find "$pre_rec/keploy/" -mindepth 1 -maxdepth 1 -type d ! -name "reports" | sort | xargs -n 1 basename) - - -# Check the exit status of the find command -if [ $? -ne 0 ]; then - echo "Error: No such file or directory." - echo "::set-output name=script_output::failure" - exit 1 -fi - -# Count the number of directories found -num_test_sets=$(echo "$test_sets" | wc -l) - -# Check if the number of directories is zero -if [ "$num_test_sets" -eq 0 ]; then - echo "No test sets found." - echo "::set-output name=script_output::failure" - exit 1 -fi - -# Loop over each directory stored in 'test_sets' -for dir in $test_sets; do - echo "Recording and replaying for (test-set): $dir" -# MODE (0, recordHosted,testBuild) , (1, recordBuild, testHosted) - if [ "$MODE" -eq 0 ]; then - echo "Latest version of keploy is being used for recording, Build version of keploy is being used for testing" - else - echo "Build version of keploy is being used for recording, Latest version of keploy is being used for testing" - fi - - sudo -E env PATH=$PATH ${KEPLOY_RECORD_BIN} record -c "sudo -E env PATH=$PATH ${KEPLOY_TEST_BIN} test -c '${COMMAND}' --proxyPort 56789 --dnsPort 46789 --delay=${DELAY} --testsets $dir --configPath '${CONFIG_PATH}' --path '$pre_rec' --enableTesting " --path "./test-bench/" --proxyPort=36789 --dnsPort 26789 --configPath "${CONFIG_PATH}" --enableTesting - # Wait for 1 second before new test-set - sleep 1 -done - -sleep 5 - -# Check whether the original tests passed or failed -overallStatus=$(check_test_status "$pre_rec" 0) -echo "Overall TestRun status for pre-recorded testscase ran via test-bench: $overallStatus" -if [ "$overallStatus" -eq 0 ]; then - echo "Pre-recorded testcases failed. Exiting..." - delete_if_exists "$pre_rec/keploy/reports" - echo "::set-output name=script_output::failure" - exit 1 -fi - -echo "Successfully recorded tests and mocks via test-bench 🎉" - -#### Testing Phase of test-bench #### -test_bench_rec="./test-bench" - -## Test assertion -pilot -test-assert -preRecPath $pre_rec -testBenchPath $test_bench_rec -exit_status=$? -echo "Test assertion exit status: $exit_status" -if [ $exit_status -eq 1 ]; then - echo "Test assertion failed with exit status $exit_status." - echo "::set-output name=script_output::failure" - exit 1 -fi - -echo "Tests are asserted successfully 🎉" - - -## Mock assertion preparation - -pilot -mock-assert -preRecPath $pre_rec -testBenchPath $test_bench_rec -exit_status=$? -echo "Mock assertion preparation exit status: $exit_status" -if [ $exit_status -eq 1 ]; then - echo "Mock assertion preparation failed with exit status $exit_status." - echo "::set-output name=script_output::failure" - exit 1 -fi - -echo "Mock assertion prepared successfully 🎉" - -## Now run the tests both for pre-recorded test cases and test-bench recorded test cases to compare the mocks (mock assertion) -delete_if_exists "$pre_rec/keploy/reports" - -## Run tests for pre-recorded test cases -sudo -E env PATH=$PATH keployR test -c "${COMMAND}" --delay ${DELAY} --path "$pre_rec" - -sleep 5 - -overallStatus=$(check_test_status "$pre_rec" 1) -echo "Overall TestRun status for pre-recorded testscase (after mock assertion): $overallStatus" -if [ "$overallStatus" -eq 0 ]; then - echo "Newly recorded mocks are not consistent with the pre-recorded mocks." - echo "::set-output name=script_output::failure" - exit 1 -fi -echo "New mocks are consistent with the pre-recorded mocks 🎉" - - -## Run tests for test-bench-recorded test cases -sudo -E env PATH=$PATH keployR test -c "${COMMAND}" --delay ${DELAY} --path "$test_bench_rec" - -sleep 5 - -overallStatus=$(check_test_status "$test_bench_rec" 1) -echo "Overall TestRun status for test-bench-recorded testscase (after mock assertion): $overallStatus" -if [ "$overallStatus" -eq 0 ]; then - echo "Old recorded mocks are not consistent with the test-bench-recorded mocks." - delete_if_exists "$test_bench_rec" - echo "::set-output name=script_output::failure" - exit 1 -fi -echo "Old mocks are consistent with the test-bench-recorded mocks 🎉" - -# Delete the tests and mocks generated via test-bench. -delete_if_exists "$test_bench_rec" - -echo "Tests and mocks are consistent for this application 🎉" -echo "::set-output name=script_output::success" - -exit 0 \ No newline at end of file diff --git a/keploy/.github/docs.svg b/keploy/.github/docs.svg deleted file mode 100755 index 79e1deb..0000000 --- a/keploy/.github/docs.svg +++ /dev/null @@ -1,21 +0,0 @@ - - 📖: docs - - - - - - - - - - - - - - - 📖 - - docs - - \ No newline at end of file diff --git a/keploy/.github/slack.svg b/keploy/.github/slack.svg deleted file mode 100755 index 6cb641a..0000000 --- a/keploy/.github/slack.svg +++ /dev/null @@ -1,20 +0,0 @@ - - slack - - - - - - - - - - - - - \ No newline at end of file diff --git a/keploy/.github/workflows/bug.yml b/keploy/.github/workflows/bug.yml deleted file mode 100755 index d187639..0000000 --- a/keploy/.github/workflows/bug.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Issue labeler -on: - issues: - types: [ opened ] - -permissions: - contents: read - -jobs: - label-component: - runs-on: ubuntu-latest - - permissions: - # required for all workflows - issues: write - - # only required for workflows in private repositories - actions: read - contents: read - - steps: - - uses: actions/checkout@v4 - - - name: Parse issue form - uses: stefanbuck/github-issue-parser@v3 - id: issue-parser - with: - template-path: .github/ISSUE_TEMPLATE/--bug-report.yaml - - - name: Set labels based on repository field - uses: redhat-plumbers-in-action/advanced-issue-labeler@v3 - with: - issue-form: ${{ steps.issue-parser.outputs.jsonString }} - section: repository - block-list: | - None - Other - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/keploy/.github/workflows/cla.yml b/keploy/.github/workflows/cla.yml deleted file mode 100755 index f734d9b..0000000 --- a/keploy/.github/workflows/cla.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: "CLA Assistant" - -on: - issue_comment: - types: - - "created" - pull_request_target: - types: - - "opened" - - "closed" - - "synchronize" - -jobs: - cla-assistant: - runs-on: "ubuntu-latest" - steps: - - uses: actions/checkout@v4 - - - name: "CLA Assistant" - if: "(github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'" - uses: "cla-assistant/github-action@v2.1.3-beta" - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - PERSONAL_ACCESS_TOKEN: "${{ secrets.PRO_ACCESS_TOKEN }}" - with: - remote-organization-name: "keploy" - remote-repository-name: "keploy" - path-to-signatures: "etc/cla-signatures/signatures.json" - path-to-document: "https://github.com/keploy/keploy/tree/main/.github/CLA.md" - branch: "cla-signatures" - allowlist: "keploy-bot,renovate" - - - name: "Post Failure Instructions" - if: failure() && github.event_name == 'pull_request_target' - run: | - gh pr comment ${{ github.event.pull_request.number }} --body "**The CLA check failed.** Please ensure you have: - - Signed the CLA by commenting **'I have read the CLA Document and I hereby sign the CLA.'** - - Used the correct email address in your commits (matches the one you used to sign the CLA). - - After fixing these issues, comment **'recheck'** to trigger the workflow again." - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/keploy/.github/workflows/codeql.yml b/keploy/.github/workflows/codeql.yml deleted file mode 100755 index 6ae28be..0000000 --- a/keploy/.github/workflows/codeql.yml +++ /dev/null @@ -1,84 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ main ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ main ] - schedule: - - cron: '34 8 * * 2' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - - - name: Create a folder for security reports - run: mkdir -p reports - - # - name: Generate Security Report - # uses: peter-murray/github-security-report-action@v2 - # with: - # token: ${{ secrets.CODEQL_EXPORT_PAT_TOKEN }} - # outputDir: ./reports - # - name: Upload artifact - # uses: actions/upload-artifact@v3 - # with: - # name: security-report - # path: /home/runner/work/keploy/results diff --git a/keploy/.github/workflows/coverage_stage.yml b/keploy/.github/workflows/coverage_stage.yml deleted file mode 100644 index 53c7495..0000000 --- a/keploy/.github/workflows/coverage_stage.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Calculate Coverage -on: - workflow_call: - -jobs: - coverage: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Create directories to store coverage data - run: | - mkdir -p unzipped-cov - mkdir -p final-cov - - - name: Download first coverage data of gin-mongo application - uses: actions/download-artifact@v4 - with: - name: coverage-reports-samples-go-gin-mongo-0 - path: ./temp-coverage-dir - - - name: Download second coverage data of gin-mongo application - uses: actions/download-artifact@v4 - with: - name: coverage-reports-samples-go-gin-mongo-1 - path: ./temp-coverage-dir - - - name: Unzip all coverage data zip files - run: | - unzip temp-coverage-dir/coverage-reports-samples-go-gin-mongo-0.zip -d unzipped-cov - unzip temp-coverage-dir/coverage-reports-samples-go-gin-mongo-1.zip -d unzipped-cov - - - name: Combine all coverage data - run: | - find unzipped-cov -mindepth 1 -maxdepth 1 -type d -exec echo {} \; >> cov-dirs.txt - go tool covdata merge -o final-cov -i=`cat cov-dirs.txt | paste -sd ","` - - - name: Calculate total coverage data and remove generated files - run: | - go tool covdata textfmt -i="./final-cov" -o="./unfiltered-cov.txt" - grep -v "go.keploy.io/server/v2/pkg/graph/generated.go" unfiltered-cov.txt > total-coverage.txt - - - name: Display the coverage data - run: | - go tool cover -func total-coverage.txt - - # - name: Generate the HTML coverage report - # run: | - # go tool cover -html=total-coverage.txt -o=coverage.html - - # - name: Upload the HTML coverage report - # id: artifact-upload-step - # uses: actions/upload-artifact@v4 - # with: - # name: coverage-report-html - # path: coverage.html diff --git a/keploy/.github/workflows/docker-publish.yml b/keploy/.github/workflows/docker-publish.yml deleted file mode 100755 index 1049ebb..0000000 --- a/keploy/.github/workflows/docker-publish.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Docker - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -on: - push: - tags: [ 'v*.*.*' ] - -env: - # Use docker.io for Docker Hub if empty - REGISTRY: ghcr.io - # github.repository as / - IMAGE_NAME: ${{ github.repository }} - - -jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Install the cosign tool except on PR - # https://github.com/sigstore/cosign-installer - - name: Install cosign - if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@v3.4.0 - - - # Workaround: https://github.com/docker/build-push-action/issues/461 - - name: Setup Docker buildx - uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf - - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - - name: Log into registry ${{ env.REGISTRY }} - if: github.event_name != 'pull_request' - uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Set version - shell: bash - run: | - echo "VERSION=$(echo ${GITHUB_REF#refs/tags/v})" >> $GITHUB_ENV - - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc - with: - context: . - platforms: linux/amd64, linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - SENTRY_DSN_DOCKER=${{secrets.SENTRY_DSN_DOCKER}} - VERSION=${{ env.VERSION }} - SERVER_URL=https://api.keploy.io - GITTHUB_APP_CLIENT_ID=${{ secrets.CLIENT_ID_GITHUB_APP }} - - # Sign the resulting Docker image digest except on PRs. - # This will only write to the public Rekor transparency log when the Docker - # repository is public to avoid leaking data. If you would like to publish - # transparency data even for private images, pass --force to cosign below. - # https://github.com/sigstore/cosign - - name: Sign the published Docker image - if: ${{ github.event_name != 'pull_request' }} - env: - COSIGN_EXPERIMENTAL: "true" - # This step uses the identity token to provision an ephemeral certificate - # against the sigstore community Fulcio instance. - run: cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} diff --git a/keploy/.github/workflows/docs.yml b/keploy/.github/workflows/docs.yml deleted file mode 100755 index 08304de..0000000 --- a/keploy/.github/workflows/docs.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Issue labeler -on: - issues: - types: [ opened ] - -permissions: - contents: read - -jobs: - label-component: - runs-on: ubuntu-latest - - permissions: - # required for all workflows - issues: write - - # only required for workflows in private repositories - actions: read - contents: read - - steps: - - uses: actions/checkout@v4 - - - name: Parse issue form - uses: stefanbuck/github-issue-parser@v3 - id: issue-parser - with: - template-path: .github/ISSUE_TEMPLATE/--documentation-tools.yaml - - - name: Set labels based on repository field - uses: redhat-plumbers-in-action/advanced-issue-labeler@v3 - with: - issue-form: ${{ steps.issue-parser.outputs.jsonString }} - section: repository - block-list: | - None - Other - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/keploy/.github/workflows/feat.yml b/keploy/.github/workflows/feat.yml deleted file mode 100755 index e61053b..0000000 --- a/keploy/.github/workflows/feat.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Issue labeler -on: - issues: - types: [ opened ] - -permissions: - contents: read - -jobs: - label-component: - runs-on: ubuntu-latest - - permissions: - # required for all workflows - issues: write - - # only required for workflows in private repositories - actions: read - contents: read - - steps: - - uses: actions/checkout@v4 - - - name: Parse issue form - uses: stefanbuck/github-issue-parser@v3 - id: issue-parser - with: - template-path: .github/ISSUE_TEMPLATE/--feature-request.yaml - - - name: Set labels based on repository field - uses: redhat-plumbers-in-action/advanced-issue-labeler@v3 - with: - issue-form: ${{ steps.issue-parser.outputs.jsonString }} - section: repository - block-list: | - None - Other - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/keploy/.github/workflows/go.yml b/keploy/.github/workflows/go.yml deleted file mode 100755 index c0f0983..0000000 --- a/keploy/.github/workflows/go.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Go on Linux - -on: - pull_request: - branches: [ main ] - -jobs: - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ">=1.23" - - - name: Build - run: go build -v ./... - - - name: Build arm - run: GOOS=linux GOARCH=arm64 go build -v ./... - - - uses: codfish/semantic-release-action@v3 - with: - dry-run: true - additional-packages: '["@semantic-release/exec@7.0.3"]' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/keploy/.github/workflows/go_macos.yaml b/keploy/.github/workflows/go_macos.yaml deleted file mode 100644 index 77d6885..0000000 --- a/keploy/.github/workflows/go_macos.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: Go on macOS - -on: - pull_request: - branches: [ main ] - -jobs: - - build: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ">=1.23" - - - name: Build amd64 - run: GOARCH=amd64 go build -v ./... - - - name: Build arm64 - run: GOARCH=arm64 go build -v ./... - diff --git a/keploy/.github/workflows/go_windows.yml b/keploy/.github/workflows/go_windows.yml deleted file mode 100644 index 48a03c9..0000000 --- a/keploy/.github/workflows/go_windows.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Go on Windows - -on: - pull_request: - branches: [ main ] - -jobs: - - build: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ">=1.23" - - - name: Build amd64 - run: | - set GOARCH=amd64 - go build -v ./... - shell: cmd - - - name: Build arm64 - run: | - set GOARCH=arm64 - go build -v ./... - shell: cmd - diff --git a/keploy/.github/workflows/golang_docker-compose.yml b/keploy/.github/workflows/golang_docker-compose.yml deleted file mode 100644 index ffe386d..0000000 --- a/keploy/.github/workflows/golang_docker-compose.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Golang On Docker Compose -on: - workflow_call: -jobs: - golang_docker_compose: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: image - uses: ./.github/actions/download-image - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - - name: Checkout the samples-go repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-go - path: samples-go - - name: Run echo-sql application - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd samples-go/echo-sql - source ./../../.github/workflows/test_workflow_scripts/golang-docker-compose.sh diff --git a/keploy/.github/workflows/golang_docker.yml b/keploy/.github/workflows/golang_docker.yml deleted file mode 100755 index b8e31b7..0000000 --- a/keploy/.github/workflows/golang_docker.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Golang On Docker -on: - workflow_call: -jobs: - golang_docker: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: image - uses: ./.github/actions/download-image - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - - name: Checkout the samples-go repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-go - path: samples-go - - name: Run gin-mongo application - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd samples-go/gin-mongo - source ./../../.github/workflows/test_workflow_scripts/golang-docker.sh diff --git a/keploy/.github/workflows/golang_http_linux.yml b/keploy/.github/workflows/golang_http_linux.yml deleted file mode 100644 index 322d40a..0000000 --- a/keploy/.github/workflows/golang_http_linux.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Golang(http) On Linux -on: - workflow_call: -jobs: - http_golang_linux: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - - name: Checkout the samples-go repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-go - path: samples-go - - - name: echo record and replay binary - run: | - echo "Record binary path: ${{ steps.record.outputs.path }}" - echo "Replay binary path: ${{ steps.replay.outputs.path }}" - - - name: Run go-http application - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd samples-go/http-pokeapi - chmod +x ./../../.github/workflows/test_workflow_scripts/golang-http-linux.sh - source ./../../.github/workflows/test_workflow_scripts/golang-http-linux.sh diff --git a/keploy/.github/workflows/golang_linux.yml b/keploy/.github/workflows/golang_linux.yml deleted file mode 100644 index c34dc28..0000000 --- a/keploy/.github/workflows/golang_linux.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Golang On Linux -on: - workflow_call: -jobs: - golang_linux: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - - name: Checkout the samples-go repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-go - path: samples-go - - name: Run samples-go application - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd samples-go/gin-mongo - source ./../../.github/workflows/test_workflow_scripts/golang-linux.sh diff --git a/keploy/.github/workflows/golang_mysql_linux.yml b/keploy/.github/workflows/golang_mysql_linux.yml deleted file mode 100644 index 2568400..0000000 --- a/keploy/.github/workflows/golang_mysql_linux.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: MySQL-Golang On Linux -on: - workflow_call: -jobs: - mysql_golang_linux: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - - name: Checkout the samples-go repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-go - path: samples-go - - name: Run echo-mysql application - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd samples-go/echo-mysql - source ./../../.github/workflows/test_workflow_scripts/golang-mysql-linux.sh \ No newline at end of file diff --git a/keploy/.github/workflows/golangci-lint.yml b/keploy/.github/workflows/golangci-lint.yml deleted file mode 100644 index 9d94af0..0000000 --- a/keploy/.github/workflows/golangci-lint.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: golangci-lint -on: - push: - branches: - - master - - main - pull_request: - -permissions: - contents: read - # Optional: allow read access to pull request. Use with `only-new-issues` option. - pull-requests: read - -# cancel the in-progress workflow when PR is refreshed. -concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} - cancel-in-progress: true - -jobs: - golangci: - name: lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-go@v5 - with: - go-version: ">=1.23" - cache: false - - - name: golangci-lint - uses: golangci/golangci-lint-action@v7 - with: - only-new-issues: true - args: --timeout=5m - - - name: Check gofmt (diff with line numbers) - run: | - output=$(gofmt -l -d .) - if [ -n "$output" ]; then - echo "🚨 gofmt found issues:" - echo "$output" - exit 1 - fi - - - name: Check for Deprecated Dependencies - run: | - source ./.github/workflows/test_workflow_scripts/check-deprecated-deps.sh \ No newline at end of file diff --git a/keploy/.github/workflows/greetings.yml b/keploy/.github/workflows/greetings.yml deleted file mode 100755 index d1bcbc2..0000000 --- a/keploy/.github/workflows/greetings.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Greetings - -on: [pull_request_target, issues] - -jobs: - greeting: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: actions/first-interaction@v1 - continue-on-error: true - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: 'Thank you and congratulations 🎉 for opening your very first issue in keploy' - pr-message: 'Thank you and congratulations 🎉 for opening your very first pull request in keploy' diff --git a/keploy/.github/workflows/java_linux.yml b/keploy/.github/workflows/java_linux.yml deleted file mode 100644 index 604b3c3..0000000 --- a/keploy/.github/workflows/java_linux.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Java on Linux -on: - workflow_call: -jobs: - java_linux: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - - name: Checkout samples-java repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-java - path: samples-java - - - name: Installing the necessary dependencies - run: | - cd samples-java/spring-petclinic/spring-petclinic-rest - ./mvnw dependency:resolve - - - name: Compile the project - run: | - cd samples-java/spring-petclinic/spring-petclinic-rest - source ./../../../.github/workflows/test_workflow_scripts/update-java.sh - ./mvnw compile - - - name: Run the spring-petclinic-rest app - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd samples-java/spring-petclinic/spring-petclinic-rest - source ./../../../.github/workflows/test_workflow_scripts/java-linux.sh diff --git a/keploy/.github/workflows/main.yml b/keploy/.github/workflows/main.yml deleted file mode 100755 index 50cc354..0000000 --- a/keploy/.github/workflows/main.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Go - -on: - push: - branches: [ main ] - -jobs: - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ">=1.23" - - - name: Build - run: | - go build -v ./... - - - name: Build arm - run: GOOS=linux GOARCH=arm64 go build -v ./... diff --git a/keploy/.github/workflows/node_docker.yml b/keploy/.github/workflows/node_docker.yml deleted file mode 100755 index e523661..0000000 --- a/keploy/.github/workflows/node_docker.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Node on Docker -on: - workflow_call: -jobs: - node_docker: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: image - uses: ./.github/actions/download-image - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - - name: Checkout samples-typescript repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-typescript - path: samples-typescript - - - name: Run the express-mongoose app - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd samples-typescript/express-mongoose - source ./../../.github/workflows/test_workflow_scripts/node-docker.sh diff --git a/keploy/.github/workflows/node_encoding.yaml b/keploy/.github/workflows/node_encoding.yaml deleted file mode 100644 index 960dbb6..0000000 --- a/keploy/.github/workflows/node_encoding.yaml +++ /dev/null @@ -1,48 +0,0 @@ -name: Node Encoding Workflow -description: "Run the Node Encoding Sample application with Keploy" -on: - workflow_call: -jobs: - node_encoding: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - - name: Checkout Encoding sample repository - uses: actions/checkout@v4 - with: - repository: ayush3160/http-br - path: http-br - - - name: Run the Encoding Sample application - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd http-br - source ./../.github/workflows/test_workflow_scripts/node-encoding.sh diff --git a/keploy/.github/workflows/node_linux.yml b/keploy/.github/workflows/node_linux.yml deleted file mode 100644 index 5a9194a..0000000 --- a/keploy/.github/workflows/node_linux.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Node on Linux -on: - workflow_call: -jobs: - node_linux: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - - name: Checkout samples-typescript repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-typescript - path: samples-typescript - - - name: Run the express-mongoose app - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd samples-typescript/express-mongoose - source ./../../.github/workflows/test_workflow_scripts/node-linux.sh diff --git a/keploy/.github/workflows/prepare_and_run.yml b/keploy/.github/workflows/prepare_and_run.yml deleted file mode 100644 index 31b9d51..0000000 --- a/keploy/.github/workflows/prepare_and_run.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Prepare Binary and Run Workflows -on: - pull_request: - branches: [main] -jobs: - build-and-upload: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build Keploy (PR) - run: go build -race -tags=viper_bind_struct -o keploy - - uses: actions/upload-artifact@v4 - with: { name: build, path: keploy } - - upload-latest: - runs-on: ubuntu-latest - steps: - - name: Upload latest release - run: | - LATEST=$(curl -s https://api.github.com/repos/keploy/keploy/releases/latest | jq -r .tag_name) - URL="https://github.com/keploy/keploy/releases/download/${LATEST}/keploy_linux_amd64.tar.gz" - curl -L "$URL" -o keploy.tar.gz - tar -xzf keploy.tar.gz - chmod +x keploy - - uses: actions/upload-artifact@v4 - with: { name: latest, path: keploy } - - build-docker-image: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build image - run: | - source ./.github/workflows/test_workflow_scripts/update-docker.sh - - - name: Push image - run: | - docker push ttl.sh/keploy/keploy:1h - - run_golang_linux: - needs: [build-and-upload, upload-latest] - uses: ./.github/workflows/golang_linux.yml - run_golang_http_linux: - needs: [build-and-upload, upload-latest] - uses: ./.github/workflows/golang_http_linux.yml - run_golang_mysql_linux: - needs: [build-and-upload, upload-latest] - uses: ./.github/workflows/golang_mysql_linux.yml - run_golang_docker: - needs: [build-and-upload, upload-latest,build-docker-image] - uses: ./.github/workflows/golang_docker.yml - run_golang_docker-compose: - needs: [build-and-upload, upload-latest, build-docker-image] - uses: ./.github/workflows/golang_docker-compose.yml - run_java_linux: - needs: [build-and-upload, upload-latest] - uses: ./.github/workflows/java_linux.yml - run_node_linux: - needs: [build-and-upload, upload-latest] - uses: ./.github/workflows/node_linux.yml - run_node_docker: - needs: [build-and-upload, upload-latest, build-docker-image] - uses: ./.github/workflows/node_docker.yml - run_python_docker: - needs: [build-and-upload, upload-latest, build-docker-image] - uses: ./.github/workflows/python_docker.yml - run_python_linux: - needs: [build-and-upload, upload-latest] - uses: ./.github/workflows/python_linux.yml - run_node_encoding: - needs: [build-and-upload, upload-latest] - uses: ./.github/workflows/node_encoding.yaml \ No newline at end of file diff --git a/keploy/.github/workflows/python_docker.yml b/keploy/.github/workflows/python_docker.yml deleted file mode 100644 index 8cbb85a..0000000 --- a/keploy/.github/workflows/python_docker.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Python On Docker -on: - workflow_call: -jobs: - python_docker: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: image - uses: ./.github/actions/download-image - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - - name: Checkout the samples-python repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-python - path: samples-python - - name: Run the flask-mongo application - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd samples-python/flask-mongo - source ./../../.github/workflows/test_workflow_scripts/python-docker.sh diff --git a/keploy/.github/workflows/python_linux.yml b/keploy/.github/workflows/python_linux.yml deleted file mode 100644 index b9ce0a7..0000000 --- a/keploy/.github/workflows/python_linux.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Python On Linux -on: - workflow_call: -jobs: - python_linux: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - job: record_latest_replay_build - record_src: latest - replay_src: build - - job: record_build_replay_latest - record_src: build - replay_src: latest - - job: record_build_replay_build - record_src: build - replay_src: build - name: ${{ matrix.job }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - id: record - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.record_src }} - - - id: replay - uses: ./.github/actions/download-binary - with: - src: ${{ matrix.replay_src }} - - name: Checkout the samples-python repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-python - path: samples-python - - name: Run the sample python application - env: - RECORD_BIN: ${{ steps.record.outputs.path }} - REPLAY_BIN: ${{ steps.replay.outputs.path }} - run: | - cd samples-python/django-postgres/django_postgres - source ../../../.github/workflows/test_workflow_scripts/python-linux.sh diff --git a/keploy/.github/workflows/release.yml b/keploy/.github/workflows/release.yml deleted file mode 100755 index 374acf9..0000000 --- a/keploy/.github/workflows/release.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: Release -on: - push: - tags: - - 'v*.*.*' - -permissions: - contents: write - -jobs: -# build-ui: -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v4 -# with: -# fetch-depth: 0 -# -# - name: Set up Go -# uses: actions/setup-go@v2 -# with: -# go-version: "1.20" - -# - name: Checkout UI -# uses: actions/checkout@v4 -# with: -# repository: keploy/ui -# path: ui -# -# - name: Cache UI -# id: cache-ui -# uses: actions/cache@v3 -# with: -# path: web -# key: ${{ hashFiles('ui/src') }} -# -# - name: Set up Node -# if: steps.cache-ui.outputs.cache-hit != 'true' -# uses: actions/setup-node@v3 -# with: -# node-version: '14' -# -# - name: Build web app -# if: steps.cache-ui.outputs.cache-hit != 'true' -# run: | -# cd $GITHUB_WORKSPACE/ui -# npm install -# npm run build -# cp -r public $GITHUB_WORKSPACE/web -# rm -rf $GITHUB_WORKSPACE/ui - - build-go: - runs-on: macos-latest - # runs-on: ubuntu-latest -# needs: build-ui - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ">=1.23" - -# - name: Checkout UI -# uses: actions/checkout@v4 -# with: -# repository: keploy/ui -# path: ui -# -# - name: Cache UI -# id: cache-ui -# uses: actions/cache@v3 -# with: -# path: web -# key: ${{ hashFiles('ui/src') }} - - name: Import Code-Signing Certificates - uses: Apple-Actions/import-codesign-certs@v5 - with: - # The certificates in a PKCS12 file encoded as a base64 string created with "openssl base64 -in cert.p12 -out cert-base64.txt" - p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }} - # The password used to import the PKCS12 file. - p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }} - - name: Install gon for code signing and app notarization - run: | - wget -q https://github.com/Bearer/gon/releases/download/v0.0.37/gon_macos.zip - unzip gon_macos.zip -d /usr/local/bin - rm -rf gon_macos.zip - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser - version: v1.26.2 - args: release --rm-dist - env: - AC_PROVIDER: ${{ secrets.APPLE_ACCOUNT_TEAM_ID }} - AC_PASSWORD: ${{ secrets.APPLE_ACCOUNT_PASSWORD }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SENTRY_DSN_BINARY: ${{ secrets.SENTRY_DSN_BINARY }} - GITTHUB_APP_CLIENT_ID: ${{ secrets.CLIENT_ID_GITHUB_APP }} - SERVER_URL: https://api.keploy.io diff --git a/keploy/.github/workflows/sample-run.yml b/keploy/.github/workflows/sample-run.yml deleted file mode 100755 index 870b80e..0000000 --- a/keploy/.github/workflows/sample-run.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: sample-run - -on: - push: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ">=1.23" - - - name: Build Keploy - run: | - go build -v ./... - - - name: Build arm - run: GOOS=linux GOARCH=arm64 go build -v ./... \ No newline at end of file diff --git a/keploy/.github/workflows/test-go-mongo-1.yml b/keploy/.github/workflows/test-go-mongo-1.yml deleted file mode 100644 index 7164ea9..0000000 --- a/keploy/.github/workflows/test-go-mongo-1.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Test GinApp 1 -on: - workflow_call: - -jobs: - test-go-mongo: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Download Keploy Build Binary - uses: actions/download-artifact@v4 - with: - name: keploy-binary - path: /usr/local/bin - - - name: Giving permission to binary - run: | - sudo chmod +x /usr/local/bin/keployB - - - name: Download the latest released binary of keploy - run: | - curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz -C /tmp - sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin/keployR - - # Get the pilot for tests and mocks assertion - - name: Get the pilot for tests and mocks assertion - run: | - curl --silent -o pilot --location "https://github.com/keploy/pilot/releases/latest/download/pilot_linux_amd64" && - sudo chmod a+x pilot && sudo mkdir -p /usr/local/bin && sudo mv pilot /usr/local/bin - - - name: Checkout to the samples-go repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-go - ref: native-linux - path: samples-go - - - name: Build the gin-mongo application - run: | - cd samples-go/gin-mongo - go build -o ginApp . - - # add the noisy fields in the config file if any. - - name: Add noisy fields in the config file - run: | - echo "You can add the noisy fields in the config file if any." - - - name: Run testing script for gin-mongo application - uses: ./.github/actions/tester - with: - working-directory: samples-go/gin-mongo - command: ./ginApp - delay: 7 - keploy-record-bin: /usr/local/bin/keployR - keploy-test-bin: /usr/local/bin/keployB - mode: 0 - - diff --git a/keploy/.github/workflows/test-go-mongo-2.yml b/keploy/.github/workflows/test-go-mongo-2.yml deleted file mode 100644 index 43daa46..0000000 --- a/keploy/.github/workflows/test-go-mongo-2.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Test GinApp 2 -on: - workflow_call: - -jobs: - test-go-mongo: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Download Keploy Build Binary - uses: actions/download-artifact@v4 - with: - name: keploy-binary - path: /usr/local/bin - - - name: Giving permission to binary - run: | - sudo chmod +x /usr/local/bin/keployB - - - name: Download the latest released binary of keploy - run: | - curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz -C /tmp - sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin/keployR - - # Get the pilot for tests and mocks assertion - - name: Get the pilot for tests and mocks assertion - run: | - curl --silent -o pilot --location "https://github.com/keploy/pilot/releases/latest/download/pilot_linux_amd64" && - sudo chmod a+x pilot && sudo mkdir -p /usr/local/bin && sudo mv pilot /usr/local/bin - - - name: Checkout to the samples-go repository - uses: actions/checkout@v4 - with: - repository: keploy/samples-go - ref: native-linux - path: samples-go - - - name: Build the gin-mongo application - run: | - cd samples-go/gin-mongo - go build -o ginApp . - - # add the noisy fields in the config file if any. - - name: Add noisy fields in the config file - run: | - echo "You can add the noisy fields in the config file if any." - - - name: Run testing script for gin-mongo application - uses: ./.github/actions/tester - with: - working-directory: samples-go/gin-mongo - command: ./ginApp - delay: 7 - keploy-record-bin: /usr/local/bin/keployB - keploy-test-bin: /usr/local/bin/keployR - mode: 1 \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/check-deprecated-deps.sh b/keploy/.github/workflows/test_workflow_scripts/check-deprecated-deps.sh deleted file mode 100644 index fbcc414..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/check-deprecated-deps.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# Extract direct dependencies from go.mod using go mod edit -json -direct_deps=$(go mod edit -json | jq -r '.Require[] | select(.Indirect == null) | .Path') - -# List all modules with their update status -output=$(go list -m -u all) - -found_deprecated=false - -while IFS= read -r line; do - mod_path=$(echo "$line" | awk '{print $1}') - - if echo "$direct_deps" | grep -q "$mod_path"; then - if [[ "$line" == *"deprecated"* || "$line" == *"retracted"* ]]; then - echo "Deprecated/retracted direct dependency found: $line" - found_deprecated=true - fi - fi -done <<< "$output" - -if [ "$found_deprecated" = true ]; then - echo "Exiting with failure due to deprecated direct dependencies." - exit 1 -fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/golang-docker-compose.sh b/keploy/.github/workflows/test_workflow_scripts/golang-docker-compose.sh deleted file mode 100644 index a196679..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/golang-docker-compose.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/bash - -source ./../../.github/workflows/test_workflow_scripts/test-iid.sh - -# Build Docker Image -docker compose build - -# Remove any preexisting keploy tests and mocks. -sudo rm -rf keploy/ - -# Generate the keploy-config file. -sudo -E env PATH=$PATH $RECORD_BIN config --generate - -# Update the global noise to ts in the config file. -config_file="./keploy.yml" -sed -i 's/global: {}/global: {"body": {"ts":[]}}/' "$config_file" - -container_kill() { - pid=$(pgrep -n keploy) - echo "$pid Keploy PID" - echo "Killing keploy" - sudo kill $pid -} - -send_request(){ - sleep 10 - app_started=false - while [ "$app_started" = false ]; do - if curl -X GET http://localhost:8082/health; then - app_started=true - fi - sleep 3 - done - echo "App started" - # Make curl calls to record the test cases and mocks. - curl --request POST \ - --url http://localhost:8082/url \ - --header 'content-type: application/json' \ - --data '{ - "url": "https://google.com" - }' - - curl --request POST \ - --url http://localhost:8082/url \ - --header 'content-type: application/json' \ - --data '{ - "url": "https://facebook.com" - }' - - curl -X GET http://localhost:8082/health - - # Wait for 5 seconds for keploy to record the test cases and mocks. - sleep 5 - container_kill - wait -} - -for i in {1..2}; do - container_name="echoApp" - send_request & - sudo -E env PATH=$PATH $RECORD_BIN record -c "docker compose up" --container-name "$container_name" --generateGithubActions=false &> "${container_name}.txt" - - if grep "WARNING: DATA RACE" "${container_name}.txt"; then - echo "Race condition detected in recording, stopping pipeline..." - cat "${container_name}.txt" - exit 1 - fi - if grep "ERROR" "${container_name}.txt"; then - echo "Error found in pipeline..." - cat "${container_name}.txt" - exit 1 - fi - sleep 5 - - echo "Recorded test case and mocks for iteration ${i}" -done - -# Shutdown services before test mode - Keploy should use mocks for dependencies -echo "Shutting down docker compose services before test mode..." -docker compose down -echo "Services stopped - Keploy should now use mocks for dependency interactions" - -# Start keploy in test mode. -test_container="echoApp" -sudo -E env PATH=$PATH $REPLAY_BIN test -c 'docker compose up' --containerName "$test_container" --apiTimeout 60 --delay 20 --generate-github-actions=false &> "${test_container}.txt" - -if grep "ERROR" "${test_container}.txt"; then - echo "Error found in pipeline..." - cat "${test_container}.txt" - exit 1 -fi - -if grep "WARNING: DATA RACE" "${test_container}.txt"; then - echo "Race condition detected in test, stopping pipeline..." - cat "${test_container}.txt" - exit 1 -fi - -all_passed=true - -for i in {0..1} -do - # Define the report file for each test set - report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" - - # Extract the test status - test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') - - # Print the status for debugging - echo "Test status for test-set-$i: $test_status" - - # Check if any test set did not pass - if [ "$test_status" != "PASSED" ]; then - all_passed=false - echo "Test-set-$i did not pass." - break # Exit the loop early as all tests need to pass - fi -done - -# Check the overall test status and exit accordingly -if [ "$all_passed" = true ]; then - echo "All tests passed" - exit 0 -else - cat "${test_container}.txt" - exit 1 -fi diff --git a/keploy/.github/workflows/test_workflow_scripts/golang-docker.sh b/keploy/.github/workflows/test_workflow_scripts/golang-docker.sh deleted file mode 100755 index 5f66285..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/golang-docker.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/bin/bash - -source ./../../.github/workflows/test_workflow_scripts/test-iid.sh - -# Start mongo before starting keploy. -docker network create keploy-network -docker run --name mongoDb --rm --net keploy-network -p 27017:27017 -d mongo - -# Generate the keploy-config file. -sudo -E env PATH=$PATH $RECORD_BIN config --generate - -# Update the global noise to ts. -config_file="./keploy.yml" -sed -i 's/global: {}/global: {"body": {"ts":[]}}/' "$config_file" - -# Remove any preexisting keploy tests and mocks. -sudo rm -rf keploy/ -docker logs mongoDb & - -# Start keploy in record mode. -docker build -t gin-mongo . -docker rm -f ginApp 2>/dev/null || true - -container_kill() { - pid=$(pgrep -n keploy) - echo "$pid Keploy PID" - echo "Killing keploy" - sudo kill $pid -} - -send_request(){ - sleep 10 - app_started=false - while [ "$app_started" = false ]; do - if curl -X GET http://localhost:8080/CJBKJd92; then - app_started=true - fi - sleep 3 - done - echo "App started" - # Start making curl calls to record the testcases and mocks. - curl --request POST \ - --url http://localhost:8080/url \ - --header 'content-type: application/json' \ - --data '{ - "url": "https://google.com" - }' - - curl --request POST \ - --url http://localhost:8080/url \ - --header 'content-type: application/json' \ - --data '{ - "url": "https://facebook.com" - }' - - curl -X GET http://localhost:8080/CJBKJd92 - - # Wait for 5 seconds for keploy to record the tcs and mocks. - sleep 5 - container_kill - wait -} - -for i in {1..2}; do - container_name="ginApp_${i}" - send_request & - sudo -E env PATH=$PATH $RECORD_BIN record -c "docker run -p8080:8080 --net keploy-network --rm --name $container_name gin-mongo" --container-name "$container_name" &> "${container_name}.txt" - - if grep "WARNING: DATA RACE" "${container_name}.txt"; then - echo "Race condition detected in recording, stopping pipeline..." - cat "${container_name}.txt" - exit 1 - fi - if grep "ERROR" "${container_name}.txt"; then - echo "Error found in pipeline..." - cat "${container_name}.txt" - exit 1 - fi - sleep 5 - - echo "Recorded test case and mocks for iteration ${i}" -done - -# Shutdown mongo before test mode - Keploy should use mocks for database interactions -echo "Shutting down mongo before test mode..." -docker stop mongoDb || true -docker rm mongoDb || true -echo "MongoDB stopped - Keploy should now use mocks for database interactions" - -# Start the keploy in test mode. -test_container="ginApp_test" -sudo -E env PATH=$PATH $REPLAY_BIN test -c 'docker run -p8080:8080 --net keploy-network --name ginApp_test gin-mongo' --containerName "$test_container" --apiTimeout 60 --delay 20 --generate-github-actions=false &> "${test_container}.txt" - -if grep "ERROR" "${test_container}.txt"; then - echo "Error found in pipeline..." - cat "${test_container}.txt" - exit 1 -fi - -if grep "WARNING: DATA RACE" "${test_container}.txt"; then - echo "Race condition detected in test, stopping pipeline..." - cat "${test_container}.txt" - exit 1 -fi - -all_passed=true - -for i in {0..1} -do - # Define the report file for each test set - report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" - - # Extract the test status - test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') - - # Print the status for debugging - echo "Test status for test-set-$i: $test_status" - - # Check if any test set did not pass - if [ "$test_status" != "PASSED" ]; then - all_passed=false - echo "Test-set-$i did not pass." - break # Exit the loop early as all tests need to pass - fi -done - -# Check the overall test status and exit accordingly -if [ "$all_passed" = true ]; then - echo "All tests passed" - exit 0 -else - cat "${test_container}.txt" - exit 1 -fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/golang-http-linux.sh b/keploy/.github/workflows/test_workflow_scripts/golang-http-linux.sh deleted file mode 100644 index 668fc02..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/golang-http-linux.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/bin/bash - -echo "$RECORD_BIN" -echo "$REPLAY_BIN" - -source ./../../.github/workflows/test_workflow_scripts/test-iid.sh -echo "iid.sh executed" - -# Checkout a different branch -git fetch origin -#git checkout native-linux - -# Check if there is a keploy-config file, if there is, delete it. -if [ -f "./keploy.yml" ]; then - rm ./keploy.yml -fi - -rm -rf keploy/ - -# Build go binary -go build -o http-pokeapi -echo "go binary built" - -# Generate the keploy-config file. -sudo "$RECORD_BIN" config --generate - -# Update the global noise to updated_at. -config_file="./keploy.yml" -sed -i 's/global: {}/global: {"body": {"updated_at":[]}}/' "$config_file" - -send_request() { - local index=$1 - - sleep 10 - app_started=false - while [ "$app_started" = false ]; do - if curl -X GET http://localhost:8080/api/locations; then - app_started=true - fi - sleep 3 - done - - echo "App started" - - response=$(curl -s -X GET http://localhost:8080/api/locations) - - # Extract any location from the reponse - location=$(echo "$response" | jq -r ".location[$index]") - - response=$(curl -s -X GET http://localhost:8080/api/locations/$location) - - # Extract any pokemon from the response - pokemon=$(echo "$response" | jq -r ".[$index]") - - curl -s -X GET http://localhost:8080/api/pokemon/$pokemon - - curl -s -X GET http://localhost:8080/api/greet - - curl -s -X GET http://localhost:8080/api/greet?format=html - - curl -s -X GET http://localhost:8080/api/greet?format=xml - - # Wait for 10 seconds for Keploy to record the tcs and mocks. - sleep 10 - pid=$(pgrep keploy) - echo "$pid Keploy PID" - echo "Killing Keploy" - sudo kill $pid -} - -for i in {1..2}; do - app_name="http-pokeapi_${i}" - send_request $i & - sudo -E env PATH="$PATH" "$RECORD_BIN" record -c "./http-pokeapi" --generateGithubActions=false &> "${app_name}.txt" - if grep "ERROR" "${app_name}.txt"; then - echo "Error found in pipeline..." - cat "${app_name}.txt" - exit 1 - fi - if grep "WARNING: DATA RACE" "${app_name}.txt"; then - echo "Race condition detected in recording, stopping pipeline..." - cat "${app_name}.txt" - exit 1 - fi - sleep 5 - wait - echo "Recorded test case and mocks for iteration ${i}" -done - -# Start the go-http app in test mode. -sudo -E env PATH="$PATH" "$REPLAY_BIN" test -c "./http-pokeapi" --delay 7 --generateGithubActions=false &> test_logs.txt - -if grep "ERROR" "test_logs.txt"; then - echo "Error found in pipeline..." - cat "test_logs.txt" - exit 1 -fi - -if grep "WARNING: DATA RACE" "test_logs.txt"; then - echo "Race condition detected in test, stopping pipeline..." - cat "test_logs.txt" - exit 1 -fi - -all_passed=true - -# Get the test results from the testReport file. -for i in {0..1} -do - # Define the report file for each test set - report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" - - # Extract the test status - test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') - - # Print the status for debugging - echo "Test status for test-set-$i: $test_status" - - # Check if any test set did not pass - if [ "$test_status" != "PASSED" ]; then - all_passed=false - echo "Test-set-$i did not pass." - break # Exit the loop early as all tests need to pass - fi -done - -# Check the overall test status and exit accordingly -if [ "$all_passed" = true ]; then - echo "All tests passed" - exit 0 -else - cat "test_logs.txt" - exit 1 -fi diff --git a/keploy/.github/workflows/test_workflow_scripts/golang-linux.sh b/keploy/.github/workflows/test_workflow_scripts/golang-linux.sh deleted file mode 100644 index 693d5ab..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/golang-linux.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/bin/bash - -source ./../../.github/workflows/test_workflow_scripts/test-iid.sh - -# Checkout a different branch -git fetch origin -git checkout native-linux - -# Start mongo before starting keploy. -docker run --rm -d -p27017:27017 --name mongoDb mongo - -# Check if there is a keploy-config file, if there is, delete it. -if [ -f "./keploy.yml" ]; then - rm ./keploy.yml -fi - -# Generate the keploy-config file. -sudo $RECORD_BIN config --generate - -# Update the global noise to ts. -config_file="./keploy.yml" -sed -i 's/global: {}/global: {"body": {"ts":[]}}/' "$config_file" - -sed -i 's/ports: 0/ports: 27017/' "$config_file" - -# Remove any preexisting keploy tests and mocks. -rm -rf keploy/ - -# Build the binary. -go build -o ginApp - - -send_request(){ - sleep 10 - app_started=false - while [ "$app_started" = false ]; do - if curl --request POST \ - --url http://localhost:8080/url \ - --header 'content-type: application/json' \ - --data '{ - "url": "https://facebook.com" - }'; then - app_started=true - fi - sleep 3 # wait for 3 seconds before checking again. - done - echo "App started" - # Start making curl calls to record the testcases and mocks. - curl --request POST \ - --url http://localhost:8080/url \ - --header 'content-type: application/json' \ - --data '{ - "url": "https://google.com" - }' - - curl --request POST \ - --url http://localhost:8080/url \ - --header 'content-type: application/json' \ - --data '{ - "url": "https://facebook.com" - }' - - curl -X GET http://localhost:8080/CJBKJd92 - - # Wait for 10 seconds for keploy to record the tcs and mocks. - sleep 10 - pid=$(pgrep keploy) - echo "$pid Keploy PID" - echo "Killing keploy" - sudo kill $pid -} - - -for i in {1..2}; do - app_name="javaApp_${i}" - send_request & - sudo -E env PATH="$PATH" $RECORD_BIN record -c "./ginApp" &> "${app_name}.txt" - if grep "ERROR" "${app_name}.txt"; then - echo "Error found in pipeline..." - cat "${app_name}.txt" - exit 1 - fi - if grep "WARNING: DATA RACE" "${app_name}.txt"; then - echo "Race condition detected in recording, stopping pipeline..." - cat "${app_name}.txt" - exit 1 - fi - sleep 5 - wait - echo "Recorded test case and mocks for iteration ${i}" -done - -# Shutdown mongo before test mode - Keploy should use mocks for database interactions -echo "Shutting down mongo before test mode..." -docker stop mongoDb || true -docker rm mongoDb || true -echo "MongoDB stopped - Keploy should now use mocks for database interactions" - -# Start the gin-mongo app in test mode. -sudo -E env PATH="$PATH" $REPLAY_BIN test -c "./ginApp" --delay 7 &> test_logs.txt - -if grep "ERROR" "test_logs.txt"; then - echo "Error found in pipeline..." - cat "test_logs.txt" - exit 1 -fi - -if grep "WARNING: DATA RACE" "test_logs.txt"; then - echo "Race condition detected in test, stopping pipeline..." - cat "test_logs.txt" - exit 1 -fi - -all_passed=true - - -# Get the test results from the testReport file. -for i in {0..1} -do - # Define the report file for each test set - report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" - - # Extract the test status - test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') - - # Print the status for debugging - echo "Test status for test-set-$i: $test_status" - - # Check if any test set did not pass - if [ "$test_status" != "PASSED" ]; then - all_passed=false - echo "Test-set-$i did not pass." - break # Exit the loop early as all tests need to pass - fi -done - -# Check the overall test status and exit accordingly -if [ "$all_passed" = true ]; then - echo "All tests passed" - exit 0 -else - cat "test_logs.txt" - exit 1 -fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh b/keploy/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh deleted file mode 100644 index f4f85d4..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env bash - -# safer bash, but we’ll locally disable -e around commands we want to inspect -set -Eeuo pipefail - -# ----- helpers ----- -section() { echo "::group::$*"; } -endsec() { echo "::endgroup::"; } -die() { - rc=$? - echo "::error::Pipeline failed (exit=$rc). Dumping context…" - echo "== docker ps =="; docker ps || true - echo "== mysql logs (complete) =="; docker logs mysql-container || true - echo "== workspace tree (depth 3) =="; find . -maxdepth 3 -type d -print | sort || true - echo "== keploy tree (depth 4) =="; find ./keploy -maxdepth 4 -type f -print 2>/dev/null | sort || true - echo "== *.txt logs (complete) =="; for f in ./*.txt; do [[ -f "$f" ]] && { echo "--- $f ---"; cat "$f"; }; done - [[ -f test_logs.txt ]] && { echo "== test_logs.txt (complete) =="; cat test_logs.txt; } - exit "$rc" -} -trap die ERR - -wait_for_mysql() { - section "Wait for MySQL readiness" - # ping until mysqld accepts connections - for i in {1..60}; do - if docker exec mysql-container mysql -uroot -ppassword -e "SELECT 1" >/dev/null 2>&1; then - echo "MySQL is ready." - endsec; return 0 - fi - sleep 1 - done - echo "::error::MySQL did not become ready in time" - endsec; return 1 -} - -send_request() { - local kp_pid="$1" - - # Wait for the app to report healthy - for i in {1..60}; do - if curl -fsS http://localhost:9090/healthcheck >/dev/null; then - echo "good!App started" - break - fi - sleep 1 - done - - curl -sS -X POST http://localhost:9090/shorten -H "Content-Type: application/json" \ - -d '{"url": "https://github.com"}' || true - - curl -sS http://localhost:9090/resolve/4KepjkTT || true - - # Give Keploy a moment to persist artifacts, then stop it cleanly - sleep 10 - echo "$kp_pid Keploy PID" - echo "Killing Keploy" - sudo kill "$kp_pid" 2>/dev/null || true -} - -run_record_iteration() { - local idx="$1" - local app_name="urlShort_${idx}" - - section "Record iteration $idx" - - # Clean slate per run - rm -rf keploy/ keploy.yml || true - - # Start mysql (once) only for first iteration - if ! docker ps --format '{{.Names}}' | grep -q '^mysql-container$'; then - docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=uss \ - -p 3306:3306 --rm -d mysql:latest - wait_for_mysql - fi - - # Generate config - sudo "$RECORD_BIN" config --generate - sed -i 's/global: {}/global: {"body": {"updated_at":[]}}/' ./keploy.yml - - # Build app - go build -o urlShort - - # Start recording in background so we capture its PID explicitly - sudo -E env PATH="$PATH" "$RECORD_BIN" record -c "./urlShort" --generateGithubActions=false \ - > "${app_name}.txt" 2>&1 & - local KEPLOY_PID=$! - - # Drive traffic + stop keploy - send_request "$KEPLOY_PID" - - # Wait for keploy exit and capture code - set +e - wait "$KEPLOY_PID" - local rc=$? - set -e - echo "Record exit code: $rc" - if [[ $rc -ne 0 ]]; then - echo "::error::Keploy record exited with $rc (iteration $idx)" - fi - - # Quick sanity: ensure something was written - echo "== keploy artifacts after record ==" - find ./keploy -maxdepth 3 -type f | sort || true - - # Fail on obvious errors/races in log - if grep -q "WARNING: DATA RACE" "${app_name}.txt"; then - echo "::error::Data race detected in ${app_name}.txt" - cat "${app_name}.txt" - return 1 - fi - if grep -q "ERROR" "${app_name}.txt"; then - echo "::warning::Errors found in ${app_name}.txt (not fatal unless record failed)" - cat "${app_name}.txt" - fi - - endsec -} - -# ----- main flow ----- - -section "Environment" -echo "RECORD_BIN: $RECORD_BIN" -echo "REPLAY_BIN : $REPLAY_BIN" -"$RECORD_BIN" version 2>/dev/null || true -"$REPLAY_BIN" version 2>/dev/null || true -endsec - -for i in 1 2; do - run_record_iteration "$i" - echo "Recorded test case and mocks for iteration $i" -done - -section "Shutdown MySQL before test mode" -# Stop MySQL container - Keploy should use mocks for database interactions -docker stop mysql-container || true -docker rm mysql-container || true -echo "MySQL stopped - Keploy should now use mocks for database interactions" -endsec - -section "Replay" -# Run replay but DON'T crash the step; capture rc and print logs -set +e -sudo -E env PATH="$PATH" "$REPLAY_BIN" test -c "./urlShort" --delay 7 --generateGithubActions=false \ - > test_logs.txt 2>&1 -REPLAY_RC=$? -set -e -echo "Replay exit code: $REPLAY_RC" -cat test_logs.txt || true -endsec - -# If replay failed, still try to read reports to say which set failed -section "Check reports" -# Find the most recent test-run dir (don’t assume test-run-0) -RUN_DIR=$(ls -1dt ./keploy/reports/test-run-* 2>/dev/null | head -n1 || true) -if [[ -z "${RUN_DIR:-}" ]]; then - echo "::error::No test-run directory found under ./keploy/reports" - [[ $REPLAY_RC -ne 0 ]] && exit "$REPLAY_RC" || exit 1 -fi - -echo "Using reports from: $RUN_DIR" -all_passed=true -for rpt in "$RUN_DIR"/test-set-*-report.yaml; do - [[ -f "$rpt" ]] || continue - status=$(awk '/^status:/{print $2; exit}' "$rpt") - echo "Test status for $(basename "$rpt"): ${status:-}" - if [[ "$status" != "PASSED" ]]; then - all_passed=false - fi -done -endsec - -if [[ "$all_passed" == "true" && $REPLAY_RC -eq 0 ]]; then - echo "All tests passed" - exit 0 -fi - -echo "::error::Some tests failed or replay exited non-zero" -exit 1 \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/java-linux.sh b/keploy/.github/workflows/test_workflow_scripts/java-linux.sh deleted file mode 100644 index f3a36ba..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/java-linux.sh +++ /dev/null @@ -1,255 +0,0 @@ -#!/usr/bin/env bash -# Safe, chatty CI script for Java + Postgres + Keploy with auto API-prefix detection - -set -Eeuo pipefail - -section() { echo "::group::$*"; } -endsec() { echo "::endgroup::"; } - -die() { - rc=$? - echo "::error::Pipeline failed (exit=$rc). Dumping context…" - echo "== docker ps =="; docker ps || true - echo "== postgres logs (complete) =="; docker logs mypostgres || true - echo "== workspace tree (depth 3) =="; find . -maxdepth 3 -type d -print | sort || true - echo "== keploy tree (depth 4) =="; find ./keploy -maxdepth 4 -type f -print 2>/dev/null | sort || true - echo "== *.txt logs (complete) =="; for f in ./*.txt; do [[ -f "$f" ]] && { echo "--- $f ---"; cat "$f"; }; done - [[ -f test_logs.txt ]] && { echo "== test_logs.txt (complete) =="; cat test_logs.txt; } - exit "$rc" -} -trap die ERR - -http_code() { - # prints HTTP status code or 000 on error - curl -s -o /dev/null -w "%{http_code}" "$1" 2>/dev/null || echo 000 -} - -wait_for_postgres() { - section "Wait for Postgres readiness" - for i in {1..120}; do - if docker exec mypostgres pg_isready -U petclinic -d petclinic >/dev/null 2>&1; then - echo "Postgres is ready." - endsec; return 0 - fi - # Fallback probe - docker exec mypostgres psql -U petclinic -d petclinic -c "SELECT 1" >/dev/null 2>&1 && { echo "Postgres responded."; endsec; return 0; } - sleep 1 - done - echo "::error::Postgres did not become ready in time" - endsec; return 1 -} - -wait_for_http_port() { - # waits for *any* HTTP response from root or actuator (not strict 200) - local base="http://localhost:9966" - section "Wait for app HTTP port" - for i in {1..180}; do - if curl -sS "${base}/" -o /dev/null || curl -sS "${base}/actuator/health" -o /dev/null; then - echo "HTTP port responded." - endsec; return 0 - fi - sleep 1 - done - echo "::error::App did not open HTTP port on 9966" - endsec; return 1 -} - -detect_api_prefix() { - # returns either /petclinic/api or /api (echo to stdout), otherwise empty - local base="http://localhost:9966" - local candidates=( "/petclinic/api" "/api" ) - for p in "${candidates[@]}"; do - local code - code=$(http_code "${base}${p}/pettypes") - if [[ "$code" =~ ^(200|201|202|204)$ ]]; then - echo "$p"; return 0 - fi - done - # If no 2xx, still check which gives *any* non-404 (e.g., 401/403 if security toggled) - for p in "${candidates[@]}"; do - local code - code=$(http_code "${base}${p}/pettypes") - if [[ "$code" != "404" && "$code" != "000" ]]; then - echo "$p"; return 0 - fi - done - # Fallback to actuator presence: assume /api if actuator exists - if [[ "$(http_code "${base}/actuator/health")" == "200" ]]; then - echo "/api"; return 0 - fi - echo "" - return 1 -} - -send_request() { - local kp_pid="$1" - local base="http://localhost:9966" - - wait_for_http_port - - # Try to detect API prefix dynamically - local API_PREFIX - API_PREFIX=$(detect_api_prefix || true) - - if [[ -z "${API_PREFIX}" ]]; then - echo "::warning::Could not auto-detect API prefix. Trying both /petclinic/api and /api." - # Try both paths to maximize coverage - local paths=( "/petclinic/api" "/api" ) - for pref in "${paths[@]}"; do - curl -sS "${base}${pref}/pettypes" || true - curl -sS --request POST \ - --url "${base}${pref}/pettypes" \ - --header 'content-type: application/json' \ - --data '{"name":"John Doe"}' || true - curl -sS "${base}${pref}/pettypes" || true - curl -sS --request POST \ - --url "${base}${pref}/pettypes" \ - --header 'content-type: application/json' \ - --data '{"name":"Alice Green"}' || true - curl -sS "${base}${pref}/pettypes" || true - curl -sS --request DELETE "${base}${pref}/pettypes/1" || true - curl -sS "${base}${pref}/pettypes" || true - done - else - echo "Detected API prefix: ${API_PREFIX}" - echo "good!App started" - curl -sS "${base}${API_PREFIX}/pettypes" || true - - curl -sS --request POST \ - --url "${base}${API_PREFIX}/pettypes" \ - --header 'content-type: application/json' \ - --data '{"name":"John Doe"}' || true - - curl -sS "${base}${API_PREFIX}/pettypes" || true - - curl -sS --request POST \ - --url "${base}${API_PREFIX}/pettypes" \ - --header 'content-type: application/json' \ - --data '{"name":"Alice Green"}' || true - - curl -sS "${base}${API_PREFIX}/pettypes" || true - - curl -sS --request DELETE "${base}${API_PREFIX}/pettypes/1" || true - - curl -sS "${base}${API_PREFIX}/pettypes" || true - fi - - # Let keploy persist, then stop it - sleep 10 - echo "$kp_pid Keploy PID" - echo "Killing keploy" - sudo kill "$kp_pid" 2>/dev/null || true -} - -# ----- main ----- - -source ./../../../.github/workflows/test_workflow_scripts/test-iid.sh - -section "Git branch" -git fetch origin -git checkout native-linux -endsec - -section "Start Postgres" -docker run -d --name mypostgres -e POSTGRES_USER=petclinic -e POSTGRES_PASSWORD=petclinic \ - -e POSTGRES_DB=petclinic -p 5432:5432 postgres:15.2 -wait_for_postgres -# seed DB -docker cp ./src/main/resources/db/postgresql/initDB.sql mypostgres:/initDB.sql -docker exec mypostgres psql -U petclinic -d petclinic -f /initDB.sql -endsec - -section "Java setup" -source ./../../../.github/workflows/test_workflow_scripts/update-java.sh -endsec - -# Clean once (keep artifacts across iterations) -sudo rm -rf keploy/ - -for i in 1 2; do - section "Record iteration $i" - - # Build app (captured to log) - mvn clean install -Dmaven.test.skip=true | tee -a mvn_build.log - - app_name="javaApp_${i}" - - # Start keploy in background, capture PID - sudo -E env PATH="$PATH" "$RECORD_BIN" record \ - -c 'java -jar target/spring-petclinic-rest-3.0.2.jar' \ - > "${app_name}.txt" 2>&1 & - KEPLOY_PID=$! - - # Drive traffic and stop keploy - send_request "$KEPLOY_PID" - - # Wait for keploy exit and capture code - set +e - wait "$KEPLOY_PID" - rc=$? - set -e - echo "Record exit code: $rc" - [[ $rc -ne 0 ]] && echo "::warning::Keploy record exited non-zero (iteration $i)" - - # Quick sanity: ensure something was written - echo "== keploy artifacts after record ==" - find ./keploy -maxdepth 3 -type f | sort || true - - # Surface issues from record logs - if grep -q "WARNING: DATA RACE" "${app_name}.txt"; then - echo "::error::Data race detected in ${app_name}.txt" - cat "${app_name}.txt" - exit 1 - fi - if grep -q "ERROR" "${app_name}.txt"; then - echo "::warning::Errors found in ${app_name}.txt" - cat "${app_name}.txt" - fi - - endsec - echo "Recorded test case and mocks for iteration ${i}" -done - -section "Shutdown Postgres before test mode" -# Stop Postgres container - Keploy should use mocks for database interactions -docker stop mypostgres || true -docker rm mypostgres || true -echo "Postgres stopped - Keploy should now use mocks for database interactions" -endsec - -section "Replay" -set +e -sudo -E env PATH="$PATH" "$REPLAY_BIN" test \ - -c 'java -jar target/spring-petclinic-rest-3.0.2.jar' \ - --delay 20 \ - > test_logs.txt 2>&1 -REPLAY_RC=$? -set -e -echo "Replay exit code: $REPLAY_RC" -cat test_logs.txt || true -endsec - -section "Check reports" -RUN_DIR=$(ls -1dt ./keploy/reports/test-run-* 2>/dev/null | head -n1 || true) -if [[ -z "${RUN_DIR:-}" ]]; then - echo "::error::No test-run directory found under ./keploy/reports" - [[ $REPLAY_RC -ne 0 ]] && exit "$REPLAY_RC" || exit 1 -fi -echo "Using reports from: $RUN_DIR" - -all_passed=true -for rpt in "$RUN_DIR"/test-set-*-report.yaml; do - [[ -f "$rpt" ]] || continue - status=$(awk '/^status:/{print $2; exit}' "$rpt") - echo "Test status for $(basename "$rpt"): ${status:-}" - [[ "$status" == "PASSED" ]] || all_passed=false -done -endsec - -if [[ "$all_passed" == "true" && $REPLAY_RC -eq 0 ]]; then - echo "All tests passed" - exit 0 -fi - -echo "::error::Some tests failed or replay exited non-zero" -exit 1 \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/node-docker.sh b/keploy/.github/workflows/test_workflow_scripts/node-docker.sh deleted file mode 100755 index 10da181..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/node-docker.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/bash - -source ./../../.github/workflows/test_workflow_scripts/test-iid.sh - -# Start the docker container. -docker network create keploy-network -docker run --name mongoDb --rm --net keploy-network -p 27017:27017 -d mongo - -# Remove any preexisting keploy tests. -sudo rm -rf keploy/ - -# Build the image of the application. -docker build -t node-app:1.0 . - -container_kill() { - pid=$(pgrep -n keploy) - echo "$pid Keploy PID" - echo "Killing keploy" - sudo kill $pid -} - -send_request(){ - sleep 10 - # Wait for the application to start. - app_started=false - while [ "$app_started" = false ]; do - if curl -X GET http://localhost:8000/students; then - app_started=true - fi - sleep 3 # wait for 3 seconds before checking again. - done - - # Start making curl calls to record the testcases and mocks. - curl --request POST \ - --url http://localhost:8000/students \ - --header 'content-type: application/json' \ - --data '{ - "name":"John Doe", - "email":"john@xyz.com", - "phone":"0123456799" - }' - - curl --request POST \ - --url http://localhost:8000/students \ - --header 'content-type: application/json' \ - --data '{ - "name":"Alice Green", - "email":"green@alice.com", - "phone":"3939201584" - }' - - curl -X GET http://localhost:8000/students - # Wait for 5 seconds for keploy to record the tcs and mocks. - sleep 5 - container_kill - wait -} - -for i in {1..2}; do - # Start keploy in record mode. - container_name="nodeApp_${i}" - send_request & - sudo -E env PATH=$PATH $RECORD_BIN record -c "docker run -p 8000:8000 --name "${container_name}" --network keploy-network node-app:1.0" --container-name "${container_name}" &> "${container_name}.txt" - if grep "ERROR" "${container_name}.txt"; then - echo "Error found in pipeline..." - cat "${container_name}.txt" - exit 1 - fi - if grep "WARNING: DATA RACE" "${container_name}.txt"; then - echo "Race condition detected in recording, stopping pipeline..." - cat "${container_name}.txt" - exit 1 - fi - sleep 5 - - echo "Recorded test case and mocks for iteration ${i}" -done - -# Shutdown mongo before test mode - Keploy should use mocks for database interactions -echo "Shutting down mongo before test mode..." -docker stop mongoDb || true -docker rm mongoDb || true -echo "MongoDB stopped - Keploy should now use mocks for database interactions" - -# Start keploy in test mode. -test_container="nodeApp_test" -sudo -E env PATH=$PATH $REPLAY_BIN test -c "docker run -p8000:8000 --rm --name $test_container --network keploy-network node-app:1.0" --containerName "$test_container" --apiTimeout 30 --delay 30 --generate-github-actions=false &> "${test_container}.txt" -if grep "ERROR" "${test_container}.txt"; then - echo "Error found in pipeline..." - cat "${test_container}.txt" - exit 1 -fi -# Monitor Docker logs for race conditions during testing. -if grep "WARNING: DATA RACE" "${test_container}.txt"; then - echo "Race condition detected in test, stopping pipeline..." - cat "${test_container}.txt" - exit 1 -fi -all_passed=true - -for i in {0..1} -do - # Define the report file for each test set - report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" - - # Extract the test status - test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') - - # Print the status for debugging - echo "Test status for test-set-$i: $test_status" - - # Check if any test set did not pass - if [ "$test_status" != "PASSED" ]; then - all_passed=false - echo "Test-set-$i did not pass." - break # Exit the loop early as all tests need to pass - fi -done - -# Check the overall test status and exit accordingly -if [ "$all_passed" = true ]; then - echo "All tests passed" - exit 0 -else - cat "${test_container}.txt" - exit 1 -fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/node-encoding.sh b/keploy/.github/workflows/test_workflow_scripts/node-encoding.sh deleted file mode 100644 index bdaf008..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/node-encoding.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/bash - -# Load test scripts and start MongoDB container -source ./../.github/workflows/test_workflow_scripts/test-iid.sh - -# Prepare environment -npm install -rm -rf keploy/ - -# Check if there is a keploy-config file, if there is, delete it. -if [ -f "./keploy.yml" ]; then - rm ./keploy.yml -fi - -echo "Record Bin: $RECORD_BIN" -echo "Record Version:" -sudo $RECORD_BIN --version -echo "Replay Bin: $REPLAY_BIN" -echo "Replay Version:" -sudo $REPLAY_BIN --version - -# Generate the keploy-config file. -sudo $RECORD_BIN config --generate - -# Update the global noise to ts. -config_file="./keploy.yml" -sed -i 's/global: {}/global: {"header": {"Etag":""}}/' "$config_file" - -send_request(){ - node server.js & - node_pid=$! - sleep 10 - app_started=false - while [ "$app_started" = false ]; do - if curl http://localhost:3000/; then - app_started=true - fi - sleep 3 # wait for 3 seconds before checking again. - done - echo "App started" - # Start making curl calls to record the testcases and mocks. - curl -v -H "Accept-Encoding: br" -i http://localhost:3000/ --output - - curl -v -H "Accept-Encoding: gzip" -i http://localhost:3000/ --output - - curl -v -H "Accept-Encoding: br" -i http://localhost:3000/proxy --output - - curl -v -H "Accept-Encoding: gzip" -i http://localhost:3000/proxy --output - - node request.js & - request_pid=$! - # Wait for 10 seconds for keploy to record the tcs and mocks. - sleep 10 - echo "Killing request.js" - if kill -0 $request_pid 2>/dev/null; then - kill $request_pid - wait $request_pid 2>/dev/null - fi - echo "Killing node server.js" - kill $node_pid - wait $node_pid 2>/dev/null - pid=$(pgrep keploy) - echo "$pid Keploy PID" - echo "Killing keploy" - sudo kill $pid -} - -# Record and test sessions in a loop -for i in {1..2}; do - app_name="nodeApp_${i}" - send_request & - sudo -E env PATH=$PATH $RECORD_BIN record -c 'node app.js' &> "${app_name}.txt" - cat "${app_name}.txt" - if grep "ERROR" "${app_name}.txt"; then - echo "Error found in pipeline..." - cat "${app_name}.txt" - exit 1 - fi - if grep "WARNING: DATA RACE" "${app_name}.txt"; then - echo "Race condition detected in recording, stopping pipeline..." - cat "${app_name}.txt" - exit 1 - fi - sleep 5 - wait - echo "Recorded test case and mocks for iteration ${i}" -done - -# Test modes and result checking -sudo -E env PATH=$PATH $REPLAY_BIN test -c 'node app.js' --delay 10 &> test_logs1.txt -cat test_logs1.txt -if grep "ERROR" "test_logs1.txt"; then - echo "Error found in pipeline..." - cat "test_logs1.txt" - exit 1 -fi -if grep "WARNING: DATA RACE" "test_logs1.txt"; then - echo "Race condition detected in test, stopping pipeline..." - cat "test_logs1.txt" - exit 1 -fi - -sudo -E env PATH=$PATH $REPLAY_BIN test -c 'node app.js' --delay 10 --testsets test-set-0 &> test_logs2.txt -cat test_logs2.txt -if grep "ERROR" "test_logs2.txt"; then - echo "Error found in pipeline..." - cat "test_logs2.txt" - exit 1 -fi -if grep "WARNING: DATA RACE" "test_logs2.txt"; then - echo "Race condition detected in test, stopping pipeline..." - cat "test_logs2.txt" - exit 1 -fi - -sudo -E env PATH=$PATH $REPLAY_BIN test -c 'node app.js' --apiTimeout 30 --delay 10 &> test_logs3.txt -cat test_logs3.txt -if grep "ERROR" "test_logs3.txt"; then - echo "Error found in pipeline..." - cat "test_logs3.txt" - exit 1 -fi -if grep "WARNING: DATA RACE" "test_logs3.txt"; then - echo "Race condition detected in test, stopping pipeline..." - cat "test_logs3.txt" - exit 1 -fi - -all_passed=true - -for i in {0..2} -do - report_file="./keploy/reports/test-run-$i/test-set-0-report.yaml" - # Extract the test status - test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') - - # Print the status for debugging - echo "Test status for test-set-$i: $test_status" - - # Check if any test set did not pass - if [ "$test_status" != "PASSED" ]; then - all_passed=false - echo "Test-set-$i did not pass." - cat "test_logs${i+1}.txt" - break # Exit the loop early as all tests need to pass - fi -done - - -# Check the overall test status and exit accordingly -if [ "$all_passed" = true ]; then - report_file="./keploy/reports/test-run-0/test-set-1-report.yaml" - test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') - - # Print the status for debugging - echo "Test status for test-set-0: $test_status" - - # Check if any test set did not pass - if [ "$test_status" != "PASSED" ]; then - all_passed=false - echo "Test-set-1 did not pass." - cat "test_logs1.txt" - exit 1 - fi - echo "All tests passed" - exit 0 -else - exit 1 -fi diff --git a/keploy/.github/workflows/test_workflow_scripts/node-linux.sh b/keploy/.github/workflows/test_workflow_scripts/node-linux.sh deleted file mode 100755 index 474bcbf..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/node-linux.sh +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env bash -# Safe, chatty CI script for Node + Mongo + Keploy - -set -Eeuo pipefail - -section() { echo "::group::$*"; } -endsec() { echo "::endgroup::"; } - -die() { - rc=$? - echo "::error::Pipeline failed (exit=$rc). Dumping context…" - echo "== docker ps =="; docker ps || true - echo "== mongo logs (complete) =="; docker logs mongoDb || true - echo "== workspace tree (depth 3) =="; find . -maxdepth 3 -type d -print | sort || true - echo "== keploy tree (depth 4) =="; find ./keploy -maxdepth 4 -type f -print 2>/dev/null | sort || true - echo "== *.txt logs (complete) =="; for f in ./*.txt; do [[ -f "$f" ]] && { echo "--- $f ---"; cat "$f"; }; done - for f in test_logs*.txt; do [[ -f "$f" ]] && { echo "== $f (complete) =="; cat "$f"; }; done - exit "$rc" -} -trap die ERR - -wait_for_mongo() { - section "Wait for Mongo readiness" - for i in {1..90}; do - if docker exec mongoDb mongosh --quiet --eval "db.adminCommand('ping').ok" >/dev/null 2>&1; then - echo "Mongo responds to ping." - endsec; return 0 - fi - if (echo > /dev/tcp/127.0.0.1/27017) >/dev/null 2>&1; then - echo "Mongo TCP port open." - endsec; return 0 - fi - sleep 1 - done - echo "::error::Mongo did not become ready in time" - endsec; return 1 -} - -wait_for_http() { - local url="$1" tries="${2:-60}" - for _ in $(seq 1 "$tries"); do - if curl -fsS "$url" >/dev/null; then return 0; fi - sleep 1 - done - return 1 -} - -send_request() { - local kp_pid="$1" - - if ! wait_for_http "http://localhost:8000/students" 120; then - echo "::error::App did not become healthy at /students" - else - echo "good!App started" - fi - - curl -sS --request POST --url http://localhost:8000/students \ - --header 'content-type: application/json' \ - --data '{"name":"John Doe","email":"john@xyiz.com","phone":"0123456799"}' || true - - curl -sS --request POST --url http://localhost:8000/students \ - --header 'content-type: application/json' \ - --data '{"name":"Alice Green","email":"green@alice.com","phone":"3939201584"}' || true - - curl -sS http://localhost:8000/students || true - curl -sS http://localhost:8000/get || true - - sleep 10 - echo "$kp_pid Keploy PID" - echo "Killing keploy" - sudo kill "$kp_pid" 2>/dev/null || true -} - -# ----- main ----- - -# Load test scripts and start MongoDB container -source ./../../.github/workflows/test_workflow_scripts/test-iid.sh - -section "Start Mongo" -docker run --name mongoDb --rm -p 27017:27017 -d mongo -wait_for_mongo -endsec - -section "Prepare app" -npm ci || npm install -sed -i "s/mongoDb:27017/localhost:27017/" "src/db/connection.js" -rm -rf keploy/ -[[ -f "./keploy.yml" ]] && rm ./keploy.yml - -# Generate the keploy-config file. -sudo "$RECORD_BIN" config --generate - -# Update the global noise to page (ignore changes to this field) -config_file="./keploy.yml" -sed -i 's/global: {}/global: {"body": {"page":[]}}/' "$config_file" -endsec - -for i in 1 2; do - section "Record iteration $i" - app_name="nodeApp_${i}" - - # Start keploy recording in background, capture PID - sudo -E env PATH="$PATH" "$RECORD_BIN" record -c 'npm start' \ - > "${app_name}.txt" 2>&1 & - KEPLOY_PID=$! - - # Drive traffic and stop keploy - send_request "$KEPLOY_PID" - - # Wait + capture rc - set +e - wait "$KEPLOY_PID" - rc=$? - set -e - echo "Record exit code: $rc" - [[ $rc -ne 0 ]] && echo "::warning::Keploy record exited non-zero (iteration $i)" - - echo "== keploy artifacts (depth 3) ==" - find ./keploy -maxdepth 3 -type f | sort || true - - if grep -q "WARNING: DATA RACE" "${app_name}.txt"; then - echo "::error::Data race detected in ${app_name}.txt" - cat "${app_name}.txt" - exit 1 - fi - if grep -q "ERROR" "${app_name}.txt"; then - echo "::warning::Errors found in ${app_name}.txt" - cat "${app_name}.txt" - fi - - endsec - echo "Recorded test case and mocks for iteration ${i}" -done - -# Optional tweak to a mock; guard if file exists -mocks_file="keploy/test-set-0/tests/test-5.yaml" -if [[ -f "$mocks_file" ]]; then - sed -i 's/"page":1/"page":4/' "$mocks_file" -else - echo "::warning::$mocks_file not found; skipping page change" -fi - -# ---- Replays ---- -# Shutdown MongoDB before test mode to verify Keploy mocking works correctly -section "Shutdown MongoDB before test mode" -docker stop mongoDb || true -docker rm mongoDb || true -echo "MongoDB stopped - Keploy should now use mocks for database interactions" -endsec - -run_replay() { - local idx="$1" - local extra_args="${2:-}" - local logfile="test_logs${idx}.txt" - - section "Replay #$idx (args: ${extra_args:-})" - set +e - sudo -E env PATH="$PATH" "$REPLAY_BIN" test -c 'npm start' --delay 10 $extra_args \ - > "$logfile" 2>&1 - local rc=$? - set -e - echo "Replay #$idx exit code: $rc" - cat "$logfile" || true - - # Find newest run dir and print set statuses - local RUN_DIR - RUN_DIR=$(ls -1dt ./keploy/reports/test-run-* 2>/dev/null | head -n1 || true) - if [[ -z "${RUN_DIR:-}" ]]; then - echo "::error::No test-run directory found after replay #$idx" - return "$rc" - fi - echo "Using reports from: $RUN_DIR" - local any_fail=false - for rpt in "$RUN_DIR"/test-set-*-report.yaml; do - [[ -f "$rpt" ]] || continue - local status - status=$(awk '/^status:/{print $2; exit}' "$rpt") - echo "Test status for $(basename "$rpt"): ${status:-}" - if [[ "$status" != "PASSED" ]]; then any_fail=true; fi - done - endsec - - if $any_fail; then - return 1 - else - return "$rc" - fi -} - -run_replay 1 -run_replay 2 "--testsets test-set-0" - -# enable selected tests in keploy.yml (guarded) -if [[ -f "./keploy.yml" ]]; then - sed -i 's/selectedTests: {}/selectedTests: {"test-set-0": ["test-1", "test-2"]}/' "./keploy.yml" || true -else - echo "::warning::keploy.yml missing; cannot set selectedTests" -fi - -run_replay 3 "--apiTimeout 30" - -echo "All replays completed. If no errors above, CI can PASS." -exit 0 \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/python-docker.sh b/keploy/.github/workflows/test_workflow_scripts/python-docker.sh deleted file mode 100755 index 3bc3eea..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/python-docker.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/bash - -source ./../../.github/workflows/test_workflow_scripts/test-iid.sh - -# Start mongo before starting keploy. -docker network create keploy-network -docker run --name mongo --rm --net keploy-network -p 27017:27017 -d mongo - -# Set up environment -rm -rf keploy/ # Clean up old test data -docker build -t flask-app:1.0 . # Build the Docker image - -# Configure keploy -sed -i 's/global: {}/global: {"header": {"Allow":[]}}/' "./keploy.yml" -sleep 5 # Allow time for configuration to apply - - -container_kill() { - pid=$(pgrep -n keploy) - echo "$pid Keploy PID" - echo "Killing keploy" - sudo kill $pid -} - -send_request(){ - local container_name=$1 - sleep 10 - app_started=false - while [ "$app_started" = false ]; do - if curl --silent http://localhost:6000/students; then - app_started=true - else - sleep 3 # Check every 3 seconds - fi - done - # Start making curl calls to record the testcases and mocks. - curl -X POST -H "Content-Type: application/json" -d '{"student_id": "12345", "name": "John Doe", "age": 20}' http://localhost:6000/students - curl -X POST -H "Content-Type: application/json" -d '{"student_id": "12346", "name": "Alice Green", "age": 22}' http://localhost:6000/students - curl http://localhost:6000/students - curl -X PUT -H "Content-Type: application/json" -d '{"name": "Jane Smith", "age": 21}' http://localhost:6000/students/12345 - curl http://localhost:6000/students - curl -X DELETE http://localhost:6000/students/12345 - - # Wait for 5 seconds for keploy to record the tcs and mocks. - sleep 5 - container_kill - wait -} - -# Record sessions -for i in {1..2}; do - container_name="flaskApp_${i}" - send_request & - sudo -E env PATH=$PATH $RECORD_BIN record -c "docker run -p6000:6000 --net keploy-network --rm --name $container_name flask-app:1.0" --container-name "$container_name" &> "${container_name}.txt" - if grep "ERROR" "${container_name}.txt"; then - echo "Error found in pipeline..." - cat "${container_name}.txt" - exit 1 - fi - if grep "WARNING: DATA RACE" "${container_name}.txt"; then - echo "Race condition detected in recording, stopping pipeline..." - cat "${container_name}.txt" - exit 1 - fi - sleep 5 - - echo "Recorded test case and mocks for iteration ${i}" -done - -# Shutdown mongo before test mode - Keploy should use mocks for database interactions -echo "Shutting down mongo before test mode..." -docker stop mongo || true -docker rm mongo || true -echo "MongoDB stopped - Keploy should now use mocks for database interactions" - -# Testing phase -test_container="flashApp_test" -sudo -E env PATH=$PATH $REPLAY_BIN test -c "docker run -p8080:8080 --net keploy-network --name $test_container flask-app:1.0" --containerName "$test_container" --apiTimeout 60 --delay 20 --generate-github-actions=false &> "${test_container}.txt" -if grep "ERROR" "${test_container}.txt"; then - echo "Error found in pipeline..." - cat "${test_container}.txt" - exit 1 -fi -if grep "WARNING: DATA RACE" "${test_container}.txt"; then - echo "Race condition detected in test, stopping pipeline..." - cat "${test_container}.txt" - exit 1 -fi - -all_passed=true - -for i in {0..1} -do - # Define the report file for each test set - report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" - - # Extract the test status - test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') - - # Print the status for debugging - echo "Test status for test-set-$i: $test_status" - - # Check if any test set did not pass - if [ "$test_status" != "PASSED" ]; then - all_passed=false - echo "Test-set-$i did not pass." - break # Exit the loop early as all tests need to pass - fi -done - -# Check the overall test status and exit accordingly -if [ "$all_passed" = true ]; then - echo "All tests passed" - exit 0 -else - cat "${test_container}.txt" - exit 1 -fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/python-linux.sh b/keploy/.github/workflows/test_workflow_scripts/python-linux.sh deleted file mode 100644 index c064607..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/python-linux.sh +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/bash - -source ./../../../.github/workflows/test_workflow_scripts/test-iid.sh - -# Checkout to the specified branch -git fetch origin -git checkout native-linux - -# Start the postgres database -docker compose up -d - -# Install dependencies -pip3 install -r requirements.txt - -# Setup environment -export PYTHON_PATH=./venv/lib/python3.10/site-packages/django - -# Database migrations -python3 manage.py makemigrations -python3 manage.py migrate - -# Configuration and cleanup -sudo $RECORD_BIN config --generate -sudo rm -rf keploy/ # Clean old test data -config_file="./keploy.yml" -sed -i 's/global: {}/global: {"header": {"Allow":[],}}/' "$config_file" -sleep 5 # Allow time for configuration changes - -send_request(){ - sleep 10 - app_started=false - while [ "$app_started" = false ]; do - if curl -X GET http://127.0.0.1:8000/; then - app_started=true - fi - sleep 3 # wait for 3 seconds before checking again. - done - echo "App started" - # Start making curl calls to record the testcases and mocks. - curl --location 'http://127.0.0.1:8000/user/' --header 'Content-Type: application/json' --data-raw '{ - "name": "Jane Smith", - "email": "jane.smith@example.com", - "password": "smith567", - "website": "www.janesmith.com" - }' - curl --location 'http://127.0.0.1:8000/user/' --header 'Content-Type: application/json' --data-raw '{ - "name": "John Doe", - "email": "john.doe@example.com", - "password": "john567", - "website": "www.johndoe.com" - }' - curl --location 'http://127.0.0.1:8000/user/' - # Wait for 10 seconds for keploy to record the tcs and mocks. - sleep 10 - pid=$(pgrep keploy) - echo "$pid Keploy PID" - echo "Killing keploy" - sudo kill $pid -} - -# Record and Test cycles -for i in {1..2}; do - app_name="flaskApp_${i}" - send_request & - sudo -E env PATH="$PATH" $RECORD_BIN record -c "python3 manage.py runserver" &> "${app_name}.txt" - if grep "ERROR" "${app_name}.txt"; then - echo "Error found in pipeline..." - cat "${app_name}.txt" - exit 1 - fi - if grep "WARNING: DATA RACE" "${app_name}.txt"; then - echo "Race condition detected in recording, stopping pipeline..." - cat "${app_name}.txt" - exit 1 - fi - sleep 5 - wait - echo "Recorded test case and mocks for iteration ${i}" -done - -# Shutdown postgres before test mode - Keploy should use mocks for database interactions -echo "Shutting down postgres before test mode..." -docker compose down -echo "Postgres stopped - Keploy should now use mocks for database interactions" - -# Testing phase -sudo -E env PATH="$PATH" $REPLAY_BIN test -c "python3 manage.py runserver" --delay 10 &> test_logs.txt - -if grep "ERROR" "test_logs.txt"; then - echo "Error found in pipeline..." - cat "test_logs.txt" - exit 1 -fi -if grep "WARNING: DATA RACE" "test_logs.txt"; then - echo "Race condition detected in test, stopping pipeline..." - cat "test_logs.txt" - exit 1 -fi - -all_passed=true - -for i in {0..1} -do - # Define the report file for each test set - report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml" - - # Extract the test status - test_status=$(grep 'status:' "$report_file" | head -n 1 | awk '{print $2}') - - # Print the status for debugging - echo "Test status for test-set-$i: $test_status" - - # Check if any test set did not pass - if [ "$test_status" != "PASSED" ]; then - all_passed=false - echo "Test-set-$i did not pass." - break # Exit the loop early as all tests need to pass - fi -done - -# Check the overall test status and exit accordingly -if [ "$all_passed" = true ]; then - echo "All tests passed" - exit 0 -else - cat "test_logs.txt" - exit 1 -fi \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/test-iid.sh b/keploy/.github/workflows/test_workflow_scripts/test-iid.sh deleted file mode 100644 index 45faacf..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/test-iid.sh +++ /dev/null @@ -1,4 +0,0 @@ -# Add fake installation-id for the workflow. -sudo mkdir ~/.keploy -sudo touch ~/.keploy/installation-id.yaml -echo "ObjectID('123456789')" | sudo tee ~/.keploy/installation-id.yaml > /dev/null \ No newline at end of file diff --git a/keploy/.github/workflows/test_workflow_scripts/update-docker.sh b/keploy/.github/workflows/test_workflow_scripts/update-docker.sh deleted file mode 100644 index ca5907d..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/update-docker.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Define the Dockerfile path -DOCKERFILE_PATH="./Dockerfile" - -# Function to add the -race flag to the go build command in the Dockerfile -update_dockerfile() { - echo "Updating Dockerfile to include the -race flag in the go build command..." - - # Use sed to update the Dockerfile - sed -i 's/RUN go build -tags=viper_bind_struct -ldflags="-X main.dsn=$SENTRY_DSN_DOCKER -X main.version=$VERSION" -o keploy ./RUN go build -race -tags=viper_bind_struct -ldflags="-X main.dsn=$SENTRY_DSN_DOCKER -X main.version=$VERSION" -o keploy ./' "$DOCKERFILE_PATH" - - echo "Dockerfile updated successfully." -} - -# Function to build the Docker image -build_docker_image() { - echo "Building Docker image..." - - # Build the Docker image - docker image build -t ttl.sh/keploy/keploy:1h . - - if [ $? -eq 0 ]; then - echo "Docker image built successfully." - else - echo "Failed to build Docker image." - exit 1 - fi -} - -# Main function to update the Dockerfile and build the Docker image -main() { - update_dockerfile - build_docker_image -} - -# Run the main function -main diff --git a/keploy/.github/workflows/test_workflow_scripts/update-java.sh b/keploy/.github/workflows/test_workflow_scripts/update-java.sh deleted file mode 100644 index f744fbb..0000000 --- a/keploy/.github/workflows/test_workflow_scripts/update-java.sh +++ /dev/null @@ -1,8 +0,0 @@ -#! /bin/bash - -# Update the java version -sudo apt update -sudo apt install openjdk-17-jre -y -export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 -export PATH=$JAVA_HOME/bin:$PATH -source ~/.bashrc \ No newline at end of file diff --git a/keploy/.github/write-good.yml b/keploy/.github/write-good.yml deleted file mode 100755 index 78accbc..0000000 --- a/keploy/.github/write-good.yml +++ /dev/null @@ -1,3 +0,0 @@ -writeGood: true -alex: true -spellchecker: true \ No newline at end of file diff --git a/keploy/.gitignore b/keploy/.gitignore deleted file mode 100755 index c0795f6..0000000 --- a/keploy/.gitignore +++ /dev/null @@ -1,61 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Ignore the server binary output by `go build` but not any folders with the name server -/server -!/server/ - -# Ignore the config and log files -keploy-config.yaml -keploy.yml -keploy-logs.txt - - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out -.deploy.sh - -### Go Patch ### -/vendor/ -/Godeps/ - -# Dependency directories (remove the comment below to include it) -# vendor/ -.idea/ -web/public/* -!web/public/README.md -!web/public/index.html - - -.vscode -.env -ui - -coverage.tmp.txt -coverage.txt -test-reports -.DS_Store -._.DS_Store -**/.DS_Store -**/._.DS_Store - -#Ignore the zip files -*.zip - -#Ignore the test reports -test-reports - -#Ignore the c and header files -*.c -*.h - -# Ignore the debug_bin -__debug_bin* -keploy \ No newline at end of file diff --git a/keploy/.golangci.yml b/keploy/.golangci.yml deleted file mode 100644 index 88b3898..0000000 --- a/keploy/.golangci.yml +++ /dev/null @@ -1,24 +0,0 @@ -# This is the configuration for golangci-lint for the restic project. -# -# A sample config with all settings is here: -# https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml -version: "2" - -linters: - enable: - - govet - - staticcheck - - errcheck - - ineffassign - - unused - exclusions: - rules: [] - paths-except: - - pkg/core/hooks/bpf_arm64_bpfel.go - - pkg/core/hooks/bpf_x86_bpfel.go - - pkg/service/utgen - -formatters: - enable: - - gofmt - - goimports \ No newline at end of file diff --git a/keploy/.pre-commit-config.yaml b/keploy/.pre-commit-config.yaml deleted file mode 100755 index 03cb10b..0000000 --- a/keploy/.pre-commit-config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -repos: -- hooks: - - id: commitizen - stages: - - commit-msg - repo: https://github.com/commitizen-tools/commitizen - rev: v2.21.2 diff --git a/keploy/.releaserc.json b/keploy/.releaserc.json deleted file mode 100755 index eac6d3c..0000000 --- a/keploy/.releaserc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "branches": ["main"], - "repositoryUrl": "git@github.com:keploy/keploy.git", - "plugins": [ - "@semantic-release/commit-analyzer", - "@semantic-release/release-notes-generator", - "@semantic-release/github", - ["@semantic-release/exec", { - "prepareCmd" : "set-version ${nextRelease.version}", - "publishCmd" : "publish-package" - }] - ] -} \ No newline at end of file diff --git a/keploy/CITATION.cff b/keploy/CITATION.cff deleted file mode 100755 index 0d5b322..0000000 --- a/keploy/CITATION.cff +++ /dev/null @@ -1,7 +0,0 @@ -cff-version: 1.2.0 -title: 'Keploy: No code API testing platform. Create unit tests and data mocks from API calls.' -message: 'To cite Keploy in publications use:' -type: software -author: Keploy Inc -repository-code: 'https://github.com/keploy/keploy' -license: Apache License 2.0 \ No newline at end of file diff --git a/keploy/CODE_OF_CONDUCT.md b/keploy/CODE_OF_CONDUCT.md deleted file mode 100755 index 8b5b1bc..0000000 --- a/keploy/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,126 +0,0 @@ -# Contributor Code of Conduct for Keploy - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Expected Behavior - -The following behaviors are expected and requested of all community members: - - * Act authentically and participate actively in the community to help maintain a positive and productive environment. - * Show consideration and respect in all your actions and speech. Avoid any behavior that is demeaning, discriminatory, or harassing. - * Seek collaboration as an initial step instead of conflict. - * Refrain from demeaning, discriminatory, or harassing behavior and speech. - * Report any unsafe situations, distress or violations of the code of conduct to the maintainers through [Slack](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg). -* Practice empathy and kindness towards other community members. -* Respect diverse opinions, perspectives, and experiences. -* Give and receive constructive feedback in a gracious manner. -* Take responsibility for your actions and apologize for mistakes. Use them as learning opportunities. -* Prioritize the well-being and success of the community as a whole over individual gain. - -Examples of unacceptable behavior include: - -* Violence, threats of violence or any language that incites violence towards any individual or group is prohibited. - * Discriminatory jokes and language, such as those based on gender, race, sexual orientation, religion, ability, or any other characteristic, is strictly forbidden. - * Displaying or sharing sexually explicit or violent content is prohibited. - * Any form of harassment, including but not limited to "doxing" (posting or threatening to post other people's personally identifying information) is prohibited. - * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. -* Publishing any personal information of others without their explicit consent is strictly forbidden. -* Using sexualized language or imagery, or making any sexual advances towards another person is prohibited. -* Harassment, whether it be public or private, will not be tolerated. - -## Enforcement Responsibilities - -Organizations' maintainers are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -They have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces related to Keploy, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -support@keploy.io. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Contact info - -* [Slack](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) -* [Mail](hello@keploy.io) - -## Support 🙏 - -This project needs a ⭐️ from you. Don't forget to leave a star ⭐️ - -## Happy Contributions !! diff --git a/keploy/CONTRIBUTING.md b/keploy/CONTRIBUTING.md deleted file mode 100755 index e30112d..0000000 --- a/keploy/CONTRIBUTING.md +++ /dev/null @@ -1,122 +0,0 @@ -# Contributing to Keploy - -Thank you for your interest in Keploy and for taking the time to contribute to this project. 🙌 Keploy is a project by developers for developers and there are a lot of ways you can contribute. - -If you don't know where to start contributing, ask us on our [Slack channel](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg). - -## Code of conduct - -Contributors are expected to adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). - -## Prerequisites for the contributors - -Contributors should have knowledge of git, go, and markdown for most projects since the project work heavily depends on them. -We encourage Contributors to set up Keploy for local development and play around with the code and tests to get more comfortable with the project. - -Sections - --
General Contribution Flow - - Developer Certificate of Origin -- Keploy Contribution Flow - - Keploy Server - - Keploy Documentation - -# General Contribution Flow - -## Signing-off on Commits (Developer Certificate of Origin) - -To contribute to this project, you must agree to the Developer Certificate of -Origin (DCO) for each commit you make. The DCO is a simple statement that you, -as a contributor, have the legal right to make the contribution. - -See the [DCO](https://developercertificate.org) file for the full text of what you must agree to -and how it works [here](https://github.com/probot/dco#how-it-works). -To signify that you agree to the DCO for contributions, you simply add a line to each of your -git commit messages: - -``` -Signed-off-by: Jane Smith -``` - -In most cases, you can add this signoff to your commit automatically with the -`-s` or `--signoff` flag to `git commit`. You must use your real name and a reachable email -address (sorry, no pseudonyms or anonymous contributions). An example of signing off on a commit: - -``` -$ commit -s -m “my commit message w/signoff” -``` - -To ensure all your commits are signed, you may choose to add this alias to your global `.gitconfig`: - -_~/.gitconfig_ - -``` -[alias] - amend = commit -s --amend - cm = commit -s -m - commit = commit -s -``` - -# How to contribute ? - -We encourage contributions from the community. - -**Create a [GitHub issue](https://github.com/keploy/keploy/issues) for any changes beyond typos and small fixes.** - -We review GitHub issues and PRs on a regular schedule. - -To ensure that each change is relevant and properly peer reviewed, please adhere to best practices for open-source contributions. -This means that if you are outside the Keploy organization, you must fork the repository and create PRs from branches on your own fork. -The README in GitHub's [first-contributions repo](https://github.com/firstcontributions/first-contributions) provides an example. - -## ## How to set up the docs website locally? - -1. Fork the repository - -
- -2. Clone the repository with the following command. Replace the with your username - -```sh -git clone https://github.com//keploy.git -``` - -
- -3. Go into the directory containing the project and edit the changes. - - -When we merge your PR, a new build automatically occurs and your changes publish to [https://keploy.io](https://github.com/keploy/keploy). - -## Keploy Contribution Flow - -Keploy is written in `Go` (Golang) and leverages Go Modules. Relevant coding style guidelines are the [Go Code Review Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) and the _Formatting and style_ section of Peter Bourgon's [Go: Best -Practices for Production Environments](https://peter.bourgon.org/go-in-production/#formatting-and-style). - -There are many ways in which you can contribute to Keploy. - -### Keploy Server - -#### Report a Bug -Report all issues through GitHub Issues using the [Report a Bug](https://github.com/keploy/keploy/issues/new?assignees=&labels=&template=bug_report.md&title=) template. -To help resolve your issue as quickly as possible, read the template and provide all the requested information. - -#### Feature request -We welcome all feature requests, whether it's to add new functionality to an existing extension or to offer an idea for a brand new extension. -File your feature request through GitHub Issues using the [Feature Request](https://github.com/keploy/keploy/issues/new?assignees=&labels=&template=feature_request.md&title=) template. - -#### Close a Bug -We welcome contributions that help make keploy bug free & improve the experience of our users. You can also find issues tagged [Good First Issues](https://github.com/keploy/keploy/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). - -### Keploy Documentation - -The Keploy documentation site uses Docusaurus 2, which is a static website generator, you can make changes locally without previewing them in the browser. - -In the process of shipping features quickly, we may forget to keep our docs up to date. You can help by suggesting improvements to our documentation using the [Documentation Improvement](https://github.com/keploy/docs/issues) template. - -Please refer to [Keploy Docs Contributing Guide](https://github.com/keploy/docs/blob/main/CONTRIBUTING.md#-how-to-set-up-the-docs-website-locally) for setting up your development environment and the follow [Keploy Style Guide](https://github.com/keploy/docs/blob/main/STYLE.md). - - -# Contact - -Feel free to join [slack](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) to start a conversation with us. diff --git a/keploy/DEBUG.md b/keploy/DEBUG.md deleted file mode 100644 index 210ef44..0000000 --- a/keploy/DEBUG.md +++ /dev/null @@ -1,79 +0,0 @@ -# 🐰 Keploy Debugging Guide: - -## 1) Capturing Stack Traces with `SIGQUIT` - -If Keploy appears stuck or unresponsive, and you want a quick insight into what it's doing (e.g., which goroutines are running), you can send a `SIGQUIT` signal. This is a fast alternative to setting up `pprof` and will make the Go runtime dump a full stack trace to `stderr`. - ---- - -## 🔧 **When Keploy is Running Natively (on the Host)** - -### 1. **Find the PID of the Keploy process** - -You can use the proxy port Keploy listens on (`16789` by default) to locate the process: - -```bash -sudo lsof -i:16789 -``` -Example output: - -```bash -keploy 12345 root ... TCP *:16789 ... -``` - -In this case, 12345 is the PID of the keploy process. - -### 2. Send a SIGQUIT signal to the Keploy process -```bash -sudo kill -SIGQUIT 12345 -``` - -The Go runtime will then print a full goroutine stack trace to the terminal or to wherever your logs are configured to go. - - - -## 🐳 When Keploy is Running in Docker (with --pid=host) - -If Keploy is running in a Docker container with the --pid=host flag, docker kill --signal=SIGQUIT won't work because Keploy is not PID 1 inside the container. You'll need to send the signal to the actual host PID. - -### 1. Find the host PID of the /app/keploy process - -Use docker top to inspect the container's process tree: - -```bash -docker top keploy-v2 -``` - -Look for a line where the command starts with /app/keploy: -```bash -UID PID PPID C STIME TTY TIME CMD -root 341003 ... ... ... ... ... /app/keploy record -c ... -``` -In this case, 341003 is the host PID of the running Go binary inside the container. - -### 2. Send SIGQUIT to that PID from the host - -```bash -sudo kill -SIGQUIT 341003 -``` - -This will cause the Go runtime to emit a full stack trace from the running keploy process. - -You should see a stack trace similar to: -```bash -SIGQUIT: quit -goroutine 1 [running]: -main.main() - /app/main.go:42 +0x123 -... -``` - -⸻ - -🧠 Notes - • This is a quick way to inspect a hung or slow keploy run without setting up full profiling. - • Make sure the Go binary is not built with -ldflags="-s -w" — that strips debug symbols, making stack traces useless. - • Do not intercept SIGQUIT in your code using signal.Notify(..., syscall.SIGQUIT) — it prevents the Go runtime from printing the trace. - • When using --pid=host, ensure you signal the actual host PID, not via docker kill. - -⸻ \ No newline at end of file diff --git a/keploy/Dockerfile b/keploy/Dockerfile deleted file mode 100755 index eff8195..0000000 --- a/keploy/Dockerfile +++ /dev/null @@ -1,57 +0,0 @@ -# === Build Stage === -FROM golang:1.24 AS build - -# Set the working directory -WORKDIR /app - -# Define build arguments for ldflags -ARG SENTRY_DSN_DOCKER -ARG VERSION -ARG SERVER_URL - -# Copy the Go module files and download dependencies -COPY go.mod go.sum /app/ -RUN go mod download - -# Copy the contents of the current directory into the build container -COPY . /app - -# Build the keploy binary -# setting GOMAXPROCS to avoid crashing qemu while building different arch with docker buildx -# ref - https://github.com/golang/go/issues/70329#issuecomment-2559049444 -RUN GOMAXPROCS=2 go build -tags=viper_bind_struct -ldflags="-X main.dsn=$SENTRY_DSN_DOCKER -X main.version=$VERSION -X main.apiServerURI=$SERVER_URL -X main.gitHubClientID=$GITTHUB_APP_CLIENT_ID" -o keploy . - -# === Runtime Stage === -FROM debian:bookworm-slim - -ENV KEPLOY_INDOCKER=true - -# Update the package lists and install required packages -RUN apt-get update -RUN apt-get install -y ca-certificates curl sudo && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Install Docker engine -RUN curl -fsSL https://get.docker.com -o get-docker.sh && \ - sh get-docker.sh && \ - rm get-docker.sh - -# Install docker-compose to PATH -# Install specific version of Docker Compose plugin (v2.29.1) -RUN mkdir -p /usr/lib/docker/cli-plugins && \ - curl -SL "https://github.com/docker/compose/releases/download/v2.29.1/docker-compose-linux-$(uname -m)" -o /usr/lib/docker/cli-plugins/docker-compose && \ - chmod +x /usr/lib/docker/cli-plugins/docker-compose - -# Copy the keploy binary and the entrypoint script from the build container -COPY --from=build /app/keploy /app/keploy -COPY --from=build /app/entrypoint.sh /app/entrypoint.sh - -# windows comapatibility -RUN sed -i 's/\r$//' /app/entrypoint.sh - -# Make the entrypoint.sh file executable -RUN chmod +x /app/entrypoint.sh - -# Set the entrypoint -ENTRYPOINT ["/app/entrypoint.sh", "/app/keploy"] diff --git a/keploy/HACKTOBERFEST_GUIDE.md b/keploy/HACKTOBERFEST_GUIDE.md deleted file mode 100644 index 6311476..0000000 --- a/keploy/HACKTOBERFEST_GUIDE.md +++ /dev/null @@ -1,82 +0,0 @@ -

- -

Celebrate -Open Source with Hacktoberfest 2023

- -![image](https://github.com/keploy/docs/blob/main/static/img/hacktoberfest-2023.png?raw=true) - -

𝑶𝒏𝒆 𝒄𝒐𝒏𝒕𝒓𝒊𝒃𝒖𝒕𝒊𝒐𝒏 𝒂𝒕 𝒂 𝒕𝒊𝒎𝒆

- -

- ---- - - -## Our Journey with Hacktoberfest ❤️ - -[Hacktoberfest](https://hacktoberfest.com/) is an initiative that matters very deeply to us. We launched the first iteration of Keploy as a mere open-source project in December 2021. Hacktoberfest 2022 was truly a game-changer for us, as we saw over 200 contributions from some lovely members of the open-source community that October. - -There are many different ways you can contribute to [Keploy](https://keploy.io). If you’ve ever wanted to contribute to open-source now is your chance! - -All backgrounds and skill levels are encouraged to participate. [Learn How to Contribute?](https://opensource.guide/how-to-contribute) - -# About Keploy - -Keploy is a next-gen E2E testing tool that provides an easy way to capture and generate tests(KTests) and data-mocks(KMocks) from real API calls. It automatically generates mocks and stubs, making the testing process simpler and more efficient. - -**- Automatically Mocks Dependencies**
-**- Zero Code Change Integration**
-**- Language-Agnostic Support**
-**- Native Docker/Kubernetes Support**
-**- Asynchronous Processes Support**
- -
- -[
⭐ Star and try Out Keploy ➜
](https://keploy.io) - -
- -___ - -### Learn more about projects and contributing - -👨🏻‍💻 **Code Contribubtion** - -Code contributions are a great way to get involved in supporting open source, and learn new skills. Here are some examples of ways you can contribute to open-source projects: - -- 👉 Bug fixes :- If you'd like to break and build software, fix a current issue reported by the community and be a hero! - -- 👉 Implement features :- You can choose from a carefully curated selection of Hacktoberfest requests that have been made by community members. - -- 👉 Build a demo app :- Build a demo app with Keploy and share it with the community. - - -📝 **Low Code and No-Code Contribution** - -You can choose from a carefully curated selection of Hacktoberfest requests that have been made by community members. - -- Technical documentation -- User experience testing -- Case studies -- Technical blog post or tutorial -- Translating to Other Languages -- Give Talks or presentations -- Organize event with community - -# Community Support - -The open source community needs you. Do you have what it takes to join the community and build a better future? We’re here to help you. - -

- -   - -   - -   - -   -

- -
- diff --git a/keploy/LICENSE b/keploy/LICENSE deleted file mode 100755 index c20e7bd..0000000 --- a/keploy/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2022 Keploy Inc - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/keploy/README-UnitGen.md b/keploy/README-UnitGen.md deleted file mode 100644 index ff86a43..0000000 --- a/keploy/README-UnitGen.md +++ /dev/null @@ -1,278 +0,0 @@ -

- keploy logo -

-

- -⚡️ Generate unit tests with LLMs, that actually works ⚡️ - -

-

-🌟 The must-have tool for developers in the AI-Gen era 🌟 -

- ---- - -

- - - Keploy X - - - - Help us reach 20k stars! - - - - Keploy CNCF Landscape - - -[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) -[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) -[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/keployio) - -

- ---- - -Keploy-gen uses LLMs to understand code semantics and generates meaningful **unit tests**. It's inspired by the [Automated Unit Test Improvement using LLM at Meta](https://arxiv.org/pdf/2402.09171). - -### Objectives - -- **Automate unit test generation (UTG)**: Quickly generate comprehensive unit tests and reduce the redundant manual effort. - -- **Improve edge cases**: Extend and improve the scope of tests to cover more complex scenarios that are often missed manually. - -- **Boost test coverage**: As codebase grows, ensuring exhaustive coverage should become feasible. - -## Core Components - -| **Phase** | **Activities** | **Tools/Technologies** | -| ----------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------- | -| **Code Analysis** | Analyze the code structure and dependencies. | Static analysis tools, LLMs | -| **Prompt Engineering** | Generation of targeted prompts to guide the LLM in producing relevant tests. | LLMs, Custom scripts | -| **Iterative Test Refinement** | Cyclic process of refining tests by running them, analyzing coverage, and incorporating feedback. | Testing frameworks (e.g., JUnit, pytest) | - -### Process Overview - -Referred from [Meta's research](https://arxiv.org/pdf/2402.09171), TestGen-LLM top level architecture. - -Test refinement process of unit test generator - -## Prerequisites - -**AI model Setup** - Set the environment variable **API_KEY**. -``` -export API_KEY=xxxx -``` - -**API_KEY** can be from either of one these: -- **OpenAI's GPT-4o** directly **[preferred]**. - -- Alternative LLMs via [litellm](https://github.com/BerriAI/litellm?tab=readme-ov-file#quick-start-proxy---cli). - -- Azure OpenAI - -## Installation - -Install Keploy locally by running the following command: - -#### ➡ Linux/Mac - -```shell - curl --silent -O -L https://keploy.io/install.sh && source install.sh -``` - -#### ➡ Windows - -- [Download](https://github.com/keploy/keploy/releases/latest/download/keploy_windows_amd64.tar.gz) and **move the keploy.exe file** to `C:\Windows\System32` - -### ![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) ➡ Running with Node.js/TypeScript applications - -- Ensure you've set the API key, as mentioned in pre-requisites above: - - ```shell - export API_KEY=xxxx - ``` - -- Ensure **Cobertura** formatted coverage reports, edit `jest.config.js` or `package.json`: -
- - ```json - // package.json - "jest": { - "coverageReporters": ["text", "cobertura"], - } - ``` - or - - ```javascript - // jest.config.js - module.exports = { - coverageReporters: ["text", "cobertura"], - }; - ``` - -#### Generating Unit Tests - -- Run the following command in the root of your application. -
- - - **For Single Test File:** If you prefer to test a smaller section of your application or to control costs, consider generating tests for a single source and its corresponding test file: - - ```shell - keploy gen --sourceFilePath="" --testFilePath="" --testCommand="npm test" --coverageReportPath="" - ``` - -
- - - **For Entire Application** use the following command to generate tests across: - - ⚠️ **Warning:** Executing this command will generate unit tests for all files in the application. Depending on the size of the codebase, this process may take between 20 minutes to an hour and will incur costs related to LLM usage. - - ```bash - keploy gen --testCommand="npm test" --testDir="test" --coverageReportPath="" - ``` - - 🎉 You should see improved test cases and code-coverage. ✅ Enjoy coding with enhanced unit test coverage! 🫰 - -### ![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) → Running with Golang applications - -- Ensure you've set the API key, as mentioned in pre-requisites above: - - ```shell - export API_KEY=xxxx - ``` - -- To ensure **Cobertura** formatted coverage reports, add: - ```bash - go install github.com/axw/gocov/gocov@v1.1.0 - go install github.com/AlekSi/gocov-xml@v1.1.0 - ``` -#### Generating Unit Tests -- Run the following command in the root of your application. -
- - - **For Single Test File:** If you prefer to test a smaller section of your application or to control costs, consider generating tests for a single source and its corresponding test file: - - ```shell - keploy gen --sourceFilePath="" --testFilePath="" --testCommand="go test -v ./... -coverprofile=coverage.out && gocov convert coverage.out | gocov-xml > coverage.xml" --coverageReportPath="" - ``` - -
- - - **For Entire Application** use the following command to generate tests across: - - ⚠️ **Warning:** Executing this command will generate unit tests for all files in the application. Depending on the size of the codebase, this process may take between 20 minutes to an hour and will incur costs related to LLM usage. - - ```bash - keploy gen --testDir="." --testCommand="go test -v ./... -coverprofile=coverage.out && gocov convert coverage.out | gocov-xml > coverage.xml" --coverageReportPath="" - ``` - - 🎉 You should see improved test cases and code-coverage. ✅ Enjoy coding with enhanced unit test coverage! 🫰 - -### → Setup for Other Languages - -- Ensure you've set the API key, as mentioned in pre-requisites above: - - ```shell - export API_KEY=xxxx - ``` - -- Ensure that your unit test report format is **Cobertura**(it's very common). -- Generate tests using keploy-gen: - ```bash - keploy gen --sourceFilePath="" --testFilePath="" --testCommand="" --coverageReportPath="" - ``` - -## Configuration - -Configure Keploy using command-line flags: - -```bash - - --sourceFilePath "" - --testFilePath "" - --coverageReportPath "coverage.xml" - --testCommand "" - --coverageFormat "cobertura" - --expectedCoverage 100 - --maxIterations 5 - --testDir "" - --llmBaseUrl "https://api.openai.com/v1" - --model "gpt-4o" - --llmApiVersion " -``` - -- `sourceFilePath`: Path to the source file for which tests are to be generated. -- `testFilePath`: Path where the generated tests will be saved. -- `coverageReportPath`: Path to generate the coverage report. -- `testCommand` (required): Command to execute tests and generate the coverage report. -- `coverageFormat`: Type of the coverage report (default "cobertura"). -- `expectedCoverage`: Desired coverage percentage (default 100%). -- `maxIterations`: Maximum number of iterations for refining tests (default 5). -- `testDir`: Directory where tests will be written. -- `llmBaseUrl`: Base url of the llm. -- `model`: Specifies the AI model to use (default "gpt-4o"). -- `llmApiVersion`: API version of the llm if any (default "") - -# Frequently Asked Questions - -1. What is Keploy's Unit Test Generator (UTG)?
- - Keploy's UTG automates the creation of unit tests based on code semantics, enhancing test coverage and reliability. - -2. Does Keploy send your private data to any cloud server for test generation?
- - No, Keploy does not send any user code to remote systems, except when using the unit test generation feature. When using the UT gen feature, only the source code and the unit test code will be sent to the Large Language Model (LLM) you are using. By default, Keploy uses - litellm to support vast number of LLM backends. Yes, if your organization has its own LLM(a private one), you can use it with Keploy. This ensures that data is not sent to any external systems. - -3. How does Keploy contribute to improving unit test coverage?
- - By providing a zero code platform for automated testing, Keploy empowers developers to scale up their unit test coverage without extensive coding knowledge. This integration enhances testing reports, ultimately boosting confidence in the product's quality. - -4. Is Keploy cost-effective for automated unit testing?
- - Yes, Keploy optimizes costs by automating repetitive testing tasks and improving overall test efficiency. - -5. How does Keploy generate coverage reports?
- - Keploy generates detailed Cobertura format reports, offering insights into test effectiveness and code quality. - -6. Can Keploy handle large codebases efficiently?
- - Yes, Keploy is designed to handle large codebases efficiently, though processing time may vary based on project size and complexity. - -# 🙋🏻‍♀️ Questions? 🙋🏻‍♂️ - -Reach out to us. We're here to answer! - -[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) -[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) -[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) -[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) - - -# 📝 Sample QuickStarts -- ![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) : Try a unit-gen on [Mux-SQL](https://github.com/keploy/samples-go/tree/main/mux-sql#create-unit-testcase-with-keploy) app - -- ![Node](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node&logoColor=white) : Try a unit-gen on [Express-Mongoose](https://github.com/keploy/samples-typescript/tree/main/express-mongoose#create-unit-testcase-with-keploy) app - -## 🌐 Language Support - -![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) -![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) - -Other language may be supported, we've not tested them yet. If your **coverage reports** are of **Cobertura format** then you should be able to use keploy-gen in any language. - -## Dev Support - -Keploy-gen is not just a project but an attempt to make developers life easier testing applications. -It aims to simplify the creation and maintenance of tests, ensuring high coverage, and adapts to the complexity of modern software development. - -#### Prompt Generation - -Referred from [Meta's research](https://arxiv.org/pdf/2402.09171), the four primary prompts used in the deployment for the December 2023 Instagram and Facebook app test-a-thons - -| Prompt Name | Template | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| extend_test | Here is a Kotlin unit test class: {`existing_test_class`}. Write an extended version of the test class that includes additional tests to cover some extra corner cases. | -| extend_coverage | Here is a Kotlin unit test class and the class that it tests: {`existing_test_class`} {`class_under_test`}. Write an extended version of the test class that includes additional unit tests that will increase the test coverage of the class under test. | -| corner_cases | Here is a Kotlin unit test class and the class that it tests: {`existing_test_class`} {`class_under_test`}. Write an extended version of the test class that includes additional unit tests that will cover corner cases missed by the original and will increase the test coverage of the class under test. | -| statement_to_complete | Here is a Kotlin class under test {`class_under_test`} This class under test can be tested with this Kotlin unit test class {`existing_test_class`}. Here is an extended version of the unit test class that includes additional unit test cases that will cover methods, edge cases, corner cases, and other features of the class under test that were missed by the original unit test class: | - -Limitation: This project currently doesn't generate quality fresh tests if there are no existing tests to learn from. - -Enjoy coding with enhanced unit test coverage! 🫰 diff --git a/keploy/README.md b/keploy/README.md deleted file mode 100755 index ea0d5b6..0000000 --- a/keploy/README.md +++ /dev/null @@ -1,172 +0,0 @@ - -

- keploy logo -

-

- -⚡️ API tests faster than unit tests, from user traffic ⚡️ - -

-

-🌟 The must-have tool for developers in the AI-Gen era 🌟 -

- ---- - -

- - - Keploy X! - - - - Help us reach 20k stars! - - - - Keploy CNCF Landscape - - -[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) -[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) -[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) -[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) - -keploy%2Fkeploy | Trendshift -

- - -[Keploy](https://keploy.io) is **developer-centric** API testing tool that creates **tests along with built-in-mocks**, faster than unit tests. - -Keploy not only records API calls, but also records database calls and replays them during testing, making it **easy to use, powerful, and extensible**. - -Convert API calls to test cases - -> 🐰 **Fun fact:** Keploy uses itself for testing! Check out our swanky coverage badge: [![Coverage Status](https://coveralls.io/repos/github/keploy/keploy/badge.svg?branch=main&kill_cache=1)](https://coveralls.io/github/keploy/keploy?branch=main&kill_cache=1)   - -## 🚨 Here for [Unit Test Generator](README-UnitGen.md) (ut-gen)? -Keploy has newly launched the world's first unit test generator(ut-gen) implementation of [Meta LLM research paper](https://arxiv.org/pdf/2402.09171), it understands code semantics and generates meaningful unit tests, aiming to: - -- **Automate unit test generation (UTG)**: Quickly generate comprehensive unit tests and reduce redundant manual effort. - -- **Improve edge cases**: Extend and improve the scope of automated tests to cover more complex scenarios, often missed manually. - -- **Boost test coverage**: As codebases grow, ensuring exhaustive coverage should become feasible, aligning with our mission. - -### 📜 Follow [Unit Test Generator README](README-UnitGen.md)! ✅ - -## 📘 Documentation! -Become a Keploy pro with **[Keploy Documentation](https://keploy.io/docs/)**. - -Record Replay Testing - -# 🚀 Quick Installation (API test generator) - -Integrate Keploy by installing the agent locally. No code-changes required. - -```shell -curl --silent -O -L https://keploy.io/install.sh && source install.sh -``` - -## 🎬 Recording Testcases - -Start your app with Keploy to convert API calls as Tests and Mocks/Stubs. - -```zsh -keploy record -c "CMD_TO_RUN_APP" -``` -For example, if you're using a simple Python app the `CMD_TO_RUN_APP` would resemble to `python main.py`, for Golang `go run main.go`, for java `java -jar xyz.jar`, for node `npm start`.. - -```zsh -keploy record -c "python main.py" -``` - -## 🧪 Running Tests -Shut down the databases, redis, kafka or any other services your application uses. Keploy doesn't need those during test. -```zsh -keploy test -c "CMD_TO_RUN_APP" --delay 10 -``` - -## ✅ Test Coverage Integration -To integrate with your unit-testing library and see combine test coverage, follow this [test-coverage guide](https://keploy.io/docs/server/sdk-installation/go/). - -> #### **If You Had Fun:** Please leave a 🌟 star on this repo! It's free and will bring a smile. 😄 👏 - -## One-Click Setup 🚀 - -Setup and run keploy quickly, with no local machine installation required: - -[![GitHub Codescape](https://img.shields.io/badge/GH%20codespace-3670A0?style=for-the-badge&logo=github&logoColor=fff)]([https://github.dev/Sonichigo/mux-sql](https://github.dev/Sonichigo/mux-sql)) - -## 🤔 Questions? -Reach out to us. We're here to help! - -[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) -[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) -[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) -[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) - - -## 🌐 Language Support -From Go's gopher 🐹 to Python's snake 🐍, we support: - -![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) -![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=for-the-badge&logo=java&logoColor=white) -![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) -![Rust](https://img.shields.io/badge/Rust-darkred?style=for-the-badge&logo=rust&logoColor=white) -![C#](https://img.shields.io/badge/csharp-purple?style=for-the-badge&logo=csharp&logoColor=white) -![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) - -## 🫰 Keploy Adopters 🧡 - -So you and your organisation are using Keploy? That’s great. Please add yourselves to [**this list,**](https://github.com/orgs/keploy/discussions/1765) and we'll send you goodies! 💖 - - -We are happy and proud to have you all as part of our community! 💖 - -## 🎩 How's the Magic Happen? -Keploy proxy captures and replays **ALL** (CRUD operations, including non-idempotent APIs) of your app's network interactions. - - -Take a journey to **[How Keploy Works?](https://keploy.io/docs/keploy-explained/how-keploy-works/)** to discover the tricks behind the curtain! - - ## 🔧 Core Features - -- ♻️ **Combined Test Coverage:** Merge your Keploy Tests with your fave testing libraries(JUnit, go-test, py-test, jest) to see a combined test coverage. - - -- 🤖 **EBPF Instrumentation:** Keploy uses EBPF like a secret sauce to make integration code-less, language-agnostic, and oh-so-lightweight. - - -- 🌐 **CI/CD Integration:** Run tests with mocks anywhere you like—locally on the CLI, in your CI pipeline (Jenkins, Github Actions..) , or even across a Kubernetes cluster. - - -- 📽️ **Record-Replay Complex Flows:** Keploy can record and replay complex, distributed API flows as mocks and stubs. It's like having a time machine for your tests—saving you tons of time! - - -- 🎭 **Multi-Purpose Mocks:** You can also use Keploy-generated Mocks, as server Tests! - - -👉 **Explore the code on GitHub**: [github.com/keploy/keploy](https://github.com/keploy/keploy) - - -## 👨🏻‍💻 Let's Build Together! 👩🏻‍💻 -Whether you're a newbie coder or a wizard 🧙‍♀️, your perspective is golden. Take a peek at our: - -📜 [Contribution Guidelines](https://github.com/keploy/keploy/blob/main/CONTRIBUTING.md) - -❤️ [Code of Conduct](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) - - -## 🐲 Current Limitations! -- **Unit Testing:** While Keploy is designed to run alongside unit testing frameworks (Go test, JUnit..) and can add to the overall code coverage, it still generates integration tests. -- **Production Lands**: Keploy is currently focused on generating tests for developers. These tests can be captured from any environment, but we have not tested it on high volume production environments. This would need robust deduplication to avoid too many redundant tests being captured. We do have ideas on building a robust deduplication system [#27](https://github.com/keploy/keploy/issues/27) - -## ✨ Resources! -🤔 [FAQs](https://keploy.io/docs/keploy-explained/faq/) - -🕵️‍️ [Why Keploy](https://keploy.io/docs/keploy-explained/why-keploy/) - -⚙️ [Installation Guide](https://keploy.io/docs/application-development/) - -📖 [Contribution Guide](https://keploy.io/docs/keploy-explained/contribution-guide/) diff --git a/keploy/READMEes-Es.md b/keploy/READMEes-Es.md deleted file mode 100644 index 8503289..0000000 --- a/keploy/READMEes-Es.md +++ /dev/null @@ -1,243 +0,0 @@ -

- keploy logo -

-

- -⚡️ Backend tests faster than unit-tests, from user traffic ⚡️ - -

-

-🌟 The must-have tool for developers in the AI-Gen era 🌟 -

- ---- - -

- - - - - - - - - - - - Keploy is released under the Apache License - - - - - - - - - - - - - - - - - - - - PRs welcome! - - - Help us reach 20k stars! - - - Join our Community! - - - - Keploy X - -

- -## 🎤 Presentando Keploy 🐰 -Keploy es una herramienta de prueba de backend centrada en el **desarrollador**. Realiza pruebas de backend con **mocks incorporados**, más rápido que las pruebas unitarias, a partir del tráfico del usuario, lo que lo hace **fácil de usar, potente y extensible**. 🛠 - -¿Listo para la magia? Aquí están las características principales de Keploy: - -- ♻️ **Cobertura de prueba combinada:** Fusiona tus pruebas de Keploy con tus bibliotecas de pruebas favoritas (junit, go-test, py-test, jest) para ver una cobertura de prueba combinada. - -- 🤖 **Instrumentación EBPF:** Keploy utiliza EBPF como un ingrediente secreto para hacer que la integración sea sin código, independiente del lenguaje y muy ligera. - -- 🌐 **Integración CI/CD:** Ejecuta pruebas con mocks donde quieras, ya sea localmente en la CLI, en tu canal de integración continua o incluso en un clúster de Kubernetes. ¡Es prueba donde la necesitas! - -- 🎭 **Mocks multipropósito:** Úsalos en pruebas existentes, como pruebas de servidor o simplemente para impresionar a tus amigos. - -- 📽️ **Grabación y reproducción de flujos complejos:** Keploy puede grabar y reproducir flujos de API complejos y distribuidos como mocks y stubs. Es como tener una máquina del tiempo para tus pruebas, ¡ahorrándote mucho tiempo! - -![Generar caso de prueba a partir de una llamada API](https://raw.githubusercontent.com/keploy/docs/main/static/gif/how-keploy-works.gif) - -> 🐰 **Dato curioso:** ¡Keploy se utiliza a sí mismo para realizar pruebas! Echa un vistazo a nuestra elegante insignia de cobertura: [![Estado de cobertura](https://coveralls.io/repos/github/keploy/keploy/badge.svg?branch=main&kill_cache=1)](https://coveralls.io/github/keploy/keploy?branch=main&kill_cache=1)   - -## 🌐 Soporte de idiomas -Desde el gopher de Go 🐹 hasta la serpiente de Python 🐍, ofrecemos soporte para: - -![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) -![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=for-the-badge&logo=java&logoColor=white) -![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) -![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) - -## 🎩 ¿Cómo funciona la magia? -Nuestro mágico 🧙‍♂️ proxy de Keploy captura y reproduce **TODAS** las interacciones de red de tu aplicación (operaciones CRUD, incluyendo APIs no idempotentes). - -Realiza un viaje a **[¿Cómo funciona Keploy?](https://docs.keploy.io/docs/keploy-explained/how-keploy-works)** para descubrir los trucos detrás del telón. - -![Generar caso de prueba a partir de una llamada API](https://raw.githubusercontent.com/keploy/docs/main/static/gif/record-replay.gif) - -## 📘 ¡Aprende más! -Conviértete en un profesional de Keploy con nuestra **[Documentación](https://docs.keploy.io/)**. - -# Instalación rápida - -Usando **Binario** ( Linux / WSL) -- - -Keploy se puede utilizar en Linux nativamente y a través de WSL en Windows. - -### Descarga el binario de Keploy. - -```zsh -curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz -C /tmp - -sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin && keploy - -
- Arquitectura ARM -curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_arm64.tar.gz" | tar xz -C /tmp - -sudo mkdir-p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin && keploy -
- -### Captura de casos de prueba -Para iniciar la grabación de llamadas a la API, ejecuta este comando en tu terminal donde normalmente ejecutas tu aplicación. Si necesitas configurar variables de entorno, hazlo de la manera habitual: - -```zsh -sudo -E env PATH=$PATH keploy record -c "CMD_PARA_EJECUTAR_LA_APP" -``` - -Por ejemplo, si estás utilizando un programa sencillo de Golang, el comando se vería así: - -```zsh -sudo -E env PATH=$PATH keploy record -c "CMD_PARA_EJECUTAR_LA_APP" -``` - -### Ejecución de casos de prueba - -Para ejecutar los casos de prueba y generar un informe de cobertura de pruebas, utiliza este comando en la terminal donde normalmente ejecutas tu aplicación. Si necesitas configurar variables de entorno, hazlo de la manera habitual: - -```zsh -sudo -E env PATH=$PATH keploy test -c "CMD_PARA_EJECUTAR_LA_APP" --delay 10 - ``` - - Por ejemplo, si estás utilizando un framework de Golang, el comando sería: - - ```zsh - sudo -E env PATH=$PATH keploy test -c "go run main.go" --delay 10 - ``` - - Instalación de Docker - -Keploy se puede utilizar en Linux y Windows a través de Docker. - -> **️ Nota:** MacOS necesitan instalar [Colima](https://github.com/abiosoft/colima#installation). Usuarios de Windows necesitan installar [WSL](https://learn.microsoft.com/en-us/windows/wsl/install#install-wsl-command). - -### Creación de alias - -Creemos un alias para Keploy: - -```shell -alias keploy='sudo docker run --pull always --name keploy-v2 -p 16789:16789 --privileged --pid=host -it -v $(pwd):$(pwd) -w $(pwd) -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/keploy/keploy' -``` - -### Grabación de Casos de Prueba y Datos Simulados - -Aquí tienes algunos puntos a considerar antes de la grabación: -- Si estás ejecutando mediante **docker-compose**, asegúrate de incluir el `` en el servicio de tu aplicación en el archivo docker-compose.yaml [como se muestra aquí](https://github.com/keploy/samples-python/blob/9d6cf40da2eb75f6e035bedfb30e54564785d5c9/flask-mongo/docker-compose.yml#L14). -- Debes ejecutar los contenedores en una red, si no es así, asegúrate de que todos tus contenedores estén en la misma red con la propiedad externa activada - [como se muestra aquí](https://github.com/keploy/samples-python/blob/9d6cf40da2eb75f6e035bedfb30e54564785d5c9/flask-mongo/docker-compose.yml#L24). Reemplaza el **nombre de la red** (bandera `--network`) por tu red personalizada si la cambiaste anteriormente, como la red en el ejemplo dado. -- `Docker_CMD_to_run_user_container` se refiere al **comando de Docker para iniciar** la aplicación. - -Utiliza el alias de keploy que creamos para capturar casos de prueba. **Ejecuta** el siguiente comando dentro del **directorio raíz** de tu aplicación. - -```shell -keploy record -c "Docker_CMD_to_run_user_container --network " --containerName "" -``` - -Realiza llamadas API utilizando herramientas como [Hoppscotch](https://hoppscotch.io/), [Postman](https://www.postman.com/) o comandos cURL. - -Keploy capturará las llamadas API que hayas realizado, generando suites de pruebas que comprenden **casos de prueba (KTests) y simulaciones de datos (KMocks)** en formato `YAML`. - -### Ejecución de Casos de Prueba - -Ahora, utiliza el alias keployV2 que creamos para ejecutar los casos de prueba. Sigue estos pasos en el **directorio raíz** de tu aplicación. - -Cuando utilices **docker-compose** para iniciar la aplicación, es importante asegurarse de que el parámetro `--containerName` coincida con el nombre del contenedor en tu archivo `docker-compose.yaml`. - -```shell -keploy test -c "Docker_CMD_to_run_user_container --network " --containerName "" --delay 20 -``` - -## 🤔 Preguntas? -¡Contáctanos! Estamos aquí para ayudarte. - -[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) -[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) -[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) -[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) - -## 💖 ¡Construyamos Juntos! -Ya seas un principiante o un mago 🧙‍♀️ en la programación, tu perspectiva es valiosa. Echa un vistazo a nuestras: - -📜 [Directrices de Contribución](https://github.com/keploy/keploy/blob/main/CONTRIBUTING.md) - -❤️ [Código de Conducta](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) - -## 🌟 Características - -### **🚀 Exporta, mantiene y muestra pruebas y simulaciones!** - -Genera Casos de Prueba desde Llamadas API - -### **🤝 Saluda a los populares frameworks de pruebas - Go-Test, JUnit, Py-Test, Jest y más!** - -Genera Casos de Prueba desde Llamadas API - -### **🕵️ Detecta ruido con precisión de cirujano!** -Filtra campos ruidosos en las respuestas de las API como (marcas de tiempo, valores aleatorios) para asegurar pruebas de alta calidad. - -### **📊 ¡Saluda a una mayor cobertura!** -Keploy se asegura de que no se generen casos de prueba redundantes. - -## 🐲 Los Desafíos que Enfrentamos! -- **Pruebas Unitarias:** Aunque Keploy está diseñado para funcionar junto con los marcos de pruebas unitarias (Go test, JUnit, etc.) y puede contribuir a la cobertura de código general, todavía genera pruebas de extremo a extremo (E2E). -- **Entornos de Producción:** Keploy actualmente se centra en generar pruebas para desarrolladores. Estas pruebas se pueden capturar desde cualquier entorno, pero no las hemos probado en entornos de producción de alto volumen. Esto requeriría una sólida deduplicación para evitar la captura de pruebas redundantes en exceso. Tenemos ideas para construir un sistema de deduplicación sólido [#27](https://github.com/keploy/keploy/issues/27) - -## ✨ Recursos! -🤔 [Preguntas Frecuentes](https://docs.keploy.io/docs/keploy-explained/faq) - -🕵️‍️ [¿Por Qué Keploy?](https://docs.keploy.io/docs/keploy-explained/why-keploy) - -⚙️ [Guía de Instalación](https://docs.keploy.io/docs/server/server-installation) - -📖 [Guía de Contribución](https://docs.keploy.io/docs/devtools/server-contrib-guide/) - -## 🌟 Salón de Contribuyentes -

- contribuyentes -

- -### Premios Disponibles - -| Nombre | Icono | Descripción | -| ---- | ---- | ----------- | -| Creador de Documentos | icono-de-docs | ¡Premiado por ayudar a mejorar la documentación de Keploy! | -| Cada Bit Cuenta | icono-de-commit | ¡Ningún commit es demasiado pequeño! | -| Héroe de Solicitudes de Extracción | icono-de-PR-hero | ¡Eres un héroe de solicitudes de extracción, sigue así! | -| Cercano| icono-de-closer | ¡Solo los cercanos consiguen café! | diff --git a/keploy/READMEja-JP.md b/keploy/READMEja-JP.md deleted file mode 100644 index 20a5067..0000000 --- a/keploy/READMEja-JP.md +++ /dev/null @@ -1,160 +0,0 @@ -

- keploy logo -

-

- -⚡️ ユーザートラフィックからのユニットテストよりも速いAPIテスト ⚡️ - -

-

-🌟 AI-Gen時代の開発者に必須のツール 🌟 -

- ---- - -

- - - Keploy X - - - - Help us reach 20k stars! - - - - Keploy CNCF Landscape - - -[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) -[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) -[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) -[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) - -

- - -[Keploy](https://keploy.io) は、**開発者中心**のAPIテストツールで、**組み込みモック**を使用してユニットテストよりも速くテストを作成します。 - -KeployはAPI呼び出しだけでなく、データベース呼び出しも記録し、テスト中に再生するため、**使いやすく、強力で、拡張性があります**。 - -Convert API calls to test cases - -> 🐰 **面白い事実:** Keployは自分自身をテストに使用しています!私たちの素晴らしいカバレッジバッジをチェックしてください: [![Coverage Status](https://coveralls.io/repos/github/keploy/keploy/badge.svg?branch=main&kill_cache=1)](https://coveralls.io/github/keploy/keploy?branch=main&kill_cache=1)   - -## 🚨 [ユニットテストジェネレーター](README-UnitGen.md) (ut-gen) のためにここにいますか? -Keployは、[Meta LLM研究論文](https://arxiv.org/pdf/2402.09171)の世界初のユニットテストジェネレーター(ut-gen)実装を新たに発表しました。これはコードのセマンティクスを理解し、意味のあるユニットテストを生成します。目指すのは: - -- **ユニットテスト生成の自動化 (UTG)**: 包括的なユニットテストを迅速に生成し、冗長な手動作業を削減します。 - -- **エッジケースの改善**: 自動テストの範囲を拡張し、手動で見逃されがちな複雑なシナリオをカバーします。 - -- **テストカバレッジの向上**: コードベースが成長するにつれて、徹底的なカバレッジを確保することが可能になります。 - -### 📜 [ユニットテストジェネレーター README](README-UnitGen.md) をフォローしてください! ✅ - -## 📘 ドキュメント! -**[Keploy Documentation](https://keploy.io/docs/)** でKeployのプロフェッショナルになりましょう。 - -Record Replay Testing - -# 🚀 クイックインストール (APIテストジェネレーター) - -エージェントをローカルにインストールしてKeployを統合します。コード変更は不要です。 - -```shell -curl --silent -O -L https://keploy.io/install.sh && source install.sh -``` - -## 🎬 テストケースの記録 - -API呼び出しをテストとモック/スタブに変換するために、Keployを使用してアプリを開始します。 - -```zsh -keploy record -c "CMD_TO_RUN_APP" -``` -例えば、シンプルなPythonアプリを使用している場合、`CMD_TO_RUN_APP`は`python main.py`、Golangの場合は`go run main.go`、Javaの場合は`java -jar xyz.jar`、Nodeの場合は`npm start`のようになります。 - -```zsh -keploy record -c "python main.py" -``` - -## 🧪 テストの実行 -データベース、Redis、Kafka、またはアプリケーションが使用する他のサービスをシャットダウンします。Keployはテスト中にそれらを必要としません。 -```zsh -keploy test -c "CMD_TO_RUN_APP" --delay 10 -``` - -## ✅ テストカバレッジの統合 -ユニットテストライブラリと統合して、結合テストカバレッジを表示するには、この[テストカバレッジガイド](https://keploy.io/docs/server/sdk-installation/go/)に従ってください。 - -> #### **楽しんでいただけましたか:** このリポジトリに🌟スターを残してください!無料で笑顔をもたらします。😄 👏 - -## ワンクリックセットアップ 🚀 - -ローカルマシンのインストールなしでKeployを迅速にセットアップして実行します: - -[![GitHub Codescape](https://img.shields.io/badge/GH%20codespace-3670A0?style=for-the-badge&logo=github&logoColor=fff)]([https://github.dev/Sonichigo/mux-sql](https://github.dev/Sonichigo/mux-sql)) - -## 🤔 質問がありますか? -私たちに連絡してください。お手伝いします! - -[![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/keploy/shared_invite/zt-357qqm9b5-PbZRVu3Yt2rJIa6ofrwWNg) -[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/keploy/) -[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/channel/UC6OTg7F4o0WkmNtSoob34lg) -[![X](https://img.shields.io/badge/X-%231DA1F2.svg?style=for-the-badge&logo=X&logoColor=white)](https://x.com/Keployio) - - -## 🌐 言語サポート -Goのゴーファー 🐹 からPythonのスネーク 🐍 まで、以下の言語をサポートしています: - -![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) -![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=for-the-badge&logo=java&logoColor=white) -![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) -![Rust](https://img.shields.io/badge/Rust-darkred?style=for-the-badge&logo=rust&logoColor=white) -![C#](https://img.shields.io/badge/csharp-purple?style=for-the-badge&logo=csharp&logoColor=white) -![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) - -## 🫰 Keployの採用者 🧡 - -あなたとあなたの組織がKeployを使用しているのですか?それは素晴らしいことです。 [**このリスト**](https://github.com/orgs/keploy/discussions/1765) に追加してください。グッズをお送りします!💖 - -私たちは、あなたたち全員が私たちのコミュニティの一員であることを誇りに思います!💖 - -## 🎩 魔法はどのように起こるのか? -Keployプロキシは、アプリの**すべての**ネットワークインタラクション(CRUD操作、非冪等なAPIを含む)をキャプチャして再生します。 - -**[Keployの仕組み](https://keploy.io/docs/keploy-explained/how-keploy-works/)** の旅に出て、カーテンの裏にあるトリックを発見してください! - -ここにKeployの主な機能があります: 🛠 - -- ♻️ **結合テストカバレッジ:** Keployテストをお気に入りのテストライブラリ(JUnit、go-test、py-test、jest)と統合して、結合テストカバレッジを表示します。 - -- 🤖 **EBPFインストルメンテーション:** KeployはEBPFを使用して、コードレス、言語非依存、非常に軽量な統合を実現します。 - -- 🌐 **CI/CD統合:** テストをローカルCLI、CIパイプライン(Jenkins、Github Actions..)、またはKubernetesクラスター全体で実行します。 - -- 📽️ **複雑なフローの記録と再生:** Keployは、複雑で分散したAPIフローをモックとスタブとして記録して再生できます。これは、テストのためのタイムマシンを持っているようなもので、たくさんの時間を節約できます! - -- 🎭 **多目的モック:** Keployモックをサーバーテストとしても使用できます! - -## 👨🏻‍💻 一緒に構築しましょう! 👩🏻‍💻 -初心者のコーダーでもウィザードでも 🧙‍♀️、あなたの視点は貴重です。以下をチェックしてください: - -📜 [貢献ガイドライン](https://github.com/keploy/keploy/blob/main/CONTRIBUTING.md) - -❤️ [行動規範](https://github.com/keploy/keploy/blob/main/CODE_OF_CONDUCT.md) - - -## 🐲 現在の制限事項! -- **ユニットテスト:** Keployはユニットテストフレームワーク(Go test、JUnit..)と一緒に実行するように設計されており、全体的なコードカバレッジに追加することができますが、それでも統合テストを生成します。 -- **プロダクション環境:** Keployは現在、開発者向けのテスト生成に焦点を当てています。これらのテストは任意の環境からキャプチャできますが、高ボリュームのプロダクション環境ではテストしていません。これは、過剰な冗長テストのキャプチャを避けるために堅牢な重複排除が必要です。堅牢な重複排除システムの構築についてのアイデアがあります [#27](https://github.com/keploy/keploy/issues/27) - -## ✨ リソース! -🤔 [FAQ](https://keploy.io/docs/keploy-explained/faq/) - -🕵️‍️ [なぜKeploy](https://keploy.io/docs/keploy-explained/why-keploy/) - -⚙️ [インストールガイド](https://keploy.io/docs/application-development/) - -📖 [貢献ガイド](https://keploy.io/docs/keploy-explained/contribution-guide/) diff --git a/keploy/SECURITY.md b/keploy/SECURITY.md deleted file mode 100755 index 3eafe68..0000000 --- a/keploy/SECURITY.md +++ /dev/null @@ -1,9 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -We value security for the project very highly. We encourage all users to report any vulnerabilities they discover to us. -If you find a security vulnerability in the Keploy project, please report it responsibly by sending an email to hello@keploy.io - -At this juncture, we don't have a bug bounty program. We are a small team trying to solve a big problem. We urge you to report any vulnerabilities responsibly -so that we can continue building a secure application for the entire community. \ No newline at end of file diff --git a/keploy/cli/README.md b/keploy/cli/README.md deleted file mode 100755 index 71c21c5..0000000 --- a/keploy/cli/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# CMD Package Documentation - -In this package, the `root` command and its `subcommands` are defined -for the CLI. This package, which is called from the main package, utilizes the -`pkg` services to execute commands. diff --git a/keploy/cli/cli.go b/keploy/cli/cli.go deleted file mode 100644 index d7ae3aa..0000000 --- a/keploy/cli/cli.go +++ /dev/null @@ -1,22 +0,0 @@ -// Package cli provides functionality for the command-line interface of the application. -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - "go.uber.org/zap" -) - -type HookFunc func(context.Context, *zap.Logger, *config.Config, ServiceFactory, CmdConfigurator) *cobra.Command - -// Registered holds the registered command hooks -var Registered map[string]HookFunc - -func Register(name string, f HookFunc) { - if Registered == nil { - Registered = make(map[string]HookFunc) - } - Registered[name] = f -} diff --git a/keploy/cli/config.go b/keploy/cli/config.go deleted file mode 100644 index 8a7b0f5..0000000 --- a/keploy/cli/config.go +++ /dev/null @@ -1,75 +0,0 @@ -package cli - -import ( - "context" - "errors" - "path/filepath" - - "go.keploy.io/server/v2/config" - - toolsSvc "go.keploy.io/server/v2/pkg/service/tools" - "go.keploy.io/server/v2/utils" - - "github.com/spf13/cobra" - "go.uber.org/zap" -) - -func init() { - Register("config", Config) -} - -func Config(ctx context.Context, logger *zap.Logger, cfg *config.Config, servicefactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "config", - Short: "manage keploy configuration file", - Example: "keploy config --generate --path /path/to/localdir", - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.ValidateFlags(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - isGenerate, err := cmd.Flags().GetBool("generate") - if err != nil { - utils.LogError(logger, err, "failed to get generate flag") - return err - } - - if isGenerate { - filePath := filepath.Join(cfg.Path, "keploy.yml") - if !cfg.InCi && utils.CheckFileExists(filePath) { - override, err := utils.AskForConfirmation(ctx, "Config file already exists. Do you want to override it?") - if err != nil { - utils.LogError(logger, err, "failed to ask for confirmation") - return err - } - if !override { - logger.Info("Skipping config file override") - return nil - } - } - svc, err := servicefactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return err - } - var tools toolsSvc.Service - var ok bool - if tools, ok = svc.(toolsSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy tools service interface") - return err - } - if err := tools.CreateConfig(ctx, filePath, ""); err != nil { - utils.LogError(logger, err, "failed to create config") - return err - } - logger.Info("Config file generated successfully") - return nil - } - return errors.New("only generate flag is supported in the config command") - }, - } - if err := cmdConfigurator.AddFlags(cmd); err != nil { - utils.LogError(logger, err, "failed to add flags") - return nil - } - return cmd -} diff --git a/keploy/cli/contract.go b/keploy/cli/contract.go deleted file mode 100644 index e1e0eb7..0000000 --- a/keploy/cli/contract.go +++ /dev/null @@ -1,134 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - contractSvc "go.keploy.io/server/v2/pkg/service/contract" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("contract", Contract) -} - -func Contract(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "contract", - Short: "Manage keploy contracts", - } - - cmd.AddCommand(Generate(ctx, logger, serviceFactory, cmdConfigurator)) - cmd.AddCommand(Download(ctx, logger, serviceFactory, cmdConfigurator)) - cmd.AddCommand(Validate(ctx, logger, serviceFactory, cmdConfigurator)) - for _, subCmd := range cmd.Commands() { - err := cmdConfigurator.AddFlags(subCmd) - if err != nil { - utils.LogError(logger, err, "failed to add flags to command", zap.String("command", subCmd.Name())) - } - } - return cmd -} - -func Generate(ctx context.Context, logger *zap.Logger, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "generate", - Short: "Generate contract for specified services", - Example: `keploy contract generate --service="email,notify"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, "contract") - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var contract contractSvc.Service - var ok bool - if contract, ok = svc.(contractSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy contract service interface") - return nil - } - // Extract services from the flag - - err = contract.Generate(ctx, true) - - if err != nil { - utils.LogError(logger, err, "failed to generate contract") - return nil - } - - return nil - }, - } - - return cmd - -} - -func Download(ctx context.Context, logger *zap.Logger, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "download", - Short: "Download contract for specified services", - Example: `keploy contract download --service="email,notify" --path /local/path`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, "contract") - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var contract contractSvc.Service - var ok bool - if contract, ok = svc.(contractSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy contract service interface") - return nil - } - err = contract.Download(ctx, true) - - if err != nil { - utils.LogError(logger, err, "failed to download contract") - } - return nil - }, - } - - return cmd -} - -func Validate(ctx context.Context, logger *zap.Logger, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "test", - Short: "Validate contract for specified services", - Example: `keploy contract test --service="email,notify" --path /local/path`, - - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, "contract") - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var contract contractSvc.Service - var ok bool - if contract, ok = svc.(contractSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy contract service interface") - return nil - } - err = contract.Validate(ctx) - if err != nil { - utils.LogError(logger, err, "failed to validate contract") - } - return nil - }, - } - - return cmd -} diff --git a/keploy/cli/examples.go b/keploy/cli/examples.go deleted file mode 100755 index d8f3baf..0000000 --- a/keploy/cli/examples.go +++ /dev/null @@ -1,47 +0,0 @@ -package cli - -import ( - "context" - "fmt" - "os" - - "go.keploy.io/server/v2/cli/provider" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - - "github.com/spf13/cobra" -) - -func init() { - Register("example", Example) -} - -func Example(_ context.Context, logger *zap.Logger, _ *config.Config, _ ServiceFactory, _ CmdConfigurator) *cobra.Command { - var customSetup bool - var cmd = &cobra.Command{ - Use: "example", - Short: "Example to record and test via keploy", - RunE: func(cmd *cobra.Command, _ []string) error { - disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) - provider.PrintLogo(os.Stdout, disableAnsi) - customSetup, err := cmd.Flags().GetBool("customSetup") - if err != nil { - utils.LogError(logger, nil, "failed to read the customSetup flag") - return err - } - if customSetup { - fmt.Println(provider.Examples) - return nil - } - fmt.Println(provider.ExampleOneClickInstall) - fmt.Println(provider.WithoutexampleOneClickInstall) - return nil - }, - } - cmd.SetHelpTemplate(provider.CustomHelpTemplate) - - cmd.Flags().Bool("customSetup", customSetup, "Check if the user is using one click install") - - return cmd -} diff --git a/keploy/cli/export.go b/keploy/cli/export.go deleted file mode 100644 index 854ab91..0000000 --- a/keploy/cli/export.go +++ /dev/null @@ -1,63 +0,0 @@ -package cli - -import ( - "context" - "os" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/cli/provider" - "go.keploy.io/server/v2/config" - toolsSvc "go.keploy.io/server/v2/pkg/service/tools" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("export", Export) -} - -func Export(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - - var exportCmd = &cobra.Command{ - Use: "export", - Short: "export Keploy tests as postman collection", - Example: "keploy export", - RunE: func(cmd *cobra.Command, _ []string) error { - disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) - provider.PrintLogo(os.Stdout, disableAnsi) - return cmd.Help() - }, - } - var postmanCmd = &cobra.Command{ - Use: "postman", - Short: "export Keploy tests as Postman collection", - Example: "keploy export postman", - RunE: func(cmd *cobra.Command, _ []string) error { - disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) - provider.PrintLogo(os.Stdout, disableAnsi) - svc, err := serviceFactory.GetService(ctx, "export") - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var tools toolsSvc.Service - var ok bool - if tools, ok = svc.(toolsSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy tools service interface") - return nil - } - err = tools.Export(ctx) // Assuming ExportPostmanCollection is a method in tools service - if err != nil { - utils.LogError(logger, err, "failed to export Postman collection") - } - return nil - }, - } - exportCmd.AddCommand(postmanCmd) - - if err := cmdConfigurator.AddFlags(exportCmd); err != nil { - utils.LogError(logger, err, "failed to add export cmd flags") - return nil - } - return exportCmd -} diff --git a/keploy/cli/import.go b/keploy/cli/import.go deleted file mode 100644 index e589e29..0000000 --- a/keploy/cli/import.go +++ /dev/null @@ -1,72 +0,0 @@ -package cli - -import ( - "context" - "os" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/cli/provider" - "go.keploy.io/server/v2/config" - toolsSvc "go.keploy.io/server/v2/pkg/service/tools" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("import", Import) -} - -func Import(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - - var importCmd = &cobra.Command{ - Use: "import", - Short: "import postman collection to Keploy tests", - Example: "keploy import", - RunE: func(cmd *cobra.Command, _ []string) error { - disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) - provider.PrintLogo(os.Stdout, disableAnsi) - return cmd.Help() - }, - } - - var postmanCmd = &cobra.Command{ - Use: "postman", - Short: "import postman collection to Keploy tests", - Example: "keploy import postman", - RunE: func(cmd *cobra.Command, _ []string) error { - disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) - provider.PrintLogo(os.Stdout, disableAnsi) - path, _ := cmd.Flags().GetString("path") - if path == "" { - path = "output.json" - } - basePath, _ := cmd.Flags().GetString("base-path") - svc, err := serviceFactory.GetService(ctx, "import") - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var tools toolsSvc.Service - var ok bool - if tools, ok = svc.(toolsSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy tools service interface") - return nil - } - err = tools.Import(ctx, path, basePath) - if err != nil { - utils.LogError(logger, err, "failed to import Postman collection") - } - return nil - }, - } - importCmd.AddCommand(postmanCmd) - - for _, subCmd := range importCmd.Commands() { - err := cmdConfigurator.AddFlags(subCmd) - if err != nil { - utils.LogError(logger, err, "failed to add flags to command", zap.String("command", subCmd.Name())) - } - } - - return importCmd -} diff --git a/keploy/cli/load.go b/keploy/cli/load.go deleted file mode 100644 index 5787e34..0000000 --- a/keploy/cli/load.go +++ /dev/null @@ -1,66 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - loadSvc "go.keploy.io/server/v2/pkg/service/load" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("load", Load) -} - -func Load(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "load", - Short: "load test a given testsuite.", - Example: `keploy load -f test_suite.yaml --out json > report.json`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, args []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service") - return nil - } - - // Get CLI parameters - vus, _ := cmd.Flags().GetInt("vus") - duration, _ := cmd.Flags().GetString("duration") - rps, _ := cmd.Flags().GetInt("rps") - - // values comming from CLI flags to override the spec.load options. - ctx := context.WithValue(ctx, "vus", vus) - ctx = context.WithValue(ctx, "duration", duration) - ctx = context.WithValue(ctx, "rps", rps) - - var ltSvc loadSvc.Service - var ok bool - if ltSvc, ok = svc.(loadSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy load service interface") - return nil - } - - err = ltSvc.Start(ctx) - if err != nil { - utils.LogError(logger, err, "failed to start the load tester") - return nil - } - - return nil - }, - } - - err := cmdConfigurator.AddFlags(cmd) - if err != nil { - utils.LogError(logger, err, "failed to add load flags") - return nil - } - - return cmd -} diff --git a/keploy/cli/login.go b/keploy/cli/login.go deleted file mode 100644 index 6d41466..0000000 --- a/keploy/cli/login.go +++ /dev/null @@ -1,41 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - toolsSvc "go.keploy.io/server/v2/pkg/service/tools" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("login", Login) -} - -func Login(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, _ CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "login", - Short: "login to keploy via github", - Example: `keploy login`, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var tools toolsSvc.Service - var ok bool - if tools, ok = svc.(toolsSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy record service interface") - return nil - } - tools.Login(ctx) - return nil - }, - } - - return cmd -} diff --git a/keploy/cli/mock.go b/keploy/cli/mock.go deleted file mode 100644 index 65150ba..0000000 --- a/keploy/cli/mock.go +++ /dev/null @@ -1,94 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - replaySvc "go.keploy.io/server/v2/pkg/service/replay" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("mock", Mock) -} - -func Mock(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "mock", - Short: "Managing mocks", - } - - cmd.AddCommand(DownloadMocks(ctx, logger, serviceFactory, cmdConfigurator)) - cmd.AddCommand(UploadMocks(ctx, logger, serviceFactory, cmdConfigurator)) - for _, subCmd := range cmd.Commands() { - err := cmdConfigurator.AddFlags(subCmd) - if err != nil { - utils.LogError(logger, err, "failed to add flags to command", zap.String("command", subCmd.Name())) - } - } - return cmd -} - -func DownloadMocks(ctx context.Context, logger *zap.Logger, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "download", - Short: "Download mocks from the keploy registry", - Example: `keploy mock download`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Parent().Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Parent().Name())) - return nil - } - var replay replaySvc.Service - var ok bool - if replay, ok = svc.(replaySvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy replay service interface") - return nil - } - if err := replay.DownloadMocks(ctx); err != nil { - utils.LogError(logger, err, "failed to download mocks from keploy registry") - return nil - } - return nil - }, - } - - return cmd -} - -func UploadMocks(ctx context.Context, logger *zap.Logger, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "upload", - Short: "Upload mocks to the keploy registry", - Example: `keploy mock upload`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Parent().Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Parent().Name())) - return nil - } - var replay replaySvc.Service - var ok bool - if replay, ok = svc.(replaySvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy replay service interface") - return nil - } - if err := replay.UploadMocks(ctx); err != nil { - utils.LogError(logger, err, "failed to upload mocks to the keploy registry") - return nil - } - return nil - }, - } - - return cmd -} diff --git a/keploy/cli/normalise.go b/keploy/cli/normalise.go deleted file mode 100644 index ff5a33b..0000000 --- a/keploy/cli/normalise.go +++ /dev/null @@ -1,50 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - replaySvc "go.keploy.io/server/v2/pkg/service/replay" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("normalize", Normalize) -} - -// Normalize retrieves the command to normalize Keploy -func Normalize(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var normalizeCmd = &cobra.Command{ - Use: "normalize", - Short: "Normalize Keploy", - Example: "keploy normalize --test-run testrun --tests test-set-1:test-case-1 test-case-2,test-set-2:test-case-1 test-case-2 ", - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.ValidateFlags(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var replay replaySvc.Service - var ok bool - if replay, ok = svc.(replaySvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy replay service interface") - return nil - } - if err := replay.Normalize(ctx); err != nil { - utils.LogError(logger, err, "failed to normalize test cases") - return nil - } - return nil - }, - } - if err := cmdConfigurator.AddFlags(normalizeCmd); err != nil { - utils.LogError(logger, err, "failed to add normalize cmd flags") - return nil - } - return normalizeCmd -} diff --git a/keploy/cli/provider/cmd.go b/keploy/cli/provider/cmd.go deleted file mode 100644 index 41622e8..0000000 --- a/keploy/cli/provider/cmd.go +++ /dev/null @@ -1,1079 +0,0 @@ -// Package provider provides functionality for the keploy provider. -package provider - -import ( - "context" - "errors" - "fmt" - "os" - "path/filepath" - - "strings" - "time" - - "gopkg.in/yaml.v3" - - "github.com/fatih/color" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/service/tools" - "go.keploy.io/server/v2/utils" - "go.keploy.io/server/v2/utils/log" - "go.uber.org/zap" -) - -func LogExample(example string) string { - return fmt.Sprintf("Example usage: %s", example) -} - -var CustomHelpTemplate = ` -{{if .Example}}Examples: -{{.Example}} -{{end}} -{{if .HasAvailableSubCommands}}Guided Commands:{{range .Commands}}{{if .IsAvailableCommand}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}} -{{end}} -{{if .HasAvailableFlags}}Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}} -{{end}} -Use "{{.CommandPath}} [command] --help" for more information about a command. -` - -var WithoutexampleOneClickInstall = ` -Note: If installed keploy without One Click Install, use "keploy example --customSetup true" -` -var Examples = ` -Golang Application - Record: - sudo -E env PATH=$PATH keploy record -c "/path/to/user/app/binary" - - Test: - sudo -E env PATH=$PATH keploy test -c "/path/to/user/app/binary" --delay 10 - -Node Application - Record: - sudo -E env PATH=$PATH keploy record -c “npm start --prefix /path/to/node/app" - - Test: - sudo -E env PATH=$PATH keploy test -c “npm start --prefix /path/to/node/app" --delay 10 - -Java - Record: - sudo -E env PATH=$PATH keploy record -c "java -jar /path/to/java-project/target/jar" - - Test: - sudo -E env PATH=$PATH keploy test -c "java -jar /path/to/java-project/target/jar" --delay 10 - -Docker - Alias: - alias keploy='sudo docker run --name keploy-ebpf -p 16789:16789 --privileged --pid=host -it -v $(pwd):$(pwd) -w $(pwd) -v /sys/fs/cgroup:/sys/fs/cgroup - -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/keploy/keploy' - - Record: - keploy record -c "docker run -p 8080:8080 --name --network " --buildDelay 60 - - Test: - keploy test -c "docker run -p 8080:8080 --name --network " --delay 10 --buildDelay 60 - -` - -var ExampleOneClickInstall = ` -Golang Application - Record: - keploy record -c "/path/to/user/app/binary" - - Test: - keploy test -c "/path/to/user/app/binary" --delay 10 - -Node Application - Record: - keploy record -c “npm start --prefix /path/to/node/app" - - Test: - keploy test -c “npm start --prefix /path/to/node/app" --delay 10 - -Java - Record: - keploy record -c "java -jar /path/to/java-project/target/jar" - - Test: - keploy test -c "java -jar /path/to/java-project/target/jar" --delay 10 - -Docker - Record: - keploy record -c "docker run -p 8080:8080 --name --network " --buildDelay 60 - - Test: - keploy test -c "docker run -p 8080:8080 --name --network " --delay 1 --buildDelay 60 -` - -var RootCustomHelpTemplate = `{{.Short}} - -Usage:{{if .Runnable}} - {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} - {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasExample}} - -Available Commands:{{range .Commands}}{{if .IsAvailableCommand}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableFlags}} - -Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableLocalFlags}} - -Guided Commands:{{range .Commands}}{{if and (not .IsAvailableCommand) (not .Hidden)}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}} - -Examples: -{{.Example}} - -Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} -` - -var RootExamples = ` - Record: - keploy record -c "docker run -p 8080:8080 --name --network keploy-network " --container-name "" --buildDelay 60 - - Test: - keploy test --c "docker run -p 8080:8080 --name --network keploy-network " --delay 10 --buildDelay 60 - - Config: - keploy config --generate -p "/path/to/localdir" -` - -var VersionTemplate = `{{with .Version}}{{printf "Keploy %s" .}}{{end}}{{"\n"}}` -var IsConfigFileFound = true - -type CmdConfigurator struct { - logger *zap.Logger - cfg *config.Config -} - -func NewCmdConfigurator(logger *zap.Logger, config *config.Config) *CmdConfigurator { - return &CmdConfigurator{ - logger: logger, - cfg: config, - } -} - -func (c *CmdConfigurator) AddFlags(cmd *cobra.Command) error { - //sets the displayment of flag-related errors - cmd.SilenceErrors = true - cmd.SetFlagErrorFunc(func(_ *cobra.Command, err error) error { - PrintLogo(os.Stdout, true) - color.Red(fmt.Sprintf("❌ error: %v", err)) - fmt.Println() - return err - }) - - //add flags - var err error - cmd.Flags().SetNormalizeFunc(aliasNormalizeFunc) - cmd.Flags().String("configPath", ".", "Path to the local directory where keploy configuration file is stored") - - // Flags for secure subcommands - if cmd.Parent() != nil && cmd.Parent().Name() == "secure" { - switch cmd.Name() { - case "add": - cmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") - case "remove": - cmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") - cmd.Flags().String("id", "", "ID of the custom check to remove") - case "update": - cmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") - cmd.Flags().String("id", "", "ID of the custom check to update") - case "list": - cmd.Flags().String("rule-set", "basic", "Specify which checks to list: 'basic' (built-in), 'custom'") - cmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") - } - return nil - } - - switch cmd.Name() { - case "secure": - cmd.Flags().String("base-url", "", "Base URL of the application to be tested.") - cmd.Flags().String("ts-path", "keploy/testsuite", "Directory path containing test suite YAML files.") - cmd.Flags().String("ts-file", "suite-0.yaml", "Name of the testsuite YAML file.") - cmd.Flags().String("rule-set", "basic", "Specify which checks to execute: 'basic' (built-in), 'custom'") - cmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") - case "load": - cmd.Flags().String("base-url", "", "Base URL of the application to be tested.") - cmd.Flags().String("ts-path", "keploy/testsuite", "Directory path containing test suite YAML files.") - cmd.Flags().String("ts-file", "suite-0.yaml", "Name of the testsuite YAML file.") - cmd.Flags().Int("vus", 1, "Number of virtual users to run.") - cmd.Flags().String("duration", "", "Time for the load test to keep running.") - cmd.Flags().Int("rps", 0, "Number of requests per second to run.") - case "testsuite": - cmd.Flags().String("base-url", "", "Base URL of the application to be tested.") - cmd.Flags().String("ts-path", "keploy/testsuite", "Directory path containing test suite YAML files.") - cmd.Flags().String("ts-file", "suite-0.yaml", "Name of the testsuite YAML file.") - - case "upload": //for uploading mocks - cmd.Flags().StringP("path", "p", ".", "Path to local keploy directory where generated mocks are stored") - cmd.Flags().StringSliceP("test-sets", "t", utils.Keys(c.cfg.Test.SelectedTests), "Testsets to consider e.g. -t \"test-set-1, test-set-2\"") - case "generate", "download": - - if cmd.Name() == "download" && cmd.Parent() != nil && cmd.Parent().Name() == "mock" { // for downloading mocks - cmd.Flags().StringP("path", "p", ".", "Path to local keploy directory where generated mocks are stored") - cmd.Flags().StringSliceP("test-sets", "t", utils.Keys(c.cfg.Test.SelectedTests), "Testsets to consider e.g. -t \"test-set-1, test-set-2\"") - return nil - } - - cmd.Flags().StringSliceP("services", "s", c.cfg.Contract.Services, "Specify the services for which to generate/download contracts") - cmd.Flags().StringSliceP("tests", "t", c.cfg.Contract.Tests, "Specify the tests for which to generate/download contracts") - cmd.Flags().StringP("path", "p", ".", "Specify the path to generate/download contracts") - if cmd.Name() == "download" { // for downloading contracts - cmd.Flags().String("driven", c.cfg.Contract.Driven, "Specify the path to download contracts") - } - - case "update", "export", "import": - return nil - case "postman": - cmd.Flags().StringP("path", "p", "", "Specify the path to the postman collection") - cmd.Flags().String("base-path", c.cfg.Test.BasePath, "basePath to hit the server while importing keploy tests from postman collection with no response in the collection") - case "normalize": - cmd.Flags().StringP("path", "p", ".", "Path to local directory where generated testcases/mocks/reports are stored") - cmd.Flags().String("test-run", "", "Test Run to be normalized") - cmd.Flags().String("tests", "", "Test Sets to be normalized") - case "config": - cmd.Flags().StringP("path", "p", ".", "Path to local directory where generated config is stored") - cmd.Flags().Bool("generate", false, "Generate a new keploy configuration file") - case "templatize": - cmd.Flags().StringP("path", "p", ".", "Path to local directory where generated testcases/mocks are stored") - cmd.Flags().StringSliceP("testsets", "t", c.cfg.Templatize.TestSets, "Testsets to run e.g. --testsets \"test-set-1, test-set-2\"") - case "gen": - cmd.Flags().String("source-file-path", "", "Path to the source file.") - cmd.Flags().String("test-file-path", "", "Path to the input test file.") - cmd.Flags().String("coverage-report-path", "coverage.xml", "Path to the code coverage report file.") - cmd.Flags().String("test-command", "", "The command to run tests and generate coverage report.") - cmd.Flags().String("coverage-format", "cobertura", "Type of coverage report.") - cmd.Flags().Int("expected-coverage", 80, "The desired coverage percentage.") - cmd.Flags().Int("max-iterations", 5, "The maximum number of iterations.") - cmd.Flags().String("test-dir", "", "Path to the test directory.") - cmd.Flags().String("llm-base-url", "", "Base URL for the AI model.") - cmd.Flags().String("model", "gpt-4o", "Model to use for the AI.") - cmd.Flags().String("llm-api-version", "", "API version of the llm") - cmd.Flags().String("additional-prompt", "", "Additional prompt to be used for the AI model.") - cmd.Flags().String("function-under-test", "", "The specific function for which tests will be generated.") - cmd.Flags().Bool("flakiness", false, "The flakiness check to run the passed tests for flakiness") - err := cmd.MarkFlagRequired("test-command") - if err != nil { - errMsg := "failed to mark testCommand as required flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - case "record", "test", "rerecord": - if cmd.Parent() != nil && cmd.Parent().Name() == "contract" { - cmd.Flags().StringSliceP("services", "s", c.cfg.Contract.Services, "Specify the services for which to generate contracts") - cmd.Flags().StringP("path", "p", ".", "Specify the path to generate contracts") - cmd.Flags().Bool("download", true, "Specify whether to download contracts or not") - cmd.Flags().Bool("generate", true, "Specify whether to generate schemas for the current service or not") - cmd.Flags().String("driven", c.cfg.Contract.Driven, "Specify the driven flag to validate contracts") - return nil - } - - cmd.Flags().StringP("path", "p", ".", "Path to local directory where generated testcases/mocks are stored") - cmd.Flags().Uint32("proxy-port", c.cfg.ProxyPort, "Port used by the Keploy proxy server to intercept the outgoing dependency calls") - cmd.Flags().Uint32("dns-port", c.cfg.DNSPort, "Port used by the Keploy DNS server to intercept the DNS queries") - cmd.Flags().StringP("command", "c", c.cfg.Command, "Command to start the user application") - cmd.Flags().String("cmd-type", c.cfg.CommandType, "Type of command to start the user application (native/docker/docker-compose)") - cmd.Flags().Uint64P("build-delay", "b", c.cfg.BuildDelay, "User provided time to wait docker container build") - cmd.Flags().String("container-name", c.cfg.ContainerName, "Name of the application's docker container") - cmd.Flags().StringP("network-name", "n", c.cfg.NetworkName, "Name of the application's docker network") - cmd.Flags().UintSlice("pass-through-ports", config.GetByPassPorts(c.cfg), "Ports to bypass the proxy server and ignore the traffic") - cmd.Flags().Uint64P("app-id", "a", c.cfg.AppID, "A unique name for the user's application") - cmd.Flags().String("app-name", c.cfg.AppName, "Name of the user's application") - cmd.Flags().Bool("generate-github-actions", c.cfg.GenerateGithubActions, "Generate Github Actions workflow file") - cmd.Flags().Bool("in-ci", c.cfg.InCi, "is CI Running or not") - //add rest of the uncommon flags for record, test, rerecord commands - c.AddUncommonFlags(cmd) - - case "report": - cmd.Flags().StringSliceP("test-sets", "t", utils.Keys(c.cfg.Test.SelectedTests), "Testsets to report e.g. --testsets \"test-set-1, test-set-2\"") - cmd.Flags().StringP("path", "p", ".", "Path to local directory where generated testcases/mocks are stored") - - case "keploy": - cmd.PersistentFlags().Bool("debug", c.cfg.Debug, "Run in debug mode") - cmd.PersistentFlags().Bool("disable-tele", c.cfg.DisableTele, "Run in telemetry mode") - cmd.PersistentFlags().Bool("disable-ansi", c.cfg.DisableANSI, "Disable ANSI color in logs") - err = cmd.PersistentFlags().MarkHidden("disable-tele") - if err != nil { - errMsg := "failed to mark telemetry as hidden flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - cmd.PersistentFlags().Bool("enable-testing", c.cfg.EnableTesting, "Enable testing keploy with keploy") - err = cmd.PersistentFlags().MarkHidden("enable-testing") - if err != nil { - errMsg := "failed to mark enableTesting as hidden flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - default: - return errors.New("unknown command name") - } - - return nil -} - -func (c *CmdConfigurator) AddUncommonFlags(cmd *cobra.Command) { - switch cmd.Name() { - case "record": - cmd.Flags().Duration("record-timer", 0, "User provided time to record its application (e.g., \"5s\" for 5 seconds, \"1m\" for 1 minute)") - cmd.Flags().String("base-path", c.cfg.Record.BasePath, "Base URL to hit the server while recording the testcases") - cmd.Flags().String("metadata", c.cfg.Record.Metadata, "Metadata to be stored in config.yaml as key-value pairs (e.g., \"key1=value1,key2=value2\")") - case "test", "rerecord": - cmd.Flags().StringSliceP("test-sets", "t", utils.Keys(c.cfg.Test.SelectedTests), "Testsets to run e.g. --testsets \"test-set-1, test-set-2\"") - cmd.Flags().String("host", c.cfg.Test.Host, "Custom host to replace the actual host in the testcases") - cmd.Flags().Uint32("port", c.cfg.Test.Port, "Custom port to replace the actual port in the testcases") - cmd.Flags().Uint64P("delay", "d", 5, "User provided time to run its application") - if cmd.Name() == "test" { - cmd.Flags().Uint64("api-timeout", c.cfg.Test.APITimeout, "User provided timeout for calling its application") - cmd.Flags().String("mongo-password", c.cfg.Test.MongoPassword, "Authentication password for mocking MongoDB conn") - cmd.Flags().String("coverage-report-path", c.cfg.Test.CoverageReportPath, "Write a go coverage profile to the file in the given directory.") - cmd.Flags().VarP(&c.cfg.Test.Language, "language", "l", "Application programming language") - cmd.Flags().Bool("ignore-ordering", c.cfg.Test.IgnoreOrdering, "Ignore ordering of array in response") - cmd.Flags().Bool("skip-coverage", c.cfg.Test.SkipCoverage, "skip code coverage computation while running the test cases") - cmd.Flags().Bool("remove-unused-mocks", c.cfg.Test.RemoveUnusedMocks, "Clear the unused mocks for the passed test-sets") - cmd.Flags().Bool("fallBack-on-miss", c.cfg.Test.FallBackOnMiss, "Enable connecting to actual service if mock not found during test mode") - cmd.Flags().String("jacoco-agent-path", c.cfg.Test.JacocoAgentPath, "Only applicable for test coverage for Java projects. You can override the jacoco agent jar by proving its path") - cmd.Flags().String("base-path", c.cfg.Test.BasePath, "Custom api basePath/origin to replace the actual basePath/origin in the testcases; App flag is ignored and app will not be started & instrumented when this is set since the application running on a different machine") - cmd.Flags().Bool("update-template", c.cfg.Test.UpdateTemplate, "Update the template with the result of the testcases.") - cmd.Flags().Bool("mocking", true, "enable/disable mocking for the testcases") - cmd.Flags().Bool("disableMockUpload", c.cfg.Test.DisableMockUpload, "Store/Fetch mocks locally") - cmd.Flags().Bool("useLocalMock", false, "Use local mocks instead of fetching from the cloud") - cmd.Flags().Bool("disable-line-coverage", c.cfg.Test.DisableLineCoverage, "Disable line coverage generation.") - cmd.Flags().Bool("must-pass", c.cfg.Test.MustPass, "enforces that the tests must pass, if it doesn't, remove failing testcases") - cmd.Flags().Uint32Var(&c.cfg.Test.MaxFailAttempts, "max-fail-attempts", 5, "maximum number of testset failure that can be allowed during must-pass mode") - cmd.Flags().Uint32Var(&c.cfg.Test.MaxFlakyChecks, "flaky-check-retry", 1, "maximum number of retries to check for flakiness") - } - } -} - -func aliasNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName { - var flagNameMapping = map[string]string{ - "testsets": "test-sets", - "delay": "delay", - "apiTimeout": "api-timeout", - "mongoPassword": "mongo-password", - "coverageReportPath": "coverage-report-path", - "language": "language", - "ignoreOrdering": "ignore-ordering", - "coverage": "coverage", - "removeUnusedMocks": "remove-unused-mocks", - "goCoverage": "go-coverage", - "fallBackOnMiss": "fallBack-on-miss", - "basePath": "base-path", - "updateTemplate": "update-template", - "mocking": "mocking", - "sourceFilePath": "source-file-path", - "testFilePath": "test-file-path", - "testCommand": "test-command", - "coverageFormat": "coverage-format", - "expectedCoverage": "expected-coverage", - "maxIterations": "max-iterations", - "testDir": "test-dir", - "llmBaseUrl": "llm-base-url", - "model": "model", - "llmApiVersion": "llm-api-version", - "configPath": "config-path", - "path": "path", - "port": "port", - "proxyPort": "proxy-port", - "dnsPort": "dns-port", - "command": "command", - "cmdType": "cmd-type", - "buildDelay": "build-delay", - "containerName": "container-name", - "networkName": "network-name", - "passThroughPorts": "pass-through-ports", - "appId": "app-id", - "appName": "app-name", - "generateGithubActions": "generate-github-actions", - "disableTele": "disable-tele", - "disableANSI": "disable-ansi", - "selectedTests": "selected-tests", - "testReport": "test-report", - "enableTesting": "enable-testing", - "inDocker": "in-docker", - "keployContainer": "keploy-container", - "keployNetwork": "keploy-network", - "recordTimer": "record-timer", - "urlMethods": "url-methods", - "inCi": "in-ci", - } - - if newName, ok := flagNameMapping[name]; ok { - name = newName - } - return pflag.NormalizedName(name) -} - -func (c *CmdConfigurator) Validate(ctx context.Context, cmd *cobra.Command) error { - err := isCompatible(c.logger) - if err != nil { - return err - } - defaultCfg := *c.cfg - err = c.PreProcessFlags(cmd) - if err != nil { - c.logger.Error("failed to preprocess flags", zap.Error(err)) - return err - } - err = c.ValidateFlags(ctx, cmd) - if err != nil { - if err == c.noCommandError() { - utils.LogError(c.logger, nil, "missing required -c flag or appCmd in config file") - if c.cfg.InDocker { - c.logger.Info(`Example usage: keploy test -c "docker run -p 8080:8080 --network myNetworkName myApplicationImageName" --delay 6`) - } else { - c.logger.Info(LogExample(RootExamples)) - } - } - c.logger.Error("failed to validate flags", zap.Error(err)) - return err - } - - appName, err := utils.GetLastDirectory() - if err != nil { - return fmt.Errorf("failed to get the last directory for appName: %v", err) - } - - if c.cfg.AppName == "" { - c.logger.Info("Using the last directory name as appName : " + appName) - c.cfg.AppName = appName - } else if c.cfg.AppName != appName { - c.logger.Warn("AppName in config (" + c.cfg.AppName + ") does not match current directory name (" + appName + "). using current directory name as appName") - c.cfg.AppName = appName - } - - if !IsConfigFileFound { - err := c.CreateConfigFile(ctx, defaultCfg) - if err != nil { - c.logger.Error("failed to create config file", zap.Error(err)) - return err - } - } - return nil -} - -func (c *CmdConfigurator) PreProcessFlags(cmd *cobra.Command) error { - // used to bind common flags for commands like record, test. For eg: PATH, PORT, COMMAND etc. - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - errMsg := "failed to bind flags to config" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - // used to bind flags with environment variables - viper.AutomaticEnv() - viper.SetEnvPrefix("KEPLOY") - - //used to bind flags specific to the command for eg: testsets, delay, recordTimer etc. (nested flags) - err = utils.BindFlagsToViper(c.logger, cmd, "") - if err != nil { - errMsg := "failed to bind cmd specific flags to viper" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - configPath, err := cmd.Flags().GetString("configPath") - if err != nil { - utils.LogError(c.logger, nil, "failed to read the config path") - return err - } - viper.SetConfigName("keploy") - viper.SetConfigType("yml") - viper.AddConfigPath(configPath) - if err := viper.ReadInConfig(); err != nil { - var configFileNotFoundError viper.ConfigFileNotFoundError - if !errors.As(err, &configFileNotFoundError) { - errMsg := "failed to read config file" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - IsConfigFileFound = false - c.logger.Info("config file not found; proceeding with flags only") - } - - if err := viper.Unmarshal(c.cfg); err != nil { - errMsg := "failed to unmarshal the config" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - c.cfg.ConfigPath = configPath - return nil -} -func (c *CmdConfigurator) ValidateFlags(ctx context.Context, cmd *cobra.Command) error { - disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) - PrintLogo(os.Stdout, disableAnsi) - if c.cfg.Debug { - logger, err := log.ChangeLogLevel(zap.DebugLevel) - *c.logger = *logger - if err != nil { - errMsg := "failed to change log level" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - } - - if c.cfg.Record.BasePath != "" { - port, err := pkg.ExtractPort(c.cfg.Record.BasePath) - if err != nil { - errMsg := "failed to extract port from base URL" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.Port = port - c.cfg.E2E = true - } - - if c.cfg.EnableTesting { - // Add mode to logger to debug the keploy during testing - logger, err := log.AddMode(cmd.Name()) - *c.logger = *logger - if err != nil { - errMsg := "failed to add mode to logger" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.DisableTele = true - } - - if c.cfg.DisableANSI { - logger, err := log.ChangeColorEncoding() - models.IsAnsiDisabled = true - *c.logger = *logger - if err != nil { - errMsg := "failed to change color encoding" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.logger.Info("Color encoding is disabled") - } - - c.logger.Debug("config has been initialised", zap.Any("for cmd", cmd.Name()), zap.Any("config", c.cfg)) - - switch cmd.Name() { - - case "secure": - baseURL, err := cmd.Flags().GetString("base-url") - if err != nil { - errMsg := "failed to get base-url flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.TestSuite.BaseURL = baseURL - - tsPath, err := cmd.Flags().GetString("ts-path") - if err != nil { - errMsg := "failed to get ts-path flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.TestSuite.TSPath = tsPath - - file, err := cmd.Flags().GetString("ts-file") - if err != nil { - errMsg := "failed to get ts-file flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.TestSuite.TSFile = file - - case "load": - baseURL, err := cmd.Flags().GetString("base-url") - if err != nil { - errMsg := "failed to get base-url flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.TestSuite.BaseURL = baseURL - - tsPath, err := cmd.Flags().GetString("ts-path") - if err != nil { - errMsg := "failed to get ts-path flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.TestSuite.TSPath = tsPath - - file, err := cmd.Flags().GetString("ts-file") - if err != nil { - errMsg := "failed to get ts-file flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.TestSuite.TSFile = file - - _, err = cmd.Flags().GetInt("vus") - if err != nil { - errMsg := "failed to get vus flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - _, err = cmd.Flags().GetString("duration") - if err != nil { - errMsg := "failed to get duration flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - _, err = cmd.Flags().GetInt("rps") - if err != nil { - errMsg := "failed to get rps flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - case "testsuite": - baseURL, err := cmd.Flags().GetString("base-url") - if err != nil { - errMsg := "failed to get base-url flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.TestSuite.BaseURL = baseURL - - tsPath, err := cmd.Flags().GetString("ts-path") - if err != nil { - errMsg := "failed to get ts-path flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.TestSuite.TSPath = tsPath - - tsFile, err := cmd.Flags().GetString("ts-file") - if err != nil { - errMsg := "failed to get ts-file flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.TestSuite.TSFile = tsFile - case "upload": //for uploading mocks - path, err := cmd.Flags().GetString("path") - if err != nil { - errMsg := "failed to get the path" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.Path = utils.ToAbsPath(c.logger, path) - - testSets, err := cmd.Flags().GetStringSlice("test-sets") - if err != nil { - errMsg := "failed to get the test-sets" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - config.SetSelectedTests(c.cfg, testSets) - - case "report": - path, err := cmd.Flags().GetString("path") - if err != nil { - errMsg := "failed to get the path" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.Path = utils.ToAbsPath(c.logger, path) - - testSets, err := cmd.Flags().GetStringSlice("test-sets") - if err != nil { - errMsg := "failed to get the test-sets" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - config.SetSelectedTestSets(c.cfg, testSets) - - case "generate", "download": - - if cmd.Name() == "download" && cmd.Parent() != nil && cmd.Parent().Name() == "mock" { - path, err := cmd.Flags().GetString("path") - if err != nil { - errMsg := "failed to get the path" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.Path = utils.ToAbsPath(c.logger, path) - - testSets, err := cmd.Flags().GetStringSlice("testsets") - if err != nil { - errMsg := "failed to get the testsets" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - config.SetSelectedTests(c.cfg, testSets) - return nil - } - - path, err := cmd.Flags().GetString("path") - if err != nil { - errMsg := "failed to get the path" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - c.cfg.Contract.Path = utils.ToAbsPath(c.logger, path) - - services, err := cmd.Flags().GetStringSlice("services") - if err != nil { - errMsg := "failed to get the services" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - config.SetSelectedServices(c.cfg, services) - - selectedTests, err := cmd.Flags().GetStringSlice("tests") - if err != nil { - errMsg := "failed to get the tests" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - config.SetSelectedContractTests(c.cfg, selectedTests) - - if cmd.Name() == "download" { - c.cfg.Contract.Driven, err = cmd.Flags().GetString("driven") - if err != nil { - errMsg := "failed to get the driven flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - } - - c.cfg.Path = utils.ToAbsPath(c.logger, path) - - case "config": - path, err := cmd.Flags().GetString("path") - if err != nil { - errMsg := "failed to get the path" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.Path, err = utils.GetAbsPath(path) - if err != nil { - errMsg := "failed to get the absolute path" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - case "record", "test", "rerecord": - - if cmd.Parent() != nil && cmd.Parent().Name() == "contract" { - path, err := cmd.Flags().GetString("path") - if err != nil { - errMsg := "failed to get the path" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - c.cfg.Contract.Path = utils.ToAbsPath(c.logger, path) - - services, err := cmd.Flags().GetStringSlice("services") - if err != nil { - errMsg := "failed to get the services" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - config.SetSelectedServices(c.cfg, services) - - c.cfg.Contract.Download, err = cmd.Flags().GetBool("download") - if err != nil { - errMsg := "failed to get the download flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.Contract.Generate, err = cmd.Flags().GetBool("generate") - if err != nil { - errMsg := "failed to get the generate flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.Contract.Driven, err = cmd.Flags().GetString("driven") - if err != nil { - errMsg := "failed to get the driven flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - c.cfg.Path = utils.ToAbsPath(c.logger, path) - return nil - } - - // set the command type - c.cfg.CommandType = string(utils.FindDockerCmd(c.cfg.Command)) - - // empty the command if base path is provided, because no need of command even if provided - if c.cfg.Test.BasePath != "" { - c.cfg.CommandType = string(utils.Empty) - c.cfg.Command = "" - } - - if c.cfg.GenerateGithubActions && utils.CmdType(c.cfg.CommandType) != utils.Empty { - defer utils.GenerateGithubActions(c.logger, c.cfg.Command) - } - if c.cfg.InDocker { - c.logger.Info("detected that Keploy is running in a docker container") - if len(c.cfg.Path) > 0 { - curDir, err := os.Getwd() - if err != nil { - errMsg := "failed to get current working directory" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - if strings.Contains(c.cfg.Path, "..") { - - c.cfg.Path, err = utils.GetAbsPath(filepath.Clean(c.cfg.Path)) - if err != nil { - return fmt.Errorf("failed to get the absolute path from relative path: %w", err) - } - - relativePath, err := filepath.Rel(curDir, c.cfg.Path) - if err != nil { - errMsg := "failed to get the relative path from absolute path" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - if relativePath == ".." || strings.HasPrefix(relativePath, "../") { - errMsg := "path provided is not a subdirectory of current directory. Keploy only supports recording testcases in the current directory or its subdirectories" - utils.LogError(c.logger, err, errMsg, zap.String("path:", c.cfg.Path)) - return errors.New(errMsg) - } - } - } - // check if the buildDelay is less than 30 seconds - if time.Duration(c.cfg.BuildDelay)*time.Second <= 30*time.Second { - c.logger.Warn(fmt.Sprintf("buildDelay is set to %v, incase your docker container takes more time to build use --buildDelay to set custom delay", c.cfg.BuildDelay)) - c.logger.Info(`Example usage: keploy record -c "docker-compose up --build" --buildDelay 35`) - } - if utils.CmdType(c.cfg.Command) == utils.DockerCompose { - if c.cfg.ContainerName == "" { - utils.LogError(c.logger, nil, "Couldn't find containerName") - c.logger.Info(`Example usage: keploy record -c "docker run -p 8080:8080 --network myNetworkName myApplicationImageName" --delay 6`) - return errors.New("missing required --container-name flag or containerName in config file") - } - } - } - err := StartInDocker(ctx, c.logger, c.cfg) - if err != nil { - return err - } - - absPath, err := utils.GetAbsPath(c.cfg.Path) - if err != nil { - utils.LogError(c.logger, err, "error while getting absolute path") - return errors.New("failed to get the absolute path") - } - c.cfg.Path = absPath + "/keploy" - - // handle the app command - if c.cfg.Command == "" { - if !alreadyRunning(cmd.Name(), c.cfg.Test.BasePath) { - return c.noCommandError() - } - } - - bypassPorts, err := cmd.Flags().GetUintSlice("passThroughPorts") - if err != nil { - errMsg := "failed to read the ports of outgoing calls to be ignored" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - config.SetByPassPorts(c.cfg, bypassPorts) - - if cmd.Name() == "record" { - metadata, err := cmd.Flags().GetString("metadata") - if err != nil { - errMsg := "failed to get the metadata flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.Record.Metadata = metadata - } - - if cmd.Name() == "test" || cmd.Name() == "rerecord" { - //check if the keploy folder exists - if _, err := os.Stat(c.cfg.Path); os.IsNotExist(err) { - recordCmd := models.HighlightGrayString("keploy record") - errMsg := fmt.Sprintf("No test-sets found. Please record testcases using %s command", recordCmd) - utils.LogError(c.logger, nil, errMsg) - return errors.New(errMsg) - } - - testSets, err := cmd.Flags().GetStringSlice("testsets") - if err != nil { - errMsg := "failed to get the testsets" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - config.SetSelectedTests(c.cfg, testSets) - if cmd.Name() == "rerecord" { - c.cfg.Test.SkipCoverage = true - host, err := cmd.Flags().GetString("host") - if err != nil { - errMsg := "failed to get the provided host" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.ReRecord.Host = host - port, err := cmd.Flags().GetUint32("port") - if err != nil { - errMsg := "failed to get the provided port" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - c.cfg.ReRecord.Port = port - c.cfg.Test.Delay, err = cmd.Flags().GetUint64("delay") - if err != nil { - errMsg := "failed to get the provided delay" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - return nil - } - - // enforce that the test-sets are provided when --must-pass is set to true - // to prevent accidental deletion of failed testcases in testsets which was due to application changes - // and not due to flakiness or our internal issue. - mustPass, err := cmd.Flags().GetBool("must-pass") - if err != nil { - errMsg := "failed to get the must-pass flag" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - if mustPass { - c.cfg.Test.SkipCoverage = true - c.cfg.Test.DisableMockUpload = true - } - - // in mustpass mode, set the maxFlakyChecks count to 3 explicitly, - // if it is not set through cmd flag. - if mustPass && !cmd.Flags().Changed("flaky-check-retry") { - c.cfg.Test.MaxFlakyChecks = 3 - } - - // if the user passes a value for this field, store it - if cmd.Flags().Changed("flaky-check-retry") { - c.cfg.Test.MaxFlakyChecks, err = cmd.Flags().GetUint32("flaky-check-retry") - if err != nil { - errMsg := "failed to get the provided flaky-check-retry count" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - } - - // if the user passes a value for this field, store it - if cmd.Flags().Changed("max-fail-attempts") { - c.cfg.Test.MaxFailAttempts, err = cmd.Flags().GetUint32("max-fail-attempts") - if err != nil { - errMsg := "failed to get the provided max-fail-attempts count" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - } - - // don't allow zero maxFlakyChecks and if must pass mode is enabled, then maxFailAttempts can't be zero. - if c.cfg.Test.MaxFlakyChecks == 0 { - return fmt.Errorf("value for maxFlakyChecks cannot be zero") - } - if mustPass && c.cfg.Test.MaxFailAttempts == 0 { - return fmt.Errorf("in must pass mode, value for maxFailAttempts cannot be zero") - } - - if mustPass && !cmd.Flags().Changed("test-sets") { - return fmt.Errorf("--test-sets flag must be set to use --must-pass=true") - } - - // skip coverage by default if command is of type docker - if utils.CmdType(c.cfg.CommandType) != "native" && !cmd.Flags().Changed("skip-coverage") { - c.cfg.Test.SkipCoverage = true - } - - if c.cfg.Test.Delay <= 5 { - c.logger.Warn(fmt.Sprintf("Delay is set to %d seconds, incase your app takes more time to start use --delay to set custom delay", c.cfg.Test.Delay)) - if c.cfg.InDocker { - c.logger.Info(`Example usage: keploy test -c "docker run -p 8080:8080 --network myNetworkName myApplicationImageName" --delay 6`) - } else { - c.logger.Info("Example usage: " + cmd.Example) - } - } - } - - case "normalize": - c.cfg.Path = utils.ToAbsPath(c.logger, c.cfg.Path) - tests, err := cmd.Flags().GetString("tests") - if err != nil { - errMsg := "failed to read tests to be normalized" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - err = config.SetSelectedTestsNormalize(c.cfg, tests) - if err != nil { - errMsg := "failed to normalize the selected tests" - utils.LogError(c.logger, err, errMsg) - return errors.New(errMsg) - } - - case "templatize": - c.cfg.Path = utils.ToAbsPath(c.logger, c.cfg.Path) - case "gen": - if os.Getenv("API_KEY") == "" { - utils.LogError(c.logger, nil, "API_KEY is not set") - return errors.New("API_KEY is not set") - } - if (c.cfg.Gen.SourceFilePath == "" && c.cfg.Gen.TestFilePath != "") || c.cfg.Gen.SourceFilePath != "" && c.cfg.Gen.TestFilePath == "" { - utils.LogError(c.logger, nil, "One of the SourceFilePath and TestFilePath is mentioned. Either provide both or neither") - return errors.New("sourceFilePath and testFilePath misconfigured") - } else if c.cfg.Gen.SourceFilePath == "" && c.cfg.Gen.TestFilePath == "" { - if c.cfg.Gen.TestDir == "" { - utils.LogError(c.logger, nil, "TestDir is not set, Please specify the test directory") - return errors.New("TestDir is not set") - } - } - } - - return nil -} - -func (c *CmdConfigurator) CreateConfigFile(ctx context.Context, defaultCfg config.Config) error { - defaultCfg = c.UpdateConfigData(defaultCfg) - toolSvc := tools.NewTools(c.logger, nil, nil, nil, nil, nil) - configData := defaultCfg - configDataBytes, err := yaml.Marshal(configData) - if err != nil { - utils.LogError(c.logger, err, "failed to marshal config data") - return errors.New("failed to marshal config data") - } - err = toolSvc.CreateConfig(ctx, c.cfg.ConfigPath+"/keploy.yml", string(configDataBytes)) - if err != nil { - utils.LogError(c.logger, err, "failed to create config file") - return errors.New("failed to create config file") - } - c.logger.Info("Generated config file based on the flags that are used") - return nil -} - -func (c *CmdConfigurator) UpdateConfigData(defaultCfg config.Config) config.Config { - defaultCfg.Command = c.cfg.Command - defaultCfg.Test.Delay = c.cfg.Test.Delay - defaultCfg.AppName = c.cfg.AppName - defaultCfg.Test.APITimeout = c.cfg.Test.APITimeout - defaultCfg.ContainerName = c.cfg.ContainerName - defaultCfg.Test.IgnoreOrdering = c.cfg.Test.IgnoreOrdering - defaultCfg.Test.Language = c.cfg.Test.Language - defaultCfg.DisableANSI = c.cfg.DisableANSI - defaultCfg.Test.SkipCoverage = c.cfg.Test.SkipCoverage - defaultCfg.Test.Mocking = c.cfg.Test.Mocking - defaultCfg.Test.DisableLineCoverage = c.cfg.Test.DisableLineCoverage - - defaultCfg.TestSuite.TSPath = c.cfg.TestSuite.TSPath - defaultCfg.TestSuite.TSFile = c.cfg.TestSuite.TSFile - return defaultCfg -} diff --git a/keploy/cli/provider/common.go b/keploy/cli/provider/common.go deleted file mode 100644 index 807d8da..0000000 --- a/keploy/cli/provider/common.go +++ /dev/null @@ -1,20 +0,0 @@ -package provider - -import ( - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/storage" - "go.keploy.io/server/v2/pkg/platform/yaml/configdb/testset" - mockdb "go.keploy.io/server/v2/pkg/platform/yaml/mockdb" - openapidb "go.keploy.io/server/v2/pkg/platform/yaml/openapidb" - reportdb "go.keploy.io/server/v2/pkg/platform/yaml/reportdb" - testdb "go.keploy.io/server/v2/pkg/platform/yaml/testdb" -) - -type commonPlatformServices struct { - YamlTestDB *testdb.TestYaml - YamlMockDb *mockdb.MockYaml - YamlOpenAPIDb *openapidb.OpenAPIYaml - YamlReportDb *reportdb.TestReport - YamlTestSetDB *testset.Db[*models.TestSet] - Storage *storage.Storage -} diff --git a/keploy/cli/provider/compat_linux.go b/keploy/cli/provider/compat_linux.go deleted file mode 100644 index 1951cd5..0000000 --- a/keploy/cli/provider/compat_linux.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build linux - -package provider - -import ( - "errors" - - "github.com/moby/moby/pkg/parsers/kernel" - "go.uber.org/zap" -) - -func isCompatible(logger *zap.Logger) error { - //check if the version of the kernel is above 5.10 for eBPF support - isValid := kernel.CheckKernelVersion(5, 10, 0) - if !isValid { - c, err := kernel.GetKernelVersion() - if err != nil { - logger.Error("Error getting kernel version", zap.Error(err)) - return err - } - errMsg := "detected linux kernel version" + c.String() + ". Keploy requires linux kernel version 5.10 or above. Please upgrade your kernel or docker version.\n" - logger.Error(errMsg) - return errors.New(errMsg) - } - // TODO check for cgroup v2 support - return nil -} diff --git a/keploy/cli/provider/compat_others.go b/keploy/cli/provider/compat_others.go deleted file mode 100644 index 5d47879..0000000 --- a/keploy/cli/provider/compat_others.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !linux - -// This is a placeholder file for other OSes. - -package provider - -import "go.uber.org/zap" - -func isCompatible(logger *zap.Logger) error { - return nil -} diff --git a/keploy/cli/provider/core_service_linux.go b/keploy/cli/provider/core_service_linux.go deleted file mode 100644 index 3f1773c..0000000 --- a/keploy/cli/provider/core_service_linux.go +++ /dev/null @@ -1,125 +0,0 @@ -//go:build linux - -package provider - -import ( - "context" - "errors" - "fmt" - "path/filepath" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/core" - "go.keploy.io/server/v2/pkg/core/hooks" - "go.keploy.io/server/v2/pkg/core/proxy" - "go.keploy.io/server/v2/pkg/core/tester" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/docker" - "go.keploy.io/server/v2/pkg/platform/storage" - "go.keploy.io/server/v2/pkg/platform/telemetry" - "go.keploy.io/server/v2/pkg/platform/yaml/configdb/testset" - mockdb "go.keploy.io/server/v2/pkg/platform/yaml/mockdb" - openapidb "go.keploy.io/server/v2/pkg/platform/yaml/openapidb" - reportdb "go.keploy.io/server/v2/pkg/platform/yaml/reportdb" - testdb "go.keploy.io/server/v2/pkg/platform/yaml/testdb" - "go.keploy.io/server/v2/pkg/service" - "go.keploy.io/server/v2/pkg/service/contract" - "go.keploy.io/server/v2/pkg/service/orchestrator" - "go.keploy.io/server/v2/pkg/service/record" - "go.keploy.io/server/v2/pkg/service/replay" - "go.keploy.io/server/v2/pkg/service/report" - "go.keploy.io/server/v2/pkg/service/tools" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type CommonInternalService struct { - commonPlatformServices - Instrumentation *core.Core -} - -func Get(ctx context.Context, cmd string, cfg *config.Config, logger *zap.Logger, tel *telemetry.Telemetry, auth service.Auth) (interface{}, error) { - commonServices, err := GetCommonServices(ctx, cfg, logger) - if err != nil { - return nil, err - } - contractSvc := contract.New(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlOpenAPIDb, cfg) - recordSvc := record.New(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, tel, commonServices.Instrumentation, commonServices.YamlTestSetDB, cfg) - replaySvc := replay.NewReplayer(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlReportDb, commonServices.YamlTestSetDB, tel, commonServices.Instrumentation, auth, commonServices.Storage, cfg) - toolsSvc := tools.NewTools(logger, commonServices.YamlTestSetDB, commonServices.YamlTestDB, tel, auth, cfg) - reportSvc := report.New(logger, cfg, commonServices.YamlReportDb, commonServices.YamlTestDB) - switch cmd { - case "rerecord": - return orchestrator.New(logger, recordSvc, toolsSvc, replaySvc, cfg), nil - case "record": - return recordSvc, nil - case "test", "normalize", "mock": - return replaySvc, nil - case "templatize", "config", "update", "login", "export", "import": - return toolsSvc, nil - case "contract": - return contractSvc, nil - case "report": - return reportSvc, nil - default: - return nil, errors.New("invalid command") - } - -} - -func GetCommonServices(_ context.Context, c *config.Config, logger *zap.Logger) (*CommonInternalService, error) { - - h := hooks.NewHooks(logger, c) - p := proxy.New(logger, h, c) - //for keploy test bench - t := tester.New(logger, h) - - var client docker.Client - var err error - if utils.IsDockerCmd(utils.CmdType(c.CommandType)) { - client, err = docker.New(logger) - if err != nil { - utils.LogError(logger, err, "failed to create docker client") - } - - //parse docker command only in case of docker start or docker run commands - if utils.CmdType(c.CommandType) != utils.DockerCompose { - cont, net, err := docker.ParseDockerCmd(c.Command, utils.CmdType(c.CommandType), client) - logger.Debug("container and network parsed from command", zap.String("container", cont), zap.String("network", net), zap.String("command", c.Command)) - if err != nil { - utils.LogError(logger, err, "failed to parse container name from given docker command", zap.String("cmd", c.Command)) - } - if c.ContainerName != "" && c.ContainerName != cont { - logger.Warn(fmt.Sprintf("given app container:(%v) is different from parsed app container:(%v), taking parsed value", c.ContainerName, cont)) - } - c.ContainerName = cont - - if c.NetworkName != "" && c.NetworkName != net { - logger.Warn(fmt.Sprintf("given docker network:(%v) is different from parsed docker network:(%v), taking parsed value", c.NetworkName, net)) - } - c.NetworkName = net - - logger.Debug("Using container and network", zap.String("container", c.ContainerName), zap.String("network", c.NetworkName)) - } - } - - instrumentation := core.New(logger, h, p, t, client) - - testDB := testdb.New(logger, c.Path) - mockDB := mockdb.New(logger, c.Path, "") - openAPIdb := openapidb.New(logger, filepath.Join(c.Path, "schema")) - reportDB := reportdb.New(logger, c.Path+"/reports") - testSetDb := testset.New[*models.TestSet](logger, c.Path) - storage := storage.New(c.APIServerURL, logger) - return &CommonInternalService{ - commonPlatformServices{ - YamlTestDB: testDB, - YamlMockDb: mockDB, - YamlOpenAPIDb: openAPIdb, - YamlReportDb: reportDB, - YamlTestSetDB: testSetDb, - Storage: storage, - }, - instrumentation, - }, nil -} diff --git a/keploy/cli/provider/core_service_others.go b/keploy/cli/provider/core_service_others.go deleted file mode 100644 index 4b982aa..0000000 --- a/keploy/cli/provider/core_service_others.go +++ /dev/null @@ -1,83 +0,0 @@ -//go:build !linux - -package provider - -import ( - "context" - "errors" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/core" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/storage" - "go.keploy.io/server/v2/pkg/platform/telemetry" - "go.keploy.io/server/v2/pkg/platform/yaml/configdb/testset" - mockdb "go.keploy.io/server/v2/pkg/platform/yaml/mockdb" - openapidb "go.keploy.io/server/v2/pkg/platform/yaml/openapidb" - reportdb "go.keploy.io/server/v2/pkg/platform/yaml/reportdb" - testdb "go.keploy.io/server/v2/pkg/platform/yaml/testdb" - - "go.keploy.io/server/v2/pkg/service" - "go.keploy.io/server/v2/pkg/service/contract" - "go.keploy.io/server/v2/pkg/service/replay" - "go.keploy.io/server/v2/pkg/service/report" - "go.keploy.io/server/v2/pkg/service/tools" - "go.uber.org/zap" -) - -type CommonInternalService struct { - commonPlatformServices - Instrumentation *core.Core -} - -func Get(ctx context.Context, cmd string, c *config.Config, logger *zap.Logger, tel *telemetry.Telemetry, auth service.Auth) (interface{}, error) { - commonServices, err := GetCommonServices(ctx, c, logger) - if err != nil { - return nil, err - } - contractSvc := contract.New(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlOpenAPIDb, c) - - replaySvc := replay.NewReplayer(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlReportDb, commonServices.YamlTestSetDB, tel, commonServices.Instrumentation, auth, commonServices.Storage, c) - - toolsSvc := tools.NewTools(logger, commonServices.YamlTestSetDB, commonServices.YamlTestDB, tel, auth, c) - reportSvc := report.New(logger, c, commonServices.YamlReportDb, commonServices.YamlTestDB) - - if (cmd == "test" && c.Test.BasePath != "") || cmd == "normalize" || cmd == "mock" { - return replaySvc, nil - } - - if cmd == "templatize" || cmd == "config" || cmd == "update" || cmd == "login" || cmd == "export" || cmd == "import" { - return toolsSvc, nil - } - - if cmd == "contract" { - return contractSvc, nil - } - - if cmd == "report" { - return reportSvc, nil - } - - return nil, errors.New("command not supported in non linux os. if you are on windows or mac, please use the dockerized version of your application") -} - -func GetCommonServices(_ context.Context, c *config.Config, logger *zap.Logger) (*CommonInternalService, error) { - instrumentation := core.New(logger) - testDB := testdb.New(logger, c.Path) - mockDB := mockdb.New(logger, c.Path, "") - openAPIdb := openapidb.New(logger, c.Path) - reportDB := reportdb.New(logger, c.Path+"/reports") - testSetDb := testset.New[*models.TestSet](logger, c.Path) - storage := storage.New(c.APIServerURL, logger) - return &CommonInternalService{ - commonPlatformServices{ - YamlTestDB: testDB, - YamlMockDb: mockDB, - YamlOpenAPIDb: openAPIdb, - YamlReportDb: reportDB, - YamlTestSetDB: testSetDb, - Storage: storage, - }, - instrumentation, - }, nil -} diff --git a/keploy/cli/provider/docker.go b/keploy/cli/provider/docker.go deleted file mode 100644 index be16a52..0000000 --- a/keploy/cli/provider/docker.go +++ /dev/null @@ -1,297 +0,0 @@ -package provider - -import ( - "context" - "errors" - "fmt" - "os" - "os/exec" - "runtime" - "strconv" - "strings" - "syscall" - - "github.com/docker/docker/api/types" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/platform/docker" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "golang.org/x/term" -) - -type DockerConfigStruct struct { - DockerImage string - Envs map[string]string -} - -var DockerConfig = DockerConfigStruct{ - DockerImage: "ghcr.io/keploy/keploy", -} - -func GenerateDockerEnvs(config DockerConfigStruct) string { - var envs []string - for key, value := range config.Envs { - if runtime.GOOS == "windows" { - envs = append(envs, fmt.Sprintf("-e %s=%s", key, value)) - } else { - envs = append(envs, fmt.Sprintf("-e %s='%s'", key, value)) - } - } - return strings.Join(envs, " ") -} - -// StartInDocker will check if the docker command is provided as an input -// then start the Keploy as a docker container and run the command -// should also return a boolean if the execution is moved to docker -func StartInDocker(ctx context.Context, logger *zap.Logger, conf *config.Config) error { - - if DockerConfig.Envs == nil { - DockerConfig.Envs = map[string]string{ - "INSTALLATION_ID": conf.InstallationID, - } - } else { - DockerConfig.Envs["INSTALLATION_ID"] = conf.InstallationID - } - - //Check if app command starts with docker or docker-compose. - // If it does, then we would run the docker version of keploy and - // pass the command and control to it. - cmdType := utils.FindDockerCmd(conf.Command) - if conf.InDocker || !(utils.IsDockerCmd(cmdType)) { - return nil - } - // pass the all the commands and args to the docker version of Keploy - err := RunInDocker(ctx, logger) - if err != nil { - utils.LogError(logger, err, "failed to run the command in docker") - return err - } - // gracefully exit the current process - logger.Info("exiting the current process as the command is moved to docker") - - if utils.LogFile != nil { - err := utils.LogFile.Close() - if err != nil { - utils.LogError(logger, err, "Failed to close Keploy Logs") - } - if err := utils.DeleteFileIfNotExists(logger, "keploy-logs.txt"); err != nil { - return nil - } - if err := utils.DeleteFileIfNotExists(logger, "docker-compose-tmp.yaml"); err != nil { - return nil - } - } - - os.Exit(0) - return nil -} - -func RunInDocker(ctx context.Context, logger *zap.Logger) error { - //Get the correct keploy alias. - keployAlias, err := getAlias(ctx, logger) - if err != nil { - return err - } - - var quotedArgs []string - - for _, arg := range os.Args[1:] { - quotedArgs = append(quotedArgs, strconv.Quote(arg)) - } - client, err := docker.New(logger) - if err != nil { - utils.LogError(logger, err, "failed to initalise docker") - return err - } - addKeployNetwork(ctx, logger, client) - err = client.CreateVolume(ctx, "debugfs", true) - if err != nil { - utils.LogError(logger, err, "failed to debugfs volume") - return err - } - - var cmd *exec.Cmd - - // Detect the operating system - if runtime.GOOS == "windows" { - var args []string - args = append(args, "/C") - args = append(args, strings.Split(keployAlias, " ")...) - args = append(args, os.Args[1:]...) - // Use cmd.exe /C for Windows - cmd = exec.CommandContext( - ctx, - "cmd.exe", - args..., - ) - } else { - // Use sh -c for Unix-like systems - cmd = exec.CommandContext( - ctx, - "sh", - "-c", - keployAlias+" "+strings.Join(quotedArgs, " "), - ) - } - - cmd.Cancel = func() error { - err := utils.SendSignal(logger, -cmd.Process.Pid, syscall.SIGINT) - if err != nil { - utils.LogError(logger, err, "failed to start stop docker") - return err - } - return nil - } - - cmd.Stdout = os.Stdout - cmd.Stdin = os.Stdin - cmd.Stderr = os.Stderr - - logger.Debug("running the following command in docker", zap.String("command", cmd.String())) - err = cmd.Run() - if err != nil { - if ctx.Err() == context.Canceled { - return ctx.Err() - } - utils.LogError(logger, err, "failed to start keploy in docker") - return err - } - return nil -} - -func getAlias(ctx context.Context, logger *zap.Logger) (string, error) { - // Get the name of the operating system. - osName := runtime.GOOS - //TODO: configure the hardcoded port mapping - img := DockerConfig.DockerImage + ":v" + utils.Version - logger.Info("Starting keploy in docker with image", zap.String("image:", img)) - envs := GenerateDockerEnvs(DockerConfig) - if envs != "" { - envs = envs + " " - } - var ttyFlag string - - if term.IsTerminal(int(os.Stdin.Fd())) { - ttyFlag = " -it " - } else { - ttyFlag = " " - } - - switch osName { - case "linux": - alias := "sudo docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + " -v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img - return alias, nil - case "windows": - // Get the current working directory - pwd, err := os.Getwd() - if err != nil { - utils.LogError(logger, err, "failed to get the current working directory") - } - dpwd := convertPathToUnixStyle(pwd) - cmd := exec.CommandContext(ctx, "docker", "context", "ls", "--format", "{{.Name}}\t{{.Current}}") - out, err := cmd.Output() - if err != nil { - utils.LogError(logger, err, "failed to get the current docker context") - return "", errors.New("failed to get alias") - } - dockerContext := strings.Split(strings.TrimSpace(string(out)), "\n")[0] - if len(dockerContext) == 0 { - utils.LogError(logger, nil, "failed to get the current docker context") - return "", errors.New("failed to get alias") - } - dockerContext = strings.Split(dockerContext, "\n")[0] - if dockerContext == "colima" { - logger.Info("Starting keploy in docker with colima context, as that is the current context.") - alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + pwd + ":" + dpwd + " -w " + dpwd + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("USERPROFILE") + "\\.keploy-config:/root/.keploy-config -v " + os.Getenv("USERPROFILE") + "\\.keploy:/root/.keploy --rm " + img - return alias, nil - } - // if default docker context is used - logger.Info("Starting keploy in docker with default context, as that is the current context.") - alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + pwd + ":" + dpwd + " -w " + dpwd + " -v /sys/fs/cgroup:/sys/fs/cgroup -v debugfs:/sys/kernel/debug:rw -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("USERPROFILE") + "\\.keploy-config:/root/.keploy-config -v " + os.Getenv("USERPROFILE") + "\\.keploy:/root/.keploy --rm " + img - return alias, nil - case "darwin": - // Get the context and docker daemon endpoint. - cmd := exec.CommandContext(ctx, "docker", "context", "inspect", "--format", "{{if .Metadata}}Name={{.Name}} {{end}}{{if .Endpoints.docker}}Endpoint={{.Endpoints.docker.Host}}{{end}}") - out, err := cmd.Output() - if err != nil { - utils.LogError(logger, err, "failed to inspect the docker context") - return "", errors.New("failed to get alias") - } - - output := strings.TrimSpace(string(out)) - var currentContext, dockerEndpoint string - - // Parse the output for current context and endpoint - for _, part := range strings.Fields(output) { - if strings.HasPrefix(part, "Name=") { - currentContext = strings.TrimPrefix(part, "Name=") - } else if strings.HasPrefix(part, "Endpoint=") { - dockerEndpoint = strings.TrimPrefix(part, "Endpoint=") - } - } - - // Check if we found a current context - if currentContext == "" { - utils.LogError(logger, nil, "failed to find the current docker context") - return "", errors.New("failed to get alias") - } - - // Construct the alias command based on context-specific `debugfs` mount - var alias string - if currentContext == "colima" { - - // To allow docker client to connect to the colima daemon because by default it uses the default docker daemon - err := os.Setenv("DOCKER_HOST", dockerEndpoint) - if err != nil { - utils.LogError(logger, err, "failed to set DOCKER_HOST environment variable for colima context") - return "", errors.New("failed to get alias") - } - logger.Info("Starting keploy in docker with colima context, as that is the current context.") - alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img - return alias, nil - } - // if default docker context is used - logger.Info("Starting keploy in docker with default context, as that is the current context.") - alias = "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v debugfs:/sys/kernel/debug:rw -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img - return alias, nil - } - return "", errors.New("failed to get alias") -} - -func addKeployNetwork(ctx context.Context, logger *zap.Logger, client docker.Client) { - - // Check if the 'keploy-network' network exists - networks, err := client.NetworkList(ctx, types.NetworkListOptions{}) - if err != nil { - logger.Debug("failed to list docker networks") - return - } - - for _, network := range networks { - if network.Name == "keploy-network" { - logger.Debug("keploy network already exists") - return - } - } - - // Create the 'keploy' network if it doesn't exist - _, err = client.NetworkCreate(ctx, "keploy-network", types.NetworkCreate{ - CheckDuplicate: true, - }) - if err != nil { - logger.Debug("failed to create keploy network") - return - } - - logger.Debug("keploy network created") -} - -func convertPathToUnixStyle(path string) string { - // Replace backslashes with forward slashes - unixPath := strings.ReplaceAll(path, "\\", "/") - // Remove 'C:' - if len(unixPath) > 1 && unixPath[1] == ':' { - unixPath = unixPath[2:] - } - return unixPath -} diff --git a/keploy/cli/provider/service.go b/keploy/cli/provider/service.go deleted file mode 100644 index 1a38790..0000000 --- a/keploy/cli/provider/service.go +++ /dev/null @@ -1,60 +0,0 @@ -package provider - -import ( - "context" - "errors" - "sync" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/platform/telemetry" - "go.keploy.io/server/v2/pkg/service" - "go.keploy.io/server/v2/utils" - - "go.keploy.io/server/v2/pkg/service/load" - "go.keploy.io/server/v2/pkg/service/secure" - "go.keploy.io/server/v2/pkg/service/testsuite" - "go.keploy.io/server/v2/pkg/service/utgen" - "go.uber.org/zap" -) - -var TeleGlobalMap sync.Map - -type ServiceProvider struct { - logger *zap.Logger - cfg *config.Config - auth service.Auth -} - -func NewServiceProvider(logger *zap.Logger, cfg *config.Config, auth service.Auth) *ServiceProvider { - return &ServiceProvider{ - logger: logger, - cfg: cfg, - auth: auth, - } -} - -func (n *ServiceProvider) GetService(ctx context.Context, cmd string) (interface{}, error) { - - tel := telemetry.NewTelemetry(n.logger, telemetry.Options{ - Enabled: !n.cfg.DisableTele, - Version: utils.Version, - GlobalMap: TeleGlobalMap, - InstallationID: n.cfg.InstallationID, - }) - tel.Ping() - - switch cmd { - case "secure": - return secure.NewSecurityChecker(n.cfg, n.logger) - case "load": - return load.NewLoadTester(n.cfg, n.logger) - case "testsuite": - return testsuite.NewTSExecutor(n.cfg, n.logger, false) - case "gen": - return utgen.NewUnitTestGenerator(n.cfg, tel, n.auth, n.logger) - case "record", "test", "mock", "normalize", "rerecord", "contract", "config", "update", "login", "export", "import", "templatize", "report": - return Get(ctx, cmd, n.cfg, n.logger, tel, n.auth) - default: - return nil, errors.New("invalid command") - } -} diff --git a/keploy/cli/provider/util.go b/keploy/cli/provider/util.go deleted file mode 100644 index eb743c7..0000000 --- a/keploy/cli/provider/util.go +++ /dev/null @@ -1,101 +0,0 @@ -package provider - -import ( - "errors" - "fmt" - "io" - "log" - "os" - "strings" - - "go.keploy.io/server/v2/utils" -) - -func (c *CmdConfigurator) noCommandError() error { - return errors.New("missing required -c flag or appCmd in config file") -} - -// alreadyRunning checks that during test mode, if user provides the basePath, then it implies that the application is already running somewhere. -func alreadyRunning(cmd, basePath string) bool { - return (cmd == "test" && basePath != "") -} - -var Logo = ` - ▓██▓▄ - ▓▓▓▓██▓█▓▄ - ████████▓▒ - ▀▓▓███▄ ▄▄ ▄ ▌ - ▄▌▌▓▓████▄ ██ ▓█▀ ▄▌▀▄ ▓▓▌▄ ▓█ ▄▌▓▓▌▄ ▌▌ ▓ - ▓█████████▌▓▓ ██▓█▄ ▓█▄▓▓ ▐█▌ ██ ▓█ █▌ ██ █▌ █▓ - ▓▓▓▓▀▀▀▀▓▓▓▓▓▓▌ ██ █▓ ▓▌▄▄ ▐█▓▄▓█▀ █▓█ ▀█▄▄█▀ █▓█ - ▓▌ ▐█▌ █▌ - ▓ -` - -func PrintLogo(wr io.Writer, disableANSI bool) { - if os.Getenv("BINARY_TO_DOCKER") != "true" { - printKeployLogo(wr, disableANSI, Logo) - // print version to the same writer - _, err := fmt.Fprintf(wr, "%s: %v\n\n", utils.VersionIdenitfier, utils.Version) - if err != nil { - log.Fatalf("Error printing version: %v", err) - } - } -} - -func printKeployLogo(wr io.Writer, disableANSI bool, logo string) { - const reset = "\033[0m" - lines := strings.Split(logo, "\n") - - if !disableANSI { - for i, line := range lines { - for j, ch := range line { - color := getLogoColor(i, j) - // wrapper now uses fmt.Fprint, so this will correctly print color + char + reset - FprintWrapper(false, wr, color, string(ch), reset) - } - FprintWrapper(true, wr) // newline after each line - } - } else { - // plain logo (no per-char coloring) - FprintWrapper(false, wr, logo) - FprintWrapper(true, wr) - } -} - -// FprintWrapper prints all its args (like fmt.Fprint) and optionally a leading newline. -func FprintWrapper(newLine bool, wr io.Writer, a ...interface{}) { - if newLine { - if _, err := fmt.Fprintln(wr); err != nil { - log.Fatalf("Error printing newline: %v", err) - } - } - if len(a) > 0 { - if _, err := fmt.Fprint(wr, a...); err != nil { - log.Fatalf("Error printing output: %v", err) - } - } -} - -// Get the color for the logo at position (i, j) -func getLogoColor(i, j int) string { - gradientColors := []string{ - "\033[38;5;202m", // Dark Orange - "\033[38;5;208m", - "\033[38;5;214m", // Light Orange - "\033[38;5;226m", // Light Yellow - } - - switch { - case i <= 5: - return gradientColors[0] - case i == 6 && j <= 42: - return gradientColors[1] - case i == 7 && j <= 49: - return gradientColors[2] - case j <= 38: - return gradientColors[3] - default: - return gradientColors[0] - } -} diff --git a/keploy/cli/record.go b/keploy/cli/record.go deleted file mode 100755 index 8349543..0000000 --- a/keploy/cli/record.go +++ /dev/null @@ -1,55 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - recordSvc "go.keploy.io/server/v2/pkg/service/record" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("record", Record) -} - -func Record(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "record", - Short: "record the keploy testcases from the API calls", - Example: `keploy record -c "/path/to/user/app"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var record recordSvc.Service - var ok bool - if record, ok = svc.(recordSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy record service interface") - return nil - } - - err = record.Start(ctx, false) - if err != nil { - utils.LogError(logger, err, "failed to record") - return nil - } - - return nil - }, - } - - err := cmdConfigurator.AddFlags(cmd) - if err != nil { - utils.LogError(logger, err, "failed to add record flags") - return nil - } - - return cmd -} diff --git a/keploy/cli/report.go b/keploy/cli/report.go deleted file mode 100644 index 4a01a56..0000000 --- a/keploy/cli/report.go +++ /dev/null @@ -1,55 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - reportSvc "go.keploy.io/server/v2/pkg/service/report" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("report", Report) -} - -func Report(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "report", - Short: "report the keploy test results from the API calls", - Example: `keploy report -t "test-set-id"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var report reportSvc.Service - var ok bool - if report, ok = svc.(reportSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy report service interface") - return nil - } - - err = report.GenerateReport(ctx) - if err != nil { - utils.LogError(logger, err, "failed to generate report") - return nil - } - - return nil - }, - } - - err := cmdConfigurator.AddFlags(cmd) - if err != nil { - utils.LogError(logger, err, "failed to add report flags") - return nil - } - - return cmd -} diff --git a/keploy/cli/rerecord.go b/keploy/cli/rerecord.go deleted file mode 100644 index 17ffda8..0000000 --- a/keploy/cli/rerecord.go +++ /dev/null @@ -1,56 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/service/orchestrator" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("rerecord", ReRecord) -} - -func ReRecord(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "rerecord", - Short: "ReRecord new keploy testcases/mocks from the existing test cases for the given testset(s)", - Example: `keploy rerecord -c "user app cmd" -t "test-set-1,teset-set-3"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - - var orch orchestrator.Service - var ok bool - if orch, ok = svc.(orchestrator.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy orchestrator service interface") - return nil - } - - err = orch.ReRecord(ctx) - if err != nil { - utils.LogError(logger, err, "failed to re-record") - return nil - } - - return nil - }, - } - - err := cmdConfigurator.AddFlags(cmd) - if err != nil { - utils.LogError(logger, err, "failed to add rerecord flags") - return nil - } - - return cmd -} diff --git a/keploy/cli/root.go b/keploy/cli/root.go deleted file mode 100755 index ce7b8a4..0000000 --- a/keploy/cli/root.go +++ /dev/null @@ -1,55 +0,0 @@ -package cli - -import ( - "context" - "os" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/cli/provider" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func Root(ctx context.Context, logger *zap.Logger, svcFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - conf := config.New() - - var rootCmd = &cobra.Command{ - Use: "keploy", - Short: "Keploy CLI", - Example: provider.RootExamples, - Version: utils.Version, - PreRun: func(cmd *cobra.Command, _ []string) { - disableAnsi, _ := cmd.Flags().GetBool("disable-ansi") - provider.PrintLogo(os.Stdout, disableAnsi) - }, - } - - defaultHelpFunc := rootCmd.HelpFunc() - - rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { - disableAnsi, _ := cmd.Flags().GetBool("disable-ansi") - provider.PrintLogo(os.Stdout, disableAnsi) - - // Use the default help function instead of calling the parent's HelpFunc - defaultHelpFunc(cmd, args) - }) - - rootCmd.CompletionOptions.DisableDefaultCmd = true - - rootCmd.SetHelpTemplate(provider.RootCustomHelpTemplate) - - rootCmd.SetVersionTemplate(provider.VersionTemplate) - - err := cmdConfigurator.AddFlags(rootCmd) - if err != nil { - utils.LogError(logger, err, "failed to set flags") - return nil - } - - for _, cmd := range Registered { - c := cmd(ctx, logger, conf, svcFactory, cmdConfigurator) - rootCmd.AddCommand(c) - } - return rootCmd -} diff --git a/keploy/cli/secure.go b/keploy/cli/secure.go deleted file mode 100644 index ede4010..0000000 --- a/keploy/cli/secure.go +++ /dev/null @@ -1,286 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - secureSvc "go.keploy.io/server/v2/pkg/service/secure" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("secure", Secure) -} - -func Secure(ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "secure", - Short: "check security vulnerabilities against a given API url (--base-url)", - Example: `keploy secure --base-url "http://localhost:8080/path/to/user/app"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, args []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service") - return nil - } - - var secSvc secureSvc.Service - var ok bool - if secSvc, ok = svc.(secureSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy secure service interface") - return nil - } - - // Get flag values - ruleSet, _ := cmd.Flags().GetString("rule-set") - checksPath, _ := cmd.Flags().GetString("checks-path") - - // Add flag values to context - ctx = context.WithValue(ctx, "rule-set", ruleSet) - ctx = context.WithValue(ctx, "checks-path", checksPath) - - _, err = secSvc.Start(ctx) - if err != nil { - utils.LogError(logger, err, "failed to start secure") - return nil - } - - return nil - }, - } - - // Add flags for the main secure command - err := cmdConfigurator.AddFlags(cmd) - if err != nil { - utils.LogError(logger, err, "failed to add secure flags") - return nil - } - - if addCmd := AddCRCommand(cmd, ctx, logger, cfg, serviceFactory, cmdConfigurator); addCmd != nil { - cmd.AddCommand(addCmd) - // Add flags for the subcommand - err := cmdConfigurator.AddFlags(addCmd) - if err != nil { - utils.LogError(logger, err, "failed to add secure add flags") - } - } - - if removeCmd := RemoveCRCommand(cmd, ctx, logger, cfg, serviceFactory, cmdConfigurator); removeCmd != nil { - cmd.AddCommand(removeCmd) - // Add flags for the subcommand - err := cmdConfigurator.AddFlags(removeCmd) - if err != nil { - utils.LogError(logger, err, "failed to add secure remove flags") - } - } - - if updateCmd := UpdateCRCommand(cmd, ctx, logger, cfg, serviceFactory, cmdConfigurator); updateCmd != nil { - cmd.AddCommand(updateCmd) - // Add flags for the subcommand - err := cmdConfigurator.AddFlags(updateCmd) - if err != nil { - utils.LogError(logger, err, "failed to add secure update flags") - } - } - - if listCmd := ListCRsCommand(cmd, ctx, logger, cfg, serviceFactory, cmdConfigurator); listCmd != nil { - cmd.AddCommand(listCmd) - // Add flags for the subcommand - err := cmdConfigurator.AddFlags(listCmd) - if err != nil { - utils.LogError(logger, err, "failed to add secure list flags") - } - } - - return cmd -} - -func AddCRCommand(cmd *cobra.Command, ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var addCmd = &cobra.Command{ - Use: "add", - Short: "add a custom security check", - Example: `keploy secure add --checks-path "./custom-checks.yaml"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, args []string) error { - svc, err := serviceFactory.GetService(ctx, "secure") - if err != nil { - utils.LogError(logger, err, "failed to get secure service") - return nil - } - - var secSvc secureSvc.Service - var ok bool - if secSvc, ok = svc.(secureSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy secure service interface") - return nil - } - - // Get flag values - checksPath, _ := cmd.Flags().GetString("checks-path") - - // Add flag values to context - ctx = context.WithValue(ctx, "checks-path", checksPath) - - err = secSvc.AddCustomCheck(ctx) - if err != nil { - utils.LogError(logger, err, "failed to add custom check") - return nil - } - - return nil - }, - } - - // addCmd.Flags().String("configPath", ".", "Path to the local directory where keploy configuration file is stored") - // addCmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") - - return addCmd -} - -func RemoveCRCommand(cmd *cobra.Command, ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var removeCmd = &cobra.Command{ - Use: "remove", - Short: "remove a custom security check", - Example: `keploy secure remove --id --checks-path "./custom-checks.yaml"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, args []string) error { - svc, err := serviceFactory.GetService(ctx, "secure") - if err != nil { - utils.LogError(logger, err, "failed to get secure service") - return nil - } - - var secSvc secureSvc.Service - var ok bool - if secSvc, ok = svc.(secureSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy secure service interface") - return nil - } - - // Get flag values - checksPath, _ := cmd.Flags().GetString("checks-path") - id, _ := cmd.Flags().GetString("id") - - // Add flag values to context - ctx = context.WithValue(ctx, "checks-path", checksPath) - ctx = context.WithValue(ctx, "id", id) - - err = secSvc.RemoveCustomCheck(ctx) - if err != nil { - utils.LogError(logger, err, "failed to remove custom check") - return nil - } - - return nil - }, - } - - // removeCmd.Flags().String("configPath", ".", "Path to the local directory where keploy configuration file is stored") - // removeCmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") - // removeCmd.Flags().String("id", "", "ID of the custom check to remove") - - return removeCmd -} - -func UpdateCRCommand(cmd *cobra.Command, ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var updateCmd = &cobra.Command{ - Use: "update", - Short: "update a custom security check", - Example: `keploy secure update --id --checks-path "./custom-checks.yaml"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, args []string) error { - svc, err := serviceFactory.GetService(ctx, "secure") - if err != nil { - utils.LogError(logger, err, "failed to get secure service") - return nil - } - - var secSvc secureSvc.Service - var ok bool - if secSvc, ok = svc.(secureSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy secure service interface") - return nil - } - - // Get flag values - checksPath, _ := cmd.Flags().GetString("checks-path") - id, _ := cmd.Flags().GetString("id") - - // Add flag values to context - ctx = context.WithValue(ctx, "checks-path", checksPath) - ctx = context.WithValue(ctx, "id", id) - - err = secSvc.UpdateCustomCheck(ctx) - if err != nil { - utils.LogError(logger, err, "failed to update custom check") - return nil - } - - return nil - }, - } - - // updateCmd.Flags().String("configPath", ".", "Path to the local directory where keploy configuration file is stored") - // updateCmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") - // updateCmd.Flags().String("id", "", "ID of the custom check to update") - - return updateCmd -} - -func ListCRsCommand(cmd *cobra.Command, ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var listCmd = &cobra.Command{ - Use: "list", - Short: "list all built-in or custom security checks", - Example: `keploy secure list --rule-set custom --checks-path "./custom-checks.yaml"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, args []string) error { - svc, err := serviceFactory.GetService(ctx, "secure") - if err != nil { - utils.LogError(logger, err, "failed to get secure service") - return nil - } - - var secSvc secureSvc.Service - var ok bool - if secSvc, ok = svc.(secureSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy secure service interface") - return nil - } - - // Get flag values - ruleSet, _ := cmd.Flags().GetString("rule-set") - checksPath, _ := cmd.Flags().GetString("checks-path") - - // Add flag values to context - ctx = context.WithValue(ctx, "rule-set", ruleSet) - ctx = context.WithValue(ctx, "checks-path", checksPath) - - err = secSvc.ListChecks(ctx) - if err != nil { - utils.LogError(logger, err, "failed to list checks") - return nil - } - - return nil - }, - } - - // listCmd.Flags().String("configPath", ".", "Path to the local directory where keploy configuration file is stored") - // listCmd.Flags().String("rule-set", "basic", "Specify which checks to list: 'basic' (built-in), 'custom'") - // listCmd.Flags().String("checks-path", "keploy/secure/custom-checks.yaml", "Path to the custom checks file") - - return listCmd -} diff --git a/keploy/cli/service.go b/keploy/cli/service.go deleted file mode 100644 index 8e0f815..0000000 --- a/keploy/cli/service.go +++ /dev/null @@ -1,17 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" -) - -type ServiceFactory interface { - GetService(ctx context.Context, cmd string) (interface{}, error) -} - -type CmdConfigurator interface { - AddFlags(cmd *cobra.Command) error - ValidateFlags(ctx context.Context, cmd *cobra.Command) error - Validate(ctx context.Context, cmd *cobra.Command) error -} diff --git a/keploy/cli/templatize.go b/keploy/cli/templatize.go deleted file mode 100644 index 6e63688..0000000 --- a/keploy/cli/templatize.go +++ /dev/null @@ -1,53 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - toolsSvc "go.keploy.io/server/v2/pkg/service/tools" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("Templatize", Templatize) -} - -func Templatize(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "templatize", - Short: "templatize the keploy testcases for re-record", - Example: `keploy templatize -t "test-set-1,teset-set-3" for particular testsets and keploy templatize for all testsets`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - // Get the replay service. - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var tools toolsSvc.Service - var ok bool - if tools, ok = svc.(toolsSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy tools service interface") - return nil - } - if err := tools.Templatize(ctx); err != nil { - utils.LogError(logger, err, "failed to templatize test cases") - return nil - } - return nil - }, - } - - err := cmdConfigurator.AddFlags(cmd) - if err != nil { - utils.LogError(logger, err, "failed to add templatize flags") - return nil - } - - return cmd -} diff --git a/keploy/cli/test.go b/keploy/cli/test.go deleted file mode 100755 index 6b91330..0000000 --- a/keploy/cli/test.go +++ /dev/null @@ -1,63 +0,0 @@ -package cli - -import ( - "context" - - "go.keploy.io/server/v2/utils" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - replaySvc "go.keploy.io/server/v2/pkg/service/replay" - "go.uber.org/zap" -) - -func init() { - Register("test", Test) -} - -func Test(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var testCmd = &cobra.Command{ - Use: "test", - Short: "run the recorded testcases and execute assertions", - Example: `keploy test -c "/path/to/user/app" --delay 6`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var replay replaySvc.Service - var ok bool - if replay, ok = svc.(replaySvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy replay service interface") - return nil - } - // defering the stop function to stop keploy in case of any error in test or in case of context cancellation - defer func() { - select { - case <-ctx.Done(): - break - default: - utils.ExecCancel() - } - }() - err = replay.Start(ctx) - if err != nil { - utils.LogError(logger, err, "failed to replay") - } - - return nil - }, - } - - err := cmdConfigurator.AddFlags(testCmd) - if err != nil { - utils.LogError(logger, err, "failed to add test flags") - return nil - } - - return testCmd -} diff --git a/keploy/cli/testsuite.go b/keploy/cli/testsuite.go deleted file mode 100644 index 501f3c5..0000000 --- a/keploy/cli/testsuite.go +++ /dev/null @@ -1,60 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - testsuiteSvc "go.keploy.io/server/v2/pkg/service/testsuite" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("testsuite", TestSuite) -} - -func TestSuite(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "testsuite", - Short: "execute a testsuite against a given url (--base-url)", - Example: `keploy testsuite --base-url "http://localhost:8080/path/to/user/app"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, args []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service") - return nil - } - - var tsSvc testsuiteSvc.Service - var ok bool - if tsSvc, ok = svc.(testsuiteSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy testsuite service interface") - return nil - } - - // If any other command is using the TSExecutor there is no need to report to the screen. - // for example, the load command uses the TSExecutor to execute the testsuite. It does not need to report the execution to the screen. - ctx = context.WithValue(ctx, "command", cmd.Name()) - - _, err = tsSvc.Execute(ctx, nil) - if err != nil { - utils.LogError(logger, err, "failed to execute testsuite") - return nil - } - - return nil - }, - } - - err := cmdConfigurator.AddFlags(cmd) - if err != nil { - utils.LogError(logger, err, "failed to add testsuite flags") - return nil - } - - return cmd -} diff --git a/keploy/cli/update.go b/keploy/cli/update.go deleted file mode 100644 index 64cbf3f..0000000 --- a/keploy/cli/update.go +++ /dev/null @@ -1,51 +0,0 @@ -package cli - -import ( - "context" - "os" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/cli/provider" - "go.keploy.io/server/v2/config" - toolsSvc "go.keploy.io/server/v2/pkg/service/tools" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - Register("update", Update) -} - -// Update retrieves the command to tools Keploy -func Update(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var updateCmd = &cobra.Command{ - Use: "update", - Short: "Update Keploy ", - Example: "keploy update", - RunE: func(cmd *cobra.Command, _ []string) error { - disableAnsi, _ := (cmd.Flags().GetBool("disable-ansi")) - provider.PrintLogo(os.Stdout, disableAnsi) - svc, err := serviceFactory.GetService(ctx, "update") - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var tools toolsSvc.Service - var ok bool - if tools, ok = svc.(toolsSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy tools service interface") - return nil - } - err = tools.Update(ctx) - if err != nil { - utils.LogError(logger, err, "failed to update") - } - return nil - }, - } - if err := cmdConfigurator.AddFlags(updateCmd); err != nil { - utils.LogError(logger, err, "failed to add update cmd flags") - return nil - } - return updateCmd -} diff --git a/keploy/cli/utgen.go b/keploy/cli/utgen.go deleted file mode 100644 index 5143225..0000000 --- a/keploy/cli/utgen.go +++ /dev/null @@ -1,56 +0,0 @@ -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "go.keploy.io/server/v2/config" - utgenSvc "go.keploy.io/server/v2/pkg/service/utgen" - "go.keploy.io/server/v2/utils" - - "go.uber.org/zap" -) - -func init() { - Register("gen", GenerateUT) -} - -func GenerateUT(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "gen", - Short: "generate unit tests using AI", - Example: `keploy gen"`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - return cmdConfigurator.Validate(ctx, cmd) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service", zap.String("command", cmd.Name())) - return nil - } - var utg utgenSvc.Service - var ok bool - if utg, ok = svc.(utgenSvc.Service); !ok { - utils.LogError(logger, nil, "service doesn't satisfy unit test generation service interface") - return nil - } - - err = utg.Start(ctx) - if err != nil { - utils.LogError(logger, err, "failed to generate unit tests") - return nil - } - - return nil - }, - } - - err := cmdConfigurator.AddFlags(cmd) - if err != nil { - utils.LogError(logger, err, "failed to add unit test generation flags") - return nil - } - - return cmd -} diff --git a/keploy/config/config.go b/keploy/config/config.go deleted file mode 100644 index 7d00e0c..0000000 --- a/keploy/config/config.go +++ /dev/null @@ -1,259 +0,0 @@ -// Package config provides configuration structures for the application. -package config - -import ( - "errors" - "fmt" - "strings" - "time" -) - -type Config struct { - Path string `json:"path" yaml:"path" mapstructure:"path"` - AppID uint64 `json:"appId" yaml:"appId" mapstructure:"appId"` - AppName string `json:"appName" yaml:"appName" mapstructure:"appName"` - Command string `json:"command" yaml:"command" mapstructure:"command"` - Templatize Templatize `json:"templatize" yaml:"templatize" mapstructure:"templatize"` - Port uint32 `json:"port" yaml:"port" mapstructure:"port"` - E2E bool `json:"e2e" yaml:"e2e" mapstructure:"e2e"` - DNSPort uint32 `json:"dnsPort" yaml:"dnsPort" mapstructure:"dnsPort"` - ProxyPort uint32 `json:"proxyPort" yaml:"proxyPort" mapstructure:"proxyPort"` - Debug bool `json:"debug" yaml:"debug" mapstructure:"debug"` - DisableTele bool `json:"disableTele" yaml:"disableTele" mapstructure:"disableTele"` - DisableANSI bool `json:"disableANSI" yaml:"disableANSI" mapstructure:"disableANSI"` - InDocker bool `json:"inDocker" yaml:"-" mapstructure:"inDocker"` - ContainerName string `json:"containerName" yaml:"containerName" mapstructure:"containerName"` - NetworkName string `json:"networkName" yaml:"networkName" mapstructure:"networkName"` - BuildDelay uint64 `json:"buildDelay" yaml:"buildDelay" mapstructure:"buildDelay"` - Test Test `json:"test" yaml:"test" mapstructure:"test"` - Record Record `json:"record" yaml:"record" mapstructure:"record"` - Report Report `json:"report" yaml:"report" mapstructure:"report"` - Gen UtGen `json:"gen" yaml:"-" mapstructure:"gen"` - Normalize Normalize `json:"normalize" yaml:"-" mapstructure:"normalize"` - ReRecord ReRecord `json:"rerecord" yaml:"-" mapstructure:"rerecord"` - ConfigPath string `json:"configPath" yaml:"configPath" mapstructure:"configPath"` - BypassRules []BypassRule `json:"bypassRules" yaml:"bypassRules" mapstructure:"bypassRules"` - EnableTesting bool `json:"enableTesting" yaml:"-" mapstructure:"enableTesting"` - GenerateGithubActions bool `json:"generateGithubActions" yaml:"generateGithubActions" mapstructure:"generateGithubActions"` - KeployContainer string `json:"keployContainer" yaml:"keployContainer" mapstructure:"keployContainer"` - KeployNetwork string `json:"keployNetwork" yaml:"keployNetwork" mapstructure:"keployNetwork"` - CommandType string `json:"cmdType" yaml:"cmdType" mapstructure:"cmdType"` - Contract Contract `json:"contract" yaml:"contract" mapstructure:"contract"` - TestSuite TestSuite `json:"testSuite" yaml:"testSuite" mapstructure:"testSuite"` - - InCi bool `json:"inCi" yaml:"inCi" mapstructure:"inCi"` - InstallationID string `json:"-" yaml:"-" mapstructure:"-"` - Version string `json:"-" yaml:"-" mapstructure:"-"` - APIServerURL string `json:"-" yaml:"-" mapstructure:"-"` - GitHubClientID string `json:"-" yaml:"-" mapstructure:"-"` -} - -type TestSuite struct { - TSPath string `json:"tsPath" yaml:"tsPath" mapstructure:"tsPath"` - TSFile string `json:"tsFile" yaml:"tsFile" mapstructure:"tsFile"` - BaseURL string `json:"baseUrl" yaml:"baseUrl" mapstructure:"baseUrl"` -} - -type UtGen struct { - SourceFilePath string `json:"sourceFilePath" yaml:"sourceFilePath" mapstructure:"sourceFilePath"` - TestFilePath string `json:"testFilePath" yaml:"testFilePath" mapstructure:"testFilePath"` - CoverageReportPath string `json:"coverageReportPath" yaml:"coverageReportPath" mapstructure:"coverageReportPath"` - TestCommand string `json:"testCommand" yaml:"testCommand" mapstructure:"testCommand"` - CoverageFormat string `json:"coverageFormat" yaml:"coverageFormat" mapstructure:"coverageFormat"` - DesiredCoverage float64 `json:"expectedCoverage" yaml:"expectedCoverage" mapstructure:"expectedCoverage"` - MaxIterations int `json:"maxIterations" yaml:"maxIterations" mapstructure:"maxIterations"` - TestDir string `json:"testDir" yaml:"testDir" mapstructure:"testDir"` - APIBaseURL string `json:"llmBaseUrl" yaml:"llmBaseUrl" mapstructure:"llmBaseUrl"` - Model string `json:"model" yaml:"model" mapstructure:"model"` - APIVersion string `json:"llmApiVersion" yaml:"llmApiVersion" mapstructure:"llmApiVersion"` - AdditionalPrompt string `json:"additionalPrompt" yaml:"additionalPrompt" mapstructure:"additionalPrompt"` - FunctionUnderTest string `json:"functionUnderTest" yaml:"-" mapstructure:"functionUnderTest"` - Flakiness bool `json:"flakiness" yaml:"flakiness" mapstructure:"flakiness"` -} -type Templatize struct { - TestSets []string `json:"testSets" yaml:"testSets" mapstructure:"testSets"` -} - -type Record struct { - Filters []Filter `json:"filters" yaml:"filters" mapstructure:"filters"` - BasePath string `json:"basePath" yaml:"basePath" mapstructure:"basePath"` - RecordTimer time.Duration `json:"recordTimer" yaml:"recordTimer" mapstructure:"recordTimer"` - Metadata string `json:"metadata" yaml:"metadata" mapstructure:"metadata"` -} - -type ReRecord struct { - SelectedTests []string `json:"selectedTests" yaml:"selectedTests" mapstructure:"selectedTests"` - Filters []Filter `json:"filters" yaml:"filters" mapstructure:"filters"` - Host string `json:"host" yaml:"host" mapstructure:"host"` - Port uint32 `json:"port" yaml:"port" mapstructure:"port"` -} -type Contract struct { - Services []string `json:"services" yaml:"services" mapstructure:"services"` - Tests []string `json:"tests" yaml:"tests" mapstructure:"tests"` - Path string `json:"path" yaml:"path" mapstructure:"path"` - Download bool `json:"download" yaml:"download" mapstructure:"download"` - Generate bool `json:"generate" yaml:"generate" mapstructure:"generate"` - Driven string `json:"driven" yaml:"driven" mapstructure:"driven"` - Mappings Mappings `json:"mappings" yaml:"mappings" mapstructure:"mappings"` -} -type Mappings struct { - ServicesMapping map[string][]string `json:"servicesMapping" yaml:"servicesMapping" mapstructure:"servicesMapping"` - Self string `json:"self" yaml:"self" mapstructure:"self"` -} - -type Normalize struct { - SelectedTests []SelectedTests `json:"selectedTests" yaml:"selectedTests" mapstructure:"selectedTests"` - TestRun string `json:"testReport" yaml:"testReport" mapstructure:"testReport"` -} - -type BypassRule struct { - Path string `json:"path" yaml:"path" mapstructure:"path"` - Host string `json:"host" yaml:"host" mapstructure:"host"` - Port uint `json:"port" yaml:"port" mapstructure:"port"` -} -type MatchType string - -const ( - OR MatchType = "OR" - AND MatchType = "AND" -) - -type Filter struct { - BypassRule `mapstructure:",squash"` - URLMethods []string `json:"urlMethods" yaml:"urlMethods" mapstructure:"urlMethods"` - Headers map[string]string `json:"headers" yaml:"headers" mapstructure:"headers"` - MatchType MatchType `json:"matchType"` -} -type Test struct { - SelectedTests map[string][]string `json:"selectedTests" yaml:"selectedTests" mapstructure:"selectedTests"` - GlobalNoise Globalnoise `json:"globalNoise" yaml:"globalNoise" mapstructure:"globalNoise"` - Delay uint64 `json:"delay" yaml:"delay" mapstructure:"delay"` - Host string `json:"host" yaml:"host" mapstructure:"host"` - Port uint32 `json:"port" yaml:"port" mapstructure:"port"` - APITimeout uint64 `json:"apiTimeout" yaml:"apiTimeout" mapstructure:"apiTimeout"` - SkipCoverage bool `json:"skipCoverage" yaml:"skipCoverage" mapstructure:"skipCoverage"` // boolean to capture the coverage in test - CoverageReportPath string `json:"coverageReportPath" yaml:"coverageReportPath" mapstructure:"coverageReportPath"` // directory path to store the coverage files - IgnoreOrdering bool `json:"ignoreOrdering" yaml:"ignoreOrdering" mapstructure:"ignoreOrdering"` - MongoPassword string `json:"mongoPassword" yaml:"mongoPassword" mapstructure:"mongoPassword"` - Language Language `json:"language" yaml:"language" mapstructure:"language"` - RemoveUnusedMocks bool `json:"removeUnusedMocks" yaml:"removeUnusedMocks" mapstructure:"removeUnusedMocks"` - FallBackOnMiss bool `json:"fallBackOnMiss" yaml:"fallBackOnMiss" mapstructure:"fallBackOnMiss"` - JacocoAgentPath string `json:"jacocoAgentPath" yaml:"jacocoAgentPath" mapstructure:"jacocoAgentPath"` - BasePath string `json:"basePath" yaml:"basePath" mapstructure:"basePath"` - Mocking bool `json:"mocking" yaml:"mocking" mapstructure:"mocking"` - IgnoredTests map[string][]string `json:"ignoredTests" yaml:"ignoredTests" mapstructure:"ignoredTests"` - DisableLineCoverage bool `json:"disableLineCoverage" yaml:"disableLineCoverage" mapstructure:"disableLineCoverage"` - DisableMockUpload bool `json:"disableMockUpload" yaml:"disableMockUpload" mapstructure:"disableMockUpload"` - UseLocalMock bool `json:"useLocalMock" yaml:"useLocalMock" mapstructure:"useLocalMock"` - UpdateTemplate bool `json:"updateTemplate" yaml:"updateTemplate" mapstructure:"updateTemplate"` - MustPass bool `json:"mustPass" yaml:"mustPass" mapstructure:"mustPass"` - MaxFailAttempts uint32 `json:"maxFailAttempts" yaml:"maxFailAttempts" mapstructure:"maxFailAttempts"` - MaxFlakyChecks uint32 `json:"maxFlakyChecks" yaml:"maxFlakyChecks" mapstructure:"maxFlakyChecks"` -} - -type Report struct { - SelectedTestSets map[string][]string `json:"selectedTestSets" yaml:"selectedTestSets" mapstructure:"selectedTestSets"` -} - -type Language string - -// String is used both by fmt.Print and by Cobra in help text -func (e *Language) String() string { - return string(*e) -} - -// Set must have pointer receiver so it doesn't change the value of a copy -func (e *Language) Set(v string) error { - switch v { - case "go", "java", "python", "javascript": - *e = Language(v) - return nil - default: - return errors.New(`must be one of "go", "java", "python" or "javascript"`) - } -} - -// Type is only used in help text -func (e *Language) Type() string { - return "myEnum" -} - -type Globalnoise struct { - Global GlobalNoise `json:"global" yaml:"global" mapstructure:"global"` - Testsets TestsetNoise `json:"test-sets" yaml:"test-sets" mapstructure:"test-sets"` -} - -type SelectedTests struct { - TestSet string `json:"testSet" yaml:"testSet" mapstructure:"testSet"` - Tests []string `json:"tests" yaml:"tests" mapstructure:"tests"` -} - -type ( - Noise map[string][]string - GlobalNoise map[string]map[string][]string - TestsetNoise map[string]map[string]map[string][]string -) - -func SetByPassPorts(conf *Config, ports []uint) { - for _, port := range ports { - conf.BypassRules = append(conf.BypassRules, BypassRule{ - Path: "", - Host: "", - Port: port, - }) - } -} - -func GetByPassPorts(conf *Config) []uint { - var ports []uint - for _, rule := range conf.BypassRules { - ports = append(ports, rule.Port) - } - return ports -} - -func SetSelectedTests(conf *Config, testSets []string) { - conf.Test.SelectedTests = make(map[string][]string) - for _, testSet := range testSets { - conf.Test.SelectedTests[testSet] = []string{} - } -} -func SetSelectedServices(conf *Config, services []string) { - // string is "s1,s2" so i want to get s1,s2 - conf.Contract.Services = services -} -func SetSelectedContractTests(conf *Config, tests []string) { - - conf.Contract.Tests = tests -} - -func SetSelectedTestSets(conf *Config, testSets []string) { - conf.Report.SelectedTestSets = make(map[string][]string) - for _, testSet := range testSets { - conf.Report.SelectedTestSets[testSet] = []string{} - } -} - -func SetSelectedTestsNormalize(conf *Config, value string) error { - testSets := strings.FieldsFunc(value, func(r rune) bool { - return r == ',' || r == ' ' - }) - var tests []SelectedTests - if len(testSets) == 0 { - conf.Normalize.SelectedTests = tests - return nil - } - for _, ts := range testSets { - parts := strings.Split(ts, ":") - if len(parts) != 2 { - return fmt.Errorf("invalid format: %s", ts) - } - testCases := strings.Split(parts[1], " ") - tests = append(tests, SelectedTests{ - TestSet: parts[0], - Tests: testCases, - }) - } - conf.Normalize.SelectedTests = tests - return nil -} diff --git a/keploy/config/default.go b/keploy/config/default.go deleted file mode 100644 index 1d7b0e3..0000000 --- a/keploy/config/default.go +++ /dev/null @@ -1,134 +0,0 @@ -package config - -import ( - yaml3 "gopkg.in/yaml.v3" - "sigs.k8s.io/kustomize/kyaml/yaml" - "sigs.k8s.io/kustomize/kyaml/yaml/merge2" - "sigs.k8s.io/kustomize/kyaml/yaml/walk" -) - -// defaultConfig is a variable to store the default configuration of the Keploy CLI. It is not a constant because enterprise need update the default configuration. -var defaultConfig = ` -path: "" -appId: 0 -appName: "" -command: "" -templatize: - testSets: [] -port: 0 -proxyPort: 16789 -dnsPort: 26789 -debug: false -disableANSI: false -disableTele: false -generateGithubActions: false -containerName: "" -networkName: "" -buildDelay: 30 -test: - selectedTests: {} - ignoredTests: {} - globalNoise: - global: {} - test-sets: {} - delay: 5 - host: "" - port: 0 - apiTimeout: 5 - skipCoverage: false - coverageReportPath: "" - ignoreOrdering: true - mongoPassword: "default@123" - language: "" - removeUnusedMocks: false - fallBackOnMiss: false - jacocoAgentPath: "" - basePath: "" - mocking: true - disableLineCoverage: false - disableMockUpload: true - useLocalMock: false - updateTemplate: false - mustPass: false - maxFailAttempts: 5 - maxFlakyChecks: 1 -record: - recordTimer: 0s - filters: [] -configPath: "" -bypassRules: [] -contract: - driven: "consumer" - mappings: - servicesMapping: {} - self: "s1" - services: [] - tests: [] - path: "" - download: false - generate: false -inCi: false -` - -func GetDefaultConfig() string { - return defaultConfig -} - -func SetDefaultConfig(cfgStr string) { - defaultConfig = cfgStr -} - -const InternalConfig = ` -enableTesting: false -keployContainer: "keploy-v2" -keployNetwork: "keploy-network" -inDocker: false -cmdType: "native" -` - -var config = &Config{} - -func New() *Config { - // merge default config with internal config - mergedConfig, err := Merge(defaultConfig, InternalConfig) - if err != nil { - panic(err) - } - err = yaml3.Unmarshal([]byte(mergedConfig), config) - if err != nil { - panic(err) - } - return config -} - -func Merge(srcStr, destStr string) (string, error) { - return mergeStrings(srcStr, destStr, false, yaml.MergeOptions{}) -} - -// Reference: https://github.com/kubernetes-sigs/kustomize/blob/537c4fa5c2bf3292b273876f50c62ce1c81714d7/kyaml/yaml/merge2/merge2.go#L24 -// VisitKeysAsScalars is set to true to enable merging comments. -// inferAssociativeLists is set to fasle to disable merging associative lists. -func mergeStrings(srcStr, destStr string, infer bool, mergeOptions yaml.MergeOptions) (string, error) { - src, err := yaml.Parse(srcStr) - if err != nil { - return "", err - } - - dest, err := yaml.Parse(destStr) - if err != nil { - return "", err - } - - result, err := walk.Walker{ - Sources: []*yaml.RNode{dest, src}, - Visitor: merge2.Merger{}, - InferAssociativeLists: infer, - VisitKeysAsScalars: true, - MergeOptions: mergeOptions, - }.Walk() - if err != nil { - return "", err - } - - return result.String() -} diff --git a/keploy/entrypoint.sh b/keploy/entrypoint.sh deleted file mode 100755 index 532f13f..0000000 --- a/keploy/entrypoint.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -if ! mountpoint -q /sys/kernel/debug; then - sudo mount -t debugfs debugfs /sys/kernel/debug -fi - -sudo -E "$@" diff --git a/keploy/go.mod b/keploy/go.mod deleted file mode 100755 index 1e3898a..0000000 --- a/keploy/go.mod +++ /dev/null @@ -1,159 +0,0 @@ -module go.keploy.io/server/v2 - -go 1.24.4 - -toolchain go1.24.6 - -replace github.com/jackc/pgproto3/v2 => github.com/keploy/pgproto3/v2 v2.0.5 - -require ( - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/cilium/ebpf v0.19.0 - github.com/cloudflare/cfssl v1.6.4 - github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v24.0.4+incompatible - github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/fatih/color v1.18.0 - github.com/k0kubun/pp/v3 v3.2.0 - github.com/miekg/dns v1.1.57 - github.com/morikuni/aec v1.0.0 // indirect - github.com/olekukonko/tablewriter v0.0.5 - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/spf13/cobra v1.9.1 - go.mongodb.org/mongo-driver v1.11.6 - go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/sys v0.33.0 - google.golang.org/protobuf v1.36.5 -) - -require ( - github.com/containerd/log v0.1.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/getkin/kin-openapi v0.126.0 - github.com/go-errors/errors v1.4.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/google/certificate-transparency-go v1.2.1 // indirect - github.com/google/gnostic-models v0.6.9 // indirect - github.com/hashicorp/hcl v1.0.1-vault-7 // indirect - github.com/invopop/yaml v0.3.1 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/magiconair/properties v1.8.9 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/subosito/gotenv v1.6.0 // indirect - github.com/tidwall/gjson v1.18.0 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.1 // indirect - github.com/tidwall/sjson v1.2.5 // indirect - github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - sigs.k8s.io/yaml v1.4.0 // indirect -) - -require ( - gopkg.in/yaml.v3 v3.0.1 - gotest.tools/v3 v3.5.0 // indirect -) - -require ( - github.com/go-logr/logr v1.4.2 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/snappy v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.0 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jmoiron/sqlx v1.4.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9 - github.com/rivo/uniseg v0.4.7 // indirect - github.com/spf13/pflag v1.0.6 - github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b // indirect - github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc // indirect - github.com/zmap/zlint/v3 v3.1.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.25.0 // indirect - golang.org/x/net v0.40.0 - golang.org/x/text v0.26.0 - golang.org/x/tools v0.33.0 // indirect - k8s.io/klog/v2 v2.130.1 // indirect -) - -require ( - facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb - github.com/7sDream/geko v0.1.1 - github.com/agnivade/levenshtein v1.1.1 - github.com/andybalholm/brotli v1.2.0 - github.com/charmbracelet/glamour v0.6.0 - github.com/denisbrodbeck/machineid v1.0.1 - github.com/emirpasic/gods v1.18.1 - github.com/getsentry/sentry-go v0.28.1 - github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/google/uuid v1.6.0 - github.com/jackc/pgproto3/v2 v2.3.2 - github.com/keploy/jsonDiff v1.0.8 - github.com/shirou/gopsutil/v3 v3.24.3 - github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.10.0 - github.com/wI2L/jsondiff v0.5.0 - github.com/xdg-go/pbkdf2 v1.0.0 - github.com/xdg-go/scram v1.1.1 - github.com/xdg-go/stringprep v1.0.4 - golang.org/x/sync v0.15.0 - golang.org/x/term v0.32.0 - google.golang.org/grpc v1.71.0 - gopkg.in/yaml.v2 v2.4.0 - helm.sh/helm/v3 v3.18.3 - sigs.k8s.io/kustomize/kyaml v0.19.0 - vitess.io/vitess v0.22.1 -) - -require github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - -require ( - github.com/golang/glog v1.2.4 // indirect - github.com/gorilla/mux v1.8.1 - github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20241121165744-79df5c4772f2 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/stretchr/objx v0.5.2 // indirect - golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect - golang.org/x/time v0.12.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect -) - -require ( - github.com/alecthomas/chroma v0.10.0 // indirect - github.com/aymanbagabas/go-osc52 v1.0.3 // indirect - github.com/aymerick/douceur v0.2.0 // indirect - github.com/dlclark/regexp2 v1.4.0 // indirect - github.com/gorilla/css v1.0.1 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/microcosm-cc/bluemonday v1.0.27 // indirect - github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect - github.com/moby/moby v26.0.2+incompatible - github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.13.0 // indirect - github.com/yuin/goldmark v1.5.2 // indirect - github.com/yuin/goldmark-emoji v1.0.1 // indirect -) diff --git a/keploy/go.sum b/keploy/go.sum deleted file mode 100644 index b0b510c..0000000 --- a/keploy/go.sum +++ /dev/null @@ -1,470 +0,0 @@ -facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:1pSweJFeR3Pqx7uoelppkzeegfUBXL6I2FFAbfXw570= -facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:npRYmtaITVom7rcSo+pRURltHSG2r4TQM1cdqJ2dUB0= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/7sDream/geko v0.1.1 h1:Ms9RVcUJDPsUuRlk3T3sGmSGAMuttqh2/3okZ4yWUpU= -github.com/7sDream/geko v0.1.1/go.mod h1:NcSQXSUpcoqNP8BkOLP2TsinxRVjnMkDYCKl9L1Czxk= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= -github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= -github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= -github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= -github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= -github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= -github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= -github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= -github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= -github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= -github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao= -github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY= -github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8= -github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= -github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= -github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.4+incompatible h1:s/LVDftw9hjblvqIeTiGYXBCD95nOEEl7qRsRrIOuQI= -github.com/docker/docker v24.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/getkin/kin-openapi v0.126.0 h1:c2cSgLnAsS0xYfKsgt5oBV6MYRM/giU8/RtwUY4wyfY= -github.com/getkin/kin-openapi v0.126.0/go.mod h1:7mONz8IwmSRg6RttPu6v8U/OJ+gr+J99qSFNjPGSQqw= -github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= -github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= -github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= -github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= -github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= -github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/certificate-transparency-go v1.2.1 h1:4iW/NwzqOqYEEoCBEFP+jPbBXbLqMpq3CifMyOnDUME= -github.com/google/certificate-transparency-go v1.2.1/go.mod h1:bvn/ytAccv+I6+DGkqpvSsEdiVGramgaSC6RD3tEmeE= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= -github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= -github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= -github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= -github.com/jackc/chunkreader/v2 v2.0.0 h1:DUwgMQuuPnS0rhMXenUtZpqZqrR/30NWY+qQvTpSvEs= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= -github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= -github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= -github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= -github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= -github.com/keploy/jsonDiff v1.0.8 h1:B/75cfLNZ7kayAjF1Jox70Iwa/nwCjBYNOuSt+RHH5A= -github.com/keploy/jsonDiff v1.0.8/go.mod h1:wUuLbVs3Oe3mIQ61C7G88bppP//ArLtoDU0S9Awwv+s= -github.com/keploy/pgproto3/v2 v2.0.5 h1:8spdNKZ+nOnHVxiimDsqulBRN6viPXPghkA7xppnzJ8= -github.com/keploy/pgproto3/v2 v2.0.5/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= -github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= -github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= -github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= -github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= -github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= -github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= -github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/moby v26.0.2+incompatible h1:t41TD3nRvK8E6bZFJdKrmNlH8Xe3epTmdNXf/mnfLKk= -github.com/moby/moby v26.0.2+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= -github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= -github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= -github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= -github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= -github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= -github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= -github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetscale/vtprotobuf v0.6.1-0.20241121165744-79df5c4772f2 h1:1sLMdKq4gNANTj0dUibycTLzpIEKVnLnbaEkxws78nw= -github.com/planetscale/vtprotobuf v0.6.1-0.20241121165744-79df5c4772f2/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9 h1:arwj11zP0yJIxIRiDn22E0H8PxfF7TsTrc2wIPFIsf4= -github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9/go.mod h1:SKZx6stCn03JN3BOWTwvVIO2ajMkb/zQdTceXYhKw/4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= -github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/wI2L/jsondiff v0.5.0 h1:RRMTi/mH+R2aXcPe1VYyvGINJqQfC3R+KSEakuU1Ikw= -github.com/wI2L/jsondiff v0.5.0/go.mod h1:qqG6hnK0Lsrz2BpIVCxWiK9ItsBCpIZQiv0izJjOZ9s= -github.com/weppos/publicsuffix-go v0.13.1-0.20210123135404-5fd73613514e/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= -github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b h1:FsyNrX12e5BkplJq7wKOLk0+C6LZ+KGXvuEcKUYm5ss= -github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= -github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= -github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= -github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= -github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= -github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= -github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= -github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= -github.com/zmap/zcrypto v0.0.0-20210123152837-9cf5beac6d91/go.mod h1:R/deQh6+tSWlgI9tb4jNmXxn8nSCabl5ZQsBX9//I/E= -github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc h1:zkGwegkOW709y0oiAraH/3D8njopUR/pARHv4tZZ6pw= -github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc/go.mod h1:FM4U1E3NzlNMRnSUTU3P1UdukWhYGifqEsjk9fn7BCk= -github.com/zmap/zlint/v3 v3.1.0 h1:WjVytZo79m/L1+/Mlphl09WBob6YTGljN5IGWZFpAv0= -github.com/zmap/zlint/v3 v3.1.0/go.mod h1:L7t8s3sEKkb0A2BxGy1IWrxt1ZATa1R4QfJZaQOD3zU= -go.mongodb.org/mongo-driver v1.11.6 h1:XM7G6PjiGAO5betLF13BIa5TlLUUE3uJ/2Ox3Lz1K+o= -go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.18.3 h1:+cvyGKgs7Jt7BN3Klmb4SsG4IkVpA7GAZVGvMz6VO4I= -helm.sh/helm/v3 v3.18.3/go.mod h1:wUc4n3txYBocM7S9RjTeZBN9T/b5MjffpcSsWEjSIpw= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= -sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= -sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= -sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -vitess.io/vitess v0.22.1 h1:nCA0v6tt3YVPf8qWMQJktzxZlsLYwdPxplo2TbSEm9A= -vitess.io/vitess v0.22.1/go.mod h1:JF7nQQ+XUP7og7ZNzOw0p+zKvUTwYd6TTKfFyzyfE9o= diff --git a/keploy/gon.json b/keploy/gon.json deleted file mode 100755 index 29d89bd..0000000 --- a/keploy/gon.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "source" : ["./dist/keploy-mac-universal_darwin_all/keploy"], - "bundle_id" : "io.keploy.server", - "apple_id": { - "username" : "shubhamkjain@outlook.com" - }, - "sign" :{ - "application_identity" : "Developer ID Application: Shubham Jain" - }, - "zip" :{ - "output_path" : "keploy.zip" - } -} \ No newline at end of file diff --git a/keploy/goreleaser.yaml b/keploy/goreleaser.yaml deleted file mode 100755 index 869d254..0000000 --- a/keploy/goreleaser.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# GoReleaser configuration -archives: - - - name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" - -builds: - - binary: keploy - id: keploy - main: ./main.go - ldflags: - - -s -w -X main.dsn={{.Env.SENTRY_DSN_BINARY}} - - -s -w -X main.version={{.Version}} - - -s -w -X main.apiServerURI={{.Env.SERVER_URL}} - - -s -w -X main.gitHubClientID={{.Env.GITTHUB_APP_CLIENT_ID}} - env: - - CGO_ENABLED=0 - goos: - - linux - - darwin - - windows - goarch: - - amd64 - - arm64 - tags: - - viper_bind_struct - - # - binary: keploy - # id: keploy-macos - # main: ./cli/server/main.go - # env: - # - CGO_ENABLED=0 - # goos: - # - darwin - # goarch: - # - amd64 - # - arm64 - -universal_binaries: -- - # ID of resulting universal binary. - # - # Defaults to the project name. - id: keploy-mac-universal - - # IDs to use to filter the built binaries. - # - # Defaults to the `id` field. - # Since: v1.3. - ids: - - keploy - - # Whether to remove the previous single-arch binaries from the artifact list. - # If left as false, your end release might have both several macOS archives: - # amd64, arm64 and all. - # - # Defaults to false. - replace: true - hooks: - post: gon -log-level=info -log-json ./gon.json - diff --git a/keploy/gsoc25_performance&security-testing.md b/keploy/gsoc25_performance&security-testing.md deleted file mode 100644 index b7145a3..0000000 --- a/keploy/gsoc25_performance&security-testing.md +++ /dev/null @@ -1,558 +0,0 @@ -# Keploy GSoC'25_Performance&Security Features Documentation - -This document provides a comprehensive overview of the three advanced features implemented in Keploy: **TestSuite Execution**, **Load Testing**, and **Security Checking**. - -## Table of Contents -1. [Overview](#overview) -2. [TestSuite Feature](#testsuite-feature) -3. [Load Testing Feature](#load-testing-feature) -4. [Security Checking Feature](#security-checking-feature) -5. [Integration and Architecture](#integration-and-architecture) -6. [Usage Examples](#usage-examples) -7. [Configuration](#configuration) - ---- - -## Overview - -These three features work together to provide a comprehensive testing solution: - -- **TestSuite**: Core execution engine for running API test suites defined in YAML -- **Load Testing**: Performance testing capability that executes test suites under load -- **Security Checking**: Security vulnerability scanning for API endpoints - -All features are integrated into the Keploy CLI and share common configuration patterns and data structures. - ---- - -## TestSuite Feature - -### Purpose -The TestSuite feature provides a declarative way to define and execute API test sequences using YAML configuration files. It serves as the foundation for both load testing and security checking. - -### Architecture - -#### Core Components - -1. **TSExecutor** (`pkg/service/testsuite/testsuite.go`) - - Main execution engine for test suites - - Handles HTTP requests, response validation, and variable extraction - - Supports rate limiting for controlled execution - -2. **TestSuite Data Structures** - ```go - type TestSuite struct { - Version string `yaml:"version"` - Kind string `yaml:"kind"` - Name string `yaml:"name"` - Spec TestSuiteSpec `yaml:"spec"` - } - - type TestSuiteSpec struct { - Metadata TestSuiteMetadata `yaml:"metadata"` - Security Security `yaml:"security,omitempty"` - Load LoadOptions `yaml:"load,omitempty"` - Steps []TestStep `yaml:"steps"` - } - ``` - -3. **TestStep Execution** - - Each step represents an HTTP request with assertions - - Supports variable extraction and interpolation - - Provides detailed execution results - -#### Key Features - -- **Variable Extraction**: Extract values from responses using JSON path notation -- **Variable Interpolation**: Use extracted variables in subsequent requests -- **Assertions**: Validate response status codes, headers, and body content -- **Rate Limiting**: Control request rate for load testing scenarios -- **Detailed Reporting**: Comprehensive execution reports with timing and error information - -### Implementation Details - -#### CLI Integration (`cli/testsuite.go`) -```go -func TestSuite(ctx context.Context, logger *zap.Logger, _ *config.Config, - serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "testsuite", - Short: "execute a testsuite against a given url (--base-url)", - Example: `keploy testsuite --base-url "http://localhost:8080/path/to/user/app"`, - RunE: func(cmd *cobra.Command, args []string) error { - // Service factory pattern for dependency injection - svc, err := serviceFactory.GetService(ctx, cmd.Name()) - if err != nil { - utils.LogError(logger, err, "failed to get service") - return nil - } - - // Execute the test suite - _, err = tsSvc.Execute(ctx, nil) - return err - }, - } -} -``` - -#### Service Factory Integration (`cli/provider/service.go`) -The service factory provides dependency injection and service instantiation: -```go -func (n *ServiceProvider) GetService(ctx context.Context, cmd string) (interface{}, error) { - switch cmd { - case "testsuite": - return testsuite.NewTSExecutor(n.cfg, n.logger, false) - // ... other services - } -} -``` - -### Usage -```bash -# Execute a test suite -keploy testsuite --base-url "http://localhost:8080" --ts-path ./keploy/testsuite --ts-file suite-0.yaml - -# Or simply passing the base URL keeping the defalut value. -keploy testsuite --base-url "http://localhost:8080" -``` - ---- - -## Load Testing Feature - -### Purpose -The Load Testing feature enables performance testing by executing test suites under various load profiles. It provides comprehensive metrics collection and threshold-based pass/fail criteria. - -### Architecture - -#### Core Components - -1. **LoadTester** (`pkg/service/load/load.go`) - - Main coordinator for load test execution - - Manages test suite parsing and load configuration - - Generates comprehensive load test reports - -2. **Scheduler** (`pkg/service/load/scheduler.go`) - - Orchestrates virtual user (VU) execution - - Supports multiple load profiles (constant, ramping) - - Manages timing and synchronization - -3. **VUWorker** (`pkg/service/load/vu_worker.go`) - - Individual virtual user implementation - - Executes test suite repeatedly under load - - Collects per-VU metrics and timing data - -4. **MetricsCollector** (`pkg/service/load/metrics_collector.go`) - - Aggregates metrics from all virtual users - - Provides centralized data collection point - - Calculates aggregate statistics - -5. **ThresholdEvaluator** (`pkg/service/load/threshold_evaluator.go`) - - Evaluates performance thresholds - - Determines pass/fail status for load tests - - Supports various metric types and conditions - -#### Load Profiles - -1. **Constant VUs Profile** - - Maintains constant number of virtual users - - Consistent load throughout test duration - ```yaml - load: - profile: constant_vus - vus: 10 - duration: 5m - rps: 50 - ``` - -2. **Ramping VUs Profile** - - Gradually increases/decreases virtual users - - Supports multiple stages with different targets - ```yaml - load: - profile: ramping_vus - vus: 30 - duration: 3m - stages: - - duration: 30s - target: 10 - - duration: 2m - target: 30 - ``` - -#### Metrics and Thresholds - -The system collects comprehensive metrics: -- **Request metrics**: Count, failure rate, response times -- **Performance metrics**: P90,95,99 latency, throughput -- **Data transfer**: Bytes sent/received - -Thresholds provide pass/fail criteria: -```yaml -thresholds: - - metric: http_req_duration_p95 - condition: "< 500ms" - severity: high - comment: "Ensure 95% of requests are below 500ms latency" - - metric: http_req_failed_rate - condition: "<= 1%" - severity: critical - comment: "Error rate must stay under 1%" -``` - -### Implementation Details - -#### CLI Integration (`cli/load.go`) -```go -func Load(ctx context.Context, logger *zap.Logger, _ *config.Config, - serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command { - var cmd = &cobra.Command{ - Use: "load", - Short: "load test a given testsuite.", - Example: `keploy load -f test_suite.yaml --out json > report.json`, - RunE: func(cmd *cobra.Command, args []string) error { - // CLI parameter extraction - vus, _ := cmd.Flags().GetInt("vus") - duration, _ := cmd.Flags().GetString("duration") - rps, _ := cmd.Flags().GetInt("rps") - - // Context with CLI overrides - ctx := context.WithValue(ctx, "vus", vus) - ctx = context.WithValue(ctx, "duration", duration) - ctx = context.WithValue(ctx, "rps", rps) - - return ltSvc.Start(ctx) - }, - } -} -``` - -#### Integrated Dashboard -- **Built-in Web Server**: Embedded dashboard server integrated directly into Keploy binary -- **Auto-Browser Launch**: Automatically opens browser to dashboard URL when load test starts -- **DashboardExposer** (`pkg/service/load/dashboard_exposer.go`): - - Serves embedded React dashboard from Go binary - - Provides real-time metrics API endpoints -- **Exporter** (`pkg/service/load/exporter.go`): Real-time metrics streaming to dashboard -- **Live Monitoring**: Real-time updates of VU count, RPS, response times, and threshold status -- **Zero Configuration**: No external dependencies or separate dashboard setup required - -#### Dashboard Architecture - -The dashboard system is fully integrated into the Keploy binary using Go's embed feature: - -```go -//go:embed out/* -var content embed.FS - -func (de *DashboardExposer) fileSystem() http.FileSystem { - fsys, err := fs.Sub(content, "out") - return http.FS(fsys) -} -``` - -**Key Features:** -- **Embedded Static Files**: Frontend assets bundled into Go binary at compile time -- **Smart Browser Detection**: Cross-platform browser launching with WSL support - -### Advanced Features - -1. **Rate Limiting**: Precise RPS control using token bucket algorithm -2. **Integrated Dashboard**: Built-in web dashboard that automatically launches in browser - - **Embedded Server**: Dashboard server runs within Keploy binary (no external dependencies) - - **Auto-Launch**: Browser automatically opens to `http://localhost:3000` when load test starts - - **Real-time Monitoring**: Live metrics updates including VU count, RPS, response times - - **Threshold Visualization**: Real-time pass/fail status of performance thresholds -3. **Report Generation**: JSON and CLI-formatted comprehensive reports -4. **CLI Overrides**: Command-line parameters override YAML configuration -5. **Threshold Evaluation**: Automated pass/fail determination with live status updates - -### Usage -```bash -# Basic load test (automatically opens dashboard in browser) -keploy load -f ./keploy/suite-0.yaml --base-url "http://localhost:8080" - -# With CLI overrides (dashboard launches at http://localhost:3000) -keploy load -f ./keploy/suite-0.yaml --base-url "http://localhost:8080" --vus 20 --duration 10m --rps 100 -``` - ---- - -## Security Checking Feature - -### Purpose -The Security Checking feature provides automated security vulnerability scanning for API endpoints. It executes both built-in and custom security checks against API responses and requests. - -### Architecture - -#### Core Components - -1. **SecurityChecker** (`pkg/service/secure/secure.go`) - - Main security scanning engine - - Manages built-in and custom security checks - - Executes checks against test suite results - -2. **Security Checks System** - - Built-in checks for common vulnerabilities - - Custom check support via YAML configuration - - Severity-based filtering and reporting - -3. **AllowList System** - - Filter false positives - - Customizable per header, key, or pattern - - Integration with test suite configuration - -#### Security Check Types - -The system includes various built-in security checks: - -1. **Header Security Checks** - - Missing security headers (HSTS, CSP, X-Frame-Options) - - Insecure header values - - Information disclosure in headers - -2. **Response Body Checks** - - Sensitive data exposure - - Error message leakage - - Debug information disclosure - -3. **Cookie Security** - - Missing secure flags - - HttpOnly flag validation - - SameSite attribute checking - -4. **General Security** - - HTTP vs HTTPS enforcement - - Server information disclosure - - Version information exposure - -#### Custom Security Checks - -Users can define custom security checks: -```yaml -# custom-checks.yaml -- id: custom_header_check - name: Custom Header Validation - description: Check for custom security header - severity: MEDIUM - type: header - target: response - key: X-Custom-Security - operation: exists - status: enabled -``` - -### Implementation Details - -#### CLI Integration (`cli/secure.go`) -The security feature provides multiple subcommands: -- `keploy secure` - Run security checks -- `keploy secure add` - Add custom security check -- `keploy secure remove` - Remove custom security check -- `keploy secure update` - Update custom security check -- `keploy secure list` - List available security checks - -#### Security Check Execution -```go -type SecurityCheck struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Severity string `json:"severity"` // "CRITICAL", "HIGH", "MEDIUM", "LOW" - Type string `json:"type"` // "header", "body", "cookie", "url" - Target string `json:"target"` // "request", "response" - Key string `json:"key"` // Header name, JSON path, etc. - Value string `json:"value,omitempty"` // Expected value or pattern - Operation string `json:"operation"` // "exists", "equals", "contains", "regex" - Status string `json:"status"` // "enabled", "disabled" -} -``` - -#### Integration with TestSuite -Security checks are executed against test suite results: -1. Test suite executes API calls -2. Security checker analyzes requests/responses -3. Checks are applied based on configuration -4. Results are filtered by severity and allowlists -5. Comprehensive security report is generated - -### Usage -```bash -# Run security checks -keploy secure --base-url "http://localhost:8080" - -# List available checks -keploy secure list --rule-set basic - -# Add custom check -keploy secure add - -# Run with custom severity threshold -keploy secure --severity-threshold HIGH -``` - ---- - -## Integration and Architecture - -### Service Factory Pattern - -All three features are integrated through a unified service factory pattern: - -```go -// cli/provider/service.go -func (n *ServiceProvider) GetService(ctx context.Context, cmd string) (interface{}, error) { - switch cmd { - case "secure": - return secure.NewSecurityChecker(n.cfg, n.logger) - case "load": - return load.NewLoadTester(n.cfg, n.logger) - case "testsuite": - return testsuite.NewTSExecutor(n.cfg, n.logger, false) - // ... other services - } -} -``` - -### Shared Configuration - -All features share common configuration patterns: - -```yaml -# keploy.yml -testSuite: - tsPath: keploy/testsuite - tsFile: suite-0.yaml - baseUrl: "" - -load: - vus: 0 - duration: "" - rps: 0 - output: "" - -# Suite configuration (suite-0.yaml) -spec: - security: - ruleset: basic - severity_threshold: MEDIUM - disable: [] - allowlist: - headers: ["Server"] - keys: ["data.debug"] - - load: - profile: constant_vus - vus: 10 - duration: 5m - thresholds: - - metric: http_req_duration_p95 - condition: "< 500ms" - severity: high -``` - -### Cross-Feature Integration - -1. **Load Testing + Security**: Load tests can include security checking -2. **TestSuite Foundation**: Both load and security features build on testsuite execution -3. **Shared Reporting**: Common report formats and output options -4. **Configuration Inheritance**: Features inherit and extend base configurations - ---- - -## Usage Examples - -### Complete Test Suite Example - -```yaml -# suite-0.yaml -version: api.keploy.io/v2beta1 -kind: TestSuite -name: Todo_CRUD_Operations -spec: - metadata: - description: Test CRUD operations for Todo API - - security: - ruleset: basic - severity_threshold: MEDIUM - allowlist: - headers: ["Server"] - keys: ["data.debug"] - - load: - profile: ramping_vus - vus: 30 - duration: 3m - stages: - - duration: 30s - target: 10 - - duration: 2m - target: 30 - thresholds: - - metric: http_req_duration_p95 - condition: "< 500ms" - severity: high - - steps: - - name: Create_todo - method: POST - url: /todos - headers: - Content-Type: application/json - body: | - { - "title": "Test Todo", - "completed": false - } - extract: - todo_id: id - assert: - - type: status_code - expected_string: "201" - - - name: Get_todo - method: GET - url: /todos/{{todo_id}} - assert: - - type: status_code - expected_string: "200" -``` - -### Command Usage Examples - -```bash -# 1. Test functionality -keploy testsuite --base-url "http://localhost:8080" - -# 2. Perform load testing (dashboard launches for real-time monitoring) -keploy load --base-url "http://localhost:8080" - -# 3. Security scanning -keploy secure --base-url "http://localhost:8080" -``` - ---- - -## Configuration - -### Global Configuration (`keploy.yml`) - -```yaml -testSuite: - tsPath: keploy/testsuite # Path to test suite directory - tsFile: suite-0.yaml # Test suite file name - baseUrl: "" # Base URL for API endpoints -``` - -### Feature-Specific Configuration - -Each feature supports extensive configuration through both YAML files and CLI flags, providing flexibility for different testing scenarios and environments. - ---- - -## Conclusion - -These three features provide a comprehensive testing solution that covers functional testing, performance testing, and security scanning. The modular architecture and shared configuration patterns make them easy to use individually or in combination, while the integration with Keploy's existing ecosystem ensures seamless workflow integration. - -The features are designed with extensibility in mind, supporting custom security checks, flexible load profiles, and comprehensive reporting options to meet diverse testing requirements. diff --git a/keploy/keploy.sh b/keploy/keploy.sh deleted file mode 100644 index 2fe4f30..0000000 --- a/keploy/keploy.sh +++ /dev/null @@ -1,295 +0,0 @@ -#!/bin/bash - -installKeploy (){ - version="latest" - IS_CI=false - NO_ROOT=false - PLATFORM="$(basename "$SHELL")" - for arg in "$@" - do - case $arg in - -isCI) - IS_CI=true - shift - ;; - -v) - if [[ "$2" =~ ^v[0-9]+.* ]]; then - version="$2" - shift 2 - else - echo "Invalid version format. Please use '-v v'." - return 1 - fi - ;; - -noRoot) - NO_ROOT=true - shift 1 - ;; - -platform) - PLATFORM="$2" - shift 2 - ;; - *) - ;; - esac - done - if [ "$version" != "latest" ]; then - echo "Installing Keploy version: $version......" - fi - - move_keploy_binary() { - # Check if NO_ROOT is set to true - if [ "$NO_ROOT" = "true" ]; then - # Move without sudo - target_dir="$HOME/.keploy/bin" - source_dir="/tmp/keploy" # Default source directory - - # Create the target directory in the user's home directory - mkdir -p "$target_dir" - if [ $? -ne 0 ]; then - echo "Error: Failed to create directory $target_dir" - exit 1 - fi - - # Check if the OS is macOS (Darwin) to set the correct source path - OS_NAME=$(uname) # Get the operating system name - if [ "$OS_NAME" = "Darwin" ]; then - source_dir="/tmp/keploy/keploy" # Set source directory to the binary inside the extracted folder - fi - - # Move the keploy binary to the user's home directory bin - if [ -f "$source_dir" ]; then - mv "$source_dir" "$target_dir/keploy" - if [ $? -ne 0 ]; then - echo "Error: Failed to move the keploy binary from $source_dir to $target_dir" - exit 1 - fi - else - echo "Error: $source_dir does not exist." - exit 1 - fi - - # Make sure the binary is executable - chmod +x "$target_dir/keploy" - if [ $? -ne 0 ]; then - echo "Error: Failed to make the keploy binary executable" - exit 1 - fi - else - source_dir="/tmp/keploy" - OS_NAME=$(uname) # Get the operating system name - if [ "$OS_NAME" = "Darwin" ]; then - source_dir="/tmp/keploy/keploy" # Set source directory to the binary inside the extracted folder - fi - sudo mkdir -p /usr/local/bin && sudo mv "$source_dir" /usr/local/bin/keploy - fi - set_alias - } - - install_keploy_darwin_all() { - if [ "$version" != "latest" ]; then - download_url="https://github.com/keploy/keploy/releases/download/$version/keploy_darwin_all.tar.gz" - else - download_url="https://github.com/keploy/keploy/releases/latest/download/keploy_darwin_all.tar.gz" - fi - # macOS tar does not support --overwrite option so we need to remove the directory first - # to avoid the "File exists" error - rm -rf /tmp/keploy - mkdir -p /tmp/keploy - curl --silent --location "$download_url" | tar xz -C /tmp/keploy/ - move_keploy_binary - delete_keploy_alias - } - - install_keploy_arm() { - if [ "$version" != "latest" ]; then - download_url="https://github.com/keploy/keploy/releases/download/$version/keploy_linux_arm64.tar.gz" - else - download_url="https://github.com/keploy/keploy/releases/latest/download/keploy_linux_arm64.tar.gz" - fi - curl --silent --location "$download_url" | tar xz --overwrite -C /tmp - move_keploy_binary - } - - - install_keploy_amd() { - if [ "$version" != "latest" ]; then - download_url="https://github.com/keploy/keploy/releases/download/$version/keploy_linux_amd64.tar.gz" - else - download_url="https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" - fi - curl --silent --location "$download_url" | tar xz --overwrite -C /tmp - move_keploy_binary - } - - append_to_rc() { - last_byte=$(tail -c 1 $2) - if [[ "$last_byte" != "" ]]; then - echo -e "\n$1" >> $2 - else - echo "$1" >> $2 - fi - source $2 - } - - update_path() { - PATH_CMD="export PATH=\"\$HOME/.keploy/bin:\$PATH\"" - rc_file="$1" - if [ -f "$rc_file" ]; then - if ! grep -q "$PATH_CMD" "$rc_file"; then - append_to_rc "$PATH_CMD" "$rc_file" - fi - else - export PATH="$PATH_CMD" - fi - } - - # Get the alias to set and set it - set_alias() { - current_shell="$PLATFORM" - if [ "$NO_ROOT" = "true" ]; then - # Just update the PATH in .zshrc or .bashrc, no alias needed - if [[ "$current_shell" = "zsh" || "$current_shell" = "-zsh" ]]; then - update_path "$HOME/.zshrc" - elif [[ "$current_shell" = "bash" || "$current_shell" = "-bash" ]]; then - update_path "$HOME/.bashrc" - else - update_path "$HOME/.profile" - fi - else - ALIAS_CMD="alias keploy='sudo -E env PATH=\"\$PATH\" keploy'" - # Handle zsh or bash for non-macOS systems - if [[ "$current_shell" = "zsh" || "$current_shell" = "-zsh" ]]; then - if [ -f "$HOME/.zshrc" ]; then - if grep -q "alias keploy=" "$HOME/.zshrc"; then - sed -i '/alias keploy/d' "$HOME/.zshrc" - fi - append_to_rc "$ALIAS_CMD" ~/.zshrc - else - alias keploy="$ALIAS_CMD" - fi - elif [[ "$current_shell" = "bash" || "$current_shell" = "-bash" ]]; then - if [ -f "$HOME/.bashrc" ]; then - if grep -q "alias keploy=" "$HOME/.bashrc"; then - sed -i '/alias keploy/d' "$HOME/.bashrc" - fi - append_to_rc "$ALIAS_CMD" ~/.bashrc - else - alias keploy="$ALIAS_CMD" - fi - else - if [ -f "$HOME/.profile" ]; then - if grep -q "alias keploy=" "$HOME/.profile"; then - sed -i '/alias keploy/d' "$HOME/.profile" - fi - append_to_rc "$ALIAS_CMD" ~/.profile - else - alias keploy="$ALIAS_CMD" - fi - fi - - fi - - } - - delete_keploy_alias() { - current_shell="$PLATFORM" - shell_rc_file="" - # Determine the shell configuration file based on the current shell - if [[ "$current_shell" = "zsh" || "$current_shell" = "-zsh" ]]; then - shell_rc_file="$HOME/.zshrc" - elif [[ "$current_shell" = "bash" || "$current_shell" = "-bash" ]]; then - shell_rc_file="$HOME/.bashrc" - else - echo "Unsupported shell: $current_shell" - return - fi - # Delete alias from the shell configuration file if it exists - if [ -f "$shell_rc_file" ]; then - if grep -q "alias keploy=" "$shell_rc_file"; then - if [[ "$(uname)" = "Darwin" ]]; then - sed -i '' '/alias keploy/d' "$shell_rc_file" - else - sed -i '/alias keploy/d' "$shell_rc_file" - fi - fi - fi - # Unset the alias in the current shell session if it exists - if alias keploy &>/dev/null; then - unalias keploy - fi - } - - cleanup_tmp() { - # Remove extracted files /tmp directory - tmp_files=("LICENSE" "README.md" "READMEes-Es.md" "README-UnitGen.md") - for file in "${tmp_files[@]}"; do - if [ -f "/tmp/$file" ]; then - if [ "$NO_ROOT" = "true" ]; then - rm -rf "/tmp/$file" - else - sudo rm -rf "/tmp/$file" - fi - - fi - done - } - - ARCH=$(uname -m) - - OS_NAME="$(uname -s)" - if [ "$OS_NAME" = "Darwin" ]; then - NO_ROOT=true - fi - - if [ "$IS_CI" = false ]; then - OS_NAME="$(uname -s)" - if [ "$OS_NAME" = "Darwin" ]; then - cleanup_tmp - install_keploy_darwin_all - return - elif [ "$OS_NAME" = "Linux" ]; then - if [ "$NO_ROOT" = false ]; then - if ! mountpoint -q /sys/kernel/debug; then - sudo mount -t debugfs debugfs /sys/kernel/debug - fi - fi - if [ "$ARCH" = "x86_64" ]; then - cleanup_tmp - install_keploy_amd - elif [ "$ARCH" = "aarch64" ]; then - cleanup_tmp - install_keploy_arm - else - echo "Unsupported architecture: $ARCH" - return - fi - elif [[ "$OS_NAME" == MINGW32_NT* ]]; then - echo "\e]8;; https://pureinfotech.com/install-windows-subsystem-linux-2-windows-10\aWindows not supported please run on WSL2\e]8;;\a" - elif [[ "$OS_NAME" == MINGW64_NT* ]]; then - echo "\e]8;; https://pureinfotech.com/install-windows-subsystem-linux-2-windows-10\aWindows not supported please run on WSL2\e]8;;\a" - else - echo "Unknown OS, install Linux to run Keploy" - fi - else - if [ "$ARCH" = "x86_64" ]; then - cleanup_tmp - install_keploy_amd - elif [ "$ARCH" = "aarch64" ]; then - cleanup_tmp - install_keploy_arm - else - echo "Unsupported architecture: $ARCH" - return - fi - fi -} - -installKeploy "$@" - -if command -v keploy &> /dev/null; then - keploy example - cleanup_tmp - rm -rf keploy.sh - rm -rf install.sh -fi \ No newline at end of file diff --git a/keploy/main.go b/keploy/main.go deleted file mode 100755 index acea761..0000000 --- a/keploy/main.go +++ /dev/null @@ -1,117 +0,0 @@ -// Package main is the entry point for the keploy application. -package main - -import ( - "context" - "fmt" - "os" - "strings" - - "go.keploy.io/server/v2/cli" - "go.keploy.io/server/v2/cli/provider" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/platform/auth" - userDb "go.keploy.io/server/v2/pkg/platform/yaml/configdb/user" - "go.keploy.io/server/v2/utils" - "go.keploy.io/server/v2/utils/log" - //pprof for debugging - // "net/http" - // _ "net/http/pprof" -) - -// version is the version of the server and will be injected during build by ldflags, same with dsn -// see https://goreleaser.com/customization/build/ - -var version string -var dsn string -var apiServerURI = "http://localhost:8083" -var gitHubClientID = "Iv23liFBvIVhL29i9BAp" - -func main() { - // Uncomment the following code to enable pprof for debugging - // go func() { - // fmt.Println("Starting pprof server for debugging...") - // err := http.ListenAndServe("localhost:6060", nil) - // if err != nil { - // fmt.Println("Failed to start the pprof server for debugging", err) - // return - // } - // }() - setVersion() - ctx := utils.NewCtx() - start(ctx) - os.Exit(utils.ErrCode) -} - -func setVersion() { - if version == "" { - version = "2-dev" - } - utils.Version = version - utils.VersionIdenitfier = "version" -} - -func start(ctx context.Context) { - logger, logFile, err := log.New() - if err != nil { - fmt.Println("Failed to start the logger for the CLI", err) - return - } - utils.LogFile = logFile - - defer func() { - inDocker := os.Getenv("KEPLOY_INDOCKER") - if inDocker != "true" { - if utils.LogFile != nil { - err := utils.LogFile.Close() - if err != nil { - utils.LogError(logger, err, "Failed to close Keploy Logs") - } - } - if err := utils.DeleteFileIfNotExists(logger, "keploy-logs.txt"); err != nil { - return - } - if err := utils.DeleteFileIfNotExists(logger, "docker-compose-tmp.yaml"); err != nil { - return - } - } - }() - defer utils.Recover(logger) - - // The 'umask' command is commonly used in various operating systems to regulate the permissions of newly created files. - // These 'umask' values subtract from the permissions assigned by the process, effectively lowering the permissions. - // For example, if a file is created with permissions '777' and the 'umask' is '022', the resulting permissions will be '755', - // reducing certain permissions for security purposes. - // Setting 'umask' to '0' ensures that 'keploy' can precisely control the permissions of the files it creates. - // However, it's important to note that this approach may not work in scenarios involving mounted volumes, - // as the 'umask' is set by the host system, and cannot be overridden by 'keploy' or individual processes. - oldMask := utils.SetUmask() - defer utils.RestoreUmask(oldMask) - - if dsn != "" { - utils.SentryInit(logger, dsn) - //logger = utils.ModifyToSentryLogger(ctx, logger, sentry.CurrentHub().Client(), configDb) - } - conf := config.New() - conf.APIServerURL = apiServerURI - conf.GitHubClientID = gitHubClientID - userDb := userDb.New(logger, conf) - conf.InstallationID, err = userDb.GetInstallationID(ctx) - if err != nil { - errMsg := "failed to get installation id" - utils.LogError(logger, err, errMsg) - os.Exit(1) - } - auth := auth.New(conf.APIServerURL, conf.InstallationID, logger, conf.GitHubClientID) - - svcProvider := provider.NewServiceProvider(logger, conf, auth) - cmdConfigurator := provider.NewCmdConfigurator(logger, conf) - rootCmd := cli.Root(ctx, logger, svcProvider, cmdConfigurator) - if err := rootCmd.Execute(); err != nil { - if strings.HasPrefix(err.Error(), "unknown command") || strings.HasPrefix(err.Error(), "unknown shorthand") { - fmt.Println("Error: ", err.Error()) - fmt.Println("Run 'keploy --help' for usage.") - os.Exit(1) - } - } -} diff --git a/keploy/oss-pledge.json b/keploy/oss-pledge.json deleted file mode 100644 index c3d34d1..0000000 --- a/keploy/oss-pledge.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "Keploy Inc", - "description": "Keploy is an open-source, zero-code testing tool that makes API and microservices testing simplify and helps developers boost code coverage. As an open-source project itself, we use a lot of open-source projects and we think it's fit to give it back to community.", - "urlLearnMore": "https://keploy.io/blog/community/introducing-the-keploy-oss-fund", - "urlSquareLogoWithBackground": "https://avatars.githubusercontent.com/u/92252339", - "annualReports": [ - { - "url": "https://keploy.io/blog/community/introducing-the-keploy-oss-fund", - "dateYearEnding": "2024-12-31", - "averageNumberOfDevs": 12, - "payments": 12500 - } - ] -} diff --git a/keploy/package-lock.json b/keploy/package-lock.json deleted file mode 100644 index 1ef61ac..0000000 --- a/keploy/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "keploy", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/keploy/pkg/README.md b/keploy/pkg/README.md deleted file mode 100755 index 7d0c1a5..0000000 --- a/keploy/pkg/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# PKG Documentation - -This package comprises interface methods and Go structs, -which can be utilized by external applications. It's -employed to execute logic and store data for the CLI -commands. \ No newline at end of file diff --git a/keploy/pkg/core/app/app.go b/keploy/pkg/core/app/app.go deleted file mode 100644 index de959c2..0000000 --- a/keploy/pkg/core/app/app.go +++ /dev/null @@ -1,582 +0,0 @@ -//go:build linux - -// Package app provides functionality for managing applications. -package app - -import ( - "context" - "errors" - "fmt" - "os/exec" - "syscall" - "time" - - "golang.org/x/sync/errgroup" - - "go.keploy.io/server/v2/pkg/models" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/events" - "github.com/docker/docker/api/types/filters" - "go.keploy.io/server/v2/pkg/platform/docker" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func NewApp(logger *zap.Logger, id uint64, cmd string, client docker.Client, opts Options) *App { - app := &App{ - logger: logger, - id: id, - cmd: cmd, - docker: client, - kind: utils.FindDockerCmd(cmd), - keployContainer: "keploy-v2", - container: opts.Container, - containerDelay: opts.DockerDelay, - containerNetwork: opts.DockerNetwork, - } - return app -} - -type App struct { - logger *zap.Logger - docker docker.Client - id uint64 - cmd string - kind utils.CmdType - containerDelay uint64 - container string - containerNetwork string - containerIPv4 chan string - keployNetwork string - keployContainer string - keployIPv4 string - inodeChan chan uint64 - EnableTesting bool - Mode models.Mode -} - -type Options struct { - // canExit disables any error returned if the app exits by itself. - //CanExit bool - Container string - DockerDelay uint64 - DockerNetwork string -} - -func (a *App) Setup(_ context.Context) error { - - if utils.IsDockerCmd(a.kind) && isDetachMode(a.logger, a.cmd, a.kind) { - return fmt.Errorf("application could not be started in detached mode") - } - - switch a.kind { - case utils.DockerRun, utils.DockerStart: - err := a.SetupDocker() - if err != nil { - return err - } - case utils.DockerCompose: - err := a.SetupCompose() - if err != nil { - return err - } - default: - // setup native binary - } - return nil -} - -func (a *App) KeployIPv4Addr() string { - return a.keployIPv4 -} - -func (a *App) ContainerIPv4Addr() string { - return <-a.containerIPv4 -} -func (a *App) SetContainerIPv4Addr(ipAddr string) { - a.logger.Debug("setting container IPv4 address", zap.String("ipAddr", ipAddr)) - a.containerIPv4 <- ipAddr -} - -func (a *App) SetupDocker() error { - - if a.kind == utils.DockerStart { - running, err := a.docker.IsContainerRunning(a.container) - if err != nil { - return err - } - if running { - return fmt.Errorf("docker container is already in running state") - } - } - - //injecting appNetwork to keploy. - err := a.injectNetwork(a.containerNetwork) - if err != nil { - utils.LogError(a.logger, err, fmt.Sprintf("failed to inject network:%v to the keploy container", a.containerNetwork)) - return err - } - return nil -} - -func (a *App) SetupCompose() error { - if a.container == "" { - utils.LogError(a.logger, nil, "container name not found", zap.String("AppCmd", a.cmd)) - return errors.New("container name not found") - } - a.logger.Info("keploy requires docker compose containers to be run with external network") - //finding the user docker-compose file in the current directory. - // TODO currently we just return the first default docker-compose file found in the current directory - // we should add support for multiple docker-compose files by either parsing cmd for path - // or by asking the user to provide the path - // kdocker-compose.yaml file will be run instead of the user docker-compose.yaml file acc to below cases - - path := findComposeFile(a.cmd) - if path == "" { - return errors.New("can't find the docker compose file of user. Are you in the right directory? ") - } - - a.logger.Info(fmt.Sprintf("Found docker compose file path: %s", path)) - - newPath := "docker-compose-tmp.yaml" - - compose, err := a.docker.ReadComposeFile(path) - if err != nil { - utils.LogError(a.logger, err, "failed to read the compose file") - return err - } - composeChanged := false - - // Check if docker compose file uses relative file names for bind mounts - ok := a.docker.HasRelativePath(compose) - if ok { - err = a.docker.ForceAbsolutePath(compose, path) - if err != nil { - utils.LogError(a.logger, nil, "failed to convert relative paths to absolute paths in volume mounts in docker compose file") - return err - } - composeChanged = true - } - - // Checking info about the network and whether its external:true - info := a.docker.GetNetworkInfo(compose) - - if info == nil { - info, err = a.docker.SetKeployNetwork(compose) - if err != nil { - utils.LogError(a.logger, nil, "failed to set default network in the compose file", zap.String("network", a.keployNetwork)) - return err - } - composeChanged = true - } - - if !info.External { - err = a.docker.MakeNetworkExternal(compose) - if err != nil { - utils.LogError(a.logger, nil, "failed to make the network external in the compose file", zap.String("network", info.Name)) - return fmt.Errorf("error while updating network to external: %v", err) - } - composeChanged = true - } - - a.keployNetwork = info.Name - - ok, err = a.docker.NetworkExists(a.keployNetwork) - if err != nil { - utils.LogError(a.logger, nil, "failed to find default network", zap.String("network", a.keployNetwork)) - return err - } - - //if keploy-network doesn't exist locally then create it - if !ok { - err = a.docker.CreateNetwork(a.keployNetwork) - if err != nil { - utils.LogError(a.logger, nil, "failed to create default network", zap.String("network", a.keployNetwork)) - return err - } - } - - if composeChanged { - err = a.docker.WriteComposeFile(compose, newPath) - if err != nil { - utils.LogError(a.logger, nil, "failed to write the compose file", zap.String("path", newPath)) - } - a.logger.Info("Created new docker-compose for keploy internal use", zap.String("path", newPath)) - //Now replace the running command to run the kdocker-compose.yaml file instead of user docker compose file. - a.cmd = modifyDockerComposeCommand(a.cmd, newPath) - } - - if a.containerNetwork == "" { - a.containerNetwork = a.keployNetwork - } - err = a.injectNetwork(a.containerNetwork) - if err != nil { - utils.LogError(a.logger, err, fmt.Sprintf("failed to inject network:%v to the keploy container", a.containerNetwork)) - return err - } - return nil -} - -func (a *App) SetAppCommand(appCommand string) { - a.logger.Debug("Setting App Command", zap.String("cmd", appCommand)) - a.cmd = appCommand -} - -func (a *App) GetAppCommand() string { - return a.cmd -} - -func (a *App) Kind(_ context.Context) utils.CmdType { - return a.kind -} - -// injectNetwork attaches the given network to the keploy container -// and also sends the keploy container ip of the new network interface to the kernel space -func (a *App) injectNetwork(network string) error { - // inject the network to the keploy container - a.logger.Info(fmt.Sprintf("trying to inject network:%v to the keploy container", network)) - err := a.docker.AttachNetwork(a.keployContainer, []string{network}) - if err != nil { - utils.LogError(a.logger, nil, "failed to inject network to the keploy container") - return err - } - - a.keployNetwork = network - - //sending new proxy ip to kernel, since dynamically injected new network has different ip for keploy. - inspect, err := a.docker.ContainerInspect(context.Background(), a.keployContainer) - if err != nil { - utils.LogError(a.logger, nil, fmt.Sprintf("failed to get inspect keploy container:%v", inspect)) - return err - } - - keployNetworks := inspect.NetworkSettings.Networks - //Here we considering that the application would use only one custom network. - //TODO: handle for application having multiple custom networks - //TODO: check the logic for correctness - for n, settings := range keployNetworks { - if n == network { - a.keployIPv4 = settings.IPAddress - a.logger.Info("Successfully injected network to the keploy container", zap.Any("Keploy container", a.keployContainer), zap.Any("appNetwork", network), zap.String("keploy container ip", a.keployIPv4)) - return nil - } - //if networkName != "bridge" { - // network = networkName - // newProxyIpString = networkSettings.IPAddress - // a.logger.Debug(fmt.Sprintf("Network Name: %s, New Proxy IP: %s\n", networkName, networkSettings.IPAddress)) - //} - } - return fmt.Errorf("failed to find the network:%v in the keploy container", network) -} -func (a *App) extractMeta(ctx context.Context, e events.Message) (bool, error) { - - if e.Action != "start" { - return false, nil - } - // Fetch container details by inspecting using container ID to check if container is created - info, err := a.docker.ContainerInspect(ctx, e.ID) - if err != nil { - a.logger.Debug("failed to inspect container by container Id", zap.Error(err)) - return false, err - } - - // Check if the container's name matches the desired name - if info.Name != "/"+a.container { - a.logger.Debug("ignoring container creation for unrelated container", zap.String("containerName", info.Name)) - return false, nil - } - - // Set Docker Container ID - a.docker.SetContainerID(e.ID) - a.logger.Debug("checking for container pid", zap.Any("containerDetails.State.Pid", info.State.Pid)) - if info.State.Pid == 0 { - return false, errors.New("failed to get the pid of the container") - } - a.logger.Debug("", zap.Any("containerDetails.State.Pid", info.State.Pid), zap.String("containerName", a.container)) - inode, err := getInode(info.State.Pid) - if err != nil { - return false, err - } - - a.inodeChan <- inode - a.logger.Debug("container started and successfully extracted inode", zap.Any("inode", inode)) - if info.NetworkSettings == nil || info.NetworkSettings.Networks == nil { - a.logger.Debug("container network settings not available", zap.Any("containerDetails.NetworkSettings", info.NetworkSettings)) - return false, nil - } - - n, ok := info.NetworkSettings.Networks[a.containerNetwork] - if !ok || n == nil { - a.logger.Debug("container network not found", zap.Any("containerDetails.NetworkSettings.Networks", info.NetworkSettings.Networks)) - return false, fmt.Errorf("container network not found: %s", fmt.Sprintf("%+v", info.NetworkSettings.Networks)) - } - a.SetContainerIPv4Addr(n.IPAddress) - return inode != 0 && n.IPAddress != "", nil -} - -func (a *App) getDockerMeta(ctx context.Context) <-chan error { - // listen for the docker daemon events - defer a.logger.Debug("exiting from goroutine of docker daemon event listener") - - errCh := make(chan error, 1) - timer := time.NewTimer(time.Duration(a.containerDelay) * time.Second) - logTicker := time.NewTicker(1 * time.Second) - defer logTicker.Stop() - - eventFilter := filters.NewArgs( - filters.KeyValuePair{Key: "type", Value: "container"}, - filters.KeyValuePair{Key: "type", Value: "network"}, - filters.KeyValuePair{Key: "action", Value: "create"}, - filters.KeyValuePair{Key: "action", Value: "connect"}, - filters.KeyValuePair{Key: "action", Value: "start"}, - ) - - messages, errCh2 := a.docker.Events(ctx, types.EventsOptions{ - Filters: eventFilter, - }) - - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - errCh <- errors.New("failed to get the error group from the context") - return errCh - } - - g.Go(func() error { - defer utils.Recover(a.logger) - // closing the channels in any case when returning. - defer func() { - a.logger.Debug("closing err, containerIPv4 and inode channels ") - close(errCh) - close(a.containerIPv4) - close(a.inodeChan) - }() - for { - select { - case <-timer.C: - errCh <- errors.New("timeout waiting for the container to start") - return nil - case <-ctx.Done(): - a.logger.Debug("context cancelled, stopping the listener for container creation event.") - errCh <- ctx.Err() - return nil - case e := <-messages: - done, err := a.extractMeta(ctx, e) - if err != nil { - errCh <- err - return nil - } - - if done { - return nil - } - // for debugging purposes - case <-logTicker.C: - a.logger.Debug("still waiting for the container to start.", zap.String("containerName", a.container)) - return nil - case err := <-errCh2: - errCh <- err - return nil - } - } - }) - return errCh -} - -func (a *App) runDocker(ctx context.Context) models.AppError { - // if a.cmd is empty, it means the user wants to run the application manually, - // so we don't need to run the application in a goroutine - if a.cmd == "" { - return models.AppError{} - } - - g, ctx := errgroup.WithContext(ctx) - ctx = context.WithValue(ctx, models.ErrGroupKey, g) - - dockerMetaCtx, cancel := context.WithCancel(ctx) - - defer func() { - cancel() - err := g.Wait() - if err != nil { - utils.LogError(a.logger, err, "failed to run dockerized app") - } - }() - - errCh := make(chan error, 1) - - // listen for the "create container" event in order to send the inode of the container to the kernel - errCh2 := a.getDockerMeta(dockerMetaCtx) - - g.Go(func() error { - defer utils.Recover(a.logger) - defer close(errCh) - err := a.run(ctx) - if err.Err != nil { - utils.LogError(a.logger, err.Err, "Application stopped with the error") - errCh <- err.Err - } - return nil - }) - - select { - case err := <-errCh: - if err != nil && errors.Is(err, context.Canceled) { - return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: ctx.Err()} - } - return models.AppError{AppErrorType: models.ErrInternal, Err: err} - case err := <-errCh2: - if err != nil && errors.Is(err, context.Canceled) { - return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: ctx.Err()} - } - return models.AppError{AppErrorType: models.ErrInternal, Err: err} - case <-ctx.Done(): - return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: ctx.Err()} - } -} - -func (a *App) Run(ctx context.Context, inodeChan chan uint64) models.AppError { - a.inodeChan = inodeChan - a.containerIPv4 = make(chan string, 1) - if utils.IsDockerCmd(a.kind) { - return a.runDocker(ctx) - } - return a.run(ctx) -} -func (a *App) waitTillExit() { - timeout := time.NewTimer(30 * time.Second) - logTicker := time.NewTicker(1 * time.Second) - defer logTicker.Stop() - defer timeout.Stop() - - containerID := a.container - for { - select { - case <-logTicker.C: - // Inspect the container status - containerJSON, err := a.docker.ContainerInspect(context.Background(), containerID) - if err != nil { - a.logger.Debug("failed to inspect container", zap.String("containerID", containerID), zap.Error(err)) - return - } - - a.logger.Debug("container status", zap.String("status", containerJSON.State.Status), zap.String("containerName", a.container)) - // Check if container is stopped or dead - if containerJSON.State.Status == "exited" || containerJSON.State.Status == "dead" { - return - } - case <-timeout.C: - a.logger.Warn("timeout waiting for the container to stop", zap.String("containerID", containerID)) - return - } - } -} - -func (a *App) run(ctx context.Context) models.AppError { - userCmd := a.cmd - - if utils.FindDockerCmd(a.cmd) == utils.DockerRun { - userCmd = utils.EnsureRmBeforeName(userCmd) - } - - // Define the function to cancel the command - cmdCancel := func(cmd *exec.Cmd) func() error { - return func() error { - if utils.IsDockerCmd(a.kind) { - a.logger.Debug("sending SIGINT to the container", zap.Any("cmd.Process.Pid", cmd.Process.Pid)) - err := utils.SendSignal(a.logger, -cmd.Process.Pid, syscall.SIGINT) - - return err - } - return utils.InterruptProcessTree(a.logger, cmd.Process.Pid, syscall.SIGINT) - } - } - - var err error - cmdErr := utils.ExecuteCommand(ctx, a.logger, userCmd, cmdCancel, 25*time.Second) - if cmdErr.Err != nil { - switch cmdErr.Type { - case utils.Init: - return models.AppError{AppErrorType: models.ErrCommandError, Err: cmdErr.Err} - case utils.Runtime: - err = cmdErr.Err - } - } - - if utils.IsDockerCmd(a.kind) { - a.waitTillExit() - } - - select { - case <-ctx.Done(): - a.logger.Debug("context cancelled, error while waiting for the app to exit", zap.Error(ctx.Err())) - return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: ctx.Err()} - default: - if a.Mode == models.MODE_RECORD && a.EnableTesting { - a.logger.Info("waiting for some time before returning the error to allow recording of test cases when testing keploy with itself") - time.Sleep(3 * time.Second) - a.logger.Debug("test binary stopped", zap.Error(err)) - return models.AppError{AppErrorType: models.ErrTestBinStopped, Err: context.Canceled} - } - - if err != nil { - return models.AppError{AppErrorType: models.ErrUnExpected, Err: err} - } - return models.AppError{AppErrorType: models.ErrAppStopped, Err: nil} - } -} - -//if a.docker.GetContainerID() == "" { -// a.logger.Debug("still waiting for the container to start.", zap.String("containerName", a.container)) -// continue -//} -////Inspecting the application container again since the ip and pid takes some time to be linked to the container. -//info, err := a.docker.ContainerInspect(ctx, a.container) -//if err != nil { -// return err -//} -// -//a.logger.Debug("checking for container pid", zap.Any("containerDetails.State.Pid", info.State.Pid)) -//if info.State.Pid == 0 { -// a.logger.Debug("container not yet started", zap.Any("containerDetails.State.Pid", info.State.Pid)) -// continue -//} -//a.logger.Debug("", zap.Any("containerDetails.State.Pid", info.State.Pid), zap.String("containerName", a.container)) -//a.inode,err = getInode(info.State.Pid) -//if err != nil { -// return err -//} -//if info.NetworkSettings == nil || info.NetworkSettings.Networks == nil { -// a.logger.Debug("container network settings not available", zap.Any("containerDetails.NetworkSettings", info.NetworkSettings)) -// continue -//} -// -//n, ok := info.NetworkSettings.Networks[a.containerNetwork] -//if !ok || n == nil { -// return errors.New("container network not found") -//} -//a.keployIPv4 = n.IPAddress -//a.logger.Info("container started successfully", zap.Any("", info.NetworkSettings.Networks)) -//return - -//case e := <-messages: -// if e.Type != events.ContainerEventType || e.Action != "start" { -// continue -// } -// -// // Fetch container details by inspecting using container ID to check if container is created -// c, err := a.docker.ContainerInspect(ctx, e.ID) -// if err != nil { -// a.logger.Debug("failed to inspect container by container Id", zap.Error(err)) -// return err -// } -// -// // Check if the container's name matches the desired name -// if c.Name != "/"+a.container { -// a.logger.Debug("ignoring container creation for unrelated container", zap.String("containerName", c.Name)) -// continue -// } -// // Set Docker Container ID -// a.docker.SetContainerID(e.ID) -// -// a.logger.Debug("container created for desired app", zap.Any("ID", e.ID)) diff --git a/keploy/pkg/core/app/util.go b/keploy/pkg/core/app/util.go deleted file mode 100644 index 6d374fe..0000000 --- a/keploy/pkg/core/app/util.go +++ /dev/null @@ -1,103 +0,0 @@ -//go:build linux - -package app - -import ( - "fmt" - "os" - "path/filepath" - "regexp" - "slices" - "strconv" - "strings" - "syscall" - - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func findComposeFile(cmd string) string { - - cmdArgs := strings.Fields(cmd) - - for i := 0; i < len(cmdArgs); i++ { - if cmdArgs[i] == "-f" && i+1 < len(cmdArgs) { - return cmdArgs[i+1] - } - } - - filenames := []string{"docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"} - - for _, filename := range filenames { - if _, err := os.Stat(filename); !os.IsNotExist(err) { - return filename - } - } - - return "" -} - -func modifyDockerComposeCommand(appCmd, newComposeFile string) string { - // Ensure newComposeFile starts with ./ - if !strings.HasPrefix(newComposeFile, "./") { - newComposeFile = "./" + newComposeFile - } - - // Define a regular expression pattern to match "-f " - pattern := `(-f\s+("[^"]+"|'[^']+'|\S+))` - re := regexp.MustCompile(pattern) - - // Check if the "-f " pattern exists in the appCmd - if re.MatchString(appCmd) { - // Replace it with the new Compose file - return re.ReplaceAllString(appCmd, fmt.Sprintf("-f %s", newComposeFile)) - } - - // If the pattern doesn't exist, inject the new Compose file right after "docker-compose" or "docker compose" - upIdx := strings.Index(appCmd, " up") - if upIdx != -1 { - return fmt.Sprintf("%s -f %s%s", appCmd[:upIdx], newComposeFile, appCmd[upIdx:]) - } - - return fmt.Sprintf("%s -f %s", appCmd, newComposeFile) -} - -func getInode(pid int) (uint64, error) { - path := filepath.Join("/proc", strconv.Itoa(pid), "ns", "pid") - - f, err := os.Stat(path) - if err != nil { - return 0, err - } - // Dev := (f.Sys().(*syscall.Stat_t)).Dev - i := (f.Sys().(*syscall.Stat_t)).Ino - if i == 0 { - return 0, fmt.Errorf("failed to get the inode of the process") - } - return i, nil -} - -func isDetachMode(logger *zap.Logger, command string, kind utils.CmdType) bool { - args := strings.Fields(command) - - if kind == utils.DockerStart { - flags := []string{"-a", "--attach", "-i", "--interactive"} - - for _, arg := range args { - if slices.Contains(flags, arg) { - return false - } - } - utils.LogError(logger, fmt.Errorf("docker start require --attach/-a or --interactive/-i flag"), "failed to start command") - return true - } - - for _, arg := range args { - if arg == "-d" || arg == "--detach" { - utils.LogError(logger, fmt.Errorf("detach mode is not allowed in Keploy command"), "failed to start command") - return true - } - } - - return false -} diff --git a/keploy/pkg/core/core_linux.go b/keploy/pkg/core/core_linux.go deleted file mode 100644 index 3ac1296..0000000 --- a/keploy/pkg/core/core_linux.go +++ /dev/null @@ -1,279 +0,0 @@ -//go:build linux - -// Package core provides functionality for managing core functionalities in Keploy. -package core - -import ( - "context" - "errors" - "fmt" - "sync" - - "golang.org/x/sync/errgroup" - - "go.keploy.io/server/v2/pkg/core/app" - "go.keploy.io/server/v2/pkg/core/hooks/structs" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/docker" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type Core struct { - Proxy // embedding the Proxy interface to transfer the proxy methods to the core object - Hooks // embedding the Hooks interface to transfer the hooks methods to the core object - Tester // embedding the Tester interface to transfer the tester methods to the core object - dockerClient docker.Client //embedding the docker client to transfer the docker client methods to the core object - logger *zap.Logger - id utils.AutoInc - apps sync.Map - proxyStarted bool -} - -func New(logger *zap.Logger, hook Hooks, proxy Proxy, tester Tester, client docker.Client) *Core { - return &Core{ - logger: logger, - Hooks: hook, - Proxy: proxy, - Tester: tester, - dockerClient: client, - } -} - -func (c *Core) Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) { - // create a new app and store it in the map - id := uint64(c.id.Next()) - a := app.NewApp(c.logger, id, cmd, c.dockerClient, app.Options{ - DockerNetwork: opts.DockerNetwork, - Container: opts.Container, - DockerDelay: opts.DockerDelay, - }) - c.apps.Store(id, a) - - err := a.Setup(ctx) - if err != nil { - utils.LogError(c.logger, err, "failed to setup app") - return 0, err - } - return id, nil -} - -func (c *Core) getApp(id uint64) (*app.App, error) { - a, ok := c.apps.Load(id) - if !ok { - return nil, fmt.Errorf("app with id:%v not found", id) - } - - // type assertion on the app - h, ok := a.(*app.App) - if !ok { - return nil, fmt.Errorf("failed to type assert app with id:%v", id) - } - - return h, nil -} - -func (c *Core) Hook(ctx context.Context, id uint64, opts models.HookOptions) error { - hookErr := errors.New("failed to hook into the app") - - a, err := c.getApp(id) - if err != nil { - utils.LogError(c.logger, err, "failed to get app") - return hookErr - } - - isDocker := utils.IsDockerCmd(a.Kind(ctx)) - - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context") - } - - // Create a new error group for the hooks (Always required) - hookErrGrp, _ := errgroup.WithContext(ctx) - hookCtx := context.WithoutCancel(ctx) //so that main context doesn't cancel the hookCtx to control the lifecycle of the hooks - hookCtx, hookCtxCancel := context.WithCancel(hookCtx) - hookCtx = context.WithValue(hookCtx, models.ErrGroupKey, hookErrGrp) - - // create a new error group for the proxy - proxyErrGrp, _ := errgroup.WithContext(ctx) - proxyCtx := context.WithoutCancel(ctx) //so that main context doesn't cancel the proxyCtx to control the lifecycle of the proxy - proxyCtx, proxyCtxCancel := context.WithCancel(proxyCtx) - proxyCtx = context.WithValue(proxyCtx, models.ErrGroupKey, proxyErrGrp) - - g.Go(func() error { - <-ctx.Done() - proxyCtxCancel() - err = proxyErrGrp.Wait() - if err != nil { - utils.LogError(c.logger, err, "failed to stop the proxy") - } - - hookCtxCancel() - err := hookErrGrp.Wait() - if err != nil { - utils.LogError(c.logger, err, "failed to unload the hooks") - } - - //deleting in order to free the memory in case of rerecord. otherwise different app id will be created for the same app. - c.apps.Delete(id) - c.id.Reset() - - return nil - }) - - // Load hooks - err = c.Load(hookCtx, id, HookCfg{ - AppID: id, - Pid: 0, - IsDocker: isDocker, - KeployIPV4: a.KeployIPv4Addr(), - Mode: opts.Mode, - Rules: opts.Rules, - E2E: opts.E2E, - Port: opts.Port, - }) - if err != nil { - utils.LogError(c.logger, err, "failed to load hooks") - return hookErr - } - - if c.proxyStarted { - c.logger.Debug("Proxy already started") - // return nil - } - - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - // TODO: Hooks can be loaded multiple times but proxy should be started only once - // if there is another containerized app, then we need to pass new (ip:port) of proxy to the eBPF - // as the network namespace is different for each container and so is the keploy/proxy IP to communicate with the app. - // start proxy - err = c.StartProxy(proxyCtx, ProxyOptions{ - DNSIPv4Addr: a.KeployIPv4Addr(), - //DnsIPv6Addr: "" - }) - if err != nil { - utils.LogError(c.logger, err, "failed to start proxy") - return hookErr - } - - c.proxyStarted = true - - // For keploy test bench - if opts.EnableTesting { - - // enable testing in the app - a.EnableTesting = true - a.Mode = opts.Mode - - // Setting up the test bench - err := c.Tester.Setup(ctx, models.TestingOptions{Mode: opts.Mode}) - if err != nil { - utils.LogError(c.logger, err, "error while setting up the test bench environment") - return errors.New("failed to setup the test bench") - } - } - - return nil -} - -// GetHookUnloadDone returns a channel that signals when hooks are completely unloaded -func (c *Core) GetHookUnloadDone(id uint64) <-chan struct{} { - return c.GetUnloadDone() -} - -func (c *Core) Run(ctx context.Context, id uint64, opts models.RunOptions) models.AppError { - a, err := c.getApp(id) - if err != nil { - utils.LogError(c.logger, err, "failed to get app") - return models.AppError{AppErrorType: models.ErrInternal, Err: err} - } - - runAppErrGrp, runAppCtx := errgroup.WithContext(ctx) - - inodeErrCh := make(chan error, 1) - appErrCh := make(chan models.AppError, 1) - inodeChan := make(chan uint64, 1) //send inode to the hook - - defer func() { - err := runAppErrGrp.Wait() - defer close(inodeErrCh) - if err != nil { - utils.LogError(c.logger, err, "failed to stop the app") - } - }() - - runAppErrGrp.Go(func() error { - defer utils.Recover(c.logger) - if a.Kind(ctx) == utils.Native { - close(inodeChan) // since we are not using inode in native mode - return nil - } - select { - case inode := <-inodeChan: - err := c.SendDockerAppInfo(id, structs.DockerAppInfo{AppInode: inode, ClientID: id}) - if err != nil { - utils.LogError(c.logger, err, "") - - inodeErrCh <- errors.New("failed to send inode to the kernel") - } - case <-ctx.Done(): - return nil - } - return nil - }) - - originalApp := a.GetAppCommand() - runAppErrGrp.Go(func() error { - defer utils.Recover(c.logger) - defer close(appErrCh) - defer a.SetAppCommand(originalApp) - if opts.AppCommand != "" { - a.SetAppCommand(opts.AppCommand) - } - appErr := a.Run(runAppCtx, inodeChan) - if appErr.Err != nil { - utils.LogError(c.logger, appErr.Err, "error while running the app") - appErrCh <- appErr - } - return nil - }) - - select { - case <-runAppCtx.Done(): - c.logger.Debug("Run context cancelled, stopping the app and reverting the app command") - return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: nil} - case appErr := <-appErrCh: - return appErr - case inodeErr := <-inodeErrCh: - return models.AppError{AppErrorType: models.ErrInternal, Err: inodeErr} - } -} - -func (c *Core) GetContainerIP(_ context.Context, id uint64) (string, error) { - - a, err := c.getApp(id) - if err != nil { - utils.LogError(c.logger, err, "failed to get app") - return "", err - } - - ip := a.ContainerIPv4Addr() - c.logger.Debug("ip address of the target app container", zap.Any("ip", ip)) - if ip == "" { - return "", fmt.Errorf("failed to get the IP address of the app container. Try increasing --delay (in seconds)") - } - - return ip, nil -} diff --git a/keploy/pkg/core/core_others.go b/keploy/pkg/core/core_others.go deleted file mode 100644 index bc205ca..0000000 --- a/keploy/pkg/core/core_others.go +++ /dev/null @@ -1,61 +0,0 @@ -//go:build !linux - -// Package core provides functionality for managing core functionalities in Keploy. -package core - -import ( - "context" - "errors" - "runtime" - - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -type Core struct { - logger *zap.Logger -} - -var errUnsupported = errors.New("instrumentation only supported on linux. Detected OS: " + runtime.GOOS) - -func New(logger *zap.Logger) *Core { - return &Core{ - logger: logger, - } -} - -func (c *Core) Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) { - return 0, errUnsupported -} - -func (c *Core) Hook(ctx context.Context, id uint64, opts models.HookOptions) error { - return errUnsupported -} - -func (c *Core) GetHookUnloadDone(id uint64) <-chan struct{} { - ch := make(chan struct{}) - close(ch) // Immediately close since no actual hooks are loaded - return ch -} - -func (c *Core) MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error { - return errUnsupported -} - -func (c *Core) SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error { - return errUnsupported -} - -func (c *Core) GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) { - return nil, errUnsupported -} - -func (c *Core) Run(ctx context.Context, id uint64, _ models.RunOptions) models.AppError { - return models.AppError{ - Err: errUnsupported, - } -} - -func (c *Core) GetContainerIP(_ context.Context, id uint64) (string, error) { - return "", errUnsupported -} diff --git a/keploy/pkg/core/core_others_test.go b/keploy/pkg/core/core_others_test.go deleted file mode 100644 index b8aaeea..0000000 --- a/keploy/pkg/core/core_others_test.go +++ /dev/null @@ -1,92 +0,0 @@ -//go:build !linux - -package core - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "go.uber.org/zap" -) - -func TestCore_GetHookUnloadDone_NonLinux(t *testing.T) { - logger := zap.NewNop() - - // Create a new Core instance using the non-Linux constructor - core := New(logger) - - appID := uint64(12345) - - // Test that GetHookUnloadDone returns a channel - ch := core.GetHookUnloadDone(appID) - assert.NotNil(t, ch, "GetHookUnloadDone should return a channel") - - // For non-Linux platforms, the channel should be immediately closed - // since no actual hooks are loaded - select { - case <-ch: - // Expected behavior for non-Linux - channel is immediately closed - case <-time.After(100 * time.Millisecond): - t.Error("Channel should be immediately closed on non-Linux platforms") - } -} - -func TestCore_GetHookUnloadDone_NonLinux_MultipleCalls(t *testing.T) { - logger := zap.NewNop() - core := New(logger) - - appID := uint64(12345) - - // Get multiple channels - each should be immediately closed - ch1 := core.GetHookUnloadDone(appID) - ch2 := core.GetHookUnloadDone(appID) - - assert.NotNil(t, ch1, "First call should return a channel") - assert.NotNil(t, ch2, "Second call should return a channel") - - // Both channels should be immediately closed - select { - case <-ch1: - // Expected behavior - case <-time.After(100 * time.Millisecond): - t.Error("First channel should be immediately closed") - } - - select { - case <-ch2: - // Expected behavior - case <-time.After(100 * time.Millisecond): - t.Error("Second channel should be immediately closed") - } -} - -func TestCore_GetHookUnloadDone_NonLinux_DifferentApps(t *testing.T) { - logger := zap.NewNop() - core := New(logger) - - appID1 := uint64(12345) - appID2 := uint64(67890) - - // Get channels for different app IDs - ch1 := core.GetHookUnloadDone(appID1) - ch2 := core.GetHookUnloadDone(appID2) - - assert.NotNil(t, ch1, "First app should return a channel") - assert.NotNil(t, ch2, "Second app should return a channel") - - // Both should be immediately closed - select { - case <-ch1: - // Expected behavior - case <-time.After(100 * time.Millisecond): - t.Error("Channel for app1 should be immediately closed") - } - - select { - case <-ch2: - // Expected behavior - case <-time.After(100 * time.Millisecond): - t.Error("Channel for app2 should be immediately closed") - } -} diff --git a/keploy/pkg/core/hooks/README.md b/keploy/pkg/core/hooks/README.md deleted file mode 100755 index 24c2075..0000000 --- a/keploy/pkg/core/hooks/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Hooks Package Documentation - -The `hooks` package contains the user-space Go code responsible for -loading eBPF hooks and eBPF maps, which are used to instrument the user -API. This package is utilized by the CLI commands. Additionally, it -launches proxy on a defined port to capture egress calls. \ No newline at end of file diff --git a/keploy/pkg/core/hooks/bpf_arm64_bpfel.go b/keploy/pkg/core/hooks/bpf_arm64_bpfel.go deleted file mode 100644 index bca2d35..0000000 --- a/keploy/pkg/core/hooks/bpf_arm64_bpfel.go +++ /dev/null @@ -1,289 +0,0 @@ -// Code generated by bpf2go; DO NOT EDIT. -//go:build arm64 - -package hooks - -import ( - "bytes" - _ "embed" - "fmt" - "io" - - "github.com/cilium/ebpf" -) - -// loadBpf returns the embedded CollectionSpec for bpf. -func loadBpf() (*ebpf.CollectionSpec, error) { - reader := bytes.NewReader(_BpfBytes) - spec, err := ebpf.LoadCollectionSpecFromReader(reader) - if err != nil { - return nil, fmt.Errorf("can't load bpf: %w", err) - } - - return spec, err -} - -// loadBpfObjects loads bpf and converts it into a struct. -// -// The following types are suitable as obj argument: -// -// *bpfObjects -// *bpfPrograms -// *bpfMaps -// -// See ebpf.CollectionSpec.LoadAndAssign documentation for details. -func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { - spec, err := loadBpf() - if err != nil { - return err - } - - return spec.LoadAndAssign(obj, opts) -} - -// bpfSpecs contains maps and programs before they are loaded into the kernel. -// -// It can be passed ebpf.CollectionSpec.Assign. -type bpfSpecs struct { - bpfProgramSpecs - bpfMapSpecs - bpfVariableSpecs -} - -// bpfProgramSpecs contains programs before they are loaded into the kernel. -// -// It can be passed ebpf.CollectionSpec.Assign. -type bpfProgramSpecs struct { - K_connect4 *ebpf.ProgramSpec `ebpf:"k_connect4"` - K_connect6 *ebpf.ProgramSpec `ebpf:"k_connect6"` - K_getpeername4 *ebpf.ProgramSpec `ebpf:"k_getpeername4"` - K_getpeername6 *ebpf.ProgramSpec `ebpf:"k_getpeername6"` - SyscallProbeEntryAccept *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_accept"` - SyscallProbeEntryAccept4 *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_accept4"` - SyscallProbeEntryClose *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_close"` - SyscallProbeEntryRead *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_read"` - SyscallProbeEntryReadv *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_readv"` - SyscallProbeEntryRecvfrom *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_recvfrom"` - SyscallProbeEntrySendto *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_sendto"` - SyscallProbeEntryTcpV4Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v4_connect"` - SyscallProbeEntryTcpV4PreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v4_pre_connect"` - SyscallProbeEntryTcpV6Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v6_connect"` - SyscallProbeEntryTcpV6PreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v6_pre_connect"` - SyscallProbeEntryUdpPreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_udp_pre_connect"` - SyscallProbeEntryWrite *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_write"` - SyscallProbeEntryWritev *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_writev"` - SyscallProbeRetAccept *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_accept"` - SyscallProbeRetAccept4 *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_accept4"` - SyscallProbeRetClose *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_close"` - SyscallProbeRetConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_connect"` - SyscallProbeRetRead *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_read"` - SyscallProbeRetReadv *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_readv"` - SyscallProbeRetRecvfrom *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_recvfrom"` - SyscallProbeRetSendto *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_sendto"` - SyscallProbeRetTcpV4Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_tcp_v4_connect"` - SyscallProbeRetTcpV6Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_tcp_v6_connect"` - SyscallProbeRetWrite *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_write"` - SyscallProbeRetWritev *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_writev"` - SyscallProbeEntryConnect *ebpf.ProgramSpec `ebpf:"syscall_probe_entry_connect"` - SyscallProbeEntrySocket *ebpf.ProgramSpec `ebpf:"syscall_probe_entry_socket"` -} - -// bpfMapSpecs contains maps before they are loaded into the kernel. -// -// It can be passed ebpf.CollectionSpec.Assign. -type bpfMapSpecs struct { - ActiveAcceptArgsMap *ebpf.MapSpec `ebpf:"active_accept_args_map"` - ActiveCloseArgsMap *ebpf.MapSpec `ebpf:"active_close_args_map"` - ActiveReadArgsMap *ebpf.MapSpec `ebpf:"active_read_args_map"` - ActiveWriteArgsMap *ebpf.MapSpec `ebpf:"active_write_args_map"` - AppChildKernelPidMap *ebpf.MapSpec `ebpf:"app_child_kernel_pid_map"` - ConnInfoMap *ebpf.MapSpec `ebpf:"conn_info_map"` - CurrentSockMap *ebpf.MapSpec `ebpf:"current_sock_map"` - DestInfoMap *ebpf.MapSpec `ebpf:"dest_info_map"` - DockerAppRegistrationMap *ebpf.MapSpec `ebpf:"docker_app_registration_map"` - E2eInfoMap *ebpf.MapSpec `ebpf:"e2e_info_map"` - KeployAgentKernelPidMap *ebpf.MapSpec `ebpf:"keploy_agent_kernel_pid_map"` - KeployAgentRegistrationMap *ebpf.MapSpec `ebpf:"keploy_agent_registration_map"` - KeployClientKernelPidMap *ebpf.MapSpec `ebpf:"keploy_client_kernel_pid_map"` - KeployClientRegistrationMap *ebpf.MapSpec `ebpf:"keploy_client_registration_map"` - OutgoingConnCheckMap *ebpf.MapSpec `ebpf:"outgoing_conn_check_map"` - OutgoingConnectArgsMap *ebpf.MapSpec `ebpf:"outgoing_connect_args_map"` - RedirectProxyMap *ebpf.MapSpec `ebpf:"redirect_proxy_map"` - SocketCloseEvents *ebpf.MapSpec `ebpf:"socket_close_events"` - SocketDataEventBufferHeap *ebpf.MapSpec `ebpf:"socket_data_event_buffer_heap"` - SocketDataEvents *ebpf.MapSpec `ebpf:"socket_data_events"` - SocketOpenEvents *ebpf.MapSpec `ebpf:"socket_open_events"` - TaskStructMap *ebpf.MapSpec `ebpf:"task_struct_map"` -} - -// bpfVariableSpecs contains global variables before they are loaded into the kernel. -// -// It can be passed ebpf.CollectionSpec.Assign. -type bpfVariableSpecs struct { -} - -// bpfObjects contains all objects after they have been loaded into the kernel. -// -// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. -type bpfObjects struct { - bpfPrograms - bpfMaps - bpfVariables -} - -func (o *bpfObjects) Close() error { - return _BpfClose( - &o.bpfPrograms, - &o.bpfMaps, - ) -} - -// bpfMaps contains all maps after they have been loaded into the kernel. -// -// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. -type bpfMaps struct { - ActiveAcceptArgsMap *ebpf.Map `ebpf:"active_accept_args_map"` - ActiveCloseArgsMap *ebpf.Map `ebpf:"active_close_args_map"` - ActiveReadArgsMap *ebpf.Map `ebpf:"active_read_args_map"` - ActiveWriteArgsMap *ebpf.Map `ebpf:"active_write_args_map"` - AppChildKernelPidMap *ebpf.Map `ebpf:"app_child_kernel_pid_map"` - ConnInfoMap *ebpf.Map `ebpf:"conn_info_map"` - CurrentSockMap *ebpf.Map `ebpf:"current_sock_map"` - DestInfoMap *ebpf.Map `ebpf:"dest_info_map"` - DockerAppRegistrationMap *ebpf.Map `ebpf:"docker_app_registration_map"` - E2eInfoMap *ebpf.Map `ebpf:"e2e_info_map"` - KeployAgentKernelPidMap *ebpf.Map `ebpf:"keploy_agent_kernel_pid_map"` - KeployAgentRegistrationMap *ebpf.Map `ebpf:"keploy_agent_registration_map"` - KeployClientKernelPidMap *ebpf.Map `ebpf:"keploy_client_kernel_pid_map"` - KeployClientRegistrationMap *ebpf.Map `ebpf:"keploy_client_registration_map"` - OutgoingConnCheckMap *ebpf.Map `ebpf:"outgoing_conn_check_map"` - OutgoingConnectArgsMap *ebpf.Map `ebpf:"outgoing_connect_args_map"` - RedirectProxyMap *ebpf.Map `ebpf:"redirect_proxy_map"` - SocketCloseEvents *ebpf.Map `ebpf:"socket_close_events"` - SocketDataEventBufferHeap *ebpf.Map `ebpf:"socket_data_event_buffer_heap"` - SocketDataEvents *ebpf.Map `ebpf:"socket_data_events"` - SocketOpenEvents *ebpf.Map `ebpf:"socket_open_events"` - TaskStructMap *ebpf.Map `ebpf:"task_struct_map"` -} - -func (m *bpfMaps) Close() error { - return _BpfClose( - m.ActiveAcceptArgsMap, - m.ActiveCloseArgsMap, - m.ActiveReadArgsMap, - m.ActiveWriteArgsMap, - m.AppChildKernelPidMap, - m.ConnInfoMap, - m.CurrentSockMap, - m.DestInfoMap, - m.DockerAppRegistrationMap, - m.E2eInfoMap, - m.KeployAgentKernelPidMap, - m.KeployAgentRegistrationMap, - m.KeployClientKernelPidMap, - m.KeployClientRegistrationMap, - m.OutgoingConnCheckMap, - m.OutgoingConnectArgsMap, - m.RedirectProxyMap, - m.SocketCloseEvents, - m.SocketDataEventBufferHeap, - m.SocketDataEvents, - m.SocketOpenEvents, - m.TaskStructMap, - ) -} - -// bpfVariables contains all global variables after they have been loaded into the kernel. -// -// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. -type bpfVariables struct { -} - -// bpfPrograms contains all programs after they have been loaded into the kernel. -// -// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. -type bpfPrograms struct { - K_connect4 *ebpf.Program `ebpf:"k_connect4"` - K_connect6 *ebpf.Program `ebpf:"k_connect6"` - K_getpeername4 *ebpf.Program `ebpf:"k_getpeername4"` - K_getpeername6 *ebpf.Program `ebpf:"k_getpeername6"` - SyscallProbeEntryAccept *ebpf.Program `ebpf:"syscall__probe_entry_accept"` - SyscallProbeEntryAccept4 *ebpf.Program `ebpf:"syscall__probe_entry_accept4"` - SyscallProbeEntryClose *ebpf.Program `ebpf:"syscall__probe_entry_close"` - SyscallProbeEntryRead *ebpf.Program `ebpf:"syscall__probe_entry_read"` - SyscallProbeEntryReadv *ebpf.Program `ebpf:"syscall__probe_entry_readv"` - SyscallProbeEntryRecvfrom *ebpf.Program `ebpf:"syscall__probe_entry_recvfrom"` - SyscallProbeEntrySendto *ebpf.Program `ebpf:"syscall__probe_entry_sendto"` - SyscallProbeEntryTcpV4Connect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v4_connect"` - SyscallProbeEntryTcpV4PreConnect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v4_pre_connect"` - SyscallProbeEntryTcpV6Connect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v6_connect"` - SyscallProbeEntryTcpV6PreConnect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v6_pre_connect"` - SyscallProbeEntryUdpPreConnect *ebpf.Program `ebpf:"syscall__probe_entry_udp_pre_connect"` - SyscallProbeEntryWrite *ebpf.Program `ebpf:"syscall__probe_entry_write"` - SyscallProbeEntryWritev *ebpf.Program `ebpf:"syscall__probe_entry_writev"` - SyscallProbeRetAccept *ebpf.Program `ebpf:"syscall__probe_ret_accept"` - SyscallProbeRetAccept4 *ebpf.Program `ebpf:"syscall__probe_ret_accept4"` - SyscallProbeRetClose *ebpf.Program `ebpf:"syscall__probe_ret_close"` - SyscallProbeRetConnect *ebpf.Program `ebpf:"syscall__probe_ret_connect"` - SyscallProbeRetRead *ebpf.Program `ebpf:"syscall__probe_ret_read"` - SyscallProbeRetReadv *ebpf.Program `ebpf:"syscall__probe_ret_readv"` - SyscallProbeRetRecvfrom *ebpf.Program `ebpf:"syscall__probe_ret_recvfrom"` - SyscallProbeRetSendto *ebpf.Program `ebpf:"syscall__probe_ret_sendto"` - SyscallProbeRetTcpV4Connect *ebpf.Program `ebpf:"syscall__probe_ret_tcp_v4_connect"` - SyscallProbeRetTcpV6Connect *ebpf.Program `ebpf:"syscall__probe_ret_tcp_v6_connect"` - SyscallProbeRetWrite *ebpf.Program `ebpf:"syscall__probe_ret_write"` - SyscallProbeRetWritev *ebpf.Program `ebpf:"syscall__probe_ret_writev"` - SyscallProbeEntryConnect *ebpf.Program `ebpf:"syscall_probe_entry_connect"` - SyscallProbeEntrySocket *ebpf.Program `ebpf:"syscall_probe_entry_socket"` -} - -func (p *bpfPrograms) Close() error { - return _BpfClose( - p.K_connect4, - p.K_connect6, - p.K_getpeername4, - p.K_getpeername6, - p.SyscallProbeEntryAccept, - p.SyscallProbeEntryAccept4, - p.SyscallProbeEntryClose, - p.SyscallProbeEntryRead, - p.SyscallProbeEntryReadv, - p.SyscallProbeEntryRecvfrom, - p.SyscallProbeEntrySendto, - p.SyscallProbeEntryTcpV4Connect, - p.SyscallProbeEntryTcpV4PreConnect, - p.SyscallProbeEntryTcpV6Connect, - p.SyscallProbeEntryTcpV6PreConnect, - p.SyscallProbeEntryUdpPreConnect, - p.SyscallProbeEntryWrite, - p.SyscallProbeEntryWritev, - p.SyscallProbeRetAccept, - p.SyscallProbeRetAccept4, - p.SyscallProbeRetClose, - p.SyscallProbeRetConnect, - p.SyscallProbeRetRead, - p.SyscallProbeRetReadv, - p.SyscallProbeRetRecvfrom, - p.SyscallProbeRetSendto, - p.SyscallProbeRetTcpV4Connect, - p.SyscallProbeRetTcpV6Connect, - p.SyscallProbeRetWrite, - p.SyscallProbeRetWritev, - p.SyscallProbeEntryConnect, - p.SyscallProbeEntrySocket, - ) -} - -func _BpfClose(closers ...io.Closer) error { - for _, closer := range closers { - if err := closer.Close(); err != nil { - return err - } - } - return nil -} - -// Do not access this directly. -// -//go:embed bpf_arm64_bpfel.o -var _BpfBytes []byte diff --git a/keploy/pkg/core/hooks/bpf_arm64_bpfel.o b/keploy/pkg/core/hooks/bpf_arm64_bpfel.o deleted file mode 100644 index 9f31745df889223e01702d9fdd27821d58253d5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 381808 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 I&BOZt4cn>TzW@LL diff --git a/keploy/pkg/core/hooks/bpf_x86_bpfel.go b/keploy/pkg/core/hooks/bpf_x86_bpfel.go deleted file mode 100644 index 2137686..0000000 --- a/keploy/pkg/core/hooks/bpf_x86_bpfel.go +++ /dev/null @@ -1,289 +0,0 @@ -// Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 - -package hooks - -import ( - "bytes" - _ "embed" - "fmt" - "io" - - "github.com/cilium/ebpf" -) - -// loadBpf returns the embedded CollectionSpec for bpf. -func loadBpf() (*ebpf.CollectionSpec, error) { - reader := bytes.NewReader(_BpfBytes) - spec, err := ebpf.LoadCollectionSpecFromReader(reader) - if err != nil { - return nil, fmt.Errorf("can't load bpf: %w", err) - } - - return spec, err -} - -// loadBpfObjects loads bpf and converts it into a struct. -// -// The following types are suitable as obj argument: -// -// *bpfObjects -// *bpfPrograms -// *bpfMaps -// -// See ebpf.CollectionSpec.LoadAndAssign documentation for details. -func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { - spec, err := loadBpf() - if err != nil { - return err - } - - return spec.LoadAndAssign(obj, opts) -} - -// bpfSpecs contains maps and programs before they are loaded into the kernel. -// -// It can be passed ebpf.CollectionSpec.Assign. -type bpfSpecs struct { - bpfProgramSpecs - bpfMapSpecs - bpfVariableSpecs -} - -// bpfProgramSpecs contains programs before they are loaded into the kernel. -// -// It can be passed ebpf.CollectionSpec.Assign. -type bpfProgramSpecs struct { - K_connect4 *ebpf.ProgramSpec `ebpf:"k_connect4"` - K_connect6 *ebpf.ProgramSpec `ebpf:"k_connect6"` - K_getpeername4 *ebpf.ProgramSpec `ebpf:"k_getpeername4"` - K_getpeername6 *ebpf.ProgramSpec `ebpf:"k_getpeername6"` - SyscallProbeEntryAccept *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_accept"` - SyscallProbeEntryAccept4 *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_accept4"` - SyscallProbeEntryClose *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_close"` - SyscallProbeEntryRead *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_read"` - SyscallProbeEntryReadv *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_readv"` - SyscallProbeEntryRecvfrom *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_recvfrom"` - SyscallProbeEntrySendto *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_sendto"` - SyscallProbeEntryTcpV4Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v4_connect"` - SyscallProbeEntryTcpV4PreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v4_pre_connect"` - SyscallProbeEntryTcpV6Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v6_connect"` - SyscallProbeEntryTcpV6PreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_tcp_v6_pre_connect"` - SyscallProbeEntryUdpPreConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_udp_pre_connect"` - SyscallProbeEntryWrite *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_write"` - SyscallProbeEntryWritev *ebpf.ProgramSpec `ebpf:"syscall__probe_entry_writev"` - SyscallProbeRetAccept *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_accept"` - SyscallProbeRetAccept4 *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_accept4"` - SyscallProbeRetClose *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_close"` - SyscallProbeRetConnect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_connect"` - SyscallProbeRetRead *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_read"` - SyscallProbeRetReadv *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_readv"` - SyscallProbeRetRecvfrom *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_recvfrom"` - SyscallProbeRetSendto *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_sendto"` - SyscallProbeRetTcpV4Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_tcp_v4_connect"` - SyscallProbeRetTcpV6Connect *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_tcp_v6_connect"` - SyscallProbeRetWrite *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_write"` - SyscallProbeRetWritev *ebpf.ProgramSpec `ebpf:"syscall__probe_ret_writev"` - SyscallProbeEntryConnect *ebpf.ProgramSpec `ebpf:"syscall_probe_entry_connect"` - SyscallProbeEntrySocket *ebpf.ProgramSpec `ebpf:"syscall_probe_entry_socket"` -} - -// bpfMapSpecs contains maps before they are loaded into the kernel. -// -// It can be passed ebpf.CollectionSpec.Assign. -type bpfMapSpecs struct { - ActiveAcceptArgsMap *ebpf.MapSpec `ebpf:"active_accept_args_map"` - ActiveCloseArgsMap *ebpf.MapSpec `ebpf:"active_close_args_map"` - ActiveReadArgsMap *ebpf.MapSpec `ebpf:"active_read_args_map"` - ActiveWriteArgsMap *ebpf.MapSpec `ebpf:"active_write_args_map"` - AppChildKernelPidMap *ebpf.MapSpec `ebpf:"app_child_kernel_pid_map"` - ConnInfoMap *ebpf.MapSpec `ebpf:"conn_info_map"` - CurrentSockMap *ebpf.MapSpec `ebpf:"current_sock_map"` - DestInfoMap *ebpf.MapSpec `ebpf:"dest_info_map"` - DockerAppRegistrationMap *ebpf.MapSpec `ebpf:"docker_app_registration_map"` - E2eInfoMap *ebpf.MapSpec `ebpf:"e2e_info_map"` - KeployAgentKernelPidMap *ebpf.MapSpec `ebpf:"keploy_agent_kernel_pid_map"` - KeployAgentRegistrationMap *ebpf.MapSpec `ebpf:"keploy_agent_registration_map"` - KeployClientKernelPidMap *ebpf.MapSpec `ebpf:"keploy_client_kernel_pid_map"` - KeployClientRegistrationMap *ebpf.MapSpec `ebpf:"keploy_client_registration_map"` - OutgoingConnCheckMap *ebpf.MapSpec `ebpf:"outgoing_conn_check_map"` - OutgoingConnectArgsMap *ebpf.MapSpec `ebpf:"outgoing_connect_args_map"` - RedirectProxyMap *ebpf.MapSpec `ebpf:"redirect_proxy_map"` - SocketCloseEvents *ebpf.MapSpec `ebpf:"socket_close_events"` - SocketDataEventBufferHeap *ebpf.MapSpec `ebpf:"socket_data_event_buffer_heap"` - SocketDataEvents *ebpf.MapSpec `ebpf:"socket_data_events"` - SocketOpenEvents *ebpf.MapSpec `ebpf:"socket_open_events"` - TaskStructMap *ebpf.MapSpec `ebpf:"task_struct_map"` -} - -// bpfVariableSpecs contains global variables before they are loaded into the kernel. -// -// It can be passed ebpf.CollectionSpec.Assign. -type bpfVariableSpecs struct { -} - -// bpfObjects contains all objects after they have been loaded into the kernel. -// -// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. -type bpfObjects struct { - bpfPrograms - bpfMaps - bpfVariables -} - -func (o *bpfObjects) Close() error { - return _BpfClose( - &o.bpfPrograms, - &o.bpfMaps, - ) -} - -// bpfMaps contains all maps after they have been loaded into the kernel. -// -// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. -type bpfMaps struct { - ActiveAcceptArgsMap *ebpf.Map `ebpf:"active_accept_args_map"` - ActiveCloseArgsMap *ebpf.Map `ebpf:"active_close_args_map"` - ActiveReadArgsMap *ebpf.Map `ebpf:"active_read_args_map"` - ActiveWriteArgsMap *ebpf.Map `ebpf:"active_write_args_map"` - AppChildKernelPidMap *ebpf.Map `ebpf:"app_child_kernel_pid_map"` - ConnInfoMap *ebpf.Map `ebpf:"conn_info_map"` - CurrentSockMap *ebpf.Map `ebpf:"current_sock_map"` - DestInfoMap *ebpf.Map `ebpf:"dest_info_map"` - DockerAppRegistrationMap *ebpf.Map `ebpf:"docker_app_registration_map"` - E2eInfoMap *ebpf.Map `ebpf:"e2e_info_map"` - KeployAgentKernelPidMap *ebpf.Map `ebpf:"keploy_agent_kernel_pid_map"` - KeployAgentRegistrationMap *ebpf.Map `ebpf:"keploy_agent_registration_map"` - KeployClientKernelPidMap *ebpf.Map `ebpf:"keploy_client_kernel_pid_map"` - KeployClientRegistrationMap *ebpf.Map `ebpf:"keploy_client_registration_map"` - OutgoingConnCheckMap *ebpf.Map `ebpf:"outgoing_conn_check_map"` - OutgoingConnectArgsMap *ebpf.Map `ebpf:"outgoing_connect_args_map"` - RedirectProxyMap *ebpf.Map `ebpf:"redirect_proxy_map"` - SocketCloseEvents *ebpf.Map `ebpf:"socket_close_events"` - SocketDataEventBufferHeap *ebpf.Map `ebpf:"socket_data_event_buffer_heap"` - SocketDataEvents *ebpf.Map `ebpf:"socket_data_events"` - SocketOpenEvents *ebpf.Map `ebpf:"socket_open_events"` - TaskStructMap *ebpf.Map `ebpf:"task_struct_map"` -} - -func (m *bpfMaps) Close() error { - return _BpfClose( - m.ActiveAcceptArgsMap, - m.ActiveCloseArgsMap, - m.ActiveReadArgsMap, - m.ActiveWriteArgsMap, - m.AppChildKernelPidMap, - m.ConnInfoMap, - m.CurrentSockMap, - m.DestInfoMap, - m.DockerAppRegistrationMap, - m.E2eInfoMap, - m.KeployAgentKernelPidMap, - m.KeployAgentRegistrationMap, - m.KeployClientKernelPidMap, - m.KeployClientRegistrationMap, - m.OutgoingConnCheckMap, - m.OutgoingConnectArgsMap, - m.RedirectProxyMap, - m.SocketCloseEvents, - m.SocketDataEventBufferHeap, - m.SocketDataEvents, - m.SocketOpenEvents, - m.TaskStructMap, - ) -} - -// bpfVariables contains all global variables after they have been loaded into the kernel. -// -// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. -type bpfVariables struct { -} - -// bpfPrograms contains all programs after they have been loaded into the kernel. -// -// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. -type bpfPrograms struct { - K_connect4 *ebpf.Program `ebpf:"k_connect4"` - K_connect6 *ebpf.Program `ebpf:"k_connect6"` - K_getpeername4 *ebpf.Program `ebpf:"k_getpeername4"` - K_getpeername6 *ebpf.Program `ebpf:"k_getpeername6"` - SyscallProbeEntryAccept *ebpf.Program `ebpf:"syscall__probe_entry_accept"` - SyscallProbeEntryAccept4 *ebpf.Program `ebpf:"syscall__probe_entry_accept4"` - SyscallProbeEntryClose *ebpf.Program `ebpf:"syscall__probe_entry_close"` - SyscallProbeEntryRead *ebpf.Program `ebpf:"syscall__probe_entry_read"` - SyscallProbeEntryReadv *ebpf.Program `ebpf:"syscall__probe_entry_readv"` - SyscallProbeEntryRecvfrom *ebpf.Program `ebpf:"syscall__probe_entry_recvfrom"` - SyscallProbeEntrySendto *ebpf.Program `ebpf:"syscall__probe_entry_sendto"` - SyscallProbeEntryTcpV4Connect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v4_connect"` - SyscallProbeEntryTcpV4PreConnect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v4_pre_connect"` - SyscallProbeEntryTcpV6Connect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v6_connect"` - SyscallProbeEntryTcpV6PreConnect *ebpf.Program `ebpf:"syscall__probe_entry_tcp_v6_pre_connect"` - SyscallProbeEntryUdpPreConnect *ebpf.Program `ebpf:"syscall__probe_entry_udp_pre_connect"` - SyscallProbeEntryWrite *ebpf.Program `ebpf:"syscall__probe_entry_write"` - SyscallProbeEntryWritev *ebpf.Program `ebpf:"syscall__probe_entry_writev"` - SyscallProbeRetAccept *ebpf.Program `ebpf:"syscall__probe_ret_accept"` - SyscallProbeRetAccept4 *ebpf.Program `ebpf:"syscall__probe_ret_accept4"` - SyscallProbeRetClose *ebpf.Program `ebpf:"syscall__probe_ret_close"` - SyscallProbeRetConnect *ebpf.Program `ebpf:"syscall__probe_ret_connect"` - SyscallProbeRetRead *ebpf.Program `ebpf:"syscall__probe_ret_read"` - SyscallProbeRetReadv *ebpf.Program `ebpf:"syscall__probe_ret_readv"` - SyscallProbeRetRecvfrom *ebpf.Program `ebpf:"syscall__probe_ret_recvfrom"` - SyscallProbeRetSendto *ebpf.Program `ebpf:"syscall__probe_ret_sendto"` - SyscallProbeRetTcpV4Connect *ebpf.Program `ebpf:"syscall__probe_ret_tcp_v4_connect"` - SyscallProbeRetTcpV6Connect *ebpf.Program `ebpf:"syscall__probe_ret_tcp_v6_connect"` - SyscallProbeRetWrite *ebpf.Program `ebpf:"syscall__probe_ret_write"` - SyscallProbeRetWritev *ebpf.Program `ebpf:"syscall__probe_ret_writev"` - SyscallProbeEntryConnect *ebpf.Program `ebpf:"syscall_probe_entry_connect"` - SyscallProbeEntrySocket *ebpf.Program `ebpf:"syscall_probe_entry_socket"` -} - -func (p *bpfPrograms) Close() error { - return _BpfClose( - p.K_connect4, - p.K_connect6, - p.K_getpeername4, - p.K_getpeername6, - p.SyscallProbeEntryAccept, - p.SyscallProbeEntryAccept4, - p.SyscallProbeEntryClose, - p.SyscallProbeEntryRead, - p.SyscallProbeEntryReadv, - p.SyscallProbeEntryRecvfrom, - p.SyscallProbeEntrySendto, - p.SyscallProbeEntryTcpV4Connect, - p.SyscallProbeEntryTcpV4PreConnect, - p.SyscallProbeEntryTcpV6Connect, - p.SyscallProbeEntryTcpV6PreConnect, - p.SyscallProbeEntryUdpPreConnect, - p.SyscallProbeEntryWrite, - p.SyscallProbeEntryWritev, - p.SyscallProbeRetAccept, - p.SyscallProbeRetAccept4, - p.SyscallProbeRetClose, - p.SyscallProbeRetConnect, - p.SyscallProbeRetRead, - p.SyscallProbeRetReadv, - p.SyscallProbeRetRecvfrom, - p.SyscallProbeRetSendto, - p.SyscallProbeRetTcpV4Connect, - p.SyscallProbeRetTcpV6Connect, - p.SyscallProbeRetWrite, - p.SyscallProbeRetWritev, - p.SyscallProbeEntryConnect, - p.SyscallProbeEntrySocket, - ) -} - -func _BpfClose(closers ...io.Closer) error { - for _, closer := range closers { - if err := closer.Close(); err != nil { - return err - } - } - return nil -} - -// Do not access this directly. -// -//go:embed bpf_x86_bpfel.o -var _BpfBytes []byte diff --git a/keploy/pkg/core/hooks/bpf_x86_bpfel.o b/keploy/pkg/core/hooks/bpf_x86_bpfel.o deleted file mode 100644 index 24075fcc53324340026b5950e74fc33b49aaf4a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 381144 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 x^M73yRgw5tKUz 0 { - factory.logger.Debug("Deleting inactive trackers", - zap.Int("count", len(trackersToDelete))) - } - for _, key := range trackersToDelete { - delete(factory.connections, key) - } -} - -// GetOrCreate returns a tracker that related to the given conn and transaction ids. If there is no such tracker -// we create a new one. -func (factory *Factory) GetOrCreate(connectionID ID) *Tracker { - factory.mutex.Lock() - defer factory.mutex.Unlock() - tracker, ok := factory.connections[connectionID] - if !ok { - tracker = NewTracker(connectionID, factory.logger) - factory.connections[connectionID] = tracker - } - return tracker -} diff --git a/keploy/pkg/core/hooks/conn/socket.go b/keploy/pkg/core/hooks/conn/socket.go deleted file mode 100644 index 062552a..0000000 --- a/keploy/pkg/core/hooks/conn/socket.go +++ /dev/null @@ -1,264 +0,0 @@ -//go:build linux - -package conn - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - "os" - "time" - "unsafe" - - "golang.org/x/sync/errgroup" - "golang.org/x/sys/unix" - - "github.com/cilium/ebpf" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - - "github.com/cilium/ebpf/perf" - "github.com/cilium/ebpf/ringbuf" - "go.uber.org/zap" -) - -var eventAttributesSize = int(unsafe.Sizeof(SocketDataEvent{})) - -// ListenSocket starts the socket event listeners -func ListenSocket(ctx context.Context, l *zap.Logger, openMap, dataMap, closeMap *ebpf.Map, opts models.IncomingOptions) (<-chan *models.TestCase, error) { - t := make(chan *models.TestCase, 500) - err := initRealTimeOffset() - if err != nil { - utils.LogError(l, err, "failed to initialize real time offset") - return nil, errors.New("failed to start socket listeners") - } - c := NewFactory(5*time.Minute, l, opts) - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return nil, errors.New("failed to get the error group from the context") - } - g.Go(func() error { - defer utils.Recover(l) - go func() { - defer utils.Recover(l) - for { - select { - case <-ctx.Done(): - return - default: - // TODO refactor this to directly consume the events from the maps - c.ProcessActiveTrackers(ctx, t, opts) - time.Sleep(100 * time.Millisecond) - } - } - }() - <-ctx.Done() - close(t) - return nil - }) - - err = open(ctx, c, l, openMap) - if err != nil { - utils.LogError(l, err, "failed to start open socket listener") - return nil, errors.New("failed to start socket listeners") - } - err = data(ctx, c, l, dataMap) - if err != nil { - utils.LogError(l, err, "failed to start data socket listener") - return nil, errors.New("failed to start socket listeners") - } - err = exit(ctx, c, l, closeMap) - if err != nil { - utils.LogError(l, err, "failed to start close socket listener") - return nil, errors.New("failed to start socket listeners") - } - return t, err -} - -func open(ctx context.Context, c *Factory, l *zap.Logger, m *ebpf.Map) error { - - r, err := perf.NewReader(m, os.Getpagesize()) - if err != nil { - utils.LogError(l, nil, "failed to create perf event reader of socketOpenEvent") - return err - } - - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context") - } - g.Go(func() error { - defer utils.Recover(l) - go func() { - defer utils.Recover(l) - for { - rec, err := r.Read() - if err != nil { - if errors.Is(err, perf.ErrClosed) { - return - } - utils.LogError(l, err, "failed to read from perf socketOpenEvent reader") - continue - } - - if rec.LostSamples != 0 { - l.Debug("Unable to add samples to the socketOpenEvent array due to its full capacity", zap.Any("samples", rec.LostSamples)) - continue - } - data := rec.RawSample - var event SocketOpenEvent - - if err := binary.Read(bytes.NewReader(data), binary.LittleEndian, &event); err != nil { - utils.LogError(l, err, "failed to decode the received data from perf socketOpenEvent reader") - continue - } - - event.TimestampNano += getRealTimeOffset() - c.GetOrCreate(event.ConnID).AddOpenEvent(event) - } - }() - <-ctx.Done() // Check for context cancellation - err := r.Close() - if err != nil { - utils.LogError(l, err, "failed to close perf socketOpenEvent reader") - } - return nil - }) - return nil -} - -func data(ctx context.Context, c *Factory, l *zap.Logger, m *ebpf.Map) error { - r, err := ringbuf.NewReader(m) - if err != nil { - utils.LogError(l, nil, "failed to create ring buffer of socketDataEvent") - return err - } - - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context") - } - g.Go(func() error { - defer utils.Recover(l) - go func() { - defer utils.Recover(l) - for { - record, err := r.Read() - if err != nil { - if !errors.Is(err, ringbuf.ErrClosed) { - utils.LogError(l, err, "failed to receive signal from ringbuf socketDataEvent reader") - return - } - continue - } - - bin := record.RawSample - if len(bin) < eventAttributesSize { - l.Debug(fmt.Sprintf("Buffer's for SocketDataEvent is smaller (%d) than the minimum required (%d)", len(bin), eventAttributesSize)) - continue - } else if len(bin) > EventBodyMaxSize+eventAttributesSize { - l.Debug(fmt.Sprintf("Buffer's for SocketDataEvent is bigger (%d) than the maximum for the struct (%d)", len(bin), EventBodyMaxSize+eventAttributesSize)) - continue - } - - var event SocketDataEvent - - if err := binary.Read(bytes.NewReader(bin), binary.LittleEndian, &event); err != nil { - utils.LogError(l, err, "failed to decode the received data from ringbuf socketDataEvent reader") - continue - } - - event.TimestampNano += getRealTimeOffset() - - if event.Direction == IngressTraffic { - event.EntryTimestampNano += getRealTimeOffset() - l.Debug(fmt.Sprintf("Request EntryTimestamp :%v\n", convertUnixNanoToTime(event.EntryTimestampNano))) - } - - c.GetOrCreate(event.ConnID).AddDataEvent(event) - } - }() - <-ctx.Done() // Check for context cancellation - err := r.Close() - if err != nil { - utils.LogError(l, err, "failed to close ringbuf socketDataEvent reader") - } - return nil - }) - return nil -} - -func exit(ctx context.Context, c *Factory, l *zap.Logger, m *ebpf.Map) error { - - r, err := perf.NewReader(m, os.Getpagesize()) - if err != nil { - utils.LogError(l, nil, "failed to create perf event reader of socketCloseEvent") - return err - } - - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context") - } - g.Go(func() error { - defer utils.Recover(l) - go func() { - defer utils.Recover(l) - for { - rec, err := r.Read() - if err != nil { - if errors.Is(err, perf.ErrClosed) { - return - } - utils.LogError(l, err, "failed to read from perf socketCloseEvent reader") - continue - } - if rec.LostSamples != 0 { - l.Debug(fmt.Sprintf("perf socketCloseEvent array full, dropped %d samples", rec.LostSamples)) - continue - } - data := rec.RawSample - - var event SocketCloseEvent - if err := binary.Read(bytes.NewReader(data), binary.LittleEndian, &event); err != nil { - l.Debug(fmt.Sprintf("Failed to decode received data: %+v", err)) - continue - } - - event.TimestampNano += getRealTimeOffset() - c.GetOrCreate(event.ConnID).AddCloseEvent(event) - } - }() - - <-ctx.Done() // Check for context cancellation - err := r.Close() - if err != nil { - utils.LogError(l, err, "failed to close perf socketCloseEvent reader") - return err - } - return nil - }) - return nil -} - -// InitRealTimeOffset calculates the offset between the real clock and the monotonic clock used in the BPF. -func initRealTimeOffset() error { - var monotonicTime, realTime unix.Timespec - if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &monotonicTime); err != nil { - return fmt.Errorf("failed getting monotonic clock due to: %v", err) - } - if err := unix.ClockGettime(unix.CLOCK_REALTIME, &realTime); err != nil { - return fmt.Errorf("failed getting real clock time due to: %v", err) - } - realTimeOffset = uint64(time.Second)*(uint64(realTime.Sec)-uint64(monotonicTime.Sec)) + uint64(realTime.Nsec) - uint64(monotonicTime.Nsec) - // realTimeCopy := time.Unix(int64(realTimeOffset/1e9), int64(realTimeOffset%1e9)) - // log.Debug(fmt.Sprintf("%s real time offset is: %v", Emoji, realTimeCopy)) - return nil -} - -// GetRealTimeOffset is a getter for the real-time-offset. -func getRealTimeOffset() uint64 { - return realTimeOffset -} diff --git a/keploy/pkg/core/hooks/conn/tracker.go b/keploy/pkg/core/hooks/conn/tracker.go deleted file mode 100755 index b8b0913..0000000 --- a/keploy/pkg/core/hooks/conn/tracker.go +++ /dev/null @@ -1,571 +0,0 @@ -//go:build linux - -package conn - -import ( - "bytes" - "fmt" - "strings" - "sync" - "sync/atomic" - "time" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "golang.org/x/net/http2" - // "log" -) - -// Protocol represents the type of protocol being used -type Protocol int - -// Protocol constants for supported HTTP protocol versions -const ( - // HTTP1 represents HTTP/1.x protocol - HTTP1 Protocol = 1 - // HTTP2 represents HTTP/2 protocol - HTTP2 Protocol = 2 -) - -// StreamManager interface for managing protocol streams -type StreamManager interface { - HandleFrame(frame http2.Frame, isOutgoing bool, timestamp time.Time) error - GetCompleteStreams() []*pkg.HTTP2Stream - CleanupStream(streamID uint32) -} - -// Tracker is a routine-safe container that holds a conn with unique ID, and able to create new conn. -type Tracker struct { - connID ID - addr SockAddrIn - openTimestamp uint64 - closeTimestamp uint64 - - // Indicates the tracker stopped tracking due to closing the session. - lastActivityTimestamp uint64 - - // Queues to handle multiple ingress traffic on the same conn (keep-alive) - - // kernelRespSizes is a slice of the total number of Response bytes received in the kernel side - kernelRespSizes []uint64 - - // kernelReqSizes is a slice of the total number of Request bytes received in the kernel side - kernelReqSizes []uint64 - - // userRespSizes is a slice of the total number of Response bytes received in the user side - userRespSizes []uint64 - - // userReqSizes is a slice of the total number of Request bytes received in the user side - userReqSizes []uint64 - // userRespBufs is a slice of the Response data received in the user side on this conn - userResps [][]byte - // userReqBufs is a slice of the Request data received in the user side on this conn - userReqs [][]byte - - // req and resp are the buffers to store the request and response data for the current request - // reset after 2 seconds of inactivity - respSize uint64 - reqSize uint64 - resp []byte - req []byte - - // Additional fields to know when to capture request or response info - // reset after 2 seconds of inactivity - - lastChunkWasResp bool - lastChunkWasReq bool - recTestCounter int32 //atomic counter - // firstRequest is used to indicate if the current request is the first request on the conn - // reset after 2 seconds of inactivity - // Note: This is used to handle multiple requests on the same conn (keep-alive) - // Its different from isNewRequest which is used to indicate if the current request chunk is the first chunk of the request - firstRequest bool - - mutex sync.RWMutex - logger *zap.Logger - - reqTimestamps []time.Time - isNewRequest bool - - // New fields to support protocol detection and http2 stream management - protocol Protocol - streamMgr StreamManager - protocolDetected bool - buffer []byte -} - -// NewTracker creates a new connection tracker -func NewTracker(connID ID, logger *zap.Logger) *Tracker { - t := &Tracker{ - connID: connID, - logger: logger, - mutex: sync.RWMutex{}, - // Initialize HTTP/1 fields - req: []byte{}, - resp: []byte{}, - kernelRespSizes: []uint64{}, - kernelReqSizes: []uint64{}, - userRespSizes: []uint64{}, - userReqSizes: []uint64{}, - userResps: [][]byte{}, - userReqs: [][]byte{}, - firstRequest: true, - isNewRequest: true, - buffer: make([]byte, 0, pkg.DefaultMaxFrameSize), - } - - // Always start with HTTP/1 - t.protocol = HTTP1 - t.protocolDetected = false // Allow protocol detection - - return t -} - -func (conn *Tracker) ToBytes() ([]byte, []byte) { - conn.mutex.RLock() - defer conn.mutex.RUnlock() - return conn.req, conn.resp -} - -func (conn *Tracker) IsInactive(duration time.Duration) bool { - conn.mutex.RLock() - defer conn.mutex.RUnlock() - return uint64(time.Now().UnixNano())-conn.lastActivityTimestamp > uint64(duration.Nanoseconds()) -} - -func (conn *Tracker) incRecordTestCount() { - atomic.AddInt32(&conn.recTestCounter, 1) -} - -func (conn *Tracker) decRecordTestCount() { - atomic.AddInt32(&conn.recTestCounter, -1) -} - -// reset resets the conn's request and response data buffers. -func (conn *Tracker) reset() { - conn.firstRequest = true - conn.lastChunkWasResp = false - conn.lastChunkWasReq = false - conn.reqSize = 0 - conn.respSize = 0 - conn.resp = []byte{} - conn.req = []byte{} -} - -func (conn *Tracker) verifyRequestData(expectedRecvBytes, actualRecvBytes uint64) bool { - return expectedRecvBytes == actualRecvBytes -} - -func (conn *Tracker) verifyResponseData(expectedSentBytes, actualSentBytes uint64) bool { - return expectedSentBytes == actualSentBytes -} - -// func (conn *Tracker) Malformed() bool { -// conn.mutex.RLock() -// defer conn.mutex.RUnlock() -// // conn.log.Debug("data loss of ingress request message", zap.Any("bytes read in ebpf", conn.totalReadBytes), zap.Any("bytes received in userspace", conn.reqSize)) -// // conn.log.Debug("data loss of ingress response message", zap.Any("bytes written in ebpf", conn.totalWrittenBytes), zap.Any("bytes sent to user", conn.respSize)) -// // conn.log.Debug("", zap.Any("Request buffer", string(conn.req))) -// // conn.log.Debug("", zap.Any("Response buffer", string(conn.resp))) -// return conn.closeTimestamp != 0 && -// conn.totalReadBytes != conn.reqSize && -// conn.totalWrittenBytes != conn.respSize -// } - -func (conn *Tracker) AddOpenEvent(event SocketOpenEvent) { - conn.mutex.Lock() - defer conn.mutex.Unlock() - conn.UpdateTimestamps() - conn.addr = event.Addr - if conn.openTimestamp != 0 && conn.openTimestamp != event.TimestampNano { - conn.logger.Debug("Changed open info timestamp due to new request", zap.Any("from", conn.openTimestamp), zap.Any("to", event.TimestampNano)) - } - conn.openTimestamp = event.TimestampNano -} - -func (conn *Tracker) AddDataEvent(event SocketDataEvent) { - conn.mutex.Lock() - defer conn.mutex.Unlock() - conn.UpdateTimestamps() - - data := event.Msg[:event.MsgSize] - - // Check for HTTP/2 preface if we haven't detected protocol yet - if !conn.protocolDetected { - conn.logger.Debug("Connection check") - if isHTTP2Request(data) { - // Create HTTP/2 parser and stream manager - conn.protocol = HTTP2 - conn.streamMgr = pkg.NewStreamManager(conn.logger) - conn.protocolDetected = true - conn.logger.Debug("Detected HTTP/2 protocol (preface received)") - - // If there's more data after preface, process it as HTTP/2 - if len(data) > 24 { - // Create new event with remaining data - newEvent := event - copy(newEvent.Msg[:], data[24:]) - newEvent.MsgSize = uint32(len(data) - 24) - conn.handleHTTP2Data(newEvent) - } - return - } - - // If we see a valid HTTP/1 request line, mark as HTTP/1 - if isHTTP1Request(data) { - conn.protocolDetected = true - conn.logger.Debug("Detected HTTP/1.x protocol") - } - } - - // Process based on current protocol - switch conn.protocol { - case HTTP2: - conn.handleHTTP2Data(event) - default: - conn.handleHTTP1Data(event) - } -} - -// isHTTP1Request checks if the data starts with a valid HTTP/1 request line -func isHTTP1Request(data []byte) bool { - // Convert to string for easier checking - s := string(data) - - // Check for common HTTP methods - return strings.HasPrefix(s, "GET ") || - strings.HasPrefix(s, "POST ") || - strings.HasPrefix(s, "PUT ") || - strings.HasPrefix(s, "DELETE ") || - strings.HasPrefix(s, "HEAD ") || - strings.HasPrefix(s, "OPTIONS ") || - strings.HasPrefix(s, "PATCH ") -} - -// isHTTP2Request checks if the data starts with the HTTP/2 connection preface -func isHTTP2Request(data []byte) bool { - return len(data) >= 24 && bytes.Equal(data[:24], []byte(pkg.HTTP2Preface)) -} - -func (conn *Tracker) AddCloseEvent(event SocketCloseEvent) { - conn.mutex.Lock() - defer conn.mutex.Unlock() - conn.UpdateTimestamps() - if conn.closeTimestamp != 0 && conn.closeTimestamp != event.TimestampNano { - conn.logger.Debug("Changed close info timestamp due to new request", zap.Any("from", conn.closeTimestamp), zap.Any("to", event.TimestampNano)) - } - conn.closeTimestamp = event.TimestampNano - conn.logger.Debug(fmt.Sprintf("Got a close event from eBPF on connectionId:%v\n", event.ConnID)) -} - -func (conn *Tracker) UpdateTimestamps() { - conn.lastActivityTimestamp = uint64(time.Now().UnixNano()) -} - -// ConvertUnixNanoToTime takes a Unix timestamp in nanoseconds as a uint64 and returns the corresponding time.Time -func ConvertUnixNanoToTime(unixNano uint64) time.Time { - // Unix time is the number of seconds since January 1, 1970 UTC, - // so convert nanoseconds to seconds for time.Unix function - seconds := int64(unixNano / uint64(time.Second)) - nanoRemainder := int64(unixNano % uint64(time.Second)) - return time.Unix(seconds, nanoRemainder) -} - -// getHTTP2CompletedStream returns a completed HTTP2/gRPC stream if available -func (conn *Tracker) getHTTP2CompletedStream() *pkg.HTTP2Stream { - conn.mutex.RLock() - defer conn.mutex.RUnlock() - - if conn.streamMgr == nil { - return nil - } - - streams := conn.streamMgr.GetCompleteStreams() - if len(streams) == 0 { - return nil - } - - // Return the first completed stream - stream := streams[0] - - // Cleanup the processed stream - conn.streamMgr.CleanupStream(stream.ID) - - return stream -} - -// Existing HTTP/1 completion check -func (conn *Tracker) isHTTP1Complete() (bool, []byte, []byte, time.Time, time.Time) { - conn.mutex.RLock() - defer conn.mutex.RUnlock() - - // Get the current timestamp in nanoseconds. - currentTimestamp := uint64(time.Now().UnixNano()) - - // Calculate the time elapsed since the last activity in nanoseconds. - elapsedTime := currentTimestamp - conn.lastActivityTimestamp - - //Caveat: Added a timeout of 4 seconds, after this duration we assume that the last response data event would have come. - // This will ensure that we capture the requests responses where Connection:keep-alive is enabled. - - recordTraffic := false - - requestBuf, responseBuf := []byte{}, []byte{} - - var reqTimestamps, respTimestamp time.Time - - //if recTestCounter > 0, it means that we have num(recTestCounter) of request and response present in the queues to record. - if conn.recTestCounter > 0 { - if (len(conn.userReqSizes) > 0 && len(conn.kernelReqSizes) > 0) && - (len(conn.userRespSizes) > 0 && len(conn.kernelRespSizes) > 0) { - validReq, validRes := false, false - - expectedRecvBytes := conn.userReqSizes[0] - actualRecvBytes := conn.kernelReqSizes[0] - - if expectedRecvBytes == 0 || actualRecvBytes == 0 { - conn.logger.Warn("Malformed request", zap.Any("ExpectedRecvBytes", expectedRecvBytes), zap.Any("ActualRecvBytes", actualRecvBytes)) - } - - //popping out the current request info - conn.userReqSizes = conn.userReqSizes[1:] - conn.kernelReqSizes = conn.kernelReqSizes[1:] - - if conn.verifyRequestData(expectedRecvBytes, actualRecvBytes) { - validReq = true - } else { - conn.logger.Debug("Malformed request", zap.Any("ExpectedRecvBytes", expectedRecvBytes), zap.Any("ActualRecvBytes", actualRecvBytes)) - } - - expectedSentBytes := conn.userRespSizes[0] - actualSentBytes := conn.kernelRespSizes[0] - - //popping out the current response info - conn.userRespSizes = conn.userRespSizes[1:] - conn.kernelRespSizes = conn.kernelRespSizes[1:] - - if conn.verifyResponseData(expectedSentBytes, actualSentBytes) { - validRes = true - respTimestamp = time.Now() - } else { - conn.logger.Debug("Malformed response", zap.Any("ExpectedSentBytes", expectedSentBytes), zap.Any("ActualSentBytes", actualSentBytes)) - } - - if len(conn.userReqs) > 0 && len(conn.userResps) > 0 { //validated request, response - requestBuf = conn.userReqs[0] - responseBuf = conn.userResps[0] - - //popping out the current request & response data - conn.userReqs = conn.userReqs[1:] - conn.userResps = conn.userResps[1:] - } else { - conn.logger.Debug("no data buffer for request or response", zap.Any("Length of RecvBufQueue", len(conn.userReqs)), zap.Any("Length of SentBufQueue", len(conn.userResps))) - } - - recordTraffic = validReq && validRes - } else { - utils.LogError(conn.logger, nil, "malformed request or response") - recordTraffic = false - } - - conn.logger.Debug(fmt.Sprintf("recording traffic after verifying the request and reponse data:%v", recordTraffic)) - - // // decrease the recTestCounter - conn.decRecordTestCount() - conn.logger.Debug("verified recording", zap.Any("recordTraffic", recordTraffic)) - } else if conn.lastChunkWasResp && elapsedTime >= uint64(time.Second*2) { // Check if 2 seconds has passed since the last activity. - conn.logger.Debug("might be last request on the conn") - - if len(conn.userReqSizes) > 0 && len(conn.kernelReqSizes) > 0 { - - expectedRecvBytes := conn.userReqSizes[0] - actualRecvBytes := conn.kernelReqSizes[0] - - //popping out the current request info - conn.userReqSizes = conn.userReqSizes[1:] - conn.kernelReqSizes = conn.kernelReqSizes[1:] - - if expectedRecvBytes == 0 || actualRecvBytes == 0 { - conn.logger.Warn("Malformed request", zap.Any("ExpectedRecvBytes", expectedRecvBytes), zap.Any("ActualRecvBytes", actualRecvBytes)) - } - - if conn.verifyRequestData(expectedRecvBytes, actualRecvBytes) { - recordTraffic = true - } else { - conn.logger.Debug("Malformed request", zap.Any("ExpectedRecvBytes", expectedRecvBytes), zap.Any("ActualRecvBytes", actualRecvBytes)) - recordTraffic = false - } - - if len(conn.userReqs) > 0 { //validated request, invalided response - requestBuf = conn.userReqs[0] - //popping out the current request data - conn.userReqs = conn.userReqs[1:] - - responseBuf = conn.resp - respTimestamp = time.Now() - } else { - conn.logger.Debug("no data buffer for request", zap.Any("Length of RecvBufQueue", len(conn.userReqs))) - recordTraffic = false - } - - } else { - utils.LogError(conn.logger, nil, "malformed request") - recordTraffic = false - } - - conn.logger.Debug(fmt.Sprintf("recording traffic after verifying the request data (but not response data):%v", recordTraffic)) - //treat immediate next request as first request (2 seconds after last activity) - // this can be to avoid potential corruption in the conn - conn.reset() - - conn.logger.Debug("unverified recording", zap.Any("recordTraffic", recordTraffic)) - } - - // Checking if record traffic is recorded and request & response timestamp is captured or not. - if recordTraffic { - if len(conn.reqTimestamps) > 0 { - // Get the timestamp of current request - reqTimestamps = conn.reqTimestamps[0] - // Pop the timestamp of current request - conn.reqTimestamps = conn.reqTimestamps[1:] - } else { - conn.logger.Debug("no request timestamp found") - if len(requestBuf) > 0 { - reqLine := strings.Split(string(requestBuf), "\n") - if models.GetMode() == models.MODE_RECORD && len(reqLine) > 0 && reqLine[0] != "" { - conn.logger.Warn(fmt.Sprintf("failed to capture request timestamp for a request. Please record it again if important:%v", reqLine[0])) - } - } - recordTraffic = false - } - - conn.logger.Debug(fmt.Sprintf("TestRequestTimestamp:%v || TestResponseTimestamp:%v", reqTimestamps, respTimestamp)) - } - - return recordTraffic, requestBuf, responseBuf, reqTimestamps, respTimestamp -} - -// Add HTTP/2 specific handling -func (conn *Tracker) handleHTTP2Data(event SocketDataEvent) { - // Convert fixed-size array to slice - data := event.Msg[:event.MsgSize] - - // Append new data to the buffer - conn.buffer = append(conn.buffer, data...) - - // Process as many complete frames as possible - for len(conn.buffer) >= 9 { // Minimum frame size - frame, consumed, err := pkg.ExtractHTTP2Frame(conn.buffer) - if err != nil { - if strings.Contains(err.Error(), "incomplete frame") { - conn.logger.Debug("Incomplete frame", zap.Any("error", err)) - // Not enough data yet, wait for more - break - } - // Real error, log and remove the problematic data - conn.logger.Error("Failed to extract HTTP/2 frame", zap.Error(err)) - if len(conn.buffer) > 9 { - // Try to recover by removing the first byte and trying again next time - conn.buffer = conn.buffer[1:] - } else { - conn.buffer = nil - } - break - } - - // Handle the frame - if err := conn.streamMgr.HandleFrame(frame, event.Direction == EgressTraffic, ConvertUnixNanoToTime(event.TimestampNano)); err != nil { - conn.logger.Error("Failed to handle HTTP/2 frame", zap.Error(err)) - } - - // Remove processed data from buffer - conn.buffer = conn.buffer[consumed:] - } - - // Store timestamps for requests - if event.Direction == IngressTraffic { - conn.reqTimestamps = append(conn.reqTimestamps, ConvertUnixNanoToTime(event.EntryTimestampNano)) - } -} - -// Existing HTTP/1 handling -func (conn *Tracker) handleHTTP1Data(event SocketDataEvent) { - conn.logger.Debug(fmt.Sprintf("Got a data event from eBPF, Direction:%v || current Event Size:%v || ConnectionID:%v\n", event.Direction, event.MsgSize, event.ConnID)) - - switch event.Direction { - case EgressTraffic: - // Capturing the timestamp of response as the response just started to come. - // This is to ensure that we capture the response timestamp for the first chunk of the response. - if !conn.isNewRequest { - conn.isNewRequest = true - } - - // Assign the size of the message to the variable msgLength - msgLength := event.MsgSize - // If the size of the message exceeds the maximum allowed size, - // set msgLength to the maximum allowed size instead - if event.MsgSize > EventBodyMaxSize { - msgLength = EventBodyMaxSize - } - // Append the message (up to msgLength) to the conn's sent buffer - conn.resp = append(conn.resp, event.Msg[:msgLength]...) - conn.respSize += uint64(event.MsgSize) - - //Handling multiple request on same conn to support conn:keep-alive - if conn.firstRequest || conn.lastChunkWasReq { - conn.userReqSizes = append(conn.userReqSizes, conn.reqSize) - conn.reqSize = 0 - - conn.userReqs = append(conn.userReqs, conn.req) - conn.req = []byte{} - - conn.lastChunkWasReq = false - conn.lastChunkWasResp = true - - conn.kernelReqSizes = append(conn.kernelReqSizes, uint64(event.ValidateReadBytes)) - conn.firstRequest = false - } - - case IngressTraffic: - conn.logger.Debug("isNewRequest", zap.Any("isNewRequest", conn.isNewRequest), zap.Any("connID", conn.connID)) - // Capturing the timestamp of request as the request just started to come. - if conn.isNewRequest { - conn.reqTimestamps = append(conn.reqTimestamps, ConvertUnixNanoToTime(event.EntryTimestampNano)) - conn.isNewRequest = false - } - - // Assign the size of the message to the variable msgLength - msgLength := event.MsgSize - // If the size of the message exceeds the maximum allowed size, - // set msgLength to the maximum allowed size instead - if event.MsgSize > EventBodyMaxSize { - msgLength = EventBodyMaxSize - } - // Append the message (up to msgLength) to the conn's receive buffer - conn.req = append(conn.req, event.Msg[:msgLength]...) - conn.reqSize += uint64(event.MsgSize) - - //Handling multiple request on same conn to support conn:keep-alive - if conn.lastChunkWasResp { - // conn.userRespSizes is the total numner of bytes received in the user side - // consumer for the last response. - conn.userRespSizes = append(conn.userRespSizes, conn.respSize) - conn.respSize = 0 - - conn.userResps = append(conn.userResps, conn.resp) - conn.resp = []byte{} - - conn.lastChunkWasReq = true - conn.lastChunkWasResp = false - - conn.kernelRespSizes = append(conn.kernelRespSizes, uint64(event.ValidateWrittenBytes)) - - //Record a test case for the current request/ - conn.incRecordTestCount() - } - - default: - } -} diff --git a/keploy/pkg/core/hooks/conn/util.go b/keploy/pkg/core/hooks/conn/util.go deleted file mode 100755 index 4c480d4..0000000 --- a/keploy/pkg/core/hooks/conn/util.go +++ /dev/null @@ -1,329 +0,0 @@ -package conn - -import ( - "bytes" - "context" - "encoding/base64" - "fmt" - "io" - "mime/multipart" - "net/http" - "net/url" - "regexp" - "strconv" - "strings" - "time" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -var ( - realTimeOffset uint64 -) - -// convertUnixNanoToTime takes a Unix timestamp in nanoseconds as a uint64 and returns the corresponding time.Time -func convertUnixNanoToTime(unixNano uint64) time.Time { - // Unix time is the number of seconds since January 1, 1970 UTC, - // so convert nanoseconds to seconds for time.Unix function - seconds := int64(unixNano / uint64(time.Second)) - nanoRemainder := int64(unixNano % uint64(time.Second)) - return time.Unix(seconds, nanoRemainder) -} - -func isFiltered(logger *zap.Logger, req *http.Request, opts models.IncomingOptions) bool { - dstPort := 0 - var err error - if p := req.URL.Port(); p != "" { - dstPort, err = strconv.Atoi(p) - if err != nil { - utils.LogError(logger, err, "failed to obtain destination port from request") - return false - } - } - - var passThrough bool - - type cond struct { - eligible bool - match bool - } - - for _, filter := range opts.Filters { - - // 1. bypass rule - bypassEligible := !(filter.BypassRule.Host == "" && - filter.BypassRule.Path == "" && - filter.BypassRule.Port == 0) - - opts := models.OutgoingOptions{Rules: []config.BypassRule{filter.BypassRule}} - byPassMatch := utils.IsPassThrough(logger, req, uint(dstPort), opts) - - // 2. URL-method rule - urlMethodEligible := len(filter.URLMethods) > 0 - urlMethodMatch := false - if urlMethodEligible { - for _, m := range filter.URLMethods { - if m == req.Method { - urlMethodMatch = true - break - } - } - } - - // 3. header rule - headerEligible := len(filter.Headers) > 0 - headerMatch := false - if headerEligible { - for key, vals := range filter.Headers { - rx, err := regexp.Compile(vals) - if err != nil { - utils.LogError(logger, err, "bad header regex") - continue - } - for _, v := range req.Header.Values(key) { - if rx.MatchString(v) { - headerMatch = true - break - } - } - if headerMatch { - break - } - } - } - - conds := []cond{ - {bypassEligible, byPassMatch}, - {urlMethodEligible, urlMethodMatch}, - {headerEligible, headerMatch}, - } - - switch filter.MatchType { - case config.AND: - pass := true - seen := false - for _, c := range conds { - if !c.eligible { - continue - } // ignore ineligible ones - seen = true - if !c.match { - pass = false - break - } - } - if seen && pass { - passThrough = true - return passThrough - } - - case config.OR: - fallthrough - default: - for _, c := range conds { - if c.eligible && c.match { - passThrough = true - return passThrough - } - } - } - } - - return passThrough -} - -//// LogAny appends input of any type to a logs.txt file in the current directory -//func LogAny(value string) error { -// -// logMessage := value -// -// // Add a timestamp to the log message -// timestamp := time.Now().Format("2006-01-02 15:04:05") -// logLine := fmt.Sprintf("%s - %s\n", timestamp, logMessage) -// -// // Open logs.txt in append mode, create it if it doesn't exist -// file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) -// if err != nil { -// return err -// } -// defer file.Close() -// -// // Write the log line to the file -// _, err = file.WriteString(logLine) -// if err != nil { -// return err -// } -// -// return nil -//} - -func Capture(_ context.Context, logger *zap.Logger, t chan *models.TestCase, req *http.Request, resp *http.Response, reqTimeTest time.Time, resTimeTest time.Time, opts models.IncomingOptions) { - - var reqBody []byte - if req.Body != nil { // Read - var err error - reqBody, err = io.ReadAll(req.Body) - if err != nil { - logger.Warn("failed to read the http request body", zap.Any("metadata", utils.GetReqMeta(req)), zap.Int64("of size", int64(len(reqBody))), zap.String("body", base64.StdEncoding.EncodeToString(reqBody)), zap.Error(err)) - } - - if req.Header.Get("Content-Encoding") != "" { - reqBody, err = pkg.Decompress(logger, req.Header.Get("Content-Encoding"), reqBody) - if err != nil { - utils.LogError(logger, err, "failed to decode the http request body", zap.Any("metadata", utils.GetReqMeta(req))) - return - } - } - } - - defer func() { - err := resp.Body.Close() - if err != nil { - utils.LogError(logger, err, "failed to close the http response body") - } - }() - - respBody, err := io.ReadAll(resp.Body) - if err != nil { - utils.LogError(logger, err, "failed to read the http response body") - return - } - - if isFiltered(logger, req, opts) { - logger.Debug("The request is a filtered request") - return - } - var formData []models.FormData - if contentType := req.Header.Get("Content-Type"); strings.HasPrefix(contentType, "multipart/form-data") { - parts := strings.Split(contentType, ";") - if len(parts) > 1 { - req.Header.Set("Content-Type", strings.TrimSpace(parts[0])) - } - formData = extractFormData(logger, reqBody, contentType) - reqBody = []byte{} - } else if contentType := req.Header.Get("Content-Type"); contentType == "application/x-www-form-urlencoded" { - decodedBody, err := url.QueryUnescape(string(reqBody)) - if err != nil { - utils.LogError(logger, err, "failed to decode the url-encoded request body") - return - } - reqBody = []byte(decodedBody) - } - - if resp.Header.Get("Content-Encoding") != "" { - respBody, err = pkg.Decompress(logger, resp.Header.Get("Content-Encoding"), respBody) - if err != nil { - utils.LogError(logger, err, "failed to decompress the response body") - return - } - } - - t <- &models.TestCase{ - Version: models.GetVersion(), - Name: pkg.ToYamlHTTPHeader(req.Header)["Keploy-Test-Name"], - Kind: models.HTTP, - Created: time.Now().Unix(), - HTTPReq: models.HTTPReq{ - Method: models.Method(req.Method), - ProtoMajor: req.ProtoMajor, - ProtoMinor: req.ProtoMinor, - // URL: req.URL.String(), - // URL: fmt.Sprintf("%s://%s%s?%s", req.URL.Scheme, req.Host, req.URL.Path, req.URL.RawQuery), - URL: fmt.Sprintf("http://%s%s", req.Host, req.URL.RequestURI()), - // URL: string(b), - Form: formData, - Header: pkg.ToYamlHTTPHeader(req.Header), - Body: string(reqBody), - URLParams: pkg.URLParams(req), - Timestamp: reqTimeTest, - }, - HTTPResp: models.HTTPResp{ - StatusCode: resp.StatusCode, - Header: pkg.ToYamlHTTPHeader(resp.Header), - Body: string(respBody), - Timestamp: resTimeTest, - StatusMessage: http.StatusText(resp.StatusCode), - }, - Noise: map[string][]string{}, - // Mocks: mocks, - } -} - -func extractFormData(logger *zap.Logger, body []byte, contentType string) []models.FormData { - boundary := "" - if strings.HasPrefix(contentType, "multipart/form-data") { - parts := strings.Split(contentType, "boundary=") - if len(parts) > 1 { - boundary = strings.TrimSpace(parts[1]) - } else { - utils.LogError(logger, nil, "Invalid multipart/form-data content type") - return nil - } - } - reader := multipart.NewReader(bytes.NewReader(body), boundary) - var formData []models.FormData - - for { - part, err := reader.NextPart() - if err == io.EOF { - break - } - if err != nil { - utils.LogError(logger, err, "Error reading part") - continue - } - key := part.FormName() - if key == "" { - continue - } - - value, err := io.ReadAll(part) - if err != nil { - utils.LogError(logger, err, "Error reading part value") - continue - } - - formData = append(formData, models.FormData{ - Key: key, - Values: []string{string(value)}, - }) - } - - return formData -} - -// CaptureGRPC captures a gRPC request/response pair and sends it to the test case channel -func CaptureGRPC(ctx context.Context, logger *zap.Logger, t chan *models.TestCase, http2Stream *pkg.HTTP2Stream) { - if http2Stream == nil { - logger.Error("Stream is nil") - return - } - - if http2Stream.GRPCReq == nil || http2Stream.GRPCResp == nil { - logger.Error("gRPC request or response is nil") - return - } - - // Create test case from stream data - testCase := &models.TestCase{ - Version: models.GetVersion(), - Name: http2Stream.GRPCReq.Headers.OrdinaryHeaders["Keploy-Test-Name"], - Kind: models.GRPC_EXPORT, - Created: time.Now().Unix(), - GrpcReq: *http2Stream.GRPCReq, - GrpcResp: *http2Stream.GRPCResp, - Noise: map[string][]string{}, - } - - select { - case <-ctx.Done(): - return - case t <- testCase: - logger.Debug("Captured gRPC test case", - zap.String("path", http2Stream.GRPCReq.Headers.PseudoHeaders[":path"])) - } -} diff --git a/keploy/pkg/core/hooks/hooks.go b/keploy/pkg/core/hooks/hooks.go deleted file mode 100644 index b29f24c..0000000 --- a/keploy/pkg/core/hooks/hooks.go +++ /dev/null @@ -1,713 +0,0 @@ -//go:build linux - -// Package hooks provides functionality for managing hooks. -package hooks - -import ( - "context" - "errors" - "fmt" - "os" - "strings" - "sync" - - "golang.org/x/sync/errgroup" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/utils" - - "github.com/cilium/ebpf" - "github.com/cilium/ebpf/link" - "github.com/cilium/ebpf/rlimit" - - "go.keploy.io/server/v2/pkg/core" - "go.keploy.io/server/v2/pkg/core/hooks/conn" - "go.keploy.io/server/v2/pkg/core/hooks/structs" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -func NewHooks(logger *zap.Logger, cfg *config.Config) *Hooks { - return &Hooks{ - logger: logger, - sess: core.NewSessions(), - m: sync.Mutex{}, - objectsMutex: sync.RWMutex{}, - proxyIP4: "127.0.0.1", - proxyIP6: [4]uint32{0000, 0000, 0000, 0001}, - proxyPort: cfg.ProxyPort, - dnsPort: cfg.DNSPort, - conf: cfg, - retprobeMaxActive: 1024, - unloadDone: make(chan struct{}), - } -} - -type Hooks struct { - logger *zap.Logger - sess *core.Sessions - proxyIP4 string - proxyIP6 [4]uint32 - proxyPort uint32 - dnsPort uint32 - m sync.Mutex - objectsMutex sync.RWMutex // Protects eBPF objects during load/unload operations - conf *config.Config - // Channel to signal when unload is complete - unloadDone chan struct{} - unloadDoneMutex sync.Mutex // Protects unloadDone channel operations - // eBPF C shared maps - clientRegistrationMap *ebpf.Map - agentRegistartionMap *ebpf.Map - dockerAppRegistrationMap *ebpf.Map - redirectProxyMap *ebpf.Map - e2eAppRegistrationMap *ebpf.Map - //-------------- - - // eBPF C shared objectsobjects - // ebpf objects and events - socket link.Link - connect4 link.Link - gp4 link.Link - udpp4 link.Link - tcppv4 link.Link - tcpv4 link.Link - tcpv4Ret link.Link - connect6 link.Link - gp6 link.Link - tcppv6 link.Link - tcpv6 link.Link - tcpv6Ret link.Link - - connect link.Link - connectRet link.Link - - accept link.Link - acceptRet link.Link - accept4 link.Link - accept4Ret link.Link - read link.Link - readRet link.Link - write link.Link - writeRet link.Link - close link.Link - closeRet link.Link - sendto link.Link - sendtoRet link.Link - recvfrom link.Link - recvfromRet link.Link - objects bpfObjects - writev link.Link - writevRet link.Link - readv link.Link - readvRet link.Link - retprobeMaxActive int - appID uint64 -} - -func (h *Hooks) Load(ctx context.Context, id uint64, opts core.HookCfg) error { - - h.sess.Set(id, &core.Session{ - ID: id, - }) - - // Set the app ID for this session with proper synchronization - h.m.Lock() - h.appID = id - h.m.Unlock() - - // Reset the unload done channel for this new load - h.unloadDoneMutex.Lock() - h.unloadDone = make(chan struct{}) - h.unloadDoneMutex.Unlock() - - err := h.load(ctx, opts) - if err != nil { - return err - } - - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context") - } - - g.Go(func() error { - defer utils.Recover(h.logger) - <-ctx.Done() - h.unLoad(ctx, opts) - - //deleting in order to free the memory in case of rerecord. - h.sess.Delete(id) - - // Signal that unload is complete - h.unloadDoneMutex.Lock() - close(h.unloadDone) - h.unloadDoneMutex.Unlock() - return nil - }) - - return nil -} - -// GetUnloadDone returns a channel that will be closed when the hooks are completely unloaded -func (h *Hooks) GetUnloadDone() <-chan struct{} { - h.unloadDoneMutex.Lock() - defer h.unloadDoneMutex.Unlock() - return h.unloadDone -} - -func (h *Hooks) load(ctx context.Context, opts core.HookCfg) error { - // Allow the current process to lock memory for eBPF resources. - if err := rlimit.RemoveMemlock(); err != nil { - utils.LogError(h.logger, err, "failed to lock memory for eBPF resources") - return err - } - - // Load pre-compiled programs and maps into the kernel. - objs := bpfObjects{} - if err := loadBpfObjects(&objs, nil); err != nil { - var ve *ebpf.VerifierError - if errors.As(err, &ve) { - errString := strings.Join(ve.Log, "\n") - h.logger.Debug("verifier log: ", zap.String("err", errString)) - } - utils.LogError(h.logger, err, "failed to load eBPF objects") - return err - } - - //getting all the ebpf maps with proper synchronization - h.objectsMutex.Lock() - h.clientRegistrationMap = objs.KeployClientRegistrationMap - h.agentRegistartionMap = objs.KeployAgentRegistrationMap - h.e2eAppRegistrationMap = objs.E2eInfoMap - h.dockerAppRegistrationMap = objs.DockerAppRegistrationMap - h.objects = objs - h.objectsMutex.Unlock() - - // --------------- - - // ----- used in case of wsl ----- - socket, err := link.Kprobe("sys_socket", objs.SyscallProbeEntrySocket, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_socket") - return err - } - h.socket = socket - - if !opts.E2E { - h.redirectProxyMap = objs.RedirectProxyMap - h.objects = objs - // ------------ For Egress ------------- - udppC4, err := link.Kprobe("udp_pre_connect", objs.SyscallProbeEntryUdpPreConnect, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on udp_pre_connect") - return err - } - h.udpp4 = udppC4 - - // FOR IPV4 - tcppC4, err := link.Kprobe("tcp_v4_pre_connect", objs.SyscallProbeEntryTcpV4PreConnect, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on tcp_v4_pre_connect") - return err - } - h.tcppv4 = tcppC4 - - tcpC4, err := link.Kprobe("tcp_v4_connect", objs.SyscallProbeEntryTcpV4Connect, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on tcp_v4_connect") - return err - } - h.tcpv4 = tcpC4 - - tcpRC4, err := link.Kretprobe("tcp_v4_connect", objs.SyscallProbeRetTcpV4Connect, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on tcp_v4_connect") - return err - } - h.tcpv4Ret = tcpRC4 - - // Get the first-mounted cgroupv2 path. - cGroupPath, err := detectCgroupPath(h.logger) - if err != nil { - utils.LogError(h.logger, err, "failed to detect the cgroup path") - return err - } - - c4, err := link.AttachCgroup(link.CgroupOptions{ - Path: cGroupPath, - Attach: ebpf.AttachCGroupInet4Connect, - Program: objs.K_connect4, - }) - - if err != nil { - utils.LogError(h.logger, err, "failed to attach the connect4 cgroup hook") - return err - } - h.connect4 = c4 - - gp4, err := link.AttachCgroup(link.CgroupOptions{ - Path: cGroupPath, - Attach: ebpf.AttachCgroupInet4GetPeername, - Program: objs.K_getpeername4, - }) - - if err != nil { - utils.LogError(h.logger, err, "failed to attach the GetPeername4 cgroup hook") - return err - } - h.gp4 = gp4 - - // FOR IPV6 - - tcpPreC6, err := link.Kprobe("tcp_v6_pre_connect", objs.SyscallProbeEntryTcpV6PreConnect, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on tcp_v6_pre_connect") - return err - } - h.tcppv6 = tcpPreC6 - - tcpC6, err := link.Kprobe("tcp_v6_connect", objs.SyscallProbeEntryTcpV6Connect, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on tcp_v6_connect") - return err - } - h.tcpv6 = tcpC6 - - tcpRC6, err := link.Kretprobe("tcp_v6_connect", objs.SyscallProbeRetTcpV6Connect, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on tcp_v6_connect") - return err - } - h.tcpv6Ret = tcpRC6 - - c6, err := link.AttachCgroup(link.CgroupOptions{ - Path: cGroupPath, - Attach: ebpf.AttachCGroupInet6Connect, - Program: objs.K_connect6, - }) - - if err != nil { - utils.LogError(h.logger, err, "failed to attach the connect6 cgroup hook") - return err - } - h.connect6 = c6 - - gp6, err := link.AttachCgroup(link.CgroupOptions{ - Path: cGroupPath, - Attach: ebpf.AttachCgroupInet6GetPeername, - Program: objs.K_getpeername6, - }) - - if err != nil { - utils.LogError(h.logger, err, "failed to attach the GetPeername6 cgroup hook") - return err - } - h.gp6 = gp6 - } - - // The hook sys_connect is used to identify outgoing connections to avoid misclassifying reused FDs - // as incoming, especially when analyzing `write` syscalls. - - //Open a kprobe at the entry of connect syscall - cnt, err := link.Kprobe("sys_connect", objs.SyscallProbeEntryConnect, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_connect") - return err - } - h.connect = cnt - - //Opening a kretprobe at the exit of connect syscall - cntr, err := link.Kretprobe("sys_connect", objs.SyscallProbeRetConnect, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_connect") - return err - } - h.connectRet = cntr - - // ------------ For Ingress using Kprobes -------------- - - //Open a kprobe at the entry of sendto syscall - snd, err := link.Kprobe("sys_sendto", objs.SyscallProbeEntrySendto, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_sendto") - return err - } - h.sendto = snd - - //Opening a kretprobe at the exit of sendto syscall - sndr, err := link.Kretprobe("sys_sendto", objs.SyscallProbeRetSendto, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_sendto") - return err - } - h.sendtoRet = sndr - - // Open a Kprobe at the entry point of the kernel function and attach the - // pre-compiled program. - ac, err := link.Kprobe("sys_accept", objs.SyscallProbeEntryAccept, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_accept") - return err - } - h.accept = ac - - // Open a Kprobe at the exit point of the kernel function and attach the - // pre-compiled program. - acRet, err := link.Kretprobe("sys_accept", objs.SyscallProbeRetAccept, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_accept") - return err - } - h.acceptRet = acRet - - // Open a Kprobe at the entry point of the kernel function and attach the - // pre-compiled program. - ac4, err := link.Kprobe("sys_accept4", objs.SyscallProbeEntryAccept4, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_accept4") - return err - } - h.accept4 = ac4 - - // Open a Kprobe at the exit point of the kernel function and attach the - // pre-compiled program. - ac4Ret, err := link.Kretprobe("sys_accept4", objs.SyscallProbeRetAccept4, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_accept4") - return err - } - h.accept4Ret = ac4Ret - - // Open a Kprobe at the entry point of the kernel function and attach the - // pre-compiled program. - rd, err := link.Kprobe("sys_read", objs.SyscallProbeEntryRead, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_read") - return err - } - h.read = rd - - // Open a Kprobe at the exit point of the kernel function and attach the - // pre-compiled program. - rdRet, err := link.Kretprobe("sys_read", objs.SyscallProbeRetRead, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_read") - return err - } - h.readRet = rdRet - - // Open a Kprobe at the entry point of the kernel function and attach the - // pre-compiled program. - wt, err := link.Kprobe("sys_write", objs.SyscallProbeEntryWrite, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_write") - return err - } - h.write = wt - - // Open a Kprobe at the exit point of the kernel function and attach the - // pre-compiled program. - wtRet, err := link.Kretprobe("sys_write", objs.SyscallProbeRetWrite, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_write") - return err - } - h.writeRet = wtRet - - // Open a Kprobe at the entry point of the kernel function and attach the - // pre-compiled program for readv. - readv, err := link.Kprobe("sys_readv", objs.SyscallProbeEntryReadv, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_readv") - return err - } - h.readv = readv - - // Open a Kprobe at the exit point of the kernel function and attach the - // pre-compiled program for readv. - readvRet, err := link.Kretprobe("sys_readv", objs.SyscallProbeRetReadv, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_readv") - return err - } - h.readvRet = readvRet - - // Open a Kprobe at the entry point of the kernel function and attach the - // pre-compiled program for writev. - wtv, err := link.Kprobe("sys_writev", objs.SyscallProbeEntryWritev, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_writev") - return err - } - h.writev = wtv - - // Open a Kprobe at the exit point of the kernel function and attach the - // pre-compiled program for writev. - wtvRet, err := link.Kretprobe("sys_writev", objs.SyscallProbeRetWritev, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_writev") - return err - } - h.writevRet = wtvRet - - // Open a Kprobe at the entry point of the kernel function and attach the - // pre-compiled program. - cl, err := link.Kprobe("sys_close", objs.SyscallProbeEntryClose, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_close") - return err - } - h.close = cl - - //Attaching a kprobe at the entry of recvfrom syscall - rcv, err := link.Kprobe("sys_recvfrom", objs.SyscallProbeEntryRecvfrom, nil) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kprobe hook on sys_recvfrom") - return err - } - h.recvfrom = rcv - - //Attaching a kretprobe at the exit of recvfrom syscall - rcvr, err := link.Kretprobe("sys_recvfrom", objs.SyscallProbeRetRecvfrom, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_recvfrom") - return err - } - h.recvfromRet = rcvr - - // Open a Kprobe at the exit point of the kernel function and attach the - // pre-compiled program. - clRet, err := link.Kretprobe("sys_close", objs.SyscallProbeRetClose, &link.KprobeOptions{RetprobeMaxActive: h.retprobeMaxActive}) - if err != nil { - utils.LogError(h.logger, err, "failed to attach the kretprobe hook on sys_close") - return err - } - h.closeRet = clRet - - h.logger.Info("keploy initialized and probes added to the kernel.") - - var clientInfo = structs.ClientInfo{} - - switch opts.Mode { - case models.MODE_RECORD: - clientInfo.Mode = uint32(1) - case models.MODE_TEST: - clientInfo.Mode = uint32(2) - default: - clientInfo.Mode = uint32(0) - } - - //sending keploy pid to kernel to get filtered - inode, err := getSelfInodeNumber() - if err != nil { - utils.LogError(h.logger, err, "failed to get inode of the keploy process") - return err - } - - clientInfo.KeployClientInode = inode - clientInfo.KeployClientNsPid = uint32(os.Getpid()) - if opts.E2E { - pid, err := utils.GetPIDFromPort(ctx, h.logger, int(opts.Port)) - if err != nil { - utils.LogError(h.logger, err, "failed to get the keploy pid from the port in case of e2e") - return err - } - err = h.SendE2EInfo(pid) - if err != nil { - h.logger.Error("failed to send e2e info to the ebpf program", zap.Error(err)) - } - } - - clientInfo.IsKeployClientRegistered = uint32(0) - - if opts.IsDocker { - h.proxyIP4 = opts.KeployIPV4 - ipv6, err := ToIPv4MappedIPv6(opts.KeployIPV4) - if err != nil { - return fmt.Errorf("failed to convert ipv4:%v to ipv4 mapped ipv6 in docker env:%v", opts.KeployIPV4, err) - } - h.logger.Debug(fmt.Sprintf("IPv4-mapped IPv6 for %s is: %08x:%08x:%08x:%08x\n", h.proxyIP4, ipv6[0], ipv6[1], ipv6[2], ipv6[3])) - h.proxyIP6 = ipv6 - } - - h.logger.Debug("proxy ips", zap.String("ipv4", h.proxyIP4), zap.Any("ipv6", h.proxyIP6)) - - proxyIP, err := IPv4ToUint32(h.proxyIP4) - if err != nil { - return fmt.Errorf("failed to convert ip string:[%v] to 32-bit integer", opts.KeployIPV4) - } - - var agentInfo = structs.AgentInfo{} - - agentInfo.ProxyInfo = structs.ProxyInfo{ - IP4: proxyIP, - IP6: h.proxyIP6, - Port: h.proxyPort, - } - - agentInfo.DNSPort = int32(h.dnsPort) - - if opts.IsDocker { - clientInfo.IsDockerApp = uint32(1) - } else { - clientInfo.IsDockerApp = uint32(0) - } - - ports := GetPortToSendToKernel(ctx, opts.Rules) - for i := 0; i < 10; i++ { - if len(ports) <= i { - clientInfo.PassThroughPorts[i] = -1 - continue - } - clientInfo.PassThroughPorts[i] = int32(ports[i]) - } - - err = h.SendClientInfo(opts.AppID, clientInfo) - if err != nil { - h.logger.Error("failed to send app info to the ebpf program", zap.Error(err)) - return err - } - err = h.SendAgentInfo(agentInfo) - if err != nil { - h.logger.Error("failed to send agent info to the ebpf program", zap.Error(err)) - return err - } - - return nil -} - -func (h *Hooks) Record(ctx context.Context, _ uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) { - // TODO use the session to get the app id - // and then use the app id to get the test cases chan - // and pass that to eBPF consumers/listeners - return conn.ListenSocket(ctx, h.logger, h.objects.SocketOpenEvents, h.objects.SocketDataEvents, h.objects.SocketCloseEvents, opts) -} - -func (h *Hooks) unLoad(_ context.Context, opts core.HookCfg) { - // closing all events - //other - if err := h.socket.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the socket") - } - - // Reset the app ID with proper synchronization - h.m.Lock() - h.appID = 0 - h.m.Unlock() - - if !opts.E2E { - if err := h.udpp4.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the udpp4") - } - - if err := h.connect4.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the connect4") - } - - if err := h.gp4.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the gp4") - } - - if err := h.tcppv4.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the tcppv4") - } - - if err := h.tcpv4.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the tcpv4") - } - - if err := h.tcpv4Ret.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the tcpv4Ret") - } - - if err := h.connect6.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the connect6") - } - if err := h.gp6.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the gp6") - } - if err := h.tcppv6.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the tcppv6") - } - if err := h.tcpv6.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the tcpv6") - } - if err := h.tcpv6Ret.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the tcpv6Ret") - } - } - if err := h.accept.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the accept") - } - if err := h.acceptRet.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the acceptRet") - } - if err := h.accept4.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the accept4") - } - if err := h.accept4Ret.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the accept4Ret") - } - if err := h.read.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the read") - } - if err := h.readRet.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the readRet") - } - if err := h.write.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the write") - } - if err := h.writeRet.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the writeRet") - } - if err := h.writev.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the writev") - } - if err := h.writevRet.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the writevRet") - } - - if err := h.readv.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the readv") - } - if err := h.readvRet.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the readvRet") - } - - if err := h.close.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the close") - } - if err := h.closeRet.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the closeRet") - } - if err := h.sendto.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the sendto") - } - if err := h.sendtoRet.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the sendtoRet") - } - if err := h.recvfrom.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the recvfrom") - } - if err := h.recvfromRet.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the recvfromRet") - } - - // Close eBPF objects with proper synchronization - h.objectsMutex.Lock() - if err := h.objects.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the objects") - } - h.objectsMutex.Unlock() - - if err := h.connect.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the connect") - } - - if err := h.connectRet.Close(); err != nil { - utils.LogError(h.logger, err, "failed to close the connectRet") - } - - h.logger.Info("eBPF resources released successfully...") -} diff --git a/keploy/pkg/core/hooks/hooks_test.go b/keploy/pkg/core/hooks/hooks_test.go deleted file mode 100644 index 82e35bc..0000000 --- a/keploy/pkg/core/hooks/hooks_test.go +++ /dev/null @@ -1,273 +0,0 @@ -//go:build linux - -package hooks - -import ( - "context" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" - "golang.org/x/sync/errgroup" -) - -func TestNewHooks_UnloadDoneChannel(t *testing.T) { - logger := zap.NewNop() - cfg := &config.Config{ - ProxyPort: 6789, - DNSPort: 26789, - } - - hooks := NewHooks(logger, cfg) - - // Verify that the unloadDone channel is initialized - assert.NotNil(t, hooks.unloadDone, "unloadDone channel should be initialized") - - // Verify the channel is not closed initially - select { - case <-hooks.unloadDone: - t.Error("unloadDone channel should not be closed initially") - default: - // Expected behavior - channel is not closed - } -} - -func TestHooks_GetUnloadDone(t *testing.T) { - logger := zap.NewNop() - cfg := &config.Config{ - ProxyPort: 6789, - DNSPort: 26789, - } - - hooks := NewHooks(logger, cfg) - - // Get the channel - ch := hooks.GetUnloadDone() - assert.NotNil(t, ch, "GetUnloadDone should return a channel") - - // Verify it's the same channel - ch2 := hooks.GetUnloadDone() - assert.Equal(t, ch, ch2, "GetUnloadDone should return the same channel instance") - - // Verify the channel is not closed initially - select { - case <-ch: - t.Error("Channel should not be closed initially") - default: - // Expected behavior - } -} - -func TestHooks_GetUnloadDone_ThreadSafety(t *testing.T) { - logger := zap.NewNop() - cfg := &config.Config{ - ProxyPort: 6789, - DNSPort: 26789, - } - - hooks := NewHooks(logger, cfg) - - // Test concurrent access to GetUnloadDone - var wg sync.WaitGroup - channels := make([]<-chan struct{}, 10) - - for i := 0; i < 10; i++ { - wg.Add(1) - go func(index int) { - defer wg.Done() - channels[index] = hooks.GetUnloadDone() - }(i) - } - - wg.Wait() - - // All should return the same channel - for i := 1; i < 10; i++ { - assert.Equal(t, channels[0], channels[i], "All GetUnloadDone calls should return the same channel") - } -} - -func TestHooks_Load_ResetsUnloadDoneChannel(t *testing.T) { - logger := zap.NewNop() - cfg := &config.Config{ - ProxyPort: 6789, - DNSPort: 26789, - } - - hooks := NewHooks(logger, cfg) - - // Create a context with errgroup for Load method - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - g := &errgroup.Group{} - ctx = context.WithValue(ctx, models.ErrGroupKey, g) - - // Mock the load method to avoid eBPF dependency - // We'll test the channel reset logic by examining the struct directly - hooks.unloadDoneMutex.Lock() - oldCh := hooks.unloadDone - hooks.unloadDone = make(chan struct{}) - newCh := hooks.unloadDone - hooks.unloadDoneMutex.Unlock() - - // Verify that a new channel was created - assert.NotEqual(t, oldCh, newCh, "Load should create a new unloadDone channel") - - // Verify the new channel is not closed - select { - case <-newCh: - t.Error("New channel should not be closed") - default: - // Expected behavior - } -} - -func TestHooks_UnloadDoneChannel_ClosedOnUnload(t *testing.T) { - logger := zap.NewNop() - cfg := &config.Config{ - ProxyPort: 6789, - DNSPort: 26789, - } - - hooks := NewHooks(logger, cfg) - - // Get the channel before simulating unload - ch := hooks.GetUnloadDone() - - // Simulate the unload completion by manually closing the channel - // (This mimics what happens in the goroutine when context is cancelled) - hooks.unloadDoneMutex.Lock() - close(hooks.unloadDone) - hooks.unloadDoneMutex.Unlock() - - // Verify the channel is now closed - select { - case <-ch: - // Expected behavior - channel is closed - case <-time.After(100 * time.Millisecond): - t.Error("Channel should be closed after unload") - } -} - -func TestHooks_UnloadDoneChannel_ThreadSafetyOnClose(t *testing.T) { - logger := zap.NewNop() - cfg := &config.Config{ - ProxyPort: 6789, - DNSPort: 26789, - } - - hooks := NewHooks(logger, cfg) - - // Get multiple references to the channel - channels := make([]<-chan struct{}, 5) - for i := 0; i < 5; i++ { - channels[i] = hooks.GetUnloadDone() - } - - // Close the channel from one goroutine - go func() { - time.Sleep(50 * time.Millisecond) - hooks.unloadDoneMutex.Lock() - close(hooks.unloadDone) - hooks.unloadDoneMutex.Unlock() - }() - - // All channels should be closed - for i, ch := range channels { - select { - case <-ch: - // Expected behavior - case <-time.After(200 * time.Millisecond): - t.Errorf("Channel %d should be closed", i) - } - } -} - -func TestHooks_UnloadDoneChannel_MultipleLoads(t *testing.T) { - logger := zap.NewNop() - cfg := &config.Config{ - ProxyPort: 6789, - DNSPort: 26789, - } - - hooks := NewHooks(logger, cfg) - - // Simulate multiple load cycles - var channels []<-chan struct{} - - for i := 0; i < 3; i++ { - // Reset channel (simulating Load call) - hooks.unloadDoneMutex.Lock() - hooks.unloadDone = make(chan struct{}) - hooks.unloadDoneMutex.Unlock() - - // Get the channel - ch := hooks.GetUnloadDone() - channels = append(channels, ch) - - // Verify each channel is different - if i > 0 { - assert.NotEqual(t, channels[i-1], channels[i], "Each load should create a new channel") - } - - // Verify channel is not closed - select { - case <-ch: - t.Errorf("Channel %d should not be closed initially", i) - default: - // Expected behavior - } - } -} - -func TestHooks_UnloadDoneChannel_SequentialLoadAndUnload(t *testing.T) { - logger := zap.NewNop() - cfg := &config.Config{ - ProxyPort: 6789, - DNSPort: 26789, - } - - hooks := NewHooks(logger, cfg) - - // First load cycle - hooks.unloadDoneMutex.Lock() - hooks.unloadDone = make(chan struct{}) - hooks.unloadDoneMutex.Unlock() - - ch1 := hooks.GetUnloadDone() - - // Simulate unload - hooks.unloadDoneMutex.Lock() - close(hooks.unloadDone) - hooks.unloadDoneMutex.Unlock() - - // Verify first channel is closed - select { - case <-ch1: - // Expected behavior - case <-time.After(100 * time.Millisecond): - t.Error("First channel should be closed") - } - - // Second load cycle - hooks.unloadDoneMutex.Lock() - hooks.unloadDone = make(chan struct{}) - hooks.unloadDoneMutex.Unlock() - - ch2 := hooks.GetUnloadDone() - - // Verify second channel is different and not closed - assert.NotEqual(t, ch1, ch2, "Second load should create a new channel") - - select { - case <-ch2: - t.Error("Second channel should not be closed initially") - default: - // Expected behavior - } -} diff --git a/keploy/pkg/core/hooks/kernelComm.go b/keploy/pkg/core/hooks/kernelComm.go deleted file mode 100644 index 7a71e9b..0000000 --- a/keploy/pkg/core/hooks/kernelComm.go +++ /dev/null @@ -1,131 +0,0 @@ -//go:build linux - -package hooks - -import ( - "context" - "fmt" - - "github.com/cilium/ebpf" - "go.keploy.io/server/v2/pkg/core" - "go.keploy.io/server/v2/pkg/core/hooks/structs" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -//TODO: rename this file. - -// Get Used by proxy -func (h *Hooks) Get(_ context.Context, srcPort uint16) (*core.NetworkAddress, error) { - d, err := h.GetDestinationInfo(srcPort) - if err != nil { - return nil, err - } - - // Use the current app ID with proper synchronization - h.m.Lock() - currentAppID := h.appID - h.m.Unlock() - - s, ok := h.sess.Get(currentAppID) - if !ok { - return nil, fmt.Errorf("session not found") - } - - return &core.NetworkAddress{ - AppID: s.ID, - Version: d.IPVersion, - IPv4Addr: d.DestIP4, - IPv6Addr: d.DestIP6, - Port: d.DestPort, - }, nil -} - -// GetDestinationInfo retrieves destination information associated with a source port. -func (h *Hooks) GetDestinationInfo(srcPort uint16) (*structs.DestInfo, error) { - h.m.Lock() - defer h.m.Unlock() - destInfo := structs.DestInfo{} - if err := h.redirectProxyMap.Lookup(srcPort, &destInfo); err != nil { - return nil, err - } - return &destInfo, nil -} - -func (h *Hooks) Delete(_ context.Context, srcPort uint16) error { - return h.CleanProxyEntry(srcPort) -} - -func (h *Hooks) CleanProxyEntry(srcPort uint16) error { - h.m.Lock() - defer h.m.Unlock() - err := h.redirectProxyMap.Delete(srcPort) - if err != nil { - utils.LogError(h.logger, err, "failed to remove entry from redirect proxy map") - return err - } - h.logger.Debug("successfully removed entry from redirect proxy map", zap.Any("(Key)/SourcePort", srcPort)) - return nil -} - -func (h *Hooks) SendClientInfo(id uint64, appInfo structs.ClientInfo) error { - err := h.clientRegistrationMap.Update(id, appInfo, ebpf.UpdateAny) - if err != nil { - utils.LogError(h.logger, err, "failed to send the app info to the ebpf program") - return err - } - return nil -} - -func (h *Hooks) SendAgentInfo(agentInfo structs.AgentInfo) error { - key := 0 - err := h.agentRegistartionMap.Update(uint32(key), agentInfo, ebpf.UpdateAny) - if err != nil { - utils.LogError(h.logger, err, "failed to send the agent info to the ebpf program") - return err - } - return nil -} - -func (h *Hooks) SendE2EInfo(pid uint32) error { - key := 0 - err := h.e2eAppRegistrationMap.Update(uint64(key), pid, ebpf.UpdateAny) - if err != nil { - utils.LogError(h.logger, err, "failed to send the E2E info to the ebpf program") - return err - } - return nil -} - -func (h *Hooks) SendDockerAppInfo(appID uint64, dockerAppInfo structs.DockerAppInfo) error { - h.m.Lock() - defer h.m.Unlock() - - // Use the provided app ID or the current app ID, don't generate a random one - dockerAppID := appID - if dockerAppID == 0 { - dockerAppID = h.appID - } - - if h.appID != 0 { - err := h.dockerAppRegistrationMap.Delete(h.appID) - if err != nil { - utils.LogError(h.logger, err, "failed to remove entry from dockerAppRegistrationMap", zap.Any("(Key)/AppID", h.appID)) - return err - } - } - - // Don't override the app ID with a random number - use the real app ID - err := h.dockerAppRegistrationMap.Update(dockerAppID, dockerAppInfo, ebpf.UpdateAny) - if err != nil { - utils.LogError(h.logger, err, "failed to send the dockerAppInfo info to the ebpf program", zap.Uint64("appID", dockerAppID)) - return err - } - - // Update the app ID only if we received a valid one - if appID != 0 { - h.appID = appID - } - - return nil -} diff --git a/keploy/pkg/core/hooks/structs/structs.go b/keploy/pkg/core/hooks/structs/structs.go deleted file mode 100755 index a0e7010..0000000 --- a/keploy/pkg/core/hooks/structs/structs.go +++ /dev/null @@ -1,78 +0,0 @@ -//go:build linux - -// Package structs provides data structures for hooks. -package structs - -type BpfSpinLock struct{ Val uint32 } - -// struct dest_info_t -// { -// u32 ip_version; -// u32 dest_ip4; -// u32 dest_ip6[4]; -// u32 dest_port; -// u32 kernelPid; -// }; - -type DestInfo struct { - IPVersion uint32 - DestIP4 uint32 - DestIP6 [4]uint32 - DestPort uint32 - KernelPid uint32 - ClientID uint64 -} - -// struct proxy_info -// { -// u32 ip4; -// u32 ip6[4]; -// u32 port; -// }; - -type ProxyInfo struct { - IP4 uint32 - IP6 [4]uint32 - Port uint32 -} - -type DockerAppInfo struct { - AppInode uint64 - ClientID uint64 -} - -// struct app_info -// { -// u32 keploy_client_ns_pid; -// u64 keploy_client_inode; -// u64 app_inode; -// u32 mode; -// u32 is_docker_app; -// u32 is_keploy_client_registered; // whether the client is registered or not -// s32 pass_through_ports[PASS_THROUGH_ARRAY_SIZE]; -// }; - -type ClientInfo struct { - KeployClientInode uint64 // 8 bytes - KeployClientNsPid uint32 // 4 bytes - Mode uint32 // 4 bytes - IsDockerApp uint32 // 4 bytes - - IsKeployClientRegistered uint32 // 4 bytes - PassThroughPorts [10]int32 // 40 bytes -} - -// struct agent_info -// { -// u32 keploy_agent_ns_pid; -// u32 keploy_agent_inode; -// struct proxy_info proxy_info; -// s32 dns_port; -// }; - -type AgentInfo struct { - KeployAgentNsPid uint32 - DNSPort int32 - KeployAgentInode uint64 - ProxyInfo ProxyInfo -} diff --git a/keploy/pkg/core/hooks/util.go b/keploy/pkg/core/hooks/util.go deleted file mode 100644 index f28a9ff..0000000 --- a/keploy/pkg/core/hooks/util.go +++ /dev/null @@ -1,126 +0,0 @@ -//go:build linux - -package hooks - -import ( - "bufio" - "context" - "encoding/binary" - "errors" - "net" - "os" - "path/filepath" - "strings" - "syscall" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// IPv4ToUint32 converts a string representation of an IPv4 address to a 32-bit integer. -func IPv4ToUint32(ipStr string) (uint32, error) { - ipAddr := net.ParseIP(ipStr) - if ipAddr != nil { - ipAddr = ipAddr.To4() - if ipAddr != nil { - return binary.BigEndian.Uint32(ipAddr), nil - } - return 0, errors.New("not a valid IPv4 address") - } - return 0, errors.New("failed to parse IP address") -} - -// ToIPv4MappedIPv6 converts an IPv4 address to an IPv4-mapped IPv6 address. -func ToIPv4MappedIPv6(ipv4 string) ([4]uint32, error) { - var result [4]uint32 - - // Parse the input IPv4 address - ip := net.ParseIP(ipv4) - if ip == nil { - return result, errors.New("invalid IPv4 address") - } - - // Check if the input is an IPv4 address - ip = ip.To4() - if ip == nil { - return result, errors.New("not a valid IPv4 address") - } - - // Convert IPv4 address to IPv4-mapped IPv6 address - // IPv4-mapped IPv6 address is ::ffff:a.b.c.d - ipv6 := "::ffff:" + ipv4 - - // Parse the resulting IPv6 address - ip6 := net.ParseIP(ipv6) - if ip6 == nil { - return result, errors.New("failed to parse IPv4-mapped IPv6 address") - } - - // Convert the IPv6 address to a 16-byte representation - ip6Bytes := ip6.To16() - if ip6Bytes == nil { - return result, errors.New("failed to convert IPv6 address to bytes") - } - - // Populate the result array - for i := 0; i < 4; i++ { - result[i] = uint32(ip6Bytes[i*4])<<24 | uint32(ip6Bytes[i*4+1])<<16 | uint32(ip6Bytes[i*4+2])<<8 | uint32(ip6Bytes[i*4+3]) - } - - return result, nil -} - -// detectCgroupPath returns the first-found mount point of type cgroup2 -// and stores it in the cgroupPath global variable. -func detectCgroupPath(logger *zap.Logger) (string, error) { - f, err := os.Open("/proc/mounts") - if err != nil { - return "", err - } - defer func() { - err := f.Close() - if err != nil { - utils.LogError(logger, err, "failed to close /proc/mounts file") - } - }() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - // example fields: cgroup2 /sys/fs/cgroup/unified cgroup2 rw,nosuid,nodev,noexec,relatime 0 0 - fields := strings.Split(scanner.Text(), " ") - if len(fields) >= 3 && fields[2] == "cgroup2" { - return fields[1], nil - } - } - - return "", errors.New("cgroup2 not mounted") -} - -func getSelfInodeNumber() (uint64, error) { - p := filepath.Join("/proc", "self", "ns", "pid") - - f, err := os.Stat(p) - if err != nil { - return 0, errors.New("failed to get inode of the keploy process") - } - // Dev := (f.Sys().(*syscall.Stat_t)).Dev - Ino := (f.Sys().(*syscall.Stat_t)).Ino - if Ino != 0 { - return Ino, nil - } - return 0, nil -} - -func GetPortToSendToKernel(_ context.Context, rules []config.BypassRule) []uint { - // if the rule only contains port, then it should be sent to kernel - ports := []uint{} - for _, rule := range rules { - if rule.Host == "" && rule.Path == "" { - if rule.Port != 0 { - ports = append(ports, rule.Port) - } - } - } - return ports -} diff --git a/keploy/pkg/core/proxy/README.md b/keploy/pkg/core/proxy/README.md deleted file mode 100755 index ef79802..0000000 --- a/keploy/pkg/core/proxy/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Proxy Package Documentation - -This package includes modules that the `hooks` package utilizes to -redirect the outgoing calls of the user API. This redirection is -done with the aim to record or stub the outputs of dependency calls. \ No newline at end of file diff --git a/keploy/pkg/core/proxy/dns.go b/keploy/pkg/core/proxy/dns.go deleted file mode 100644 index ab944c0..0000000 --- a/keploy/pkg/core/proxy/dns.go +++ /dev/null @@ -1,345 +0,0 @@ -//go:build linux - -package proxy - -import ( - "context" - "errors" - "fmt" - "net" - "os" - "strings" - "sync" - - "github.com/miekg/dns" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func (p *Proxy) startTCPDNSServer(_ context.Context) error { - addr := fmt.Sprintf(":%v", p.DNSPort) - - handler := p - server := &dns.Server{ - Addr: addr, - Net: "tcp", - Handler: handler, - ReusePort: true, - } - - p.TCPDNSServer = server - - p.logger.Info(fmt.Sprintf("starting TCP DNS server at addr %v", server.Addr)) - err := server.ListenAndServe() - if err != nil { - utils.LogError(p.logger, err, "failed to start tcp dns server", zap.Any("addr", server.Addr)) - } - return nil -} - -func (p *Proxy) startUDPDNSServer(_ context.Context) error { - - addr := fmt.Sprintf(":%v", p.DNSPort) - - handler := p - server := &dns.Server{ - Addr: addr, - Net: "udp", - Handler: handler, - ReusePort: true, - // DisableBackground: true, - } - - p.UDPDNSServer = server - - p.logger.Info(fmt.Sprintf("starting UDP DNS server at addr %v", server.Addr)) - err := server.ListenAndServe() - if err != nil { - utils.LogError(p.logger, err, "failed to start udp dns server", zap.Any("addr", server.Addr)) - return err - } - return nil -} - -// For DNS caching -var cache = struct { - sync.RWMutex - m map[string][]dns.RR -}{m: make(map[string][]dns.RR)} - -func generateCacheKey(name string, qtype uint16) string { - // For MongoDB SRV queries, include "mongodb" in the cache key to differentiate from other SRV queries - if strings.HasPrefix(name, "_mongodb._tcp.") { - return fmt.Sprintf("mongodb-%s-%s", name, dns.TypeToString[qtype]) - } - return fmt.Sprintf("%s-%s", name, dns.TypeToString[qtype]) -} - -func (p *Proxy) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { - - p.logger.Debug("", zap.Any("Source socket info", w.RemoteAddr().String())) - msg := new(dns.Msg) - msg.SetReply(r) - msg.Authoritative = true - p.logger.Debug("Got some Dns queries") - for _, question := range r.Question { - p.logger.Debug("", zap.Any("Record Type", question.Qtype), zap.Any("Received Query", question.Name)) - - key := generateCacheKey(question.Name, question.Qtype) - - // Clear cache for MongoDB SRV queries to ensure fresh resolution - if strings.HasPrefix(question.Name, "_mongodb._tcp.") { - cache.Lock() - delete(cache.m, key) - cache.Unlock() - } - - // Check if the answer is cached - cache.RLock() - answers, found := cache.m[key] - cache.RUnlock() - - if !found { - // If not found in cache, resolve the DNS query only in case of record mode - //TODO: Add support for passThrough here using the src<->dst mapping - if models.GetMode() == models.MODE_RECORD { - answers = resolveDNSQuery(p.logger, question.Name) - } - - if len(answers) == 0 { - - switch question.Qtype { - // If the resolution failed, return a default A record with Proxy IP - // or AAAA record with Proxy IP6 - case dns.TypeA: - answers = []dns.RR{&dns.A{ - Hdr: dns.RR_Header{Name: question.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600}, - A: net.ParseIP(p.IP4), - }} - p.logger.Debug("failed to resolve dns query hence sending proxy ip4", zap.Any("proxy Ip", p.IP4)) - case dns.TypeAAAA: - answers = []dns.RR{&dns.AAAA{ - Hdr: dns.RR_Header{Name: question.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600}, - AAAA: net.ParseIP(p.IP6), - }} - p.logger.Debug("failed to resolve dns query hence sending proxy ip6", zap.Any("proxy Ip", p.IP6)) - case dns.TypeSRV: - // Special handling for MongoDB SRV queries - if strings.HasPrefix(question.Name, "_mongodb._tcp.") { - baseDomain := strings.TrimPrefix(question.Name, "_mongodb._tcp.") - answers = []dns.RR{&dns.SRV{ - Hdr: dns.RR_Header{Name: dns.Fqdn(question.Name), Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: 3600}, - Priority: 0, - Weight: 0, - Port: 27017, - Target: dns.Fqdn("mongodb." + baseDomain), - }} - } else { - answers = []dns.RR{&dns.SRV{ - Hdr: dns.RR_Header{Name: question.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: 3600}, - Priority: 0, - Weight: 0, - Port: 8080, - Target: dns.Fqdn("keploy.proxy"), - }} - } - p.logger.Debug("sending default SRV record response") - default: - p.logger.Error("Unsupported DNS query type", zap.Any("query type", question.Qtype)) - } - - } - p.logger.Debug(fmt.Sprintf("Answers[when resolution failed for query:%v]:\n%v\n", question.Qtype, answers)) - - // Cache the answer - cache.Lock() - cache.m[key] = answers - cache.Unlock() - p.logger.Debug(fmt.Sprintf("Answers[after caching it]:\n%v\n", answers)) - } - - p.logger.Debug(fmt.Sprintf("Answers[before appending to msg]:\n%v\n", answers)) - msg.Answer = append(msg.Answer, answers...) - p.logger.Debug(fmt.Sprintf("Answers[After appending to msg]:\n%v\n", msg.Answer)) - } - - p.logger.Debug(fmt.Sprintf("dns msg sending back:\n%v\n", msg)) - p.logger.Debug(fmt.Sprintf("dns msg RCODE sending back:\n%v\n", msg.Rcode)) - p.logger.Debug("Writing dns info back to the client...") - err := w.WriteMsg(msg) - if err != nil { - utils.LogError(p.logger, err, "failed to write dns info back to the client") - } -} - -// TODO: passThrough the dns queries rather than resolving them. -func resolveDNSQuery(logger *zap.Logger, domain string) []dns.RR { - // Remove the last dot from the domain name if it exists - domain = strings.TrimSuffix(domain, ".") - - // Use the default system resolver - resolver := net.DefaultResolver - - ctx := context.Background() - - var answers []dns.RR - - // For SRV records, handle MongoDB specific queries - if strings.HasPrefix(domain, "_mongodb._tcp.") { - baseDomain := strings.TrimPrefix(domain, "_mongodb._tcp.") - _, addrs, err := resolver.LookupSRV(ctx, "mongodb", "tcp", baseDomain) - if err == nil && len(addrs) > 0 { - for _, addr := range addrs { - answers = append(answers, &dns.SRV{ - Hdr: dns.RR_Header{Name: dns.Fqdn(domain), Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: 3600}, - Priority: addr.Priority, - Weight: addr.Weight, - Port: addr.Port, - Target: dns.Fqdn(addr.Target), - }) - } - return answers - } - // If resolution fails, return a default SRV record with a target that matches the domain suffix - return []dns.RR{&dns.SRV{ - Hdr: dns.RR_Header{Name: dns.Fqdn(domain), Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: 3600}, - Priority: 0, - Weight: 0, - Port: 27017, // Default MongoDB port - Target: dns.Fqdn("mongodb." + baseDomain), - }} - } - - // For TXT records, try to resolve them directly - txtRecords, err := resolver.LookupTXT(ctx, domain) - if err == nil && len(txtRecords) > 0 { - for _, txt := range txtRecords { - answers = append(answers, &dns.TXT{ - Hdr: dns.RR_Header{Name: dns.Fqdn(domain), Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 3600}, - Txt: []string{txt}, - }) - } - return answers - } - - // For A/AAAA records - ips, err := resolver.LookupIPAddr(ctx, domain) - // Perform the lookup with the context - // ips, err := resolver.LookupIPAddr(context.Background(), domain) - - if err != nil { - logger.Debug(fmt.Sprintf("failed to resolve the dns query for:%v", domain), zap.Error(err)) - return nil - } - - // Convert the resolved IPs to dns.RR - // var answers []dns.RR - for _, ip := range ips { - if ipv4 := ip.IP.To4(); ipv4 != nil { - answers = append(answers, &dns.A{ - Hdr: dns.RR_Header{Name: dns.Fqdn(domain), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600}, - A: ipv4, - }) - } else { - answers = append(answers, &dns.AAAA{ - Hdr: dns.RR_Header{Name: dns.Fqdn(domain), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600}, - AAAA: ip.IP, - }) - } - } - - if len(answers) > 0 { - logger.Debug("resolved the dns records successfully") - } - - return answers -} - -func (p *Proxy) stopDNSServers(_ context.Context) error { - // stop tcp dns server - if err := p.stopTCPDNSServer(); err != nil { - return err - } - // stop udp dns server - err := p.stopUDPDNSServer() - return err -} - -func (p *Proxy) stopTCPDNSServer() error { - if p.TCPDNSServer != nil { - err := p.TCPDNSServer.Shutdown() - if err != nil { - utils.LogError(p.logger, err, "failed to stop tcp dns server") - return err - } - p.logger.Info("Tcp Dns server stopped successfully") - } - return nil -} - -func (p *Proxy) stopUDPDNSServer() error { - if p.UDPDNSServer != nil { - err := p.UDPDNSServer.Shutdown() - if err != nil { - utils.LogError(p.logger, err, "failed to stop udp dns server") - return err - } - p.logger.Info("Udp Dns server stopped successfully") - } - return nil -} - -const ( - nsSwitchConfig = "/etc/nsswitch.conf" - nsSwitchPerm = 0644 -) - -// setting up the dns routing for the linux system -func (p *Proxy) setupNsswitchConfig() error { - - // Check if the nsswitch.conf present for the system - if _, err := os.Stat(nsSwitchConfig); err == nil { - // Read the current nsswitch.conf - data, err := os.ReadFile(nsSwitchConfig) - if err != nil { - utils.LogError(p.logger, err, "failed to read the nsswitch.conf file from system") - return errors.New("failed to setup the nsswitch.conf file to redirect the DNS queries to proxy") - } - // copy the data of the nsswitch.conf file in order to reset it back to the original state in the end - p.nsswitchData = data - - // Replace the hosts field value if it exists - lines := strings.Split(string(data), "\n") - for i, line := range lines { - if strings.HasPrefix(line, "hosts:") { - lines[i] = "hosts: files dns" - } - } - - data = []byte(strings.Join(lines, "\n")) - - // Write the modified nsswitch.conf back to the file - err = writeNsswitchConfig(p.logger, nsSwitchConfig, data, nsSwitchPerm) - if err != nil { - return errors.New("failed to setup the nsswitch.conf file to redirect the DNS queries to proxy") - } - - p.logger.Debug("Successfully written to nsswitch config of linux") - } - return nil -} - -// resetNsSwitchConfig resets the hosts config of nsswitch of the system -func (p *Proxy) resetNsSwitchConfig() error { - data := p.nsswitchData - - // Write the original data back to the nsswitch.conf file - err := writeNsswitchConfig(p.logger, nsSwitchConfig, data, nsSwitchPerm) - if err != nil { - return errors.New("failed to reset the nsswitch.conf back to the original state") - } - - p.logger.Debug("Successfully reset the nsswitch config of linux") - return nil -} diff --git a/keploy/pkg/core/proxy/integrations/README.md b/keploy/pkg/core/proxy/integrations/README.md deleted file mode 100755 index b5c3e39..0000000 --- a/keploy/pkg/core/proxy/integrations/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Integrations Package Documentation - -This package includes modules that are used for parsing different protocols. \ No newline at end of file diff --git a/keploy/pkg/core/proxy/integrations/generic/README.md b/keploy/pkg/core/proxy/integrations/generic/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/keploy/pkg/core/proxy/integrations/generic/decode.go b/keploy/pkg/core/proxy/integrations/generic/decode.go deleted file mode 100644 index b32a95f..0000000 --- a/keploy/pkg/core/proxy/integrations/generic/decode.go +++ /dev/null @@ -1,124 +0,0 @@ -//go:build linux - -// Package generic provides functionality for decoding generic dependencies. -package generic - -import ( - "context" - "io" - "net" - "time" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func decodeGeneric(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, _ models.OutgoingOptions) error { - genericRequests := [][]byte{reqBuf} - logger.Debug("Into the generic parser in test mode") - errCh := make(chan error, 1) - go func(errCh chan error, genericRequests [][]byte) { - defer pUtil.Recover(logger, clientConn, nil) - defer close(errCh) - for { - // Since protocol packets have to be parsed for checking stream end, - // clientConnection have deadline for read to determine the end of stream. - err := clientConn.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) - if err != nil { - utils.LogError(logger, err, "failed to set the read deadline for the client conn") - return - } - - // To read the stream of request packets from the client - for { - buffer, err := pUtil.ReadBytes(ctx, logger, clientConn) - // Applied this nolint to ignore the staticcheck error here because of readability - // nolint:staticcheck - if netErr, ok := err.(net.Error); !(ok && netErr.Timeout()) && err != nil && err.Error() != "EOF" { - utils.LogError(logger, err, "failed to read the request message in proxy for generic dependency") - return - } - if netErr, ok := err.(net.Error); (ok && netErr.Timeout()) || (err != nil && err.Error() == "EOF") { - logger.Debug("the timeout for the client read in generic or EOF") - break - } - genericRequests = append(genericRequests, buffer) - } - - if len(genericRequests) == 0 { - logger.Debug("the generic request buffer is empty") - continue - } - - // bestMatchedIndx := 0 - // fuzzy match gives the index for the best matched generic mock - matched, genericResponses, err := fuzzyMatch(ctx, logger, genericRequests, mockDb) - if err != nil { - utils.LogError(logger, err, "error while matching generic mocks") - } - - if !matched { - err := clientConn.SetReadDeadline(time.Time{}) - if err != nil { - utils.LogError(logger, err, "failed to set the read deadline for the client conn") - return - } - - logger.Debug("the genericRequests before pass through are", zap.Any("length", len(genericRequests))) - for _, genReq := range genericRequests { - logger.Debug("the genericRequests are:", zap.Any("h", string(genReq))) - } - - reqBuffer, err := pUtil.PassThrough(ctx, logger, clientConn, dstCfg, genericRequests) - if err != nil { - utils.LogError(logger, err, "failed to passthrough the generic request") - return - } - - genericRequests = [][]byte{} - logger.Debug("the request buffer after pass through in generic", zap.Any("buffer", string(reqBuffer))) - if len(reqBuffer) > 0 { - genericRequests = [][]byte{reqBuffer} - } - logger.Debug("the length of genericRequests after passThrough ", zap.Any("length", len(genericRequests))) - continue - } - for _, genericResponse := range genericResponses { - encoded := []byte(genericResponse.Message[0].Data) - if genericResponse.Message[0].Type != models.String { - encoded, err = util.DecodeBase64(genericResponse.Message[0].Data) - if err != nil { - utils.LogError(logger, err, "failed to decode the base64 response") - return - } - } - _, err := clientConn.Write(encoded) - if err != nil { - if ctx.Err() != nil { - return - } - utils.LogError(logger, err, "failed to write the response message to the client application") - return - } - } - - // Clear the genericRequests buffer for the next dependency call - genericRequests = [][]byte{} - logger.Debug("the genericRequests after the iteration", zap.Any("length", len(genericRequests))) - } - }(errCh, genericRequests) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - return nil - } - return err - } -} diff --git a/keploy/pkg/core/proxy/integrations/generic/encode.go b/keploy/pkg/core/proxy/integrations/generic/encode.go deleted file mode 100644 index 8c31f7a..0000000 --- a/keploy/pkg/core/proxy/integrations/generic/encode.go +++ /dev/null @@ -1,200 +0,0 @@ -//go:build linux - -package generic - -import ( - "context" - "encoding/base64" - "fmt" - "io" - "net" - "strconv" - "time" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func encodeGeneric(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, _ models.OutgoingOptions) error { - - var genericRequests []models.Payload - - bufStr := string(reqBuf) - dataType := models.String - if !util.IsASCII(string(reqBuf)) { - bufStr = util.EncodeBase64(reqBuf) - dataType = "binary" - } - - if bufStr != "" { - genericRequests = append(genericRequests, models.Payload{ - Origin: models.FromClient, - Message: []models.OutputBinary{ - { - Type: dataType, - Data: bufStr, - }, - }, - }) - } - _, err := destConn.Write(reqBuf) - if err != nil { - utils.LogError(logger, err, "failed to write request message to the destination server") - return err - } - var genericResponses []models.Payload - - clientBuffChan := make(chan []byte) - destBuffChan := make(chan []byte) - errChan := make(chan error) - //TODO: where to close the error channel since it is used in both the go routines - //close(errChan) - - // read requests from client - err = pUtil.ReadFromPeer(ctx, logger, clientConn, clientBuffChan, errChan, pUtil.Client) - if err != nil { - return fmt.Errorf("error reading from client:%v", err) - } - - // read responses from destination - err = pUtil.ReadFromPeer(ctx, logger, destConn, destBuffChan, errChan, pUtil.Destination) - if err != nil { - return fmt.Errorf("error reading from destination:%v", err) - } - - prevChunkWasReq := false - var reqTimestampMock = time.Now() - var resTimestampMock time.Time - - // ticker := time.NewTicker(1 * time.Second) - logger.Debug("the iteration for the generic request starts", zap.Any("genericReqs", len(genericRequests)), zap.Any("genericResps", len(genericResponses))) - for { - select { - case <-ctx.Done(): - if !prevChunkWasReq && len(genericRequests) > 0 && len(genericResponses) > 0 { - genericRequestsCopy := make([]models.Payload, len(genericRequests)) - genericResponsesCopy := make([]models.Payload, len(genericResponses)) - copy(genericResponsesCopy, genericResponses) - copy(genericRequestsCopy, genericRequests) - - metadata := make(map[string]string) - metadata["type"] = "config" - metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) - // Save the mock - mocks <- &models.Mock{ - Version: models.GetVersion(), - Name: "mocks", - Kind: models.GENERIC, - Spec: models.MockSpec{ - GenericRequests: genericRequestsCopy, - GenericResponses: genericResponsesCopy, - ReqTimestampMock: reqTimestampMock, - ResTimestampMock: resTimestampMock, - Metadata: metadata, - }, - } - return ctx.Err() - } - case buffer := <-clientBuffChan: - // Write the request message to the destination - _, err := destConn.Write(buffer) - if err != nil { - utils.LogError(logger, err, "failed to write request message to the destination server") - return err - } - - logger.Debug("the iteration for the generic request ends with no of genericReqs:" + strconv.Itoa(len(genericRequests)) + " and genericResps: " + strconv.Itoa(len(genericResponses))) - if !prevChunkWasReq && len(genericRequests) > 0 && len(genericResponses) > 0 { - genericRequestsCopy := make([]models.Payload, len(genericRequests)) - genericResponseCopy := make([]models.Payload, len(genericResponses)) - copy(genericResponseCopy, genericResponses) - copy(genericRequestsCopy, genericRequests) - go func(reqs []models.Payload, resps []models.Payload) { - metadata := make(map[string]string) - metadata["type"] = "config" - metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) - // Save the mock - mocks <- &models.Mock{ - Version: models.GetVersion(), - Name: "mocks", - Kind: models.GENERIC, - Spec: models.MockSpec{ - GenericRequests: reqs, - GenericResponses: resps, - ReqTimestampMock: reqTimestampMock, - ResTimestampMock: resTimestampMock, - Metadata: metadata, - }, - } - - }(genericRequestsCopy, genericResponseCopy) - genericRequests = []models.Payload{} - genericResponses = []models.Payload{} - } - - bufStr := string(buffer) - buffDataType := models.String - if !util.IsASCII(string(buffer)) { - bufStr = util.EncodeBase64(buffer) - buffDataType = "binary" - } - - if bufStr != "" { - genericRequests = append(genericRequests, models.Payload{ - Origin: models.FromClient, - Message: []models.OutputBinary{ - { - Type: buffDataType, - Data: bufStr, - }, - }, - }) - } - - prevChunkWasReq = true - case buffer := <-destBuffChan: - if prevChunkWasReq { - // store the request timestamp - reqTimestampMock = time.Now() - } - // Write the response message to the client - _, err := clientConn.Write(buffer) - if err != nil { - utils.LogError(logger, err, "failed to write response message to the client") - return err - } - - bufStr := string(buffer) - buffDataType := models.String - if !util.IsASCII(string(buffer)) { - bufStr = base64.StdEncoding.EncodeToString(buffer) - buffDataType = "binary" - } - - if bufStr != "" { - genericResponses = append(genericResponses, models.Payload{ - Origin: models.FromServer, - Message: []models.OutputBinary{ - { - Type: buffDataType, - Data: bufStr, - }, - }, - }) - } - - resTimestampMock = time.Now() - - logger.Debug("the iteration for the generic response ends with no of genericReqs:" + strconv.Itoa(len(genericRequests)) + " and genericResps: " + strconv.Itoa(len(genericResponses))) - prevChunkWasReq = false - case err := <-errChan: - if err == io.EOF { - return nil - } - return err - } - } -} diff --git a/keploy/pkg/core/proxy/integrations/generic/generic.go b/keploy/pkg/core/proxy/integrations/generic/generic.go deleted file mode 100755 index 1d56a62..0000000 --- a/keploy/pkg/core/proxy/integrations/generic/generic.go +++ /dev/null @@ -1,69 +0,0 @@ -//go:build linux - -package generic - -import ( - "context" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func init() { - integrations.Register(integrations.GENERIC, &integrations.Parsers{ - Initializer: New, - Priority: 100, - }) -} - -type Generic struct { - logger *zap.Logger -} - -func New(logger *zap.Logger) integrations.Integrations { - return &Generic{ - logger: logger, - } -} - -func (g *Generic) MatchType(_ context.Context, _ []byte) bool { - // generic is checked explicitly in the proxy - return false -} - -func (g *Generic) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - logger := g.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial generic message") - return err - } - - err = encodeGeneric(ctx, logger, reqBuf, src, dst, mocks, opts) - if err != nil { - utils.LogError(logger, err, "failed to encode the generic message into the yaml") - return err - } - return nil -} - -func (g *Generic) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { - logger := g.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial generic message") - return err - } - - err = decodeGeneric(ctx, logger, reqBuf, src, dstCfg, mockDb, opts) - if err != nil { - utils.LogError(logger, err, "failed to decode the generic message") - return err - } - return nil -} diff --git a/keploy/pkg/core/proxy/integrations/generic/match.go b/keploy/pkg/core/proxy/integrations/generic/match.go deleted file mode 100755 index 6fd0f1f..0000000 --- a/keploy/pkg/core/proxy/integrations/generic/match.go +++ /dev/null @@ -1,155 +0,0 @@ -//go:build linux - -package generic - -import ( - "context" - "encoding/base64" - "fmt" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.uber.org/zap" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.keploy.io/server/v2/pkg/models" -) - -// fuzzyMatch performs a fuzzy matching algorithm to find the best matching mock for the given request. -// It takes a context, a request buffer, and a mock database as input parameters. -// The function iterates over the mocks in the database and applies the fuzzy matching algorithm to find the best match. -// If a match is found, it returns the corresponding response mock and a boolean value indicating success. -// If no match is found, it returns false and a nil response. -// If an error occurs during the matching process, it returns an error. -func fuzzyMatch(ctx context.Context, logger *zap.Logger, reqBuff [][]byte, mockDb integrations.MockMemDb) (bool, []models.Payload, error) { - for { - select { - case <-ctx.Done(): - return false, nil, ctx.Err() - default: - mocks, err := mockDb.GetUnFilteredMocks() - if err != nil { - return false, nil, fmt.Errorf("error while getting unfiltered mocks %v", err) - } - - var filteredMocks []*models.Mock - var unfilteredMocks []*models.Mock - - for _, mock := range mocks { - if mock.Kind != "Generic" { - continue - } - if mock.TestModeInfo.IsFiltered { - filteredMocks = append(filteredMocks, mock) - } else { - unfilteredMocks = append(unfilteredMocks, mock) - } - } - - logger.Debug("List of mocks in the database", zap.Any("Filtered Mocks", len(filteredMocks)), zap.Any("Unfiltered Mocks", len(unfilteredMocks))) - for i, mock := range filteredMocks { - logger.Debug("Filtered Mocks", zap.Any(fmt.Sprintf("Mock[%d]", i), mock.Name), zap.Any("sortOrder", mock.TestModeInfo.SortOrder)) - } - for i, mock := range unfilteredMocks { - logger.Debug("Unfiltered Mocks", zap.Any(fmt.Sprintf("Mock[%d]", i), mock.Name), zap.Any("sortOrder", mock.TestModeInfo.SortOrder)) - } - - index := findExactMatch(filteredMocks, reqBuff) - - if index == -1 { - index = findBinaryMatch(filteredMocks, reqBuff, 0.9) - } - - if index != -1 { - responseMock := make([]models.Payload, len(filteredMocks[index].Spec.GenericResponses)) - copy(responseMock, filteredMocks[index].Spec.GenericResponses) - originalFilteredMock := *filteredMocks[index] - filteredMocks[index].TestModeInfo.IsFiltered = false - filteredMocks[index].TestModeInfo.SortOrder = pkg.GetNextSortNum() - isUpdated := mockDb.UpdateUnFilteredMock(&originalFilteredMock, filteredMocks[index]) - if !isUpdated { - continue - } - logger.Debug("Filtered mock found for generic request", zap.Any("Mock", filteredMocks[index].Name), zap.Any("sortOrder", filteredMocks[index].TestModeInfo.SortOrder)) - return true, responseMock, nil - } - - index = findExactMatch(unfilteredMocks, reqBuff) - - if index == -1 { - index = findBinaryMatch(unfilteredMocks, reqBuff, 0.4) - } - if index != -1 { - responseMock := make([]models.Payload, len(unfilteredMocks[index].Spec.GenericResponses)) - copy(responseMock, unfilteredMocks[index].Spec.GenericResponses) - originalFilteredMock := *unfilteredMocks[index] - unfilteredMocks[index].TestModeInfo.IsFiltered = false - unfilteredMocks[index].TestModeInfo.SortOrder = pkg.GetNextSortNum() - isUpdated := mockDb.UpdateUnFilteredMock(&originalFilteredMock, unfilteredMocks[index]) - if !isUpdated { - continue - } - logger.Debug("Unfiltered mock found for generic request", zap.Any("Mock", unfilteredMocks[index].Name), zap.Any("sortOrder", unfilteredMocks[index].TestModeInfo.SortOrder)) - return true, responseMock, nil - } - return false, nil, nil - } - } -} - -// TODO: need to generalize this function for different types of integrations. -func findBinaryMatch(tcsMocks []*models.Mock, reqBuffs [][]byte, mxSim float64) int { - // TODO: need find a proper similarity index to set a benchmark for matching or need to find another way to do approximate matching - mxIdx := -1 - for idx, mock := range tcsMocks { - if len(mock.Spec.GenericRequests) == len(reqBuffs) { - for requestIndex, reqBuff := range reqBuffs { - _ = base64.StdEncoding.EncodeToString(reqBuff) - encoded, _ := util.DecodeBase64(mock.Spec.GenericRequests[requestIndex].Message[0].Data) - - similarity := fuzzyCheck(encoded, reqBuff) - - if mxSim < similarity { - mxSim = similarity - mxIdx = idx - } - } - } - } - return mxIdx -} - -func fuzzyCheck(encoded, reqBuf []byte) float64 { - k := util.AdaptiveK(len(reqBuf), 3, 8, 5) - shingles1 := util.CreateShingles(encoded, k) - shingles2 := util.CreateShingles(reqBuf, k) - similarity := util.JaccardSimilarity(shingles1, shingles2) - return similarity -} - -func findExactMatch(tcsMocks []*models.Mock, reqBuffs [][]byte) int { - for idx, mock := range tcsMocks { - if len(mock.Spec.GenericRequests) == len(reqBuffs) { - matched := true // Flag to track if all requests match - - for requestIndex, reqBuff := range reqBuffs { - - bufStr := string(reqBuff) - if !util.IsASCII(string(reqBuff)) { - bufStr = util.EncodeBase64(reqBuff) - } - - // Compare the encoded data - if mock.Spec.GenericRequests[requestIndex].Message[0].Data != bufStr { - matched = false - break // Exit the loop if any request doesn't match - } - } - - if matched { - return idx - } - } - } - return -1 -} diff --git a/keploy/pkg/core/proxy/integrations/grpc/decode.go b/keploy/pkg/core/proxy/integrations/grpc/decode.go deleted file mode 100644 index 20ada3a..0000000 --- a/keploy/pkg/core/proxy/integrations/grpc/decode.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build linux - -// Package grpc provides functionality for integrating with gRPC outgoing calls. -package grpc - -import ( - "context" - "io" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "golang.org/x/net/http2" -) - -func decodeGrpc(ctx context.Context, logger *zap.Logger, _ []byte, clientConn net.Conn, _ *models.ConditionalDstCfg, mockDb integrations.MockMemDb, _ models.OutgoingOptions) error { - framer := http2.NewFramer(clientConn, clientConn) - srv := NewTranscoder(logger, framer, mockDb) - // fake server in the test mode - err := srv.ListenAndServe(ctx) - if err != nil { - if err == io.EOF { - // EOF is expected when the server closes the connection. - logger.Debug("EOF while serving grpc request") - return nil - } - utils.LogError(logger, nil, "could not serve grpc request") - return err - } - return nil -} diff --git a/keploy/pkg/core/proxy/integrations/grpc/encode.go b/keploy/pkg/core/proxy/integrations/grpc/encode.go deleted file mode 100644 index f541636..0000000 --- a/keploy/pkg/core/proxy/integrations/grpc/encode.go +++ /dev/null @@ -1,86 +0,0 @@ -//go:build linux - -package grpc - -import ( - "context" - "io" - "net" - - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "golang.org/x/sync/errgroup" -) - -func encodeGrpc(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, _ models.OutgoingOptions) error { - - // Send the client preface to the server. This should be the first thing sent from the client. - _, err := destConn.Write(reqBuf) - if err != nil { - utils.LogError(logger, err, "Could not write preface onto the destination server") - return err - } - - if ctx.Err() != nil { - return ctx.Err() - } - - streamInfoCollection := NewStreamInfoCollection() - reqFromClient := true - - serverSideDecoder := NewDecoder() - - // get the error group from the context - g := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - errCh := make(chan error, 2) - defer close(errCh) - - // Route requests from the client to the server. - g.Go(func() error { - defer pUtil.Recover(logger, clientConn, destConn) - err := transferFrame(ctx, logger, destConn, clientConn, streamInfoCollection, reqFromClient, serverSideDecoder, mocks) - if err != nil { - // check for EOF error - if err == io.EOF { - logger.Debug("EOF error received from client. Closing conn") - return nil - } - utils.LogError(logger, err, "failed to transfer frame from client to server") - if ctx.Err() != nil { //to avoid sending error to the closed channel if the context is cancelled - return ctx.Err() - } - errCh <- err - } - return nil - }) - - // Route response from the server to the client. - clientSideDecoder := NewDecoder() - g.Go(func() error { - defer pUtil.Recover(logger, clientConn, destConn) - err := transferFrame(ctx, logger, clientConn, destConn, streamInfoCollection, !reqFromClient, clientSideDecoder, mocks) - if err != nil { - utils.LogError(logger, err, "failed to transfer frame from server to client") - if ctx.Err() != nil { //to avoid sending error to the closed channel if the context is cancelled - return ctx.Err() - } - errCh <- err - } - return nil - }) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - return nil - } - return err - } - // This would practically be an infinite loop, unless the client closes the grpc conn - // during the runtime of the application. - // A grpc server/client terminating after some time maybe intentional. -} diff --git a/keploy/pkg/core/proxy/integrations/grpc/frame.go b/keploy/pkg/core/proxy/integrations/grpc/frame.go deleted file mode 100644 index 65af4f8..0000000 --- a/keploy/pkg/core/proxy/integrations/grpc/frame.go +++ /dev/null @@ -1,215 +0,0 @@ -//go:build linux - -package grpc - -import ( - "context" - "fmt" - "io" - "net" - "strings" - "time" - - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" - "golang.org/x/net/http2" - "golang.org/x/net/http2/hpack" -) - -// transferFrame reads one frame from rhs and writes it to lhs. -func transferFrame(ctx context.Context, _ *zap.Logger, lhs net.Conn, rhs net.Conn, sic *StreamInfoCollection, reqFromClient bool, decoder *hpack.Decoder, mocks chan<- *models.Mock) error { - respFromServer := !reqFromClient - framer := http2.NewFramer(lhs, rhs) - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - frame, err := framer.ReadFrame() - if err != nil { - if err == io.EOF { - return err - } - return fmt.Errorf("error reading frame %v", err) - } - - switch frame := frame.(type) { - case *http2.SettingsFrame: - settingsFrame := frame - if settingsFrame.IsAck() { - // Transfer Ack. - if err := framer.WriteSettingsAck(); err != nil { - return fmt.Errorf("could not write ack for settings frame: %v", err) - } - } else { - var settingsCollection []http2.Setting - err = settingsFrame.ForeachSetting(func(setting http2.Setting) error { - settingsCollection = append(settingsCollection, setting) - return nil - }) - if err != nil { - return fmt.Errorf("could not read settings from settings frame: %v", err) - } - - if err := framer.WriteSettings(settingsCollection...); err != nil { - return fmt.Errorf("could not write settings fraame: %v", err) - } - } - case *http2.HeadersFrame: - headersFrame := frame - streamID := headersFrame.StreamID - err := framer.WriteHeaders(http2.HeadersFrameParam{ - StreamID: streamID, - BlockFragment: headersFrame.HeaderBlockFragment(), - EndStream: headersFrame.StreamEnded(), - EndHeaders: headersFrame.HeadersEnded(), - PadLength: 0, - Priority: headersFrame.Priority, - }) - if err != nil { - return fmt.Errorf("could not write headers frame: %v", err) - } - pseudoHeaders, ordinaryHeaders, err := extractHeaders(headersFrame, decoder) - if err != nil { - return fmt.Errorf("could not extract headers from frame: %v", err) - } - - if reqFromClient { - sic.AddHeadersForRequest(streamID, pseudoHeaders, true) - sic.AddHeadersForRequest(streamID, ordinaryHeaders, false) - - } else if respFromServer { - if headersFrame.StreamEnded() { - // Trailers — filter grpc-* as trailer, rest as normal headers - pseudoNormal, pseudoTrailer := splitGrpcTrailerHeaders(pseudoHeaders) - ordinaryNormal, ordinaryTrailer := splitGrpcTrailerHeaders(ordinaryHeaders) - - // Add "normal" parts as headers (still appears in trailers, but your system might need this distinction) - sic.AddHeadersForResponse(streamID, pseudoNormal, true, false) - sic.AddHeadersForResponse(streamID, ordinaryNormal, false, false) - - // Add "grpc-" keys as actual trailers - sic.AddHeadersForResponse(streamID, pseudoTrailer, true, true) - sic.AddHeadersForResponse(streamID, ordinaryTrailer, false, true) - - } else { - // Just regular headers - sic.AddHeadersForResponse(streamID, pseudoHeaders, true, false) - sic.AddHeadersForResponse(streamID, ordinaryHeaders, false, false) - } - } - // The trailers frame has been received. The stream has been closed by the server. - // Capture the mock and clear the map, as the stream ID can be reused by client. - if respFromServer && headersFrame.StreamEnded() { - sic.PersistMockForStream(ctx, streamID, mocks) - sic.ResetStream(streamID) - } - - case *http2.DataFrame: - dataFrame := frame - err := framer.WriteData(dataFrame.StreamID, dataFrame.StreamEnded(), dataFrame.Data()) - if err != nil { - return fmt.Errorf("could not write data frame: %v", err) - } - if reqFromClient { - // Capturing the request timestamp - sic.ReqTimestampMock = time.Now() - - sic.AddPayloadForRequest(dataFrame.StreamID, dataFrame.Data()) - } else if respFromServer { - // Capturing the response timestamp - sic.ResTimestampMock = time.Now() - - sic.AddPayloadForResponse(dataFrame.StreamID, dataFrame.Data()) - } - case *http2.PingFrame: - pingFrame := frame - err := framer.WritePing(pingFrame.IsAck(), pingFrame.Data) - if err != nil { - return fmt.Errorf("could not write ACK for ping: %v", err) - } - case *http2.WindowUpdateFrame: - windowUpdateFrame := frame - err := framer.WriteWindowUpdate(windowUpdateFrame.StreamID, windowUpdateFrame.Increment) - if err != nil { - return fmt.Errorf("could not write window tools frame: %v", err) - } - case *http2.ContinuationFrame: - continuationFrame := frame - err := framer.WriteContinuation(continuationFrame.StreamID, continuationFrame.HeadersEnded(), - continuationFrame.HeaderBlockFragment()) - if err != nil { - return fmt.Errorf("could not write continuation frame: %v", err) - } - case *http2.PriorityFrame: - priorityFrame := frame - err := framer.WritePriority(priorityFrame.StreamID, priorityFrame.PriorityParam) - if err != nil { - return fmt.Errorf("could not write priority frame: %v", err) - } - case *http2.RSTStreamFrame: - rstStreamFrame := frame - err := framer.WriteRSTStream(rstStreamFrame.StreamID, rstStreamFrame.ErrCode) - if err != nil { - return fmt.Errorf("could not write reset stream frame: %v", err) - } - case *http2.GoAwayFrame: - goAwayFrame := frame - err := framer.WriteGoAway(goAwayFrame.StreamID, goAwayFrame.ErrCode, goAwayFrame.DebugData()) - if err != nil { - return fmt.Errorf("could not write GoAway frame: %v", err) - } - case *http2.PushPromiseFrame: - pushPromiseFrame := frame - err := framer.WritePushPromise(http2.PushPromiseParam{ - StreamID: pushPromiseFrame.StreamID, - PromiseID: pushPromiseFrame.PromiseID, - BlockFragment: pushPromiseFrame.HeaderBlockFragment(), - EndHeaders: pushPromiseFrame.HeadersEnded(), - PadLength: 0, - }) - if err != nil { - return fmt.Errorf("could not write PushPromise frame: %v", err) - } - } - } - } -} - -func splitGrpcTrailerHeaders(headers map[string]string) (normal map[string]string, trailer map[string]string) { - normal = make(map[string]string) - trailer = make(map[string]string) - for k, v := range headers { - if strings.HasPrefix(k, "grpc-") { - trailer[k] = v - } else { - normal[k] = v - } - } - return -} - -// constants for dynamic table size -const ( - KmaxDynamicTableSize = 4096 -) - -func extractHeaders(frame *http2.HeadersFrame, decoder *hpack.Decoder) (pseudoHeaders, ordinaryHeaders map[string]string, err error) { - hf, err := decoder.DecodeFull(frame.HeaderBlockFragment()) - if err != nil { - return nil, nil, fmt.Errorf("could not decode headers: %v", err) - } - - pseudoHeaders = make(map[string]string) - ordinaryHeaders = make(map[string]string) - - for _, header := range hf { - if header.IsPseudo() { - pseudoHeaders[header.Name] = header.Value - } else { - ordinaryHeaders[header.Name] = header.Value - } - } - - return pseudoHeaders, ordinaryHeaders, nil -} diff --git a/keploy/pkg/core/proxy/integrations/grpc/grpc.go b/keploy/pkg/core/proxy/integrations/grpc/grpc.go deleted file mode 100644 index 0c545c4..0000000 --- a/keploy/pkg/core/proxy/integrations/grpc/grpc.go +++ /dev/null @@ -1,71 +0,0 @@ -//go:build linux - -package grpc - -import ( - "bytes" - "context" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// func init() { -// integrations.Register(integrations.GRPC, &integrations.Parsers{ -// Initializer: New, -// Priority: 100, -// }) -// } - -type Grpc struct { - logger *zap.Logger -} - -func New(logger *zap.Logger) integrations.Integrations { - return &Grpc{ - logger: logger, - } -} - -// MatchType function determines if the outgoing network call is gRPC by comparing the -// message format with that of an gRPC text message. -func (g *Grpc) MatchType(_ context.Context, reqBuf []byte) bool { - return bytes.HasPrefix(reqBuf[:], []byte("PRI * HTTP/2")) -} - -func (g *Grpc) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - logger := g.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial grpc message") - return err - } - - err = encodeGrpc(ctx, logger, reqBuf, src, dst, mocks, opts) - if err != nil { - utils.LogError(logger, err, "failed to encode the grpc message into the yaml") - return err - } - return nil -} - -func (g *Grpc) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { - logger := g.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial grpc message") - return err - } - - err = decodeGrpc(ctx, logger, reqBuf, src, dstCfg, mockDb, opts) - if err != nil { - utils.LogError(logger, err, "failed to decode the grpc message from the yaml") - return err - } - return nil -} diff --git a/keploy/pkg/core/proxy/integrations/grpc/match.go b/keploy/pkg/core/proxy/integrations/grpc/match.go deleted file mode 100644 index 4ece5b9..0000000 --- a/keploy/pkg/core/proxy/integrations/grpc/match.go +++ /dev/null @@ -1,227 +0,0 @@ -//go:build linux - -package grpc - -import ( - "context" - "fmt" - - "github.com/agnivade/levenshtein" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.uber.org/zap" - - "go.keploy.io/server/v2/pkg/models" -) - -func FilterMocksRelatedToGrpc(mocks []*models.Mock) []*models.Mock { - var res []*models.Mock - for _, mock := range mocks { - if mock != nil && mock.Kind == models.GRPC_EXPORT && mock.Spec.GRPCReq != nil && mock.Spec.GRPCResp != nil { - res = append(res, mock) - } - } - return res -} - -func FilterMocksBasedOnGrpcRequest(ctx context.Context, logger *zap.Logger, grpcReq models.GrpcReq, mockDb integrations.MockMemDb) (*models.Mock, error) { - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - mocks, err := mockDb.GetFilteredMocks() - if err != nil { - return nil, fmt.Errorf("error while getting tsc mocks %v", err) - } - - var matchedMock *models.Mock - var isMatched bool - - grpcMocks := FilterMocksRelatedToGrpc(mocks) - - if len(grpcMocks) == 0 { - logger.Debug("No grpc mocks found in the db") - return nil, nil - } - - logger.Debug("Here are the grpc mocks in the db", zap.Int("len", len(grpcMocks)), zap.Any("grpcMocks", grpcMocks)) - - schemaMatched, err := schemaMatch(ctx, grpcReq, grpcMocks) - if err != nil { - return nil, err - } - - if len(schemaMatched) == 0 { - logger.Debug("No mock found with schema match") - return nil, nil - } - - logger.Debug("Here are the grpc mocks with schema match", zap.Int("len", len(schemaMatched)), zap.Any("schemaMatched", schemaMatched)) - - // Exact body Match - ok, matchedMock := exactBodyMatch(grpcReq.Body, schemaMatched) - if ok { - logger.Debug("Exact body match found", zap.Any("matchedMock", matchedMock)) - if !mockDb.DeleteFilteredMock(*matchedMock) { - continue - } - return matchedMock, nil - } - - // apply fuzzy match for body with schemaMatched mocks - - logger.Debug("Performing fuzzy match for decoded data in body") - // Perform fuzzy match on the request - isMatched, bestMatch := fuzzyMatch(schemaMatched, grpcReq.Body.DecodedData) - if isMatched { - if !mockDb.DeleteFilteredMock(*bestMatch) { - continue - } - return bestMatch, nil - } - return nil, nil - } - } -} - -func schemaMatch(ctx context.Context, req models.GrpcReq, mocks []*models.Mock) ([]*models.Mock, error) { - var schemaMatched []*models.Mock - - for _, mock := range mocks { - if ctx.Err() != nil { - return nil, ctx.Err() - } - mockReq := mock.Spec.GRPCReq - - // the pseudo headers should defintely match. - if !compareMap(mockReq.Headers.PseudoHeaders, req.Headers.PseudoHeaders) { - continue - } - - // the ordinary headers keys should match. - if !compareMapKeys(mockReq.Headers.OrdinaryHeaders, req.Headers.OrdinaryHeaders) { - continue - } - - // the content type should match. - if mockReq.Headers.OrdinaryHeaders["content-type"] != req.Headers.OrdinaryHeaders["content-type"] { - continue - } - - // additionally check for the compression flag here only - if mockReq.Body.CompressionFlag != req.Body.CompressionFlag { - continue - } - - schemaMatched = append(schemaMatched, mock) - } - - return schemaMatched, nil -} - -// Check if two maps have the same keys -func compareMapKeys(m1, m2 map[string]string) bool { - if len(m1) > len(m2) { - for k := range m2 { - if _, ok := m1[k]; !ok { - return false - } - } - } else { - for k := range m1 { - if _, ok := m2[k]; !ok { - return false - } - } - } - return true -} - -// Check if two maps are identical -func compareMap(m1, m2 map[string]string) bool { - if len(m1) != len(m2) { - return false - } - for k, v := range m1 { - if v2, ok := m2[k]; !ok || v != v2 { - return false - } - } - return true -} - -func exactBodyMatch(body models.GrpcLengthPrefixedMessage, schemaMatched []*models.Mock) (bool, *models.Mock) { - for _, mock := range schemaMatched { - if mock.Spec.GRPCReq.Body.MessageLength == body.MessageLength && mock.Spec.GRPCReq.Body.DecodedData == body.DecodedData { - return true, mock - } - } - return false, nil -} - -// Fuzzy match helper for string matching -func findStringMatch(req string, mockStrings []string) int { - minDist := int(^uint(0) >> 1) - bestMatch := -1 - for idx, mock := range mockStrings { - if !util.IsASCII(mock) { - continue - } - dist := levenshtein.ComputeDistance(req, mock) - if dist == 0 { - return 0 - } - if dist < minDist { - minDist = dist - bestMatch = idx - } - } - return bestMatch -} - -// TODO: generalize the function to work with any type of integration -func findBinaryMatch(mocks []*models.Mock, reqBuff []byte) int { - - mxSim := -1.0 - mxIdx := -1 - // find the fuzzy hash of the mocks - for idx, mock := range mocks { - encoded := []byte(mock.Spec.GRPCReq.Body.DecodedData) - k := util.AdaptiveK(len(reqBuff), 3, 8, 5) - shingles1 := util.CreateShingles(encoded, k) - shingles2 := util.CreateShingles(reqBuff, k) - similarity := util.JaccardSimilarity(shingles1, shingles2) - - // log.Debugf("Jaccard Similarity:%f\n", similarity) - - if mxSim < similarity { - mxSim = similarity - mxIdx = idx - } - } - return mxIdx -} - -// fuzzy match on the request -func fuzzyMatch(tcsMocks []*models.Mock, reqBuff string) (bool, *models.Mock) { - - // String-based fuzzy matching - mockStrings := make([]string, len(tcsMocks)) - for i := range tcsMocks { - mockStrings[i] = tcsMocks[i].Spec.GRPCReq.Body.DecodedData - } - - if util.IsASCII(reqBuff) { - idx := findStringMatch(string(reqBuff), mockStrings) - if idx != -1 { - return true, tcsMocks[idx] - } - } - - idx := findBinaryMatch(tcsMocks, []byte(reqBuff)) - if idx != -1 { - return true, tcsMocks[idx] - } - return false, nil -} diff --git a/keploy/pkg/core/proxy/integrations/grpc/stream.go b/keploy/pkg/core/proxy/integrations/grpc/stream.go deleted file mode 100644 index e98f7b1..0000000 --- a/keploy/pkg/core/proxy/integrations/grpc/stream.go +++ /dev/null @@ -1,160 +0,0 @@ -//go:build linux - -package grpc - -import ( - "context" - "encoding/binary" - "sync" - "time" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/models" -) - -// StreamInfoCollection is a thread-safe data structure to store all communications -// that happen in a stream for grpc. This includes the headers and data frame for the -// request and response. -type StreamInfoCollection struct { - mutex sync.Mutex - StreamInfo map[uint32]models.GrpcStream - ReqTimestampMock time.Time - ResTimestampMock time.Time -} - -func NewStreamInfoCollection() *StreamInfoCollection { - return &StreamInfoCollection{ - StreamInfo: make(map[uint32]models.GrpcStream), - } -} - -func (sic *StreamInfoCollection) InitialiseStream(streamID uint32) { - sic.mutex.Lock() - defer sic.mutex.Unlock() - - _, ok := sic.StreamInfo[streamID] - if !ok { - sic.StreamInfo[streamID] = models.NewGrpcStream(streamID) - } -} - -func (sic *StreamInfoCollection) AddHeadersForRequest(streamID uint32, headers map[string]string, isPseudo bool) { - // Initialise the stream before acquiring the lock for yourself. - sic.InitialiseStream(streamID) - sic.mutex.Lock() - defer sic.mutex.Unlock() - - for key, value := range headers { - if isPseudo { - sic.StreamInfo[streamID].GrpcReq.Headers.PseudoHeaders[key] = value - } else { - sic.StreamInfo[streamID].GrpcReq.Headers.OrdinaryHeaders[key] = value - } - } -} - -func (sic *StreamInfoCollection) AddHeadersForResponse(streamID uint32, headers map[string]string, isPseudo, isTrailer bool) { - // Initialise the stream before acquiring the lock for yourself. - sic.InitialiseStream(streamID) - sic.mutex.Lock() - defer sic.mutex.Unlock() - - for key, value := range headers { - if isTrailer { - if isPseudo { - sic.StreamInfo[streamID].GrpcResp.Trailers.PseudoHeaders[key] = value - } else { - sic.StreamInfo[streamID].GrpcResp.Trailers.OrdinaryHeaders[key] = value - } - } else { - if isPseudo { - sic.StreamInfo[streamID].GrpcResp.Headers.PseudoHeaders[key] = value - } else { - sic.StreamInfo[streamID].GrpcResp.Headers.OrdinaryHeaders[key] = value - } - } - } -} - -// AddPayloadForRequest adds the DATA frame to the stream. -// A data frame always appears after at least one header frame. Hence, we implicitly -// assume that the stream has been initialised. -func (sic *StreamInfoCollection) AddPayloadForRequest(streamID uint32, payload []byte) { - sic.mutex.Lock() - defer sic.mutex.Unlock() - - info := sic.StreamInfo[streamID] - - info.ReqRawData = append(info.ReqRawData, payload...) - - if !info.ReqPrefixParsed && len(info.ReqRawData) >= 5 { - info.ReqExpectedLength = binary.BigEndian.Uint32(info.ReqRawData[1:5]) - info.ReqPrefixParsed = true - } - - totalLen := 5 + int(info.ReqExpectedLength) - if info.ReqPrefixParsed && len(info.ReqRawData) >= totalLen { - info.GrpcReq.Body = pkg.CreateLengthPrefixedMessageFromPayload(info.ReqRawData[:totalLen]) - } - - sic.StreamInfo[streamID] = info -} - -// AddPayloadForResponse adds the DATA frame to the stream. -// A data frame always appears after at least one header frame. Hence, we implicitly -// assume that the stream has been initialised. -func (sic *StreamInfoCollection) AddPayloadForResponse(streamID uint32, payload []byte) { - sic.mutex.Lock() - defer sic.mutex.Unlock() - - info := sic.StreamInfo[streamID] - - info.RespRawData = append(info.RespRawData, payload...) - - if !info.RespPrefixParsed && len(info.RespRawData) >= 5 { - info.RespExpectedLength = binary.BigEndian.Uint32(info.RespRawData[1:5]) - info.RespPrefixParsed = true - } - - totalLen := 5 + int(info.RespExpectedLength) - if info.RespPrefixParsed && len(info.RespRawData) >= totalLen { - info.GrpcResp.Body = pkg.CreateLengthPrefixedMessageFromPayload(info.RespRawData[:totalLen]) - } - - sic.StreamInfo[streamID] = info -} -func (sic *StreamInfoCollection) PersistMockForStream(ctx context.Context, streamID uint32, mocks chan<- *models.Mock) { - sic.mutex.Lock() - defer sic.mutex.Unlock() - grpcReq := sic.StreamInfo[streamID].GrpcReq - grpcResp := sic.StreamInfo[streamID].GrpcResp - metadata := make(map[string]string) - metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) - // save the mock - mocks <- &models.Mock{ - Version: models.GetVersion(), - Name: "mocks", - Kind: models.GRPC_EXPORT, - Spec: models.MockSpec{ - Metadata: metadata, - GRPCReq: &grpcReq, - GRPCResp: &grpcResp, - ReqTimestampMock: sic.ReqTimestampMock, - ResTimestampMock: sic.ResTimestampMock, - }, - } -} - -func (sic *StreamInfoCollection) FetchRequestForStream(streamID uint32) models.GrpcReq { - sic.mutex.Lock() - defer sic.mutex.Unlock() - - return sic.StreamInfo[streamID].GrpcReq -} - -func (sic *StreamInfoCollection) ResetStream(streamID uint32) { - sic.mutex.Lock() - defer sic.mutex.Unlock() - - delete(sic.StreamInfo, streamID) -} diff --git a/keploy/pkg/core/proxy/integrations/grpc/transcoder.go b/keploy/pkg/core/proxy/integrations/grpc/transcoder.go deleted file mode 100644 index 300570f..0000000 --- a/keploy/pkg/core/proxy/integrations/grpc/transcoder.go +++ /dev/null @@ -1,379 +0,0 @@ -//go:build linux - -package grpc - -import ( - "bytes" - "context" - "fmt" - "io" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/utils" - - "go.uber.org/zap" - "golang.org/x/net/http2" - "golang.org/x/net/http2/hpack" -) - -type Transcoder struct { - sic *StreamInfoCollection - mockDb integrations.MockMemDb - logger *zap.Logger - framer *http2.Framer - decoder *hpack.Decoder -} - -func NewTranscoder(logger *zap.Logger, framer *http2.Framer, mockDb integrations.MockMemDb) *Transcoder { - return &Transcoder{ - logger: logger, - framer: framer, - mockDb: mockDb, - sic: NewStreamInfoCollection(), - decoder: NewDecoder(), - } -} - -// TODO: (add reason for not using the default value i.e. 16384 // 16KB) -const MAX_FRAME_SIZE = 8192 // 8KB - -func (srv *Transcoder) WriteInitialSettingsFrame() error { - var settings []http2.Setting - // TODO : Get Settings from config file. - settings = append(settings, http2.Setting{ - ID: http2.SettingMaxFrameSize, - Val: MAX_FRAME_SIZE, - }) - return srv.framer.WriteSettings(settings...) -} - -func (srv *Transcoder) ProcessPingFrame(pingFrame *http2.PingFrame) error { - if pingFrame.IsAck() { - // An endpoint MUST NOT respond to PING frames containing this flag. - return nil - } - - if pingFrame.StreamID != 0 { - // "PING frames are not associated with any individual - // stream. If a PING frame is received with a stream - // identifier field value other than 0x0, the recipient MUST - // respond with a conn error (Section 5.4.1) of type - // PROTOCOL_ERROR." - utils.LogError(srv.logger, nil, "As per HTTP/2 spec, stream ID for PING frame should be zero.", zap.Any("stream_id", pingFrame.StreamID)) - return http2.ConnectionError(http2.ErrCodeProtocol) - } - - // Write the ACK for the PING request. - return srv.framer.WritePing(true, pingFrame.Data) - -} - -func (srv *Transcoder) ProcessDataFrame(ctx context.Context, dataFrame *http2.DataFrame) error { - id := dataFrame.Header().StreamID - // DATA frame must be associated with a stream - if id == 0 { - utils.LogError(srv.logger, nil, "As per HTTP/2 spec, DATA frame must be associated with a stream.", zap.Any("stream_id", id)) - return http2.ConnectionError(http2.ErrCodeProtocol) - } - srv.sic.AddPayloadForRequest(id, dataFrame.Data()) - - if dataFrame.StreamEnded() { - defer srv.sic.ResetStream(dataFrame.StreamID) - } - - grpcReq := srv.sic.FetchRequestForStream(id) - - srv.logger.Debug("Getting mock for request from the mock database", zap.Any("request", grpcReq)) - - // Fetch all the mocks. We can't assume that the grpc calls are made in a certain order. - mock, err := FilterMocksBasedOnGrpcRequest(ctx, srv.logger, grpcReq, srv.mockDb) - if err != nil { - return fmt.Errorf("failed match mocks: %v", err) - } - if mock == nil { - return fmt.Errorf("failed to mock the output for unrecorded outgoing grpc call") - } - - srv.logger.Debug("Found a mock for the request", zap.Any("mock", mock)) - - grpcMockResp := mock.Spec.GRPCResp - - // First, send the headers frame. - buf := new(bytes.Buffer) - encoder := hpack.NewEncoder(buf) - - // The pseudo headers should be written before ordinary ones. - for key, value := range grpcMockResp.Headers.PseudoHeaders { - err := encoder.WriteField(hpack.HeaderField{ - Name: key, - Value: value, - }) - if err != nil { - utils.LogError(srv.logger, err, "could not encode pseudo header", zap.Any("key", key), zap.Any("value", value)) - return err - } - } - for key, value := range grpcMockResp.Headers.OrdinaryHeaders { - err := encoder.WriteField(hpack.HeaderField{ - Name: key, - Value: value, - }) - if err != nil { - utils.LogError(srv.logger, err, "could not encode ordinary header", zap.Any("key", key), zap.Any("value", value)) - return err - } - } - - // The headers are prepared. Write the frame. - srv.logger.Debug("Writing the first set of headers in a new HEADER frame.") - err = srv.framer.WriteHeaders(http2.HeadersFrameParam{ - StreamID: id, - BlockFragment: buf.Bytes(), - EndStream: false, - EndHeaders: true, - }) - if err != nil { - utils.LogError(srv.logger, err, "could not write the first set of headers onto client") - return err - } - - payload, err := pkg.CreatePayloadFromLengthPrefixedMessage(grpcMockResp.Body) - if err != nil { - utils.LogError(srv.logger, err, "could not create grpc payload from mocks") - return err - } - - srv.logger.Debug("Writing the payload in a DATA frame", zap.Int("payload length", len(payload))) - // Write the DATA frame with the payload. - err = srv.WriteData(ctx, id, payload) - if err != nil { - utils.LogError(srv.logger, err, "could not write the data frame onto the client") - return err - } - // Reset the buffer and start with a new encoding. - buf = new(bytes.Buffer) - encoder = hpack.NewEncoder(buf) - - srv.logger.Debug("preparing the trailers in a different HEADER frame") - //Prepare the trailers. - //The pseudo headers should be written before ordinary ones. - for key, value := range grpcMockResp.Trailers.PseudoHeaders { - err := encoder.WriteField(hpack.HeaderField{ - Name: key, - Value: value, - }) - if err != nil { - utils.LogError(srv.logger, err, "could not encode pseudo header", zap.Any("key", key), zap.Any("value", value)) - return err - } - } - for key, value := range grpcMockResp.Trailers.OrdinaryHeaders { - err := encoder.WriteField(hpack.HeaderField{ - Name: key, - Value: value, - }) - if err != nil { - utils.LogError(srv.logger, err, "could not encode ordinary header", zap.Any("key", key), zap.Any("value", value)) - return err - } - } - - // The trailer is prepared. Write the frame. - srv.logger.Debug("Writing the trailers in a different HEADER frame") - err = srv.framer.WriteHeaders(http2.HeadersFrameParam{ - StreamID: id, - BlockFragment: buf.Bytes(), - EndStream: true, - EndHeaders: true, - }) - if err != nil { - utils.LogError(srv.logger, err, "could not write the trailers onto client") - return err - } - - return nil -} - -func (srv *Transcoder) WriteData(ctx context.Context, streamID uint32, payload []byte) error { - totalLen := len(payload) - - // Fast path: if payload fits in one frame - if totalLen <= MAX_FRAME_SIZE { - select { - case <-ctx.Done(): - srv.logger.Warn("context cancelled before writing single frame") - return ctx.Err() - default: - err := srv.framer.WriteData(streamID, false, payload) - if err != nil { - utils.LogError(srv.logger, err, "could not write data frame") - return err - } - return nil - } - } - - // Chunked path - offset := 0 - for offset < totalLen { - // Check for context cancellation before each write - select { - case <-ctx.Done(): - srv.logger.Warn("context cancelled during chunked frame write") - return ctx.Err() - default: - } - - remaining := totalLen - offset - chunkSize := min(remaining, MAX_FRAME_SIZE) - - end := offset + chunkSize - data := payload[offset:end] - - srv.logger.Debug("Writing chunked data frame", zap.Int("chunk size", chunkSize), zap.Int("offset", offset), zap.Int("end", end)) - err := srv.framer.WriteData(streamID, false, data) - if err != nil { - utils.LogError(srv.logger, err, "could not write chunked data frame") - return err - } - - offset = end - if offset == totalLen { - srv.logger.Debug("the offset is equal to the total length of the payload", zap.Int("offset", offset)) - } - } - - return nil -} - -func (srv *Transcoder) ProcessWindowUpdateFrame(_ *http2.WindowUpdateFrame) error { - // Silently ignore Window tools frames, as we already know the mock payloads that we would send. - srv.logger.Debug("Received Window Update Frame. Skipping it...") - return nil -} - -func (srv *Transcoder) ProcessResetStreamFrame(resetStreamFrame *http2.RSTStreamFrame) error { - srv.sic.ResetStream(resetStreamFrame.StreamID) - return nil -} - -func (srv *Transcoder) ProcessSettingsFrame(settingsFrame *http2.SettingsFrame) error { - // ACK the settings and silently skip the processing. - // There is no actual server to tune the settings on. We already know the default settings from record mode. - // TODO : Add support for dynamically updating the settings. - if !settingsFrame.IsAck() { - return srv.framer.WriteSettingsAck() - } - return nil -} - -func (srv *Transcoder) ProcessGoAwayFrame(_ *http2.GoAwayFrame) error { - // We do not support a client that requests a server to shut down during test mode. Warn the user. - // TODO : Add support for dynamically shutting down mock server using a channel to send close request. - srv.logger.Warn("Received GoAway Frame. Ideally, clients should not close server during test mode.") - return nil -} - -func (srv *Transcoder) ProcessPriorityFrame(_ *http2.PriorityFrame) error { - // We do not support reordering of frames based on priority, because we flush after each response. - // Silently skip it. - srv.logger.Debug("Received PRIORITY frame, Skipping it...") - return nil -} - -func (srv *Transcoder) ProcessHeadersFrame(headersFrame *http2.HeadersFrame) error { - id := headersFrame.StreamID - // Streams initiated by a client MUST use odd-numbered stream identifiers - if id%2 != 1 { - utils.LogError(srv.logger, nil, "As per HTTP/2 spec, stream_id must be odd for a client if conn init by client.", zap.Any("stream_id", id)) - return http2.ConnectionError(http2.ErrCodeProtocol) - } - - pseudoHeaders, ordinaryHeaders, err := extractHeaders(headersFrame, srv.decoder) - if err != nil { - utils.LogError(srv.logger, err, "could not extract headers from frame") - } - - srv.sic.AddHeadersForRequest(id, pseudoHeaders, true) - srv.sic.AddHeadersForRequest(id, ordinaryHeaders, false) - return nil -} - -func (srv *Transcoder) ProcessPushPromise(_ *http2.PushPromiseFrame) error { - // A client cannot push. Thus, servers MUST treat the receipt of a PUSH_PROMISE - // frame as a conn error (Section 5.4.1) of type PROTOCOL_ERROR. - utils.LogError(srv.logger, nil, "As per HTTP/2 spec, client cannot send PUSH_PROMISE.") - return http2.ConnectionError(http2.ErrCodeProtocol) -} - -func (srv *Transcoder) ProcessContinuationFrame(_ *http2.ContinuationFrame) error { - // Continuation frame support is overkill currently because the headers won't exceed the frame size - // used by our mock server. - // However, if we really need this feature, we can implement it later. - utils.LogError(srv.logger, nil, "Continuation Frame received. This is unsupported currently") - return fmt.Errorf("continuation frame is unsupported in the current implementation") -} - -func (srv *Transcoder) ProcessGenericFrame(ctx context.Context, frame http2.Frame) error { - var err error - switch frame := frame.(type) { - case *http2.PingFrame: - err = srv.ProcessPingFrame(frame) - case *http2.DataFrame: - err = srv.ProcessDataFrame(ctx, frame) - case *http2.WindowUpdateFrame: - err = srv.ProcessWindowUpdateFrame(frame) - case *http2.RSTStreamFrame: - err = srv.ProcessResetStreamFrame(frame) - case *http2.SettingsFrame: - err = srv.ProcessSettingsFrame(frame) - case *http2.GoAwayFrame: - err = srv.ProcessGoAwayFrame(frame) - case *http2.PriorityFrame: - err = srv.ProcessPriorityFrame(frame) - case *http2.HeadersFrame: - err = srv.ProcessHeadersFrame(frame) - case *http2.PushPromiseFrame: - err = srv.ProcessPushPromise(frame) - case *http2.ContinuationFrame: - err = srv.ProcessContinuationFrame(frame) - default: - err = fmt.Errorf("unknown frame received from the client") - } - - return err -} - -// ListenAndServe is a forever blocking call that reads one frame at a time, and responds to them. -func (srv *Transcoder) ListenAndServe(ctx context.Context) error { - err := srv.WriteInitialSettingsFrame() - if err != nil { - utils.LogError(srv.logger, err, "could not write initial settings frame") - return err - } - - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - frame, err := srv.framer.ReadFrame() - if err != nil { - if err == io.EOF { - srv.logger.Debug("EOF reached. Closing the connection.") - return io.EOF - } - utils.LogError(srv.logger, err, "Failed to read frame") - return err - } - if ctx.Err() != nil { - return ctx.Err() - } - err = srv.ProcessGenericFrame(ctx, frame) - if err != nil { - return err - } - } - } -} diff --git a/keploy/pkg/core/proxy/integrations/grpc/util.go b/keploy/pkg/core/proxy/integrations/grpc/util.go deleted file mode 100644 index 8a7f892..0000000 --- a/keploy/pkg/core/proxy/integrations/grpc/util.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build linux - -package grpc - -import "golang.org/x/net/http2/hpack" - -// NewDecoder returns a header decoder. -func NewDecoder() *hpack.Decoder { - return hpack.NewDecoder(KmaxDynamicTableSize, nil) -} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/codec.go b/keploy/pkg/core/proxy/integrations/grpcV2/codec.go deleted file mode 100644 index 6a6c89f..0000000 --- a/keploy/pkg/core/proxy/integrations/grpcV2/codec.go +++ /dev/null @@ -1,76 +0,0 @@ -//go:build linux - -package grpcV2 - -import ( - "fmt" - - "google.golang.org/protobuf/proto" -) - -const rawCodecName = "keploy-raw" - -// rawCodec is a gRPC codec that passes byte slices through without any serialization. -// This is crucial for proxying or mocking requests when we don't have the .proto definitions. -type rawCodec struct{} - -// rawMessage is a wrapper for byte slices to satisfy the proto.Message interface. -type rawMessage struct { - data []byte -} - -func (m *rawMessage) Reset() { *m = rawMessage{} } -func (m *rawMessage) String() string { return string(m.data) } -func (*rawMessage) ProtoMessage() {} - -func (c *rawCodec) Marshal(v interface{}) ([]byte, error) { - // Marshal the rawMessage wrapper to its underlying byte slice. - if rm, ok := v.(*rawMessage); ok { - return rm.data, nil - } - // Fallback for other types, though we primarily use rawMessage. - if p, ok := v.(proto.Message); ok { - return proto.Marshal(p) - } - return nil, fmt.Errorf("failed to marshal, message is %T, want proto.Message", v) -} - -func (c *rawCodec) Unmarshal(data []byte, v interface{}) error { - // Unmarshal the byte slice into the rawMessage wrapper. - if rm, ok := v.(*rawMessage); ok { - rm.data = make([]byte, len(data)) - copy(rm.data, data) - return nil - } - // Fallback for other types. - if p, ok := v.(proto.Message); ok { - return proto.Unmarshal(data, p) - } - return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v) -} - -func (c *rawCodec) Name() string { - return rawCodecName -} - -func (c *rawCodec) String() string { - return c.Name() -} - -// passthroughCodec keeps 'proto' on the wire but avoids re-encoding. -type passthroughCodec struct{} - -func (passthroughCodec) Name() string { return "proto" } // server already knows this one -func (passthroughCodec) Marshal(v interface{}) ([]byte, error) { - if m, ok := v.(*rawMessage); ok { - return m.data, nil // send bytes exactly as we received them - } - return proto.Marshal(v.(proto.Message)) -} -func (passthroughCodec) Unmarshal(data []byte, v interface{}) error { - if m, ok := v.(*rawMessage); ok { - m.data = append([]byte(nil), data...) - return nil - } - return proto.Unmarshal(data, v.(proto.Message)) -} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/grpc.go b/keploy/pkg/core/proxy/integrations/grpcV2/grpc.go deleted file mode 100644 index 154968e..0000000 --- a/keploy/pkg/core/proxy/integrations/grpcV2/grpc.go +++ /dev/null @@ -1,92 +0,0 @@ -//go:build linux - -package grpcV2 - -import ( - "bytes" - "context" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/encoding" - "google.golang.org/grpc/status" -) - -func init() { - // Register the raw codec for passing raw bytes through the gRPC framework. - encoding.RegisterCodec(new(rawCodec)) - - integrations.Register(integrations.GRPC, &integrations.Parsers{ - Initializer: New, - Priority: 100, - }) -} - -type Grpc struct { - logger *zap.Logger -} - -func New(logger *zap.Logger) integrations.Integrations { - return &Grpc{ - logger: logger, - } -} - -// MatchType determines if the outgoing network call is gRPC by checking for the HTTP/2 preface. -func (g *Grpc) MatchType(_ context.Context, reqBuf []byte) bool { - const preface = "PRI * HTTP/2" - if len(reqBuf) < len(preface) { - return false - } - return bytes.HasPrefix(reqBuf, []byte(preface)) -} - -func (g *Grpc) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - cid, ok := ctx.Value(models.ClientConnectionIDKey).(string) - if !ok { - return status.Errorf(codes.Internal, "missing ClientConnectionID in context") - } - did, ok := ctx.Value(models.DestConnectionIDKey).(string) - if !ok { - return status.Errorf(codes.Internal, "missing DestinationConnectionID in context") - } - logger := g.logger.With( - zap.String("Client ConnectionID", cid), - zap.String("Destination ConnectionID", did), - zap.String("Client IP Address", src.RemoteAddr().String())) - // Peek the preface (needed for type detection) **but replay it** for the gRPC server. - preface, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial grpc message") - return err - } - - return recordOutgoing(ctx, logger, - newReplayConn(preface, src), // <- give server the full preface - dst, mocks) -} - -func (g *Grpc) MockOutgoing(ctx context.Context, src net.Conn, _ *models.ConditionalDstCfg, mockDb integrations.MockMemDb, _ models.OutgoingOptions) error { - cid, ok := ctx.Value(models.ClientConnectionIDKey).(string) - if !ok { - return status.Errorf(codes.Internal, "missing ClientConnectionID in context") - } - logger := g.logger.With( - zap.String("Client ConnectionID", cid), - zap.String("Client IP Address", src.RemoteAddr().String())) - // Consume the initial preface buffer from the connection. - preface, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial grpc message") - return err - } - - return mockOutgoing(ctx, logger, - newReplayConn(preface, src), // <- same in mock path - mockDb) -} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/helper.go b/keploy/pkg/core/proxy/integrations/grpcV2/helper.go deleted file mode 100644 index e4c9749..0000000 --- a/keploy/pkg/core/proxy/integrations/grpcV2/helper.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build linux - -package grpcV2 - -import ( - "github.com/protocolbuffers/protoscope" - "go.keploy.io/server/v2/pkg/models" -) - -// createLengthPrefixedMessage creates a GrpcLengthPrefixedMessage from a raw message payload. -// The gRPC framework handles the actual 5-byte wire protocol prefix. This struct -// is for Keploy's internal representation and matching. -func createLengthPrefixedMessage(data []byte) models.GrpcLengthPrefixedMessage { - // The original implementation stored the raw bytes as a string, which can - // safely hold binary data in Go. We will follow this for consistency with - // the existing fuzzy matching logic. - return models.GrpcLengthPrefixedMessage{ - // Compression flag is 0 for uncompressed. - CompressionFlag: 0, - // MessageLength is the length of the raw data. - MessageLength: uint32(len(data)), - // DecodedData holds the text representation of the wire data. - DecodedData: prettyPrintWire(data, 0), - } -} - -// createPayloadFromLengthPrefixedMessage decodes the pretty-printed data -// from a message back into its raw binary payload. -func createPayloadFromLengthPrefixedMessage(msg models.GrpcLengthPrefixedMessage) ([]byte, error) { - return parsePrettyWire(msg.DecodedData) -} - -// prettyPrintWire renders any protobuf wire payload without needing the .proto file. -// It uses protoscope for a robust, standardized text format. -func prettyPrintWire(b []byte, _ int) string { - // The indent argument is ignored as protoscope handles formatting automatically. - return protoscope.Write(b, protoscope.WriterOptions{}) -} - -// parsePrettyWire decodes the human-readable text format from protoscope -// back into its binary wire format. -func parsePrettyWire(s string) ([]byte, error) { - scanner := protoscope.NewScanner(s) - // The scanner.Exec() method handles the entire parsing process. - return scanner.Exec() -} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/listener.go b/keploy/pkg/core/proxy/integrations/grpcV2/listener.go deleted file mode 100644 index f40d5d6..0000000 --- a/keploy/pkg/core/proxy/integrations/grpcV2/listener.go +++ /dev/null @@ -1,64 +0,0 @@ -//go:build linux - -package grpcV2 - -import ( - "net" - "sync" -) - -// singleConnListener adapts an existing net.Conn so it can be passed to -// grpc.Serve, which expects something that looks like a net.Listener but -// only ever needs to serve one connection. -type singleConnListener struct { - conn net.Conn // the single connection we expose - once sync.Once // hands out conn exactly once - closeOnce sync.Once // closes the "done" channel exactly once - done chan struct{} -} - -func newSingleConnListener(conn net.Conn) *singleConnListener { - return &singleConnListener{ - conn: conn, - done: make(chan struct{}), - } -} - -func (l *singleConnListener) Accept() (net.Conn, error) { - var first bool - l.once.Do(func() { first = true }) - - if first { - // Wrap the conn so that closing it notifies the listener. - return &trackedConn{ - Conn: l.conn, - onClose: func() { - l.closeOnce.Do(func() { close(l.done) }) - }, - }, nil - } - - // After the first connection, Serve() may call Accept() again. - <-l.done // block until the trackedConn is closed - return nil, net.ErrClosed -} - -func (l *singleConnListener) Close() error { - l.closeOnce.Do(func() { close(l.done) }) - return l.conn.Close() -} - -func (l *singleConnListener) Addr() net.Addr { return l.conn.LocalAddr() } - -// trackedConn executes onClose exactly once when Close is called. -type trackedConn struct { - net.Conn - once sync.Once - onClose func() -} - -func (c *trackedConn) Close() error { - err := c.Conn.Close() - c.once.Do(c.onClose) - return err -} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/match.go b/keploy/pkg/core/proxy/integrations/grpcV2/match.go deleted file mode 100644 index bc3b9cc..0000000 --- a/keploy/pkg/core/proxy/integrations/grpcV2/match.go +++ /dev/null @@ -1,291 +0,0 @@ -//go:build linux - -package grpcV2 - -import ( - "context" - "fmt" - - "github.com/agnivade/levenshtein" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.uber.org/zap" - - "go.keploy.io/server/v2/pkg/matcher/grpc" - "go.keploy.io/server/v2/pkg/models" -) - -func FilterMocksRelatedToGrpc(mocks []*models.Mock) []*models.Mock { - var res []*models.Mock - for _, mock := range mocks { - if mock != nil && mock.Kind == models.GRPC_EXPORT && mock.Spec.GRPCReq != nil && mock.Spec.GRPCResp != nil { - res = append(res, mock) - } - } - return res -} - -func FilterMocksBasedOnGrpcRequest(ctx context.Context, logger *zap.Logger, grpcReq models.GrpcReq, mockDb integrations.MockMemDb) (*models.Mock, error) { - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - mocks, err := mockDb.GetFilteredMocks() - if err != nil { - return nil, fmt.Errorf("error while getting tsc mocks %v", err) - } - - var matchedMock *models.Mock - var isMatched bool - - grpcMocks := FilterMocksRelatedToGrpc(mocks) - - if len(grpcMocks) == 0 { - logger.Debug("No grpc mocks found in the db") - return nil, nil - } - - logger.Debug("grpc mocks in DB", zap.Int("len", len(grpcMocks))) - - for _, mock := range grpcMocks { - logger.Debug("found grpc mock", zap.String("name", mock.Name)) - } - - schemaMatched, err := schemaMatch(ctx, logger, grpcReq, grpcMocks) - if err != nil { - return nil, err - } - - if len(schemaMatched) == 0 { - logger.Debug("No mock found with schema match") - return nil, nil - } - - logger.Debug("grpc mocks with schema match", zap.Int("len", len(schemaMatched))) - - for _, mock := range schemaMatched { - logger.Debug("schema matched grpc mock", zap.String("name", mock.Name)) - } - - // Exact body Match - expBody := grpc.CanonicalizeTopLevelBlocks(grpcReq.Body.DecodedData) - ok, matchedMock := exactBodyMatch(logger, expBody, schemaMatched) - if ok { - logger.Debug("exact body match found", zap.String("name", matchedMock.Name)) - if !mockDb.DeleteFilteredMock(*matchedMock) { - continue - } - return matchedMock, nil - } - - // apply fuzzy match for body with schemaMatched mocks - - // apply fuzzy match for body with schemaMatched mocks - // Guard against quadratic work on very large bodies. - if len(expBody) > 512*1024 { - logger.Debug("skipping fuzzy match for large body", zap.Int("len", len(expBody))) - return nil, nil - } - logger.Debug("performing fuzzy match for decoded data in body") - // Perform fuzzy match on the request - isMatched, bestMatch := fuzzyMatch(schemaMatched, grpcReq.Body.DecodedData) - if isMatched { - if !mockDb.DeleteFilteredMock(*bestMatch) { - continue - } - return bestMatch, nil - } - return nil, nil - } - } -} - -func schemaMatch(ctx context.Context, logger *zap.Logger, req models.GrpcReq, mocks []*models.Mock) ([]*models.Mock, error) { - var schemaMatched []*models.Mock - - for _, mock := range mocks { - if ctx.Err() != nil { - return nil, ctx.Err() - } - mockReq := mock.Spec.GRPCReq - // Require :method and :path to match exactly; tolerate :authority-only differences. - mp := mockReq.Headers.PseudoHeaders - rp := req.Headers.PseudoHeaders - // Require presence AND equality (empty means missing) - if mp[":method"] == "" || rp[":method"] == "" || mp[":method"] != rp[":method"] || - mp[":path"] == "" || rp[":path"] == "" || mp[":path"] != rp[":path"] { - continue - } - if mp[":authority"] != rp[":authority"] { - logger.Debug("ignoring :authority mismatch for gRPC request", - zap.String("mock", mock.Name), - zap.String("mock_authority", mp[":authority"]), - zap.String("req_authority", rp[":authority"])) - } - - // For the rest of pseudo-headers, compare with :authority skipped (if present). - if !compareMapExcept(mp, rp, map[string]struct{}{":authority": {}}) { - continue - } - // the ordinary headers keys should match. - if !compareMapKeys(mockReq.Headers.OrdinaryHeaders, req.Headers.OrdinaryHeaders) { - continue - } - - // the content type should match. - if mockReq.Headers.OrdinaryHeaders["content-type"] != req.Headers.OrdinaryHeaders["content-type"] { - continue - } - - schemaMatched = append(schemaMatched, mock) - } - - return schemaMatched, nil -} - -// compareMapExcept compares two string maps, skipping keys in 'skip'. -func compareMapExcept(m1, m2 map[string]string, skip map[string]struct{}) bool { - if len(m1) != len(m2) { - // Lengths may differ only due to skipped keys; check key-wise. - } - for k, v1 := range m1 { - if _, ok := skip[k]; ok { - continue - } - if v2, ok := m2[k]; !ok || v1 != v2 { - return false - } - } - for k := range m2 { - if _, ok := skip[k]; ok { - continue - } - if _, ok := m1[k]; !ok { - return false - } - } - return true -} - -// Check if two maps have the same keys, ignoring values. -func compareMapKeys(m1, m2 map[string]string) bool { - if len(m1) != len(m2) { - return false - } - for k := range m1 { - if _, ok := m2[k]; !ok { - return false - } - } - return true -} - -// Check if two maps are identical. -func compareMap(m1, m2 map[string]string) bool { - if len(m1) != len(m2) { - return false - } - for k, v := range m1 { - if v2, ok := m2[k]; !ok || v != v2 { - return false - } - } - return true -} - -func exactBodyMatch(logger *zap.Logger, expBody string, schemaMatched []*models.Mock) (bool, *models.Mock) { - for _, mock := range schemaMatched { - got := grpc.CanonicalizeTopLevelBlocks(mock.Spec.GRPCReq.Body.DecodedData) - logger.Debug("Comparing bodies for mock", zap.String("name", mock.Name)) - if got == expBody { - return true, mock - } - } - return false, nil -} - -// fuzzyMatch logic with input trimming for performance. -func findStringMatch(req string, mockStrings []string) int { - // Trim request to 2048 characters for performance - if len(req) > 2048 { - req = req[:2048] - } - - minDist := int(^uint(0) >> 1) - bestMatch := -1 - for idx, mock := range mockStrings { - if !util.IsASCII(mock) { - continue - } - // Trim mock string to 2048 characters for performance - trimmedMock := mock - if len(mock) > 2048 { - trimmedMock = mock[:2048] - } - - dist := levenshtein.ComputeDistance(req, trimmedMock) - if dist == 0 { - return 0 - } - if dist < minDist { - minDist = dist - bestMatch = idx - } - } - return bestMatch -} - -func findBinaryMatch(mocks []*models.Mock, reqBuff []byte) int { - // Trim request buffer to 2048 bytes for performance - if len(reqBuff) > 2048 { - reqBuff = reqBuff[:2048] - } - - mxSim := -1.0 - mxIdx := -1 - for idx, mock := range mocks { - encoded := []byte(mock.Spec.GRPCReq.Body.DecodedData) - // Trim mock data to 2048 bytes for performance - if len(encoded) > 2048 { - encoded = encoded[:2048] - } - - k := util.AdaptiveK(len(reqBuff), 3, 8, 5) - shingles1 := util.CreateShingles(encoded, k) - shingles2 := util.CreateShingles(reqBuff, k) - similarity := util.JaccardSimilarity(shingles1, shingles2) - - if mxSim < similarity { - mxSim = similarity - mxIdx = idx - } - } - return mxIdx -} - -func fuzzyMatch(tcsMocks []*models.Mock, reqBuff string) (bool, *models.Mock) { - // Trim request buffer to 2048 characters for performance - trimmedReqBuff := reqBuff - if len(reqBuff) > 2048 { - trimmedReqBuff = reqBuff[:2048] - } - - mockStrings := make([]string, len(tcsMocks)) - for i := range tcsMocks { - mockStrings[i] = tcsMocks[i].Spec.GRPCReq.Body.DecodedData - } - - if util.IsASCII(trimmedReqBuff) { - idx := findStringMatch(trimmedReqBuff, mockStrings) - if idx != -1 { - return true, tcsMocks[idx] - } - } - - idx := findBinaryMatch(tcsMocks, []byte(trimmedReqBuff)) - if idx != -1 { - return true, tcsMocks[idx] - } - return false, nil -} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/mock.go b/keploy/pkg/core/proxy/integrations/grpcV2/mock.go deleted file mode 100644 index 205a69b..0000000 --- a/keploy/pkg/core/proxy/integrations/grpcV2/mock.go +++ /dev/null @@ -1,213 +0,0 @@ -//go:build linux - -package grpcV2 - -import ( - "context" - "io" - "math" - "net" - "strconv" - "strings" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -) - -// mockOutgoing starts a gRPC server to mock responses for an incoming connection. -func mockOutgoing(ctx context.Context, logger *zap.Logger, clientConn net.Conn, mockDb integrations.MockMemDb) error { - // Always close the socket when we return. - defer func() { - if err := clientConn.Close(); err != nil && - !strings.Contains(err.Error(), "use of closed network connection") { - logger.Error("failed to close client connection in mock mode", zap.Error(err)) - } - }() - - mockServer := &grpcMockServer{ - logger: logger, - mockDb: mockDb, - } - - // Create a gRPC server that uses our raw codec and unknown service handler. - srv := grpc.NewServer( - grpc.UnknownServiceHandler(mockServer.handler), - grpc.ForceServerCodec(new(rawCodec)), - ) - - // Use a single-connection listener to serve the mock on the given connection. - lis := newSingleConnListener(clientConn) - logger.Info("starting mock gRPC server") - // Run Serve in its own goroutine so we can stop it when the context is done. - srvErr := make(chan error, 1) - go func() { srvErr <- srv.Serve(lis) }() - - select { - case <-ctx.Done(): - // Parent context cancelled (Ctrl-C, timeout, etc.). - go srv.GracefulStop() - <-srvErr // wait for Serve to return - return ctx.Err() - - case err := <-srvErr: - // Serve returned on its own (connection closed, reset, etc.). - switch { - case err == nil, - err == io.EOF, - strings.Contains(err.Error(), "use of closed network connection"): - logger.Debug("client connection closed (EOF)") - return nil - case strings.Contains(err.Error(), "connection reset by peer"): - logger.Warn("client connection was reset by peer") - return nil - default: - logger.Error("mock gRPC server failed", zap.Error(err)) - return err - } - } -} - -// grpcMockServer implements the gRPC unknown service handler to mock responses. -type grpcMockServer struct { - logger *zap.Logger - mockDb integrations.MockMemDb -} - -func (s *grpcMockServer) handler(_ interface{}, stream grpc.ServerStream) error { - // 1. Extract request details - fullMethod, ok := grpc.MethodFromServerStream(stream) - if !ok { - s.logger.Error("failed to get method from stream") - return status.Errorf(codes.Internal, "failed to get method from stream") - } - - md, ok := metadata.FromIncomingContext(stream.Context()) - if !ok { - s.logger.Warn("failed to get metadata from context") - } - - s.logger.Debug("received gRPC request to mock", zap.String("method", fullMethod), zap.Any("metadata", md)) - - // Read the request body. - var requestBody []byte - reqMsg := new(rawMessage) - if err := stream.RecvMsg(reqMsg); err != nil && err != io.EOF { - s.logger.Error("failed to receive request message from stream", zap.Error(err)) - return status.Errorf(codes.Internal, "failed to receive request message: %v", err) - } - requestBody = reqMsg.data - s.logger.Debug("fully received request body", zap.Int("size", len(requestBody))) - - grpcReq := &models.GrpcReq{ - Headers: s.grpcMetadataToHeaders(md, fullMethod), - Body: createLengthPrefixedMessage(requestBody), - } - - // 2. Find a matching mock - s.logger.Debug("finding mock for gRPC request", zap.Any("request", grpcReq)) - mock, err := FilterMocksBasedOnGrpcRequest(stream.Context(), s.logger, *grpcReq, s.mockDb) - if err != nil { - s.logger.Error("failed to find mock", zap.Error(err)) - return status.Errorf(codes.Internal, "failed to find mock: %v", err) - } - if mock == nil { - s.logger.Error("no matching gRPC mock found", zap.String("method", fullMethod)) - return status.Errorf(codes.NotFound, "no matching keploy mock found for %s", fullMethod) - } - - s.logger.Debug("found matching mock", zap.String("mock.name", mock.Name), zap.String("mock.kind", string(mock.Kind))) - - // 3. Send the mocked response - grpcResp := mock.Spec.GRPCResp - - // --- Determine final gRPC status -------------------------------------- - scStr := grpcResp.Trailers.OrdinaryHeaders["grpc-status"] - scInt, err := strconv.Atoi(scStr) // bad/missing ⇒ 0 (codes.OK) - var finalCode codes.Code - if err != nil || scInt < 0 || scInt > math.MaxUint32 { - s.logger.Warn("invalid grpc-status value, defaulting to codes.OK", zap.String("grpc-status", scStr)) - finalCode = codes.OK - } else { - finalCode = codes.Code(uint32(scInt)) // 0 ⇒ OK - } - finalMsg := grpcResp.Trailers.OrdinaryHeaders["grpc-message"] - // Send headers - respMd := s.headersToGrpcMetadata(grpcResp.Headers) - if err := stream.SendHeader(respMd); err != nil { - s.logger.Error("failed to send response headers", zap.Error(err)) - return status.Errorf(codes.Internal, "failed to send headers: %v", err) - } - - // Send body **only when grpc-status == OK** - if finalCode == codes.OK { - respBody, err := createPayloadFromLengthPrefixedMessage(grpcResp.Body) - if err != nil { - s.logger.Error("failed to create payload from length-prefixed message", zap.Error(err)) - return status.Errorf(codes.Internal, "failed to create response payload: %v", err) - } - if err := stream.SendMsg(&rawMessage{data: respBody}); err != nil { - s.logger.Error("failed to send response message", zap.Error(err)) - return status.Errorf(codes.Internal, "failed to send response message: %v", err) - } - s.logger.Debug("sent mocked response body", zap.Int("size", len(respBody))) - } - - // For non-OK results return a status.Error – this makes the runtime - // write the required grpc-status / grpc-message trailers for us. - if finalCode != codes.OK { - return status.Error(finalCode, finalMsg) - } - - // Success path – attach any extra trailers and finish. - trailerMd := s.headersToGrpcMetadata(grpcResp.Trailers) - stream.SetTrailer(trailerMd) - s.logger.Debug("sent mocked response trailers", zap.Any("trailers", trailerMd)) - return nil -} - -// grpcMetadataToHeaders converts gRPC metadata to Keploy's header format. -func (s *grpcMockServer) grpcMetadataToHeaders(md metadata.MD, fullMethod string) models.GrpcHeaders { - hdr := models.GrpcHeaders{ - PseudoHeaders: make(map[string]string), - OrdinaryHeaders: make(map[string]string), - } - for k, v := range md { - val := strings.Join(v, ", ") - if strings.HasPrefix(k, ":") { - hdr.PseudoHeaders[k] = val - } else { - hdr.OrdinaryHeaders[k] = val - } - } - // Stabilise the header set so it matches what was recorded - hdr.OrdinaryHeaders["te"] = "trailers" - - // The grpc server framework consumes pseudo-headers, so we must add them back. - if method, ok := hdr.PseudoHeaders[":method"]; !ok || method == "" { - hdr.PseudoHeaders[":method"] = "POST" - } - if scheme, ok := hdr.PseudoHeaders[":scheme"]; !ok || scheme == "" { - hdr.PseudoHeaders[":scheme"] = "http" - } - if path, ok := hdr.PseudoHeaders[":path"]; !ok || path == "" { - hdr.PseudoHeaders[":path"] = fullMethod - } - return hdr -} - -// headersToGrpcMetadata converts Keploy's header format to gRPC metadata. -func (s *grpcMockServer) headersToGrpcMetadata(headers models.GrpcHeaders) metadata.MD { - md := metadata.New(nil) - for k, v := range headers.PseudoHeaders { - md.Set(k, v) - } - for k, v := range headers.OrdinaryHeaders { - md.Set(k, v) - } - return md -} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/record.go b/keploy/pkg/core/proxy/integrations/grpcV2/record.go deleted file mode 100644 index aa6a1ba..0000000 --- a/keploy/pkg/core/proxy/integrations/grpcV2/record.go +++ /dev/null @@ -1,428 +0,0 @@ -//go:build linux - -package grpcV2 - -import ( - "bytes" - "context" - "encoding/base64" - "errors" - "fmt" - "io" - "net" - "strings" - "sync" - "time" - - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/connectivity" - "google.golang.org/grpc/credentials/insecure" - _ "google.golang.org/grpc/encoding/gzip" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -) - -// recordOutgoing starts a gRPC proxy to record a session. -func recordOutgoing(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, mocks chan<- *models.Mock) error { - // Ensure connections are closed on exit - cid, ok := ctx.Value(models.ClientConnectionIDKey).(string) - if !ok { - return status.Errorf(codes.Internal, "missing ClientConnectionID in context") - } - - proxy := &grpcRecordingProxy{ - logger: logger, - destConn: destConn, - mocks: mocks, - connID: cid, - } - - defer func() { - if err := clientConn.Close(); err != nil && - !strings.Contains(err.Error(), "use of closed network connection") { - logger.Error("failed to close client connection in record mode", zap.Error(err)) - } - if err := destConn.Close(); err != nil && - !strings.Contains(err.Error(), "use of closed network connection") { - logger.Error("failed to close destination connection in record mode", zap.Error(err)) - } - // Close the grpc.ClientConn if it was created. - if proxy.cc != nil { - err := proxy.cc.Close() - if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { - logger.Error("failed to close gRPC client connection in record mode", zap.Error(err)) - } - } - }() - - // Create a gRPC server to handle the client's request - srv := grpc.NewServer( - grpc.UnknownServiceHandler(proxy.handler), - grpc.ForceServerCodec(new(rawCodec)), - ) - - lis := newSingleConnListener(clientConn) - logger.Info("starting recording gRPC proxy server") - - srvErr := make(chan error, 1) - go func() { srvErr <- srv.Serve(lis) }() - - select { - case <-ctx.Done(): - // Gracefully shut down once the recorder context is cancelled. - go srv.GracefulStop() - logger.Debug("waiting for gRPC recording proxy server to stop") - err := <-srvErr - logger.Info("gRPC recording proxy server stopped gracefully", zap.Error(err)) - return ctx.Err() - case err := <-srvErr: - switch { - case err == nil, - err == io.EOF, - strings.Contains(err.Error(), "connection reset by peer"), - strings.Contains(err.Error(), "use of closed network connection"): - logger.Info("gRPC recording proxy stopped gracefully") - return nil - default: - logger.Error("gRPC recording proxy server failed", zap.Error(err)) - return err - } - } - -} - -// grpcRecordingProxy proxies gRPC calls, recording the request and response. -type grpcRecordingProxy struct { - logger *zap.Logger - destConn net.Conn - mocks chan<- *models.Mock - connID string - ccMu sync.Mutex // protects cc - cc *grpc.ClientConn // reused for all streams on this TCP conn -} - -// getClientConn returns the (lazily-constructed) grpc.ClientConn that -// multiplexes over p.destConn. -func (p *grpcRecordingProxy) getClientConn(ctx context.Context) (*grpc.ClientConn, error) { - p.ccMu.Lock() - defer p.ccMu.Unlock() - - if p.cc != nil { - s := p.cc.GetState() - p.logger.Debug("checking gRPC client connection state", - zap.String("state", s.String()), - zap.String("connID", p.connID)) - if s != connectivity.Ready && s != connectivity.Connecting { - _ = p.cc.Close() // ignore error - // p.cc = nil // force re-dial - return nil, io.EOF - } - } - - if p.cc != nil { - return p.cc, nil - } - - p.logger.Debug("creating new gRPC client connection because p.cc is nil", zap.Any("p.cc", p.cc)) - - dialer := func(context.Context, string) (net.Conn, error) { return p.destConn, nil } - - target := p.destConn.RemoteAddr().String() // or explicit host:port string - cc, err := grpc.DialContext( - ctx, - target, - grpc.WithContextDialer(dialer), - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithDefaultCallOptions(grpc.ForceCodec(passthroughCodec{})), - ) - if err != nil { - return nil, err - } - p.cc = cc - return cc, nil -} - -// handler is the core of the proxy. It receives a call, forwards it, and records the interaction. -func (p *grpcRecordingProxy) handler(_ interface{}, clientStream grpc.ServerStream) error { - p.logger.Debug("received gRPC call") - startTime := time.Now() - clientCtx := clientStream.Context() - fullMethod, _ := grpc.MethodFromServerStream(clientStream) - connID := p.connID - if connID == "" { - connID = "0" // graceful fallback - } - - md, _ := metadata.FromIncomingContext(clientCtx) - - p.logger.Info("proxying gRPC request", zap.String("method", fullMethod), zap.Any("metadata", md)) - - // 1. Obtain (or create once) the grpc.ClientConn that sits on destConn - destClientConn, err := p.getClientConn(clientCtx) - if err != nil { - if errors.Is(err, io.EOF) { - p.logger.Warn("gRPC client connection is closed, cannot forward request", zap.Error(err)) - return io.EOF - } - p.logger.Error("failed to dial destination server", zap.Error(err)) - return status.Errorf(codes.Internal, "failed to connect to destination: %v", err) - } - - if destClientConn == nil { - p.logger.Error("destination client connection is nil") - return status.Errorf(codes.Internal, "destination client connection is nil") - } - - // 2. Forward the call to the destination - downstreamCtx, cancelDownstream := context.WithCancel(clientCtx) - defer cancelDownstream() - - // ── Clean metadata: gRPC forbids user-supplied pseudo headers ("*:"). - cleanMD := metadata.New(nil) - for k, v := range md { - fmt.Printf("[MD] key: %s, value: %s\n", k, v) - if strings.HasPrefix(k, ":") { - continue // strip pseudo-headers - } - cleanMD[k] = v - } - - downstreamCtx = metadata.NewOutgoingContext(downstreamCtx, cleanMD) - destStream, err := destClientConn.NewStream(downstreamCtx, &grpc.StreamDesc{ - StreamName: fullMethod, - ServerStreams: true, - ClientStreams: true, - }, fullMethod) - if err != nil { - if downstreamCtx.Err() != nil { - p.logger.Warn("context cancelled before creating stream to destination", zap.Error(downstreamCtx.Err())) - return status.Errorf(codes.Canceled, "context cancelled before creating stream to destination: %v", downstreamCtx.Err()) - } - p.logger.Error("failed to create new stream to destination", zap.Error(err)) - return status.Errorf(codes.Internal, "failed to create stream to destination: %v", err) - } - - // 3. Goroutines to proxy data in both directions and capture it - var wg sync.WaitGroup - var reqErr, respErr error - var reqBuf, respBuf bytes.Buffer - - wg.Add(1) - go func() { - defer wg.Done() - for { - reqMsg := new(rawMessage) - reqErr = clientStream.RecvMsg(reqMsg) - if reqErr == io.EOF { - err := destStream.CloseSend() - p.logger.Debug("client stream closed", zap.Error(err)) - if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { - p.logger.Error("failed to close send stream to destination", zap.Error(err)) - cancelDownstream() - } - return - } - if reqErr != nil { - p.logger.Error("failed to receive message from client", zap.Error(reqErr)) - cancelDownstream() - return - } - p.logger.Debug("received message from client", zap.Int("size", len(reqMsg.data)), - zap.String("Msg", reqMsg.String())) - - // append keploy at the end of message - // reqBuf.Write([]byte("keploy")) - - reqBuf.Write(reqMsg.data) - if err := destStream.SendMsg(reqMsg); err != nil { - p.logger.Error("failed to send message to destination", zap.Error(err)) - reqErr = err - return - } - } - }() - - respHeader := metadata.MD{} - wg.Add(1) - go func() { - defer wg.Done() - header, err := destStream.Header() - if err != nil { - p.logger.Warn("failed to get headers from destination stream", zap.Error(err)) - respErr = err - return - } - - respHeader = header - if err := clientStream.SendHeader(header); err != nil { - p.logger.Error("failed to send headers to client", zap.Error(err)) - respErr = err - return - } - for { - respMsg := new(rawMessage) - p.logger.Debug("received message from server", zap.Int("size", len(respMsg.data)), - zap.String("Msg", respMsg.String())) - - respErr = destStream.RecvMsg(respMsg) - if respErr != nil { - p.logger.Debug("received Error from destination", zap.Error(respErr)) - } - switch respErr { - case nil: - // normal message – relay it - case io.EOF: - p.logger.Debug("destination stream closed due to EOF") - return // clean finish - default: - // gRPC status error (business failure) is *expected*; just stop reading. - if _, ok := status.FromError(respErr); ok { - return - } - // real transport problem – still log it. - p.logger.Error("failed to receive message from destination", - zap.Error(respErr)) - return - } - respBuf.Write(respMsg.data) - if err := clientStream.SendMsg(respMsg); err != nil { - p.logger.Error("failed to send message to client", zap.Error(err)) - respErr = err - return - } - } - }() - - wg.Wait() - - // 4. Finalize and record - endTime := time.Now() - destTrailers := destStream.Trailer() - clientStream.SetTrailer(destTrailers) - // ──────────────────────────────────────────────────────────────── - // Construct & enqueue the mock **before** we possibly return. - // ──────────────────────────────────────────────────────────────── - grpcReq := &models.GrpcReq{ - Body: createLengthPrefixedMessage(reqBuf.Bytes()), - Headers: p.grpcMetadataToHeaders(md, fullMethod, false), - } - - p.logger.Debug("headers and trailer of grpc response", - zap.Any("headers", respHeader), - zap.Any("trailers", destTrailers)) - - body64 := base64.StdEncoding.EncodeToString(respBuf.Bytes()) - - p.logger.Debug("Grpc Response body", zap.Int("body size", len(respBuf.Bytes())), zap.Any("body", respBuf.String()), zap.Any("body64", body64)) - // respHeader, _ := destStream.Header() - grpcResp := &models.GrpcResp{ - Body: createLengthPrefixedMessage(respBuf.Bytes()), - Headers: p.grpcMetadataToHeaders(respHeader, "", true), - Trailers: p.grpcMetadataToHeaders(destTrailers, "", true), - } - - //------------------------------------------------------------------ - // If the server terminated the stream with a status error, make - // sure we record **that** code & message instead of default “0”. - //------------------------------------------------------------------ - if st, ok := status.FromError(respErr); ok && respErr != nil { - grpcResp.Trailers.OrdinaryHeaders["grpc-status"] = fmt.Sprintf("%d", st.Code()) - grpcResp.Trailers.OrdinaryHeaders["grpc-message"] = st.Message() - // Per gRPC spec error responses have no body – keep what we already - // captured (likely empty) but that’s harmless. - } else { - // Ensure mandatory keys are present for the happy-path case. - if _, ok := grpcResp.Trailers.OrdinaryHeaders["grpc-status"]; !ok { - grpcResp.Trailers.OrdinaryHeaders["grpc-status"] = "0" - } - if _, ok := grpcResp.Trailers.OrdinaryHeaders["grpc-message"]; !ok { - grpcResp.Trailers.OrdinaryHeaders["grpc-message"] = "" - } - } - - p.mocks <- &models.Mock{ - Version: models.GetVersion(), - Name: "mocks", - Kind: models.GRPC_EXPORT, - Spec: models.MockSpec{ - Metadata: map[string]string{"connID": connID}, - GRPCReq: grpcReq, - GRPCResp: grpcResp, - ReqTimestampMock: startTime, - ResTimestampMock: endTime, - }, - } - p.logger.Info("successfully recorded gRPC interaction", zap.String("method", fullMethod)) - - // ──────────────────────────────────────────────────────────────── - // Now decide what to return to the client. - // ──────────────────────────────────────────────────────────────── - - // Treat normal end-of-stream (EOF) and context cancellation as success. - benign := func(err error) bool { - return err == nil || err == io.EOF || errors.Is(err, context.Canceled) - } - if !benign(reqErr) { - return status.Errorf(codes.Internal, "error during request forwarding: %v", reqErr) - } - - // If the server ended the call with a (business) gRPC status error, - // propagate that **after** recording. - if s, ok := status.FromError(respErr); ok && respErr != nil { - p.logger.Debug("received gRPC status error from destination", - zap.String("code", s.Code().String()), - zap.String("message", s.Message())) - - return s.Err() - } - - // Any other non-benign transport error? - if !benign(respErr) { - return status.Errorf(codes.Internal, - "error during response forwarding: %v", respErr) - } - - return nil -} - -// grpcMetadataToHeaders converts gRPC metadata to Keploy's header format. -func (p *grpcRecordingProxy) grpcMetadataToHeaders(md metadata.MD, fullMethod string, isResponse bool) models.GrpcHeaders { - hdr := models.GrpcHeaders{ - PseudoHeaders: make(map[string]string), - OrdinaryHeaders: make(map[string]string), - } - - for k, v := range md { - val := strings.Join(v, ", ") - if strings.HasPrefix(k, ":") { - hdr.PseudoHeaders[k] = val - } else { - hdr.OrdinaryHeaders[k] = val - } - } - - if !isResponse { - if _, ok := hdr.PseudoHeaders[":method"]; !ok { - hdr.PseudoHeaders[":method"] = "POST" - } - if _, ok := hdr.PseudoHeaders[":scheme"]; !ok { - hdr.PseudoHeaders[":scheme"] = "http" - } - if _, ok := hdr.PseudoHeaders[":path"]; !ok { - hdr.PseudoHeaders[":path"] = fullMethod - } - hdr.OrdinaryHeaders["te"] = "trailers" // new – stable field - } else { - // if _, ok := hdr.PseudoHeaders[":status"]; !ok { - // hdr.PseudoHeaders[":status"] = "200" - // } - // if ct, ok := hdr.OrdinaryHeaders["content-type"]; ok && - // strings.HasPrefix(ct, "application/grpc") { - // hdr.OrdinaryHeaders["content-type"] = "application/grpc" - // } - } - return hdr -} diff --git a/keploy/pkg/core/proxy/integrations/grpcV2/replayconn.go b/keploy/pkg/core/proxy/integrations/grpcV2/replayconn.go deleted file mode 100644 index f0c77f1..0000000 --- a/keploy/pkg/core/proxy/integrations/grpcV2/replayconn.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build linux - -package grpcV2 - -import ( - "bytes" - "net" -) - -// replayConn first serves the bytes in buf, then falls through to Conn. -type replayConn struct { - net.Conn - buf *bytes.Reader -} - -func newReplayConn(initial []byte, c net.Conn) net.Conn { - return &replayConn{ - Conn: c, - buf: bytes.NewReader(initial), - } -} - -func (r *replayConn) Read(p []byte) (int, error) { - if r.buf.Len() > 0 { - return r.buf.Read(p) - } - return r.Conn.Read(p) -} diff --git a/keploy/pkg/core/proxy/integrations/http/README.md b/keploy/pkg/core/proxy/integrations/http/README.md deleted file mode 100755 index 1115002..0000000 --- a/keploy/pkg/core/proxy/integrations/http/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Http Package Documentation - -The `http` package encompasses the parser and mapping logic required -to read HTTP text messages and capture or stub the outputs. Utilized -by the `hooks` package, it aids in redirecting outgoing calls for the -purpose of recording or stubbing the outputs. diff --git a/keploy/pkg/core/proxy/integrations/http/chunk.go b/keploy/pkg/core/proxy/integrations/http/chunk.go deleted file mode 100644 index b670d0c..0000000 --- a/keploy/pkg/core/proxy/integrations/http/chunk.go +++ /dev/null @@ -1,348 +0,0 @@ -//go:build linux - -// Package http provides functionality for handling HTTP outgoing calls. -package http - -import ( - "context" - "fmt" - "io" - "net" - "strconv" - "strings" - "time" - - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func (h *HTTP) HandleChunkedRequests(ctx context.Context, finalReq *[]byte, clientConn, destConn net.Conn) error { - - if hasCompleteHeaders(*finalReq) { - h.Logger.Debug("this request has complete headers in the first chunk itself.") - } - - for !hasCompleteHeaders(*finalReq) { - h.Logger.Debug("couldn't get complete headers in first chunk so reading more chunks") - reqHeader, err := pUtil.ReadBytes(ctx, h.Logger, clientConn) - if err != nil { - utils.LogError(h.Logger, nil, "failed to read the request message from the client") - return err - } - // destConn is nil in case of test mode - if destConn != nil { - _, err = destConn.Write(reqHeader) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, nil, "failed to write request message to the destination server") - return err - } - } - - *finalReq = append(*finalReq, reqHeader...) - } - - lines := strings.Split(string(*finalReq), "\n") - var contentLengthHeader string - var transferEncodingHeader string - for _, line := range lines { - if strings.HasPrefix(line, "Content-Length:") { - contentLengthHeader = strings.TrimSpace(strings.TrimPrefix(line, "Content-Length:")) - break - } else if strings.HasPrefix(line, "Transfer-Encoding:") { - transferEncodingHeader = strings.TrimSpace(strings.TrimPrefix(line, "Transfer-Encoding:")) - break - } - } - - //Handle chunked requests - if contentLengthHeader != "" { - contentLength, err := strconv.Atoi(contentLengthHeader) - if err != nil { - utils.LogError(h.Logger, err, "failed to get the content-length header") - return fmt.Errorf("failed to handle chunked request") - } - //Get the length of the body in the request. - bodyLength := len(*finalReq) - strings.Index(string(*finalReq), "\r\n\r\n") - 4 - contentLength -= bodyLength - if contentLength > 0 { - err := h.contentLengthRequest(ctx, finalReq, clientConn, destConn, contentLength) - if err != nil { - return err - } - } - } else if transferEncodingHeader != "" { - // check if the initial request is the complete request. - if strings.HasSuffix(string(*finalReq), "0\r\n\r\n") { - return nil - } - if transferEncodingHeader == "chunked" { - err := h.chunkedRequest(ctx, finalReq, clientConn, destConn, transferEncodingHeader) - if err != nil { - return err - } - } - } - return nil -} - -// Handled chunked requests when content-length is given. -func (h *HTTP) contentLengthRequest(ctx context.Context, finalReq *[]byte, clientConn, destConn net.Conn, contentLength int) error { - for contentLength > 0 { - err := clientConn.SetReadDeadline(time.Now().Add(5 * time.Second)) - if err != nil { - utils.LogError(h.Logger, err, "failed to set the read deadline for the client conn") - return err - } - requestChunked, err := pUtil.ReadBytes(ctx, h.Logger, clientConn) - if err != nil { - if err == io.EOF { - utils.LogError(h.Logger, nil, "conn closed by the user client") - return err - } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - h.Logger.Info("Stopped getting data from the conn", zap.Error(err)) - break - } - utils.LogError(h.Logger, nil, "failed to read the response message from the destination server") - return err - } - h.Logger.Debug("This is a chunk of request[content-length]: " + string(requestChunked)) - *finalReq = append(*finalReq, requestChunked...) - contentLength -= len(requestChunked) - - // destConn is nil in case of test mode. - if destConn != nil { - _, err = destConn.Write(requestChunked) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, nil, "failed to write request message to the destination server") - return err - } - } - } - return nil -} - -// Handled chunked requests when transfer-encoding is given. -func (h *HTTP) chunkedRequest(ctx context.Context, finalReq *[]byte, clientConn, destConn net.Conn, _ string) error { - - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - //TODO: we have to implement a way to read the buffer chunk wise according to the chunk size (chunk size comes in hexadecimal) - // because it can happen that some chunks come after 5 seconds. - err := clientConn.SetReadDeadline(time.Now().Add(5 * time.Second)) - if err != nil { - utils.LogError(h.Logger, err, "failed to set the read deadline for the client conn") - return err - } - requestChunked, err := pUtil.ReadBytes(ctx, h.Logger, clientConn) - if err != nil { - if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - break - } - utils.LogError(h.Logger, nil, "failed to read the response message from the destination server") - return err - } - - *finalReq = append(*finalReq, requestChunked...) - // destConn is nil in case of test mode. - if destConn != nil { - _, err = destConn.Write(requestChunked) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, nil, "failed to write request message to the destination server") - return err - } - } - - //check if the initial request is completed - if strings.HasSuffix(string(requestChunked), "0\r\n\r\n") { - return nil - } - } - } -} - -func (h *HTTP) handleChunkedResponses(ctx context.Context, finalResp *[]byte, clientConn, destConn net.Conn, resp []byte) error { - - if hasCompleteHeaders(*finalResp) { - h.Logger.Debug("this response has complete headers in the first chunk itself.") - } - - for !hasCompleteHeaders(resp) { - h.Logger.Debug("couldn't get complete headers in first chunk so reading more chunks") - respHeader, err := pUtil.ReadBytes(ctx, h.Logger, destConn) - if err != nil { - if err == io.EOF { - h.Logger.Debug("received EOF from the server") - // if there is any buffer left before EOF, we must send it to the client and save this as mock - if len(respHeader) != 0 { - // write the response message to the user client - _, err = clientConn.Write(resp) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, nil, "failed to write response message to the user client") - return err - } - *finalResp = append(*finalResp, respHeader...) - } - return err - } - utils.LogError(h.Logger, nil, "failed to read the response message from the destination server") - return err - } - // write the response message to the user client - _, err = clientConn.Write(respHeader) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, nil, "failed to write response message to the user client") - return err - } - - *finalResp = append(*finalResp, respHeader...) - resp = append(resp, respHeader...) - } - - //Getting the content-length or the transfer-encoding header - var contentLengthHeader, transferEncodingHeader string - lines := strings.Split(string(resp), "\n") - for _, line := range lines { - if strings.HasPrefix(line, "Content-Length:") { - contentLengthHeader = strings.TrimSpace(strings.TrimPrefix(line, "Content-Length:")) - break - } else if strings.HasPrefix(line, "Transfer-Encoding:") { - transferEncodingHeader = strings.TrimSpace(strings.TrimPrefix(line, "Transfer-Encoding:")) - break - } - } - //Handle chunked responses - if contentLengthHeader != "" { - contentLength, err := strconv.Atoi(contentLengthHeader) - if err != nil { - utils.LogError(h.Logger, err, "failed to get the content-length header") - return fmt.Errorf("failed to handle chunked response") - } - bodyLength := len(resp) - strings.Index(string(resp), "\r\n\r\n") - 4 - contentLength -= bodyLength - if contentLength > 0 { - err := h.contentLengthResponse(ctx, finalResp, clientConn, destConn, contentLength) - if err != nil { - return err - } - } - } else if transferEncodingHeader != "" { - //check if the initial response is the complete response. - if strings.HasSuffix(string(*finalResp), "0\r\n\r\n") { - return nil - } - if transferEncodingHeader == "chunked" { - err := h.chunkedResponse(ctx, finalResp, clientConn, destConn) - if err != nil { - return err - } - } - } - return nil -} - -// Handled chunked responses when transfer-encoding is given. -func (h *HTTP) chunkedResponse(ctx context.Context, finalResp *[]byte, clientConn, destConn net.Conn) error { - isEOF := false - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - resp, err := pUtil.ReadBytes(ctx, h.Logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(h.Logger, err, "failed to read the response message from the destination server") - return err - } - isEOF = true - h.Logger.Debug("received EOF", zap.Error(err)) - if len(resp) == 0 { - h.Logger.Debug("exiting loop as response is complete") - break - } - } - - *finalResp = append(*finalResp, resp...) - // write the response message to the user client - _, err = clientConn.Write(resp) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, nil, "failed to write response message to the user client") - return err - } - - //In some cases need to write the response to the client - // where there is some response before getting the true EOF - if isEOF { - break - } - - if string(resp) == "0\r\n\r\n" { - return nil - } - } - } -} - -// Handled chunked responses when content-length is given. -func (h *HTTP) contentLengthResponse(ctx context.Context, finalResp *[]byte, clientConn, destConn net.Conn, contentLength int) error { - isEOF := false - for contentLength > 0 { - resp, err := pUtil.ReadBytes(ctx, h.Logger, destConn) - if err != nil { - if err == io.EOF { - isEOF = true - h.Logger.Debug("received EOF, conn closed by the destination server") - if len(resp) == 0 { - break - } - } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - h.Logger.Info("Stopped getting data from the conn", zap.Error(err)) - break - } else { - utils.LogError(h.Logger, nil, "failed to read the response message from the destination server") - return err - } - } - - h.Logger.Debug("This is a chunk of response[content-length]: " + string(resp)) - *finalResp = append(*finalResp, resp...) - contentLength -= len(resp) - - // write the response message to the user client - _, err = clientConn.Write(resp) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, nil, "failed to write response message to the user client") - return err - } - - if isEOF { - break - } - } - return nil -} diff --git a/keploy/pkg/core/proxy/integrations/http/decode.go b/keploy/pkg/core/proxy/integrations/http/decode.go deleted file mode 100644 index 2d91790..0000000 --- a/keploy/pkg/core/proxy/integrations/http/decode.go +++ /dev/null @@ -1,206 +0,0 @@ -//go:build linux - -// Package http provides functionality for handling HTTP outgoing calls. -package http - -import ( - "bufio" - "bytes" - "context" - "errors" - "fmt" - "io" - "net" - "net/http" - "strconv" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// Decodes the mocks in test mode so that they can be sent to the user application. -func (h *HTTP) decodeHTTP(ctx context.Context, reqBuf []byte, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { - errCh := make(chan error, 1) - go func(errCh chan error, reqBuf []byte, opts models.OutgoingOptions) { - defer pUtil.Recover(h.Logger, clientConn, nil) - defer close(errCh) - for { - //Check if the expected header is present - if bytes.Contains(reqBuf, []byte("Expect: 100-continue")) { - h.Logger.Debug("The expect header is present in the request buffer and writing the 100 continue response to the client") - //Send the 100 continue response - _, err := clientConn.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n")) - if err != nil { - if ctx.Err() != nil { - return - } - utils.LogError(h.Logger, err, "failed to write the 100 continue response to the user application") - errCh <- err - return - } - h.Logger.Debug("The 100 continue response has been sent to the user application") - //Read the request buffer again - newRequest, err := pUtil.ReadBytes(ctx, h.Logger, clientConn) - if err != nil { - utils.LogError(h.Logger, err, "failed to read the request buffer from the user application") - errCh <- err - return - } - //Append the new request buffer to the old request buffer - reqBuf = append(reqBuf, newRequest...) - } - - h.Logger.Debug("handling the chunked requests to read the complete request") - err := h.HandleChunkedRequests(ctx, &reqBuf, clientConn, nil) - if err != nil { - utils.LogError(h.Logger, err, "failed to handle chunked requests") - errCh <- err - return - } - - h.Logger.Debug(fmt.Sprintf("This is the complete request:\n%v", string(reqBuf))) - - //Parse the request buffer - request, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(reqBuf))) - if err != nil { - utils.LogError(h.Logger, err, "failed to parse the http request message") - errCh <- err - return - } - // Set the host header explicitely because the `http.ReadRequest`` trim the host header - // func ReadRequest(b *bufio.Reader) (*Request, error) { - // req, err := readRequest(b) - // if err != nil { - // return nil, err - // } - - // delete(req.Header, "Host") - // return req, err - // } - request.Header.Set("Host", request.Host) - - reqBody, err := io.ReadAll(request.Body) - if err != nil { - utils.LogError(h.Logger, err, "failed to read from request body", zap.Any("metadata", utils.GetReqMeta(request))) - errCh <- err - return - } - - input := &req{ - method: request.Method, - url: request.URL, - header: request.Header, - body: reqBody, - raw: reqBuf, - } - - if input.header.Get("Content-Encoding") != "" { - input.body, err = pkg.Decompress(h.Logger, input.header.Get("Content-Encoding"), input.body) - if err != nil { - utils.LogError(h.Logger, err, "failed to decode the http request body", zap.Any("metadata", utils.GetReqMeta(request))) - errCh <- err - return - } - } - - ok, stub, err := h.match(ctx, input, mockDb) // calling match function to match mocks - if err != nil { - utils.LogError(h.Logger, err, "error while matching http mocks", zap.Any("metadata", utils.GetReqMeta(request))) - errCh <- err - return - } - h.Logger.Debug("after matching the http request", zap.Any("isMatched", ok), zap.Any("stub", stub), zap.Error(err)) - - if !ok { - if !utils.IsPassThrough(h.Logger, request, dstCfg.Port, opts) { - utils.LogError(h.Logger, nil, "Didn't match any preExisting http mock", zap.Any("metadata", utils.GetReqMeta(request))) - } - if opts.FallBackOnMiss { - _, err = pUtil.PassThrough(ctx, h.Logger, clientConn, dstCfg, [][]byte{reqBuf}) - if err != nil { - utils.LogError(h.Logger, err, "failed to passThrough http request", zap.Any("metadata", utils.GetReqMeta(request))) - errCh <- err - return - } - } - errCh <- nil - return - } - - if stub == nil { - utils.LogError(h.Logger, nil, "matched mock is nil", zap.Any("metadata", utils.GetReqMeta(request))) - errCh <- errors.New("matched mock is nil") - return - } - - statusLine := fmt.Sprintf("HTTP/%d.%d %d %s\r\n", stub.Spec.HTTPReq.ProtoMajor, stub.Spec.HTTPReq.ProtoMinor, stub.Spec.HTTPResp.StatusCode, http.StatusText(stub.Spec.HTTPResp.StatusCode)) - - body := stub.Spec.HTTPResp.Body - var respBody string - var responseString string - - // Fetching the response headers - header := pkg.ToHTTPHeader(stub.Spec.HTTPResp.Header) - - //Check if the content encoding is present in the header - if encoding, ok := header["Content-Encoding"]; ok && len(encoding) > 0 { - compressedBody, err := pkg.Compress(h.Logger, encoding[0], []byte(body)) - if err != nil { - utils.LogError(h.Logger, err, "failed to compress the response body", zap.Any("metadata", utils.GetReqMeta(request))) - errCh <- err - return - } - h.Logger.Debug("the length of the response body: " + strconv.Itoa(len(compressedBody))) - respBody = string(compressedBody) - } else { - respBody = body - } - - var headers string - for key, values := range header { - if key == "Content-Length" { - values = []string{strconv.Itoa(len(respBody))} - } - for _, value := range values { - headerLine := fmt.Sprintf("%s: %s\r\n", key, value) - headers += headerLine - } - } - responseString = statusLine + headers + "\r\n" + "" + respBody - - h.Logger.Debug(fmt.Sprintf("Mock Response sending back to client:\n%v", responseString)) - - _, err = clientConn.Write([]byte(responseString)) - if err != nil { - if ctx.Err() != nil { - return - } - utils.LogError(h.Logger, err, "failed to write the mock output to the user application", zap.Any("metadata", utils.GetReqMeta(request))) - errCh <- err - return - } - - reqBuf, err = pUtil.ReadBytes(ctx, h.Logger, clientConn) - if err != nil { - h.Logger.Debug("failed to read the request buffer from the client", zap.Error(err)) - h.Logger.Debug("This was the last response from the server:\n" + string(responseString)) - errCh <- nil - return - } - } - }(errCh, reqBuf, opts) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - return nil - } - return err - } -} diff --git a/keploy/pkg/core/proxy/integrations/http/encode.go b/keploy/pkg/core/proxy/integrations/http/encode.go deleted file mode 100644 index 3cbfe32..0000000 --- a/keploy/pkg/core/proxy/integrations/http/encode.go +++ /dev/null @@ -1,262 +0,0 @@ -//go:build linux - -package http - -import ( - "context" - "errors" - "fmt" - "io" - "net" - "strings" - "time" - - "golang.org/x/sync/errgroup" - - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// encodeHTTP function parses the HTTP request and response text messages to capture outgoing network calls as mocks. -func (h *HTTP) encodeHTTP(ctx context.Context, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - - remoteAddr := destConn.RemoteAddr().(*net.TCPAddr) - destPort := uint(remoteAddr.Port) - - //Writing the request to the server. - _, err := destConn.Write(reqBuf) - if err != nil { - h.Logger.Error("failed to write request message to the destination server", zap.Error(err)) - return err - } - - if ctx.Err() != nil { - return ctx.Err() - } - - h.Logger.Debug("This is the initial request: " + string(reqBuf)) - var finalReq []byte - errCh := make(chan error, 1) - - finalReq = append(finalReq, reqBuf...) - - //get the error group from the context - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context") - } - - //for keeping conn alive - g.Go(func() error { - defer pUtil.Recover(h.Logger, clientConn, destConn) - defer close(errCh) - for { - //check if expect : 100-continue header is present - lines := strings.Split(string(finalReq), "\n") - var expectHeader string - for _, line := range lines { - if strings.HasPrefix(line, "Expect:") { - expectHeader = strings.TrimSpace(strings.TrimPrefix(line, "Expect:")) - break - } - } - if expectHeader == "100-continue" { - //Read if the response from the server is 100-continue - resp, err := pUtil.ReadBytes(ctx, h.Logger, destConn) - if err != nil { - utils.LogError(h.Logger, err, "failed to read the response message from the server after 100-continue request") - errCh <- err - return nil - } - - // write the response message to the client - _, err = clientConn.Write(resp) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, err, "failed to write response message to the user client") - errCh <- err - return nil - } - - h.Logger.Debug("This is the response from the server after the expect header" + string(resp)) - - if string(resp) != "HTTP/1.1 100 Continue\r\n\r\n" { - utils.LogError(h.Logger, nil, "failed to get the 100 continue response from the user client") - errCh <- err - return nil - } - //Reading the request buffer again - reqBuf, err = pUtil.ReadBytes(ctx, h.Logger, clientConn) - if err != nil { - utils.LogError(h.Logger, err, "failed to read the request buffer from the user client") - errCh <- err - return nil - } - // write the request message to the actual destination server - _, err = destConn.Write(reqBuf) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, err, "failed to write request message to the destination server") - errCh <- err - return nil - } - finalReq = append(finalReq, reqBuf...) - } - - // Capture the request timestamp - reqTimestampMock := time.Now() - - err := h.HandleChunkedRequests(ctx, &finalReq, clientConn, destConn) - if err != nil { - utils.LogError(h.Logger, err, "failed to handle chunked requests") - errCh <- err - return nil - } - - h.Logger.Debug(fmt.Sprintf("This is the complete request:\n%v", string(finalReq))) - // read the response from the actual server - resp, err := pUtil.ReadBytes(ctx, h.Logger, destConn) - if err != nil { - if err == io.EOF { - h.Logger.Debug("Response complete, exiting the loop.") - // if there is any buffer left before EOF, we must send it to the client and save this as mock - if len(resp) != 0 { - // Capturing the response timestamp - resTimestampMock := time.Now() - // write the response message to the user client - _, err = clientConn.Write(resp) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, err, "failed to write response message to the user client") - errCh <- err - return nil - } - - // saving last request/response on this conn. - m := &FinalHTTP{ - Req: finalReq, - Resp: resp, - ReqTimestampMock: reqTimestampMock, - ResTimestampMock: resTimestampMock, - } - err := h.parseFinalHTTP(ctx, m, destPort, mocks, opts) - if err != nil { - utils.LogError(h.Logger, err, "failed to parse the final http request and response") - errCh <- err - return nil - } - } - break - } - utils.LogError(h.Logger, err, "failed to read the response message from the destination server") - errCh <- err - return nil - } - - // Capturing the response timestamp - resTimestampMock := time.Now() - - // write the response message to the user client - _, err = clientConn.Write(resp) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, err, "failed to write response message to the user client") - errCh <- err - return nil - } - var finalResp []byte - finalResp = append(finalResp, resp...) - h.Logger.Debug("This is the initial response: " + string(resp)) - - err = h.handleChunkedResponses(ctx, &finalResp, clientConn, destConn, resp) - if err != nil { - if err == io.EOF { - h.Logger.Debug("conn closed by the server", zap.Error(err)) - //check if before EOF complete response came, and try to parse it. - m := &FinalHTTP{ - Req: finalReq, - Resp: finalResp, - ReqTimestampMock: reqTimestampMock, - ResTimestampMock: resTimestampMock, - } - parseErr := h.parseFinalHTTP(ctx, m, destPort, mocks, opts) - if parseErr != nil { - utils.LogError(h.Logger, parseErr, "failed to parse the final http request and response") - errCh <- parseErr - } - errCh <- nil - return nil - } - utils.LogError(h.Logger, err, "failed to handle chunk response") - errCh <- err - return nil - } - - h.Logger.Debug("This is the final response: " + string(finalResp)) - - m := &FinalHTTP{ - Req: finalReq, - Resp: finalResp, - ReqTimestampMock: reqTimestampMock, - ResTimestampMock: resTimestampMock, - } - - err = h.parseFinalHTTP(ctx, m, destPort, mocks, opts) - if err != nil { - utils.LogError(h.Logger, err, "failed to parse the final http request and response") - errCh <- err - return nil - } - - //resetting for the new request and response. - finalReq = []byte("") - finalResp = []byte("") - - // read the request from the same connection - h.Logger.Debug("Reading the request from the user client again from the same connection") - - finalReq, err = pUtil.ReadBytes(ctx, h.Logger, clientConn) - if err != nil { - if err != io.EOF { - h.Logger.Debug("failed to read the request message from the user client", zap.Error(err)) - h.Logger.Debug("This was the last response from the server: " + string(resp)) - errCh <- nil - return nil - } - errCh <- err - return nil - } - // write the request message to the actual destination server - _, err = destConn.Write(finalReq) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(h.Logger, err, "failed to write request message to the destination server") - errCh <- err - return nil - } - } - return nil - }) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - return nil - } - return err - } -} diff --git a/keploy/pkg/core/proxy/integrations/http/http.go b/keploy/pkg/core/proxy/integrations/http/http.go deleted file mode 100755 index b2d3d38..0000000 --- a/keploy/pkg/core/proxy/integrations/http/http.go +++ /dev/null @@ -1,212 +0,0 @@ -//go:build linux - -package http - -import ( - "bufio" - "bytes" - "context" - "fmt" - "io" - "net" - "net/http" - "strconv" - "time" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/utils" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -func init() { - integrations.Register(integrations.HTTP, &integrations.Parsers{ - Initializer: New, Priority: 100, - }) -} - -type HTTP struct { - Logger *zap.Logger - //opts globalOptions //other global options set by the proxy -} - -func New(logger *zap.Logger) integrations.Integrations { - return &HTTP{ - Logger: logger, - } -} - -type FinalHTTP struct { - Req []byte - Resp []byte - ReqTimestampMock time.Time - ResTimestampMock time.Time -} - -// MatchType function determines if the outgoing network call is HTTP by comparing the -// message format with that of an HTTP text message. -func (h *HTTP) MatchType(_ context.Context, buf []byte) bool { - isHTTP := bytes.HasPrefix(buf[:], []byte("HTTP/")) || - bytes.HasPrefix(buf[:], []byte("GET ")) || - bytes.HasPrefix(buf[:], []byte("POST ")) || - bytes.HasPrefix(buf[:], []byte("PUT ")) || - bytes.HasPrefix(buf[:], []byte("PATCH ")) || - bytes.HasPrefix(buf[:], []byte("DELETE ")) || - bytes.HasPrefix(buf[:], []byte("OPTIONS ")) || - bytes.HasPrefix(buf[:], []byte("HEAD ")) - h.Logger.Debug(fmt.Sprintf("is Http Protocol?: %v ", isHTTP)) - return isHTTP -} - -func (h *HTTP) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - logger := h.Logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - - h.Logger.Debug("Recording the outgoing http call in record mode") - - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial http message") - return err - } - err = h.encodeHTTP(ctx, reqBuf, src, dst, mocks, opts) - if err != nil { - utils.LogError(logger, err, "failed to encode the http message into the yaml") - return err - } - return nil -} - -func (h *HTTP) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { - // h.Logger = h.Logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - h.Logger.Debug("Mocking the outgoing http call in test mode") - - reqBuf, err := util.ReadInitialBuf(ctx, h.Logger, src) - if err != nil { - utils.LogError(h.Logger, err, "failed to read the initial http message") - return err - } - - err = h.decodeHTTP(ctx, reqBuf, src, dstCfg, mockDb, opts) - if err != nil { - utils.LogError(h.Logger, err, "failed to decode the http message from the yaml") - return err - } - return nil -} - -// ParseFinalHTTP is used to parse the final http request and response and save it in a yaml file -func (h *HTTP) parseFinalHTTP(ctx context.Context, mock *FinalHTTP, destPort uint, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - var req *http.Request - // converts the request message buffer to http request - req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(mock.Req))) - if err != nil { - utils.LogError(h.Logger, err, "failed to parse the http request message") - return err - } - - // Set the host header explicitely because the `http.ReadRequest`` trim the host header - // func ReadRequest(b *bufio.Reader) (*Request, error) { - // req, err := readRequest(b) - // if err != nil { - // return nil, err - // } - - // delete(req.Header, "Host") - // return req, err - // } - req.Header.Set("Host", req.Host) - - var reqBody []byte - if req.Body != nil { // Read - var err error - reqBody, err = io.ReadAll(req.Body) - if err != nil { - // TODO right way to log errors - utils.LogError(h.Logger, err, "failed to read the http request body", zap.Any("metadata", utils.GetReqMeta(req))) - return err - } - - if req.Header.Get("Content-Encoding") != "" { - reqBody, err = pkg.Decompress(h.Logger, req.Header.Get("Content-Encoding"), reqBody) - if err != nil { - utils.LogError(h.Logger, err, "failed to decode the http request body", zap.Any("metadata", utils.GetReqMeta(req))) - return err - } - } - } - - // converts the response message buffer to http response - respParsed, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(mock.Resp)), req) - if err != nil { - utils.LogError(h.Logger, err, "failed to parse the http response message", zap.Any("metadata", utils.GetReqMeta(req))) - return err - } - - //Add the content length to the headers. - var respBody []byte - //Checking if the body of the response is empty or does not exist. - if respParsed.Body != nil { // Read - respBody, err = io.ReadAll(respParsed.Body) - if err != nil { - utils.LogError(h.Logger, err, "failed to read the the http response body", zap.Any("metadata", utils.GetReqMeta(req))) - return err - } - - if respParsed.Header.Get("Content-Encoding") != "" { - respBody, err = pkg.Decompress(h.Logger, respParsed.Header.Get("Content-Encoding"), respBody) - if err != nil { - utils.LogError(h.Logger, err, "failed to decode the http response body", zap.Any("metadata", utils.GetReqMeta(req))) - return err - } - } - - h.Logger.Debug("This is the response body: " + string(respBody)) - //Set the content length to the headers. - respParsed.Header.Set("Content-Length", strconv.Itoa(len(respBody))) - } - - // store the request and responses as mocks - meta := map[string]string{ - "name": "Http", - "type": models.HTTPClient, - "operation": req.Method, - "connID": ctx.Value(models.ClientConnectionIDKey).(string), - } - - // Check if the request is a passThrough request - if utils.IsPassThrough(h.Logger, req, destPort, opts) { - h.Logger.Debug("The request is a passThrough request", zap.Any("metadata", utils.GetReqMeta(req))) - return nil - } - - mocks <- &models.Mock{ - Version: models.GetVersion(), - Name: "mocks", - Kind: models.HTTP, - Spec: models.MockSpec{ - Metadata: meta, - HTTPReq: &models.HTTPReq{ - Method: models.Method(req.Method), - ProtoMajor: req.ProtoMajor, - ProtoMinor: req.ProtoMinor, - URL: req.URL.String(), - Header: pkg.ToYamlHTTPHeader(req.Header), - Body: string(reqBody), - URLParams: pkg.URLParams(req), - }, - HTTPResp: &models.HTTPResp{ - StatusCode: respParsed.StatusCode, - Header: pkg.ToYamlHTTPHeader(respParsed.Header), - Body: string(respBody), - }, - Created: time.Now().Unix(), - - ReqTimestampMock: mock.ReqTimestampMock, - ResTimestampMock: mock.ResTimestampMock, - }, - } - return nil -} diff --git a/keploy/pkg/core/proxy/integrations/http/match.go b/keploy/pkg/core/proxy/integrations/http/match.go deleted file mode 100644 index a168d59..0000000 --- a/keploy/pkg/core/proxy/integrations/http/match.go +++ /dev/null @@ -1,349 +0,0 @@ -//go:build linux - -package http - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/agnivade/levenshtein" - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type req struct { - method string - url *url.URL - header http.Header - body []byte - raw []byte -} - -func (h *HTTP) match(ctx context.Context, input *req, mockDb integrations.MockMemDb) (bool, *models.Mock, error) { - for { - if ctx.Err() != nil { - return false, nil, ctx.Err() - } - - // Fetch and filter HTTP mocks - mocks, err := mockDb.GetUnFilteredMocks() - - if err != nil { - utils.LogError(h.Logger, err, "failed to get unfilteredMocks mocks") - return false, nil, errors.New("error while matching the request with the mocks") - } - unfilteredMocks := FilterHTTPMocks(mocks) - - h.Logger.Debug(fmt.Sprintf("Length of unfilteredMocks:%v", len(unfilteredMocks))) - - // Matching process - schemaMatched, err := h.SchemaMatch(ctx, input, unfilteredMocks) - if err != nil { - return false, nil, err - } - - if len(schemaMatched) == 0 { - return false, nil, nil - } - - // Exact body match - ok, bestMatch := h.ExactBodyMatch(input.body, schemaMatched) - if ok { - if !h.updateMock(ctx, bestMatch, mockDb) { - continue - } - return true, bestMatch, nil - } - - shortListed := schemaMatched - // Schema match for JSON bodies - if pkg.IsJSON(input.body) { - bodyMatched, err := h.PerformBodyMatch(ctx, schemaMatched, input.body) - if err != nil { - return false, nil, err - } - - if len(bodyMatched) == 0 { - h.Logger.Debug("No mock found with body schema match") - return false, nil, nil - } - - if len(bodyMatched) == 1 { - if !h.updateMock(ctx, bodyMatched[0], mockDb) { - continue - } - return true, bodyMatched[0], nil - } - - // More than one match, perform fuzzy match - shortListed = bodyMatched - } - - h.Logger.Debug("Performing fuzzy match for req buffer") - // Perform fuzzy match on the request - isMatched, bestMatch := h.PerformFuzzyMatch(shortListed, input.raw) - if isMatched { - if !h.updateMock(ctx, bestMatch, mockDb) { - continue - } - return true, bestMatch, nil - } - return false, nil, nil - } -} - -// FilterHTTPMocks Filter mocks to only HTTP mocks -func FilterHTTPMocks(mocks []*models.Mock) []*models.Mock { - var httpMocks []*models.Mock - for _, mock := range mocks { - if mock.Kind != models.Kind(models.HTTP) { - continue - } - httpMocks = append(httpMocks, mock) - } - return httpMocks -} - -// MatchBodyType Body type match check (content type matching) -func (h *HTTP) MatchBodyType(mockBody string, reqBody []byte) bool { - if mockBody == "" && string(reqBody) == "" { - return true - } - mockBodyType := pkg.GuessContentType([]byte(mockBody)) - reqBodyType := pkg.GuessContentType(reqBody) - return mockBodyType == reqBodyType -} - -func (h *HTTP) MatchURLPath(mockURL, reqPath string) bool { - parsedURL, err := url.Parse(mockURL) - if err != nil { - return false - } - return parsedURL.Path == reqPath -} - -func (h *HTTP) MapsHaveSameKeys(map1 map[string]string, map2 map[string][]string) bool { - if len(map1) != len(map2) { - return false - } - - for key := range map1 { - lkey := strings.ToLower(key) - if lkey == "keploy-test-id" || lkey == "keploy-test-set-id" { - continue - } - if _, exists := map2[key]; !exists { - return false - } - } - - for key := range map2 { - lkey := strings.ToLower(key) - if lkey == "keploy-test-id" || lkey == "keploy-test-set-id" { - continue - } - if _, exists := map1[key]; !exists { - return false - } - } - - return true -} - -// SchemaMatch match the schema of the request with the mocks -func (h *HTTP) SchemaMatch(ctx context.Context, input *req, unfilteredMocks []*models.Mock) ([]*models.Mock, error) { - var schemaMatched []*models.Mock - - for _, mock := range unfilteredMocks { - if ctx.Err() != nil { - return nil, ctx.Err() - } - - // Content type check - if input.header.Get("Content-Type") != "" { - if input.header.Get("Content-Type") != mock.Spec.HTTPReq.Header["Content-Type"] { - h.Logger.Debug("The content type of mock and request aren't the same") - continue - } - } - // Body type check - if !h.MatchBodyType(mock.Spec.HTTPReq.Body, input.body) { - h.Logger.Debug("The body of mock and request aren't of same type") - continue - } - - // URL path match - if !h.MatchURLPath(mock.Spec.HTTPReq.URL, input.url.Path) { - h.Logger.Debug("The url path of mock and request aren't the same") - continue - } - - // HTTP method match - if mock.Spec.HTTPReq.Method != models.Method(input.method) { - h.Logger.Debug("The method of mock and request aren't the same") - continue - } - - // Header key match - if !h.MapsHaveSameKeys(mock.Spec.HTTPReq.Header, input.header) { - h.Logger.Debug("The header keys of mock and request aren't the same") - continue - } - - // Query parameter match - if !h.MapsHaveSameKeys(mock.Spec.HTTPReq.URLParams, input.url.Query()) { - h.Logger.Debug("The query params of mock and request aren't the same") - continue - } - - schemaMatched = append(schemaMatched, mock) - } - - return schemaMatched, nil -} - -// ExactBodyMatch Exact body match -func (h *HTTP) ExactBodyMatch(body []byte, schemaMatched []*models.Mock) (bool, *models.Mock) { - for _, mock := range schemaMatched { - if mock.Spec.HTTPReq.Body == string(body) { - return true, mock - } - } - return false, nil -} - -func (h *HTTP) bodyMatch(mockBody, reqBody []byte) (bool, error) { - - var mockData map[string]any - var reqData map[string]any - err := json.Unmarshal(mockBody, &mockData) - if err != nil { - utils.LogError(h.Logger, err, "failed to unmarshal the mock request body", zap.String("Req", string(mockBody))) - return false, err - } - err = json.Unmarshal(reqBody, &reqData) - if err != nil { - utils.LogError(h.Logger, err, "failed to unmarshal the request body", zap.String("Req", string(reqBody))) - return false, err - } - - for key := range mockData { - _, exists := reqData[key] - if !exists { - return false, nil - } - } - return true, nil -} - -// PerformBodyMatch Perform body match for JSON data -func (h *HTTP) PerformBodyMatch(ctx context.Context, schemaMatched []*models.Mock, reqBody []byte) ([]*models.Mock, error) { - h.Logger.Debug("Performing schema match for body") - - var bodyMatched []*models.Mock - for _, mock := range schemaMatched { - if ctx.Err() != nil { - return nil, ctx.Err() - } - - ok, err := h.bodyMatch([]byte(mock.Spec.HTTPReq.Body), reqBody) - if err != nil { - h.Logger.Error("failed to do schema matching on request body", zap.Error(err)) - break - } - - if ok { - bodyMatched = append(bodyMatched, mock) - h.Logger.Debug("found a mock with body schema match") - } - } - return bodyMatched, nil -} - -// Fuzzy match helper for string matching -func (h *HTTP) findStringMatch(req string, mockStrings []string) int { - minDist := int(^uint(0) >> 1) - bestMatch := -1 - for idx, mock := range mockStrings { - if !util.IsASCII(mock) { - continue - } - dist := levenshtein.ComputeDistance(req, mock) - if dist == 0 { - return 0 - } - if dist < minDist { - minDist = dist - bestMatch = idx - } - } - return bestMatch -} - -// TODO: generalize the function to work with any type of integration -func (h *HTTP) findBinaryMatch(mocks []*models.Mock, reqBuff []byte) int { - - mxSim := -1.0 - mxIdx := -1 - // find the fuzzy hash of the mocks - for idx, mock := range mocks { - encoded, _ := decode(mock.Spec.HTTPReq.Body) - k := util.AdaptiveK(len(reqBuff), 3, 8, 5) - shingles1 := util.CreateShingles(encoded, k) - shingles2 := util.CreateShingles(reqBuff, k) - similarity := util.JaccardSimilarity(shingles1, shingles2) - - // log.Debugf("Jaccard Similarity:%f\n", similarity) - - if mxSim < similarity { - mxSim = similarity - mxIdx = idx - } - } - return mxIdx -} - -// PerformFuzzyMatch Perform fuzzy match on the request -func (h *HTTP) PerformFuzzyMatch(tcsMocks []*models.Mock, reqBuff []byte) (bool, *models.Mock) { - encodedReq := encode(reqBuff) - for _, mock := range tcsMocks { - encodedMock, _ := decode(mock.Spec.HTTPReq.Body) - if string(encodedMock) == string(reqBuff) || mock.Spec.HTTPReq.Body == encodedReq { - return true, mock - } - } - // String-based fuzzy matching - mockStrings := make([]string, len(tcsMocks)) - for i := range tcsMocks { - mockStrings[i] = tcsMocks[i].Spec.HTTPReq.Body - } - if util.IsASCII(string(reqBuff)) { - idx := h.findStringMatch(string(reqBuff), mockStrings) - if idx != -1 { - return true, tcsMocks[idx] - } - } - idx := h.findBinaryMatch(tcsMocks, reqBuff) - if idx != -1 { - return true, tcsMocks[idx] - } - return false, nil -} - -// Update the matched mock (delete or update) -func (h *HTTP) updateMock(_ context.Context, matchedMock *models.Mock, mockDb integrations.MockMemDb) bool { - originalMatchedMock := *matchedMock - matchedMock.TestModeInfo.IsFiltered = false - matchedMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() - updated := mockDb.UpdateUnFilteredMock(&originalMatchedMock, matchedMock) - return updated -} diff --git a/keploy/pkg/core/proxy/integrations/http/util.go b/keploy/pkg/core/proxy/integrations/http/util.go deleted file mode 100644 index 71b37f2..0000000 --- a/keploy/pkg/core/proxy/integrations/http/util.go +++ /dev/null @@ -1,46 +0,0 @@ -package http - -import ( - "bufio" - "bytes" - "io" - - "go.uber.org/zap" -) - -// Checks if the response is gzipped -func isGZipped(check io.ReadCloser, l *zap.Logger) (bool, *bufio.Reader) { - bufReader := bufio.NewReader(check) - peekedBytes, err := bufReader.Peek(2) - if err != nil && err != io.EOF { - l.Debug("failed to peek the response", zap.Error(err)) - return false, nil - } - if len(peekedBytes) < 2 { - return false, nil - } - if peekedBytes[0] == 0x1f && peekedBytes[1] == 0x8b { - return true, bufReader - } - return false, nil -} - -// hasCompleteHeaders checks if the given byte slice contains the complete HTTP headers -func hasCompleteHeaders(httpChunk []byte) bool { - // Define the sequence for header end: "\r\n\r\n" - headerEndSequence := []byte{'\r', '\n', '\r', '\n'} - - // Check if the byte slice contains the header end sequence - return bytes.Contains(httpChunk, headerEndSequence) -} - -func encode(buffer []byte) string { - //Encode the buffer to string - encoded := string(buffer) - return encoded -} -func decode(encoded string) ([]byte, error) { - // decode the string to a buffer. - data := []byte(encoded) - return data, nil -} diff --git a/keploy/pkg/core/proxy/integrations/integrations.go b/keploy/pkg/core/proxy/integrations/integrations.go deleted file mode 100644 index ffe162e..0000000 --- a/keploy/pkg/core/proxy/integrations/integrations.go +++ /dev/null @@ -1,54 +0,0 @@ -//go:build linux - -// Package integrations provides functionality for integrating different types of services. -package integrations - -import ( - "context" - "net" - - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -type Initializer func(logger *zap.Logger) Integrations - -type IntegrationType string - -// constants for different types of integrations -const ( - HTTP IntegrationType = "http" - GRPC IntegrationType = "grpc" - GENERIC IntegrationType = "generic" - MYSQL IntegrationType = "mysql" - POSTGRES_V1 IntegrationType = "postgres_v1" - POSTGRES_V2 IntegrationType = "postgres_v2" - MONGO IntegrationType = "mongo" - REDIS IntegrationType = "redis" -) - -type Parsers struct { - Initializer Initializer - Priority int -} - -var Registered = make(map[IntegrationType]*Parsers) - -type Integrations interface { - MatchType(ctx context.Context, reqBuf []byte) bool - RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error - MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb MockMemDb, opts models.OutgoingOptions) error -} - -func Register(name IntegrationType, p *Parsers) { - Registered[name] = p -} - -type MockMemDb interface { - GetFilteredMocks() ([]*models.Mock, error) - GetUnFilteredMocks() ([]*models.Mock, error) - UpdateUnFilteredMock(old *models.Mock, new *models.Mock) bool - DeleteFilteredMock(mock models.Mock) bool - DeleteUnFilteredMock(mock models.Mock) bool - GetMySQLCounts() (total, config, data int) -} diff --git a/keploy/pkg/core/proxy/integrations/mongo/README.md b/keploy/pkg/core/proxy/integrations/mongo/README.md deleted file mode 100644 index c9ea2e5..0000000 --- a/keploy/pkg/core/proxy/integrations/mongo/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Mongo Package Documentation - -The `mongo` package encompasses the parser and mapping logic required -to read MongoDB wire messages and capture or stub the outputs. -Utilized by the `hooks` package, it assists in redirecting outgoing -calls for the purpose of recording or stubbing the outputs. \ No newline at end of file diff --git a/keploy/pkg/core/proxy/integrations/mongo/command.go b/keploy/pkg/core/proxy/integrations/mongo/command.go deleted file mode 100644 index 4d34d1e..0000000 --- a/keploy/pkg/core/proxy/integrations/mongo/command.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build linux - -// Package mongo provides functionality for working with MongoDB outgoing calls. -package mongo - -// This file contains code from the coinbase mongobetween -// https://github.com/coinbase/mongobetween/blob/1034c5a0c3f10cb1dd84af2981bc55ea1d3b45c0/mongo/command.go#L10 -import ( - "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" -) - -type Command string - -// constants for all the commands that can be proxied -const ( - Unknown Command = "unknown" - AbortTransaction Command = "abortTransaction" - Aggregate Command = "aggregate" - CommitTransaction Command = "commandTransaction" - Count Command = "count" - CreateIndexes Command = "createIndexes" - Delete Command = "delete" - Distinct Command = "distinct" - Drop Command = "drop" - DropDatabase Command = "dropDatabase" - DropIndexes Command = "dropIndexes" - EndSessions Command = "endSessions" - Find Command = "find" - FindAndModify Command = "findAndModify" - GetMore Command = "getMore" - Insert Command = "insert" - IsMaster Command = "isMaster" - Ismaster Command = "ismaster" - ListCollections Command = "listCollections" - ListIndexes Command = "listIndexes" - ListDatabases Command = "listDatabases" - MapReduce Command = "mapReduce" - Update Command = "tools" -) - -var collectionCommands = []Command{Aggregate, Count, CreateIndexes, Delete, Distinct, Drop, DropIndexes, Find, FindAndModify, Insert, ListIndexes, MapReduce, Update} -var int32Commands = []Command{AbortTransaction, Aggregate, CommitTransaction, DropDatabase, IsMaster, Ismaster, ListCollections, ListDatabases} -var int64Commands = []Command{GetMore} -var arrayCommands = []Command{EndSessions} - -func IsWrite(command Command) bool { - switch command { - case CommitTransaction, CreateIndexes, Delete, Drop, DropIndexes, DropDatabase, FindAndModify, Insert, Update: - return true - } - return false -} - -func CommandAndCollection(msg bsoncore.Document) (Command, string) { - for _, s := range collectionCommands { - if coll, ok := msg.Lookup(string(s)).StringValueOK(); ok { - return s, coll - } - } - for _, s := range int32Commands { - value := msg.Lookup(string(s)) - if value.Data != nil { - return s, "" - } - } - for _, s := range int64Commands { - value := msg.Lookup(string(s)) - if value.Data != nil { - if coll, ok := msg.Lookup("collection").StringValueOK(); ok { - return s, coll - } - return s, "" - } - } - for _, s := range arrayCommands { - value := msg.Lookup(string(s)) - if value.Data != nil { - return s, "" - } - } - return Unknown, "" -} - -func IsIsMasterDoc(doc bsoncore.Document) bool { - isMaster := doc.Lookup(string(IsMaster)) - ismaster := doc.Lookup(string(Ismaster)) - return IsIsMasterValueTruthy(isMaster) || IsIsMasterValueTruthy(ismaster) -} - -func IsIsMasterValueTruthy(val bsoncore.Value) bool { - if intValue, isInt := val.Int32OK(); intValue > 0 { - return true - } else if !isInt { - boolValue, isBool := val.BooleanOK() - return boolValue && isBool - } - return false -} diff --git a/keploy/pkg/core/proxy/integrations/mongo/decode.go b/keploy/pkg/core/proxy/integrations/mongo/decode.go deleted file mode 100644 index 7973a38..0000000 --- a/keploy/pkg/core/proxy/integrations/mongo/decode.go +++ /dev/null @@ -1,345 +0,0 @@ -//go:build linux - -package mongo - -import ( - "context" - "errors" - "fmt" - "io" - "net" - "strconv" - "time" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" - "go.uber.org/zap" -) - -// decodeMongo decodes the mongo wire message from the client connection -// and sends the response back to the client. -func decodeMongo(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { - startedDecoding := time.Now() - requestBuffers := [][]byte{reqBuf} - - errCh := make(chan error, 1) - - go func(errCh chan error, reqBuf []byte, startedDecoding time.Time, requestBuffers [][]byte) { - defer util.Recover(logger, clientConn, nil) - defer close(errCh) - var readRequestDelay time.Duration - var err error - for { - - var ( - mongoRequests []models.MongoRequest // stores the request packet - ) - // check to read the request buffer from the client connection after the initial packet - if string(reqBuf) == "read form client conn" { - started := time.Now() - // reads the first chunk of the mongo request - reqBuf, err = util.ReadBytes(ctx, logger, clientConn) - if err != nil { - if err == io.EOF { - logger.Debug("received request buffer is empty in test mode for mongo calls") - errCh <- err - return - } - utils.LogError(logger, err, "failed to read request from the mongo client") - errCh <- err - return - } - requestBuffers = append(requestBuffers, reqBuf) - logger.Debug("the request from the mongo client", zap.Any("buffer", reqBuf)) - readRequestDelay = time.Since(started) - } - if len(reqBuf) == 0 { - errCh <- errors.New("the request buffer is empty") - return - } - logger.Debug(fmt.Sprintf("the loop starts with the time delay: %v", time.Since(startedDecoding))) - // convert the request buffer to the mongo wire message in the go struct - opReq, requestHeader, mongoRequest, err := Decode(reqBuf, logger) - if err != nil { - utils.LogError(logger, err, "failed to decode the mongo wire message from the client") - errCh <- err - return - } - mongoRequests = append(mongoRequests, models.MongoRequest{ - Header: &requestHeader, - Message: mongoRequest, - ReadDelay: int64(readRequestDelay), - }) - // check for the more_to_come flag bit in the mongo request - // header to read the next chunks of the request - if val, ok := mongoRequest.(*models.MongoOpMessage); ok && hasSecondSetBit(val.FlagBits) { - for { - started := time.Now() - logger.Debug("into the for loop for request stream") - // reads the next chunk of the mongo request - requestBuffer1, err := util.ReadBytes(ctx, logger, clientConn) - if err != nil { - if err == io.EOF { - logger.Debug("received request buffer is empty for streaming mongo request call") - errCh <- err - return - } - utils.LogError(logger, err, "failed to read request from the mongo client", zap.String("mongo server address", dstCfg.Addr)) - errCh <- err - return - } - requestBuffers = append(requestBuffers, reqBuf) - readRequestDelay = time.Since(started) - - if len(requestBuffer1) == 0 { - logger.Debug("the response from the server is complete") - break - } - // convert the request buffer to the mongo response wire message in the go struct - _, reqHeader, mongoReq, err := Decode(requestBuffer1, logger) - if err != nil { - utils.LogError(logger, err, "failed to decode the mongo wire message from the mongo client") - errCh <- err - return - } - if mongoReqVal, ok := mongoReq.(models.MongoOpMessage); ok && !hasSecondSetBit(mongoReqVal.FlagBits) { - logger.Debug("the request from the client is complete since the more_to_come flagbit is 0") - break - } - mongoRequests = append(mongoRequests, models.MongoRequest{ - Header: &reqHeader, - Message: mongoReq, - ReadDelay: int64(readRequestDelay), - }) - } - } - // check for the heartbeat request from client and use the config mocks to respond - if isHeartBeat(logger, opReq, *mongoRequests[0].Header, mongoRequests[0].Message) { - // isScramAuth(logger, opReq) { - var bestMatchIndex = -1 - var maxMatchScore = 0.0 - var configMocks []*models.Mock - for { - configMocks, err = mockDb.GetUnFilteredMocks() - if err != nil { - utils.LogError(logger, err, "error while getting config mock") - } - logger.Debug(fmt.Sprintf("the config mocks are: %v", configMocks)) - maxMatchScore = 0.0 - bestMatchIndex = -1 - // loop over the recorded config mocks to match with the incoming request - for configIndex, configMock := range configMocks { - logger.Debug("the config mock is: ", zap.Any("config mock", configMock), zap.Any("actual request", mongoRequests)) - // checking the number of chunks for recorded config mocks and the incoming request - if len(configMock.Spec.MongoRequests) != len(mongoRequests) { - continue - } - - for i, req := range configMock.Spec.MongoRequests { - // check the opcode of the incoming request and the recorded config mocks - if len(configMock.Spec.MongoRequests) != len(mongoRequests) || req.Header.Opcode != mongoRequests[i].Header.Opcode { - continue - } - switch req.Header.Opcode { - case wiremessage.OpQuery: - // check the query fields of the incoming request and the recorded config mocks - expectedQuery := req.Message.(*models.MongoOpQuery) - actualQuery := mongoRequests[i].Message.(*models.MongoOpQuery) - if actualQuery.FullCollectionName != expectedQuery.FullCollectionName || - actualQuery.ReturnFieldsSelector != expectedQuery.ReturnFieldsSelector || - actualQuery.Flags != expectedQuery.Flags || - actualQuery.NumberToReturn != expectedQuery.NumberToReturn || - actualQuery.NumberToSkip != expectedQuery.NumberToSkip { - continue - } - - // calculate the matching score for query bson dcouments of the incoming request and the recorded config mocks - expected := map[string]interface{}{} - actual := map[string]interface{}{} - err = bson.UnmarshalExtJSON([]byte(expectedQuery.Query), true, &expected) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the section of recorded request to bson document") - continue - } - err = bson.UnmarshalExtJSON([]byte(actualQuery.Query), true, &actual) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the section of incoming request to bson document") - continue - } - score := calculateMatchingScore(expected, actual) - logger.Debug("the expected and actual msg in the heartbeat OpQuery query.", zap.Any("expected", expected), zap.Any("actual", actual), zap.Any("score", score)) - if score > maxMatchScore { - maxMatchScore = score - bestMatchIndex = configIndex - } - - case wiremessage.OpMsg: - // check the OpMsg sections of the incoming request and the recorded config mocks - if req.Message.(*models.MongoOpMessage).FlagBits != mongoRequests[i].Message.(*models.MongoOpMessage).FlagBits { - continue - } - scoreSum := 0.0 - if len(req.Message.(*models.MongoOpMessage).Sections) != len(mongoRequests[i].Message.(*models.MongoOpMessage).Sections) { - continue - } - // calculate the matching score for each section of the incoming request and the recorded config mocks - for sectionIndx, section := range req.Message.(*models.MongoOpMessage).Sections { - if len(req.Message.(*models.MongoOpMessage).Sections) == len(mongoRequests[i].Message.(*models.MongoOpMessage).Sections) { - score := compareOpMsgSection(logger, section, mongoRequests[i].Message.(*models.MongoOpMessage).Sections[sectionIndx]) - scoreSum += score - } - } - currentScore := scoreSum / float64(len(mongoRequests)) - logger.Debug("the expected and actual msg in the heartbeat OpMsg single section.", zap.Any("expected", req.Message.(*models.MongoOpMessage).Sections), zap.Any("actual", mongoRequests[i].Message.(*models.MongoOpMessage).Sections), zap.Any("score", currentScore)) - if currentScore > maxMatchScore { - maxMatchScore = currentScore - bestMatchIndex = configIndex - } - default: - utils.LogError(logger, err, "the OpCode of the mongo wiremessage is invalid.", zap.Any("opcode", req.Header.Opcode)) - } - } - } - - if bestMatchIndex != -1 && maxMatchScore != 0.0 { - matchedMock := *configMocks[bestMatchIndex] - matchedMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() - isUpdated := mockDb.UpdateUnFilteredMock(configMocks[bestMatchIndex], &matchedMock) - if !isUpdated { - continue - } - } - break - } - - responseTo := mongoRequests[0].Header.RequestID - if bestMatchIndex == -1 || maxMatchScore == 0.0 { - logger.Debug("the mongo request do not matches with any config mocks", zap.Any("request", mongoRequests)) - continue - } - - // write the mongo response to the client connection from the recorded config mocks that most matches the incoming request - for _, mongoResponse := range configMocks[bestMatchIndex].Spec.MongoResponses { - switch mongoResponse.Header.Opcode { - case wiremessage.OpReply: - replySpec := mongoResponse.Message.(*models.MongoOpReply) - replyMessage, err := encodeOpReply(ctx, mongoRequests[0], configMocks[bestMatchIndex].Spec.MongoRequests[0], replySpec, opts.MongoPassword, logger) - if err != nil { - utils.LogError(logger, err, "failed to encode the recorded OpReply yaml", zap.Any("for request with id", responseTo)) - errCh <- err - return - } - requestID := wiremessage.NextRequestID() - heathCheckReplyBuffer := replyMessage.Encode(responseTo, requestID) - responseTo = requestID - logger.Debug(fmt.Sprintf("the bufffer response is: %v", string(heathCheckReplyBuffer))) - // handle scram auth request - _, err = clientConn.Write(heathCheckReplyBuffer) - if err != nil { - if ctx.Err() != nil { - return - } - utils.LogError(logger, err, "failed to write the health check reply to mongo client") - errCh <- err - return - } - case wiremessage.OpMsg: - respMessage := mongoResponse.Message.(*models.MongoOpMessage) - - var expectedRequestSections []string - if len(configMocks[bestMatchIndex].Spec.MongoRequests) > 0 { - expectedRequestSections = configMocks[bestMatchIndex].Spec.MongoRequests[0].Message.(*models.MongoOpMessage).Sections - } - message, err := encodeOpMsg(ctx, respMessage, mongoRequest.(*models.MongoOpMessage).Sections, expectedRequestSections, opts.MongoPassword, logger) - if err != nil { - utils.LogError(logger, err, "failed to encode the recorded OpMsg response", zap.Any("for request with id", responseTo)) - errCh <- err - return - } - _, err = clientConn.Write(message.Encode(responseTo, wiremessage.NextRequestID())) - if err != nil { - if ctx.Err() != nil { - return - } - utils.LogError(logger, err, "failed to write the health check opmsg to mongo client") - errCh <- err - return - } - } - } - } else { - // handle for the non-heartbeat request from the client - - // match the incoming request with the recorded tcsMocks and return a mocked response which matches most with incoming request - matched, matchedMock, err := match(ctx, logger, mongoRequests, mockDb) - if err != nil { - errCh <- err - utils.LogError(logger, err, "error while matching mongo mocks") - return - } - if !matched { - logger.Debug("mongo request not matched with any tcsMocks", zap.Any("request", mongoRequests)) - reqBuf, err = util.PassThrough(ctx, logger, clientConn, dstCfg, requestBuffers) - if err != nil { - utils.LogError(logger, err, "failed to passthrough the mongo request to the actual database server") - errCh <- err - return - } - continue - } - - responseTo := mongoRequests[0].Header.RequestID - logger.Debug("the mock matched with the current request", zap.Any("mock", matchedMock), zap.Any("responseTo", responseTo)) - - // write the mongo response to the client connection from the recorded tcsMocks that most matches the incoming request - for _, resp := range matchedMock.Spec.MongoResponses { - respMessage := resp.Message.(*models.MongoOpMessage) - var expectedRequestSections []string - if len(matchedMock.Spec.MongoRequests) > 0 { - expectedRequestSections = matchedMock.Spec.MongoRequests[0].Message.(*models.MongoOpMessage).Sections - } - message, err := encodeOpMsg(ctx, respMessage, mongoRequest.(*models.MongoOpMessage).Sections, expectedRequestSections, opts.MongoPassword, logger) - if err != nil { - utils.LogError(logger, err, "failed to encode the recorded OpMsg response", zap.Any("for request with id", responseTo)) - errCh <- err - return - } - requestID := wiremessage.NextRequestID() - _, err = clientConn.Write(message.Encode(responseTo, requestID)) - if err != nil { - if ctx.Err() != nil { - return - } - utils.LogError(logger, err, "failed to write the health check opmsg to mongo client", zap.Any("for request with id", responseTo)) - errCh <- err - return - } - responseTo = requestID - } - } - logger.Debug("the length of the requestBuffer after matching: " + strconv.Itoa(len(reqBuf)) + strconv.Itoa(len(requestBuffers[0]))) - if len(requestBuffers) > 0 && len(reqBuf) == len(requestBuffers[0]) { - reqBuf = []byte("read form client conn") - } - - // Clear the buffer for the next dependency call - requestBuffers = [][]byte{} - } - }(errCh, reqBuf, startedDecoding, requestBuffers) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - logger.Debug("connection lost from client") - return nil - } - return err - } -} diff --git a/keploy/pkg/core/proxy/integrations/mongo/encode.go b/keploy/pkg/core/proxy/integrations/mongo/encode.go deleted file mode 100644 index e7b0840..0000000 --- a/keploy/pkg/core/proxy/integrations/mongo/encode.go +++ /dev/null @@ -1,288 +0,0 @@ -//go:build linux - -package mongo - -import ( - "context" - "errors" - "fmt" - "io" - "net" - "time" - - "golang.org/x/sync/errgroup" - - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// encodeMongo records the outgoing mongo messages of the client connection, -// decodes the wiremessage binary and writes readable string -// to the yaml file. -func (m *Mongo) encodeMongo(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, _ models.OutgoingOptions) error { - - errCh := make(chan error, 1) - - //get the error group from the context - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context") - } - - g.Go(func() error { - defer pUtil.Recover(logger, clientConn, destConn) - defer close(errCh) - for { - var err error - var readRequestDelay time.Duration - - // reads the request packets from the client connection after the first request packet. - // Since, that is already read in the RecordOutgoing function. - if string(reqBuf) == "read form client conn" { - started := time.Now() - reqBuf, err = pUtil.ReadBytes(ctx, logger, clientConn) - logger.Debug("reading from the mongo conn", zap.Any("", string(reqBuf))) - if err != nil { - if err == io.EOF { - logger.Debug("received request buffer is empty in record mode for mongo call") - errCh <- err - return nil - } - utils.LogError(logger, err, "failed to read request from the mongo client", zap.String("mongo client address", clientConn.RemoteAddr().String())) - errCh <- err - return nil - } - readRequestDelay = time.Since(started) - // logStr += lstr - logger.Debug(fmt.Sprintf("the request in the mongo parser before passing to dest: %v", len(reqBuf))) - } - - var ( - mongoRequests []models.MongoRequest // stores the decoded binary packets for a request - mongoResponses []models.MongoResponse // stores the decoded binary packets for a response - ) - // decode the binary packet and store the values in the corresponding struct - opReq, requestHeader, mongoRequest, err := Decode(reqBuf, logger) - if err != nil { - utils.LogError(logger, err, "failed to decode the mongo wire message from the client") - errCh <- err - return nil - } - - mongoRequests = append(mongoRequests, models.MongoRequest{ - Header: &requestHeader, - Message: mongoRequest, - ReadDelay: int64(readRequestDelay), - }) - // forwards the request packet to the destination server - _, err = destConn.Write(reqBuf) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(logger, err, "failed to write the request buffer to mongo server", zap.String("mongo server address", destConn.RemoteAddr().String())) - errCh <- err - return nil - } - logger.Debug(fmt.Sprintf("the request in the mongo parser after passing to dest: %v", len(reqBuf))) - - // check for the request packet streaming for the mongo wire message - if val, ok := mongoRequest.(*models.MongoOpMessage); ok && hasSecondSetBit(val.FlagBits) { - for { - // read the streaming request packets - requestBuffer1, err := pUtil.ReadBytes(ctx, logger, clientConn) - if err != nil { - if err == io.EOF { - logger.Debug("received request buffer is empty in record mode for mongo request") - errCh <- err - return nil - } - utils.LogError(logger, err, "failed to read request from the mongo client", zap.String("mongo client address", clientConn.RemoteAddr().String())) - errCh <- err - return nil - } - - // forward the request packet to the destination server - _, err = destConn.Write(requestBuffer1) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(logger, err, "failed to write the reply message to mongo client") - errCh <- err - return nil - } - - if len(requestBuffer1) == 0 { - logger.Debug("the response from the server is complete") - break - } - // decode the binary packet and return the values in the corresponding structs - // for header and message. - _, reqHeader, mongoReq, err := Decode(requestBuffer1, logger) - if err != nil { - utils.LogError(logger, err, "failed to decode the mongo wire message from the destination server") - errCh <- err - return nil - } - if mongoReqVal, ok := mongoReq.(models.MongoOpMessage); ok && !hasSecondSetBit(mongoReqVal.FlagBits) { - logger.Debug("the request from the client is complete since the more_to_come flagbit is 0") - break - } - mongoRequests = append(mongoRequests, models.MongoRequest{ - Header: &reqHeader, - Message: mongoReq, - ReadDelay: int64(readRequestDelay), - }) - } - } - - reqTimestampMock := time.Now() - started := time.Now() - - // read reply message length from the destination mongo server - responsePckLengthBuffer, err := pUtil.ReadRequiredBytes(ctx, logger, destConn, 4) - if err != nil { - if err == io.EOF { - logger.Debug("received response buffer is empty in record mode for mongo call") - errCh <- err - return nil - } - utils.LogError(logger, err, "failed to read reply from the mongo server", zap.String("mongo server address", destConn.RemoteAddr().String())) - errCh <- err - return nil - } - - logger.Debug("received these pck length packets", zap.Any("packets", responsePckLengthBuffer)) - - // convert packet length to LittleEndian integer. - pckLength := getPacketLength(responsePckLengthBuffer) - logger.Debug("received pck length ", zap.Any("packet length", pckLength)) - - // read the entire response packet - responsePckDataBuffer, err := pUtil.ReadRequiredBytes(ctx, logger, destConn, int(pckLength)-4) - - logger.Debug("received these packets", zap.Any("packets", responsePckDataBuffer)) - - responseBuffer := append(responsePckLengthBuffer, responsePckDataBuffer...) - logger.Debug("reading from the destination mongo server", zap.Any("", string(responseBuffer))) - if err != nil { - if err == io.EOF { - logger.Debug("received response buffer is empty in record mode for mongo call") - errCh <- err - return nil - } - utils.LogError(logger, err, "failed to read reply from the mongo server", zap.String("mongo server address", destConn.RemoteAddr().String())) - errCh <- err - return nil - } - readResponseDelay := time.Since(started) - - // write the response packet to mongo client - _, err = clientConn.Write(responseBuffer) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(logger, err, "failed to write the reply message to mongo client") - errCh <- err - return nil - } - - // decode the binary packet of response and return the values in the corresponding structs - _, responseHeader, mongoResponse, err := Decode(responseBuffer, logger) - if err != nil { - utils.LogError(logger, err, "failed to decode the mongo wire message from the destination server") - errCh <- err - return nil - } - mongoResponses = append(mongoResponses, models.MongoResponse{ - Header: &responseHeader, - Message: mongoResponse, - ReadDelay: int64(readResponseDelay), - }) - // check for the response packet streaming for the mongo wire message - if val, ok := mongoResponse.(*models.MongoOpMessage); ok && hasSecondSetBit(val.FlagBits) { - for i := 0; ; i++ { - // handle the streaming response packets for heartbeat calls - if i == 0 && isHeartBeat(logger, opReq, *mongoRequests[0].Header, mongoRequests[0].Message) { - m.recordMessage(ctx, logger, mongoRequests, mongoResponses, opReq, reqTimestampMock, mocks) - } - started = time.Now() - - // read the response packets from the destination server - responseBuffer, err = pUtil.ReadBytes(ctx, logger, destConn) - if err != nil { - if err == io.EOF { - logger.Debug("received response buffer is empty in record mode for mongo call") - errCh <- err - return nil - } - utils.LogError(logger, err, "failed to read reply from the mongo server", zap.String("mongo server address", destConn.RemoteAddr().String())) - errCh <- err - return nil - } - logger.Debug(fmt.Sprintf("the response in the mongo parser before passing to client: %v", len(responseBuffer))) - - readResponseDelay := time.Since(started) - - // write the reply to mongo client - _, err = clientConn.Write(responseBuffer) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(logger, err, "failed to write the reply message to mongo client") - errCh <- err - return nil - } - logger.Debug(fmt.Sprintf("the response in the mongo parser after passing to client: %v", len(responseBuffer))) - - if len(responseBuffer) == 0 { - logger.Debug("the response from the server is complete") - break - } - // decode the binary packet for response and return the values in the corresponding structs - _, respHeader, mongoResp, err := Decode(responseBuffer, logger) - if err != nil { - utils.LogError(logger, err, "failed to decode the mongo wire message from the destination server") - errCh <- err - return nil - } - if mongoRespVal, ok := mongoResp.(models.MongoOpMessage); ok && !hasSecondSetBit(mongoRespVal.FlagBits) { - logger.Debug("the response from the server is complete since the more_to_come flagbit is 0") - break - } - mongoResponses = append(mongoResponses, models.MongoResponse{ - Header: &respHeader, - Message: mongoResp, - ReadDelay: int64(readResponseDelay), - }) - } - } - - // write the response packet to the yaml file - m.recordMessage(ctx, logger, mongoRequests, mongoResponses, opReq, reqTimestampMock, mocks) - // assigns "read form client conn" to the reqBuf to read the next request packet from the client connection - reqBuf = []byte("read form client conn") - } - }) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - return nil - } - return err - } -} - -// getPacketLength returns the length of the packet from the first 4 bytes of the packet. -func getPacketLength(src []byte) (length int32) { - length = int32(src[0]) | int32(src[1])<<8 | int32(src[2])<<16 | int32(src[3])<<24 - return length -} diff --git a/keploy/pkg/core/proxy/integrations/mongo/match.go b/keploy/pkg/core/proxy/integrations/mongo/match.go deleted file mode 100644 index 3a2ccce..0000000 --- a/keploy/pkg/core/proxy/integrations/mongo/match.go +++ /dev/null @@ -1,244 +0,0 @@ -//go:build linux - -package mongo - -import ( - "context" - "fmt" - "reflect" - "strings" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/utils" - "go.mongodb.org/mongo-driver/bson" - - "go.keploy.io/server/v2/pkg/models" - "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" - "go.uber.org/zap" -) - -// match mathces and returns the best matching mock for the incoming mongo requests. -func match(ctx context.Context, logger *zap.Logger, mongoRequests []models.MongoRequest, mockDb integrations.MockMemDb) (bool, *models.Mock, error) { - for { - select { - case <-ctx.Done(): - return false, nil, ctx.Err() - default: - mocks, err := mockDb.GetFilteredMocks() - if err != nil { - return false, nil, fmt.Errorf("error while getting tcs mock: %v", err) - } - var tcsMocks []*models.Mock - for _, mock := range mocks { - if mock.Kind != "Mongo" { - continue - } - tcsMocks = append(tcsMocks, mock) - } - maxMatchScore := 0.0 - bestMatchIndex := -1 - // iterate over the tcsMocks and compare the incoming mongo requests with the recorded mongo requests. - for tcsIndx, tcsMock := range tcsMocks { - if ctx.Err() != nil { - return false, nil, ctx.Err() - } - // check for the number of chunks in the incoming mongo requests and the recorded mongo requests. - if len(tcsMock.Spec.MongoRequests) == len(mongoRequests) { - for i, req := range tcsMock.Spec.MongoRequests { - if ctx.Err() != nil { - return false, nil, ctx.Err() - } - // check for the opcode of the incoming mongo requests and the recorded mongo requests. - if len(tcsMock.Spec.MongoRequests) != len(mongoRequests) || req.Header.Opcode != mongoRequests[i].Header.Opcode { - logger.Debug("the received request is not of same type with the tcmocks", zap.Any("at index", tcsIndx)) - continue - } - switch req.Header.Opcode { - case wiremessage.OpMsg: - if req.Message.(*models.MongoOpMessage).FlagBits != mongoRequests[i].Message.(*models.MongoOpMessage).FlagBits { - logger.Debug("the received request is not of same flagbit with the tcmocks", zap.Any("at index", tcsIndx)) - continue - } - scoreSum := 0.0 - for sectionIndx, section := range req.Message.(*models.MongoOpMessage).Sections { - if len(req.Message.(*models.MongoOpMessage).Sections) == len(mongoRequests[i].Message.(*models.MongoOpMessage).Sections) { - score := compareOpMsgSection(logger, section, mongoRequests[i].Message.(*models.MongoOpMessage).Sections[sectionIndx]) - scoreSum += score - } - } - currentScore := scoreSum / float64(len(mongoRequests)) - if currentScore > maxMatchScore { - maxMatchScore = currentScore - bestMatchIndex = tcsIndx - } - default: - utils.LogError(logger, nil, "the OpCode of the mongo wiremessage is invalid.", zap.Any("OpCode", req.Header.Opcode)) - } - } - } - } - if bestMatchIndex == -1 { - return false, nil, nil - } - mock := tcsMocks[bestMatchIndex] - isDeleted := mockDb.DeleteFilteredMock(*mock) - if !isDeleted { - continue - } - return true, mock, nil - } - } -} - -func compareOpMsgSection(logger *zap.Logger, expectedSection, actualSection string) float64 { - // check that the sections are of same type. SectionSingle (section[16] is "m") or SectionSequence (section[16] is "i"). - if (len(expectedSection) < 16 || len(actualSection) < 16) && expectedSection[16] != actualSection[16] { - return 0 - } - logger.Debug(fmt.Sprintf("the sections are. Expected: %v\n and actual: %v", expectedSection, actualSection)) - switch { - case strings.HasPrefix(expectedSection, "{ SectionSingle identifier:"): - var expectedIdentifier string - var expectedMsgsStr string - // // Define the regular expression pattern - // // Compile the regular expression - // // Find submatches using the regular expression - - expectedIdentifier, expectedMsgsStr, err := decodeOpMsgSectionSequence(expectedSection) - if err != nil { - logger.Debug(fmt.Sprintf("the section in mongo OpMsg wiremessage: %v", expectedSection)) - utils.LogError(logger, err, "failed to fetch the identifier/msgs from the section single of recorded OpMsg", zap.Any("identifier", expectedIdentifier)) - return 0 - } - - var actualIdentifier string - var actualMsgsStr string - // _, err = fmt.Sscanf(actualSection, "{ SectionSingle identifier: %s , msgs: [ %s ] }", &actualIdentifier, &actualMsgsStr) - actualIdentifier, actualMsgsStr, err = decodeOpMsgSectionSequence(actualSection) - if err != nil { - utils.LogError(logger, err, "failed to fetch the identifier/msgs from the section single of incoming OpMsg", zap.Any("identifier", actualIdentifier)) - return 0 - } - - // // Compile the regular expression - // // Find submatches using the regular expression - - logger.Debug("the expected section", zap.Any("identifier", expectedIdentifier), zap.Any("docs", expectedMsgsStr)) - logger.Debug("the actual section", zap.Any("identifier", actualIdentifier), zap.Any("docs", actualMsgsStr)) - - expectedMsgs := strings.Split(expectedMsgsStr, ", ") - actualMsgs := strings.Split(actualMsgsStr, ", ") - if len(expectedMsgs) != len(actualMsgs) || expectedIdentifier != actualIdentifier { - return 0 - } - score := 0.0 - for i := range expectedMsgs { - expected := map[string]interface{}{} - actual := map[string]interface{}{} - err := bson.UnmarshalExtJSON([]byte(expectedMsgs[i]), true, &expected) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the section of recorded request to bson document") - return 0 - } - err = bson.UnmarshalExtJSON([]byte(actualMsgs[i]), true, &actual) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the section of incoming request to bson document") - return 0 - } - score += calculateMatchingScore(expected, actual) - } - logger.Debug("the matching score for sectionSequence", zap.Any("", score)) - return score - case strings.HasPrefix(expectedSection, "{ SectionSingle msg:"): - var expectedMsgsStr string - expectedMsgsStr, err := extractSectionSingle(expectedSection) - if err != nil { - utils.LogError(logger, err, "failed to fetch the msgs from the single section of recorded OpMsg") - return 0 - } - // // Define the regular expression pattern - // // Compile the regular expression - // // Find submatches using the regular expression - - var actualMsgsStr string - actualMsgsStr, err = extractSectionSingle(actualSection) - if err != nil { - utils.LogError(logger, err, "failed to fetch the msgs from the single section of incoming OpMsg") - return 0 - } - // // Compile the regular expression - // // Find submatches using the regular expression - - expected := map[string]interface{}{} - actual := map[string]interface{}{} - - err = bson.UnmarshalExtJSON([]byte(expectedMsgsStr), true, &expected) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the section of recorded request to bson document") - return 0 - } - err = bson.UnmarshalExtJSON([]byte(actualMsgsStr), true, &actual) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the section of incoming request to bson document") - return 0 - } - logger.Debug("the expected and actual msg in the single section.", zap.Any("expected", expected), zap.Any("actual", actual), zap.Any("score", calculateMatchingScore(expected, actual))) - return calculateMatchingScore(expected, actual) - - default: - utils.LogError(logger, nil, "failed to detect the OpMsg section into mongo request wiremessage due to invalid format") - return 0 - } -} - -func calculateMatchingScore(obj1, obj2 map[string]interface{}) float64 { - totalFields := len(obj2) - if len(obj1) > totalFields { - totalFields = len(obj1) - } - matchingFields := 0.0 - - for key, value := range obj2 { - if obj1Value, ok := obj1[key]; ok { - if reflect.DeepEqual(value, obj1Value) { - matchingFields++ - } else if reflect.TypeOf(value) == reflect.TypeOf(obj1Value) { - if isNestedMap(value) { - if isNestedMap(obj1Value) { - matchingFields += calculateMatchingScore(obj1Value.(map[string]interface{}), value.(map[string]interface{})) - } - } else if isSlice(value) { - if isSlice(obj1Value) { - matchingFields += calculateMatchingScoreForSlices(obj1Value.([]interface{}), value.([]interface{})) - } - } - } - } - } - - return float64(matchingFields) / float64(totalFields) -} - -func calculateMatchingScoreForSlices(slice1, slice2 []interface{}) float64 { - matchingCount := 0 - - if len(slice1) == len(slice2) { - for indx2, item2 := range slice2 { - if len(slice1) > indx2 && reflect.DeepEqual(item2, slice1[indx2]) { - matchingCount++ - } - } - } - - return float64(matchingCount) / float64(len(slice2)) -} - -func isNestedMap(value interface{}) bool { - _, ok := value.(map[string]interface{}) - return ok -} - -func isSlice(value interface{}) bool { - _, ok := value.([]interface{}) - return ok -} diff --git a/keploy/pkg/core/proxy/integrations/mongo/mongo.go b/keploy/pkg/core/proxy/integrations/mongo/mongo.go deleted file mode 100644 index 0f6799a..0000000 --- a/keploy/pkg/core/proxy/integrations/mongo/mongo.go +++ /dev/null @@ -1,128 +0,0 @@ -//go:build linux - -package mongo - -import ( - "context" - "encoding/binary" - "net" - "sync" - "time" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/utils" - - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -func init() { - integrations.Register(integrations.MONGO, &integrations.Parsers{ - Initializer: New, - Priority: 100, - }) -} - -type Mongo struct { - logger *zap.Logger - recordedConfigRequests sync.Map -} - -func New(logger *zap.Logger) integrations.Integrations { - return &Mongo{ - logger: logger, - recordedConfigRequests: sync.Map{}, - } -} - -// MatchType determines if the outgoing network call is Mongo by comparing the -// message format with that of a mongo wire message. -func (m *Mongo) MatchType(_ context.Context, buffer []byte) bool { - if len(buffer) < 4 { - return false - } - // identifies by the starting 4 bytes of the message, since that - // are the length of the message. - messageLength := binary.LittleEndian.Uint32(buffer[0:4]) - return int(messageLength) == len(buffer) -} - -// RecordOutgoing records the outgoing mongo messages of the client connection into the yaml file. -// The database connection is keep-alive so, this function will be called during the connection establishment. -func (m *Mongo) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - logger := m.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial mongo message") - return err - } - - // the mongo messages are converted to the yaml format. - // - // initially the reqBuf contains the first network packet - // from the client connection which is used to determine - // the packet type in MatchType. - err = m.encodeMongo(ctx, logger, reqBuf, src, dst, mocks, opts) - if err != nil { - utils.LogError(logger, err, "failed to encode the mongo message into the yaml") - return err - } - return nil -} - -// MockOutgoing reads the outgoing mongo requests of the client connection and -// mocks the responses from the yaml file. The database connection is keep-alive -func (m *Mongo) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { - // read the initial buffer from the client connection. Initially the - // reqBuf contains the first network packet from the client connection - // which is used to determine the packet type in MatchType. - logger := m.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial mongo message") - return err - } - - // converts the yaml string into the binary packet - err = decodeMongo(ctx, logger, reqBuf, src, dstCfg, mockDb, opts) - if err != nil { - utils.LogError(logger, err, "failed to decode the mongo message") - return err - } - return nil -} - -// recordMessage records the mongo messages into the yaml file. -func (m *Mongo) recordMessage(ctx context.Context, logger *zap.Logger, mongoRequests []models.MongoRequest, mongoResponses []models.MongoResponse, opReq Operation, reqTimestampMock time.Time, mocks chan<- *models.Mock) { - shouldRecordCalls := true // boolean to check for already saved config mocks - name := "mocks" - meta1 := map[string]string{ - "operation": opReq.String(), - "connID": ctx.Value(models.ClientConnectionIDKey).(string), - } - - // check that the packet is heartbeat or not. - // See: https://github.com/mongodb/mongo-go-driver/blob/8489898c64a2d8c2e2160006eb851a11a9db9e9d/x/mongo/driver/operation/hello.go#L503 - if isHeartBeat(logger, opReq, *mongoRequests[0].Header, mongoRequests[0].Message) { - meta1["type"] = "config" - } - // record the mongo messages - if shouldRecordCalls { - mongoMock := &models.Mock{ - Version: models.GetVersion(), - Kind: models.Mongo, - Name: name, - Spec: models.MockSpec{ - Metadata: meta1, - MongoRequests: mongoRequests, - MongoResponses: mongoResponses, - Created: time.Now().Unix(), - ReqTimestampMock: reqTimestampMock, - ResTimestampMock: time.Now(), - }, - } - // Save the mock - mocks <- mongoMock - } -} diff --git a/keploy/pkg/core/proxy/integrations/mongo/operation.go b/keploy/pkg/core/proxy/integrations/mongo/operation.go deleted file mode 100644 index fe0522c..0000000 --- a/keploy/pkg/core/proxy/integrations/mongo/operation.go +++ /dev/null @@ -1,1575 +0,0 @@ -//go:build linux - -package mongo - -import ( - "context" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "regexp" - "strconv" - "strings" - "time" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/scram" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" - "go.mongodb.org/mongo-driver/x/mongo/driver" - "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" - "go.uber.org/zap" -) - -type Message struct { - Wm []byte - Op Operation -} - -type TransactionDetails struct { - LsID []byte - TxnNumber int64 - IsStartTransaction bool -} - -type Operation interface { - fmt.Stringer - OpCode() wiremessage.OpCode - Encode(responseTo, requestID int32) []byte - IsIsMaster() bool - IsIsAdminDB() bool - CursorID() (cursorID int64, ok bool) - RequestID() int32 - Error() error - Unacknowledged() bool - CommandAndCollection() (Command, string) - TransactionDetails() *TransactionDetails -} - -// Decode decodes the wire message binary into the operation and the mongo message. -// -// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation.go#L1361-L1426 -func Decode(wm []byte, logger *zap.Logger) (Operation, models.MongoHeader, interface{}, error) { - wmLength := len(wm) - // the wiremessage is at least 16 bytes long - length, reqID, responseTo, opCode, wmBody, ok := wiremessage.ReadHeader(wm) - messageHeader := models.MongoHeader{ - Length: length, - RequestID: reqID, - ResponseTo: responseTo, - Opcode: wiremessage.OpCode(opCode), - } - logger.Debug(fmt.Sprintf("the mongo msg header: %v", messageHeader)) - if !ok || int(length) > wmLength { - return nil, messageHeader, &models.MongoOpMessage{}, errors.New("malformed wire message: insufficient bytes") - } - - var ( - op Operation - err error - mongoMsg interface{} - ) - - switch opCode { - case wiremessage.OpQuery: - // decodeQuery is a helper function to decode the OpQuery operation - op, err = decodeQuery(reqID, wmBody) - if err != nil { - return nil, messageHeader, mongoMsg, err - } - // marshal the query document to json - jsonBytes, err := bson.MarshalExtJSON(op.(*opQuery).query, true, false) - if err != nil { - return nil, messageHeader, &models.MongoOpMessage{}, fmt.Errorf("malformed bson document: %v", err.Error()) - } - jsonString := string(jsonBytes) - - mongoMsg = &models.MongoOpQuery{ - Flags: int32(op.(*opQuery).flags), - FullCollectionName: op.(*opQuery).fullCollectionName, - NumberToSkip: op.(*opQuery).numberToSkip, - NumberToReturn: op.(*opQuery).numberToReturn, - Query: jsonString, - ReturnFieldsSelector: op.(*opQuery).returnFieldsSelector.String(), - } - case wiremessage.OpMsg: - // decodeMsg is a helper function to decode the OpMsg operation - op, err = decodeMsg(reqID, wmBody, logger) - if err != nil { - return nil, messageHeader, mongoMsg, err - } - var sections []string - for _, section := range op.(*opMsg).sections { - sections = append(sections, section.String()) - } - mongoMsg = &models.MongoOpMessage{ - FlagBits: int(op.(*opMsg).flags), - Sections: sections, - Checksum: int(op.(*opMsg).checksum), - } - case wiremessage.OpReply: - // decodeReply is a helper function to decode the OpReply operation - op, err = decodeReply(reqID, wmBody) - if err != nil { - return nil, messageHeader, mongoMsg, err - } - var replyDocs []string - for _, v := range op.(*opReply).documents { - // marshal the reply document to json - jsonBytes, err := bson.MarshalExtJSON(v, true, false) - if err != nil { - return nil, messageHeader, &models.MongoOpMessage{}, fmt.Errorf("malformed bson document: %v", err.Error()) - } - jsonString := string(jsonBytes) - replyDocs = append(replyDocs, jsonString) - } - mongoMsg = &models.MongoOpReply{ - ResponseFlags: int32(op.(*opReply).flags), - CursorID: op.(*opReply).cursorID, - StartingFrom: op.(*opReply).startingFrom, - NumberReturned: op.(*opReply).numReturned, - Documents: replyDocs, - } - default: - op = &opUnknown{ - opCode: opCode, - reqID: reqID, - wm: wm, - } - } - if err != nil { - return nil, messageHeader, mongoMsg, err - } - logger.Debug(fmt.Sprintf("the decoded string for the wiremessage: %v", op.String())) - return op, messageHeader, mongoMsg, nil -} - -type opUnknown struct { - opCode wiremessage.OpCode - reqID int32 - wm []byte -} - -func (o *opUnknown) IsIsAdminDB() bool { - return false -} - -func (o *opUnknown) TransactionDetails() *TransactionDetails { - return nil -} - -func (o *opUnknown) OpCode() wiremessage.OpCode { - return o.opCode -} - -func (o *opUnknown) Encode(_, _ int32) []byte { - return o.wm -} - -func (o *opUnknown) IsIsMaster() bool { - return false -} - -func (o *opUnknown) CursorID() (cursorID int64, ok bool) { - return 0, false -} - -func (o *opUnknown) RequestID() int32 { - return o.reqID -} - -func (o *opUnknown) Error() error { - return nil -} - -func (o *opUnknown) Unacknowledged() bool { - return false -} - -func (o *opUnknown) CommandAndCollection() (Command, string) { - return Unknown, "" -} - -func (o *opUnknown) String() string { - return fmt.Sprintf("{ OpUnknown opCode: %d, wm: %s }", o.opCode, o.wm) -} - -// https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#wire-op-query -type opQuery struct { - reqID int32 - flags wiremessage.QueryFlag - fullCollectionName string - numberToSkip int32 - numberToReturn int32 - query bsoncore.Document - returnFieldsSelector bsoncore.Document -} - -func (q *opQuery) IsIsAdminDB() bool { - return false -} - -func (q *opQuery) TransactionDetails() *TransactionDetails { - return nil -} - -// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/topology/server_test.go#L968-L1003 -func decodeQuery(reqID int32, wm []byte) (*opQuery, error) { - var ok bool - q := opQuery{ - reqID: reqID, - } - - q.flags, wm, ok = wiremessage.ReadQueryFlags(wm) - if !ok { - return nil, errors.New("malformed query message: missing OP_QUERY flags") - } - - q.fullCollectionName, wm, ok = wiremessage.ReadQueryFullCollectionName(wm) - if !ok { - return nil, errors.New("malformed query message: full collection name") - } - - q.numberToSkip, wm, ok = wiremessage.ReadQueryNumberToSkip(wm) - if !ok { - return nil, errors.New("malformed query message: number to skip") - } - - q.numberToReturn, wm, ok = wiremessage.ReadQueryNumberToReturn(wm) - if !ok { - return nil, errors.New("malformed query message: number to return") - } - - q.query, wm, ok = wiremessage.ReadQueryQuery(wm) - if !ok { - return nil, errors.New("malformed query message: query document") - } - - if len(wm) > 0 { - q.returnFieldsSelector, _, ok = wiremessage.ReadQueryReturnFieldsSelector(wm) - if !ok { - return nil, errors.New("malformed query message: return fields selector") - } - } - - return &q, nil -} - -func (q *opQuery) OpCode() wiremessage.OpCode { - return wiremessage.OpQuery -} - -// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation_legacy.go#L179-L189 -func (q *opQuery) Encode(responseTo, _ int32) []byte { - var buffer []byte - idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpQuery) - buffer = wiremessage.AppendQueryFlags(buffer, q.flags) - buffer = wiremessage.AppendQueryFullCollectionName(buffer, q.fullCollectionName) - buffer = wiremessage.AppendQueryNumberToSkip(buffer, q.numberToSkip) - buffer = wiremessage.AppendQueryNumberToReturn(buffer, q.numberToReturn) - buffer = append(buffer, q.query...) - if len(q.returnFieldsSelector) != 0 { - // returnFieldsSelector is optional - buffer = append(buffer, q.returnFieldsSelector...) - } - buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) - return buffer -} - -func (q *opQuery) CursorID() (cursorID int64, ok bool) { - return q.query.Lookup("getMore").Int64OK() -} - -func (q *opQuery) RequestID() int32 { - return q.reqID -} - -func (q *opQuery) IsIsMaster() bool { - if q.fullCollectionName != "admin.$cmd" { - return false - } - return IsIsMasterDoc(q.query) -} - -func (q *opQuery) Error() error { - return nil -} - -func (q *opQuery) Unacknowledged() bool { - return false -} - -func (q *opQuery) CommandAndCollection() (Command, string) { - return Find, q.fullCollectionName -} - -func (q *opQuery) String() string { - return fmt.Sprintf("{ OpQuery flags: %s, fullCollectionName: %s, numberToSkip: %d, numberToReturn: %d, query: %s, returnFieldsSelector: %s }", q.flags.String(), q.fullCollectionName, q.numberToSkip, q.numberToReturn, q.query.String(), q.returnFieldsSelector.String()) -} - -// https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op-msg -type opMsg struct { - reqID int32 - flags wiremessage.MsgFlag - sections []opMsgSection - checksum uint32 - logger *zap.Logger -} - -type opMsgSection interface { - fmt.Stringer - cursorID() (cursorID int64, ok bool) - isIsMaster() bool - isDbAdmin() bool - append(buffer []byte) []byte - commandAndCollection() (Command, string) -} - -type opMsgSectionSingle struct { - msg bsoncore.Document -} - -func (o *opMsgSectionSingle) cursorID() (cursorID int64, ok bool) { - if getMore, ok := o.msg.Lookup("getMore").Int64OK(); ok { - return getMore, ok - } - return o.msg.Lookup("cursor", "id").Int64OK() -} - -func (o *opMsgSectionSingle) isIsMaster() bool { - if db, ok := o.msg.Lookup("$db").StringValueOK(); ok && db == "admin" { - return IsIsMasterDoc(o.msg) - } - return false -} - -func (o *opMsgSectionSingle) isDbAdmin() bool { - if db, ok := o.msg.Lookup("$db").StringValueOK(); ok && db == "admin" { - return true - } - return false -} - -func (o *opMsgSectionSingle) append(buffer []byte) []byte { - buffer = wiremessage.AppendMsgSectionType(buffer, wiremessage.SingleDocument) - return append(buffer, o.msg...) -} - -func (o *opMsgSectionSingle) commandAndCollection() (Command, string) { - return CommandAndCollection(o.msg) -} - -func (o *opMsgSectionSingle) String() string { - jsonBytes, err := bson.MarshalExtJSON(o.msg, true, false) - if err != nil { - return "" - } - jsonString := string(jsonBytes) - - return fmt.Sprintf("{ SectionSingle msg: %s }", jsonString) -} - -type opMsgSectionSequence struct { - identifier string - msgs []bsoncore.Document -} - -func (o *opMsgSectionSequence) cursorID() (cursorID int64, ok bool) { - // assume no cursor IDs are returned in OP_MSG document sequences - return 0, false -} - -func (o *opMsgSectionSequence) isIsMaster() bool { - return false -} -func (o *opMsgSectionSequence) isDbAdmin() bool { - res := true - for _, v := range o.msgs { - if db, ok := v.Lookup("$db").StringValueOK(); !ok || db != "admin" { - res = false - break - } - } - return res -} - -func (o *opMsgSectionSequence) append(buffer []byte) []byte { - buffer = wiremessage.AppendMsgSectionType(buffer, wiremessage.DocumentSequence) - - length := int32(len(o.identifier) + 5) - for _, msg := range o.msgs { - length += int32(len(msg)) - } - - buffer = appendi32(buffer, length) - buffer = appendCString(buffer, o.identifier) - for _, msg := range o.msgs { - buffer = append(buffer, msg...) - } - - return buffer -} - -func (o *opMsgSectionSequence) commandAndCollection() (Command, string) { - return Unknown, "" -} - -func (o *opMsgSectionSequence) String() string { - var msgs []string - for _, msg := range o.msgs { - jsonBytes, err := bson.MarshalExtJSON(msg, true, false) - if err != nil { - return "" - } - jsonString := string(jsonBytes) - msgs = append(msgs, jsonString) - } - return fmt.Sprintf("{ SectionSingle identifier: %s , msgs: [ %s ] }", o.identifier, strings.Join(msgs, ", ")) -} - -func decodeOpMsgSectionSequence(section string) (string, string, error) { - var identifier, message = "", "" - - // Define the regular expression pattern - pattern := `\{ SectionSingle identifier: (.+?) , msgs: \[ (.+?) \] \}` - - // Compile the regular expression - regex := regexp.MustCompile(pattern) - - // Find submatches using the regular expression - submatches := regex.FindStringSubmatch(section) - if submatches == nil || len(submatches) != 3 { - return identifier, message, errors.New("invalid format of message section sequence") - } - identifier = submatches[1] - message = submatches[2] - return identifier, message, nil - -} - -func extractSectionSingle(data string) (string, error) { - // Look for the prefix before the actual content - prefix := "{ SectionSingle msg: " - startIndex := strings.Index(data, prefix) - if startIndex == -1 { - return "", fmt.Errorf("start not found") - } - - // Adjust the start index to skip the prefix - startIndex += len(prefix) - - // We'll assume the content ends with " }" that closes the sectionSingle - endIndex := strings.LastIndex(data[startIndex:], " }") - if endIndex == -1 { - return "", fmt.Errorf("end not found") - } - - // Adjust the end index relative to the entire string - endIndex += startIndex - - // Extract the content between the start and end indexes - content := data[startIndex:endIndex] - - // Clean up the extracted content - content = strings.Trim(content, " ") - - return content, nil -} - -func processOpReply(ctx context.Context, expectedRequest, actualRequest models.MongoRequest, expectedResponse *models.MongoOpReply, mongoPassword string, logger *zap.Logger) (string, bool) { - if len(expectedResponse.Documents) == 0 { - return "", false - } - - for _, document := range expectedResponse.Documents { - var responseMsg map[string]interface{} - err := json.Unmarshal([]byte(document), &responseMsg) - if err != nil { - logger.Error("Failed to unmarshal JSON document of OpReply", zap.Error(err)) - return "", false - } - - // Extract and decode the payload from the actual MongoDB request - responseMsgData, ok := responseMsg["speculativeAuthenticate"].(map[string]interface{}) - if !ok { - _, ok = responseMsg["payload"].(map[string]interface{}) - if !ok { - logger.Debug("failed to extract payload from response data", zap.Any("responseMsg", responseMsg)) - continue - } - responseMsgData = responseMsg - } - - // Extract and decode the expected MongoDB request payload - expectedrequestPayload := expectedRequest.Message.(*models.MongoOpQuery).Query // Assuming the query is the payload - var expectedrequestPayloadMap map[string]interface{} - err = json.Unmarshal([]byte(expectedrequestPayload), &expectedrequestPayloadMap) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal request payload into map") - return "", false - } - expectedRequest, ok := expectedrequestPayloadMap["speculativeAuthenticate"].(map[string]interface{}) - if !ok { - _, ok = expectedrequestPayloadMap["payload"].(map[string]interface{}) - if !ok { - logger.Debug("failed to extract payload from response data", zap.Any("responseMsg", responseMsg)) - continue - } - expectedRequest = expectedrequestPayloadMap - } - - actualRequestPayload := actualRequest.Message.(*models.MongoOpQuery).Query // Assuming the query is the payload - var actualRequestPayloadMap map[string]interface{} - err = json.Unmarshal([]byte(actualRequestPayload), &actualRequestPayloadMap) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal request payload into map") - return "", false - } - - actualRequest, ok := actualRequestPayloadMap["speculativeAuthenticate"].(map[string]interface{}) - if !ok { - _, ok = actualRequestPayloadMap["payload"].(map[string]interface{}) - if !ok { - logger.Debug("failed to extract payload from response data", zap.Any("responseMsg", responseMsg)) - continue - } - actualRequest = actualRequestPayloadMap - } - - // TODO should move this to scram related file - - _, ok = actualRequest["saslStart"] - if !ok { - logger.Debug("failed to extract saslStart from response data", zap.Any("responseMsgData", responseMsgData)) - } else { - resPayload, err := extractAuthPayload(responseMsgData) - if err != nil { - logger.Debug("Failed to fetch the payload from the received MongoDB response", zap.Error(err)) - continue - } - logger.Debug(fmt.Sprintf("Payload of the received response: %s", resPayload)) - - decodedResPayload, err := decodeBase64Str(resPayload) - if err != nil { - logger.Error("Error decoding the received payload base64 string", zap.Error(err)) - return "", false - } - logger.Debug(fmt.Sprintf("Decoded payload of the actual for the SASLStart: %s", string(decodedResPayload))) - - expectedPayload, err := extractAuthPayload(expectedRequest) - if err != nil { - logger.Debug("Failed to fetch the payload from the expected MongoDB request", zap.Error(err)) - continue - } - logger.Debug(fmt.Sprintf("Payload of the expected request: %s", expectedPayload)) - - decodedExpPayload, err := decodeBase64Str(expectedPayload) - if err != nil { - logger.Error("Error decoding the expected request payload base64 string", zap.Error(err)) - return "", false - } - logger.Debug(fmt.Sprintf("Decoded payload of the expected for the SASLStart: %s", string(decodedExpPayload))) - - actualReqPayload, err := extractAuthPayload(actualRequest) - if err != nil { - logger.Debug("Failed to extract the payload from the actual MongoDB request", zap.Error(err)) - continue - } - // Extract and decode the payload from the actual MongoDB request - decodedReqPayload, err := decodeBase64Str(actualReqPayload) - if err != nil { - logger.Error("Failed to fetch the payload from the actual MongoDB request", zap.Error(err)) - return "", false - } - logger.Debug(fmt.Sprintf("Payload of the actual request: %s", decodedReqPayload)) - - newFirstAuthResponse, err := scram.GenerateServerFirstMessage(decodedExpPayload, decodedReqPayload, decodedResPayload, logger) - if err != nil { - return "", false - } - logger.Debug("After replacing the new client nonce in auth response", zap.String("first response", newFirstAuthResponse)) - - conversationID, err := extractConversationID(responseMsgData) - if err != nil { - logger.Error("Failed to fetch the conversationId for the SCRAM auth from the recorded first response", zap.Error(err)) - return "", false - } - // Generate the auth message from the received first request and recorded first response - authMessage := scram.GenerateAuthMessage(string(decodedReqPayload), newFirstAuthResponse, logger) - if authMessage == "" { - err := errors.New("failed to generate auth message") - logger.Error("Auth message generation failed", zap.Error(err)) - return "", false - } - authMechanism, ok := actualRequest["mechanism"].(string) - if !ok { - logger.Debug("failed to auth mechanism from expected request data", zap.Any("expectedRequest", actualRequest)) - continue - } - if authMechanism != util.SCRAM_SHA_1 && authMechanism != util.SCRAM_SHA_256 { - logger.Error("Invalid authentication mechanism", zap.String("authMechanism", authMechanism)) - return "", false - } - authMessage = authMessage + ",auth=" + authMechanism - logger.Debug("the auth message for the SCRAM saslstart authentication", zap.String("conversation-id", conversationID), zap.String("authMessage", authMessage)) - - connID := ctx.Value(models.ClientConnectionIDKey).(string) - authMessageMap.Store(connID+"+"+conversationID, authMessage) - // Marshal the new first response for the SCRAM authentication - authResponse := base64.StdEncoding.EncodeToString([]byte(newFirstAuthResponse)) - if authResponse != "" { - return authResponse, true - } - - } - - _, ok = actualRequest["saslContinue"] - if !ok { - logger.Debug("failed to extract saslContinue from response data", zap.Any("responseMsgData", responseMsgData)) - } else { - responsePayload, err := extractAuthPayload(responseMsgData) - if err != nil { - utils.LogError(logger, err, "failed to fetch the payload from the recorded mongo response") - return "", false - } - logger.Debug(fmt.Sprint("the payload of the recorded second response of SCRAM: ", responsePayload)) - - decodedResponsePayload, err := decodeBase64Str(responsePayload) - if err != nil { - utils.LogError(logger, err, "Error decoding the recorded saslContinue response payload base64 string") - return "", false - } - logger.Debug(fmt.Sprint("the decoded payload of the repsonse for the saslContinue: ", (string)(decodedResponsePayload))) - - fields := strings.Split(string(decodedResponsePayload), ",") - verifier, err := parseFieldBase64(fields[0], "v") - if err != nil { - logger.Debug("failed to parse the verifier of final response message", zap.Any("parsing error", err.Error())) - return "", false - } - logger.Debug("the recorded verifier of the auth request", zap.Any("verifier/server-signature", string(verifier))) - - // fetch the conversation id - conversationID, err := extractConversationID(actualRequest) - if err != nil { - utils.LogError(logger, err, "failed to fetch the conversationId for the SCRAM auth from the received final response") - return "", false - } - logger.Debug("fetched conversationId for the SCRAM authentication", zap.String("cid", conversationID)) - - salt := "" - itr := 0 - authType := "" - // get the authMessage from the saslStart conversation. Since, saslContinue have the same conversationId - // authMsg := authMessageMap[conversationID] - authMessage, ok := authMessageMap.Load(conversationID) - authMessageStr := "" - if ok { - authMessageStr = authMessage.(string) - } - - // get the salt and iteration from the authMessage to generate salted password - fields = strings.Split(authMessageStr, ",") - filteredFields := []string{} - for _, part := range fields { - if strings.HasPrefix(part, "s=") { - // Split based on "=" and get the value of "s" - saltByt, err := decodeBase64Str(strings.TrimPrefix(part, "s=")) - if err != nil { - utils.LogError(logger, err, "failed to decode the base64 string of salt") - return "", false - } - salt = string(saltByt) - } - if strings.HasPrefix(part, "i=") { - // Split based on "=" and get the value of "i" - itr, err = strconv.Atoi(strings.Split(part, "=")[1]) - if err != nil { - utils.LogError(logger, err, "failed to convert the string into integer") - return "", false - } - } - if strings.HasPrefix(part, "auth=") { - // Only add fields that are not prefixed with "auth=" - authType = strings.Split(part, "=")[1] - if err != nil { - utils.LogError(logger, err, "failed to convert the string into integer") - return "", false - } - } else { - filteredFields = append(filteredFields, part) - } - } - authMessageStr = strings.Join(filteredFields, ",") - // Since, the server proof is the signature generated by the authMessage and salted password. - // So, need to return the new server proof according to the new authMessage which is different from the recorded. - newVerifier, err := scram.GenerateServerFinalMessage(authMessageStr, authType, mongoPassword, salt, itr, logger) - if err != nil { - utils.LogError(logger, err, "failed to get the new server proof") - return "", false - } - return base64.StdEncoding.EncodeToString([]byte("v=" + newVerifier)), true - } - - } - - return "", false -} - -// encodeOpMsg encodes the OpMsg value into a mongo wire message. -func encodeOpMsg(ctx context.Context, responseOpMsg *models.MongoOpMessage, actualRequestMsgSections []string, expectedRequestMsgSections []string, mongoPassword string, logger *zap.Logger) (*opMsg, error) { - message := &opMsg{ - flags: wiremessage.MsgFlag(responseOpMsg.FlagBits), - checksum: uint32(responseOpMsg.Checksum), - sections: []opMsgSection{}, - logger: logger, - } - for messageIndex, messageValue := range responseOpMsg.Sections { - switch { - case strings.HasPrefix(messageValue, "{ SectionSingle identifier:"): - identifier, msgsStr, err := decodeOpMsgSectionSequence(responseOpMsg.Sections[messageIndex]) - if err != nil { - utils.LogError(logger, err, "failed to extract the msg section from recorded message") - return nil, err - } - msgs := strings.Split(msgsStr, ", ") - docs := []bsoncore.Document{} - for _, msg := range msgs { - var unmarshaledDoc bsoncore.Document - err = bson.UnmarshalExtJSON([]byte(msg), true, &unmarshaledDoc) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the recorded document string of OpMsg") - return nil, err - } - docs = append(docs, unmarshaledDoc) - } - message.sections = append(message.sections, &opMsgSectionSequence{ - identifier: identifier, - msgs: docs, - }) - case strings.HasPrefix(messageValue, "{ SectionSingle msg:"): - sectionStr, err := extractSectionSingle(responseOpMsg.Sections[messageIndex]) - if err != nil { - utils.LogError(logger, err, "failed to extract the msg section from recorded message single section") - return nil, err - } - resultStr, ok, err := handleScramAuth(ctx, actualRequestMsgSections, expectedRequestMsgSections, sectionStr, mongoPassword, logger) - if err != nil { - return nil, err - } - if ok { - logger.Debug("new responses have been generated for the scram authentication", zap.Any("response", resultStr)) - sectionStr = resultStr - } - - var unmarshaledDoc bsoncore.Document - - err = bson.UnmarshalExtJSON([]byte(sectionStr), true, &unmarshaledDoc) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the recorded document string of OpMsg") - return nil, err - } - message.sections = append(message.sections, &opMsgSectionSingle{ - msg: unmarshaledDoc, - }) - default: - utils.LogError(logger, nil, "failed to encode the OpMsg section into mongo wiremessage because of invalid format", zap.Any("section", messageValue)) - } - } - return message, nil -} - -// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation.go#L1387-L1423 -func decodeMsg(reqID int32, wm []byte, logger *zap.Logger) (*opMsg, error) { - var ok bool - m := opMsg{ - reqID: reqID, - logger: logger, - } - - m.flags, wm, ok = wiremessage.ReadMsgFlags(wm) - if !ok { - return nil, errors.New("malformed wire message: missing OP_MSG flags") - } - - checksumPresent := m.flags&wiremessage.ChecksumPresent == wiremessage.ChecksumPresent - for len(wm) > 0 { - // If the checksumPresent flag is set, the last four bytes of the message contain the checksum. - if checksumPresent && len(wm) == 4 { - m.checksum, wm, ok = wiremessage.ReadMsgChecksum(wm) - if !ok { - return nil, errors.New("malformed wire message: insufficient bytes to read checksum") - } - continue - } - - var stype wiremessage.SectionType - stype, wm, ok = wiremessage.ReadMsgSectionType(wm) - if !ok { - return nil, errors.New("malformed wire message: insufficient bytes to read section type") - } - - switch stype { - case wiremessage.SingleDocument: - s := opMsgSectionSingle{} - s.msg, wm, ok = wiremessage.ReadMsgSectionSingleDocument(wm) - if !ok { - return nil, errors.New("malformed wire message: insufficient bytes to read single document") - } - m.sections = append(m.sections, &s) - case wiremessage.DocumentSequence: - s := opMsgSectionSequence{} - s.identifier, s.msgs, wm, ok = wiremessage.ReadMsgSectionDocumentSequence(wm) - if !ok { - return nil, errors.New("malformed wire message: insufficient bytes to read document sequence") - } - m.sections = append(m.sections, &s) - default: - return nil, fmt.Errorf("malformed wire message: unknown section type %v", stype) - } - } - - return &m, nil -} - -func (m *opMsg) OpCode() wiremessage.OpCode { - return wiremessage.OpMsg -} - -// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation.go#L898-L904 -func (m *opMsg) Encode(responseTo, requestID int32) []byte { - var buffer []byte - m.logger.Debug(fmt.Sprintf("the responseTo for the OpMsg: %v, for requestId: %v", responseTo, wiremessage.NextRequestID())) - - idx, buffer := wiremessage.AppendHeaderStart(buffer, requestID, responseTo, wiremessage.OpMsg) - buffer = wiremessage.AppendMsgFlags(buffer, m.flags) - for _, section := range m.sections { - buffer = section.append(buffer) - } - if m.flags&wiremessage.ChecksumPresent == wiremessage.ChecksumPresent { - // The checksum is a uint32, but we can use appendi32 to encode it. Overflow/underflow when casting to int32 is - // not a concern here because the bytes in the number do not change after casting. - buffer = appendi32(buffer, int32(m.checksum)) - } - buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) - m.logger.Debug(fmt.Sprintf("opmsg string: %v", m.String())) - return buffer -} - -func (m *opMsg) IsIsMaster() bool { - for _, section := range m.sections { - if section.isIsMaster() { - return true - } - } - return false -} - -func (m *opMsg) IsIsAdminDB() bool { - for _, section := range m.sections { - if section.isDbAdmin() { - return true - } - } - return false -} - -func (m *opMsg) CursorID() (cursorID int64, ok bool) { - for _, section := range m.sections { - if cursorID, ok := section.cursorID(); ok { - return cursorID, ok - } - } - return 0, false -} - -func (m *opMsg) RequestID() int32 { - return m.reqID -} - -func (m *opMsg) Error() error { - if len(m.sections) == 0 { - return nil - } - single, ok := m.sections[0].(*opMsgSectionSingle) - if !ok { - return nil - } - return driver.ExtractErrorFromServerResponse(single.msg) -} - -func (m *opMsg) Unacknowledged() bool { - return m.flags&wiremessage.MoreToCome == wiremessage.MoreToCome -} - -func (m *opMsg) CommandAndCollection() (Command, string) { - for _, section := range m.sections { - command, collection := section.commandAndCollection() - if command != Unknown { - return command, collection - } - } - return Unknown, "" -} - -// TransactionDetails See https://github.com/mongodb/specifications/blob/master/source/transactions/transactions.rst -// Version 4.0 of the server introduces multi-statement transactions. -// opMsg is available from wire protocol 3.6 -// deprecated operations such OP_UPDATE OP_INSERT are not supposed to support transaction statements. -// When constructing any other command within a transaction, drivers MUST add the lsid, txnNumber, and autocommit fields. -func (m *opMsg) TransactionDetails() *TransactionDetails { - - for _, section := range m.sections { - - if single, ok := section.(*opMsgSectionSingle); ok { - _, lsID, ok := single.msg.Lookup("lsid", "id").BinaryOK() - if !ok { - continue - } - - txnNumber, ok := single.msg.Lookup("txnNumber").Int64OK() - if !ok { - continue - } - - _, ok = single.msg.Lookup("autocommit").BooleanOK() - if !ok { - continue - } - - startTransaction, ok := single.msg.Lookup("startTransaction").BooleanOK() - return &TransactionDetails{ - LsID: lsID, - TxnNumber: txnNumber, - IsStartTransaction: ok && startTransaction, - } - } - } - - return nil -} - -func (m *opMsg) GetFlagBits() int32 { - return int32(m.flags) -} - -func (m *opMsg) String() string { - var sections []string - for _, section := range m.sections { - sections = append(sections, section.String()) - } - return fmt.Sprintf("{ OpMsg flags: %d, sections: [%s], checksum: %d }", m.flags, strings.Join(sections, ", "), m.checksum) -} - -// https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op-reply -type opReply struct { - reqID int32 - flags wiremessage.ReplyFlag - cursorID int64 - startingFrom int32 - numReturned int32 - documents []bsoncore.Document -} - -func (r *opReply) TransactionDetails() *TransactionDetails { - return nil -} - -func encodeOpReply(ctx context.Context, actualRequest models.MongoRequest, expectedRequest models.MongoRequest, expectedResponse *models.MongoOpReply, mongoPassword string, logger *zap.Logger) (*opReply, error) { - - replyDocs := []bsoncore.Document{} - updatedFirstResponse, isResponseUpdated := processOpReply(ctx, expectedRequest, actualRequest, expectedResponse, mongoPassword, logger) - for _, v := range expectedResponse.Documents { - var unmarshaledDoc bsoncore.Document - logger.Debug(fmt.Sprintf("the document string is: %v", string(v))) - var result map[string]interface{} - - err := json.Unmarshal([]byte(v), &result) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal string document of OpReply") - return nil, err - } - // set the fields for handshake calls at test mode - localTime, ok := result["localTime"].(map[string]interface{}) - if ok { - localTime["$date"].(map[string]interface{})["$numberLong"] = strconv.FormatInt(time.Now().Unix(), 10) - logger.Debug(fmt.Sprintf("the updated document string is: %v", result["localTime"].(map[string]interface{})["$date"].(map[string]interface{})["$numberLong"])) - } - if isResponseUpdated { - // add checks - query, ok := result["speculativeAuthenticate"].(map[string]interface{}) - if !ok { - logger.Debug("failed to extract payload from response data", zap.Any("responseMsg", result)) - payload, ok := result["payload"].(map[string]interface{}) - if !ok { - logger.Debug("failed to extract payload from response data", zap.Any("responseMsg", result)) - continue - } - payload["$binary"].(map[string]interface{})["base64"] = updatedFirstResponse - } else { - query["payload"].(map[string]interface{})["$binary"].(map[string]interface{})["base64"] = updatedFirstResponse - } - } - v, err := json.Marshal(result) - if err != nil { - utils.LogError(logger, err, "failed to marshal the updated string document of OpReply") - return nil, err - } - err = bson.UnmarshalExtJSON([]byte(v), false, &unmarshaledDoc) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the updated document string of OpReply") - return nil, err - } - elements, _ := unmarshaledDoc.Elements() - logger.Debug(fmt.Sprintf("the elements of the reply docs: %v", elements)) - replyDocs = append(replyDocs, unmarshaledDoc) - - } - - return &opReply{ - flags: wiremessage.ReplyFlag(expectedResponse.ResponseFlags), - cursorID: expectedResponse.CursorID, - startingFrom: expectedResponse.StartingFrom, - numReturned: expectedResponse.NumberReturned, - documents: replyDocs, - }, nil -} - -// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation.go#L1297-L1358 -func decodeReply(reqID int32, wm []byte) (*opReply, error) { - var ok bool - r := opReply{ - reqID: reqID, - } - - r.flags, wm, ok = wiremessage.ReadReplyFlags(wm) - if !ok { - return nil, errors.New("malformed reply message: missing OP_REPLY flags") - } - - r.cursorID, wm, ok = wiremessage.ReadReplyCursorID(wm) - if !ok { - return nil, errors.New("malformed reply message: cursor id") - } - - r.startingFrom, wm, ok = wiremessage.ReadReplyStartingFrom(wm) - if !ok { - return nil, errors.New("malformed reply message: starting from") - } - - r.numReturned, wm, ok = wiremessage.ReadReplyNumberReturned(wm) - if !ok { - return nil, errors.New("malformed reply message: number returned") - } - - r.documents, _, ok = wiremessage.ReadReplyDocuments(wm) - if !ok { - return nil, errors.New("malformed reply message: could not read documents from reply") - } - - return &r, nil -} - -func (r *opReply) OpCode() wiremessage.OpCode { - return wiremessage.OpReply -} - -// see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/drivertest/channel_conn.go#L73-L82 -func (r *opReply) Encode(responseTo, requestID int32) []byte { - var buffer []byte - idx, buffer := wiremessage.AppendHeaderStart(buffer, requestID, responseTo, wiremessage.OpReply) - buffer = wiremessage.AppendReplyFlags(buffer, r.flags) - buffer = wiremessage.AppendReplyCursorID(buffer, r.cursorID) - buffer = wiremessage.AppendReplyStartingFrom(buffer, r.startingFrom) - buffer = wiremessage.AppendReplyNumberReturned(buffer, r.numReturned) - for _, doc := range r.documents { - buffer = append(buffer, doc...) - } - buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) - return buffer -} - -func (r *opReply) IsIsMaster() bool { - return false -} - -func (r *opReply) IsIsAdminDB() bool { - return false -} - -func (r *opReply) CursorID() (cursorID int64, ok bool) { - return r.cursorID, true -} - -func (r *opReply) RequestID() int32 { - return r.reqID -} - -func (r *opReply) Error() error { - if len(r.documents) == 0 { - return nil - } - return driver.ExtractErrorFromServerResponse(r.documents[0]) -} - -func (r *opReply) Unacknowledged() bool { - return false -} - -func (r *opReply) CommandAndCollection() (Command, string) { - return Find, "" -} - -func (r *opReply) String() string { - var documents []string - for _, document := range r.documents { - documents = append(documents, document.String()) - } - return fmt.Sprintf("{ OpReply flags: %d, cursorID: %d, startingFrom: %d, numReturned: %d, documents: [%s] }", r.flags, r.cursorID, r.startingFrom, r.numReturned, strings.Join(documents, ", ")) -} - -// https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op-get-more -// type opGetMore struct { -// reqID int32 -// fullCollectionName string -// numberToReturn int32 -// cursorID int64 -// } - -// func (g *opGetMore) TransactionDetails() *TransactionDetails { -// return nil -// } - -// // see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation.go#L1297-L1358 -// func decodeGetMore(reqID int32, wm []byte) (*opGetMore, error) { -// var ok bool -// g := opGetMore{ -// reqID: reqID, -// } - -// // the driver doesn't support any ReadGetMore* methods, so reuse methods from other operations - -// _, wm, ok = wiremessage.ReadKillCursorsZero(wm) -// if !ok { -// return nil, errors.New("malformed get_more message: missing zero") -// } - -// g.fullCollectionName, wm, ok = wiremessage.ReadQueryFullCollectionName(wm) -// if !ok { -// return nil, errors.New("malformed get_more message: missing full collection name") -// } - -// g.numberToReturn, wm, ok = wiremessage.ReadQueryNumberToReturn(wm) -// if !ok { -// return nil, errors.New("malformed get_more message: missing number to return") -// } - -// g.cursorID, _, ok = wiremessage.ReadReplyCursorID(wm) -// if !ok { -// return nil, errors.New("malformed get_more message: missing cursorID") -// } - -// return &g, nil -// } - -// func (g *opGetMore) OpCode() wiremessage.OpCode { -// return wiremessage.OpGetMore -// } - -// // see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation_legacy.go#L284-L291 -// func (g *opGetMore) Encode(responseTo, requestId int32) []byte { -// var buffer []byte -// idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpGetMore) -// buffer = wiremessage.AppendGetMoreZero(buffer) -// buffer = wiremessage.AppendGetMoreFullCollectionName(buffer, g.fullCollectionName) -// buffer = wiremessage.AppendGetMoreNumberToReturn(buffer, g.numberToReturn) -// buffer = wiremessage.AppendGetMoreCursorID(buffer, g.cursorID) -// buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) -// return buffer -// } - -// func (g *opGetMore) IsIsMaster() bool { -// return false -// } - -// func (g *opGetMore) CursorID() (cursorID int64, ok bool) { -// return g.cursorID, true -// } - -// func (g *opGetMore) RequestID() int32 { -// return g.reqID -// } - -// func (g *opGetMore) Error() error { -// return nil -// } - -// func (g *opGetMore) Unacknowledged() bool { -// return false -// } - -// func (g *opGetMore) CommandAndCollection() (Command, string) { -// return GetMore, g.fullCollectionName -// } - -// func (g *opGetMore) String() string { -// return fmt.Sprintf("{ OpGetMore fullCollectionName: %s, numberToReturn: %d, cursorID: %d }", g.fullCollectionName, g.numberToReturn, g.cursorID) -// } - -// // https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op_update -// type opUpdate struct { -// reqID int32 -// fullCollectionName string -// flags int32 -// selector bsoncore.Document -// tools bsoncore.Document -// } - -// func (u *opUpdate) TransactionDetails() *TransactionDetails { -// return nil -// } - -// func decodeUpdate(reqID int32, wm []byte) (*opUpdate, error) { -// var ok bool -// u := opUpdate{ -// reqID: reqID, -// } - -// u.fullCollectionName, wm, ok = readCString(wm) -// if !ok { -// return nil, errors.New("malformed tools message: full collection name") -// } - -// u.flags, wm, ok = readi32(wm) -// if !ok { -// return nil, errors.New("malformed tools message: missing OP_UPDATE flags") -// } - -// u.selector, wm, ok = bsoncore.ReadDocument(wm) -// if !ok { -// return nil, errors.New("malformed tools message: selector document") -// } - -// u.tools, _, ok = bsoncore.ReadDocument(wm) -// if !ok { -// return nil, errors.New("malformed tools message: tools document") -// } - -// return &u, nil -// } - -// func (u *opUpdate) OpCode() wiremessage.OpCode { -// return wiremessage.OpUpdate -// } - -// func (u *opUpdate) Encode(responseTo, requestId int32) []byte { -// var buffer []byte -// idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpUpdate) -// buffer = appendCString(buffer, u.fullCollectionName) -// buffer = appendi32(buffer, u.flags) -// buffer = append(buffer, u.selector...) -// buffer = append(buffer, u.tools...) -// buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) -// return buffer -// } - -// func (u *opUpdate) IsIsMaster() bool { -// return false -// } - -// func (u *opUpdate) CursorID() (cursorID int64, ok bool) { -// return 0, false -// } - -// func (u *opUpdate) RequestID() int32 { -// return u.reqID -// } - -// func (u *opUpdate) Error() error { -// return nil -// } - -// func (u *opUpdate) Unacknowledged() bool { -// return false -// } - -// func (u *opUpdate) CommandAndCollection() (Command, string) { -// return Update, u.fullCollectionName -// } - -// func (u *opUpdate) String() string { -// return fmt.Sprintf("{ OpQuery fullCollectionName: %s, flags: %d, selector: %s, tools: %s }", u.fullCollectionName, u.flags, u.selector.String(), u.tools.String()) -// } - -// // https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op_insert -// type opInsert struct { -// reqID int32 -// flags int32 -// fullCollectionName string -// documents []bsoncore.Document -// } - -// func (i *opInsert) TransactionDetails() *TransactionDetails { -// return nil -// } - -// func decodeInsert(reqID int32, wm []byte) (*opInsert, error) { -// var ok bool -// i := opInsert{ -// reqID: reqID, -// } - -// i.flags, wm, ok = readi32(wm) -// if !ok { -// return nil, errors.New("malformed insert message: missing OP_INSERT flags") -// } - -// i.fullCollectionName, wm, ok = readCString(wm) -// if !ok { -// return nil, errors.New("malformed insert message: full collection name") -// } - -// i.documents, _, ok = wiremessage.ReadReplyDocuments(wm) -// if !ok { -// return nil, errors.New("malformed insert message: could not read documents") -// } - -// return &i, nil -// } - -// func (i *opInsert) OpCode() wiremessage.OpCode { -// return wiremessage.OpInsert -// } - -// func (i *opInsert) Encode(responseTo, requestId int32) []byte { -// var buffer []byte -// idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpInsert) -// buffer = appendi32(buffer, i.flags) -// buffer = appendCString(buffer, i.fullCollectionName) -// for _, doc := range i.documents { -// buffer = append(buffer, doc...) -// } -// buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) -// return buffer -// } - -// func (i *opInsert) IsIsMaster() bool { -// return false -// } - -// func (i *opInsert) CursorID() (cursorID int64, ok bool) { -// return 0, false -// } - -// func (i *opInsert) RequestID() int32 { -// return i.reqID -// } - -// func (i *opInsert) Error() error { -// return nil -// } - -// func (i *opInsert) Unacknowledged() bool { -// return false -// } - -// func (i *opInsert) CommandAndCollection() (Command, string) { -// return Insert, i.fullCollectionName -// } - -// func (i *opInsert) String() string { -// var documents []string -// for _, document := range i.documents { -// documents = append(documents, document.String()) -// } -// return fmt.Sprintf("{ OpInsert flags: %d, fullCollectionName: %s, documents: %s }", i.flags, i.fullCollectionName, strings.Join(documents, ", ")) -// } - -// // https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op_insert -// type opDelete struct { -// reqID int32 -// fullCollectionName string -// flags int32 -// selector bsoncore.Document -// } - -// func (d *opDelete) TransactionDetails() *TransactionDetails { -// return nil -// } - -// func decodeDelete(reqID int32, wm []byte) (*opDelete, error) { -// var ok bool -// d := opDelete{ -// reqID: reqID, -// } - -// _, wm, ok = readi32(wm) -// if !ok { -// return nil, errors.New("malformed delete message: missing zero") -// } - -// d.fullCollectionName, wm, ok = readCString(wm) -// if !ok { -// return nil, errors.New("malformed delete message: full collection name") -// } - -// d.flags, wm, ok = readi32(wm) -// if !ok { -// return nil, errors.New("malformed delete message: missing OP_DELETE flags") -// } - -// d.selector, _, ok = bsoncore.ReadDocument(wm) -// if !ok { -// return nil, errors.New("malformed delete message: selector document") -// } - -// return &d, nil -// } - -// func (d *opDelete) OpCode() wiremessage.OpCode { -// return wiremessage.OpDelete -// } - -// func (d *opDelete) Encode(responseTo, requestId int32) []byte { -// var buffer []byte -// idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpDelete) -// buffer = appendCString(buffer, d.fullCollectionName) -// buffer = appendi32(buffer, d.flags) -// buffer = append(buffer, d.selector...) -// buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) -// return buffer -// } - -// func (d *opDelete) IsIsMaster() bool { -// return false -// } - -// func (d *opDelete) CursorID() (cursorID int64, ok bool) { -// return 0, false -// } - -// func (d *opDelete) RequestID() int32 { -// return d.reqID -// } - -// func (d *opDelete) Error() error { -// return nil -// } - -// func (d *opDelete) Unacknowledged() bool { -// return false -// } - -// func (d *opDelete) CommandAndCollection() (Command, string) { -// return Delete, d.fullCollectionName -// } - -// func (d *opDelete) String() string { -// return fmt.Sprintf("{ OpDelete fullCollectionName: %s, flags: %d, selector: %s }", d.fullCollectionName, d.flags, d.selector.String()) -// } - -// // https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op_kill_cursors -// type opKillCursors struct { -// reqID int32 -// cursorIDs []int64 -// } - -// func (k *opKillCursors) TransactionDetails() *TransactionDetails { -// return nil -// } - -// func decodeKillCursors(reqID int32, wm []byte) (*opKillCursors, error) { -// var ok bool -// k := opKillCursors{ -// reqID: reqID, -// } - -// _, wm, ok = wiremessage.ReadKillCursorsZero(wm) -// if !ok { -// return nil, errors.New("malformed kill_cursors message: missing zero") -// } - -// var numIDs int32 -// numIDs, wm, ok = wiremessage.ReadKillCursorsNumberIDs(wm) -// if !ok { -// return nil, errors.New("malformed kill_cursors message: missing number of cursor IDs") -// } - -// k.cursorIDs, _, ok = wiremessage.ReadKillCursorsCursorIDs(wm, numIDs) -// if !ok { -// return nil, errors.New("malformed kill_cursors message: missing cursor IDs") -// } - -// return &k, nil -// } - -// func (k *opKillCursors) OpCode() wiremessage.OpCode { -// return wiremessage.OpKillCursors -// } - -// // see https://github.com/mongodb/mongo-go-driver/blob/v1.7.2/x/mongo/driver/operation_legacy.go#L378-L384 -// func (k *opKillCursors) Encode(responseTo, requestId int32) []byte { -// var buffer []byte -// idx, buffer := wiremessage.AppendHeaderStart(buffer, 0, responseTo, wiremessage.OpKillCursors) -// buffer = wiremessage.AppendKillCursorsZero(buffer) -// buffer = wiremessage.AppendKillCursorsNumberIDs(buffer, int32(len(k.cursorIDs))) -// buffer = wiremessage.AppendKillCursorsCursorIDs(buffer, k.cursorIDs) -// buffer = bsoncore.UpdateLength(buffer, idx, int32(len(buffer[idx:]))) -// return buffer -// } - -// func (k *opKillCursors) IsIsMaster() bool { -// return false -// } - -// func (k *opKillCursors) CursorID() (cursorID int64, ok bool) { -// return 0, false -// } - -// func (k *opKillCursors) RequestID() int32 { -// return k.reqID -// } - -// func (k *opKillCursors) Error() error { -// return nil -// } - -// func (k *opKillCursors) Unacknowledged() bool { -// return false -// } - -// func (k *opKillCursors) CommandAndCollection() (Command, string) { -// return Unknown, "" -// } - -// func (k *opKillCursors) String() string { -// return fmt.Sprintf("{ OpKillCursors cursorIDs: %v }", k.cursorIDs) -// } - -func appendi32(dst []byte, i32 int32) []byte { - return append(dst, byte(i32), byte(i32>>8), byte(i32>>16), byte(i32>>24)) -} - -func appendCString(b []byte, str string) []byte { - b = append(b, str...) - return append(b, 0x00) -} - -// func readi32(src []byte) (int32, []byte, bool) { -// if len(src) < 4 { -// return 0, src, false -// } - -// return int32(src[0]) | int32(src[1])<<8 | int32(src[2])<<16 | int32(src[3])<<24, src[4:], true -// } - -// func readCString(src []byte) (string, []byte, bool) { -// idx := bytes.IndexByte(src, 0x00) -// if idx < 0 { -// return "", src, false -// } -// return string(src[:idx]), src[idx+1:], true -// } diff --git a/keploy/pkg/core/proxy/integrations/mongo/scramAuth.go b/keploy/pkg/core/proxy/integrations/mongo/scramAuth.go deleted file mode 100644 index 160df01..0000000 --- a/keploy/pkg/core/proxy/integrations/mongo/scramAuth.go +++ /dev/null @@ -1,512 +0,0 @@ -//go:build linux - -package mongo - -import ( - "context" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "strconv" - "strings" - "sync" - - scramUtil "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.keploy.io/server/v2/pkg/models" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/scram" - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// // scramauth.go (or util.go) -// func isScramAuth(logger *zap.Logger, mongoReq interface{}) bool { -// switch r := mongoReq.(type) { - -// case *models.MongoOpMessage: -// // Reuse the old isScramAuthRequest that checks if the sections -// // have "saslStart"/"saslContinue". -// return isScramAuthRequest(r.Sections, logger) - -// case *models.MongoOpQuery: -// // If your driver is sending legacy OpQuery handshake, -// // you can parse r.Query (the JSON) to see if there's a "saslStart" or "saslContinue". -// if strings.Contains(r.Query, "saslStart") || strings.Contains(r.Query, "saslContinue") { -// logger.Info("the received request is saslStart or saslContinue", -// zap.Any("OpQuery", r.Query)) -// return true -// } - -// } -// return false -// } - -func isScramAuthRequest(actualRequestSections []string, logger *zap.Logger) bool { - // Iterate over each section in the actual request sections - for _, v := range actualRequestSections { - // Extract the message from the section - actualMsg, err := extractMsgFromSection(v) - if err != nil { - utils.LogError(logger, err, "failed to extract the section of the received mongo request message", zap.Any("the section", v)) - return false - } - - conversationID, _ := extractConversationID(actualMsg) - // Check if the message is for starting the SASL (authentication) process - if _, exists := actualMsg["saslStart"]; exists { - logger.Debug("the received request is saslStart", - zap.Any("OpMsg", actualMsg), - zap.Any("conversationId", conversationID)) - return true - // Check if the message is for final request of the SASL (authentication) process - } else if _, exists := actualMsg["saslContinue"]; exists { - logger.Debug("the received request is saslContinue", - zap.Any("OpMsg", actualMsg), - zap.Any("conversationId", conversationID), - ) - return true - } - - } - return false -} - -// authMessageMap stores the auth message from the saslStart request for the converstionIds. So, that -// it can be used in the saslContinue request to generate the new server proof -var authMessageMap = sync.Map{} - -// handleScramAuth handles the SCRAM authentication requests by generating the -// appropriate response string. -// -// Parameters: -// - actualRequestSections: The sections from the received request received. -// - expectedRequestSections: The sections that are recorded in the auth request. -// - responseSection: The section to be used for the response. -// - log: The logging instance for recording activities and errors. -// -// Returns: -// - The generated response string. -// - A boolean indicating if the processing was successful. -// - An error, if any, that occurred during processing. -func handleScramAuth(ctx context.Context, actualRequestSections, expectedRequestSections []string, responseSection, mongoPassword string, logger *zap.Logger) (string, bool, error) { - // Iterate over each section in the actual request sections - for i, v := range actualRequestSections { - // single document do not uses section sequence for SCRAM auth - if !strings.HasPrefix(v, "{ SectionSingle msg:") { - continue - } - - // Extract the message from the section - actualMsg, err := extractMsgFromSection(v) - if err != nil { - utils.LogError(logger, err, "failed to extract the section of the received mongo request message") - return "", false, err - } - - // Check if the message is for starting the SASL (authentication) process - if _, exists := actualMsg["saslStart"]; exists { - mechanism, exists := actualMsg["mechanism"] - // Check the authentication mechanism used and ensure it contains "SCRAM" - if mechanism, ok := mechanism.(string); exists && ok && strings.Contains(mechanism, "SCRAM") { - if _, exists := actualMsg["payload"]; exists { - return handleSaslStart(ctx, i, actualMsg, expectedRequestSections, responseSection, logger) - } - } - // Check if the message is for final request of the SASL (authentication) process - } else if _, exists := actualMsg["saslContinue"]; exists { - if _, exists := actualMsg["payload"]; exists { - return handleSaslContinue(ctx, actualMsg, responseSection, mongoPassword, logger) - } - } - } - return "", false, nil -} - -// extractAuthPayload extracts the base64 authentication payload from a given data structure. -// -// Parameters: -// - data: The interface{} that should represent a nested map with expected keys. -// -// Returns: -// - The extracted base64 string from the nested map structure. -// - An error if the data doesn't have the expected nested structure or if the expected keys are missing. -func extractAuthPayload(data interface{}) (string, error) { - // Top-level map - topMap, ok := data.(map[string]interface{}) - if !ok { - return "", errors.New("expected top-level data to be a map") - } - - // Payload map - payload, ok := topMap["payload"].(map[string]interface{}) - if !ok { - return "", errors.New("expected 'payload' to be a map") - } - - // $binary map - binaryMap, ok := payload["$binary"].(map[string]interface{}) - if !ok { - return "", errors.New("expected '$binary' to be a map") - } - - // Base64 string - base64Str, ok := binaryMap["base64"].(string) - if !ok { - return "", errors.New("expected 'base64' to be a string") - } - - return base64Str, nil -} - -// extractConversationID extracts the 'conversationId' from a given data structure. Example: {"conversationId":{"$numberInt":"113"}} -// -// Parameters: -// - data: The interface{} that should represent a map containing the key 'conversationId'. -// -// Returns: -// - The extracted conversationId as a string. -// - An error if the expected 'conversationId' structure isn't present or if other expected keys are missing. -func extractConversationID(data interface{}) (string, error) { - // Top-level map - topMap, ok := data.(map[string]interface{}) - if !ok { - return "", errors.New("expected top-level data to be a map") - } - - conversationID, exists := topMap["conversationId"] - if !exists { - return "", errors.New("'conversationId' not found") - } - - // conversationId map - conversationIDMap, ok := conversationID.(map[string]interface{}) - if !ok { - return "", errors.New("expected 'conversationId' to be a map") - } - - // Check presence of "$numberInt" - num, exists := conversationIDMap["$numberInt"] - if !exists { - return "", errors.New("'$numberInt' not found") - } - numberIntStr, present := num.(string) - if !present { - return "", errors.New("expected '$numberInt' to be a string") - } - - return numberIntStr, nil -} - -// updateConversationID updates the 'conversationId' in a given data structure. Example: {"conversationId":{"$numberInt":"113"}} -func updateConversationID(actualMsg map[string]interface{}, newConversationID int) (map[string]interface{}, error) { - // Check if conversationID exists and is a map - conversationID, exists := actualMsg["conversationId"] - if !exists { - return actualMsg, errors.New("'conversationId' not found") - } - - conversationIDMap, ok := conversationID.(map[string]interface{}) - if !ok { - return actualMsg, errors.New("expected 'conversationId' to be a map") - } - - // Update the "$numberInt" field with the new value - conversationIDMap["$numberInt"] = fmt.Sprintf("%d", newConversationID) - actualMsg["conversationId"] = conversationIDMap - return actualMsg, nil -} - -// decodeBase64Str is a function variable that wraps the standard Base64 decoding method, -// taking a Base64 encoded string and returning its decoded byte array and any error. -var decodeBase64Str = base64.StdEncoding.DecodeString - -// extractMsgFromSection decodes an OP_MSG section string, and then -// unmarshals the resulting string into a map. -// -// Parameters: -// - section: The OP_MSG section string to decode and unmarshal. -// -// Returns: -// - A map containing the key-value pairs from the unmarshaled section. -// - An error if there's an issue during decoding or unmarshaling. -func extractMsgFromSection(section string) (map[string]interface{}, error) { - var err error - var sectionStr string - var result map[string]interface{} - - if strings.HasPrefix(section, "{ SectionSingle msg:") { - sectionStr, err = extractSectionSingle(section) - if err != nil { - return nil, err - } - err = json.Unmarshal([]byte(sectionStr), &result) - if err != nil { - return nil, err - } - } - - return result, nil -} - -func handleSaslStart(ctx context.Context, i int, actualMsg map[string]interface{}, expectedRequestSections []string, responseSection string, logger *zap.Logger) (string, bool, error) { - actualReqPayload, err := extractAuthPayload(actualMsg) - if err != nil { - utils.LogError(logger, err, "failed to fetch the payload from the received mongo request") - return "", false, err - } - logger.Debug(fmt.Sprint("the payload of the received request: ", actualReqPayload)) - - // Decode the base64 encoded payload of the received mongo request - decodedActualReqPayload, err := decodeBase64Str(actualReqPayload) - if err != nil { - utils.LogError(logger, err, "Error decoding the received payload base64 string") - return "", false, err - } - logger.Debug(fmt.Sprint("the decoded payload of the actual for the saslstart: ", (string)(decodedActualReqPayload))) - - // check to ensure that the matched recorded mongo request contains the auth payload for SCRAM - if len(expectedRequestSections) < i+1 { - err = errors.New("unrecorded message sections for the received auth request") - utils.LogError(logger, err, "failed to match the message section payload") - return "", false, err - } - - expectedMsg, err := extractMsgFromSection(expectedRequestSections[i]) - if err != nil { - utils.LogError(logger, err, "failed to extract the section of the recorded mongo request message") - return "", false, err - } - - expectedReqPayload, err := extractAuthPayload(expectedMsg) - if err != nil { - utils.LogError(logger, err, "failed to fetch the payload from the recorded mongo request") - return "", false, err - } - logger.Debug(fmt.Sprint("the payload of the recorded request: ", expectedReqPayload)) - - // Decode the base64 encoded payload of the recorded mongo request - decodedExpectedReqPayload, err := decodeBase64Str(expectedReqPayload) - if err != nil { - utils.LogError(logger, err, "Error decoding the recorded request payload base64 string") - return "", false, err - } - logger.Debug(fmt.Sprint("the decoded payload of the expected for the saslstart: ", (string)(decodedExpectedReqPayload))) - - // the payload of the recorded first response of SCRAM authentication - var responseMsg map[string]interface{} - - err = json.Unmarshal([]byte(responseSection), &responseMsg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal string document of OpReply") - return "", false, err - } - responsePayload, err := extractAuthPayload(responseMsg) - if err != nil { - utils.LogError(logger, err, "failed to fetch the payload from the recorded mongo response") - return "", false, err - } - logger.Debug(fmt.Sprint("the payload of the recorded response: ", responsePayload)) - - // Decode the base64 encoded payload of the recorded mongo response - decodedResponsePayload, err := decodeBase64Str(responsePayload) - if err != nil { - utils.LogError(logger, err, "Error decoding the recorded response payload base64 string") - return "", false, err - } - logger.Debug(fmt.Sprint("the decoded payload of the repsonse for the saslstart: ", (string)(decodedResponsePayload))) - - // Generate the first response for the saslStart request by - // replacing the old client nonce with new client nonce - newFirstAuthResponse, err := scram.GenerateServerFirstMessage(decodedExpectedReqPayload, decodedActualReqPayload, decodedResponsePayload, logger) - if err != nil { - utils.LogError(logger, err, "failed to generate the new first response for the SCRAM authentication") - return "", false, err - } - logger.Debug("after replacing the new client nonce in auth response", zap.String("first response", newFirstAuthResponse)) - // replace the payload with new first response auth - responseMsg["payload"].(map[string]interface{})["$binary"].(map[string]interface{})["base64"] = base64.StdEncoding.EncodeToString([]byte(newFirstAuthResponse)) - responseMsg, err = updateConversationID(responseMsg, int(util.GetNextID())) - if err != nil { - utils.LogError(logger, err, "failed to update the conversationId in the sasl start auth message") - return "", false, err - } - - // fetch the conversation id - conversationID, err := extractConversationID(responseMsg) - if err != nil { - utils.LogError(logger, err, "failed to fetch the conversationId for the SCRAM auth from the recorded first response") - return "", false, err - } - logger.Debug("fetch the conversationId for the SCRAM authentication", zap.String("cid", conversationID)) - // generate the auth message from the received first request and recorded first response - authMessage := scram.GenerateAuthMessage(string(decodedActualReqPayload), newFirstAuthResponse, logger) - authMechanism, ok := actualMsg["mechanism"].(string) - if !ok { - logger.Debug("failed to auth mechanism from expected request data", zap.Any("expectedRequest", actualMsg)) - } else { - if authMechanism != scramUtil.SCRAM_SHA_1 && authMechanism != scramUtil.SCRAM_SHA_256 { - logger.Error("Invalid authentication mechanism", zap.String("authMechanism", authMechanism)) - return "", false, errors.New("invalid authentication mechanism") - } - - authMessage = authMessage + ",auth=" + authMechanism - // store the auth message in the global map for the conversationId - } - connID := ctx.Value(models.ClientConnectionIDKey).(string) - authMessageMap.Store(connID+"+"+conversationID, authMessage) - - logger.Debug("generate the new auth message for the received auth request", zap.String("msg", authMessage)) - - // marshal the new first response for the SCRAM authentication - newAuthResponse, err := json.Marshal(responseMsg) - if err != nil { - utils.LogError(logger, err, "failed to marshal the first auth response for SCRAM") - return "", false, err - } - return string(newAuthResponse), true, nil -} - -// handleSaslContinue processes a SASL continuation message, updates the payload with -// the new verifier, which is prepared by the new auth message. -// -// Parameters: -// - actualMsg: The actual message map from the client. -// - responseSection: The section string to be used for the response. -// - log: The logging instance for recording activities and errors. -// -// Returns: -// - The updated response section string. -// - A boolean indicating if the processing was successful. -// - An error, if any, that occurred during processing. -func handleSaslContinue(ctx context.Context, actualMsg map[string]interface{}, responseSection, mongoPassword string, logger *zap.Logger) (string, bool, error) { - var responseMsg map[string]interface{} - - err := json.Unmarshal([]byte(responseSection), &responseMsg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal string document of OpReply") - return "", false, err - } - logger.Debug(fmt.Sprintf("the recorded OpMsg section: %v", responseMsg)) - - responsePayload, err := extractAuthPayload(responseMsg) - if err != nil { - utils.LogError(logger, err, "failed to fetch the payload from the recorded mongo response") - return "", false, err - } - logger.Debug(fmt.Sprint("the payload of the recorded second response of SCRAM: ", responsePayload)) - - decodedResponsePayload, err := decodeBase64Str(responsePayload) - if err != nil { - utils.LogError(logger, err, "Error decoding the recorded saslContinue response payload base64 string") - return "", false, err - } - logger.Debug(fmt.Sprint("the decoded payload of the repsonse for the saslContinue: ", (string)(decodedResponsePayload))) - - fields := strings.Split(string(decodedResponsePayload), ",") - verifier, err := parseFieldBase64(fields[0], "v") - if err != nil { - logger.Debug("failed to parse the verifier of final response message", zap.Any("parsing error", err.Error())) - return "", false, nil - } - logger.Debug("the recorded verifier of the auth request", zap.Any("verifier/server-signature", string(verifier))) - - // fetch the conversation id - conversationID, err := extractConversationID(actualMsg) - if err != nil { - utils.LogError(logger, err, "failed to fetch the conversationId for the SCRAM auth from the received final response") - return "", false, err - } - logger.Debug("fetched conversationId for the SCRAM authentication", zap.String("cid", conversationID), zap.String("verifier", string(verifier))) - - salt := "" - itr := 0 - authType := "" - // get the authMessage from the saslStart conversation. Since, saslContinue have the same conversationId - // authMsg := authMessageMap[conversationID] - connID := ctx.Value(models.ClientConnectionIDKey).(string) - authMessage, ok := authMessageMap.Load(connID + "+" + conversationID) - authMessageStr := "" - if ok { - authMessageStr = authMessage.(string) - } - - logger.Debug("fetch the auth message for the SCRAM authentication", zap.String("cid", conversationID), zap.String("authMessage", authMessageStr)) - - // get the salt and iteration from the authMessage to generate salted password - fields = strings.Split(authMessageStr, ",") - filteredFields := []string{} - for _, part := range fields { - if strings.HasPrefix(part, "s=") { - // Split based on "=" and get the value of "s" - saltByt, err := decodeBase64Str(strings.TrimPrefix(part, "s=")) - if err != nil { - utils.LogError(logger, err, "failed to decode the base64 string of salt") - return "", false, err - } - salt = string(saltByt) - } - if strings.HasPrefix(part, "i=") { - // Split based on "=" and get the value of "i" - itr, err = strconv.Atoi(strings.Split(part, "=")[1]) - if err != nil { - utils.LogError(logger, err, "failed to convert the string into integer") - return "", false, err - } - } - if strings.HasPrefix(part, "auth=") { - // Only add fields that are not prefixed with "auth=" - authType = strings.Split(part, "=")[1] - if err != nil { - utils.LogError(logger, err, "failed to convert the string into integer") - return "", false, err - } - } else { - filteredFields = append(filteredFields, part) - } - } - authMessageStr = strings.Join(filteredFields, ",") - // Since, the server proof is the signature generated by the authMessage and salted password. - // So, need to return the new server proof according to the new authMessage which is different from the recorded. - newVerifier, err := scram.GenerateServerFinalMessage(authMessageStr, authType, mongoPassword, salt, itr, logger) - if err != nil { - utils.LogError(logger, err, "failed to get the new server proof") - return "", false, err - } - - // tools the payload of the mongo response for the authentication - responseMsg["payload"].(map[string]interface{})["$binary"].(map[string]interface{})["base64"] = base64.StdEncoding.EncodeToString([]byte("v=" + newVerifier)) - byt, err := json.Marshal(responseMsg) - if err != nil { - utils.LogError(logger, err, "failed to marshal the updated string document of OpReply") - return "", false, err - } - responseSection = string(byt) - return responseSection, true, nil -} - -func parseField(s, k string) (string, error) { - t := strings.TrimPrefix(s, k+"=") - if t == s { - return "", fmt.Errorf("error parsing '%s' for field '%s'", s, k) - } - return t, nil -} - -func parseFieldBase64(s, k string) ([]byte, error) { - if !strings.Contains(s, k+"=") { - return nil, fmt.Errorf("verifier doesn't exist in string '%s'", s) - } - raw, err := parseField(s, k) - if err != nil { - return nil, err - } - - dec, err := decodeBase64Str(raw) - if err != nil { - return nil, err - } - - return dec, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mongo/util.go b/keploy/pkg/core/proxy/integrations/mongo/util.go deleted file mode 100644 index 31ca69a..0000000 --- a/keploy/pkg/core/proxy/integrations/mongo/util.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build linux - -package mongo - -import ( - "strings" - - "go.keploy.io/server/v2/pkg/models" - "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" - "go.uber.org/zap" -) - -func hasSecondSetBit(num int) bool { - // Shift the number right by 1 bit and check if the least significant bit is set - return (num>>1)&1 == 1 -} - -// Skip heartbeat from capturing in the global set of mocks. Since, the heartbeat packet always contain the "hello" boolean. -// See: https://github.com/mongodb/mongo-go-driver/blob/8489898c64a2d8c2e2160006eb851a11a9db9e9d/x/mongo/driver/operation/hello.go#L503 -func isHeartBeat(logger *zap.Logger, opReq Operation, requestHeader models.MongoHeader, mongoRequest interface{}) bool { - - switch requestHeader.Opcode { - case wiremessage.OpQuery: - return true - case wiremessage.OpMsg: - _, ok := mongoRequest.(*models.MongoOpMessage) - if ok { - return (opReq.IsIsAdminDB() && strings.Contains(opReq.String(), "hello")) || - opReq.IsIsMaster() || - isScramAuthRequest(mongoRequest.(*models.MongoOpMessage).Sections, logger) - } - default: - return false - } - return false -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/README.md b/keploy/pkg/core/proxy/integrations/mysql/README.md deleted file mode 100644 index 7dcd4c8..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# MySQL Package Documentation - -The `mysqlparser` package encompasses the parser and mapping logic required -to read MySql binary messages and capture and test the outputs. -Utilized by the `hooks` package, it assists in redirecting outgoing -calls for the purpose of recording or testing the outputs. - -## SSL Support - -Please note that SSL is currently not supported in the MySQL package. To use the package without SSL, you can include the following parameters in your database URL like the following example: - -`jdbc:mysql://localhost:3306/db_name?useSSL=false&allowPublicKeyRetrieval=true` - -## The following MySQL packet types are handled in the parser: - -**COM_PING**: A ping command sent to the server to check if it's alive and responsive. - -**COM_STMT_EXECUTE**: Executes a prepared statement that was prepared using the COM_STMT_PREPARE command. - -**COM_STMT_FETCH**: Fetches rows from a statement which produced a result set. Used with cursors in server-side prepared statements. - -**COM_STMT_PREPARE**: Prepares a SQL statement for execution. - -**COM_STMT_CLOSE**: Closes a prepared statement, freeing up server resources associated with it. - -**COM_CHANGE_USER**: Changes the user of the current connection and resets the connection state. - -**MySQLOK**: A packet indicating a successful operation. It is usually received after commands like INSERT, UPDATE, DELETE, etc. - -**MySQLErr**: An error packet sent from the server to the client, indicating an error occurred with the last command sent. - -**RESULT_SET_PACKET**: Contains the actual result set data returned by a query. It's a series of packets containing rows and columns of data. - -**MySQLHandshakeV10**: The initial handshake packet sent from the server to the client when a connection is established, containing authentication and connection details. - -**HANDSHAKE_RESPONSE**: The response packet sent by the client in reply to MySQLHandshakeV10, containing client authentication data. - -**MySQLQuery**: Contains a SQL query that is to be executed on the server. - -**AUTH_SWITCH_REQUEST**: Sent by the server to request an authentication method switch during the connection process. - -**AUTH_SWITCH_RESPONSE**: Sent by the client to respond to the AUTH_SWITCH_REQUEST, containing authentication data. - -**MySQLEOF**: An EOF (End Of File) packet that marks the end of a result set or the end of the fields list. - -**AUTH_MORE_DATA**: Sent by the server if it needs more data for authentication (used in plugins). - -**COM_STMT_SEND_LONG_DATA**: Sends data for a column in a row to be inserted/updated in a table using a prepared statement. - -**COM_STMT_RESET**: Resets the data of a prepared statement which was accumulated with COM_STMT_SEND_LONG_DATA commands. - -**COM_QUIT**: Sent by the client to close the connection to the server gracefully. diff --git a/keploy/pkg/core/proxy/integrations/mysql/mysql.go b/keploy/pkg/core/proxy/integrations/mysql/mysql.go deleted file mode 100644 index 3e8bba5..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/mysql.go +++ /dev/null @@ -1,62 +0,0 @@ -//go:build linux - -// Package mysql provides the MySQL integration. -package mysql - -import ( - "context" - "io" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/recorder" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/replayer" - - "go.keploy.io/server/v2/utils" - - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -func init() { - integrations.Register(integrations.MYSQL, &integrations.Parsers{ - Initializer: New, - Priority: 100, - }) -} - -type MySQL struct { - logger *zap.Logger -} - -func New(logger *zap.Logger) integrations.Integrations { - return &MySQL{ - logger: logger, - } -} - -func (m *MySQL) MatchType(_ context.Context, _ []byte) bool { - //Returning false here because sql parser is using the ports to check if the packet is mysql or not. - return false -} - -func (m *MySQL) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - logger := m.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - - err := recorder.Record(ctx, logger, src, dst, mocks, opts) - if err != nil { - utils.LogError(logger, err, "failed to encode the mysql message into the yaml") - return err - } - return nil -} - -func (m *MySQL) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { - logger := m.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - err := replayer.Replay(ctx, logger, src, dstCfg, mockDb, opts) - if err != nil && err != io.EOF { - utils.LogError(logger, err, "failed to decode the mysql message from the yaml") - return err - } - return nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/panic_fix_test.go b/keploy/pkg/core/proxy/integrations/mysql/panic_fix_test.go deleted file mode 100644 index fb85ccf..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/panic_fix_test.go +++ /dev/null @@ -1,192 +0,0 @@ -//go:build linux - -package mysql - -import ( - "context" - "fmt" - "testing" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/conn" - "go.uber.org/zap" -) - -// TestPanicFixes demonstrates that the previous panic-causing conditions now return errors instead -func TestPanicFixes(t *testing.T) { - logger := zap.NewNop() - ctx := context.Background() - - // Test cases that would have caused panics before the fix - panicCausingInputs := []struct { - name string - test func(t *testing.T) - }{ - { - name: "ReadLengthEncodedInteger with insufficient bytes for 2-byte value", - test: func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("ReadLengthEncodedInteger panicked: %v", r) - } - }() - - // This would have caused a panic before the fix - _, isNull, n := utils.ReadLengthEncodedInteger([]byte{0xFC, 0x01}) // Missing one byte - - // Should return error state (isNull=true, n=0) - if !isNull || n != 0 { - t.Errorf("Expected error state for insufficient bytes, got isNull=%t, n=%d", isNull, n) - } - }, - }, - { - name: "ReadLengthEncodedInteger with insufficient bytes for 3-byte value", - test: func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("ReadLengthEncodedInteger panicked: %v", r) - } - }() - - // This would have caused a panic before the fix - _, isNull, n := utils.ReadLengthEncodedInteger([]byte{0xFD, 0x01, 0x02}) // Missing one byte - - // Should return error state - if !isNull || n != 0 { - t.Errorf("Expected error state for insufficient bytes, got isNull=%t, n=%d", isNull, n) - } - }, - }, - { - name: "ReadLengthEncodedInteger with insufficient bytes for 8-byte value", - test: func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("ReadLengthEncodedInteger panicked: %v", r) - } - }() - - // This would have caused a panic before the fix - _, isNull, n := utils.ReadLengthEncodedInteger([]byte{0xFE, 0x01, 0x02, 0x03, 0x04}) // Missing 4 bytes - - // Should return error state - if !isNull || n != 0 { - t.Errorf("Expected error state for insufficient bytes, got isNull=%t, n=%d", isNull, n) - } - }, - }, - { - name: "DecodeHandshakeResponse with malformed connection attributes", - test: func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("DecodeHandshakeResponse panicked: %v", r) - } - }() - - // Create a packet that would have caused a panic in connection attributes parsing - data := make([]byte, 32) - // Set CLIENT_PROTOCOL_41 (0x200) + CLIENT_CONNECT_ATTRS (0x80000) - data[0] = 0x00 - data[1] = 0x02 - data[2] = 0x08 // CLIENT_CONNECT_ATTRS - data[3] = 0x00 - - // Add null terminator for username - data = append(data, 0x00) - // Add auth response - data = append(data, 0x00, 0x00) // length + filler - // Add connection attributes with insufficient data - this would have caused panic - data = append(data, 0x0A) // total length = 10 - data = append(data, 0x05) // key length = 5 - data = append(data, []byte{0x01, 0x02}...) // Only 2 bytes for key (need 5) - - // This should return an error, not panic - result, err := conn.DecodeHandshakeResponse(ctx, logger, data) - - if err == nil { - t.Errorf("Expected error but got none") - } - if result != nil { - t.Errorf("Expected nil result on error") - } - }, - }, - { - name: "DecodeHandshakeResponse with auth response overflow", - test: func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("DecodeHandshakeResponse panicked: %v", r) - } - }() - - // Create a packet that would have caused panic in auth response parsing - data := make([]byte, 32) - // CLIENT_PROTOCOL_41 only (non-plugin auth) - data[0] = 0x00 - data[1] = 0x02 - data[2] = 0x00 - data[3] = 0x00 - - // Add username - data = append(data, []byte("user")...) - data = append(data, 0x00) // null terminator - // Add auth response with overflow - this would have caused panic - data = append(data, 0x10, 0x00) // auth length = 16 + filler - data = append(data, []byte{0x01, 0x02, 0x03}...) // Only 3 bytes (need 16) - - // This should return an error, not panic - result, err := conn.DecodeHandshakeResponse(ctx, logger, data) - - if err == nil { - t.Errorf("Expected error but got none") - } - if result != nil { - t.Errorf("Expected nil result on error") - } - }, - }, - } - - for _, tc := range panicCausingInputs { - t.Run(tc.name, tc.test) - } -} - -// TestFuzzingResilience tests that random malformed data doesn't cause panics -func TestFuzzingResilience(t *testing.T) { - logger := zap.NewNop() - ctx := context.Background() - - // Test with various sizes of random/malformed data - testCases := [][]byte{ - {}, // Empty - {0xFF}, // Single byte - {0xFC}, // 2-byte marker only - {0xFD}, // 3-byte marker only - {0xFE}, // 8-byte marker only - {0xFC, 0xFF}, // 2-byte marker with 1 byte - {0xFD, 0xFF, 0xFF}, // 3-byte marker with 2 bytes - {0xFE, 0xFF, 0xFF, 0xFF, 0xFF}, // 8-byte marker with 4 bytes - make([]byte, 31), // Just under minimum handshake size - make([]byte, 100), // Large buffer with zeros - } - - for i, data := range testCases { - t.Run(fmt.Sprintf("fuzz_case_%d", i), func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("Unexpected panic with fuzz data %d: %v", i, r) - } - }() - - // Test ReadLengthEncodedInteger - utils.ReadLengthEncodedInteger(data) - - // Test DecodeHandshakeResponse - conn.DecodeHandshakeResponse(ctx, logger, data) - }) - } -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/recorder/conn.go b/keploy/pkg/core/proxy/integrations/mysql/recorder/conn.go deleted file mode 100644 index eb475ff..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/recorder/conn.go +++ /dev/null @@ -1,730 +0,0 @@ -//go:build linux - -package recorder - -import ( - "bufio" - "context" - "crypto/tls" - "fmt" - "io" - "net" - "time" - - mysqlUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" - intgUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - pTls "go.keploy.io/server/v2/pkg/core/proxy/tls" - pUtils "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// Record mode -type handshakeRes struct { - req []mysql.Request - resp []mysql.Response - requestOperation string - responseOperation string - reqTimestamp time.Time - tlsClientConn net.Conn - tlsDestConn net.Conn -} - -func handleInitialHandshake(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext, opts models.OutgoingOptions) (handshakeRes, error) { - - res := handshakeRes{ - req: make([]mysql.Request, 0), - resp: make([]mysql.Response, 0), - } - - // Read the initial handshake from the server (server-greetings) - handshake, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - utils.LogError(logger, err, "failed to read initial handshake from server") - return res, err - } - - // Write the initial handshake to the client - _, err = clientConn.Write(handshake) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write server greetings to the client") - - return res, err - } - - // Set the timestamp of the initial request - res.reqTimestamp = time.Now() - - // Decode server handshake packet - handshakePkt, err := wire.DecodePayload(ctx, logger, handshake, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode handshake packet") - return res, err - } - - // Set the intial request operation - res.requestOperation = handshakePkt.Header.Type - - // Get the initial Plugin Name - pluginName, err := wire.GetPluginName(handshakePkt.Message) - if err != nil { - utils.LogError(logger, err, "failed to get initial plugin name") - return res, err - } - - // Set the initial plugin name - decodeCtx.PluginName = pluginName - - res.resp = append(res.resp, mysql.Response{ - PacketBundle: *handshakePkt, - }) - - // Handshake response from client (or SSL request) - handshakeResponse, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - if err == io.EOF { - logger.Debug("received request buffer is empty in record mode for mysql call") - return res, err - } - utils.LogError(logger, err, "failed to read handshake response from client") - - return res, err - } - - _, err = destConn.Write(handshakeResponse) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write handshake response to server") - - return res, err - } - - // Decode client handshake response (or SSL) packet - handshakeResponsePkt, err := wire.DecodePayload(ctx, logger, handshakeResponse, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode handshake response packet") - return res, err - } - - res.req = append(res.req, mysql.Request{ - PacketBundle: *handshakeResponsePkt, - }) - - // handle the SSL request - if decodeCtx.UseSSL { - - reader := bufio.NewReader(clientConn) - initialData := make([]byte, 5) - // reading the initial data from the client connection to determine if the connection is a TLS handshake - testBuffer, err := reader.Peek(len(initialData)) - if err != nil { - if err == io.EOF && len(testBuffer) == 0 { - logger.Debug("received EOF, closing conn", zap.Error(err)) - return res, nil - } - utils.LogError(logger, err, "failed to peek the mysql request message in proxy") - return res, err - } - - multiReader := io.MultiReader(reader, clientConn) - clientConn = &pUtils.Conn{ - Conn: clientConn, - Reader: multiReader, - Logger: logger, - } - - // handle the TLS connection and get the upgraded client connection - isTLS := pTls.IsTLSHandshake(testBuffer) - if isTLS { - clientConn, err = pTls.HandleTLSConnection(ctx, logger, clientConn, opts.Backdate) - if err != nil { - utils.LogError(logger, err, "failed to handle TLS conn") - return res, err - } - } - - // upgrade the destConn to TLS if the client connection is upgraded to TLS - var tlsDestConn *tls.Conn - if isTLS { - - remoteAddr := clientConn.RemoteAddr().(*net.TCPAddr) - sourcePort := remoteAddr.Port - - url, ok := pTls.SrcPortToDstURL.Load(sourcePort) - if !ok { - utils.LogError(logger, err, "failed to fetch the destination url") - return res, err - } - - //type case the dstUrl to string - dstURL, ok := url.(string) - if !ok { - utils.LogError(logger, err, "failed to type cast the destination url") - return res, err - } - - addr := fmt.Sprintf("%v:%v", dstURL, opts.DstCfg.Port) - tlsConfig := &tls.Config{ - InsecureSkipVerify: true, - ServerName: dstURL, - } - - logger.Debug("Upgrading the destination connection to TLS", zap.String("Destination Addr", addr), zap.String("ServerName", tlsConfig.ServerName)) - - tlsDestConn = tls.Client(destConn, tlsConfig) - err = tlsDestConn.Handshake() - if err != nil { - utils.LogError(logger, err, "failed to upgrade the destination connection to TLS for mysql") - return res, err - } - logger.Debug("TLS connection established with the destination server", zap.Any("Destination Addr", destConn.RemoteAddr().String())) - - // Update the destination connection to TLS connection - destConn = tlsDestConn - } - - // Update this tls connection information in the handshake result - res.tlsClientConn = clientConn - res.tlsDestConn = destConn - - // Store (Reset) the last operation for the upgraded client connection, because after ssl request the client will send the handshake response packet again. - decodeCtx.LastOp.Store(clientConn, mysql.HandshakeV10) - - // Store the server greeting packet for the upgraded client connection - sg, ok := handshakePkt.Message.(*mysql.HandshakeV10Packet) - if !ok { - return res, fmt.Errorf("failed to type assert handshake packet") - } - decodeCtx.ServerGreetings.Store(clientConn, sg) - - // Read the handshake response packet from the client - handshakeResponse, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - if err == io.EOF { - logger.Debug("received request buffer is empty in record mode for mysql call") - return res, err - } - utils.LogError(logger, err, "failed to read handshake response from client") - - return res, err - } - - _, err = destConn.Write(handshakeResponse) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write handshake response to server") - - return res, err - } - - // Decode client handshake response packet - handshakeResponsePkt, err := wire.DecodePayload(ctx, logger, handshakeResponse, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode handshake response packet") - return res, err - } - - res.req = append(res.req, mysql.Request{ - PacketBundle: *handshakeResponsePkt, - }) - } - - // Read the next auth packet, - // It can be either auth more data if authentication from both server and client are agreed.(caching_sha2_password) - // or auth switch request if the server wants to switch the auth mechanism - // or it can be OK packet in case of native password - authData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err == io.EOF { - logger.Debug("received request buffer is empty in record mode for mysql call") - - return res, err - } - utils.LogError(logger, err, "failed to read auth or final response packet from server during handshake") - return res, err - } - - // AuthSwitchRequest: If the server sends an AuthSwitchRequest, then there must be a diff auth type with its data - // AuthMoreData: If the server sends an AuthMoreData, then it tells the auth mechanism type for the initial plugin name or for the auth switch request. - // OK/ERR: If the server sends an OK/ERR packet, in case of native password. - _, err = clientConn.Write(authData) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write auth packet to client during handshake") - return res, err - } - - // Decode auth or final response packet - authDecider, err := wire.DecodePayload(ctx, logger, authData, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode auth packet during handshake") - return res, err - } - - // check if the authDecider is of type AuthSwitchRequestPacket. - // AuthSwitchRequestPacket is sent by the server to the client to switch the auth mechanism - if _, ok := authDecider.Message.(*mysql.AuthSwitchRequestPacket); ok { - - logger.Debug("Server is changing the auth mechanism by sending AuthSwitchRequestPacket") - - //save the auth switch request packet - res.resp = append(res.resp, mysql.Response{ - PacketBundle: *authDecider, - }) - - pkt := authDecider.Message.(*mysql.AuthSwitchRequestPacket) - - // Change the plugin name due to auth switch request - decodeCtx.PluginName = pkt.PluginName - - // read the auth switch response from the client - authSwitchResponse, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - if err == io.EOF { - logger.Debug("received request buffer is empty in record mode for mysql call") - return res, err - } - utils.LogError(logger, err, "failed to read auth switch response from client") - return res, err - } - - _, err = destConn.Write(authSwitchResponse) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write auth switch response to server") - return res, err - } - - // Decode the auth switch response packet - authSwithResp, err := mysqlUtils.BytesToMySQLPacket(authSwitchResponse) - if err != nil { - utils.LogError(logger, err, "failed to parse MySQL packet") - return res, err - } - - authSwithRespPkt := &mysql.PacketBundle{ - Header: &mysql.PacketInfo{ - Header: &authSwithResp.Header, - Type: mysql.AuthSwithResponse, // there is no specific identifier for AuthSwitchResponse - }, - Message: intgUtils.EncodeBase64(authSwithResp.Payload), - } - - // save the auth switch response packet - res.req = append(res.req, mysql.Request{ - PacketBundle: *authSwithRespPkt, - }) - - logger.Debug("Auth mechanism is switched successfully") - - // read the further auth packet, now it can be either auth more data or OK packet - authData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err == io.EOF { - logger.Debug("received request buffer is empty in record mode for mysql call") - return res, err - } - utils.LogError(logger, err, "failed to read auth data from the server after handling auth switch response") - - return res, err - } - - _, err = clientConn.Write(authData) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write auth data to client after handling auth switch response") - return res, err - } - - // It can be either auth more data or OK packet - authDecider, err = wire.DecodePayload(ctx, logger, authData, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode auth data packet after handling auth switch response") - return res, err - } - } - - var authRes handshakeRes - switch authDecider.Message.(type) { - case *mysql.AuthMoreDataPacket: - authRes, err = handleAuth(ctx, logger, authDecider, clientConn, destConn, decodeCtx) - if err != nil { - return res, fmt.Errorf("failed to handle auth more data: %w", err) - } - case *mysql.OKPacket: - authRes, err = handleAuth(ctx, logger, authDecider, clientConn, destConn, decodeCtx) - if err != nil { - return res, fmt.Errorf("failed to handle ok packet: %w", err) - } - } - - setHandshakeResult(&res, authRes) - - return res, nil -} - -func setHandshakeResult(res *handshakeRes, authRes handshakeRes) { - res.req = append(res.req, authRes.req...) - res.resp = append(res.resp, authRes.resp...) - res.responseOperation = authRes.responseOperation -} - -func handleAuth(ctx context.Context, logger *zap.Logger, authPkt *mysql.PacketBundle, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (handshakeRes, error) { - res := handshakeRes{ - req: make([]mysql.Request, 0), - resp: make([]mysql.Response, 0), - } - - switch mysql.AuthPluginName(decodeCtx.PluginName) { - case mysql.Native: - res.resp = append(res.resp, mysql.Response{ - PacketBundle: *authPkt, - }) - - res.responseOperation = authPkt.Header.Type - logger.Debug("native password authentication is handled successfully") - case mysql.CachingSha2: - result, err := handleCachingSha2Password(ctx, logger, authPkt, clientConn, destConn, decodeCtx) - if err != nil { - return res, fmt.Errorf("failed to handle caching sha2 password: %w", err) - } - logger.Debug("caching sha2 password authentication is handled successfully") - setHandshakeResult(&res, result) - case mysql.Sha256: - return res, fmt.Errorf("Sha256 Password authentication is not supported") - default: - return res, fmt.Errorf("unsupported authentication plugin: %s", decodeCtx.PluginName) - } - - return res, nil -} - -func handleCachingSha2Password(ctx context.Context, logger *zap.Logger, authPkt *mysql.PacketBundle, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (handshakeRes, error) { - res := handshakeRes{ - req: make([]mysql.Request, 0), - resp: make([]mysql.Response, 0), - } - - var authMechanism string - var err error - var ok bool - var authMorePkt *mysql.AuthMoreDataPacket - - // check if the authPkt is of type AuthMoreDataPacket - if authMorePkt, ok = authPkt.Message.(*mysql.AuthMoreDataPacket); !ok { - return res, fmt.Errorf("invalid packet type for caching sha2 password mechanism, expected: AuthMoreDataPacket, found: %T", authPkt.Message) - } - - // Getting the string value of the caching_sha2_password mechanism - authMechanism, err = wire.GetCachingSha2PasswordMechanism(authMorePkt.Data[0]) - if err != nil { - return res, fmt.Errorf("failed to get caching sha2 password mechanism: %w", err) - } - authMorePkt.Data = authMechanism - - // save the auth more data packet - res.resp = append(res.resp, mysql.Response{ - PacketBundle: *authPkt, - }) - - auth, err := wire.StringToCachingSha2PasswordMechanism(authMechanism) - if err != nil { - return res, fmt.Errorf("failed to convert string to caching sha2 password mechanism: %w", err) - } - - var result handshakeRes - switch auth { - case mysql.PerformFullAuthentication: - result, err = handleFullAuth(ctx, logger, clientConn, destConn, decodeCtx) - if err != nil { - return res, fmt.Errorf("failed to handle caching sha2 password full auth: %w", err) - } - case mysql.FastAuthSuccess: - result, err = handleFastAuthSuccess(ctx, logger, clientConn, destConn, decodeCtx) - if err != nil { - return res, fmt.Errorf("failed to handle caching sha2 password fast auth success: %w", err) - } - } - - setHandshakeResult(&res, result) - - return res, nil -} - -func handleFastAuthSuccess(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (handshakeRes, error) { - res := handshakeRes{ - req: make([]mysql.Request, 0), - resp: make([]mysql.Response, 0), - } - - //As per wire shark capture, during fast auth success, server sends OK packet just after auth more data - - // read the ok/err packet from the server after auth more data - finalResp, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err == io.EOF { - logger.Debug("received request buffer is empty in record mode for mysql call") - return res, err - } - utils.LogError(logger, err, "failed to read final response packet from server") - return res, err - } - - // write the ok/err packet to the client - _, err = clientConn.Write(finalResp) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write ok/err packet to client during fast auth mechanism") - return res, err - } - - finalPkt, err := wire.DecodePayload(ctx, logger, finalResp, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode final response packet after auth data packet") - return res, err - } - - res.resp = append(res.resp, mysql.Response{ - PacketBundle: *finalPkt, - }) - - // Set the final response operation of the handshake - res.responseOperation = finalPkt.Header.Type - logger.Debug("fast auth success is handled successfully") - - return res, nil -} - -func handleFullAuth(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (handshakeRes, error) { - res := handshakeRes{ - req: make([]mysql.Request, 0), - resp: make([]mysql.Response, 0), - } - - // If the connection is using SSL, we don't need to exchange the public key and encrypted password, - // we can directly handle the plain password. - // This is because the SSL connection already provides a secure channel for the password exchange. - if decodeCtx.UseSSL { - logger.Debug("Handling caching_sha2_password full auth in SSL request, using plain password") - res2, err := handlePlainPassword(ctx, logger, clientConn, destConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to handle plain password in caching_sha2_password(full auth) in ssl request") - return res, fmt.Errorf("failed to handle plain password in caching_sha2_password full auth: %w", err) - } - // Set the final response operation of the handshake - setHandshakeResult(&res, res2) - return res, nil - } - - // read the public key request from the client - publicKeyRequest, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - utils.LogError(logger, err, "failed to read public key request from client") - return res, err - } - _, err = destConn.Write(publicKeyRequest) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write public key request to server") - return res, err - } - - publicKeyReqPkt, err := wire.DecodePayload(ctx, logger, publicKeyRequest, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode public key request packet") - return res, err - } - - res.req = append(res.req, mysql.Request{ - PacketBundle: *publicKeyReqPkt, - }) - - // read the "public key" as response from the server - pubKey, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - utils.LogError(logger, err, "failed to read public key from server") - return res, err - } - _, err = clientConn.Write(pubKey) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write public key response to client") - return res, err - } - - pubKeyPkt, err := wire.DecodePayload(ctx, logger, pubKey, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode public key packet") - return res, err - } - - pubKeyPkt.Meta = map[string]string{ - "auth operation": "public key response", - } - - res.resp = append(res.resp, mysql.Response{ - PacketBundle: *pubKeyPkt, - }) - - // read the encrypted password from the client - encryptPass, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - utils.LogError(logger, err, "failed to read encrypted password from client") - - return res, err - } - _, err = destConn.Write(encryptPass) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write encrypted password to server") - return res, err - } - - encPass, err := mysqlUtils.BytesToMySQLPacket(encryptPass) - if err != nil { - utils.LogError(logger, err, "failed to parse MySQL packet") - return res, err - } - - encryptPassPkt := &mysql.PacketBundle{ - Header: &mysql.PacketInfo{ - Header: &encPass.Header, - Type: mysql.EncryptedPassword, - }, - Message: intgUtils.EncodeBase64(encPass.Payload), - } - - res.req = append(res.req, mysql.Request{ - PacketBundle: *encryptPassPkt, - }) - - // read the final response from the server (ok or error) - finalServerResponse, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - utils.LogError(logger, err, "failed to read final response from server") - return res, err - } - _, err = clientConn.Write(finalServerResponse) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write final response to client") - - return res, err - } - - finalResPkt, err := wire.DecodePayload(ctx, logger, finalServerResponse, clientConn, decodeCtx) - - if err != nil { - utils.LogError(logger, err, "failed to decode final response packet during caching sha2 password full auth") - return res, err - } - - res.resp = append(res.resp, mysql.Response{ - PacketBundle: *finalResPkt, - }) - - // Set the final response operation of the handshake - res.responseOperation = finalResPkt.Header.Type - - logger.Debug("full auth is handled successfully") - return res, nil -} - -func handlePlainPassword(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (handshakeRes, error) { - res := handshakeRes{ - req: make([]mysql.Request, 0), - resp: make([]mysql.Response, 0), - } - - // read the plain password from the client - plainPassBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - utils.LogError(logger, err, "failed to read plain password from the client") - return res, err - } - _, err = destConn.Write(plainPassBuf) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write plain password to the server") - return res, err - } - - plainPass, err := mysqlUtils.BytesToMySQLPacket(plainPassBuf) - if err != nil { - utils.LogError(logger, err, "failed to parse MySQL packet") - return res, err - } - - plainPassPkt := &mysql.PacketBundle{ - Header: &mysql.PacketInfo{ - Header: &plainPass.Header, - Type: mysql.PlainPassword, - }, - Message: intgUtils.EncodeBase64(plainPass.Payload), - } - - res.req = append(res.req, mysql.Request{ - PacketBundle: *plainPassPkt, - }) - - // read the final response from the server (ok or error) - finalServerResponse, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - utils.LogError(logger, err, "failed to read final response from server") - return res, err - } - _, err = clientConn.Write(finalServerResponse) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write final response to client") - - return res, err - } - - finalResPkt, err := wire.DecodePayload(ctx, logger, finalServerResponse, clientConn, decodeCtx) - - if err != nil { - utils.LogError(logger, err, "failed to decode final response packet during caching sha2 password full auth (plain password)") - return res, err - } - - res.resp = append(res.resp, mysql.Response{ - PacketBundle: *finalResPkt, - }) - - // Set the final response operation of the handshake - res.responseOperation = finalResPkt.Header.Type - - logger.Debug("full auth (plain password) is handled successfully") - return res, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/recorder/query.go b/keploy/pkg/core/proxy/integrations/mysql/recorder/query.go deleted file mode 100644 index 122b57e..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/recorder/query.go +++ /dev/null @@ -1,559 +0,0 @@ -//go:build linux - -package recorder - -import ( - "context" - "fmt" - "io" - "net" - "time" - - mysqlUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func handleClientQueries(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, mocks chan<- *models.Mock, decodeCtx *wire.DecodeContext) error { - var ( - requests []mysql.Request - responses []mysql.Response - ) - - //for keeping conn alive - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - - // read the command from the client - command, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read command packet from client") - } - return err - } - - // write the command to the destination server - _, err = destConn.Write(command) - if err != nil { - utils.LogError(logger, err, "failed to write command to the server") - return err - } - - // Getting timestamp for the request - reqTimestamp := time.Now() - - commandPkt, err := wire.DecodePayload(ctx, logger, command, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode the MySQL packet from the client") - return err - } - - requests = append(requests, mysql.Request{ - PacketBundle: *commandPkt, - }) - - // handle no response commands like COM_STMT_CLOSE, COM_STMT_SEND_LONG_DATA, etc - if wire.IsNoResponseCommand(commandPkt.Header.Type) { - recordMock(ctx, requests, responses, "mocks", commandPkt.Header.Type, "NO Response Packet", mocks, reqTimestamp) - // reset the requests and responses - requests = []mysql.Request{} - responses = []mysql.Response{} - logger.Debug("No response command", zap.Any("packet", commandPkt.Header.Type)) - continue - } - - commandRespPkt, err := handleQueryResponse(ctx, logger, clientConn, destConn, decodeCtx) - if err != nil { - if err == io.EOF && commandPkt.Header.Type == mysql.CommandStatusToString(mysql.COM_QUIT) { - logger.Debug("server closed the connection without any response") - return err - } - utils.LogError(logger, err, "failed to handle the query response") - return err - } - - responses = append(responses, mysql.Response{ - PacketBundle: *commandRespPkt, - }) - - // record the mock - recordMock(ctx, requests, responses, "mocks", commandPkt.Header.Type, commandRespPkt.Header.Type, mocks, reqTimestamp) - - // reset the requests and responses - requests = []mysql.Request{} - responses = []mysql.Response{} - } - } -} - -func handleQueryResponse(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, decodeCtx *wire.DecodeContext) (*mysql.PacketBundle, error) { - // read the command response from the destination server - commandResp, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read command response from the server") - } - return nil, err - } - - // write the command response to the client - _, err = clientConn.Write(commandResp) - if err != nil { - utils.LogError(logger, err, "failed to write command response to the client") - return nil, err - } - - //decode the command response packet - commandRespPkt, err := wire.DecodePayload(ctx, logger, commandResp, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode the command response packet") - return nil, err - } - - // check if the command response is an error or ok packet - if commandRespPkt.Header.Type == mysql.StatusToString(mysql.ERR) || commandRespPkt.Header.Type == mysql.StatusToString(mysql.OK) { - logger.Debug("command response packet", zap.Any("packet", commandRespPkt.Header.Type)) - return commandRespPkt, nil - } - - // Get the last operation in order to handle current packet if it is not an error or ok packet - lastOp, ok := decodeCtx.LastOp.Load(clientConn) - if !ok { - return nil, fmt.Errorf("failed to get the last operation from the context while handling the query response") - } - - var queryResponsePkt *mysql.PacketBundle - - switch lastOp { - case mysql.COM_QUERY: - logger.Debug("Handling text result set", zap.Any("lastOp", lastOp)) - // handle the query response (TextResultSet) - queryResponsePkt, err = handleTextResultSet(ctx, logger, clientConn, destConn, commandRespPkt, decodeCtx) - if err != nil { - return nil, fmt.Errorf("failed to handle the query response packet: %w", err) - } - - case mysql.COM_STMT_PREPARE: - logger.Debug("Handling prepare Statement Response OK", zap.Any("lastOp", lastOp)) - // handle the prepared statement response (COM_STMT_PREPARE_OK) - queryResponsePkt, err = handlePreparedStmtResponse(ctx, logger, clientConn, destConn, commandRespPkt, decodeCtx) - if err != nil { - return nil, fmt.Errorf("failed to handle the prepared statement response: %w", err) - } - case mysql.COM_STMT_EXECUTE: - logger.Debug("Handling binary protocol result set", zap.Any("lastOp", lastOp)) - // handle the statment execute response (BinaryProtocolResultSet) - queryResponsePkt, err = handleBinaryResultSet(ctx, logger, clientConn, destConn, commandRespPkt, decodeCtx) - if err != nil { - return nil, fmt.Errorf("failed to handle the statement execute response: %w", err) - } - - default: - return nil, fmt.Errorf("unsupported operation: %x", lastOp) - } - - return queryResponsePkt, nil -} - -func handlePreparedStmtResponse(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, commandRespPkt *mysql.PacketBundle, decodeCtx *wire.DecodeContext) (*mysql.PacketBundle, error) { - - //commandRespPkt is the response to prepare, there are parameters, intermediate EOF, columns, and EOF packets to be handled - //ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_prepare.html#sect_protocol_com_stmt_prepare_response_ok - - responseOk, ok := commandRespPkt.Message.(*mysql.StmtPrepareOkPacket) - if !ok { - return nil, fmt.Errorf("expected StmtPrepareOkPacket, got %T", commandRespPkt.Message) - } - - logger.Debug("Parsing the params and columns in the prepared statement response", zap.Any("responseOk", responseOk)) - - //See if there are any parameters - if responseOk.NumParams > 0 { - for i := uint16(0); i < responseOk.NumParams; i++ { - - // Read the column definition packet - colData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read column data for parameter definition") - } - return nil, err - } - - // Write the column definition packet to the client - _, err = clientConn.Write(colData) - if err != nil { - utils.LogError(logger, err, "failed to write column data for parameter definition") - return nil, err - } - - // Decode the column definition packet - column, _, err := rowscols.DecodeColumn(ctx, logger, colData) - if err != nil { - return nil, fmt.Errorf("failed to decode column definition packet: %w", err) - } - - responseOk.ParamDefs = append(responseOk.ParamDefs, column) - } - - logger.Debug("ParamsDefs after parsing", zap.Any("ParamDefs", responseOk.ParamDefs)) - - // Read the EOF packet for parameter definition - eofData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read EOF packet for parameter definition") - } - return nil, err - } - - // Write the EOF packet for parameter definition to the client - _, err = clientConn.Write(eofData) - if err != nil { - utils.LogError(logger, err, "failed to write EOF packet for parameter definition to the client") - return nil, err - } - - // Validate the EOF packet for parameter definition - if !mysqlUtils.IsEOFPacket(eofData) { - return nil, fmt.Errorf("expected EOF packet for parameter definition, got %v", eofData) - } - - responseOk.EOFAfterParamDefs = eofData - - logger.Debug("Eof after param defs", zap.Any("eofData", eofData)) - } - - //See if there are any columns - if responseOk.NumColumns > 0 { - for i := uint16(0); i < responseOk.NumColumns; i++ { - - // Read the column definition packet - colData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read column data for column definition") - } - return nil, err - } - - // Write the column definition packet to the client - _, err = clientConn.Write(colData) - if err != nil { - utils.LogError(logger, err, "failed to write column data for column definition") - return nil, err - } - - // Decode the column definition packet - column, _, err := rowscols.DecodeColumn(ctx, logger, colData) - if err != nil { - return nil, fmt.Errorf("failed to decode column definition packet: %w", err) - } - - responseOk.ColumnDefs = append(responseOk.ColumnDefs, column) - } - - logger.Debug("ColumnDefs after parsing", zap.Any("ColumnDefs", responseOk.ColumnDefs)) - - // Read the EOF packet for column definition - eofData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read EOF packet for column definition") - } - return nil, err - } - - // Write the EOF packet for column definition to the client - _, err = clientConn.Write(eofData) - if err != nil { - utils.LogError(logger, err, "failed to write EOF packet for column definition to the client") - return nil, err - } - - // Validate the EOF packet for column definition - if !mysqlUtils.IsEOFPacket(eofData) { - return nil, fmt.Errorf("expected EOF packet for column definition, got %v, while handling prepared statement response", eofData) - } - - responseOk.EOFAfterColumnDefs = eofData - - logger.Debug("Eof after column defs", zap.Any("eofData", eofData)) - } - - //set the lastOp to COM_STMT_PREPARE_OK - decodeCtx.LastOp.Store(clientConn, mysql.OK) - - // commandRespPkt.Message = responseOk // need to check whether this is necessary - - return commandRespPkt, nil -} - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_query_response_text_resultset.html - -func handleTextResultSet(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, textResultSetPkt *mysql.PacketBundle, decodeCtx *wire.DecodeContext) (*mysql.PacketBundle, error) { - - // colCountPkt is the first packet of the text result set, it is followed by column definition packets, intermediate eof, row data packets and final eof - - textResultSet, ok := textResultSetPkt.Message.(*mysql.TextResultSet) - if !ok { - return nil, fmt.Errorf("expected TextResultSet, got %T", textResultSetPkt.Message) - } - - // Read the column count packet - colCount := textResultSet.ColumnCount - - // Read the column definition packets - for i := uint64(0); i < colCount; i++ { - // Read the column definition packet - colData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read column definition packet") - } - return nil, err - } - - // Write the column definition packet to the client - _, err = clientConn.Write(colData) - if err != nil { - utils.LogError(logger, err, "failed to write column definition packet") - return nil, err - } - - // Decode the column definition packet - column, _, err := rowscols.DecodeColumn(ctx, logger, colData) - if err != nil { - return nil, fmt.Errorf("failed to decode column definition packet: %w", err) - } - - textResultSet.Columns = append(textResultSet.Columns, column) - } - - if decodeCtx.ClientCapabilities&mysql.CLIENT_DEPRECATE_EOF == 0 { - logger.Debug("EOF packet is not deprecated while handling textResultSet") - - // Read the EOF packet for column definition - eofData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read EOF packet for column definition") - } - return nil, err - } - - // Write the EOF packet for column definition to the client - _, err = clientConn.Write(eofData) - if err != nil { - utils.LogError(logger, err, "failed to write EOF packet for column definition to the client") - return nil, err - } - - // Validate the EOF packet for column definition - if !mysqlUtils.IsEOFPacket(eofData) { - return nil, fmt.Errorf("expected EOF packet for column definition, got %v, while handling textResultSet", eofData) - } - - textResultSet.EOFAfterColumns = eofData - - } - // Read the row data packets -rowLoop: - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - - // Read the packet - data, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read data packet while reading row data") - } - return nil, err - } - - // Write the packet to the client - _, err = clientConn.Write(data) - if err != nil { - utils.LogError(logger, err, "failed to write data packet while reading row data") - return nil, err - } - - // // Break if the data packet is a generic response - // resp, ok := mysqlUtils.IsGenericResponse(data) - // if ok { - // textResultSet.FinalResponse = &mysql.GenericResponse{ - // Data: data, - // Type: resp, - // } - // break rowLoop - // } - - // Break if the data packet is an EOF packet, But we need to check for generic response - // Right now we are just checking for EOF packet as we couldn't differentiate between the generic response and row data packet - if mysqlUtils.IsEOFPacket(data) { - logger.Debug("Found EOF packet after row data in text resultset") - textResultSet.FinalResponse = &mysql.GenericResponse{ - Data: data, - Type: mysql.StatusToString(mysql.EOF), - } - break rowLoop - } - - // It must be a row data packet - row, _, err := rowscols.DecodeTextRow(ctx, logger, data, textResultSet.Columns) - if err != nil { - return nil, fmt.Errorf("failed to decode row data packet: %w", err) - } - textResultSet.Rows = append(textResultSet.Rows, row) - } - } - - // reset the last OP - decodeCtx.LastOp.Store(clientConn, wire.RESET) - - return textResultSetPkt, nil -} - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html -// (BinaryProtocolResultset) - -func handleBinaryResultSet(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, binaryResultSetPkt *mysql.PacketBundle, decodeCtx *wire.DecodeContext) (*mysql.PacketBundle, error) { - - // colCountPkt is the first packet of the binary result set, it is followed by column definition packets,intermediate eof, row data packets and final eof - - binaryResultSet, ok := binaryResultSetPkt.Message.(*mysql.BinaryProtocolResultSet) - if !ok { - return nil, fmt.Errorf("expected TextResultSet, got %T", binaryResultSetPkt.Message) - } - - // Read the column count packet - colCount := binaryResultSet.ColumnCount - - logger.Debug("ColCount in handleBinaryResultSet: ", zap.Any("ColCount", colCount)) - // Read the column definition packets - for i := uint64(0); i < colCount; i++ { - // Read the column definition packet - colData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read column definition packet") - } - return nil, err - } - - // Write the column definition packet to the client - _, err = clientConn.Write(colData) - if err != nil { - utils.LogError(logger, err, "failed to write column definition packet") - return nil, err - } - - // Decode the column definition packet - column, _, err := rowscols.DecodeColumn(ctx, logger, colData) - if err != nil { - return nil, fmt.Errorf("failed to decode column definition packet: %w", err) - } - - binaryResultSet.Columns = append(binaryResultSet.Columns, column) - } - - logger.Debug("Columns: ", zap.Any("Columns", binaryResultSet.Columns)) - - // Read the EOF packet for column definition - eofData, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read EOF packet for column definition") - } - return nil, err - } - - // Write the EOF packet for column definition to the client - _, err = clientConn.Write(eofData) - if err != nil { - utils.LogError(logger, err, "failed to write EOF packet for column definition to the client") - return nil, err - } - - // Validate the EOF packet for column definition - if !mysqlUtils.IsEOFPacket(eofData) { - return nil, fmt.Errorf("expected EOF packet for column definition, got %v, while handling BinaryProtocolResultSet", eofData) - } - - binaryResultSet.EOFAfterColumns = eofData - - // Read the row data packets -rowLoop: - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - - // Read the packet - data, err := mysqlUtils.ReadPacketBuffer(ctx, logger, destConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read data packet while reading row data") - } - return nil, err - } - - // Write the packet to the client - _, err = clientConn.Write(data) - if err != nil { - utils.LogError(logger, err, "failed to write data packet while reading row data") - return nil, err - } - - // Break if the data packet is a generic response - // resp, ok := mysqlUtils.IsGenericResponse(data) - // if ok { - // binaryResultSet.FinalResponse = &mysql.GenericResponse{ - // Data: data, - // Type: resp, - // } - // //debug log - // fmt.Println("Found generic response after row data") - // break rowLoop - // } - - // Break if the data packet is an EOF packet, But we need to check for generic response - // Right now we are just checking for EOF packet as we couldn't differentiate between the generic response and row data packet - if mysqlUtils.IsEOFPacket(data) { - logger.Debug("Found EOF packet after row data in binary resultset") - binaryResultSet.FinalResponse = &mysql.GenericResponse{ - Data: data, - Type: mysql.StatusToString(mysql.EOF), - } - break rowLoop - } - - // It must be a row data packet - row, _, err := rowscols.DecodeBinaryRow(ctx, logger, data, binaryResultSet.Columns) - if err != nil { - return nil, fmt.Errorf("failed to decode row data packet: %w", err) - } - binaryResultSet.Rows = append(binaryResultSet.Rows, row) - } - } - - logger.Debug("Rows: ", zap.Any("Rows", binaryResultSet.Rows)) - - // reset the last OP - decodeCtx.LastOp.Store(clientConn, wire.RESET) - - return binaryResultSetPkt, nil - -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/recorder/record.go b/keploy/pkg/core/proxy/integrations/mysql/recorder/record.go deleted file mode 100644 index 867f273..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/recorder/record.go +++ /dev/null @@ -1,131 +0,0 @@ -//go:build linux - -// Package recorder is used to record the MySQL traffic between the client and the server. -package recorder - -import ( - "context" - "errors" - "io" - "net" - "time" - - "golang.org/x/sync/errgroup" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// Binary to Mock Yaml - -func Record(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - - var ( - requests []mysql.Request - responses []mysql.Response - ) - - errCh := make(chan error, 1) - - //get the error group from the context - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context") - } - - g.Go(func() error { - defer pUtil.Recover(logger, clientConn, destConn) - defer close(errCh) - - // Helper struct for decoding packets - decodeCtx := &wire.DecodeContext{ - Mode: models.MODE_RECORD, - // Map for storing last operation per connection - LastOp: wire.NewLastOpMap(), - // Map for storing server greetings (inc capabilities, auth plugin, etc) per initial handshake (per connection) - ServerGreetings: wire.NewGreetings(), - // Map for storing prepared statements per connection - PreparedStatements: make(map[uint32]*mysql.StmtPrepareOkPacket), - } - decodeCtx.LastOp.Store(clientConn, wire.RESET) //resetting last command for new loop - - // handle the initial client-server handshake (connection phase) - result, err := handleInitialHandshake(ctx, logger, clientConn, destConn, decodeCtx, opts) - if err != nil { - utils.LogError(logger, err, "failed to handle initial handshake") - errCh <- err - return nil - } - requests = append(requests, result.req...) - responses = append(responses, result.resp...) - - reqTimestamp := result.reqTimestamp - - recordMock(ctx, requests, responses, "config", result.requestOperation, result.responseOperation, mocks, reqTimestamp) - - // reset the requests and responses - requests = []mysql.Request{} - responses = []mysql.Response{} - - if decodeCtx.UseSSL { - if result.tlsClientConn == nil || result.tlsDestConn == nil { - utils.LogError(logger, err, "Expected Tls connections are nil", zap.Any("tlsClientConn", result.tlsClientConn), zap.Any("tlsDestConn", result.tlsDestConn)) - errCh <- errors.New("tls connection is not established") - return nil - } - clientConn = result.tlsClientConn - destConn = result.tlsDestConn - } - - lstOp, _ := decodeCtx.LastOp.Load(clientConn) - logger.Debug("last operation after initial handshake", zap.Any("last operation", lstOp)) - - // handle the client-server interaction (command phase) - err = handleClientQueries(ctx, logger, clientConn, destConn, mocks, decodeCtx) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to handle client queries") - } - errCh <- err - return nil - } - return nil - }) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - return nil - } - return err - } -} - -func recordMock(ctx context.Context, requests []mysql.Request, responses []mysql.Response, mockType, requestOperation, responseOperation string, mocks chan<- *models.Mock, reqTimestampMock time.Time) { - meta := map[string]string{ - "type": mockType, - "requestOperation": requestOperation, - "responseOperation": responseOperation, - "connID": ctx.Value(models.ClientConnectionIDKey).(string), - } - mysqlMock := &models.Mock{ - Version: models.GetVersion(), - Kind: models.MySQL, - Name: mockType, - Spec: models.MockSpec{ - Metadata: meta, - MySQLRequests: requests, - MySQLResponses: responses, - Created: time.Now().Unix(), - ReqTimestampMock: reqTimestampMock, - ResTimestampMock: time.Now(), - }, - } - mocks <- mysqlMock -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/replayer/conn.go b/keploy/pkg/core/proxy/integrations/mysql/replayer/conn.go deleted file mode 100644 index b6edfb0..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/replayer/conn.go +++ /dev/null @@ -1,719 +0,0 @@ -//go:build linux - -package replayer - -import ( - "bufio" - "context" - "fmt" - "io" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - mysqlUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" - intgUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - pTls "go.keploy.io/server/v2/pkg/core/proxy/tls" - pUtils "go.keploy.io/server/v2/pkg/core/proxy/util" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type reqResp struct { - req []mysql.Request - resp []mysql.Response -} - -type handshakeRes struct { - tlsClientConn net.Conn -} - -// Replay mode -func simulateInitialHandshake(ctx context.Context, logger *zap.Logger, clientConn net.Conn, mocks []*models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext, opts models.OutgoingOptions) (handshakeRes, error) { - // Get the mock for initial handshake - initialHandshakeMock := mocks[0] - - // Read the intial request and response for the handshake from the mocks - resp := initialHandshakeMock.Spec.MySQLResponses - req := initialHandshakeMock.Spec.MySQLRequests - - res := handshakeRes{} - reqIdx, respIdx := 0, 0 - - if len(resp) == 0 || len(req) == 0 { - utils.LogError(logger, nil, "no mysql mocks found for initial handshake") - return res, nil - } - - handshake, ok := resp[respIdx].Message.(*mysql.HandshakeV10Packet) - if !ok { - utils.LogError(logger, nil, "failed to assert handshake packet") - return res, nil - } - - // Store the server greetings - decodeCtx.ServerGreetings.Store(clientConn, handshake) - - // Set the intial auth plugin - decodeCtx.PluginName = handshake.AuthPluginName - decodeCtx.ServerCaps = handshake.CapabilityFlags - var err error - - // encode the response - buf, err := wire.EncodeToBinary(ctx, logger, &resp[respIdx].PacketBundle, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to encode handshake packet") - return res, err - } - - // Write the initial handshake to the client - _, err = clientConn.Write(buf) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write server greetings to the client") - - return res, err - } - - respIdx++ - - // Read the client request, (handshake response or ssl request) - handshakeResponseBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - utils.LogError(logger, err, "failed to read handshake response from client") - return res, err - } - - // Decode the handshakeResponse or sslRequest - pkt, err := wire.DecodePayload(ctx, logger, handshakeResponseBuf, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode handshake response from client") - return res, err - } - - // handle the SSL request - if decodeCtx.UseSSL { - _, ok := pkt.Message.(*mysql.SSLRequestPacket) - if !ok { - utils.LogError(logger, nil, "failed to assert SSL request packet") - return res, nil - } - - // Get the SSL request from the mock - _, ok = req[reqIdx].Message.(*mysql.SSLRequestPacket) - if !ok { - utils.LogError(logger, nil, "failed to assert mock SSL request packet", zap.Any("expected", req[reqIdx].Header.Type)) - return res, nil - } - - // Match the SSL request from the client with the mock - err = matchSSLRequest(ctx, logger, req[reqIdx].PacketBundle, *pkt) - if err != nil { - utils.LogError(logger, err, "error while matching SSL request") - return res, err - } - reqIdx++ // matched with the mock so increment the index - - // Upgrade the client connection to TLS - reader := bufio.NewReader(clientConn) - initialData := make([]byte, 5) - // reading the initial data from the client connection to determine if the connection is a TLS handshake - testBuffer, err := reader.Peek(len(initialData)) - if err != nil { - if err == io.EOF && len(testBuffer) == 0 { - logger.Debug("received EOF, closing conn", zap.Error(err)) - return res, nil - } - utils.LogError(logger, err, "failed to peek the mysql request message in proxy") - return res, err - } - - multiReader := io.MultiReader(reader, clientConn) - clientConn = &pUtils.Conn{ - Conn: clientConn, - Reader: multiReader, - Logger: logger, - } - - // handle the TLS connection and get the upgraded client connection - isTLS := pTls.IsTLSHandshake(testBuffer) - if isTLS { - clientConn, err = pTls.HandleTLSConnection(ctx, logger, clientConn, opts.Backdate) - if err != nil { - utils.LogError(logger, err, "failed to handle TLS conn") - return res, err - } - } - - // Update this tls connection information in the handshake result - res.tlsClientConn = clientConn - - // Store (Reset) the last operation for the upgraded client connection, because after ssl request the client will send the handshake response packet again. - decodeCtx.LastOp.Store(clientConn, mysql.HandshakeV10) - - // Store the server greeting packet for the upgraded client connection - decodeCtx.ServerGreetings.Store(clientConn, handshake) - - // read the actual handshake response packet - handshakeResponseBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - utils.LogError(logger, err, "failed to read handshake response from client") - return res, err - } - - // Decode the handshakeResponse - pkt, err = wire.DecodePayload(ctx, logger, handshakeResponseBuf, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode handshake response from client") - return res, err - } - } - - hr41, ok := pkt.Message.(*mysql.HandshakeResponse41Packet) - if !ok { - utils.LogError(logger, nil, "failed to assert actual handshake response packet") - return res, nil - } - decodeCtx.ClientCaps = hr41.CapabilityFlags // live client caps - - // Get the handshake response from the mock - hrec, ok := req[reqIdx].Message.(*mysql.HandshakeResponse41Packet) - if !ok { - utils.LogError(logger, nil, "failed to assert mock handshake response packet") - return res, nil - } - decodeCtx.RecordedClientCaps = hrec.CapabilityFlags - - // Match the handshake response from the client with the mock - logger.Debug("matching handshake response", zap.Any("actual", pkt), zap.Any("mock", req[reqIdx].PacketBundle)) - err = matchHanshakeResponse41(ctx, logger, req[reqIdx].PacketBundle, *pkt) - if err != nil { - utils.LogError(logger, err, "error while matching handshakeResponse41") - return res, err - } - reqIdx++ // matched with the mock so increment the index - - // Get the next response in order to find the auth mechanism - if len(resp) < respIdx+1 { - utils.LogError(logger, nil, "no mysql mocks found for auth mechanism") - return res, nil - } - - // Get the next packet to decide the auth mechanism or auth switching - // For Native password: next packet is Ok/Err - // For CachingSha2 password: next packet is AuthMoreData - - authDecider := resp[respIdx].Header.Type - - // Check if the next packet is AuthSwitchRequest - // Server sends AuthSwitchRequest when it wants to switch the auth mechanism - if authDecider == mysql.AuthStatusToString(mysql.AuthSwitchRequest) { - logger.Debug("Auth switch request found, switching the auth mechanism") - - // Get the AuthSwitchRequest packet - authSwithReqPkt, ok := resp[respIdx].Message.(*mysql.AuthSwitchRequestPacket) - if !ok { - utils.LogError(logger, nil, "failed to assert auth switch request packet") - return res, nil - } - - // Change the auth plugin name - decodeCtx.PluginName = authSwithReqPkt.PluginName - - // Encode the AuthSwitchRequest packet - buf, err = wire.EncodeToBinary(ctx, logger, &resp[respIdx].PacketBundle, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to encode auth switch request packet") - return res, err - } - - // Write the AuthSwitchRequest packet to the client - _, err = clientConn.Write(buf) - if err != nil { - if ctx.Err() != nil { - return res, ctx.Err() - } - utils.LogError(logger, err, "failed to write auth switch request to the client") - return res, err - } - - respIdx++ - - // Read the auth switch response from the client - authSwitchRespBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - utils.LogError(logger, err, "failed to read auth switch response from the client") - return res, err - } - - // Get the packet from the buffer - authSwitchRespPkt, err := mysqlUtils.BytesToMySQLPacket(authSwitchRespBuf) - if err != nil { - utils.LogError(logger, err, "failed to convert auth switch response to packet") - return res, err - } - - if len(req) < reqIdx+1 { - utils.LogError(logger, nil, "no mysql mocks found for auth switch response") - return res, fmt.Errorf("no mysql mocks found for auth switch response") - } - - // Get the auth switch response from the mock - authSwitchRespMock := req[reqIdx].PacketBundle - - if authSwitchRespMock.Header.Type != mysql.AuthSwithResponse { - utils.LogError(logger, nil, "expected auth switch response mock not found", zap.Any("found", authSwitchRespMock.Header.Type)) - return res, fmt.Errorf("expected %s but found %s", mysql.AuthSwithResponse, authSwitchRespMock.Header.Type) - } - - // Since auth switch response data can be different, we should just check the sequence number - if authSwitchRespMock.Header.Header.SequenceID != authSwitchRespPkt.Header.SequenceID { - utils.LogError(logger, nil, "sequence number mismatch for auth switch response", zap.Any("expected", authSwitchRespMock.Header.Header.SequenceID), zap.Any("actual", authSwitchRespPkt.Header.SequenceID)) - return res, fmt.Errorf("sequence number mismatch for auth switch response") - } - - logger.Debug("auth mechanism switched successfully") - - reqIdx++ - - // Get the next packet to decide the auth mechanism - if len(resp) < respIdx+1 { - utils.LogError(logger, nil, "no mysql mocks found for auth mechanism after auth switch request") - return res, nil - } - - authDecider = resp[respIdx].Header.Type - } - - switch authDecider { - case mysql.StatusToString(mysql.OK): - var nativePassMocks reqResp - nativePassMocks.resp = resp[respIdx:] - - // It means we need to simulate the native password - err := simulateNativePassword(ctx, logger, clientConn, nativePassMocks, initialHandshakeMock, mockDb, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to simulate native password") - return res, err - } - - case mysql.AuthStatusToString(mysql.AuthMoreData): - - var cacheSha2PassMock reqResp - cacheSha2PassMock.req = req[reqIdx:] - cacheSha2PassMock.resp = resp[respIdx:] - - // It means we need to simulate the caching_sha2_password - err := simulateCacheSha2Password(ctx, logger, clientConn, cacheSha2PassMock, initialHandshakeMock, mockDb, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to simulate caching_sha2_password") - return res, err - } - } - - return res, nil -} - -func simulateNativePassword(ctx context.Context, logger *zap.Logger, clientConn net.Conn, nativePassMocks reqResp, initialHandshakeMock *models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) error { - - logger.Debug("final response for native password", zap.Any("response", nativePassMocks.resp[0].Header.Type)) - - // Send the final response (OK/Err) to the client - buf, err := wire.EncodeToBinary(ctx, logger, &nativePassMocks.resp[0].PacketBundle, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to encode final response packet for native password") - return err - } - - _, err = clientConn.Write(buf) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(logger, err, "failed to write final response for native password to the client") - return err - } - - //update the config mock (since it can be reused in case of more connections compared to record mode) - ok := updateMock(ctx, logger, initialHandshakeMock, mockDb) - if !ok { - utils.LogError(logger, nil, "failed to update the mock unfiltered mock during native password") - } - - logger.Debug("native password completed successfully") - - return nil -} - -func simulateCacheSha2Password(ctx context.Context, logger *zap.Logger, clientConn net.Conn, cacheSha2PassMock reqResp, initialHandshakeMock *models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) error { - resp := cacheSha2PassMock.resp - - // Get the AuthMoreData - if len(resp) < 1 { - utils.LogError(logger, nil, "no mysql mocks found for auth more data") - } - - //check if the response is of type AuthMoreData - if _, ok := resp[0].Message.(*mysql.AuthMoreDataPacket); !ok { - utils.LogError(logger, nil, "failed to assert auth more data packet") - return fmt.Errorf("failed to get auth more data packet, expected %T but got %T", mysql.AuthMoreDataPacket{}, resp[0].Message) - } - - // Get the auth more data packet - pkt, ok := resp[0].Message.(*mysql.AuthMoreDataPacket) - if !ok { - utils.LogError(logger, nil, "failed to assert auth more data packet") - return nil - } - - var mechanismString string - CachingSha2PasswordMechanism := pkt.Data - - logger.Debug("[DEBUG] CachingSha2PasswordMechanism CachingSha2PasswordMechanism", zap.String("mechanism", CachingSha2PasswordMechanism), zap.Binary("mechanismBytes", []byte(CachingSha2PasswordMechanism))) - - if len(CachingSha2PasswordMechanism) == 1 { - // CachingSha2PasswordMechanism single byte -> map to symbolic string - b := CachingSha2PasswordMechanism[0] - logger.Debug("[DEBUG] CachingSha2PasswordMechanism byte value", zap.Uint8("mechanismByte", b)) - mechanismString = mysql.CachingSha2PasswordToString(mysql.CachingSha2Password(b)) - } else if len(CachingSha2PasswordMechanism) > 1 { - // already symbolic - mechanismString = CachingSha2PasswordMechanism - } else { - mechanismString = "UNKNOWN" - } - logger.Debug("[DEBUG] CachingSha2PasswordMechanism normalized", zap.String("mechanismString", mechanismString)) - - authBuf, err := wire.EncodeToBinary(ctx, logger, &resp[0].PacketBundle, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to encode auth more data packet") - return err - } - - // Write the AuthMoreData packet to the client - _, err = clientConn.Write(authBuf) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(logger, err, "failed to write auth more data or auth switch request to the client") - return err - } - - if len(cacheSha2PassMock.resp) < 2 { - utils.LogError(logger, nil, "response mock not found for caching_sha2_password after auth more data") - return fmt.Errorf("response mock not found for caching_sha2_password after auth more data") - } - - //update the cacheSha2PassMock resp - cacheSha2PassMock.resp = cacheSha2PassMock.resp[1:] - - //simulate the caching_sha2_password auth mechanism - switch mechanismString { - case mysql.CachingSha2PasswordToString(mysql.PerformFullAuthentication): - err := simulateFullAuth(ctx, logger, clientConn, cacheSha2PassMock, initialHandshakeMock, mockDb, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to simulate full auth") - return err - } - case mysql.CachingSha2PasswordToString(mysql.FastAuthSuccess): - err := simulateFastAuthSuccess(ctx, logger, clientConn, cacheSha2PassMock, initialHandshakeMock, mockDb, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to simulate fast auth success") - return err - } - default: - // return an error - utils.LogError(logger, nil, "unknown caching_sha2_password mechanism", zap.String("mechanism", mechanismString)) - return fmt.Errorf("unknown caching_sha2_password mechanism: %s", mechanismString) - } - return nil -} - -func simulateFastAuthSuccess(ctx context.Context, logger *zap.Logger, clientConn net.Conn, fastAuthMocks reqResp, initialHandshakeMock *models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) error { - resp := fastAuthMocks.resp - - if len(resp) < 1 { - utils.LogError(logger, nil, "final response mock not found for fast auth success") - return fmt.Errorf("final response mock not found for fast auth success") - } - - logger.Debug("final response for fast auth success", zap.Any("response", resp[0].Header.Type)) - - // Send the final response (OK/Err) to the client - buf, err := wire.EncodeToBinary(ctx, logger, &resp[0].PacketBundle, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to encode final response packet for fast auth success") - return err - } - - _, err = clientConn.Write(buf) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(logger, err, "failed to write final response for fast auth success to the client") - return err - } - - //update the config mock (since it can be reused in case of more connections compared to record mode) - //TODO: need to check when updateMock is unsuccessful - ok := updateMock(ctx, logger, initialHandshakeMock, mockDb) - if !ok { - utils.LogError(logger, nil, "failed to update the mock unfiltered mock during fast auth success") - } - - logger.Debug("fast auth success completed successfully") - - return nil -} - -func simulateFullAuth(ctx context.Context, logger *zap.Logger, clientConn net.Conn, fullAuthMocks reqResp, initialHandshakeMock *models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) error { - - resp := fullAuthMocks.resp - req := fullAuthMocks.req - - if decodeCtx.UseSSL { - logger.Debug("This is an ssl request, simulating plain password in caching_sha2_password full auth") - err := simulatePlainPassword(ctx, logger, clientConn, fullAuthMocks, initialHandshakeMock, mockDb, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to simulate plain password in caching_sha2_password full auth") - return err - } - return nil - } - - // read the public key request from the client - publicKeyRequestBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - utils.LogError(logger, err, "failed to read public key request from client") - return err - } - - // decode the public key request - pkt, err := wire.DecodePayload(ctx, logger, publicKeyRequestBuf, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode public key request from client") - return err - } - - publicKey, ok := pkt.Message.(string) - if !ok { - utils.LogError(logger, nil, "failed to assert public key request packet") - return nil - } - - // Get the public key response from the mock - if len(req) < 1 { - utils.LogError(logger, nil, "no mysql mocks found for public key response") - return fmt.Errorf("no mysql mocks found for public key response") - } - - publicKeyMock, ok := req[0].Message.(string) - if !ok { - utils.LogError(logger, nil, "failed to assert public key response packet") - return nil - } - - // Match the header of the public key request - ok = matchHeader(*req[0].Header.Header, *pkt.Header.Header) - if !ok { - utils.LogError(logger, nil, "header mismatch for public key request", zap.Any("expected", req[0].Header.Header), zap.Any("actual", pkt.Header.Header)) - return nil - } - - // Match the public key response from the client with the mock - if publicKey != publicKeyMock { - utils.LogError(logger, nil, "public key mismatch", zap.Any("actual", publicKey), zap.Any("expected", publicKeyMock)) - return fmt.Errorf("public key mismatch") - } - - // Get the AuthMoreData for sending the public key - if len(resp) < 1 { - utils.LogError(logger, nil, "no mysql mocks found for auth more data (public key)") - return fmt.Errorf("no mysql mocks found for auth more data (public key)") - } - - // Get the AuthMoreData packet - _, ok = resp[0].Message.(*mysql.AuthMoreDataPacket) - if !ok { - utils.LogError(logger, nil, "failed to assert auth more data packet (public key)") - return nil - } - - // encode the public key response - buf, err := wire.EncodeToBinary(ctx, logger, &resp[0].PacketBundle, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to encode public key response packet") - return err - } - - // Write the public key response to the client - _, err = clientConn.Write(buf) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(logger, err, "failed to write public key response to the client") - return err - } - - // Read the encrypted password from the client - - encryptedPasswordBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - utils.LogError(logger, err, "failed to read encrypted password from client") - return err - } - - // Get the packet from the buffer - encryptedPassPkt, err := mysqlUtils.BytesToMySQLPacket(encryptedPasswordBuf) - if err != nil { - utils.LogError(logger, err, "failed to convert encrypted password to packet") - return err - } - - if len(req) < 2 { - utils.LogError(logger, nil, "no mysql mocks found for encrypted password during full auth") - return fmt.Errorf("no mysql mocks found for encrypted password during full auth") - } - - // Get the encrypted password from the mock - encryptedPassMock := req[1].PacketBundle - - if encryptedPassMock.Header.Type != mysql.EncryptedPassword { - utils.LogError(logger, nil, "expected encrypted password mock not found", zap.Any("found", encryptedPassMock.Header.Type)) - return fmt.Errorf("expected %s but found %s", mysql.EncryptedPassword, encryptedPassMock.Header.Type) - } - - // Since encrypted password can be different, we should just check the sequence number - if encryptedPassMock.Header.Header.SequenceID != encryptedPassPkt.Header.SequenceID { - utils.LogError(logger, nil, "sequence number mismatch for encrypted password", zap.Any("expected", encryptedPassMock.Header.Header.SequenceID), zap.Any("actual", encryptedPassPkt.Header.SequenceID)) - return fmt.Errorf("sequence number mismatch for encrypted password") - } - - //Now send the final response (OK/Err) to the client - if len(resp) < 2 { - utils.LogError(logger, nil, "final response mock not found for full auth") - return fmt.Errorf("final response mock not found for full auth") - } - - logger.Debug("final response for full auth", zap.Any("response", resp[1].Header.Type)) - - // Get the final response (OK/Err) from the mock - // Send the final response (OK/Err) to the client - buf, err = wire.EncodeToBinary(ctx, logger, &resp[1].PacketBundle, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to encode final response packet for full auth") - return err - } - - _, err = clientConn.Write(buf) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(logger, err, "failed to write final response for full auth to the client") - return err - } - - // FullAuth mechanism only comes for the first time unless COM_CHANGE_USER is called (that is not supported for now). - // Afterwards only fast auth success is expected. So, we can delete this. - ok = mockDb.DeleteUnFilteredMock(*initialHandshakeMock) - // TODO: need to check what to do in this case - if !ok { - utils.LogError(logger, nil, "failed to delete unfiltered mock during full auth") - } - - logger.Debug("full auth completed successfully") - - return nil -} - -func simulatePlainPassword(ctx context.Context, logger *zap.Logger, clientConn net.Conn, fullAuthMocks reqResp, initialHandshakeMock *models.Mock, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) error { - - req := fullAuthMocks.req - resp := fullAuthMocks.resp - - // read the plain password from the client - plainPassBuf, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - utils.LogError(logger, err, "failed to read plain password from client") - return err - } - - // Get the packet from the buffer - plainPassPkt, err := mysqlUtils.BytesToMySQLPacket(plainPassBuf) - if err != nil { - utils.LogError(logger, err, "failed to convert plain password to packet") - return err - } - - plainPass := string(intgUtils.EncodeBase64(plainPassPkt.Payload)) - - // Get the plain password from the mock - if len(req) < 1 { - utils.LogError(logger, nil, "no mysql mocks found for plain password") - return fmt.Errorf("no mysql mocks found for plain password") - } - - plainPassMock, ok := req[0].Message.(string) - if !ok { - utils.LogError(logger, nil, "failed to assert plain password packet") - return fmt.Errorf("failed to assert plain password packet") - } - - // Match the header of the plain password - ok = matchHeader(*req[0].Header.Header, plainPassPkt.Header) - if !ok { - utils.LogError(logger, nil, "header mismatch for plain password", zap.Any("expected", req[0].Header.Header), zap.Any("actual", plainPassPkt.Header)) - return fmt.Errorf("header mismatch for plain password") - } - - // Match the plain password from the client with the mock - if plainPass != plainPassMock { - utils.LogError(logger, nil, "plain password mismatch", zap.Any("actual", plainPass), zap.Any("expected", plainPassMock)) - return fmt.Errorf("plain password mismatch") - } - - //Now send the final response (OK/Err) to the client - if len(resp) < 1 { - utils.LogError(logger, nil, "final response mock not found for full auth (plain password)") - return fmt.Errorf("final response mock not found for full auth (plain password)") - } - - logger.Debug("final response for full auth(plain password)", zap.Any("response", resp[0].Header.Type)) - - // Get the final response (OK/Err) from the mock - // Send the final response (OK/Err) to the client - buf, err := wire.EncodeToBinary(ctx, logger, &resp[0].PacketBundle, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to encode final response packet for full auth (plain password)") - return err - } - - _, err = clientConn.Write(buf) - if err != nil { - if ctx.Err() != nil { - return ctx.Err() - } - utils.LogError(logger, err, "failed to write final response for full auth (plain password) to the client") - return err - } - - // FullAuth mechanism only comes for the first time unless COM_CHANGE_USER is called (that is not supported for now). - // Afterwards only fast auth success is expected. So, we can delete this. - ok = mockDb.DeleteUnFilteredMock(*initialHandshakeMock) - // TODO: need to check what to do in this case - if !ok { - utils.LogError(logger, nil, "failed to delete unfiltered mock during full auth (plain password) in ssl request") - } - - logger.Debug("full auth (plain-password) in ssl request completed successfully") - return nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/replayer/match.go b/keploy/pkg/core/proxy/integrations/mysql/replayer/match.go deleted file mode 100644 index 298a575..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/replayer/match.go +++ /dev/null @@ -1,749 +0,0 @@ -//go:build linux - -package replayer - -import ( - "bytes" - "context" - "fmt" - "io" - "reflect" - "strings" - "sync" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "vitess.io/vitess/go/vt/sqlparser" -) - -var querySigCache sync.Map // map[string]string - -// case-insensitive prefix check without allocation -func hasPrefixFold(s, p string) bool { - if len(s) < len(p) { - return false - } - return strings.EqualFold(s[:len(p)], p) -} - -func getQueryStructureCached(sql string) (string, error) { - if v, ok := querySigCache.Load(sql); ok { - return v.(string), nil - } - sig, err := getQueryStructure(sql) - if err == nil { - querySigCache.Store(sql, sig) - } - return sig, err -} - -func matchHeader(expected, actual mysql.Header) bool { - - // Match the payloadlength - if actual.PayloadLength != expected.PayloadLength { - return false - } - - // Match the sequence number - if actual.SequenceID != expected.SequenceID { - return false - } - - return true -} - -func matchSSLRequest(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) error { - // Match the type - if expected.Header.Type != actual.Header.Type { - return fmt.Errorf("type mismatch for ssl request") - } - - //Don't match the header, because the payload length can be different. - - // Match the payload - expectedMessage, _ := expected.Message.(*mysql.SSLRequestPacket) - actualMessage, _ := actual.Message.(*mysql.SSLRequestPacket) - - // Match the MaxPacketSize - if expectedMessage.MaxPacketSize != actualMessage.MaxPacketSize { - return fmt.Errorf("max packet size mismatch for ssl request, expected: %d, actual: %d", expectedMessage.MaxPacketSize, actualMessage.MaxPacketSize) - } - - // Match the CharacterSet - if expectedMessage.CharacterSet != actualMessage.CharacterSet { - return fmt.Errorf("character set mismatch for ssl request, expected: %d, actual: %d", expectedMessage.CharacterSet, actualMessage.CharacterSet) - } - - // Match the Filler - if expectedMessage.Filler != actualMessage.Filler { - return fmt.Errorf("filler mismatch for ssl request, expected: %v, actual: %v", expectedMessage.Filler, actualMessage.Filler) - } - - return nil -} - -func matchHanshakeResponse41(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) error { - // Match the type - if expected.Header.Type != actual.Header.Type { - return fmt.Errorf("type mismatch for handshake response") - } - - //Don't match the header, because the payload length can be different. - - // Match the payload - - //Get the packet type from both the packet bundles - // we don't need to do type assertion because its already done in the caller function - - exp := expected.Message.(*mysql.HandshakeResponse41Packet) - act := actual.Message.(*mysql.HandshakeResponse41Packet) - - // // Match the CapabilityFlags - // if exp.CapabilityFlags != act.CapabilityFlags { - // return fmt.Errorf("capability flags mismatch for handshake response, expected: %d, actual: %d", exp.CapabilityFlags, act.CapabilityFlags) - // } - - // Match the MaxPacketSize - if exp.MaxPacketSize != act.MaxPacketSize { - return fmt.Errorf("max packet size mismatch for handshake response, expected: %d, actual: %d", exp.MaxPacketSize, act.MaxPacketSize) - } - - // Match the CharacterSet - if exp.CharacterSet != act.CharacterSet { - return fmt.Errorf("character set mismatch for handshake response, expected: %d, actual: %d", exp.CharacterSet, act.CharacterSet) - } - - // Match the Filler - if exp.Filler != act.Filler { - return fmt.Errorf("filler mismatch for handshake response, expected: %v, actual: %v", exp.Filler, act.Filler) - } - - // Match the Username - if exp.Username != act.Username { - return fmt.Errorf("username mismatch for handshake response, expected: %s, actual: %s", exp.Username, act.Username) - } - - // Match the AuthResponse - if !bytes.Equal(exp.AuthResponse, act.AuthResponse) { - return fmt.Errorf("auth response mismatch for handshake response, expected: %v, actual: %v", exp.AuthResponse, act.AuthResponse) - } - - // Match the Database (backward-compatible: ignore old mocks with junk bytes / off-by-one) - if !dbEqualCompat(exp.Database, act.Database) { - return fmt.Errorf("database mismatch for handshake response, expected: %s, actual: %s", printable(exp.Database), printable(act.Database)) - } - - // Match the AuthPluginName (tolerate unknown/garbled plugin names in old mocks) - if !pluginEqualCompat(exp.AuthPluginName, act.AuthPluginName) { - return fmt.Errorf("auth plugin name mismatch for handshake response, expected: %s, actual: %s", printable(exp.AuthPluginName), printable(act.AuthPluginName)) - } - - // // Match the ConnectionAttributes - // if len(exp.ConnectionAttributes) != len(act.ConnectionAttributes) { - // return fmt.Errorf("connection attributes length mismatch for handshake response, expected: %d, actual: %d", len(exp.ConnectionAttributes), len(act.ConnectionAttributes)) - // } - - // for key, value := range exp.ConnectionAttributes { - // if act.ConnectionAttributes[key] != value && key != "_pid" { - // return fmt.Errorf("connection attributes mismatch for handshake response, expected: %s, actual: %s", value, act.ConnectionAttributes[key]) - // } - // } - - // Match the ZstdCompressionLevel - if exp.ZstdCompressionLevel != act.ZstdCompressionLevel { - return fmt.Errorf("zstd compression level mismatch for handshake response") - } - - return nil -} - -func matchCommand(ctx context.Context, logger *zap.Logger, req mysql.Request, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext) (*mysql.Response, bool, error) { - // Precompute string constants once (avoid frequent map lookups) - var ( - sCOM_QUIT = mysql.CommandStatusToString(mysql.COM_QUIT) - sCOM_QUERY = mysql.CommandStatusToString(mysql.COM_QUERY) - sCOM_STMT_PREP = mysql.CommandStatusToString(mysql.COM_STMT_PREPARE) - sCOM_STMT_EXEC = mysql.CommandStatusToString(mysql.COM_STMT_EXECUTE) - sCOM_STMT_CLOSE = mysql.CommandStatusToString(mysql.COM_STMT_CLOSE) - sCOM_INIT_DB = mysql.CommandStatusToString(mysql.COM_INIT_DB) - sCOM_STATS = mysql.CommandStatusToString(mysql.COM_STATISTICS) - sCOM_DEBUG = mysql.CommandStatusToString(mysql.COM_DEBUG) - sCOM_PING = mysql.CommandStatusToString(mysql.COM_PING) - sCOM_RESET_CONN = mysql.CommandStatusToString(mysql.COM_RESET_CONNECTION) - ) - - // Fast path: QUIT may have no mock - if req.Header.Type == sCOM_QUIT { - return nil, false, io.EOF - } - - // Single fetch; no struct copies (see MockManager changes) - unfiltered, err := mockDb.GetUnFilteredMocks() - if err != nil { - if ctx.Err() != nil { - return nil, false, ctx.Err() - } - utils.LogError(logger, err, "failed to get unfiltered mocks") - return nil, false, err - } - if len(unfiltered) == 0 { - utils.LogError(logger, nil, "no mysql mocks found") - return nil, false, fmt.Errorf("no mysql mocks found") - } - - var ( - maxMatchedCount int - matchedResp *mysql.Response - matchedMock *models.Mock - queryMatched bool - ) - - // Single pass: filter & match on the fly. - for _, mock := range unfiltered { - if mock.Kind != models.MySQL { - continue - } - if mock.Spec.Metadata["type"] == "config" { - continue // command-phase only wants data mocks - } - for _, mockReq := range mock.Spec.MySQLRequests { - select { - case <-ctx.Done(): - return nil, false, ctx.Err() - default: - } - switch req.Header.Type { - case sCOM_STMT_CLOSE: - if c := matchClosePacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { - maxMatchedCount, matchedResp, matchedMock = c, &mysql.Response{}, mock - } - case sCOM_QUERY: - if ok, c := matchQueryPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); ok { - matchedResp, matchedMock, queryMatched = &mock.Spec.MySQLResponses[0], mock, true - } else if c > maxMatchedCount { - maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock - } - case sCOM_STMT_PREP: - if ok, c := matchPreparePacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); ok { - matchedResp, matchedMock, queryMatched = &mock.Spec.MySQLResponses[0], mock, true - } else if c > maxMatchedCount { - maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock - } - case sCOM_STMT_EXEC: - if c := matchStmtExecutePacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { - maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock - } - case sCOM_INIT_DB: - if c := matchInitDbPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { - maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock - } - case sCOM_STATS: - if c := matchStatisticsPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { - maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock - } - case sCOM_DEBUG: - if c := matchDebugPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { - maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock - } - case sCOM_PING: - if c := matchPingPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { - maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock - } - case sCOM_RESET_CONN: - if c := matchResetConnectionPacket(ctx, logger, mockReq.PacketBundle, req.PacketBundle); c > maxMatchedCount { - maxMatchedCount, matchedResp, matchedMock = c, &mock.Spec.MySQLResponses[0], mock - } - } - } - if queryMatched { - break - } - } - - if matchedResp == nil { - // Graceful generic OK for common control statements (no mocks) - if req.Header.Type == sCOM_QUERY { - if qp, ok := req.Message.(*mysql.QueryPacket); ok { - q := strings.TrimSpace(qp.Query) - switch { - case strings.EqualFold(q, "BEGIN"), - strings.EqualFold(q, "START TRANSACTION"), - strings.EqualFold(q, "COMMIT"), - strings.EqualFold(q, "ROLLBACK"), - hasPrefixFold(q, "SET "), - // NEW: DDL/control that only expects an OK from server - hasPrefixFold(q, "ALTER "), - hasPrefixFold(q, "CREATE "), - hasPrefixFold(q, "DROP "), - hasPrefixFold(q, "TRUNCATE "), - hasPrefixFold(q, "RENAME "), - hasPrefixFold(q, "LOCK TABLES"), - hasPrefixFold(q, "UNLOCK TABLES"), - hasPrefixFold(q, "SAVEPOINT "), - hasPrefixFold(q, "RELEASE SAVEPOINT "), - hasPrefixFold(q, "USE "): - // Build a minimal OK; encoder will set length from payload. - seq := byte(1) - if req.PacketBundle.Header != nil && req.PacketBundle.Header.Header != nil { - seq = req.PacketBundle.Header.Header.SequenceID + 1 - } - generic := &mysql.Response{ - PacketBundle: mysql.PacketBundle{ - Header: &mysql.PacketInfo{ - Header: &mysql.Header{PayloadLength: 7, SequenceID: seq}, - Type: mysql.StatusToString(mysql.OK), - }, - Message: &mysql.OKPacket{ - Header: mysql.OK, - AffectedRows: 0, - LastInsertID: 0, - StatusFlags: 0x0002, - Warnings: 0, - Info: "", - }, - }, - } - logger.Debug("Returning synthetic OK for unmocked control/DDL", zap.String("query", q)) - - return generic, true, nil - } - } - } - return nil, false, nil - } - - // Persist prepared-statement metadata - if req.Header.Type == sCOM_STMT_PREP { - if prepareOkResp, ok := matchedResp.Message.(*mysql.StmtPrepareOkPacket); ok && prepareOkResp != nil { - decodeCtx.PreparedStatements[prepareOkResp.StatementID] = prepareOkResp - } - } - - if okk := updateMock(ctx, logger, matchedMock, mockDb); !okk { - logger.Debug("failed to update the matched mock") - // Re-fetch once to avoid spin - return nil, false, fmt.Errorf("failed to update matched mock") - } - return matchedResp, true, nil -} - -func matchClosePacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { - matchCount := 0 - // Match the type and return zero if the types are not equal - if expected.Header.Type != actual.Header.Type { - return 0 - } - // Match the header - ok := matchHeader(*expected.Header.Header, *actual.Header.Header) - if ok { - matchCount += 2 - } - expectedMessage, _ := expected.Message.(*mysql.StmtClosePacket) - actualMessage, _ := actual.Message.(*mysql.StmtClosePacket) - // Match the statementID - if expectedMessage.StatementID == actualMessage.StatementID { - matchCount++ - } - return matchCount -} - -func getQueryStructure(sql string) (string, error) { - - opts := sqlparser.Options{} - parser, err := sqlparser.New(opts) - if err != nil { - return "", fmt.Errorf("failed to create MYSQL query parser: %w", err) - } - - stmt, err := parser.Parse(sql) - if err != nil { - return "", fmt.Errorf("failed to parse SQL: %w", err) - } - - var structureParts []string - // Walk the AST and collect the Go type of each grammatical node. - err = sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) { - structureParts = append(structureParts, reflect.TypeOf(node).String()) - return true, nil - }, stmt) - - if err != nil { - return "", fmt.Errorf("failed to walk the AST: %w", err) - } - - return strings.Join(structureParts, "->"), nil -} - -func matchQuery(_ context.Context, log *zap.Logger, expected, actual mysql.PacketBundle, getQuery func(packet mysql.PacketBundle) string) (bool, int) { - matchCount := 0 - - // Match the type and return zero if the types are not equal - if expected.Header.Type != actual.Header.Type { - return false, 0 - } - - expectedQuery := getQuery(expected) - actualQuery := getQuery(actual) - - if actual.Header.Header.PayloadLength == expected.Header.Header.PayloadLength { - matchCount++ - if expectedQuery == actualQuery { - matchCount++ - log.Debug("Query Exact matched", - zap.String("expected query", expectedQuery), - zap.String("actual query", actualQuery)) - return true, matchCount - } - } - - // check if any of them the query is dml and other is not, then there is no match. - if sqlparser.IsDML(expectedQuery) && !sqlparser.IsDML(actualQuery) { - log.Debug("expected query is dml but actual is not", - zap.String("expected query", expectedQuery), - zap.String("actual query", actualQuery)) - return false, 0 - } else if !sqlparser.IsDML(expectedQuery) && sqlparser.IsDML(actualQuery) { - log.Debug("actual query is dml but expected is not", - zap.String("expected query", expectedQuery), - zap.String("actual query", actualQuery)) - return false, 0 - } - - if !(sqlparser.IsDML(expectedQuery) && sqlparser.IsDML(actualQuery)) { - log.Debug("No Query is dml", - zap.String("expected query", expectedQuery), - zap.String("actual query", actualQuery)) - return false, matchCount - } - - // Here we can compare the structure of the queries, as both are DML queries. - log.Debug("Both queries are DML", - zap.String("expected query", expectedQuery), - zap.String("actual query", actualQuery)) - - actualSignature, err := getQueryStructureCached(actualQuery) - if err != nil { - log.Warn("failed to get actual query structure", - zap.String("actual Query", actualQuery), - zap.Error(err)) - return false, matchCount - } - - expectedSignature, err := getQueryStructureCached(expectedQuery) - if err != nil { - log.Warn("failed to get expected query structure", - zap.String("expected Query", expectedQuery), - zap.Error(err)) - return false, matchCount - } - - if expectedSignature == actualSignature { - log.Debug("query structure matched", - zap.String("expected signature", expectedSignature), - zap.String("actual signature", actualSignature)) - return true, matchCount - } - - return false, matchCount -} - -func matchQueryPacket(ctx context.Context, log *zap.Logger, expected, actual mysql.PacketBundle) (bool, int) { - getQuery := func(packet mysql.PacketBundle) string { - msg, _ := packet.Message.(*mysql.QueryPacket) - return msg.Query - } - return matchQuery(ctx, log, expected, actual, getQuery) -} - -func matchPreparePacket(ctx context.Context, log *zap.Logger, expected, actual mysql.PacketBundle) (bool, int) { - getQuery := func(packet mysql.PacketBundle) string { - msg, _ := packet.Message.(*mysql.StmtPreparePacket) - return msg.Query - } - return matchQuery(ctx, log, expected, actual, getQuery) -} - -func matchStmtExecutePacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { - matchCount := 0 - - // Match the type and return zero if the types are not equal - if expected.Header.Type != actual.Header.Type { - return 0 - } - // Match the header - if matchHeader(*expected.Header.Header, *actual.Header.Header) { - matchCount += 2 - } - expectedMessage, _ := expected.Message.(*mysql.StmtExecutePacket) - actualMessage, _ := actual.Message.(*mysql.StmtExecutePacket) - // Match the status - if expectedMessage.Status == actualMessage.Status { - matchCount++ - } - // Match the statementID - if expectedMessage.StatementID == actualMessage.StatementID { - matchCount++ - } - // Match the flags - if expectedMessage.Flags == actualMessage.Flags { - matchCount++ - } - // Match the iteration count - if expectedMessage.IterationCount == actualMessage.IterationCount { - matchCount++ - } - // Match the parameter count - if expectedMessage.ParameterCount == actualMessage.ParameterCount { - matchCount++ - } - - // Match the newParamsBindFlag - if expectedMessage.NewParamsBindFlag == actualMessage.NewParamsBindFlag { - matchCount++ - } - - // Match the parameters - if len(expectedMessage.Parameters) == len(actualMessage.Parameters) { - for i := range expectedMessage.Parameters { - ep := expectedMessage.Parameters[i] - ap := actualMessage.Parameters[i] - if ep.Type == ap.Type && - ep.Name == ap.Name && - ep.Unsigned == ap.Unsigned && - paramValueEqual(ep.Value, ap.Value) { - matchCount++ - } - } - } - return matchCount -} - -func paramValueEqual(a, b interface{}) bool { - switch av := a.(type) { - case []byte: - bv, ok := b.([]byte) - return ok && bytes.Equal(av, bv) - case string: - bv, ok := b.(string) - return ok && av == bv - case int: - bv, ok := b.(int) - return ok && av == bv - case int32: - bv, ok := b.(int32) - return ok && av == bv - case int64: - switch bv := b.(type) { - case int64: - return av == bv - case int: - return av == int64(bv) - } - case uint32: - bv, ok := b.(uint32) - return ok && av == bv - case uint64: - bv, ok := b.(uint64) - return ok && av == bv - case float32: - bv, ok := b.(float32) - return ok && av == bv - case float64: - bv, ok := b.(float64) - return ok && av == bv - case bool: - bv, ok := b.(bool) - return ok && av == bv - } - // Fallback (rare) - return reflect.DeepEqual(a, b) -} - -// matching for utility commands -func matchQuitPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { - matchCount := 0 - // Match the type and return zero if the types are not equal - if expected.Header.Type != actual.Header.Type { - return 0 - } - // Match the header - if matchHeader(*expected.Header.Header, *actual.Header.Header) { - matchCount += 2 - } - expectedMessage, _ := expected.Message.(*mysql.QuitPacket) - actualMessage, _ := actual.Message.(*mysql.QuitPacket) - // Match the command for quit packet - if expectedMessage.Command == actualMessage.Command { - matchCount++ - } - return matchCount -} - -func matchInitDbPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { - matchCount := 0 - // Match the type and return zero if the types are not equal - if expected.Header.Type != actual.Header.Type { - return 0 - } - // Match the header - if matchHeader(*expected.Header.Header, *actual.Header.Header) { - matchCount += 2 - } - expectedMessage, _ := expected.Message.(*mysql.InitDBPacket) - actualMessage, _ := actual.Message.(*mysql.InitDBPacket) - // Match the command for init db packet - if expectedMessage.Command == actualMessage.Command { - matchCount++ - } - // Match the schema for init db packet - if expectedMessage.Schema == actualMessage.Schema { - matchCount++ - } - return matchCount -} - -func matchStatisticsPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { - matchCount := 0 - // Match the type and return zero if the types are not equal - if expected.Header.Type != actual.Header.Type { - return 0 - } - // Match the header - if matchHeader(*expected.Header.Header, *actual.Header.Header) { - matchCount += 2 - } - expectedMessage, _ := expected.Message.(*mysql.StatisticsPacket) - actualMessage, _ := actual.Message.(*mysql.StatisticsPacket) - // Match the command for statistics packet - if expectedMessage.Command == actualMessage.Command { - matchCount++ - } - return matchCount -} - -func matchDebugPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { - matchCount := 0 - // Match the type and return zero if the types are not equal - if expected.Header.Type != actual.Header.Type { - return 0 - } - // Match the header - if matchHeader(*expected.Header.Header, *actual.Header.Header) { - matchCount += 2 - } - expectedMessage, _ := expected.Message.(*mysql.DebugPacket) - actualMessage, _ := actual.Message.(*mysql.DebugPacket) - // Match the command for debug packet - if expectedMessage.Command == actualMessage.Command { - matchCount++ - } - return matchCount -} - -func matchPingPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { - matchCount := 0 - // Match the type and return zero if the types are not equal - if expected.Header.Type != actual.Header.Type { - return 0 - } - // Match the header - if matchHeader(*expected.Header.Header, *actual.Header.Header) { - matchCount += 2 - } - expectedMessage, _ := expected.Message.(*mysql.PingPacket) - actualMessage, _ := actual.Message.(*mysql.PingPacket) - // Match the command for ping packet - if expectedMessage.Command == actualMessage.Command { - matchCount++ - } - return matchCount -} - -func matchResetConnectionPacket(_ context.Context, _ *zap.Logger, expected, actual mysql.PacketBundle) int { - matchCount := 0 - // Match the type and return zero if the types are not equal - if expected.Header.Type != actual.Header.Type { - return 0 - } - // Match the header - if matchHeader(*expected.Header.Header, *actual.Header.Header) { - matchCount += 2 - } - expectedMessage, _ := expected.Message.(*mysql.ResetConnectionPacket) - actualMessage, _ := actual.Message.(*mysql.ResetConnectionPacket) - // Match the command for reset connection packet - if expectedMessage.Command == actualMessage.Command { - matchCount++ - } - return matchCount -} - -// The same function is used in http parser as well, If you find this useful you can extract it to a common package -// and delete the duplicate code. -// updateMock processes the matched mock based on its filtered status. -func updateMock(_ context.Context, logger *zap.Logger, matchedMock *models.Mock, mockDb integrations.MockMemDb) bool { - originalMatchedMock := *matchedMock - matchedMock.TestModeInfo.IsFiltered = false - matchedMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() - updated := mockDb.UpdateUnFilteredMock(&originalMatchedMock, matchedMock) - if !updated { - logger.Debug("failed to update matched mock") - } - return updated -} - -// printable strips non-printable bytes (common in legacy mocks) -func printable(s string) string { - var b strings.Builder - for _, r := range s { - if r >= 32 && r <= 126 { - b.WriteRune(r) - } - } - return b.String() -} - -// Back-compat database compare: -// - Strip junk bytes -// - Treat empty on either side as OK -// - Allow suffix match (e.g., "...uss" vs "ss") to accommodate off-by-one legacy encodes -func dbEqualCompat(exp, act string) bool { - ex := printable(strings.TrimSpace(exp)) - ac := printable(strings.TrimSpace(act)) - if ex == ac { - return true - } - if ex == "" || ac == "" { - return true - } - return strings.HasSuffix(ex, ac) || strings.HasSuffix(ac, ex) -} - -var knownPlugins = map[string]struct{}{ - "caching_sha2_password": {}, - "mysql_native_password": {}, - "mysql_clear_password": {}, -} - -// Back-compat plugin compare: -// - Strip junk -// - If both are known plugin names and differ -> mismatch -// - Otherwise (unknown/garbled on either side) -> tolerate -func pluginEqualCompat(exp, act string) bool { - ex := printable(strings.TrimSpace(exp)) - ac := printable(strings.TrimSpace(act)) - if ex == ac { - return true - } - _, exKnown := knownPlugins[ex] - _, acKnown := knownPlugins[ac] - if exKnown && acKnown { - return false - } - return true -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/replayer/query.go b/keploy/pkg/core/proxy/integrations/mysql/replayer/query.go deleted file mode 100644 index e290ef9..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/replayer/query.go +++ /dev/null @@ -1,184 +0,0 @@ -//go:build linux - -package replayer - -import ( - "context" - "fmt" - "io" - "net" - "time" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - mysqlUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func simulateCommandPhase(ctx context.Context, logger *zap.Logger, clientConn net.Conn, mockDb integrations.MockMemDb, decodeCtx *wire.DecodeContext, opts models.OutgoingOptions) error { - - // Log initial mock state at the start of command phase - total, cfg, data := mockDb.GetMySQLCounts() - logger.Info("Command phase starting", - zap.Int("total_mysql_mocks", total), - zap.Int("config_mocks", cfg), - zap.Int("data_mocks_available", data)) - - commandCount := 0 - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - commandCount++ - - logger.Debug("Starting new command iteration", - zap.Int("command_count", commandCount)) - - // Set a read deadline on the client connection - readTimeout := 2 * time.Second * time.Duration(opts.SQLDelay) - err := clientConn.SetReadDeadline(time.Now().Add(readTimeout)) - if err != nil { - utils.LogError(logger, err, "failed to set read deadline on client conn") - return err - } - - logger.Debug("About to read next command from client", - zap.Int("command_count", commandCount), - zap.Duration("read_timeout", readTimeout)) - - // read the command from the client - command, err := mysqlUtils.ReadPacketBuffer(ctx, logger, clientConn) - if err != nil { - if ne, ok := err.(net.Error); ok && ne.Timeout() { - // Idle wait: keep the connection open and continue polling - logger.Debug("read timeout waiting for next client command; keeping connection open") - // Optional: back off a bit to avoid hot loop - time.Sleep(50 * time.Millisecond) - // Clear deadline or set another future deadline, then keep looping - _ = clientConn.SetReadDeadline(time.Now().Add(readTimeout)) - continue - } - if err == io.EOF { - logger.Debug("client closed the connection (EOF)") - } else { - utils.LogError(logger, err, "failed to read command packet from client") - } - return err - } - - logger.Debug("Successfully read command from client", - zap.Int("command_count", commandCount), - zap.Int("command_size_bytes", len(command))) - - // reset the read deadline - err = clientConn.SetReadDeadline(time.Time{}) - if err != nil { - utils.LogError(logger, err, "failed to reset read deadline on client conn") - return err - } - - // Decode the command - commandPkt, err := wire.DecodePayload(ctx, logger, command, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to decode the MySQL packet from the client") - } - - req := mysql.Request{ - PacketBundle: *commandPkt, - } - - // Match the request with the mock - resp, ok, err := matchCommand(ctx, logger, req, mockDb, decodeCtx) - if err != nil { - if err == io.EOF { - logger.Info("Connection closing due to EOF from matchCommand", - zap.Int("commands_processed", commandCount), - zap.String("request_type", req.Header.Type)) - return io.EOF - } - logger.Error("Connection closing due to match command error", - zap.Error(err), - zap.Int("commands_processed", commandCount), - zap.String("request_type", req.Header.Type)) - utils.LogError(logger, err, "failed to match the command") - return err - } - - if !ok { - logger.Error("Connection closing due to no matching mock found", - zap.Int("commands_processed", commandCount), - zap.String("request_type", req.Header.Type)) - utils.LogError(logger, nil, "No matching mock found for the command", zap.Any("command", command)) - return fmt.Errorf("error while simulating the command phase due to no matching mock found") - } - - logger.Debug("Matched the command with the mock", zap.Any("mock", resp)) - - // Handle prepared statement cleanup for COM_STMT_CLOSE - if commandPkt.Header.Type == mysql.CommandStatusToString(mysql.COM_STMT_CLOSE) { - if closePacket, ok := commandPkt.Message.(*mysql.StmtClosePacket); ok { - delete(decodeCtx.PreparedStatements, closePacket.StatementID) - logger.Debug("Cleaned up prepared statement", zap.Uint32("StatementID", closePacket.StatementID)) - } - } - - // We could have just returned before matching the command for no response commands. - // But we need to remove the corresponding mock from the mockDb for no response commands. - if wire.IsNoResponseCommand(commandPkt.Header.Type) { - // No response for COM_STMT_CLOSE and COM_STMT_SEND_LONG_DATA - logger.Debug("No response for the command", zap.Any("command", command)) - continue - } - - //Encode the matched resp - buf, err := wire.EncodeToBinary(ctx, logger, &resp.PacketBundle, clientConn, decodeCtx) - if err != nil { - utils.LogError(logger, err, "failed to encode the response", zap.Any("response", resp)) - return err - } - - logger.Debug("About to write response to client", - zap.String("request_type", req.Header.Type), - zap.String("response_type", resp.Header.Type), - zap.Int("response_size_bytes", len(buf)), - zap.Int("commands_processed", commandCount)) - - // Write the response to the client - _, err = clientConn.Write(buf) - if err != nil { - if ctx.Err() != nil { - logger.Debug("context done while writing the response to the client", zap.Error(ctx.Err())) - return ctx.Err() - } - logger.Error("Failed to write response to client", - zap.Error(err), - zap.String("request_type", req.Header.Type), - zap.Int("commands_processed", commandCount)) - utils.LogError(logger, err, "failed to write the response to the client") - return err - } - - logger.Debug("successfully wrote the response to the client", - zap.Any("request", req.Header.Type), - zap.String("response_type", resp.Header.Type), - zap.Int("response_size_bytes", len(buf)), - zap.Int("commands_processed", commandCount)) - - // Check connection state after writing large response - if len(buf) > 1000 { - logger.Debug("Large response written, checking connection state", - zap.Int("response_size_bytes", len(buf)), - zap.String("request_type", req.Header.Type)) - } - - // Add a small delay and log to see if connection is still alive - logger.Debug("Response write completed, continuing to next iteration", - zap.Int("commands_processed", commandCount), - zap.String("last_request", req.Header.Type)) - } - } -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/replayer/replay.go b/keploy/pkg/core/proxy/integrations/mysql/replayer/replay.go deleted file mode 100644 index 807051d..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/replayer/replay.go +++ /dev/null @@ -1,124 +0,0 @@ -//go:build linux - -// Package replayer is used to mock the MySQL traffic between the client and the server. -package replayer - -import ( - "context" - "io" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire" - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// Mock Yaml to Binary - -func Replay(ctx context.Context, logger *zap.Logger, clientConn net.Conn, _ *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { - errCh := make(chan error, 1) - - unfiltered, err := mockDb.GetUnFilteredMocks() - if err != nil { - utils.LogError(logger, err, "failed to get unfiltered mocks") - return err - } - - var configMocks []*models.Mock - var hasMySQLMocks bool - var totalMySQLMocks int - var dataMocks int - // Get the mocks having "config" metadata and check for any MySQL mocks in a single pass. - for _, mock := range unfiltered { - if mock.Kind == models.MySQL { - hasMySQLMocks = true - totalMySQLMocks++ - if mock.Spec.Metadata["type"] == "config" { - configMocks = append(configMocks, mock) - } else { - dataMocks++ - } - } - } - - logger.Info("MySQL replay session starting", - zap.Int("total_unfiltered_mocks", len(unfiltered)), - zap.Int("total_mysql_mocks", totalMySQLMocks), - zap.Int("config_mocks", len(configMocks)), - zap.Int("data_mocks", dataMocks), - zap.Bool("has_mysql_mocks", hasMySQLMocks)) - - if !hasMySQLMocks { - utils.LogError(logger, nil, "no mysql mocks found") - return nil - } - - if len(configMocks) == 0 { - utils.LogError(logger, nil, "no mysql config mocks found for handshake") - return nil - } - - go func(errCh chan error, configMocks []*models.Mock, mockDb integrations.MockMemDb, opts models.OutgoingOptions) { - defer pUtil.Recover(logger, clientConn, nil) - defer close(errCh) - - // Helper struct for decoding packets - decodeCtx := &wire.DecodeContext{ - Mode: models.MODE_TEST, - // Map for storing last operation per connection - LastOp: wire.NewLastOpMap(), - // Map for storing server greetings (inc capabilities, auth plugin, etc) per initial handshake (per connection) - ServerGreetings: wire.NewGreetings(), - // Map for storing prepared statements per connection - PreparedStatements: make(map[uint32]*mysql.StmtPrepareOkPacket), - PluginName: string(mysql.CachingSha2), // usually a default plugin in newer versions of MySQL - PreferRecordedCaps: true, - } - decodeCtx.LastOp.Store(clientConn, wire.RESET) //resetting last command for new loop - - // Simulate the initial client-server handshake (connection phase) - - res, err := simulateInitialHandshake(ctx, logger, clientConn, configMocks, mockDb, decodeCtx, opts) - if err != nil { - utils.LogError(logger, err, "failed to simulate initial handshake") - errCh <- err - return - } - - if decodeCtx.UseSSL { - if res.tlsClientConn == nil { - logger.Error("SSL is enabled but could not get the tls client connection") - errCh <- nil - return - } - clientConn = res.tlsClientConn - } - - logger.Debug("Initial handshake completed successfully") - - // Simulate the client-server interaction (command phase) - err = simulateCommandPhase(ctx, logger, clientConn, mockDb, decodeCtx, opts) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to simulate command phase") - } - errCh <- err - return - } - - }(errCh, configMocks, mockDb, opts) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - return nil - } - return err - } -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/utils/util.go b/keploy/pkg/core/proxy/integrations/mysql/utils/util.go deleted file mode 100644 index 2c3a31e..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/utils/util.go +++ /dev/null @@ -1,360 +0,0 @@ -//go:build linux - -// Package utils provides utility functions for MySQL packets -package utils - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - "io" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// ReadFirstBuffer reads the first buffer from either clientConn or destConn -func ReadFirstBuffer(ctx context.Context, logger *zap.Logger, clientConn, destConn net.Conn) ([]byte, string, error) { - // Attempt to read from destConn first - buf, err := util.ReadBytes(ctx, logger, destConn) - // If there is data from destConn, return it - if err == nil { - return buf, "destination", nil - } - // If the error is a timeout, try to read from clientConn - if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - buf, err = util.ReadBytes(ctx, logger, clientConn) - // If there is data from clientConn, return it - if err == nil { - return buf, "client", nil - } - // Return any error from reading clientConn - return nil, "", err - } - // Return any other error from reading destConn - return nil, "", err -} - -// ReadPacketStream reads packets from the connection and sends them to the bufferChannel -func ReadPacketStream(ctx context.Context, logger *zap.Logger, conn net.Conn, bufferChannel chan []byte, errChannel chan error) { - for { - select { - case <-ctx.Done(): - // errChannel <- ctx.Err() - return - default: - if conn == nil { - logger.Debug("the conn is nil") - } - buffer, err := ReadPacketBuffer(ctx, logger, conn) - if err != nil { - if ctx.Err() != nil { // to avoid sending buffer to closed channel if the context is cancelled - return - } - if err != io.EOF { - utils.LogError(logger, err, "failed to read mysql packet buffer") - } - errChannel <- err - return - } - if ctx.Err() != nil { // to avoid sending buffer to closed channel if the context is cancelled - return - } - bufferChannel <- buffer - } - } -} - -// ReadPacketBuffer reads a MySQL packet from the connection -func ReadPacketBuffer(ctx context.Context, logger *zap.Logger, conn net.Conn) ([]byte, error) { - var packetBuffer []byte - // first read the header length - header, err := util.ReadRequiredBytes(ctx, logger, conn, 4) - if err != nil { - if err == io.EOF { - return nil, err - } - // return packetBuffer, fmt.Errorf("failed to read mysql packet header: %w", err) - return packetBuffer, err - } - - packetBuffer = append(packetBuffer, header...) - - // read the payload length - payloadLength := GetPayloadLength(header[:3]) - if payloadLength > 0 { - payload, err := util.ReadRequiredBytes(ctx, logger, conn, int(payloadLength)) - if err != nil { - if err == io.EOF { - return nil, err - } - // return packetBuffer, fmt.Errorf("failed to read mysql packet payload: %w", err) - return packetBuffer, err - } - packetBuffer = append(packetBuffer, payload...) - } - - return packetBuffer, nil -} - -// BytesToMySQLPacket converts a byte slice to a MySQL packet -func BytesToMySQLPacket(buffer []byte) (mysql.Packet, error) { - if len(buffer) < 4 { - return mysql.Packet{}, errors.New("buffer is nil or too short to be a valid MySQL packet") - } - - tempBuffer := make([]byte, 4) - copy(tempBuffer, buffer[:3]) - length := binary.LittleEndian.Uint32(tempBuffer) - sequenceID := buffer[3] - - payload := buffer[4:] - - return mysql.Packet{ - Header: mysql.Header{ - PayloadLength: length, - SequenceID: sequenceID, - }, - Payload: payload, - }, nil -} - -// GetPayloadLength returns the length of the payload from the first 3 bytes of the packet. -func GetPayloadLength(src []byte) (length uint32) { - length = uint32(src[0]) | uint32(src[1])<<8 | uint32(src[2])<<16 - return length -} - -func ReadLengthEncodedInteger(b []byte) (num uint64, isNull bool, n int) { - if len(b) == 0 { - return 0, true, 0 - } - - switch b[0] { - // 251: NULL - case 0xfb: - return 0, true, 1 - - // 252: value of following 2 - case 0xfc: - if len(b) < 3 { - return 0, true, 0 - } - return uint64(b[1]) | uint64(b[2])<<8, false, 3 - - // 253: value of following 3 - case 0xfd: - if len(b) < 4 { - return 0, true, 0 - } - return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4 - - // 254: value of following 8 - case 0xfe: - if len(b) < 9 { - return 0, true, 0 - } - return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 | - uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 | - uint64(b[7])<<48 | uint64(b[8])<<56, - false, 9 - } - - // 0-250: value of first byte - return uint64(b[0]), false, 1 -} - -func IsEOFPacket(data []byte) bool { - if len(data) < 5 { - return false // Packet is too short to be valid - } - - // Check if the first byte is 5 or 7 - if data[0] != 5 && data[0] != 7 { - return false - } - - // Check if the packet contains the EOF marker 0xFE - return len(data) > 0 && data[4] == 0xFE -} - -func IsERRPacket(data []byte) bool { - return len(data) > 9 && data[4] == mysql.ERR -} - -func IsOKPacket(data []byte) bool { - return len(data) > 7 && data[4] == mysql.OK -} - -func IsGenericResponse(data []byte) (string, bool) { - if IsOKPacket(data) { - return "OK", true - } - if IsERRPacket(data) { - return "ERR", true - } - if IsEOFPacket(data) { - return "EOF", true - } - return "", false -} - -func ReadUint24(b []byte) uint32 { - return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 -} - -func ReadLengthEncodedString(b []byte) ([]byte, bool, int, error) { - // Get length - num, isNull, n := ReadLengthEncodedInteger(b) - if num < 1 { - return b[n:n], isNull, n, nil - } - - n += int(num) - - // Check data length - if len(b) >= n { - return b[n-int(num) : n : n], false, n, nil - } - return nil, false, n, io.EOF -} - -// ReadNullTerminatedString reads a null-terminated string from a byte slice -func ReadNullTerminatedString(b []byte) ([]byte, int, error) { - i := bytes.IndexByte(b, 0x00) - if i == -1 { - return nil, 0, io.EOF - } - return b[:i], i + 1, nil -} - -func WriteStream(ctx context.Context, logger *zap.Logger, conn net.Conn, buff [][]byte) error { - for _, b := range buff { - if ctx.Err() != nil { - return ctx.Err() - } - _, err := conn.Write(b) - if err != nil { - utils.LogError(logger, err, "failed to write to connection") - return err - } - } - return nil -} - -func WriteLengthEncodedString(buf *bytes.Buffer, s string) error { - length := len(s) - if err := WriteLengthEncodedInteger(buf, uint64(length)); err != nil { - return err - } - if _, err := buf.WriteString(s); err != nil { - return err - } - return nil -} - -func WriteLengthEncodedInteger(buf *bytes.Buffer, num uint64) error { - switch { - case num <= 250: - if err := buf.WriteByte(byte(num)); err != nil { - return err - } - case num <= 0xFFFF: - if err := buf.WriteByte(0xFC); err != nil { - return err - } - if err := binary.Write(buf, binary.LittleEndian, uint16(num)); err != nil { - return err - } - case num <= 0xFFFFFF: - if err := buf.WriteByte(0xFD); err != nil { - return err - } - num24 := []byte{byte(num), byte(num >> 8), byte(num >> 16)} - if _, err := buf.Write(num24); err != nil { - return err - } - default: - if err := buf.WriteByte(0xFE); err != nil { - return err - } - if err := binary.Write(buf, binary.LittleEndian, num); err != nil { - return err - } - } - return nil -} - -func WriteUint24(buf *bytes.Buffer, value uint32) error { - if value > 0xFFFFFF { - return errors.New("value exceeds 24 bits") - } - buf.WriteByte(byte(value)) - buf.WriteByte(byte(value >> 8)) - buf.WriteByte(byte(value >> 16)) - return nil -} - -func ParseBinaryDate(b []byte) (interface{}, int, error) { - if len(b) == 0 { - return nil, 0, nil - } - length := b[0] - if length == 0 { - return nil, 1, nil - } - year := binary.LittleEndian.Uint16(b[1:3]) - month := b[3] - day := b[4] - return fmt.Sprintf("%04d-%02d-%02d", year, month, day), int(length) + 1, nil -} - -func ParseBinaryDateTime(b []byte) (interface{}, int, error) { - if len(b) == 0 { - return nil, 0, nil - } - length := b[0] - if length == 0 { - return nil, 1, nil - } - year := binary.LittleEndian.Uint16(b[1:3]) - month := b[3] - day := b[4] - hour := b[5] - minute := b[6] - second := b[7] - if length > 7 { - microsecond := binary.LittleEndian.Uint32(b[8:12]) - return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", year, month, day, hour, minute, second, microsecond), int(length) + 1, nil - } - return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second), int(length) + 1, nil -} - -func ParseBinaryTime(b []byte) (interface{}, int, error) { - if len(b) == 0 { - return nil, 0, nil - } - length := b[0] - if length == 0 { - return nil, 1, nil - } - isNegative := b[1] == 1 - days := binary.LittleEndian.Uint32(b[2:6]) - hours := b[6] - minutes := b[7] - seconds := b[8] - var microseconds uint32 - if length > 8 { - microseconds = binary.LittleEndian.Uint32(b[9:13]) - } - timeString := fmt.Sprintf("%d %02d:%02d:%02d.%06d", days, hours, minutes, seconds, microseconds) - if isNegative { - timeString = "-" + timeString - } - return timeString, int(length) + 1, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/utils/util_test.go b/keploy/pkg/core/proxy/integrations/mysql/utils/util_test.go deleted file mode 100644 index fe5a6a1..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/utils/util_test.go +++ /dev/null @@ -1,211 +0,0 @@ -//go:build linux - -package utils - -import ( - "fmt" - "testing" -) - -func TestReadLengthEncodedInteger(t *testing.T) { - tests := []struct { - name string - input []byte - expectedNum uint64 - expectedNull bool - expectedN int - expectValid bool - }{ - // Test empty input - { - name: "empty input", - input: []byte{}, - expectedNum: 0, - expectedNull: true, - expectedN: 0, - expectValid: true, - }, - - // Test single byte values (0-250) - { - name: "single byte value 0", - input: []byte{0x00}, - expectedNum: 0, - expectedNull: false, - expectedN: 1, - expectValid: true, - }, - { - name: "single byte value 250", - input: []byte{0xFA}, - expectedNum: 250, - expectedNull: false, - expectedN: 1, - expectValid: true, - }, - - // Test NULL value (251) - { - name: "null value", - input: []byte{0xFB}, - expectedNum: 0, - expectedNull: true, - expectedN: 1, - expectValid: true, - }, - - // Test 2-byte values (252) - { - name: "2-byte value with sufficient data", - input: []byte{0xFC, 0x01, 0x02}, - expectedNum: 0x0201, // 513 in little endian - expectedNull: false, - expectedN: 3, - expectValid: true, - }, - { - name: "2-byte value with insufficient data (panic fix test)", - input: []byte{0xFC, 0x01}, // Missing one byte - expectedNum: 0, - expectedNull: true, - expectedN: 0, - expectValid: true, // Should not panic, returns error state - }, - { - name: "2-byte value with only marker (panic fix test)", - input: []byte{0xFC}, // Missing both bytes - expectedNum: 0, - expectedNull: true, - expectedN: 0, - expectValid: true, // Should not panic, returns error state - }, - - // Test 3-byte values (253) - { - name: "3-byte value with sufficient data", - input: []byte{0xFD, 0x01, 0x02, 0x03}, - expectedNum: 0x030201, // little endian - expectedNull: false, - expectedN: 4, - expectValid: true, - }, - { - name: "3-byte value with insufficient data (panic fix test)", - input: []byte{0xFD, 0x01, 0x02}, // Missing one byte - expectedNum: 0, - expectedNull: true, - expectedN: 0, - expectValid: true, - }, - { - name: "3-byte value with only marker (panic fix test)", - input: []byte{0xFD}, // Missing all bytes - expectedNum: 0, - expectedNull: true, - expectedN: 0, - expectValid: true, - }, - - // Test 8-byte values (254) - { - name: "8-byte value with sufficient data", - input: []byte{0xFE, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - expectedNum: 0x0807060504030201, // little endian - expectedNull: false, - expectedN: 9, - expectValid: true, - }, - { - name: "8-byte value with insufficient data (panic fix test)", - input: []byte{0xFE, 0x01, 0x02, 0x03, 0x04}, // Missing 4 bytes - expectedNum: 0, - expectedNull: true, - expectedN: 0, - expectValid: true, - }, - { - name: "8-byte value with only marker (panic fix test)", - input: []byte{0xFE}, // Missing all bytes - expectedNum: 0, - expectedNull: true, - expectedN: 0, - expectValid: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Test that it doesn't panic - defer func() { - if r := recover(); r != nil { - t.Errorf("ReadLengthEncodedInteger panicked with input %v: %v", tt.input, r) - } - }() - - num, isNull, n := ReadLengthEncodedInteger(tt.input) - - if num != tt.expectedNum { - t.Errorf("Expected num %d, got %d", tt.expectedNum, num) - } - if isNull != tt.expectedNull { - t.Errorf("Expected isNull %t, got %t", tt.expectedNull, isNull) - } - if n != tt.expectedN { - t.Errorf("Expected n %d, got %d", tt.expectedN, n) - } - }) - } -} - -func TestReadLengthEncodedIntegerEdgeCases(t *testing.T) { - // Test with malformed data that previously caused panics - edgeCases := [][]byte{ - {0xFC}, // 2-byte marker with no data - {0xFC, 0x01}, // 2-byte marker with only 1 byte - {0xFD}, // 3-byte marker with no data - {0xFD, 0x01}, // 3-byte marker with only 1 byte - {0xFD, 0x01, 0x02}, // 3-byte marker with only 2 bytes - {0xFE}, // 8-byte marker with no data - {0xFE, 0x01}, // 8-byte marker with only 1 byte - {0xFE, 0x01, 0x02, 0x03}, // 8-byte marker with only 4 bytes - } - - for i, data := range edgeCases { - t.Run(fmt.Sprintf("edge_case_%d", i), func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("ReadLengthEncodedInteger panicked with edge case %d (data: %v): %v", i, data, r) - } - }() - - _, isNull, n := ReadLengthEncodedInteger(data) - - // Should return error state (isNull=true, n=0) for insufficient data - if len(data) > 0 && data[0] >= 0xFC { - if !isNull || n != 0 { - t.Errorf("Expected error state (isNull=true, n=0) for insufficient data, got isNull=%t, n=%d", isNull, n) - } - } - }) - } -} - -func BenchmarkReadLengthEncodedInteger(b *testing.B) { - testCases := []struct { - name string - data []byte - }{ - {"single_byte", []byte{0x42}}, - {"two_bytes", []byte{0xFC, 0x01, 0x02}}, - {"three_bytes", []byte{0xFD, 0x01, 0x02, 0x03}}, - {"eight_bytes", []byte{0xFE, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}, - } - - for _, tc := range testCases { - b.Run(tc.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { - ReadLengthEncodedInteger(tc.data) - } - }) - } -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/decode.go b/keploy/pkg/core/proxy/integrations/mysql/wire/decode.go deleted file mode 100644 index f443a3f..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/decode.go +++ /dev/null @@ -1,433 +0,0 @@ -//go:build linux - -// Package wire provides encoding and decoding operation of MySQL packets. -package wire - -import ( - "context" - "fmt" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase" - connection "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/conn" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/utility" - - itgUtils "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -// DecodePayload is used to decode mysql packets that don't consist of multiple packets within them, because we are reading per packet. -func DecodePayload(ctx context.Context, logger *zap.Logger, data []byte, clientConn net.Conn, decodeCtx *DecodeContext) (*mysql.PacketBundle, error) { - //Parse the data into mysql header and payload - packet, err := utils.BytesToMySQLPacket(data) - if err != nil { - return &mysql.PacketBundle{}, fmt.Errorf("failed to parse MySQL packet: %w", err) - } - - if len(packet.Payload) < 1 { - return &mysql.PacketBundle{}, fmt.Errorf("invalid packet, payload is empty") - } - - lastOp, ok := decodeCtx.LastOp.Load(clientConn) - if !ok { - logger.Debug("Last operation not found in DecodePayload") - lastOp = 0x00 - } - - logger.Debug("Last operation in DecodePayload", zap.String("operation", fmt.Sprintf("%#x", lastOp)), zap.Any("header", packet.Header)) - logger.Debug("Mode", zap.Any("Mode", decodeCtx.Mode)) - - if (lastOp == mysql.COM_QUERY || lastOp == mysql.COM_STMT_EXECUTE) && decodeCtx.Mode == models.MODE_RECORD { - return handleQueryStmtResponse(ctx, logger, packet, clientConn, lastOp, decodeCtx) - } - - parsedPacket, err := decodePacket(ctx, logger, packet, clientConn, lastOp, decodeCtx) - if err != nil { - return &mysql.PacketBundle{}, fmt.Errorf("failed to decode packet: %w", err) - } - - return parsedPacket, nil -} - -func handleQueryStmtResponse(ctx context.Context, logger *zap.Logger, packet mysql.Packet, clientConn net.Conn, lastOp byte, decodeCtx *DecodeContext) (*mysql.PacketBundle, error) { - //Get the Header & payload of the packet - header := packet.Header - payload := packet.Payload - - parsedPacket := &mysql.PacketBundle{ - Header: &mysql.PacketInfo{ - Header: &header, - }, - } - - payloadType := payload[0] - - sg, ok := decodeCtx.ServerGreetings.Load(clientConn) - if !ok { - return parsedPacket, fmt.Errorf("server Greetings not found") - } - - logger.Debug("Last operation when handling client query", zap.Any("last operation", mysql.CommandStatusToString(lastOp))) - - switch payloadType { - case mysql.OK: - pkt, err := phase.DecodeOk(ctx, payload, sg.CapabilityFlags) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode OK packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.OK), clientConn, RESET, decodeCtx) - - case mysql.ERR: - - pkt, err := phase.DecodeERR(ctx, payload, sg.CapabilityFlags) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode ERR packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.ERR), clientConn, RESET, decodeCtx) - - case mysql.EOF: - pkt, err := phase.DecodeEOF(ctx, payload, sg.CapabilityFlags) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode EOF packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.EOF), clientConn, RESET, decodeCtx) - - case mysql.LocalInFile: - parsedPacket.Header.Type = "LocalInFile" - decodeCtx.LastOp.Store(clientConn, RESET) //reset the last operation - return parsedPacket, fmt.Errorf("LocalInFile not supported") - default: - //If the packet is not OK, ERR, EOF or LocalInFile, then it is a result set - var pktType string - var rowType query.RowType - if lastOp == mysql.COM_STMT_EXECUTE { - rowType = query.Binary - pktType = string(mysql.Binary) - } else { - rowType = query.Text - pktType = string(mysql.Text) - } - - pkt, err := query.DecodeResultSetMetadata(ctx, logger, payload, rowType) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode result set: %w", err) - } - - // Do not change the last operation if the packet is a result set, it will be changed when the result set is fully received - setPacketInfo(ctx, parsedPacket, pkt, pktType, clientConn, lastOp, decodeCtx) - } - - return parsedPacket, nil -} - -func decodePacket(ctx context.Context, logger *zap.Logger, packet mysql.Packet, clientConn net.Conn, lastOp byte, decodeCtx *DecodeContext) (*mysql.PacketBundle, error) { - //Get the Header & payload of the packet - header := packet.Header - payload := packet.Payload - - parsedPacket := &mysql.PacketBundle{ - Header: &mysql.PacketInfo{ - Header: &header, - }, - } - - payloadType := payload[0] - - var sg *mysql.HandshakeV10Packet - var ok bool - // No need to find the server greetings in the map if the payload is HandshakeV10 because it is the first packet and going to be stored in the map - if payloadType != mysql.HandshakeV10 { - sg, ok = decodeCtx.ServerGreetings.Load(clientConn) - if !ok { - return parsedPacket, fmt.Errorf("server Greetings not found") - } - } - - logger.Debug("payload info", zap.Any("last operation", lastOp), zap.Any("payload type", payloadType)) - - // Handle handshakeResponse41 separately, because its status is not defined and can be changed with the client capabilities. - if lastOp == mysql.HandshakeV10 { - logger.Debug("HandshakeResponse41/SSL Request packet", zap.Any("Type", payloadType)) - pkt, err := connection.DecodeHandshakeResponse(ctx, logger, payload) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode HandshakeResponse41 packet: %w", err) - } - - var pktType string - switch pkt := pkt.(type) { - case *mysql.HandshakeResponse41Packet: - // Store the client capabilities to use it later - decodeCtx.ClientCapabilities = pkt.CapabilityFlags - - pktType = mysql.HandshakeResponse41 - lastOp = payloadType - case *mysql.SSLRequestPacket: - // Store the client capabilities to use it later - decodeCtx.ClientCapabilities = pkt.CapabilityFlags - - pktType = mysql.SSLRequest - decodeCtx.UseSSL = true - logger.Info("SSL Request packet detected") - // Don't change the last operation if the packet is an SSL Request - } - - setPacketInfo(ctx, parsedPacket, pkt, pktType, clientConn, lastOp, decodeCtx) - - logger.Debug("HandshakeResponse41/SSL Request decoded", zap.Any("parsed packet", parsedPacket)) - - return parsedPacket, nil - } - - switch { - // generic response packets - case payloadType == mysql.EOF && len(payload) == 5: //assuming that the payload is always 5 bytes - logger.Debug("EOF packet", zap.Any("Type", payloadType)) - pkt, err := phase.DecodeEOF(ctx, payload, sg.CapabilityFlags) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode EOF packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.EOF), clientConn, mysql.EOF, decodeCtx) - logger.Debug("EOF decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == mysql.ERR: - logger.Debug("ERR packet", zap.Any("Type", payloadType)) - pkt, err := phase.DecodeERR(ctx, payload, sg.CapabilityFlags) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode ERR packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.ERR), clientConn, mysql.ERR, decodeCtx) - logger.Debug("ERR decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == mysql.OK: - if lastOp == mysql.COM_STMT_PREPARE { - logger.Debug("COM_STMT_PREPARE_OK packet", zap.Any("Type", payloadType)) - pkt, err := preparedstmt.DecodePrepareOk(ctx, logger, payload, sg.CapabilityFlags) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode COM_STMT_PREPARE_OK packet: %w", err) - } - - // Do not change the last operation if the packet is a prepared statement, it will be changed when the prepared statement is fully received - setPacketInfo(ctx, parsedPacket, pkt, "COM_STMT_PREPARE_OK", clientConn, lastOp, decodeCtx) - // Store the prepared statement to use it later - decodeCtx.PreparedStatements[pkt.StatementID] = pkt - logger.Debug("Prepared statement stored", zap.Any("statementId", pkt.StatementID), zap.Any("prepared statement", pkt)) - logger.Debug("COM_STMT_PREPARE_OK decoded", zap.Any("parsed packet", parsedPacket)) - - } else { - logger.Debug("OK packet", zap.Any("Type", payloadType)) - pkt, err := phase.DecodeOk(ctx, payload, sg.CapabilityFlags) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode OK packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.StatusToString(mysql.OK), clientConn, mysql.OK, decodeCtx) - logger.Debug("OK decoded", zap.Any("parsed packet", parsedPacket)) - } - - // auth packets - case payloadType == 0x01: - if len(payload) == 1 { - logger.Debug("COM_QUIT packet", zap.Any("Type", payloadType)) - pkt := &mysql.QuitPacket{ - Command: payloadType, - } - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_QUIT), clientConn, mysql.COM_QUIT, decodeCtx) - logger.Debug("COM_QUIT decoded", zap.Any("parsed packet", parsedPacket)) - } else { - //otherwise it is a AuthMoreData packet - logger.Debug("AuthMoreData packet", zap.Any("Type", payloadType)) - pkt, err := connection.DecodeAuthMoreData(ctx, payload) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode AuthMoreData packet: %w", err) - } - setPacketInfo(ctx, parsedPacket, pkt, mysql.AuthStatusToString(mysql.AuthMoreData), clientConn, mysql.AuthMoreData, decodeCtx) - logger.Debug("AuthMoreData decoded", zap.Any("parsed packet", parsedPacket)) - } - case payloadType == mysql.AuthSwitchRequest && len(payload) > 5: //conflicting with EOF packet, assuming that the payload is always greater than 5 bytes - logger.Debug("AuthSwitchRequest packet", zap.Any("Type", payloadType)) - pkt, err := connection.DecodeAuthSwitchRequest(ctx, payload) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode AuthSwitchRequest packet: %w", err) - } - setPacketInfo(ctx, parsedPacket, pkt, mysql.AuthStatusToString(mysql.AuthSwitchRequest), clientConn, mysql.AuthSwitchRequest, decodeCtx) - logger.Debug("AuthSwitchRequest decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == 0x02: - if len(payload) == 1 { - logger.Debug(("Request public key detected")) - setPacketInfo(ctx, parsedPacket, "request_public_key", mysql.CachingSha2PasswordToString(mysql.RequestPublicKey), clientConn, byte(mysql.RequestPublicKey), decodeCtx) - logger.Debug("Request public key decoded", zap.Any("parsed packet", parsedPacket)) - } else { - logger.Debug("AuthNextFactor packet", zap.Any("Type", payloadType)) - pkt, err := connection.DecodeAuthNextFactor(ctx, payload) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode AuthNextFactor packet: %w", err) - } - logger.Warn("AuthNextFactor packet not supported, further flow can be affected") - setPacketInfo(ctx, parsedPacket, pkt, mysql.AuthStatusToString(mysql.AuthNextFactor), clientConn, mysql.AuthNextFactor, decodeCtx) - logger.Debug("AuthNextFactor decoded", zap.Any("parsed packet", parsedPacket)) - } - case payloadType == mysql.HandshakeV10: - logger.Debug("HandshakeV10 packet", zap.Any("Type", payloadType)) - pkt, err := connection.DecodeHandshakeV10(ctx, logger, payload) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode HandshakeV10 packet: %w", err) - } - // Store the server greetings to use it later - decodeCtx.ServerGreetings.Store(clientConn, pkt) - setPacketInfo(ctx, parsedPacket, pkt, mysql.AuthStatusToString(mysql.HandshakeV10), clientConn, mysql.HandshakeV10, decodeCtx) - - logger.Debug("HandshakeV10 decoded", zap.Any("parsed packet", parsedPacket)) - - // utility packets - case payloadType == mysql.COM_QUIT: - logger.Debug("COM_QUIT packet", zap.Any("Type", payloadType)) - pkt := &mysql.QuitPacket{ - Command: payloadType, - } - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_QUIT), clientConn, mysql.COM_QUIT, decodeCtx) - logger.Debug("COM_QUIT decoded", zap.Any("parsed packet", parsedPacket)) - case payloadType == mysql.COM_INIT_DB: - logger.Debug("COM_INIT_DB packet", zap.Any("Type", payloadType)) - pkt, err := utility.DecodeInitDb(ctx, payload) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode COM_INIT_DB packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_INIT_DB), clientConn, mysql.COM_INIT_DB, decodeCtx) - logger.Debug("COM_INIT_DB decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == mysql.COM_STATISTICS: - logger.Debug("COM_STATISTICS packet", zap.Any("Type", payloadType)) - pkt := &mysql.StatisticsPacket{ - Command: payloadType, - } - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STATISTICS), clientConn, mysql.COM_STATISTICS, decodeCtx) - logger.Debug("COM_STATISTICS decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == mysql.COM_DEBUG: - logger.Debug("COM_DEBUG packet", zap.Any("Type", payloadType)) - pkt := &mysql.DebugPacket{ - Command: payloadType, - } - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_DEBUG), clientConn, mysql.COM_DEBUG, decodeCtx) - logger.Debug("COM_DEBUG decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == mysql.COM_PING: - logger.Debug("COM_PING packet", zap.Any("Type", payloadType)) - pkt := &mysql.PingPacket{ - Command: payloadType, - } - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_PING), clientConn, mysql.COM_PING, decodeCtx) - logger.Debug("COM_PING decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == mysql.COM_CHANGE_USER: - logger.Debug("COM_CHANGE_USER packet", zap.Any("Type", payloadType)) - pkt := &mysql.ChangeUserPacket{ - Command: payloadType, - } - logger.Warn("COM_CHANGE_USER packet not supported, further flow can be affected") - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_CHANGE_USER), clientConn, mysql.COM_CHANGE_USER, decodeCtx) - logger.Debug("COM_CHANGE_USER decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == mysql.COM_RESET_CONNECTION: - logger.Debug("COM_RESET_CONNECTION packet", zap.Any("Type", payloadType)) - pkt := &mysql.ResetConnectionPacket{ - Command: payloadType, - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_RESET_CONNECTION), clientConn, mysql.COM_RESET_CONNECTION, decodeCtx) - logger.Debug("COM_RESET_CONNECTION decoded", zap.Any("parsed packet", parsedPacket)) - - // case payloadType == mysql.COM_SET_OPTION: - // logger.Debug("COM_SET_OPTION packet", zap.Any("Type", payloadType)) - // pkt, err := utility.DecodeSetOption(ctx, payload) - // if err != nil { - // return parsedPacket, fmt.Errorf("failed to decode COM_SET_OPTION packet: %w", err) - // } - - // setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_SET_OPTION), mysql.COM_SET_OPTION, decodeCtx) - - // command packets - case payloadType == mysql.COM_QUERY: - logger.Debug("COM_QUERY packet", zap.Any("Type", payloadType)) - - pkt, err := query.DecodeQuery(ctx, logger, payload, decodeCtx.ClientCapabilities) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode COM_QUERY packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_QUERY), clientConn, mysql.COM_QUERY, decodeCtx) - - lstOp, _ := decodeCtx.LastOp.Load(clientConn) - logger.Debug("COM_QUERY decoded", zap.Any("parsed packet", parsedPacket), zap.Any("last operation", lstOp)) - - case payloadType == mysql.COM_STMT_PREPARE: - logger.Debug("COM_STMT_PREPARE packet", zap.Any("Type", payloadType)) - - pkt, err := preparedstmt.DecodeStmtPrepare(ctx, payload) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode COM_STMT_PREPARE packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STMT_PREPARE), clientConn, mysql.COM_STMT_PREPARE, decodeCtx) - logger.Debug("COM_STMT_PREPARE decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == mysql.COM_STMT_EXECUTE: - logger.Debug("COM_STMT_EXECUTE packet", zap.Any("Type", payloadType)) - pkt, err := preparedstmt.DecodeStmtExecute(ctx, logger, payload, decodeCtx.PreparedStatements, decodeCtx.ClientCapabilities) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode COM_STMT_EXECUTE packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STMT_EXECUTE), clientConn, mysql.COM_STMT_EXECUTE, decodeCtx) - logger.Debug("COM_STMT_EXECUTE decoded", zap.Any("parsed packet", parsedPacket)) - - // case payloadType == mysql.COM_STMT_FETCH: - case payloadType == mysql.COM_STMT_CLOSE: - logger.Debug("COM_STMT_CLOSE packet", zap.Any("Type", payloadType)) - pkt, err := preparedstmt.DecoderStmtClose(ctx, payload) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode COM_STMT_CLOSE packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STMT_CLOSE), clientConn, mysql.COM_STMT_CLOSE, decodeCtx) - logger.Debug("COM_STMT_CLOSE decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == mysql.COM_STMT_RESET: - logger.Debug("COM_STMT_RESET packet", zap.Any("Type", payloadType)) - pkt, err := preparedstmt.DecodeStmtReset(ctx, payload) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode COM_STMT_RESET packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STMT_RESET), clientConn, mysql.COM_STMT_RESET, decodeCtx) - - logger.Debug("COM_STMT_RESET decoded", zap.Any("parsed packet", parsedPacket)) - - case payloadType == mysql.COM_STMT_SEND_LONG_DATA: - logger.Debug("COM_STMT_SEND_LONG_DATA packet", zap.Any("Type", payloadType)) - pkt, err := preparedstmt.DecodeStmtSendLongData(ctx, payload) - if err != nil { - return parsedPacket, fmt.Errorf("failed to decode COM_STMT_SEND_LONG_DATA packet: %w", err) - } - - setPacketInfo(ctx, parsedPacket, pkt, mysql.CommandStatusToString(mysql.COM_STMT_SEND_LONG_DATA), clientConn, mysql.COM_STMT_SEND_LONG_DATA, decodeCtx) - logger.Debug("COM_STMT_SEND_LONG_DATA decoded", zap.Any("parsed packet", parsedPacket)) - default: - logger.Warn("Unknown packet type", zap.String("PacketType", fmt.Sprintf("%#x", payloadType)), zap.Any("payload", payload), zap.Any("last operation", lastOp)) - setPacketInfo(ctx, parsedPacket, itgUtils.EncodeBase64(payload), fmt.Sprintf("%#x", payloadType), clientConn, RESET, decodeCtx) - } - - return parsedPacket, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/encode.go b/keploy/pkg/core/proxy/integrations/mysql/wire/encode.go deleted file mode 100644 index 71d1f78..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/encode.go +++ /dev/null @@ -1,179 +0,0 @@ -//go:build linux - -package wire - -import ( - "context" - "encoding/binary" - "fmt" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/conn" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -func EncodeToBinary(ctx context.Context, logger *zap.Logger, packet *mysql.PacketBundle, clientConn net.Conn, decodeCtx *DecodeContext) ([]byte, error) { - - var data []byte - var err error - - //Get the server greeting from the decode context - serverGreeting, ok := decodeCtx.ServerGreetings.Load(clientConn) - if !ok { - return nil, fmt.Errorf("server greeting not found for connection %s", clientConn.RemoteAddr().String()) - } - - switch packet.Message.(type) { - // generic response packets - case *mysql.EOFPacket: - pkt, ok := packet.Message.(*mysql.EOFPacket) - if !ok { - return nil, fmt.Errorf("expected EOFPacket, got %T", packet.Message) - } - - data, err = phase.EncodeEOF(ctx, pkt, serverGreeting.CapabilityFlags) - if err != nil { - return nil, fmt.Errorf("error encoding EOF packet: %v", err) - } - - decodeCtx.LastOp.Store(clientConn, mysql.EOF) - - case *mysql.ERRPacket: - pkt, ok := packet.Message.(*mysql.ERRPacket) - if !ok { - return nil, fmt.Errorf("expected ERRPacket, got %T", packet.Message) - } - - data, err = phase.EncodeErr(ctx, pkt, serverGreeting.CapabilityFlags) - if err != nil { - return nil, fmt.Errorf("error encoding ERR packet: %v", err) - } - - decodeCtx.LastOp.Store(clientConn, mysql.ERR) - - case *mysql.OKPacket: - pkt, ok := packet.Message.(*mysql.OKPacket) - if !ok { - return nil, fmt.Errorf("expected OKPacket, got %T", packet.Message) - } - - data, err = phase.EncodeOk(ctx, pkt, serverGreeting.CapabilityFlags) - if err != nil { - return nil, fmt.Errorf("error encoding OK packet: %v", err) - } - - decodeCtx.LastOp.Store(clientConn, mysql.OK) - - // connection phase packets - case *mysql.AuthMoreDataPacket: - pkt, ok := packet.Message.(*mysql.AuthMoreDataPacket) - if !ok { - return nil, fmt.Errorf("expected AuthMoreDataPacket, got %T", packet.Message) - } - - data, err = conn.EncodeAuthMoreData(ctx, pkt) - if err != nil { - return nil, fmt.Errorf("error encoding AuthMoreData packet: %v", err) - } - - decodeCtx.LastOp.Store(clientConn, mysql.AuthMoreData) - - case *mysql.AuthSwitchRequestPacket: - pkt, ok := packet.Message.(*mysql.AuthSwitchRequestPacket) - if !ok { - return nil, fmt.Errorf("expected AuthSwitchRequestPacket, got %T", packet.Message) - } - - data, err = conn.EncodeAuthSwitchRequest(ctx, pkt) - if err != nil { - return nil, fmt.Errorf("error encoding AuthSwitchRequest packet: %v", err) - } - - decodeCtx.LastOp.Store(clientConn, mysql.AuthSwitchRequest) - - case *mysql.HandshakeV10Packet: - pkt, ok := packet.Message.(*mysql.HandshakeV10Packet) - if !ok { - return nil, fmt.Errorf("expected HandshakeV10Packet, got %T", packet.Message) - } - - data, err = conn.EncodeHandshakeV10(ctx, logger, pkt) - if err != nil { - return nil, fmt.Errorf("error encoding HandshakeV10 packet: %v", err) - } - - decodeCtx.LastOp.Store(clientConn, mysql.HandshakeV10) - - // command phase packets - case *mysql.StmtPrepareOkPacket: - pkt, ok := packet.Message.(*mysql.StmtPrepareOkPacket) - if !ok { - return nil, fmt.Errorf("expected StmtPrepareOkPacket, got %T", packet.Message) - } - - data, err = preparedstmt.EncodePrepareOk(ctx, logger, pkt, serverGreeting.CapabilityFlags) - if err != nil { - return nil, fmt.Errorf("error encoding StmtPrepareOkPacket: %v", err) - } - - decodeCtx.LastOp.Store(clientConn, mysql.COM_STMT_PREPARE) - - case *mysql.TextResultSet: - pkt, ok := packet.Message.(*mysql.TextResultSet) - if !ok { - return nil, fmt.Errorf("expected TextResultSet, got %T", packet.Message) - } - - data, err = query.EncodeTextResultSet(ctx, logger, pkt) - if err != nil { - return nil, fmt.Errorf("error encoding TextResultSet: %v", err) - } - - case *mysql.BinaryProtocolResultSet: - pkt, ok := packet.Message.(*mysql.BinaryProtocolResultSet) - if !ok { - return nil, fmt.Errorf("expected BinaryProtocolResultSet, got %T", packet.Message) - } - - data, err = query.EncodeBinaryResultSet(ctx, logger, pkt) - if err != nil { - return nil, fmt.Errorf("error encoding BinaryProtocolResultSet: %v", err) - } - } - - // Encode the header for the packet - header := make([]byte, 4) - // IMPORTANT: - // For composite (multi-packet) messages, `data` contains multiple packets where - // only the FIRST sub-packet lacks its 4-byte header. The recorded header's - // PayloadLength corresponds to that first payload only (e.g., column-count = 1). - // For single-packet messages, we can safely use len(data). - payloadLen := len(data) - if isCompositeMessage(packet.Message) && packet.Header != nil && packet.Header.Header != nil { - payloadLen = int(packet.Header.Header.PayloadLength) - } - binary.LittleEndian.PutUint32(header, uint32(payloadLen)) - header[3] = packet.Header.Header.SequenceID - data = append(header, data...) - - logger.Debug("Encoded Packet", zap.String("packet", packet.Header.Type), zap.ByteString("data", data)) - - return data, nil -} - -// isCompositeMessage tells whether the encoded `data` contains multiple packets, -// where only the first packet should use the header we write here. -func isCompositeMessage(msg interface{}) bool { - switch msg.(type) { - case *mysql.TextResultSet, - *mysql.BinaryProtocolResultSet, - *mysql.StmtPrepareOkPacket: - return true - default: - return false - } -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authMoreDataPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authMoreDataPacket.go deleted file mode 100644 index c19ee19..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authMoreDataPacket.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build linux - -package conn - -import ( - "bytes" - "context" - "fmt" - - "go.keploy.io/server/v2/pkg/models/mysql" -) - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_more_data.html - -func DecodeAuthMoreData(_ context.Context, data []byte) (*mysql.AuthMoreDataPacket, error) { - return &mysql.AuthMoreDataPacket{ - StatusTag: data[0], - Data: string(data[1:]), - }, nil -} - -func EncodeAuthMoreData(_ context.Context, packet *mysql.AuthMoreDataPacket) ([]byte, error) { - buf := new(bytes.Buffer) - - // Write StatusTag - if err := buf.WriteByte(packet.StatusTag); err != nil { - return nil, fmt.Errorf("failed to write StatusTag for AuthMoreData packet: %w", err) - } - - switch packet.Data { - case mysql.CachingSha2PasswordToString(mysql.PerformFullAuthentication): - packet.Data = string(mysql.PerformFullAuthentication) - case mysql.CachingSha2PasswordToString(mysql.FastAuthSuccess): - packet.Data = string(mysql.FastAuthSuccess) - } - - // Write Data - if _, err := buf.WriteString(packet.Data); err != nil { - return nil, fmt.Errorf("failed to write Data for authMoreData packet: %w", err) - } - - return buf.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authNextFactorPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authNextFactorPacket.go deleted file mode 100644 index ddeb065..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authNextFactorPacket.go +++ /dev/null @@ -1,56 +0,0 @@ -//go:build linux - -package conn - -import ( - "bytes" - "context" - "errors" - "fmt" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/models/mysql" -) - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_next_factor_request.html - -func DecodeAuthNextFactor(_ context.Context, data []byte) (*mysql.AuthNextFactorPacket, error) { - - packet := &mysql.AuthNextFactorPacket{ - PacketType: data[0], - } - - data, idx, err := utils.ReadNullTerminatedString(data[1:]) - if err != nil { - return nil, fmt.Errorf("malformed handshake response packet: missing null terminator for PluginName") - } - packet.PluginName = string(data) - packet.PluginData = string(data[idx:]) - - return packet, nil -} - -func EncodeAuthNextFactor(_ context.Context, packet *mysql.AuthNextFactorPacket) ([]byte, error) { - buf := new(bytes.Buffer) - - // Write PacketType - if err := buf.WriteByte(packet.PacketType); err != nil { - return nil, errors.New("failed to write PacketType") - } - - // Write PluginName followed by a null terminator - if _, err := buf.WriteString(packet.PluginName); err != nil { - return nil, errors.New("failed to write PluginName for AuthNextFactor packet") - } - - if err := buf.WriteByte(0x00); err != nil { - return nil, errors.New("failed to write null terminator for PluginName for AuthNextFactor packet") - } - - // Write PluginData - if _, err := buf.WriteString(packet.PluginData); err != nil { - return nil, errors.New("failed to write PluginData for AuthNextFactor packet") - } - - return buf.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchRequestPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchRequestPacket.go deleted file mode 100644 index e41f88c..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchRequestPacket.go +++ /dev/null @@ -1,59 +0,0 @@ -//go:build linux - -package conn - -import ( - "bytes" - "context" - "encoding/base64" - "errors" - - "go.keploy.io/server/v2/pkg/models/mysql" -) - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_request.html - -func DecodeAuthSwitchRequest(_ context.Context, data []byte) (*mysql.AuthSwitchRequestPacket, error) { - - packet := &mysql.AuthSwitchRequestPacket{ - StatusTag: data[0], - } - - // Splitting data by null byte to get plugin name and auth data - parts := bytes.SplitN(data[1:], []byte{0x00}, 2) - packet.PluginName = string(parts[0]) - if len(parts) > 1 { - packet.PluginData = base64.RawStdEncoding.EncodeToString(parts[1]) - } - - return packet, nil -} - -func EncodeAuthSwitchRequest(_ context.Context, packet *mysql.AuthSwitchRequestPacket) ([]byte, error) { - buf := new(bytes.Buffer) - - // Write StatusTag - if err := buf.WriteByte(packet.StatusTag); err != nil { - return nil, errors.New("failed to write StatusTag") - } - - // Write PluginName followed by a null terminator - if _, err := buf.WriteString(packet.PluginName); err != nil { - return nil, errors.New("failed to write PluginName for AuthSwitchRequest packet") - } - if err := buf.WriteByte(0x00); err != nil { - return nil, errors.New("failed to write null terminator for PluginName for AuthSwitchRequest packet") - } - - pluginData, err := base64.RawStdEncoding.DecodeString(packet.PluginData) - if err != nil { - return nil, errors.New("failed to decode PluginData for AuthSwitchRequest packet") - } - - // Write PluginData - if _, err := buf.WriteString(string(pluginData)); err != nil { - return nil, errors.New("failed to write PluginData for AuthSwitchRequest packet") - } - - return buf.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchResponsePacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchResponsePacket.go deleted file mode 100644 index 4f2307d..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/authSwitchResponsePacket.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build linux - -package conn - -import ( - "bytes" - "context" - "encoding/base64" - "errors" - - "go.keploy.io/server/v2/pkg/models/mysql" -) - -//ref:https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_response.html - -func DecodeAuthSwitchResponse(_ context.Context, data []byte) (*mysql.AuthSwitchResponsePacket, error) { - return &mysql.AuthSwitchResponsePacket{ - Data: base64.StdEncoding.EncodeToString(data), - }, nil -} - -func EncodeAuthSwitchResponse(_ context.Context, packet *mysql.AuthSwitchResponsePacket) ([]byte, error) { - buf := new(bytes.Buffer) - - // Write Data - if _, err := buf.WriteString(packet.Data); err != nil { - return nil, errors.New("failed to write Data for AuthSwitchResponse packet") - } - - return buf.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go deleted file mode 100644 index a2893d3..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go +++ /dev/null @@ -1,272 +0,0 @@ -//go:build linux - -package conn - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_response.html -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_ssl_request.html - -func DecodeHandshakeResponse(_ context.Context, logger *zap.Logger, data []byte) (interface{}, error) { - - if len(data) < 32 { - return nil, errors.New("handshake response packet too short") - } - - origData := data - - packet := &mysql.HandshakeResponse41Packet{} - - packet.CapabilityFlags = binary.LittleEndian.Uint32(data[:4]) - data = data[4:] - - if packet.CapabilityFlags&mysql.CLIENT_PROTOCOL_41 == 0 { - return nil, errors.New("CLIENT_PROTOCOL_41 compatible client is required") - } - - packet.MaxPacketSize = binary.LittleEndian.Uint32(data[:4]) - data = data[4:] - - packet.CharacterSet = data[0] - data = data[1:] - - copy(packet.Filler[:], data[:23]) - data = data[23:] - - // Check if it is a SSL Request Packet - if len(origData) == (4 + 4 + 1 + 23) { - if packet.CapabilityFlags&mysql.CLIENT_SSL != 0 { - logger.Debug("Client requested SSL connection") - return &mysql.SSLRequestPacket{ - CapabilityFlags: packet.CapabilityFlags, - MaxPacketSize: packet.MaxPacketSize, - CharacterSet: packet.CharacterSet, - Filler: packet.Filler, - }, nil - } - } - - idx := bytes.IndexByte(data, 0x00) - if idx == -1 { - return nil, errors.New("malformed handshake response packet: missing null terminator for Username") - } - - packet.Username = string(data[:idx]) - data = data[idx+1:] - - if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA != 0 { - if len(data) < 1 { - return nil, errors.New("handshake response packet too short for auth data length") - } - length := int(data[0]) - data = data[1:] - - if length > 0 { - if len(data) < length { - return nil, errors.New("handshake response packet too short for auth data") - } - packet.AuthResponse = data[:length] - data = data[length:] - } - } else { - if len(data) < 2 { - return nil, errors.New("handshake response packet too short for auth data length") - } - authLen := int(data[0]) - data = data[2:] - - if len(data) < authLen { - return nil, errors.New("handshake response packet too short for auth data") - } - packet.AuthResponse = data[:authLen] - data = data[authLen:] - } - - if packet.CapabilityFlags&mysql.CLIENT_CONNECT_WITH_DB != 0 { - idx = bytes.IndexByte(data, 0x00) - if idx != -1 { - packet.Database = string(data[:idx]) - data = data[idx+1:] - } - } - - if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 { - idx = bytes.IndexByte(data, 0x00) - if idx == -1 { - return nil, errors.New("malformed handshake response packet: missing null terminator for AuthPluginName") - } - packet.AuthPluginName = string(data[:idx]) - data = data[idx+1:] - } - - if packet.CapabilityFlags&mysql.CLIENT_CONNECT_ATTRS != 0 { - if len(data) < 4 { - return nil, errors.New("handshake response packet too short for connection attributes") - } - - totalLength, isNull, n := utils.ReadLengthEncodedInteger(data) - if isNull || n == 0 { - return nil, errors.New("error decoding total length of connection attributes") - } - data = data[n:] - - // Check if we have enough data for the total attributes length - if len(data) < int(totalLength) { - return nil, errors.New("handshake response packet too short for connection attributes data") - } - - attributesData := data[:totalLength] - data = data[totalLength:] - - packet.ConnectionAttributes = make(map[string]string) - for len(attributesData) > 0 { - keyLength, isNull, n := utils.ReadLengthEncodedInteger(attributesData) - if isNull || n == 0 { - return nil, errors.New("malformed handshake response packet: null length encoded integer for connection attribute key") - } - attributesData = attributesData[n:] - - // Check if we have enough data for the key - if len(attributesData) < int(keyLength) { - return nil, errors.New("handshake response packet too short for connection attribute key") - } - - key := string(attributesData[:keyLength]) - attributesData = attributesData[keyLength:] - - valueLength, isNull, n := utils.ReadLengthEncodedInteger(attributesData) - if isNull || n == 0 { - return nil, errors.New("malformed handshake response packet: null length encoded integer for connection attribute value") - } - attributesData = attributesData[n:] - - // Check if we have enough data for the value - if len(attributesData) < int(valueLength) { - return nil, errors.New("handshake response packet too short for connection attribute value") - } - - value := string(attributesData[:valueLength]) - attributesData = attributesData[valueLength:] - - packet.ConnectionAttributes[key] = value - } - } - if len(data) > 0 { - if packet.CapabilityFlags&mysql.CLIENT_ZSTD_COMPRESSION_ALGORITHM != 0 { - if len(data) < 1 { - return nil, errors.New("handshake response packet too short for ZSTD compression level") - } - packet.ZstdCompressionLevel = data[0] - } - } - - return packet, nil -} - -func EncodeHandshakeResponse41(_ context.Context, _ *zap.Logger, packet *mysql.HandshakeResponse41Packet) ([]byte, error) { - buf := new(bytes.Buffer) - - // Write Capability Flags - if err := binary.Write(buf, binary.LittleEndian, packet.CapabilityFlags); err != nil { - return nil, fmt.Errorf("failed to write CapabilityFlags for HandshakeResponse41Packet: %w", err) - } - - // Write Max Packet Size - if err := binary.Write(buf, binary.LittleEndian, packet.MaxPacketSize); err != nil { - return nil, fmt.Errorf("failed to write MaxPacketSize for HandshakeResponse41Packet: %w", err) - } - - // Write Character Set - if err := buf.WriteByte(packet.CharacterSet); err != nil { - return nil, fmt.Errorf("failed to write CharacterSet for HandshakeResponse41Packet: %w", err) - } - - // Write Filler - if _, err := buf.Write(packet.Filler[:]); err != nil { - return nil, fmt.Errorf("failed to write Filler for HandshakeResponse41Packet: %w", err) - } - - // Write Username - if _, err := buf.WriteString(packet.Username); err != nil { - return nil, fmt.Errorf("failed to write Username for HandshakeResponse41Packet: %w", err) - } - if err := buf.WriteByte(0x00); err != nil { - return nil, fmt.Errorf("failed to write null terminator for Username for HandshakeResponse41Packet: %w", err) - } - - // Write Auth Response - if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA != 0 { - if err := buf.WriteByte(byte(len(packet.AuthResponse))); err != nil { - return nil, fmt.Errorf("failed to write length of AuthResponse for HandshakeResponse41Packet: %w", err) - } - if _, err := buf.Write(packet.AuthResponse); err != nil { - return nil, fmt.Errorf("failed to write AuthResponse for HandshakeResponse41Packet: %w", err) - } - } else { - if err := buf.WriteByte(byte(len(packet.AuthResponse))); err != nil { - return nil, fmt.Errorf("failed to write length of AuthResponse for HandshakeResponse41Packet: %w", err) - } - if _, err := buf.Write(packet.AuthResponse); err != nil { - return nil, fmt.Errorf("failed to write AuthResponse for HandshakeResponse41Packet: %w", err) - } - } - - // Write Database - if packet.CapabilityFlags&mysql.CLIENT_CONNECT_WITH_DB != 0 { - if _, err := buf.WriteString(packet.Database); err != nil { - return nil, fmt.Errorf("failed to write Database for HandshakeResponse41Packet: %w", err) - } - if err := buf.WriteByte(0x00); err != nil { - return nil, fmt.Errorf("failed to write null terminator for Database for HandshakeResponse41Packet: %w", err) - } - } - - // Write Auth Plugin Name - if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 { - if _, err := buf.WriteString(packet.AuthPluginName); err != nil { - return nil, fmt.Errorf("failed to write AuthPluginName for HandshakeResponse41Packet: %w", err) - } - if err := buf.WriteByte(0x00); err != nil { - return nil, fmt.Errorf("failed to write null terminator for AuthPluginName for HandshakeResponse41Packet: %w", err) - } - } - - // Write Connection Attributes - if packet.CapabilityFlags&mysql.CLIENT_CONNECT_ATTRS != 0 { - totalLength := 0 - for key, value := range packet.ConnectionAttributes { - totalLength += len(key) + len(value) + 2 // 2 bytes for length-encoded integer prefixes - } - - if err := utils.WriteLengthEncodedInteger(buf, uint64(totalLength)); err != nil { - return nil, fmt.Errorf("failed to write total length of ConnectionAttributes for HandshakeResponse41Packet: %w", err) - } - - for key, value := range packet.ConnectionAttributes { - if err := utils.WriteLengthEncodedString(buf, key); err != nil { - return nil, fmt.Errorf("failed to write ConnectionAttribute key for HandshakeResponse41Packet: %w", err) - } - if err := utils.WriteLengthEncodedString(buf, value); err != nil { - return nil, fmt.Errorf("failed to write ConnectionAttribute value for HandshakeResponse41Packet: %w", err) - } - } - } - // Write Zstd Compression Level - if packet.CapabilityFlags&mysql.CLIENT_ZSTD_COMPRESSION_ALGORITHM != 0 { - if err := buf.WriteByte(packet.ZstdCompressionLevel); err != nil { - return nil, fmt.Errorf("failed to write ZstdCompressionLevel for HandshakeResponse41Packet: %w", err) - } - } - - return buf.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet_test.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet_test.go deleted file mode 100644 index b6710c6..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet_test.go +++ /dev/null @@ -1,234 +0,0 @@ -//go:build linux - -package conn - -import ( - "context" - "testing" - - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -func TestDecodeHandshakeResponse_BoundsChecking(t *testing.T) { - logger := zap.NewNop() - ctx := context.Background() - - tests := []struct { - name string - data []byte - expectError bool - errorContains string - }{ - { - name: "too short packet", - data: make([]byte, 10), // Less than minimum 32 bytes - expectError: true, - errorContains: "handshake response packet too short", - }, - { - name: "minimum valid packet", - data: func() []byte { - // Create minimal valid handshake response - data := make([]byte, 32) - // Set CLIENT_PROTOCOL_41 capability (0x200) - data[0] = 0x00 - data[1] = 0x02 // 0x0200 = 512 = CLIENT_PROTOCOL_41 - data[2] = 0x00 - data[3] = 0x00 - // Add null terminator for username at position 32 - data = append(data, 0x00) - // Add auth response length = 0 for non-plugin auth - data = append(data, 0x00, 0x00) // length + filler - return data - }(), - expectError: false, - }, - { - name: "missing username null terminator", - data: func() []byte { - data := make([]byte, 32) - data[0] = 0x00 - data[1] = 0x02 // CLIENT_PROTOCOL_41 - data[2] = 0x00 - data[3] = 0x00 - // No null terminator for username - return append(data, []byte("username")...) - }(), - expectError: true, - errorContains: "missing null terminator for Username", - }, - { - name: "auth response - insufficient data for length (plugin auth)", - data: func() []byte { - data := make([]byte, 32) - // CLIENT_PROTOCOL_41 (0x200) + CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA (0x100000) - data[0] = 0x00 - data[1] = 0x02 // CLIENT_PROTOCOL_41 - data[2] = 0x10 // CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA - data[3] = 0x00 - // Add null terminator for username - data = append(data, 0x00) - // Don't add auth length byte - return data - }(), - expectError: true, - errorContains: "handshake response packet too short for auth data length", - }, - { - name: "auth response - insufficient data for auth data (plugin auth)", - data: func() []byte { - data := make([]byte, 32) - // CLIENT_PROTOCOL_41 + CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA - data[0] = 0x00 - data[1] = 0x02 // CLIENT_PROTOCOL_41 - data[2] = 0x10 // CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA - data[3] = 0x00 - // Add null terminator for username - data = append(data, 0x00) - // Add auth length = 5 but only provide 3 bytes - data = append(data, 0x05) - data = append(data, []byte{0x01, 0x02, 0x03}...) - return data - }(), - expectError: true, - errorContains: "handshake response packet too short for auth data", - }, - { - name: "auth response - insufficient data for length (non-plugin auth)", - data: func() []byte { - data := make([]byte, 32) - // CLIENT_PROTOCOL_41 only (no CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) - data[0] = 0x00 - data[1] = 0x02 - data[2] = 0x00 - data[3] = 0x00 - // Add null terminator for username - data = append(data, 0x00) - // Add only 1 byte (need 2 for non-plugin auth) - data = append(data, 0x05) - return data - }(), - expectError: true, - errorContains: "handshake response packet too short for auth data length", - }, - { - name: "auth response - insufficient data for auth data (non-plugin auth)", - data: func() []byte { - data := make([]byte, 32) - // CLIENT_PROTOCOL_41 only - data[0] = 0x00 - data[1] = 0x02 - data[2] = 0x00 - data[3] = 0x00 - // Add null terminator for username - data = append(data, 0x00) - // Add auth length = 5 but only provide 3 bytes (after skipping 2 bytes) - data = append(data, 0x05, 0x00) // length + filler - data = append(data, []byte{0x01, 0x02, 0x03}...) // Only 3 bytes (need 5) - return data - }(), - expectError: true, - errorContains: "handshake response packet too short for auth data", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("DecodeHandshakeResponse panicked: %v", r) - } - }() - - result, err := DecodeHandshakeResponse(ctx, logger, tt.data) - - if tt.expectError { - if err == nil { - t.Errorf("Expected error but got none") - } else if tt.errorContains != "" && !contains(err.Error(), tt.errorContains) { - t.Errorf("Expected error to contain '%s', but got: %s", tt.errorContains, err.Error()) - } - if result != nil { - t.Errorf("Expected nil result on error, but got: %v", result) - } - } else { - if err != nil { - t.Errorf("Expected no error but got: %s", err.Error()) - } - if result == nil { - t.Errorf("Expected non-nil result but got nil") - } - } - }) - } -} - -func TestDecodeHandshakeResponse_ValidCases(t *testing.T) { - logger := zap.NewNop() - ctx := context.Background() - - tests := []struct { - name string - data []byte - validate func(t *testing.T, result interface{}) - }{ - { - name: "valid basic handshake", - data: func() []byte { - data := make([]byte, 32) - // CLIENT_PROTOCOL_41 (0x200) - data[0] = 0x00 - data[1] = 0x02 - data[2] = 0x00 - data[3] = 0x00 - // Add username - data = append(data, []byte("testuser")...) - data = append(data, 0x00) // null terminator - // Add auth response (non-plugin style) - data = append(data, 0x00, 0x00) // auth length = 0 + filler - return data - }(), - validate: func(t *testing.T, result interface{}) { - packet, ok := result.(*mysql.HandshakeResponse41Packet) - if !ok { - t.Errorf("Expected HandshakeResponse41Packet, got %T", result) - return - } - if packet.Username != "testuser" { - t.Errorf("Expected username 'testuser', got '%s'", packet.Username) - } - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("DecodeHandshakeResponse panicked: %v", r) - } - }() - - result, err := DecodeHandshakeResponse(ctx, logger, tt.data) - if err != nil { - t.Errorf("Unexpected error: %s", err.Error()) - return - } - - if tt.validate != nil { - tt.validate(t, result) - } - }) - } -} - -// Helper function to check if string contains substring -func contains(s, substr string) bool { - for i := 0; i <= len(s)-len(substr); i++ { - if s[i:i+len(substr)] == substr { - return true - } - } - return false -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeV10Packet.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeV10Packet.go deleted file mode 100644 index 40de4cf..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeV10Packet.go +++ /dev/null @@ -1,167 +0,0 @@ -//go:build linux - -// Package conn provides decoding and encoding of connection phase mysql packets -package conn - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html - -func DecodeHandshakeV10(_ context.Context, _ *zap.Logger, data []byte) (*mysql.HandshakeV10Packet, error) { - - if len(data) < 4 { - return nil, fmt.Errorf("handshake packet too short") - } - - packet := &mysql.HandshakeV10Packet{} - packet.ProtocolVersion = data[0] - - idx := bytes.IndexByte(data[1:], 0x00) - if idx == -1 { - return nil, fmt.Errorf("malformed handshake packet: missing null terminator for ServerVersion") - } - - packet.ServerVersion = string(data[1 : 1+idx]) - data = data[1+idx+1:] - - if len(data) < 4 { - return nil, fmt.Errorf("handshake packet too short for ConnectionID") - } - - packet.ConnectionID = binary.LittleEndian.Uint32(data[:4]) - data = data[4:] - - if len(data) < 9 { // 8 bytes of AuthPluginData + 1 byte filler - return nil, fmt.Errorf("handshake packet too short for AuthPluginData") - } - packet.AuthPluginData = append([]byte{}, data[:8]...) - - packet.Filler = data[8] - - data = data[9:] // Skip 8 bytes of AuthPluginData and 1 byte filler - - if len(data) < 5 { // Capability flags (2 bytes), character set (1 byte), status flags (2 bytes) - return nil, fmt.Errorf("handshake packet too short for flags") - } - - capabilityFlagsLower := binary.LittleEndian.Uint16(data[:2]) - data = data[2:] - - packet.CharacterSet = data[0] - data = data[1:] - - packet.StatusFlags = binary.LittleEndian.Uint16(data[:2]) - data = data[2:] - - capabilityFlagsUpper := binary.LittleEndian.Uint16(data[:2]) - data = data[2:] - - packet.CapabilityFlags = uint32(capabilityFlagsLower) | uint32(capabilityFlagsUpper)<<16 - var authPluginDataLen int - - packet.CapabilityFlags = uint32(capabilityFlagsLower) | uint32(capabilityFlagsUpper)<<16 - - if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 { - authPluginDataLen = int(data[0]) - data = data[1:] // Skip 1 byte AuthPluginDataLen - } else { - data = data[1:] // constant 0x00 - } - - data = data[10:] // Skip 10 bytes reserved (all 0s) - - if authPluginDataLen > 8 { - lenToRead := min(authPluginDataLen-8, len(data)) - packet.AuthPluginData = append(packet.AuthPluginData, data[:lenToRead]...) - data = data[lenToRead:] - } - - if len(data) == 0 { - return nil, fmt.Errorf("handshake packet too short for AuthPluginName") - } - - if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 { - idx = bytes.IndexByte(data, 0x00) - if idx == -1 { - return nil, fmt.Errorf("malformed handshake packet: missing null terminator for AuthPluginName") - } - packet.AuthPluginName = string(data[:idx]) - } - - return packet, nil -} - -func EncodeHandshakeV10(_ context.Context, _ *zap.Logger, packet *mysql.HandshakeV10Packet) ([]byte, error) { - buf := new(bytes.Buffer) - - // Protocol version - buf.WriteByte(packet.ProtocolVersion) - - // Server version - buf.WriteString(packet.ServerVersion) - buf.WriteByte(0x00) // Null terminator - - // Connection ID - if err := binary.Write(buf, binary.LittleEndian, packet.ConnectionID); err != nil { - return nil, err - } - - // Auth-plugin-data-part-1 (first 8 bytes) - if len(packet.AuthPluginData) < 8 { - return nil, errors.New("auth plugin data too short") - } - buf.Write(packet.AuthPluginData[:8]) - - // Filler - buf.WriteByte(packet.Filler) - - // Capability flags (lower 2 bytes) - if err := binary.Write(buf, binary.LittleEndian, uint16(packet.CapabilityFlags&0xFFFF)); err != nil { - return nil, err - } - - // Character set - buf.WriteByte(packet.CharacterSet) - - // Status flags - if err := binary.Write(buf, binary.LittleEndian, packet.StatusFlags); err != nil { - return nil, err - } - - // Capability flags (upper 2 bytes) - if err := binary.Write(buf, binary.LittleEndian, uint16((packet.CapabilityFlags>>16)&0xFFFF)); err != nil { - return nil, err - } - - // Length of auth-plugin-data - if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 && len(packet.AuthPluginData) >= 21 { - buf.WriteByte(byte(len(packet.AuthPluginData))) // Length of entire auth plugin data - } else { - buf.WriteByte(0x00) - } - - // Reserved (10 zero bytes) - buf.Write(make([]byte, 10)) - - // Auth-plugin-data-part-2 (remaining auth data) - if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 && len(packet.AuthPluginData) > 8 { - buf.Write(packet.AuthPluginData[8:]) // Write all remaining bytes of auth plugin data - } - - // Auth-plugin name - if packet.CapabilityFlags&mysql.CLIENT_PLUGIN_AUTH != 0 { - buf.WriteString(packet.AuthPluginName) - buf.WriteByte(0x00) // Null terminator - } - - return buf.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/generic.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/generic.go deleted file mode 100644 index d0663b7..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/generic.go +++ /dev/null @@ -1,205 +0,0 @@ -//go:build linux - -// Package phase contains the encoding and decoding functions for the different phases of the MySQL protocol. And also contains the same for EOF, ERR, and OK packets. -package phase - -import ( - "bytes" - "context" - "encoding/binary" - "fmt" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/models/mysql" -) - -// EOF packets (encoding & decoding) - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_eof_packet.html - -func DecodeEOF(_ context.Context, data []byte, capabilities uint32) (*mysql.EOFPacket, error) { - if len(data) > 5 { - return nil, fmt.Errorf("EOF packet too long for EOF") - } - - packet := &mysql.EOFPacket{ - Header: data[0], - } - - if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { - packet.Warnings = binary.LittleEndian.Uint16(data[1:3]) - packet.StatusFlags = binary.LittleEndian.Uint16(data[3:5]) - } - - return packet, nil -} - -func EncodeEOF(_ context.Context, packet *mysql.EOFPacket, capabilities uint32) ([]byte, error) { - buf := new(bytes.Buffer) - - // Write the header - if err := buf.WriteByte(packet.Header); err != nil { - return nil, fmt.Errorf("failed to write header: %w", err) - } - - // Write the warnings and status flags if CLIENT_PROTOCOL_41 is set - if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { - if err := binary.Write(buf, binary.LittleEndian, packet.Warnings); err != nil { - return nil, fmt.Errorf("failed to write warnings for eof packet: %w", err) - } - - if err := binary.Write(buf, binary.LittleEndian, packet.StatusFlags); err != nil { - return nil, fmt.Errorf("failed to write status flags for eof packet: %w", err) - } - } - - return buf.Bytes(), nil -} - -// ERR packets (encoding & decoding) - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_err_packet.html - -func DecodeERR(_ context.Context, data []byte, capabilities uint32) (*mysql.ERRPacket, error) { - if len(data) < 9 { - return nil, fmt.Errorf("ERR packet too short") - } - - packet := &mysql.ERRPacket{ - Header: data[0], - } - - pos := 1 - packet.ErrorCode = binary.LittleEndian.Uint16(data[pos : pos+2]) - pos += 2 - - if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { - if data[pos] != '#' { - return nil, fmt.Errorf("invalid SQL state marker: %c", data[pos]) - } - packet.SQLStateMarker = string(data[pos]) - - pos++ - packet.SQLState = string(data[pos : pos+5]) - pos += 5 - } - - packet.ErrorMessage = string(data[pos:]) - return packet, nil -} - -func EncodeErr(_ context.Context, packet *mysql.ERRPacket, capabilities uint32) ([]byte, error) { - buf := new(bytes.Buffer) - - // Write the header - if err := buf.WriteByte(packet.Header); err != nil { - return nil, fmt.Errorf("failed to write header: %w", err) - } - - // Write the error code - if err := binary.Write(buf, binary.LittleEndian, packet.ErrorCode); err != nil { - return nil, fmt.Errorf("failed to write error code: %w", err) - } - - // Write the SQL state marker and SQL state if CLIENT_PROTOCOL_41 is set - if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { - if len(packet.SQLStateMarker) != 1 || len(packet.SQLState) != 5 { - return nil, fmt.Errorf("invalid SQL state marker or SQL state length") - } - - if err := buf.WriteByte(packet.SQLStateMarker[0]); err != nil { - return nil, fmt.Errorf("failed to write SQL state marker: %w", err) - } - - if _, err := buf.WriteString(packet.SQLState); err != nil { - return nil, fmt.Errorf("failed to write SQL state: %w", err) - } - } - - // Write the error message - if _, err := buf.WriteString(packet.ErrorMessage); err != nil { - return nil, fmt.Errorf("failed to write error message: %w", err) - } - - return buf.Bytes(), nil -} - -// ref:https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html - -func DecodeOk(_ context.Context, data []byte, capabilities uint32) (*mysql.OKPacket, error) { - if len(data) < 7 { - return nil, fmt.Errorf("OK packet too short") - } - - packet := &mysql.OKPacket{ - Header: data[0], - } - - var n int - var pos = 1 - - packet.AffectedRows, _, n = utils.ReadLengthEncodedInteger(data[pos:]) - pos += n - packet.LastInsertID, _, n = utils.ReadLengthEncodedInteger(data[pos:]) - pos += n - - if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { - packet.StatusFlags = binary.LittleEndian.Uint16(data[pos:]) - pos += 2 - packet.Warnings = binary.LittleEndian.Uint16(data[pos:]) - pos += 2 - } else if capabilities&uint32(mysql.CLIENT_TRANSACTIONS) > 0 { - packet.StatusFlags = binary.LittleEndian.Uint16(data[pos:]) - pos += 2 - } - - // new ok package will check CLIENT_SESSION_TRACK too, but it is not supported in this version - - if pos < len(data) { - packet.Info = string(data[pos:]) - } - - return packet, nil -} - -func EncodeOk(_ context.Context, packet *mysql.OKPacket, capabilities uint32) ([]byte, error) { - buf := new(bytes.Buffer) - - // Write Header - if err := buf.WriteByte(packet.Header); err != nil { - return nil, fmt.Errorf("failed to write Header: %w", err) - } - - // Write Affected Rows - if err := utils.WriteLengthEncodedInteger(buf, packet.AffectedRows); err != nil { - return nil, fmt.Errorf("failed to write AffectedRows for OK packet: %w", err) - } - - // Write Last Insert ID - if err := utils.WriteLengthEncodedInteger(buf, packet.LastInsertID); err != nil { - return nil, fmt.Errorf("failed to write LastInsertID for OK packet: %w", err) - } - - // Write Status Flags and Warnings - if capabilities&uint32(mysql.CLIENT_PROTOCOL_41) > 0 { - if err := binary.Write(buf, binary.LittleEndian, packet.StatusFlags); err != nil { - return nil, fmt.Errorf("failed to write StatusFlags for OK packet: %w", err) - } - if err := binary.Write(buf, binary.LittleEndian, packet.Warnings); err != nil { - return nil, fmt.Errorf("failed to write Warnings for OK packet: %w", err) - } - } else if capabilities&uint32(mysql.CLIENT_TRANSACTIONS) > 0 { - if err := binary.Write(buf, binary.LittleEndian, packet.StatusFlags); err != nil { - return nil, fmt.Errorf("failed to write StatusFlags for OK packet: %w", err) - } - } - - // Write Info - if packet.Info != "" { - if _, err := buf.WriteString(packet.Info); err != nil { - return nil, fmt.Errorf("failed to write Info for OK packet: %w", err) - } - } - - return buf.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/ResultSetPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/ResultSetPacket.go deleted file mode 100644 index 02d4993..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/ResultSetPacket.go +++ /dev/null @@ -1,142 +0,0 @@ -//go:build linux - -package query - -import ( - "bytes" - "context" - "fmt" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -type RowType int - -// Constants for RowType -const ( - Binary RowType = iota - Text -) - -func DecodeResultSetMetadata(ctx context.Context, logger *zap.Logger, data []byte, rowType RowType) (interface{}, error) { - - // Decode the column count (No need to get the header as well as it is already decoded by the caller function) - colCount, err := rowscols.DecodeColumnCount(ctx, logger, data) - if err != nil { - return nil, fmt.Errorf("failed to decode column count packet: %w", err) - } - - switch rowType { - case Binary: - return &mysql.BinaryProtocolResultSet{ - ColumnCount: colCount, - }, nil - case Text: - return &mysql.TextResultSet{ - ColumnCount: colCount, - }, nil - } - return nil, nil -} - -func EncodeTextResultSet(ctx context.Context, logger *zap.Logger, resultSet *mysql.TextResultSet) ([]byte, error) { - buf := new(bytes.Buffer) - - // Encode the column count - if err := utils.WriteLengthEncodedInteger(buf, resultSet.ColumnCount); err != nil { - return nil, fmt.Errorf("failed to write column count for text resultset: %w", err) - } - - // Encode the column definition packets - for _, column := range resultSet.Columns { - columnBytes, err := rowscols.EncodeColumn(ctx, logger, column) - if err != nil { - return nil, fmt.Errorf("failed to encode column for text resultset: %w", err) - } - if _, err := buf.Write(columnBytes); err != nil { - return nil, fmt.Errorf("failed to write column for text resultset: %w", err) - } - } - - // Write the EOF packet after columns if present - if len(resultSet.EOFAfterColumns) != 0 { - if _, err := buf.Write(resultSet.EOFAfterColumns); err != nil { - return nil, fmt.Errorf("failed to write EOF packet after columns for text resultset: %w", err) - } - } - - // Encode each row data packet - for _, row := range resultSet.Rows { - rowBytes, err := rowscols.EncodeTextRow(ctx, logger, row, resultSet.Columns) - if err != nil { - return nil, fmt.Errorf("failed to encode row for text resultset: %w", err) - } - if _, err := buf.Write(rowBytes); err != nil { - return nil, fmt.Errorf("failed to write row for text resultset: %w", err) - } - } - // Write the final EOF packet if present - if resultSet.FinalResponse != nil && utils.IsEOFPacket(resultSet.FinalResponse.Data) { - if _, err := buf.Write(resultSet.FinalResponse.Data); err != nil { - return nil, fmt.Errorf("failed to write final EOF packet: %w", err) - } - } - - return buf.Bytes(), nil -} - -func EncodeBinaryResultSet(ctx context.Context, logger *zap.Logger, resultSet *mysql.BinaryProtocolResultSet) ([]byte, error) { - buf := new(bytes.Buffer) - - // Encode the column count - if err := utils.WriteLengthEncodedInteger(buf, resultSet.ColumnCount); err != nil { - return nil, fmt.Errorf("failed to write column count: %w", err) - } - - // Encode the column definition packets - for _, column := range resultSet.Columns { - columnBytes, err := rowscols.EncodeColumn(ctx, logger, column) - if err != nil { - return nil, fmt.Errorf("failed to encode column: %w", err) - } - if _, err := buf.Write(columnBytes); err != nil { - return nil, fmt.Errorf("failed to write column: %w", err) - } - } - - // Write the EOF packet after columns - if _, err := buf.Write(resultSet.EOFAfterColumns); err != nil { - return nil, fmt.Errorf("failed to write EOF packet after columns: %w", err) - } - - // Encode each row data packet - for _, row := range resultSet.Rows { - rowBytes, err := rowscols.EncodeBinaryRow(ctx, logger, row, resultSet.Columns) - if err != nil { - return nil, fmt.Errorf("failed to encode row: %w", err) - } - if _, err := buf.Write(rowBytes); err != nil { - return nil, fmt.Errorf("failed to write row: %w", err) - } - } - - // Write the final EOF packet if present - if resultSet.FinalResponse != nil && utils.IsEOFPacket(resultSet.FinalResponse.Data) { - logger.Debug("Writing final EOF packet for BinaryProtocolResultSet", - zap.Int("final_response_length", len(resultSet.FinalResponse.Data)), - zap.String("final_response_hex", fmt.Sprintf("%x", resultSet.FinalResponse.Data))) - if _, err := buf.Write(resultSet.FinalResponse.Data); err != nil { - return nil, fmt.Errorf("failed to write final EOF packet: %w", err) - } - logger.Debug("Successfully wrote final EOF packet for BinaryProtocolResultSet") - } else { - logger.Debug("No final EOF packet to write for BinaryProtocolResultSet", - zap.Bool("has_final_response", resultSet.FinalResponse != nil), - zap.Bool("is_eof_packet", resultSet.FinalResponse != nil && utils.IsEOFPacket(resultSet.FinalResponse.Data))) - } - - return buf.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/StmtPrepareOkPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/StmtPrepareOkPacket.go deleted file mode 100644 index ce51969..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/StmtPrepareOkPacket.go +++ /dev/null @@ -1,153 +0,0 @@ -//go:build linux - -package preparedstmt - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -// COM_STMT_PREPARE_OK: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_prepare.html#sect_protocol_com_stmt_prepare_response_ok - -func DecodePrepareOk(_ context.Context, _ *zap.Logger, data []byte, capabilities uint32) (*mysql.StmtPrepareOkPacket, error) { - if len(data) < 10 { - return nil, errors.New("data length is not enough for COM_STMT_PREPARE_OK") - } - - offset := 0 - - response := &mysql.StmtPrepareOkPacket{} - - response.Status = data[offset] - offset++ - - response.StatementID = binary.LittleEndian.Uint32(data[offset : offset+4]) - offset += 4 - - response.NumColumns = binary.LittleEndian.Uint16(data[offset : offset+2]) - offset += 2 - - response.NumParams = binary.LittleEndian.Uint16(data[offset : offset+2]) - offset += 2 - - //data[10] is reserved byte ([00] filler) - response.Filler = data[offset] - offset++ - - if len(data) >= 12 { - response.WarningCount = binary.LittleEndian.Uint16(data[offset : offset+2]) - response.WarningAvailable = true - offset += 2 - - if capabilities&uint32(mysql.CLIENT_OPTIONAL_RESULTSET_METADATA) > 0 && len(data) > offset { - response.MetaFollows = data[offset] - response.MetaFollowsAvailable = true - // offset++ - } - } - - return response, nil -} - -func EncodePrepareOk(ctx context.Context, logger *zap.Logger, packet *mysql.StmtPrepareOkPacket, capabilities uint32) ([]byte, error) { - buf := new(bytes.Buffer) - - // Encode the Prepare OK packet - prepareOkBytes, err := EncodePreparedStmtResponse(ctx, logger, packet, capabilities) - if err != nil { - return nil, fmt.Errorf("failed to encode StmtPrepareOkPacket: %w", err) - } - if _, err := buf.Write(prepareOkBytes); err != nil { - return nil, fmt.Errorf("failed to write StmtPrepareOkPacket: %w", err) - } - - // Encode parameter definitions if present - for _, paramDef := range packet.ParamDefs { - paramBytes, err := rowscols.EncodeColumn(ctx, logger, paramDef) - if err != nil { - return nil, fmt.Errorf("failed to encode parameter definition: %w", err) - } - if _, err := buf.Write(paramBytes); err != nil { - return nil, fmt.Errorf("failed to write parameter definition: %w", err) - } - } - - // Write EOF packet after parameter definitions - if packet.NumParams > 0 { - if _, err := buf.Write(packet.EOFAfterParamDefs); err != nil { - return nil, fmt.Errorf("failed to write EOF packet after parameter definitions: %w", err) - } - } - - // Encode column definitions if present - for _, columnDef := range packet.ColumnDefs { - columnBytes, err := rowscols.EncodeColumn(ctx, logger, columnDef) - if err != nil { - return nil, fmt.Errorf("failed to encode column definition: %w", err) - } - if _, err := buf.Write(columnBytes); err != nil { - return nil, fmt.Errorf("failed to write column definition: %w", err) - } - } - - // Write EOF packet after column definitions - if packet.NumColumns > 0 { - if _, err := buf.Write(packet.EOFAfterColumnDefs); err != nil { - return nil, fmt.Errorf("failed to write EOF packet after column definitions: %w", err) - } - } - - return buf.Bytes(), nil -} - -func EncodePreparedStmtResponse(_ context.Context, _ *zap.Logger, packet *mysql.StmtPrepareOkPacket, capabilities uint32) ([]byte, error) { - buf := new(bytes.Buffer) - - // Write Status - if err := buf.WriteByte(packet.Status); err != nil { - return nil, fmt.Errorf("failed to write Status: %w", err) - } - - // Write Statement ID - if err := binary.Write(buf, binary.LittleEndian, packet.StatementID); err != nil { - return nil, fmt.Errorf("failed to write StatementID: %w", err) - } - - // Write Number of Columns - if err := binary.Write(buf, binary.LittleEndian, packet.NumColumns); err != nil { - return nil, fmt.Errorf("failed to write NumColumns: %w", err) - } - - // Write Number of Parameters - if err := binary.Write(buf, binary.LittleEndian, packet.NumParams); err != nil { - return nil, fmt.Errorf("failed to write NumParams: %w", err) - } - - // Write Filler - if err := buf.WriteByte(packet.Filler); err != nil { - return nil, fmt.Errorf("failed to write Filler: %w", err) - } - - if packet.WarningAvailable { - // Write Warning Count - if err := binary.Write(buf, binary.LittleEndian, packet.WarningCount); err != nil { - return nil, fmt.Errorf("failed to write WarningCount: %w", err) - } - } - - if capabilities&uint32(mysql.CLIENT_OPTIONAL_RESULTSET_METADATA) > 0 && packet.MetaFollowsAvailable { - // Write Meta Follows - if err := buf.WriteByte(packet.MetaFollows); err != nil { - return nil, fmt.Errorf("failed to write MetaFollows: %w", err) - } - } - - return buf.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtClosePacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtClosePacket.go deleted file mode 100644 index 32a6b64..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtClosePacket.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build linux - -// Package preparedstmt provides functionality for decoding prepared statement packets. -package preparedstmt - -import ( - "context" - "encoding/binary" - "errors" - - "go.keploy.io/server/v2/pkg/models/mysql" -) - -//COM_STMT_CLOSE: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_close.html - -func DecoderStmtClose(_ context.Context, data []byte) (*mysql.StmtClosePacket, error) { - if len(data) != 5 { - return nil, errors.New("invalid packet for COM_STMT_CLOSE") - } - - packet := &mysql.StmtClosePacket{ - Status: data[0], - StatementID: binary.LittleEndian.Uint32(data[1:5]), - } - return packet, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket.go deleted file mode 100644 index c494897..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket.go +++ /dev/null @@ -1,237 +0,0 @@ -//go:build linux - -package preparedstmt - -import ( - "context" - "encoding/binary" - "fmt" - "io" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - intUtil "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -// COM_STMT_EXECUTE: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_execute.html - -func DecodeStmtExecute(_ context.Context, _ *zap.Logger, data []byte, preparedStmts map[uint32]*mysql.StmtPrepareOkPacket, clientCapabilities uint32) (*mysql.StmtExecutePacket, error) { - if len(data) < 10 { - return &mysql.StmtExecutePacket{}, fmt.Errorf("packet length too short for COM_STMT_EXECUTE") - } - - pos := 0 - - packet := &mysql.StmtExecutePacket{} - - // Read Status - if pos+1 > len(data) { - return nil, io.ErrUnexpectedEOF - } - //data[0] is COM_STMT_EXECUTE (0x17) - packet.Status = data[pos] - pos++ - - // Read StatementID - if pos+4 > len(data) { - return nil, io.ErrUnexpectedEOF - } - packet.StatementID = binary.LittleEndian.Uint32(data[pos : pos+4]) - pos += 4 - - stmtPrepOk, ok := preparedStmts[packet.StatementID] - if !ok && stmtPrepOk == nil { - return nil, fmt.Errorf("prepared statement with ID %d not found", packet.StatementID) - } - - // Read Flags - if pos+1 > len(data) { - return nil, io.ErrUnexpectedEOF - } - packet.Flags = data[pos] - pos++ - - // Read IterationCount - if pos+4 > len(data) { - return nil, io.ErrUnexpectedEOF - } - packet.IterationCount = binary.LittleEndian.Uint32(data[pos : pos+4]) - pos += 4 - - if stmtPrepOk.NumParams > 0 || (clientCapabilities&mysql.CLIENT_QUERY_ATTRIBUTES > 0 && (packet.Flags&mysql.PARAMETER_COUNT_AVAILABLE > 0)) { - // Set the parameter count from the prepared statement - packet.ParameterCount = int(stmtPrepOk.NumParams) - - if clientCapabilities&mysql.CLIENT_QUERY_ATTRIBUTES > 0 && (packet.Flags&mysql.PARAMETER_COUNT_AVAILABLE > 0) { - // If query attributes are supported and parameter count is available in the packet, - // we could potentially override it here, but for now we use the prepared statement count - packet.ParameterCount = int(stmtPrepOk.NumParams) - } - - if packet.ParameterCount <= 0 { - return packet, nil - } - - // Read Parameters only if there are any - - // Read NULL bitmap - nullBitmapLength := (packet.ParameterCount + 7) / 8 - if pos+nullBitmapLength > len(data) { - return nil, io.ErrUnexpectedEOF - } - packet.NullBitmap = data[pos : pos+nullBitmapLength] - pos += int(nullBitmapLength) - - // Read NewParamsBindFlag - if pos+1 > len(data) { - return nil, io.ErrUnexpectedEOF - } - packet.NewParamsBindFlag = data[pos] - pos++ - - // Initialize Parameters slice regardless of NewParamsBindFlag - packet.Parameters = make([]mysql.Parameter, packet.ParameterCount) - - // Read Parameters if NewParamsBindFlag is set - if packet.NewParamsBindFlag == 1 { - for i := 0; i < packet.ParameterCount; i++ { - if pos+2 > len(data) { - return nil, io.ErrUnexpectedEOF - } - packet.Parameters[i].Type = binary.LittleEndian.Uint16(data[pos : pos+2]) - packet.Parameters[i].Unsigned = (data[pos+1] & 0x80) != 0 // Check if the highest bit is set - pos += 2 - } - } else { - // When NewParamsBindFlag is 0, we reuse the previous parameter types - // For now, we'll set a default type for all parameters - for i := 0; i < packet.ParameterCount; i++ { - packet.Parameters[i].Type = uint16(mysql.FieldTypeString) // Default type - packet.Parameters[i].Unsigned = false - } - } - - // Read Parameter Values - for i := 0; i < packet.ParameterCount; i++ { - // Check if this parameter is NULL according to the NULL bitmap - byteIndex := i / 8 - bitIndex := i % 8 - if byteIndex < len(packet.NullBitmap) && (packet.NullBitmap[byteIndex]&(1<= len(data) { - return nil, io.ErrUnexpectedEOF - } - - // Process Parameter based on its type - param := &packet.Parameters[i] - - // Handle length-encoded values (only for types that require variable-length data) - switch mysql.FieldType(param.Type) { - case mysql.FieldTypeString, mysql.FieldTypeVarString, mysql.FieldTypeVarChar, mysql.FieldTypeBLOB, mysql.FieldTypeTinyBLOB, mysql.FieldTypeMediumBLOB, mysql.FieldTypeLongBLOB, mysql.FieldTypeJSON: - // Read the length of the parameter value - length, _, n := utils.ReadLengthEncodedInteger(data[pos:]) - pos += n - if pos+int(length) > len(data) { - return nil, io.ErrUnexpectedEOF - } - - if intUtil.IsASCII(string(data[pos : pos+int(length)])) { - param.Value = string(data[pos : pos+int(length)]) - } else { - param.Value = intUtil.EncodeBase64(data[pos : pos+int(length)]) - } - pos += int(length) - case mysql.FieldTypeLong: - if len(data[pos:]) < 4 { - return nil, fmt.Errorf("malformed FieldTypeLong value") - } - if param.Unsigned { - param.Value = uint32(binary.LittleEndian.Uint32(data[pos : pos+4])) - } else { - param.Value = int32(binary.LittleEndian.Uint32(data[pos : pos+4])) - } - pos += 4 - - case mysql.FieldTypeTiny: - if len(data[pos:]) < 1 { - return nil, fmt.Errorf("malformed FieldTypeTiny value") - } - if param.Unsigned { - param.Value = uint8(data[pos]) - } else { - param.Value = int8(data[pos]) - } - pos += 1 - - case mysql.FieldTypeShort, mysql.FieldTypeYear: - if len(data[pos:]) < 2 { - return nil, fmt.Errorf("malformed FieldTypeShort value") - } - if param.Unsigned { - param.Value = uint16(binary.LittleEndian.Uint16(data[pos : pos+2])) - } else { - param.Value = int16(binary.LittleEndian.Uint16(data[pos : pos+2])) - } - pos += 2 - - case mysql.FieldTypeLongLong: - if len(data[pos:]) < 8 { - return nil, fmt.Errorf("malformed FieldTypeLongLong value") - } - if param.Unsigned { - param.Value = uint64(binary.LittleEndian.Uint64(data[pos : pos+8])) - } else { - param.Value = int64(binary.LittleEndian.Uint64(data[pos : pos+8])) - } - pos += 8 - - case mysql.FieldTypeFloat: - if len(data[pos:]) < 4 { - return nil, fmt.Errorf("malformed FieldTypeFloat value") - } - param.Value = float32(binary.LittleEndian.Uint32(data[pos : pos+4])) - pos += 4 - - case mysql.FieldTypeDouble: - if len(data[pos:]) < 8 { - return nil, fmt.Errorf("malformed FieldTypeDouble value") - } - param.Value = float64(binary.LittleEndian.Uint64(data[pos : pos+8])) - pos += 8 - - case mysql.FieldTypeDate, mysql.FieldTypeNewDate: - value, _, err := utils.ParseBinaryDate(data[pos:]) - if err != nil { - return nil, err - } - param.Value = value - pos += len(param.Value.(string)) // Assuming date parsing returns a string - - case mysql.FieldTypeTimestamp, mysql.FieldTypeDateTime: - value, _, err := utils.ParseBinaryDateTime(data[pos:]) - if err != nil { - return nil, err - } - param.Value = value - pos += len(param.Value.(string)) // Assuming datetime parsing returns a string - - case mysql.FieldTypeTime: - value, _, err := utils.ParseBinaryTime(data[pos:]) - if err != nil { - return nil, err - } - param.Value = value - pos += len(param.Value.(string)) // Assuming time parsing returns a string - default: - return nil, fmt.Errorf("unsupported parameter type: %d", param.Type) - } - } - } - return packet, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket_test.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket_test.go deleted file mode 100644 index fbbf59d..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtExecutePacket_test.go +++ /dev/null @@ -1,110 +0,0 @@ -//go:build linux - -package preparedstmt - -import ( - "context" - "testing" - - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -func TestDecodeStmtExecute_ParameterCountFix(t *testing.T) { - // Create a mock prepared statement with 3 parameters - stmtPrepOk := &mysql.StmtPrepareOkPacket{ - StatementID: 945, - NumParams: 3, - NumColumns: 10, - } - - preparedStmts := map[uint32]*mysql.StmtPrepareOkPacket{ - 945: stmtPrepOk, - } - - // Mock COM_STMT_EXECUTE packet data (39 bytes as seen in the logs) - // This represents a packet with statement ID 945, but with 3 NULL parameters - data := []byte{ - 0x17, // COM_STMT_EXECUTE command - 0xb1, 0x03, 0x00, 0x00, // Statement ID (945) - 0x00, // Flags - 0x01, 0x00, 0x00, 0x00, // Iteration count (1) - 0x01, // NULL bitmap (1 byte for 3 params, all NULL) - 0x00, // New params bind flag (0 = reuse previous types) - // No parameter type data since new_params_bind_flag = 0 - // No parameter value data since all parameters are NULL - } - - // Pad to 39 bytes total to match the logs - for len(data) < 39 { - data = append(data, 0x00) - } - - logger := zap.NewNop() - ctx := context.Background() - - // Test with CLIENT_QUERY_ATTRIBUTES disabled (common case) - clientCapabilities := uint32(0) - - packet, err := DecodeStmtExecute(ctx, logger, data, preparedStmts, clientCapabilities) - - if err != nil { - t.Fatalf("DecodeStmtExecute failed: %v", err) - } - - // Verify that ParameterCount is set correctly from the prepared statement - if packet.ParameterCount != 3 { - t.Errorf("Expected ParameterCount to be 3, got %d", packet.ParameterCount) - } - - // Verify other fields - if packet.StatementID != 945 { - t.Errorf("Expected StatementID to be 945, got %d", packet.StatementID) - } - - if packet.Status != mysql.COM_STMT_EXECUTE { - t.Errorf("Expected Status to be COM_STMT_EXECUTE (%d), got %d", mysql.COM_STMT_EXECUTE, packet.Status) - } -} - -func TestDecodeStmtExecute_NoParameters(t *testing.T) { - // Create a mock prepared statement with 0 parameters - stmtPrepOk := &mysql.StmtPrepareOkPacket{ - StatementID: 946, - NumParams: 0, - NumColumns: 10, - } - - preparedStmts := map[uint32]*mysql.StmtPrepareOkPacket{ - 946: stmtPrepOk, - } - - // Mock COM_STMT_EXECUTE packet data with no parameters - data := []byte{ - 0x17, // COM_STMT_EXECUTE command - 0xb2, 0x03, 0x00, 0x00, // Statement ID (946) - 0x00, // Flags - 0x01, 0x00, 0x00, 0x00, // Iteration count (1) - // No NULL bitmap since no parameters - // No new params bind flag since no parameters - } - - logger := zap.NewNop() - ctx := context.Background() - clientCapabilities := uint32(0) - - packet, err := DecodeStmtExecute(ctx, logger, data, preparedStmts, clientCapabilities) - - if err != nil { - t.Fatalf("DecodeStmtExecute failed: %v", err) - } - - // Verify that ParameterCount is 0 and the function returns early - if packet.ParameterCount != 0 { - t.Errorf("Expected ParameterCount to be 0, got %d", packet.ParameterCount) - } - - if packet.StatementID != 946 { - t.Errorf("Expected StatementID to be 946, got %d", packet.StatementID) - } -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtFetchPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtFetchPacket.go deleted file mode 100644 index 9b2761b..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtFetchPacket.go +++ /dev/null @@ -1,29 +0,0 @@ -//go:build linux - -package preparedstmt - -import ( - "context" - "encoding/binary" - "errors" - - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -// COM_STMT_FETCH: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_fetch.html - -func DecodeStmtFetch(_ context.Context, _ *zap.Logger, data []byte) (*mysql.StmtFetchPacket, error) { - if len(data) < 9 { - return &mysql.StmtFetchPacket{}, errors.New("data too short for COM_STMT_FETCH") - } - - packet := &mysql.StmtFetchPacket{ - Status: data[0], - } - - packet.StatementID = binary.LittleEndian.Uint32(data[1:5]) - packet.NumRows = binary.LittleEndian.Uint32(data[5:9]) - - return packet, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtPreparePacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtPreparePacket.go deleted file mode 100644 index 7e02c87..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtPreparePacket.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build linux - -package preparedstmt - -import ( - "context" - "strings" - - "go.keploy.io/server/v2/pkg/models/mysql" -) - -//COM_STMT_PREPARE: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_prepare.html - -func DecodeStmtPrepare(_ context.Context, data []byte) (*mysql.StmtPreparePacket, error) { - packet := &mysql.StmtPreparePacket{ - Command: data[0], - } - - query := string(data[1:]) - packet.Query = strings.ReplaceAll(query, "\t", "") - return packet, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtResetPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtResetPacket.go deleted file mode 100644 index 40ae959..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtResetPacket.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build linux - -package preparedstmt - -import ( - "context" - "encoding/binary" - "fmt" - - "go.keploy.io/server/v2/pkg/models/mysql" -) - -//COM_STMT_RESET: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_reset.html - -func DecodeStmtReset(_ context.Context, data []byte) (*mysql.StmtResetPacket, error) { - if len(data) != 5 { - return nil, fmt.Errorf("invalid COM_STMT_RESET packet") - } - - packet := &mysql.StmtResetPacket{ - Status: data[0], - StatementID: binary.LittleEndian.Uint32(data[1:5]), - } - return packet, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtSendLongDataPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtSendLongDataPacket.go deleted file mode 100644 index 4dc02c4..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/preparedstmt/stmtSendLongDataPacket.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build linux - -package preparedstmt - -import ( - "context" - "encoding/binary" - "fmt" - - "go.keploy.io/server/v2/pkg/models/mysql" -) - -// COM_STMT_SEND_LONG_DATA: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_send_long_data.html - -func DecodeStmtSendLongData(_ context.Context, data []byte) (*mysql.StmtSendLongDataPacket, error) { - if len(data) < 7 || data[0] != 0x18 { - return &mysql.StmtSendLongDataPacket{}, fmt.Errorf("invalid COM_STMT_SEND_LONG_DATA packet") - } - - packet := &mysql.StmtSendLongDataPacket{ - Status: data[0], - StatementID: binary.LittleEndian.Uint32(data[1:5]), - ParameterID: binary.LittleEndian.Uint16(data[5:7]), - Data: data[7:], - } - - return packet, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/queryPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/queryPacket.go deleted file mode 100644 index a6b3854..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/queryPacket.go +++ /dev/null @@ -1,225 +0,0 @@ -//go:build linux - -// Package query provides functions to decode MySQL command phase packets. -package query - -import ( - "context" - "encoding/binary" - "fmt" - "io" - "strings" - - intUtil "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -// COM_QUERY: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_query.html - -func DecodeQuery(_ context.Context, logger *zap.Logger, data []byte, clientCapabilities uint32) (*mysql.QueryPacket, error) { - - logger.Debug("Decoding query packet", zap.Int("data_length", len(data)), zap.Any("query buffer", string(data))) - - if len(data) < 2 { - return nil, fmt.Errorf("query packet is empty") - } - packet := &mysql.QueryPacket{ - Command: data[0], - } - - pos := 1 // Start reading after the command byte - - // Early return if no query attributes to process - if clientCapabilities&mysql.CLIENT_QUERY_ATTRIBUTES == 0 { - packet.Query = string(data[pos:]) - packet.Query = replaceTabsWithSpaces(packet.Query) - logger.Debug("Decoded query packet without attributes", zap.String("query", packet.Query)) - return packet, nil - } - - if pos >= len(data) { - return nil, fmt.Errorf("malformed query packet: no data for parameter_count when CLIENT_QUERY_ATTRIBUTES is set") - } - - // 1. Read parameter_count (lenenc-int). - paramCount, isNull, n := utils.ReadLengthEncodedInteger(data[pos:]) - if isNull { - return nil, fmt.Errorf("malformed query packet: got NULL for parameter_count") - } - pos += n - - packet.ParameterCount = int(paramCount) - - if pos >= len(data) { - return nil, fmt.Errorf("malformed query packet: missing parameter_set_count") - } - - // 2. Read parameter_set_count (lenenc-int). - paramSetCount, isNull, n := utils.ReadLengthEncodedInteger(data[pos:]) - if isNull || (paramSetCount != 1) { - return nil, fmt.Errorf("malformed query packet: expected parameter_set_count of 1, got %d", paramSetCount) - } - pos += n - - if paramCount > 0 { - - nullBitmapLength := (packet.ParameterCount + 7) / 8 - if pos+nullBitmapLength > len(data) { - return nil, fmt.Errorf("malformed query packet: data too short for query attribute null_bitmap") - } - packet.NullBitmap = data[pos : pos+nullBitmapLength] - pos += int(nullBitmapLength) - - if pos+1 > len(data) { - return nil, fmt.Errorf("malformed query packet: data too short for new_params_bind_flag") - } - packet.NewParamsBindFlag = data[pos] - pos++ - - if packet.NewParamsBindFlag != 1 { - return nil, fmt.Errorf("malformed query packet: new_params_bind_flag should be always 1 if parameter_count > 0") - } - - packet.Parameters = make([]mysql.Parameter, packet.ParameterCount) - - for i := 0; i < packet.ParameterCount; i++ { - if pos+2 > len(data) { - return nil, fmt.Errorf("malformed query packet: data too short for parameter types") - } - packet.Parameters[i].Type = binary.LittleEndian.Uint16(data[pos : pos+2]) - packet.Parameters[i].Unsigned = (data[pos+1] & 0x80) != 0 // Check if the highest bit is set - pos += 2 - } - - // Read Parameter Values - for i := 0; i < packet.ParameterCount; i++ { - if pos >= len(data) { - return nil, io.ErrUnexpectedEOF - } - - // Process Parameter based on its type - param := &packet.Parameters[i] - - // Handle length-encoded values (only for types that require variable-length data) - switch mysql.FieldType(param.Type) { - case mysql.FieldTypeString, mysql.FieldTypeVarString, mysql.FieldTypeVarChar, mysql.FieldTypeBLOB, mysql.FieldTypeTinyBLOB, mysql.FieldTypeMediumBLOB, mysql.FieldTypeLongBLOB, mysql.FieldTypeJSON: - // Read the length of the parameter value - length, _, n := utils.ReadLengthEncodedInteger(data[pos:]) - pos += n - if pos+int(length) > len(data) { - return nil, io.ErrUnexpectedEOF - } - - if intUtil.IsASCII(string(data[pos : pos+int(length)])) { - param.Value = string(data[pos : pos+int(length)]) - } else { - param.Value = intUtil.EncodeBase64(data[pos : pos+int(length)]) - } - pos += int(length) - case mysql.FieldTypeLong: - if len(data[pos:]) < 4 { - return nil, fmt.Errorf("malformed FieldTypeLong value") - } - if param.Unsigned { - param.Value = uint32(binary.LittleEndian.Uint32(data[pos : pos+4])) - } else { - param.Value = int32(binary.LittleEndian.Uint32(data[pos : pos+4])) - } - pos += 4 - - case mysql.FieldTypeTiny: - if len(data[pos:]) < 1 { - return nil, fmt.Errorf("malformed FieldTypeTiny value") - } - if param.Unsigned { - param.Value = uint8(data[pos]) - } else { - param.Value = int8(data[pos]) - } - pos += 1 - - case mysql.FieldTypeShort, mysql.FieldTypeYear: - if len(data[pos:]) < 2 { - return nil, fmt.Errorf("malformed FieldTypeShort value") - } - if param.Unsigned { - param.Value = uint16(binary.LittleEndian.Uint16(data[pos : pos+2])) - } else { - param.Value = int16(binary.LittleEndian.Uint16(data[pos : pos+2])) - } - pos += 2 - - case mysql.FieldTypeLongLong: - if len(data[pos:]) < 8 { - return nil, fmt.Errorf("malformed FieldTypeLongLong value") - } - if param.Unsigned { - param.Value = uint64(binary.LittleEndian.Uint64(data[pos : pos+8])) - } else { - param.Value = int64(binary.LittleEndian.Uint64(data[pos : pos+8])) - } - pos += 8 - - case mysql.FieldTypeFloat: - if len(data[pos:]) < 4 { - return nil, fmt.Errorf("malformed FieldTypeFloat value") - } - param.Value = float32(binary.LittleEndian.Uint32(data[pos : pos+4])) - pos += 4 - - case mysql.FieldTypeDouble: - if len(data[pos:]) < 8 { - return nil, fmt.Errorf("malformed FieldTypeDouble value") - } - param.Value = float64(binary.LittleEndian.Uint64(data[pos : pos+8])) - pos += 8 - - case mysql.FieldTypeDate, mysql.FieldTypeNewDate: - value, _, err := utils.ParseBinaryDate(data[pos:]) - if err != nil { - return nil, err - } - param.Value = value - pos += len(param.Value.(string)) // Assuming date parsing returns a string - - case mysql.FieldTypeTimestamp, mysql.FieldTypeDateTime: - value, _, err := utils.ParseBinaryDateTime(data[pos:]) - if err != nil { - return nil, err - } - param.Value = value - pos += len(param.Value.(string)) // Assuming datetime parsing returns a string - - case mysql.FieldTypeTime: - value, _, err := utils.ParseBinaryTime(data[pos:]) - if err != nil { - return nil, err - } - param.Value = value - pos += len(param.Value.(string)) // Assuming time parsing returns a string - default: - return nil, fmt.Errorf("unsupported parameter type: %d", param.Type) - } - } - } - - if pos >= len(data) { - return nil, fmt.Errorf("malformed query packet: no data for query") - } - - // Trim any trailing null bytes which can sometimes be appended by clients. - packet.Query = string(data[pos:]) - packet.Query = replaceTabsWithSpaces(packet.Query) - - logger.Debug("Decoded query packet with attributes", zap.String("query", packet.Query)) - - return packet, nil -} - -// This is required to replace tabs with spaces in the query string, as yaml does not support tabs. -func replaceTabsWithSpaces(query string) string { - return strings.ReplaceAll(query, "\t", " ") -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go deleted file mode 100644 index ff360da..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go +++ /dev/null @@ -1,497 +0,0 @@ -//go:build linux - -// Package rowscols provides encoding and decoding of MySQL row & column packets. -package rowscols - -import ( - "bytes" - "context" - "encoding/base64" - "encoding/binary" - "errors" - "fmt" - "strings" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html#sect_protocol_binary_resultset_row - -func DecodeBinaryRow(_ context.Context, _ *zap.Logger, data []byte, columns []*mysql.ColumnDefinition41) (*mysql.BinaryRow, int, error) { - - offset := 0 - row := &mysql.BinaryRow{ - Header: mysql.Header{ - PayloadLength: utils.ReadUint24(data[offset : offset+3]), - SequenceID: data[offset+3], - }, - } - offset += 4 - - if data[offset] != 0x00 { - return nil, offset, errors.New("malformed binary row packet") - } - row.OkAfterRow = true - offset++ - - nullBitmapLen := (len(columns) + 7 + 2) / 8 - nullBitmap := data[offset : offset+nullBitmapLen] - row.RowNullBuffer = nullBitmap - - offset += nullBitmapLen - - for i, col := range columns { - if isNull(nullBitmap, i) { // This Null doesn't progress the offset - row.Values = append(row.Values, mysql.ColumnEntry{ - Type: mysql.FieldType(col.Type), - Name: col.Name, - Value: nil, - }) - continue - } - - res, n, err := readBinaryValue(data[offset:], col) - if err != nil { - return nil, offset, err - } - - row.Values = append(row.Values, mysql.ColumnEntry{ - Type: mysql.FieldType(col.Type), - Name: col.Name, - Value: res.value, - Unsigned: res.isUnsigned, - }) - offset += n - } - return row, offset, nil -} - -func isNull(nullBitmap []byte, index int) bool { - bytePos := (index + 2) / 8 - bitPos := (index + 2) % 8 - return nullBitmap[bytePos]&(1< uint32(pos) { - //length of default value lenenc-int - defaultValueLength, _, n := utils.ReadLengthEncodedInteger(b[pos:]) - pos += n - - if pos+int(defaultValueLength) > len(b) { - - return nil, pos, fmt.Errorf("malformed packet: %v", err) - } - - //default value string[$len] - packet.DefaultValue = string(b[pos:(pos + int(defaultValueLength))]) - pos-- - } - - return packet, pos, nil -} - -func EncodeColumn(_ context.Context, _ *zap.Logger, packet *mysql.ColumnDefinition41) ([]byte, error) { - // Build the body first - body := new(bytes.Buffer) - - // Catalog, Schema, Table, OrgTable, Name, OrgName - if err := utils.WriteLengthEncodedString(body, packet.Catalog); err != nil { - return nil, fmt.Errorf("failed to write Catalog: %w", err) - } - if err := utils.WriteLengthEncodedString(body, packet.Schema); err != nil { - return nil, fmt.Errorf("failed to write Schema: %w", err) - } - if err := utils.WriteLengthEncodedString(body, packet.Table); err != nil { - return nil, fmt.Errorf("failed to write Table: %w", err) - } - if err := utils.WriteLengthEncodedString(body, packet.OrgTable); err != nil { - return nil, fmt.Errorf("failed to write OrgTable: %w", err) - } - if err := utils.WriteLengthEncodedString(body, packet.Name); err != nil { - return nil, fmt.Errorf("failed to write Name: %w", err) - } - if err := utils.WriteLengthEncodedString(body, packet.OrgName); err != nil { - return nil, fmt.Errorf("failed to write OrgName: %w", err) - } - - // Fixed-length fields length (always 0x0c) - if err := body.WriteByte(packet.FixedLength); err != nil { - return nil, fmt.Errorf("failed to write FixedLength: %w", err) - } - - // CharacterSet, ColumnLength, Type, Flags, Decimals - if err := binary.Write(body, binary.LittleEndian, packet.CharacterSet); err != nil { - return nil, fmt.Errorf("failed to write CharacterSet: %w", err) - } - if err := binary.Write(body, binary.LittleEndian, packet.ColumnLength); err != nil { - return nil, fmt.Errorf("failed to write ColumnLength: %w", err) - } - if err := body.WriteByte(packet.Type); err != nil { - return nil, fmt.Errorf("failed to write Type: %w", err) - } - if err := binary.Write(body, binary.LittleEndian, packet.Flags); err != nil { - return nil, fmt.Errorf("failed to write Flags: %w", err) - } - if err := body.WriteByte(packet.Decimals); err != nil { - return nil, fmt.Errorf("failed to write Decimals: %w", err) - } - - // Filler (2 bytes) - filler := packet.Filler - if len(filler) != 2 { - filler = []byte{0x00, 0x00} - } - if _, err := body.Write(filler); err != nil { - return nil, fmt.Errorf("failed to write Filler: %w", err) - } - - // Optional DefaultValue (COM_FIELD_LIST case) - if packet.DefaultValue != "" { - if err := utils.WriteLengthEncodedString(body, packet.DefaultValue); err != nil { - return nil, fmt.Errorf("failed to write DefaultValue: %w", err) - } - } - - // Prepend header with computed payload length - out := new(bytes.Buffer) - if err := utils.WriteUint24(out, uint32(body.Len())); err != nil { - return nil, fmt.Errorf("failed to write PayloadLength: %w", err) - } - if err := out.WriteByte(packet.Header.SequenceID); err != nil { - return nil, fmt.Errorf("failed to write SequenceID: %w", err) - } - if _, err := out.Write(body.Bytes()); err != nil { - return nil, err - } - return out.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/textRowPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/textRowPacket.go deleted file mode 100644 index 04e2d67..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/textRowPacket.go +++ /dev/null @@ -1,105 +0,0 @@ -//go:build linux - -package rowscols - -import ( - "bytes" - "context" - "fmt" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.uber.org/zap" -) - -//ref: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_query_response_text_resultset_row.html - -func DecodeTextRow(_ context.Context, _ *zap.Logger, data []byte, columns []*mysql.ColumnDefinition41) (*mysql.TextRow, int, error) { - offset := 0 - row := &mysql.TextRow{ - Header: mysql.Header{ - PayloadLength: utils.ReadUint24(data[offset : offset+3]), - SequenceID: data[offset+3], - }, - } - - offset += 4 - - for _, col := range columns { - dataLength := data[offset] - if dataLength == 0xfb { // NULL - row.Values = append(row.Values, mysql.ColumnEntry{ - Type: mysql.FieldType(col.Type), - Name: col.Name, - Value: nil, - }) - offset++ - continue - } - - value, _, n, err := utils.ReadLengthEncodedString(data[offset:]) - if err != nil { - return nil, offset, fmt.Errorf("failed to read length-encoded string: %w", err) - } - row.Values = append(row.Values, mysql.ColumnEntry{ - Type: mysql.FieldType(col.Type), - Name: col.Name, - Value: string(value), - }) - offset += n - } - - return row, offset, nil -} - -// rowscols/textRowPacket.go - -func EncodeTextRow(_ context.Context, _ *zap.Logger, row *mysql.TextRow, columns []*mysql.ColumnDefinition41) ([]byte, error) { - body := new(bytes.Buffer) - - // Write each column's value into the body - for i := range columns { - v := row.Values[i].Value - switch x := v.(type) { - case nil: - // NULL is 0xfb - if err := body.WriteByte(0xfb); err != nil { - return nil, fmt.Errorf("failed to write NULL: %w", err) - } - - case string: - if err := utils.WriteLengthEncodedString(body, x); err != nil { - return nil, fmt.Errorf("failed to write lenenc string: %w", err) - } - - case []byte: - // allow blobs as raw bytes in text rows - if err := utils.WriteLengthEncodedInteger(body, uint64(len(x))); err != nil { - return nil, fmt.Errorf("failed to write len for bytes: %w", err) - } - if _, err := body.Write(x); err != nil { - return nil, fmt.Errorf("failed to write bytes: %w", err) - } - - default: - // fall back to the textual form (keeps old recordings working) - s := fmt.Sprint(x) - if err := utils.WriteLengthEncodedString(body, s); err != nil { - return nil, fmt.Errorf("failed to write fallback string: %w", err) - } - } - } - - // Now prepend the header using the computed length - out := new(bytes.Buffer) - if err := utils.WriteUint24(out, uint32(body.Len())); err != nil { - return nil, fmt.Errorf("failed to write PayloadLength: %w", err) - } - if err := out.WriteByte(row.Header.SequenceID); err != nil { - return nil, fmt.Errorf("failed to write SequenceID: %w", err) - } - if _, err := out.Write(body.Bytes()); err != nil { - return nil, err - } - return out.Bytes(), nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/initDbPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/initDbPacket.go deleted file mode 100644 index a498e69..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/initDbPacket.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build linux - -// Package utility provides encoding and decoding of utility command packets. -package utility - -import ( - "context" - - "go.keploy.io/server/v2/pkg/models/mysql" -) - -//COM_INIT_DB: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_init_db.html - -func DecodeInitDb(_ context.Context, data []byte) (*mysql.InitDBPacket, error) { - packet := &mysql.InitDBPacket{ - Command: data[0], - Schema: string(data[1:]), - } - return packet, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/setOptionPacket.go b/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/setOptionPacket.go deleted file mode 100644 index 91599d0..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/phase/query/utility/setOptionPacket.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build linux - -package utility - -import ( - "context" - "encoding/binary" - "fmt" - - "go.keploy.io/server/v2/pkg/models/mysql" -) - -//COM_SET_OPTION: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_set_option.html - -func DecodeSetOption(_ context.Context, data []byte) (*mysql.SetOptionPacket, error) { - if len(data) < 3 { - return nil, fmt.Errorf("set option packet too short") - } - - packet := &mysql.SetOptionPacket{ - Status: data[0], - Option: binary.LittleEndian.Uint16(data[1:3]), - } - - return packet, nil -} diff --git a/keploy/pkg/core/proxy/integrations/mysql/wire/util.go b/keploy/pkg/core/proxy/integrations/mysql/wire/util.go deleted file mode 100644 index 1203af7..0000000 --- a/keploy/pkg/core/proxy/integrations/mysql/wire/util.go +++ /dev/null @@ -1,175 +0,0 @@ -//go:build linux - -package wire - -import ( - "context" - "fmt" - "net" - "sync" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/models/mysql" -) - -const RESET = 0x00 - -type DecodeContext struct { - Mode models.Mode - LastOp *LastOperation - PreparedStatements map[uint32]*mysql.StmtPrepareOkPacket - ServerGreetings *ServerGreetings - ClientCapabilities uint32 - PluginName string - UseSSL bool - // Capability flags - ServerCaps uint32 // negotiated server caps (from HandshakeV10) - ClientCaps uint32 // live client's caps (from HandshakeResponse41) - RecordedClientCaps uint32 // caps from the recorded config mock - PreferRecordedCaps bool // if true, prefer RecordedClientCaps over ClientCaps -} - -const CLIENT_DEPRECATE_EOF = 0x01000000 - -func (d *DecodeContext) effectiveClientCaps() uint32 { - if d.PreferRecordedCaps && d.RecordedClientCaps != 0 { - return d.RecordedClientCaps - } - return d.ClientCaps -} - -func (d *DecodeContext) DeprecateEOF() bool { - return (d.ServerCaps&CLIENT_DEPRECATE_EOF) != 0 && - (d.effectiveClientCaps()&CLIENT_DEPRECATE_EOF) != 0 -} - -// This map is used to store the last operation that was performed on a connection. -// It helps us to determine the last mysql packet. - -type LastOperation struct { - sync.RWMutex - operations map[net.Conn]byte -} - -func NewLastOpMap() *LastOperation { - return &LastOperation{ - operations: make(map[net.Conn]byte), - } -} - -func (lo *LastOperation) Load(key net.Conn) (value byte, ok bool) { - lo.RLock() - result, ok := lo.operations[key] - lo.RUnlock() - return result, ok -} - -func (lo *LastOperation) Store(key net.Conn, value byte) { - lo.Lock() - lo.operations[key] = value - lo.Unlock() -} - -// This map is used to store the server greetings for each connection. -// It helps us to determine the server version and capabilities. -// Capabilities are helpful in decoding some packets. - -type ServerGreetings struct { - sync.RWMutex - handshakes map[net.Conn]*mysql.HandshakeV10Packet -} - -func NewGreetings() *ServerGreetings { - return &ServerGreetings{ - handshakes: make(map[net.Conn]*mysql.HandshakeV10Packet), - } -} - -func (sg *ServerGreetings) Load(key net.Conn) (*mysql.HandshakeV10Packet, bool) { - sg.RLock() - result, ok := sg.handshakes[key] - sg.RUnlock() - return result, ok -} - -func (sg *ServerGreetings) Store(key net.Conn, value *mysql.HandshakeV10Packet) { - sg.Lock() - sg.handshakes[key] = value - sg.Unlock() -} - -func setPacketInfo(_ context.Context, parsedPacket *mysql.PacketBundle, pkt interface{}, pktType string, clientConn net.Conn, lastOp byte, decodeCtx *DecodeContext) { - parsedPacket.Header.Type = pktType - parsedPacket.Message = pkt - decodeCtx.LastOp.Store(clientConn, lastOp) -} - -func GetPluginName(buf interface{}) (string, error) { - switch v := buf.(type) { - case *mysql.HandshakeV10Packet: - return v.AuthPluginName, nil - case *mysql.AuthSwitchRequestPacket: - return v.PluginName, nil - default: - return "", fmt.Errorf("invalid packet type to get plugin name") - } -} - -func GetCachingSha2PasswordMechanism(data byte) (string, error) { - switch data { - case byte(mysql.PerformFullAuthentication): - return mysql.CachingSha2PasswordToString(mysql.PerformFullAuthentication), nil - case byte(mysql.FastAuthSuccess): - return mysql.CachingSha2PasswordToString(mysql.FastAuthSuccess), nil - default: - einval := fmt.Sprintf("invalid caching_sha2_password mechanism, found:%02x ", data) - return "", fmt.Errorf("%s", einval) - } -} - -func StringToCachingSha2PasswordMechanism(data string) (mysql.CachingSha2Password, error) { - switch data { - case "PerformFullAuthentication": - return mysql.PerformFullAuthentication, nil - case "FastAuthSuccess": - return mysql.FastAuthSuccess, nil - default: - einval := fmt.Sprintf("invalid caching_sha2_password mechanism, found:%s ", data) - return 0, fmt.Errorf("%s", einval) - } -} - -func IsGenericResponsePkt(packet *mysql.PacketBundle) bool { - if packet == nil { - return false - } - switch packet.Message.(type) { - case *mysql.OKPacket, *mysql.ERRPacket, *mysql.EOFPacket: - return true - default: - return false - } -} - -func IsNoResponseCommand(command string) bool { - switch command { - case mysql.CommandStatusToString(mysql.COM_STMT_CLOSE), mysql.CommandStatusToString(mysql.COM_STMT_SEND_LONG_DATA): - return true - default: - return false - } -} - -// PrintByteArray is only for debugging purpose -func PrintByteArray(name string, b []byte) { - fmt.Printf("%s:\n", name) - var i = 1 - for _, byte := range b { - fmt.Printf(" %02x", byte) - i++ - if i%16 == 0 { - fmt.Println() - } - } - fmt.Println() -} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/decode.go b/keploy/pkg/core/proxy/integrations/postgres/v1/decode.go deleted file mode 100644 index 25b8f5e..0000000 --- a/keploy/pkg/core/proxy/integrations/postgres/v1/decode.go +++ /dev/null @@ -1,152 +0,0 @@ -//go:build linux - -// Package v1 provides functionality for decoding Postgres requests and responses. -package v1 - -import ( - "context" - "fmt" - "io" - "net" - "strings" - "sync" - "time" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func decodePostgres(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, _ models.OutgoingOptions) error { - pgRequests := [][]byte{reqBuf} - errCh := make(chan error, 1) - - go func(errCh chan error, pgRequests [][]byte) { - defer pUtil.Recover(logger, clientConn, nil) - // close should be called from the producer of the channel - defer close(errCh) - for { - // Since protocol packets have to be parsed for checking stream end, - // clientConnection have deadline for read to determine the end of stream. - err := clientConn.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) - if err != nil && err != io.EOF && strings.Contains(err.Error(), "use of closed network connection") { - utils.LogError(logger, err, "failed to set the read deadline for the pg client conn") - errCh <- err - } - - // To read the stream of request packets from the client - for { - buffer, err := pUtil.ReadBytes(ctx, logger, clientConn) - if err != nil { - // Applied this nolint to ignore the staticcheck error here because of readability - // nolint:staticcheck - if netErr, ok := err.(net.Error); !(ok && netErr.Timeout()) { - if err == io.EOF { - logger.Debug("EOF error received from client. Closing conn in postgres !!") - errCh <- err - } - logger.Debug("failed to read the request message in proxy for postgres dependency") - errCh <- err - } - } - if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - break - } - pgRequests = append(pgRequests, buffer) - } - - if len(pgRequests) == 0 { - continue - } - var mutex sync.Mutex - matched, pgResponses, err := matchingReadablePG(ctx, logger, &mutex, pgRequests, mockDb) - if err != nil { - errCh <- fmt.Errorf("error while matching tcs mocks %v", err) - return - } - - if !matched { - logger.Debug("MISMATCHED REQ is" + string(pgRequests[0])) - _, err = pUtil.PassThrough(ctx, logger, clientConn, dstCfg, pgRequests) - if err != nil { - utils.LogError(logger, err, "failed to pass the request", zap.Any("request packets", len(pgRequests))) - errCh <- err - } - continue - } - for _, pgResponse := range pgResponses { - encoded, err := util.DecodeBase64(pgResponse.Payload) - if len(pgResponse.PacketTypes) > 0 && len(pgResponse.Payload) == 0 { - encoded, err = postgresDecoderFrontend(pgResponse) - } - if err != nil { - utils.LogError(logger, err, "failed to decode the response message in proxy for postgres dependency") - errCh <- err - } - _, err = clientConn.Write(encoded) - if err != nil && err != io.EOF && strings.Contains(err.Error(), "use of closed network connection") { - utils.LogError(logger, err, "failed to write the response message to the client application") - errCh <- err - } - } - // Clear the buffer for the next dependency call - pgRequests = [][]byte{} - } - }(errCh, pgRequests) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - return nil - } - return err - } -} - -type QueryData struct { - PrepIdentifier string `json:"PrepIdentifier" yaml:"PrepIdentifier"` - Query string `json:"Query" yaml:"Query"` -} - -type PrepMap map[string][]QueryData - -type TestPrepMap map[string][]QueryData - -func getRecordPrepStatement(allMocks []*models.Mock) PrepMap { - preparedstatement := make(PrepMap) - for _, v := range allMocks { - if v.Kind != "Postgres" { - continue - } - for _, req := range v.Spec.PostgresRequests { - var querydata []QueryData - psMap := make(map[string]string) - if len(req.PacketTypes) > 0 && req.PacketTypes[0] != "p" && req.Identfier != "StartupRequest" { - p := 0 - for _, header := range req.PacketTypes { - if header == "P" { - if strings.Contains(req.Parses[p].Name, "S_") || strings.Contains(req.Parses[p].Name, "s") { - psMap[req.Parses[p].Query] = req.Parses[p].Name - querydata = append(querydata, QueryData{PrepIdentifier: req.Parses[p].Name, - Query: req.Parses[p].Query, - }) - - } - p++ - } - } - } - // also append the query data for the prepared statement - if len(querydata) > 0 { - preparedstatement[v.ConnectionID] = append(preparedstatement[v.ConnectionID], querydata...) - } - } - - } - return preparedstatement -} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/encode.go b/keploy/pkg/core/proxy/integrations/postgres/v1/encode.go deleted file mode 100755 index 670a4d7..0000000 --- a/keploy/pkg/core/proxy/integrations/postgres/v1/encode.go +++ /dev/null @@ -1,401 +0,0 @@ -//go:build linux - -package v1 - -import ( - "context" - "encoding/binary" - "fmt" - "io" - "net" - "strconv" - "time" - - "github.com/jackc/pgproto3/v2" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "golang.org/x/sync/errgroup" -) - -func encodePostgres(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, _ models.OutgoingOptions) error { - - logger.Debug("Inside the encodePostgresOutgoing function") - var pgRequests []models.Backend - - bufStr := util.EncodeBase64(reqBuf) - pg := NewBackend() - _, err := pg.decodeStartupMessage(reqBuf) - if err != nil { - utils.LogError(logger, err, "failed to decode startup message server") - } - - if bufStr != "" { - pgRequests = append(pgRequests, models.Backend{ - PacketTypes: pg.BackendWrapper.PacketTypes, - Identfier: "StartupRequest", - Length: uint32(len(reqBuf)), - Payload: bufStr, - Bind: pg.BackendWrapper.Bind, - PasswordMessage: pg.BackendWrapper.PasswordMessage, - CancelRequest: pg.BackendWrapper.CancelRequest, - Close: pg.BackendWrapper.Close, - CopyData: pg.BackendWrapper.CopyData, - CopyDone: pg.BackendWrapper.CopyDone, - CopyFail: pg.BackendWrapper.CopyFail, - Describe: pg.BackendWrapper.Describe, - Execute: pg.BackendWrapper.Execute, - Flush: pg.BackendWrapper.Flush, - FunctionCall: pg.BackendWrapper.FunctionCall, - GssEncRequest: pg.BackendWrapper.GssEncRequest, - Parse: pg.BackendWrapper.Parse, - Query: pg.BackendWrapper.Query, - SSlRequest: pg.BackendWrapper.SSlRequest, - StartupMessage: pg.BackendWrapper.StartupMessage, - SASLInitialResponse: pg.BackendWrapper.SASLInitialResponse, - SASLResponse: pg.BackendWrapper.SASLResponse, - Sync: pg.BackendWrapper.Sync, - Terminate: pg.BackendWrapper.Terminate, - MsgType: pg.BackendWrapper.MsgType, - AuthType: pg.BackendWrapper.AuthType, - }) - - logger.Debug("Before for loop pg request starts", zap.Any("pgReqs", len(pgRequests))) - } - - _, err = destConn.Write(reqBuf) - if err != nil { - utils.LogError(logger, err, "failed to write request message to the destination server") - return err - } - var pgResponses []models.Frontend - - clientBuffChan := make(chan []byte) - destBuffChan := make(chan []byte) - errChan := make(chan error, 1) - - //get the error group from the context - g := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - - // read requests from client - g.Go(func() error { - defer pUtil.Recover(logger, clientConn, destConn) - defer close(clientBuffChan) - pUtil.ReadBuffConn(ctx, logger, clientConn, clientBuffChan, errChan) - return nil - }) - - // read responses from destination - g.Go(func() error { - defer pUtil.Recover(logger, clientConn, destConn) - defer close(destBuffChan) - pUtil.ReadBuffConn(ctx, logger, destConn, destBuffChan, errChan) - return nil - }) - - go func() { - defer pUtil.Recover(logger, clientConn, destConn) - err := g.Wait() - if err != nil { - logger.Info("error group is returning an error", zap.Error(err)) - } - close(errChan) - }() - - prevChunkWasReq := false - logger.Debug("the iteration for the pg request starts", zap.Any("pgReqs", len(pgRequests)), zap.Any("pgResps", len(pgResponses))) - - reqTimestampMock := time.Now() - var resTimestampMock time.Time - - for { - select { - case <-ctx.Done(): - if !prevChunkWasReq && len(pgRequests) > 0 && len(pgResponses) > 0 { - metadata := make(map[string]string) - metadata["type"] = "config" - metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) - // Save the mock - mocks <- &models.Mock{ - Version: models.GetVersion(), - Name: "mocks", - Kind: models.Postgres, - Spec: models.MockSpec{ - PostgresRequests: pgRequests, - PostgresResponses: pgResponses, - ReqTimestampMock: reqTimestampMock, - ResTimestampMock: resTimestampMock, - Metadata: metadata, - }, - ConnectionID: ctx.Value(models.ClientConnectionIDKey).(string), - } - return ctx.Err() - } - case buffer := <-clientBuffChan: - - // Write the request message to the destination - _, err := destConn.Write(buffer) - if err != nil { - utils.LogError(logger, err, "failed to write request message to the destination server") - return err - } - - logger.Debug("the iteration for the pg request ends with no of pgReqs:" + strconv.Itoa(len(pgRequests)) + " and pgResps: " + strconv.Itoa(len(pgResponses))) - if !prevChunkWasReq && len(pgRequests) > 0 && len(pgResponses) > 0 { - metadata := make(map[string]string) - metadata["type"] = "config" - metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) - // Save the mock - mocks <- &models.Mock{ - Version: models.GetVersion(), - Name: "mocks", - Kind: models.Postgres, - Spec: models.MockSpec{ - PostgresRequests: pgRequests, - PostgresResponses: pgResponses, - ReqTimestampMock: reqTimestampMock, - ResTimestampMock: resTimestampMock, - Metadata: metadata, - }, - ConnectionID: ctx.Value(models.ClientConnectionIDKey).(string), - } - pgRequests = []models.Backend{} - pgResponses = []models.Frontend{} - } - - bufStr := util.EncodeBase64(buffer) - if bufStr != "" { - pg := NewBackend() - var msg pgproto3.FrontendMessage - - if !isStartupPacket(buffer) && len(buffer) > 5 { - bufferCopy := buffer - for i := 0; i < len(bufferCopy)-5; { - pg.BackendWrapper.BodyLen = int(binary.BigEndian.Uint32(buffer[i+1:])) - 4 - if len(buffer) < (i + pg.BackendWrapper.BodyLen + 5) { - utils.LogError(logger, nil, "failed to translate the postgres request message due to shorter network packet buffer. Length of buffer is "+fmt.Sprint(len(buffer))+" buffer value :"+string(buffer)+" and pg.BackendWrapper.BodyLen is "+fmt.Sprint(pg.BackendWrapper.BodyLen)) - break - } - pg.BackendWrapper.MsgType = buffer[i] - - msg, err = pg.translateToReadableBackend(buffer[i:(i + pg.BackendWrapper.BodyLen + 5)]) - if err != nil && buffer[i] != 112 { - utils.LogError(logger, err, "failed to translate the request message to readable") - } - if pg.BackendWrapper.MsgType == 'p' { - pg.BackendWrapper.PasswordMessage = *msg.(*pgproto3.PasswordMessage) - } - - if pg.BackendWrapper.MsgType == 'P' { - pg.BackendWrapper.Parse = *msg.(*pgproto3.Parse) - pg.BackendWrapper.Parses = append(pg.BackendWrapper.Parses, pg.BackendWrapper.Parse) - } - - if pg.BackendWrapper.MsgType == 'B' { - pg.BackendWrapper.Bind = *msg.(*pgproto3.Bind) - pg.BackendWrapper.Binds = append(pg.BackendWrapper.Binds, pg.BackendWrapper.Bind) - } - - if pg.BackendWrapper.MsgType == 'E' { - pg.BackendWrapper.Execute = *msg.(*pgproto3.Execute) - pg.BackendWrapper.Executes = append(pg.BackendWrapper.Executes, pg.BackendWrapper.Execute) - } - - pg.BackendWrapper.PacketTypes = append(pg.BackendWrapper.PacketTypes, string(pg.BackendWrapper.MsgType)) - - i += 5 + pg.BackendWrapper.BodyLen - } - - pgMock := &models.Backend{ - PacketTypes: pg.BackendWrapper.PacketTypes, - Identfier: "ClientRequest", - Length: uint32(len(reqBuf)), - // Payload: bufStr, - Bind: pg.BackendWrapper.Bind, - Binds: pg.BackendWrapper.Binds, - PasswordMessage: pg.BackendWrapper.PasswordMessage, - CancelRequest: pg.BackendWrapper.CancelRequest, - Close: pg.BackendWrapper.Close, - CopyData: pg.BackendWrapper.CopyData, - CopyDone: pg.BackendWrapper.CopyDone, - CopyFail: pg.BackendWrapper.CopyFail, - Describe: pg.BackendWrapper.Describe, - Execute: pg.BackendWrapper.Execute, - Executes: pg.BackendWrapper.Executes, - Flush: pg.BackendWrapper.Flush, - FunctionCall: pg.BackendWrapper.FunctionCall, - GssEncRequest: pg.BackendWrapper.GssEncRequest, - Parse: pg.BackendWrapper.Parse, - Parses: pg.BackendWrapper.Parses, - Query: pg.BackendWrapper.Query, - SSlRequest: pg.BackendWrapper.SSlRequest, - StartupMessage: pg.BackendWrapper.StartupMessage, - SASLInitialResponse: pg.BackendWrapper.SASLInitialResponse, - SASLResponse: pg.BackendWrapper.SASLResponse, - Sync: pg.BackendWrapper.Sync, - Terminate: pg.BackendWrapper.Terminate, - MsgType: pg.BackendWrapper.MsgType, - AuthType: pg.BackendWrapper.AuthType, - } - afterEncoded, err := postgresDecoderBackend(*pgMock) - if err != nil { - logger.Debug("failed to decode the response message in proxy for postgres dependency", zap.Error(err)) - } - - if len(afterEncoded) != len(buffer) && len(pgMock.PacketTypes) > 0 && pgMock.PacketTypes[0] != "p" { - logger.Debug("the length of the encoded buffer is not equal to the length of the original buffer", zap.Any("after_encoded", len(afterEncoded)), zap.Any("buffer", len(buffer))) - pgMock.Payload = bufStr - } - pgRequests = append(pgRequests, *pgMock) - - } - - if isStartupPacket(buffer) { - pgMock := &models.Backend{ - Identfier: "StartupRequest", - Payload: bufStr, - } - pgRequests = append(pgRequests, *pgMock) - } - } - prevChunkWasReq = true - case buffer := <-destBuffChan: - if prevChunkWasReq { - // store the request timestamp - reqTimestampMock = time.Now() - } - - // Write the response message to the client - _, err := clientConn.Write(buffer) - if err != nil { - utils.LogError(logger, err, "failed to write response message to the client") - return err - } - - bufStr := util.EncodeBase64(buffer) - - if bufStr != "" { - pg := NewFrontend() - if !isStartupPacket(buffer) && len(buffer) > 5 && bufStr != "Tg==" { - bufferCopy := buffer - - //Saving list of packets in case of multiple packets in a single buffer steam - ps := make([]pgproto3.ParameterStatus, 0) - var dataRows []pgproto3.DataRow - - for i := 0; i < len(bufferCopy)-5; { - pg.FrontendWrapper.MsgType = buffer[i] - pg.FrontendWrapper.BodyLen = int(binary.BigEndian.Uint32(buffer[i+1:])) - 4 - msg, err := pg.translateToReadableResponse(logger, buffer[i:(i+pg.FrontendWrapper.BodyLen+5)]) - if err != nil { - utils.LogError(logger, err, "failed to translate the response message to readable") - break - } - - pg.FrontendWrapper.PacketTypes = append(pg.FrontendWrapper.PacketTypes, string(pg.FrontendWrapper.MsgType)) - i += 5 + pg.FrontendWrapper.BodyLen - - if pg.FrontendWrapper.ParameterStatus.Name != "" { - ps = append(ps, pg.FrontendWrapper.ParameterStatus) - } - if pg.FrontendWrapper.MsgType == 'C' { - pg.FrontendWrapper.CommandComplete = *msg.(*pgproto3.CommandComplete) - // empty the command tag - pg.FrontendWrapper.CommandComplete.CommandTag = []byte{} - pg.FrontendWrapper.CommandCompletes = append(pg.FrontendWrapper.CommandCompletes, pg.FrontendWrapper.CommandComplete) - } - if pg.FrontendWrapper.MsgType == 'D' && pg.FrontendWrapper.DataRow.RowValues != nil { - // Create a new slice for each DataRow - valuesCopy := make([]string, len(pg.FrontendWrapper.DataRow.RowValues)) - copy(valuesCopy, pg.FrontendWrapper.DataRow.RowValues) - - row := pgproto3.DataRow{ - RowValues: valuesCopy, // Use the copy of the values - Values: pg.FrontendWrapper.DataRow.Values, - } - dataRows = append(dataRows, row) - } - } - - if len(ps) > 0 { - pg.FrontendWrapper.ParameterStatusCombined = ps - } - if len(dataRows) > 0 { - pg.FrontendWrapper.DataRows = dataRows - } - - // from here take the msg and append its readable form to the pgResponses - pgMock := &models.Frontend{ - PacketTypes: pg.FrontendWrapper.PacketTypes, - Identfier: "ServerResponse", - Length: uint32(len(reqBuf)), - // Payload: bufStr, - AuthenticationOk: pg.FrontendWrapper.AuthenticationOk, - AuthenticationCleartextPassword: pg.FrontendWrapper.AuthenticationCleartextPassword, - AuthenticationMD5Password: pg.FrontendWrapper.AuthenticationMD5Password, - AuthenticationGSS: pg.FrontendWrapper.AuthenticationGSS, - AuthenticationGSSContinue: pg.FrontendWrapper.AuthenticationGSSContinue, - AuthenticationSASL: pg.FrontendWrapper.AuthenticationSASL, - AuthenticationSASLContinue: pg.FrontendWrapper.AuthenticationSASLContinue, - AuthenticationSASLFinal: pg.FrontendWrapper.AuthenticationSASLFinal, - BackendKeyData: pg.FrontendWrapper.BackendKeyData, - BindComplete: pg.FrontendWrapper.BindComplete, - CloseComplete: pg.FrontendWrapper.CloseComplete, - CommandComplete: pg.FrontendWrapper.CommandComplete, - CommandCompletes: pg.FrontendWrapper.CommandCompletes, - CopyData: pg.FrontendWrapper.CopyData, - CopyDone: pg.FrontendWrapper.CopyDone, - CopyInResponse: pg.FrontendWrapper.CopyInResponse, - CopyOutResponse: pg.FrontendWrapper.CopyOutResponse, - DataRow: pg.FrontendWrapper.DataRow, - DataRows: pg.FrontendWrapper.DataRows, - EmptyQueryResponse: pg.FrontendWrapper.EmptyQueryResponse, - ErrorResponse: pg.FrontendWrapper.ErrorResponse, - FunctionCallResponse: pg.FrontendWrapper.FunctionCallResponse, - NoData: pg.FrontendWrapper.NoData, - NoticeResponse: pg.FrontendWrapper.NoticeResponse, - NotificationResponse: pg.FrontendWrapper.NotificationResponse, - ParameterDescription: pg.FrontendWrapper.ParameterDescription, - ParameterStatusCombined: pg.FrontendWrapper.ParameterStatusCombined, - ParseComplete: pg.FrontendWrapper.ParseComplete, - PortalSuspended: pg.FrontendWrapper.PortalSuspended, - ReadyForQuery: pg.FrontendWrapper.ReadyForQuery, - RowDescription: pg.FrontendWrapper.RowDescription, - MsgType: pg.FrontendWrapper.MsgType, - AuthType: pg.FrontendWrapper.AuthType, - } - - afterEncoded, err := postgresDecoderFrontend(*pgMock) - if err != nil { - logger.Debug("failed to decode the response message in proxy for postgres dependency", zap.Error(err)) - } - if len(afterEncoded) != len(buffer) && len(pgMock.PacketTypes) > 0 && pgMock.PacketTypes[0] != "R" { - logger.Debug("the length of the encoded buffer is not equal to the length of the original buffer", zap.Any("after_encoded", len(afterEncoded)), zap.Any("buffer", len(buffer))) - pgMock.Payload = bufStr - } - pgResponses = append(pgResponses, *pgMock) - } - - if bufStr == "Tg==" || len(buffer) <= 5 { - - pgMock := &models.Frontend{ - Payload: bufStr, - } - pgResponses = append(pgResponses, *pgMock) - } - } - - resTimestampMock = time.Now() - - logger.Debug("the iteration for the postgres response ends with no of postgresReqs:" + strconv.Itoa(len(pgRequests)) + " and pgResps: " + strconv.Itoa(len(pgResponses))) - prevChunkWasReq = false - case err := <-errChan: - if err == io.EOF { - return nil - } - return err - } - } -} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/match.go b/keploy/pkg/core/proxy/integrations/postgres/v1/match.go deleted file mode 100644 index ae86297..0000000 --- a/keploy/pkg/core/proxy/integrations/postgres/v1/match.go +++ /dev/null @@ -1,867 +0,0 @@ -//go:build linux - -package v1 - -import ( - "context" - "encoding/base64" - "fmt" - "math" - "reflect" - "regexp" - "strings" - "sync" - - "github.com/agnivade/levenshtein" - "github.com/jackc/pgproto3/v2" - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -var testmap TestPrepMap - -func getTestPS(reqBuff [][]byte, logger *zap.Logger, ConnectionID string) { - // maintain a map of current prepared statements and their corresponding connection id - // if it's the prepared statement match the query with the recorded prepared statement and return the response of that matched prepared statement at that connection - // so if parse is coming save to a same map - actualPgReq := decodePgRequest(reqBuff[0], logger) - if actualPgReq == nil { - return - } - testmap2 := make(TestPrepMap) - if testmap != nil { - testmap2 = testmap - } - querydata := make([]QueryData, 0) - if len(actualPgReq.PacketTypes) > 0 && actualPgReq.PacketTypes[0] != "p" && actualPgReq.Identfier != "StartupRequest" { - p := 0 - for _, header := range actualPgReq.PacketTypes { - if header == "P" { - if (strings.Contains(actualPgReq.Parses[p].Name, "S_") || strings.Contains(actualPgReq.Parses[p].Name, "s")) && !IsValuePresent(ConnectionID, actualPgReq.Parses[p].Name) { - querydata = append(querydata, QueryData{PrepIdentifier: actualPgReq.Parses[p].Name, Query: actualPgReq.Parses[p].Query}) - } - p++ - } - } - } - - // also append the query data for the prepared statement - if len(querydata) > 0 { - testmap2[ConnectionID] = append(testmap2[ConnectionID], querydata...) - // logger.Debug("Test Prepared statement Map", testmap2) - testmap = testmap2 - } - -} - -func IsValuePresent(connectionid string, value string) bool { - if testmap != nil { - for _, v := range testmap[connectionid] { - if v.PrepIdentifier == value { - return true - } - } - } - return false -} - -func matchingReadablePG(ctx context.Context, logger *zap.Logger, mutex *sync.Mutex, requestBuffers [][]byte, mockDb integrations.MockMemDb) (bool, []models.Frontend, error) { -OuterLoop: - for { - select { - case <-ctx.Done(): - return false, nil, ctx.Err() - default: - - mocks, err := mockDb.GetUnFilteredMocks() - var tcsMocks []*models.Mock - - for _, mock := range mocks { - if mock.Kind != "Postgres" { - continue - } - tcsMocks = append(tcsMocks, mock) - } - if err != nil { - return false, nil, fmt.Errorf("error while getting tcs mocks %v", err) - } - - ConnectionID := ctx.Value(models.ClientConnectionIDKey).(string) - - recordedPrep := getRecordPrepStatement(tcsMocks) - reqGoingOn := decodePgRequest(requestBuffers[0], logger) - if reqGoingOn != nil { - logger.Debug("PacketTypes", zap.Any("PacketTypes", reqGoingOn.PacketTypes)) - // fmt.Println("REQUEST GOING ON - ", reqGoingOn) - logger.Debug("ConnectionId-", zap.String("ConnectionId", ConnectionID)) - logger.Debug("TestMap*****", zap.Any("TestMap", testmap)) - } - - // merge all the streaming requests into 1 for matching - newRq := mergePgRequests(requestBuffers, logger) - if len(newRq) > 0 { - requestBuffers = newRq - } - - var sortFlag = true - var sortedTcsMocks []*models.Mock - var matchedMock *models.Mock - - for _, mock := range tcsMocks { - if ctx.Err() != nil { - return false, nil, ctx.Err() - } - if mock == nil { - continue - } - - mutex.Lock() - if sortFlag { - if !mock.TestModeInfo.IsFiltered { - sortFlag = false - } else { - sortedTcsMocks = append(sortedTcsMocks, mock) - } - } - mutex.Unlock() - - initMock := *mock - if len(initMock.Spec.PostgresRequests) == len(requestBuffers) { - for requestIndex, reqBuff := range requestBuffers { - bufStr := base64.StdEncoding.EncodeToString(reqBuff) - encodedMock, err := postgresDecoderBackend(initMock.Spec.PostgresRequests[requestIndex]) - if err != nil { - logger.Debug("Error while decoding postgres request", zap.Error(err)) - } - - switch { - case bufStr == "AAAACATSFi8=": - ssl := models.Frontend{ - Payload: "Tg==", - } - return true, []models.Frontend{ssl}, nil - case initMock.Spec.PostgresRequests[requestIndex].Identfier == "StartupRequest" && isStartupPacket(reqBuff) && initMock.Spec.PostgresRequests[requestIndex].Payload != "AAAACATSFi8=" && initMock.Spec.PostgresResponses[requestIndex].AuthType == 10: - logger.Debug("CHANGING TO MD5 for Response", zap.String("mock", initMock.Name), zap.String("Req", bufStr)) - res := make([]models.Frontend, len(initMock.Spec.PostgresResponses)) - copy(res, initMock.Spec.PostgresResponses) - res[requestIndex].AuthType = 5 - newInitMock := initMock - newInitMock.TestModeInfo.IsFiltered = false - newInitMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() - isUpdated := mockDb.UpdateUnFilteredMock(&initMock, &newInitMock) - if !isUpdated { - logger.Debug("failed to update matched mock", zap.Error(err)) - continue OuterLoop - } - return true, res, nil - case len(encodedMock) > 0 && encodedMock[0] == 'p' && initMock.Spec.PostgresRequests[requestIndex].PacketTypes[0] == "p" && reqBuff[0] == 'p': - logger.Debug("CHANGING TO MD5 for Request and Response", zap.String("mock", initMock.Name), zap.String("Req", bufStr)) - - res := make([]models.Frontend, len(initMock.Spec.PostgresResponses)) - copy(res, initMock.Spec.PostgresResponses) - res[requestIndex].PacketTypes = []string{"R", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "K", "Z"} - res[requestIndex].AuthType = 0 - res[requestIndex].BackendKeyData = pgproto3.BackendKeyData{ - ProcessID: 2613, - SecretKey: 824670820, - } - res[requestIndex].ReadyForQuery.TxStatus = 73 - res[requestIndex].ParameterStatusCombined = []pgproto3.ParameterStatus{ - { - Name: "application_name", - Value: "", - }, - { - Name: "client_encoding", - Value: "UTF8", - }, - { - Name: "DateStyle", - Value: "ISO, MDY", - }, - { - Name: "integer_datetimes", - Value: "on", - }, - { - Name: "IntervalStyle", - Value: "postgres", - }, - { - Name: "is_superuser", - Value: "UTF8", - }, - { - Name: "server_version", - Value: "13.12 (Debian 13.12-1.pgdg120+1)", - }, - { - Name: "session_authorization", - Value: "keploy-user", - }, - { - Name: "standard_conforming_strings", - Value: "on", - }, - { - Name: "TimeZone", - Value: "Etc/UTC", - }, - { - Name: "TimeZone", - Value: "Etc/UTC", - }, - } - newInitMock := initMock - newInitMock.TestModeInfo.IsFiltered = false - newInitMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() - isUpdated := mockDb.UpdateUnFilteredMock(&initMock, &newInitMock) - if !isUpdated { - logger.Debug("failed to update matched mock", zap.Error(err)) - continue OuterLoop - } - return true, res, nil - } - - } - } - - // maintain test prepare statement map for each connection id - getTestPS(requestBuffers, logger, ConnectionID) - } - - logger.Debug("Sorted Mocks inside pg parser: ", zap.Any("Len of sortedTcsMocks", len(sortedTcsMocks))) - - var matched, sorted bool - var idx int - //use findBinaryMatch twice one for sorted and another for unsorted - // give more priority to sorted like if you find more than 0.5 in sorted then return that - if len(sortedTcsMocks) > 0 { - sorted = true - idx1, newMock := findPGStreamMatch(sortedTcsMocks, requestBuffers, logger, sorted, ConnectionID, recordedPrep) - if idx1 != -1 { - matched = true - matchedMock = tcsMocks[idx1] - if newMock != nil { - matchedMock = newMock - } - logger.Debug("Matched In Sorted PG Matching Stream", zap.String("mock", matchedMock.Name)) - } - - if !matched { - idx = findBinaryStreamMatch(logger, sortedTcsMocks, requestBuffers, sorted) - if idx != -1 { - matched = true - matchedMock = tcsMocks[idx] - } - } - } - - if !matched { - sorted = false - idx1, newMock := findPGStreamMatch(tcsMocks, requestBuffers, logger, sorted, ConnectionID, recordedPrep) - if idx1 != -1 { - matched = true - matchedMock = tcsMocks[idx1] - if newMock != nil { - matchedMock = newMock - } - logger.Debug("Matched In Unsorted PG Matching Stream", zap.String("mock", matchedMock.Name)) - } - if !matched { - idx = findBinaryStreamMatch(logger, tcsMocks, requestBuffers, sorted) - // check if the validate the query with the matched mock - // if the query is same then return the response of that mock - var isValid = true - if idx != -1 && len(sortedTcsMocks) != 0 { - isValid, newMock = validateMock(tcsMocks, idx, requestBuffers, logger) - logger.Debug("Is Valid", zap.Bool("Is Valid", isValid)) - } - if idx != -1 { - matched = true - matchedMock = tcsMocks[idx] - if newMock != nil && !isValid { - matchedMock = newMock - } - logger.Debug("Matched In Binary Matching for Unsorted", zap.String("mock", matchedMock.Name)) - } - - } - } - - if matched { - logger.Debug("Matched mock", zap.String("mock", matchedMock.Name)) - originalMatchedMock := *matchedMock - matchedMock.TestModeInfo.IsFiltered = false - matchedMock.TestModeInfo.SortOrder = pkg.GetNextSortNum() - updated := mockDb.UpdateUnFilteredMock(&originalMatchedMock, matchedMock) - if !updated { - logger.Debug("failed to update matched mock", zap.Error(err)) - } - return true, matchedMock.Spec.PostgresResponses, nil - } - return false, nil, nil - } - } -} - -func findBinaryStreamMatch(logger *zap.Logger, tcsMocks []*models.Mock, requestBuffers [][]byte, sorted bool) int { - mxSim := -1.0 - mxIdx := -1 - - for idx, mock := range tcsMocks { - // merging the mocks as well before comparing - mock.Spec.PostgresRequests = mergeMocks(mock.Spec.PostgresRequests, logger) - - if len(mock.Spec.PostgresRequests) == len(requestBuffers) { - for requestIndex, reqBuf := range requestBuffers { - - expectedPgReq := mock.Spec.PostgresRequests[requestIndex] - encoded, err := postgresDecoderBackend(expectedPgReq) - if err != nil { - logger.Debug("Error while decoding postgres request", zap.Error(err)) - } - var encoded64 []byte - if expectedPgReq.Payload != "" { - encoded64, err = util.DecodeBase64(mock.Spec.PostgresRequests[requestIndex].Payload) - if err != nil { - logger.Debug("Error while decoding postgres request", zap.Error(err)) - return -1 - } - } - var similarity1, similarity2 float64 - if len(encoded) > 0 { - similarity1 = fuzzyCheck(encoded, reqBuf) - } - if len(encoded64) > 0 { - similarity2 = fuzzyCheck(encoded64, reqBuf) - } - - // calculate the jaccard similarity between the two buffers one with base64 encoding and another via that - //find the max similarity between the two - similarity := math.Max(similarity1, similarity2) - if mxSim < similarity { - mxSim = similarity - mxIdx = idx - continue - } - } - } - } - - if sorted { - if mxIdx != -1 && mxSim >= 0.78 { - logger.Debug("Matched with Sorted Stream", zap.Float64("similarity", mxSim)) - } else { - mxIdx = -1 - } - } else { - if mxIdx != -1 { - logger.Debug("Matched with Unsorted Stream", zap.Float64("similarity", mxSim)) - } - } - return mxIdx -} - -func fuzzyCheck(encoded, reqBuf []byte) float64 { - k := util.AdaptiveK(len(reqBuf), 3, 8, 5) - shingles1 := util.CreateShingles(encoded, k) - shingles2 := util.CreateShingles(reqBuf, k) - similarity := util.JaccardSimilarity(shingles1, shingles2) - return similarity -} - -func findPGStreamMatch(tcsMocks []*models.Mock, requestBuffers [][]byte, logger *zap.Logger, isSorted bool, connectionID string, recordedPrep PrepMap) (int, *models.Mock) { - - mxIdx := -1 - - match := false - // loop for the exact match of the request - for idx, mock := range tcsMocks { - // merging the mocks as well before comparing - mock.Spec.PostgresRequests = mergeMocks(mock.Spec.PostgresRequests, logger) - - if len(mock.Spec.PostgresRequests) == len(requestBuffers) { - for _, reqBuff := range requestBuffers { - actualPgReq := decodePgRequest(reqBuff, logger) - if actualPgReq == nil { - return -1, nil - } - // here handle cases of prepared statement very carefully - match, err := compareExactMatch(mock, actualPgReq, logger) - if err != nil { - logger.Error("Error while matching exact match", zap.Error(err)) - continue - } - if match { - return idx, nil - } - } - } - } - if !isSorted { - return mxIdx, nil - } - // loop for the ps match of the request - if !match { - for idx, mock := range tcsMocks { - // merging the mocks as well before comparing - mock.Spec.PostgresRequests = mergeMocks(mock.Spec.PostgresRequests, logger) - - if len(mock.Spec.PostgresRequests) == len(requestBuffers) { - for _, reqBuff := range requestBuffers { - actualPgReq := decodePgRequest(reqBuff, logger) - if actualPgReq == nil { - return -1, nil - } - // just matching the corresponding PS in this case there is no need to edit the mock - match, newBindPs, err := PreparedStatementMatch(mock, actualPgReq, logger, connectionID, recordedPrep) - if err != nil { - logger.Error("Error while matching prepared statements", zap.Error(err)) - } - - if match { - logger.Debug("New Bind Prepared Statement", zap.Any("New Bind Prepared Statement", newBindPs), zap.String("ConnectionId", connectionID), zap.String("Mock Name", mock.Name)) - return idx, nil - } - // just check the query - if reflect.DeepEqual(actualPgReq.PacketTypes, []string{"P", "B", "D", "E"}) && reflect.DeepEqual(mock.Spec.PostgresRequests[0].PacketTypes, []string{"P", "B", "D", "E"}) { - if mock.Spec.PostgresRequests[0].Parses[0].Query == actualPgReq.Parses[0].Query { - return idx, nil - } - } - } - } - } - } - - if !match { - - for idx, mock := range tcsMocks { - // merging the mocks as well before comparing - mock.Spec.PostgresRequests = mergeMocks(mock.Spec.PostgresRequests, logger) - - if len(mock.Spec.PostgresRequests) == len(requestBuffers) { - for _, reqBuff := range requestBuffers { - actualPgReq := decodePgRequest(reqBuff, logger) - if actualPgReq == nil { - return -1, nil - } - - // have to ignore first parse message of begin read only - // should compare only query in the parse message - if len(actualPgReq.PacketTypes) != len(mock.Spec.PostgresRequests[0].PacketTypes) { - //check for begin read only - if len(actualPgReq.PacketTypes) > 0 && len(mock.Spec.PostgresRequests[0].PacketTypes) > 0 { - - ischanged, newMock := changeResToPS(mock, actualPgReq, logger, connectionID) - - if ischanged { - return idx, newMock - } - continue - - } - - } - } - } - } - } - - return mxIdx, nil -} - -// check what are the queries for the given ps of actualPgReq -// check if the execute query is present for that or not -// mark that mock true and return the response by changing the res format like -// postgres data types acc to result set format -func changeResToPS(mock *models.Mock, actualPgReq *models.Backend, logger *zap.Logger, connectionID string) (bool, *models.Mock) { - actualpackets := actualPgReq.PacketTypes - mockPackets := mock.Spec.PostgresRequests[0].PacketTypes - - // [P, B, E, P, B, D, E] => [B, E, B, E] - // write code that of packet is ["B", "E"] and mockPackets ["P", "B", "D", "E"] handle it in case1 - // and if packet is [B, E, B, E] and mockPackets [P, B, E, P, B, D, E] handle it in case2 - - ischanged := false - var newMock *models.Mock - // [B E P D B E] - // [P, B, E, P, B, D, E] -> [B, E, P, B, D, E] - if (reflect.DeepEqual(actualpackets, []string{"B", "E", "P", "D", "B", "E"}) || reflect.DeepEqual(actualpackets, []string{"B", "E", "P", "B", "D", "E"})) && reflect.DeepEqual(mockPackets, []string{"P", "B", "E", "P", "B", "D", "E"}) { - // logger.Debug("Handling Case 1 for mock", mock.Name) - // handleCase1(packets, mockPackets) - // also check if the second query is same or not - // logger.Debug("ActualPgReq", actualPgReq.Parses[0].Query, "MOCK REQ 1", mock.Spec.PostgresRequests[0].Parses[0].Query, "MOCK REQ 2", mock.Spec.PostgresRequests[0].Parses[1].Query) - if actualPgReq.Parses[0].Query != mock.Spec.PostgresRequests[0].Parses[1].Query { - return false, nil - } - newMock = sliceCommandTag(mock, logger, testmap[connectionID], actualPgReq, 1) - return true, newMock - } - - // case 2 - var ps string - if reflect.DeepEqual(actualpackets, []string{"B", "E"}) && reflect.DeepEqual(mockPackets, []string{"P", "B", "D", "E"}) { - // logger.Debug("Handling Case 2 for mock", mock.Name) - ps = actualPgReq.Binds[0].PreparedStatement - for _, v := range testmap[connectionID] { - if v.Query == mock.Spec.PostgresRequests[0].Parses[0].Query && v.PrepIdentifier == ps { - ischanged = true - break - } - } - } - - if ischanged { - // if strings.Contains(ps, "S_") { - // logger.Debug("Inside Prepared Statement") - newMock = sliceCommandTag(mock, logger, testmap[connectionID], actualPgReq, 2) - // } - return true, newMock - } - - // packets = []string{"B", "E", "B", "E"} - // mockPackets = []string{"P", "B", "E", "P", "B", "D", "E"} - - // Case 3 - if reflect.DeepEqual(actualpackets, []string{"B", "E", "B", "E"}) && reflect.DeepEqual(mockPackets, []string{"P", "B", "E", "P", "B", "D", "E"}) { - // logger.Debug("Handling Case 3 for mock", mock.Name) - ischanged1 := false - ps1 := actualPgReq.Binds[0].PreparedStatement - for _, v := range testmap[connectionID] { - if v.Query == mock.Spec.PostgresRequests[0].Parses[0].Query && v.PrepIdentifier == ps1 { - ischanged1 = true - break - } - } - //Matched In Binary Matching for Unsorted mock-222 - ischanged2 := false - ps2 := actualPgReq.Binds[1].PreparedStatement - for _, v := range testmap[connectionID] { - if v.Query == mock.Spec.PostgresRequests[0].Parses[1].Query && v.PrepIdentifier == ps2 { - ischanged2 = true - break - } - } - if ischanged1 && ischanged2 { - newMock = sliceCommandTag(mock, logger, testmap[connectionID], actualPgReq, 2) - return true, newMock - } - } - - // Case 4 - if reflect.DeepEqual(actualpackets, []string{"B", "E", "B", "E"}) && reflect.DeepEqual(mockPackets, []string{"B", "E", "P", "B", "D", "E"}) { - // logger.Debug("Handling Case 4 for mock", mock.Name) - // get the query for the prepared statement of test mode - ischanged := false - ps := actualPgReq.Binds[1].PreparedStatement - for _, v := range testmap[connectionID] { - if v.Query == mock.Spec.PostgresRequests[0].Parses[0].Query && v.PrepIdentifier == ps { - ischanged = true - break - } - } - if ischanged { - newMock = sliceCommandTag(mock, logger, testmap[connectionID], actualPgReq, 2) - return true, newMock - } - - } - - return false, nil - -} - -func PreparedStatementMatch(mock *models.Mock, actualPgReq *models.Backend, logger *zap.Logger, ConnectionID string, recordedPrep PrepMap) (bool, []string, error) { - // logger.Debug("Inside PreparedStatementMatch") - - if !reflect.DeepEqual(mock.Spec.PostgresRequests[0].PacketTypes, actualPgReq.PacketTypes) { - logger.Debug("mock and actual packet types are unequal", zap.Any("mock name", mock.Name)) - return false, nil, nil - } - - // get all the binds from the actualPgReq - binds := actualPgReq.Binds - newBinPreparedStatement := make([]string, 0) - mockBinds := mock.Spec.PostgresRequests[0].Binds - // If the client sent a different number of Bind messages than the mock - // recorded, the two batches can’t possibly align, so we can return early - // instead of walking the loop and risking panic due to out‑of‑bounds. - if len(binds) != len(mockBinds) { - logger.Debug("len of binds in actual request is not equal to len of binds in mock", zap.String("mock name", mock.Name)) - return false, nil, nil - } - mockConn := mock.ConnectionID - var foo = false - for idx, bind := range binds { - currentPs := bind.PreparedStatement - currentQuerydata := testmap[ConnectionID] - currentQuery := "" - // check in the map that what's the current query for this preparedstatement - // then will check what is the recorded prepared statement for this query - for _, v := range currentQuerydata { - if v.PrepIdentifier == currentPs { - // logger.Debug("Current query for this identifier is ", v.Query) - currentQuery = v.Query - break - } - } - - // this means that the bind is preceeded by a parse with name field empty - // we can say that the name field (identifier) was empty that's why it didn't get inserted in testMap. - // skip it, as it doesn't use already cached query, instead parsing followed by binding is done in the same query. - if currentQuery == "" { - continue - } - - logger.Debug("Current Query for this prepared statement", zap.String("Query", currentQuery), zap.String("Identifier", currentPs)) - foo = false - - // check if the query for mock ps (v.PreparedStatement) is same as the current query - for _, querydata := range recordedPrep[mockConn] { - if querydata.Query == currentQuery && mockBinds[idx].PreparedStatement == querydata.PrepIdentifier { - logger.Debug("Matched with the recorded prepared statement with Identifier and connectionID is", zap.String("Identifier", querydata.PrepIdentifier), zap.String("ConnectionId", mockConn), zap.String("Current Identifier", currentPs), zap.String("Query", currentQuery)) - foo = true - break - } - - } - // this means we are unable to find the query in recordedPrep or the prepared statement is not same - if !foo { - break - } - } - if !foo { - return false, nil, nil - } - - parses := actualPgReq.Parses - mockParses := mock.Spec.PostgresRequests[0].Parses - // If the client sent a different number of Parse messages than the mock - // recorded, the two batches can’t possibly align, so we can return early - // instead of walking the loop and risking panic due to out‑of‑bounds. - if len(parses) != len(mockParses) { - logger.Debug("len of parse in actual request is not equal to len of parse in mock", zap.String("mock name", mock.Name)) - return false, nil, nil - } - - foo = true - // check if all parse queries in pg request is same for the corresponding query in mock - for idx, parse := range parses { - if parse.Query != mockParses[idx].Query { - logger.Debug(fmt.Sprintf("parse query for actual request is not equal to parse query for mock name: %s, at index: %d", mock.Name, idx)) - foo = false // if any parse query is not same then break, mock didn't match - break - } - } - if foo { - return true, newBinPreparedStatement, nil - } - - return false, nil, nil -} - -func compareExactMatch(mock *models.Mock, actualPgReq *models.Backend, logger *zap.Logger) (bool, error) { - logger.Debug("Inside CompareExactMatch") - // have to ignore first parse message of begin read only - // should compare only query in the parse message - if len(actualPgReq.PacketTypes) != len(mock.Spec.PostgresRequests[0].PacketTypes) { - return false, nil - } - - // call a separate function for matching prepared statements - for idx, v := range actualPgReq.PacketTypes { - if v != mock.Spec.PostgresRequests[0].PacketTypes[idx] { - return false, nil - } - } - // IsPreparedStatement(mock, actualPgReq, logger, ConnectionId) - - // this will give me the - var ( - p, b, e = 0, 0, 0 - ) - for i := 0; i < len(actualPgReq.PacketTypes); i++ { - switch actualPgReq.PacketTypes[i] { - case "P": - // logger.Debug("Inside P") - p++ - if actualPgReq.Parses[p-1].Query != mock.Spec.PostgresRequests[0].Parses[p-1].Query { - return false, nil - } - - if actualPgReq.Parses[p-1].Name != mock.Spec.PostgresRequests[0].Parses[p-1].Name { - return false, nil - } - - if len(actualPgReq.Parses[p-1].ParameterOIDs) != len(mock.Spec.PostgresRequests[0].Parses[p-1].ParameterOIDs) { - return false, nil - } - for j := 0; j < len(actualPgReq.Parses[p-1].ParameterOIDs); j++ { - if actualPgReq.Parses[p-1].ParameterOIDs[j] != mock.Spec.PostgresRequests[0].Parses[p-1].ParameterOIDs[j] { - return false, nil - } - } - - case "B": - // logger.Debug("Inside B") - b++ - if actualPgReq.Binds[b-1].DestinationPortal != mock.Spec.PostgresRequests[0].Binds[b-1].DestinationPortal { - return false, nil - } - - if actualPgReq.Binds[b-1].PreparedStatement != mock.Spec.PostgresRequests[0].Binds[b-1].PreparedStatement { - return false, nil - } - - if len(actualPgReq.Binds[b-1].ParameterFormatCodes) != len(mock.Spec.PostgresRequests[0].Binds[b-1].ParameterFormatCodes) { - return false, nil - } - for j := 0; j < len(actualPgReq.Binds[b-1].ParameterFormatCodes); j++ { - if actualPgReq.Binds[b-1].ParameterFormatCodes[j] != mock.Spec.PostgresRequests[0].Binds[b-1].ParameterFormatCodes[j] { - return false, nil - } - } - if len(actualPgReq.Binds[b-1].Parameters) != len(mock.Spec.PostgresRequests[0].Binds[b-1].Parameters) { - return false, nil - } - for j := 0; j < len(actualPgReq.Binds[b-1].Parameters); j++ { - // parameter represents a timestamp value do not compare it just continue - if isTimestamp(actualPgReq.Binds[b-1].Parameters[j]) { - logger.Debug("found a timestamp value") - continue - } - if isBcryptHash(actualPgReq.Binds[b-1].Parameters[j]) { - logger.Debug("found a bcrypt hash") - continue - } - for i, v := range actualPgReq.Binds[b-1].Parameters[j] { - if v != mock.Spec.PostgresRequests[0].Binds[b-1].Parameters[j][i] { - return false, nil - } - } - } - if len(actualPgReq.Binds[b-1].ResultFormatCodes) != len(mock.Spec.PostgresRequests[0].Binds[b-1].ResultFormatCodes) { - return false, nil - } - for j := 0; j < len(actualPgReq.Binds[b-1].ResultFormatCodes); j++ { - if actualPgReq.Binds[b-1].ResultFormatCodes[j] != mock.Spec.PostgresRequests[0].Binds[b-1].ResultFormatCodes[j] { - return false, nil - } - } - - case "E": - // logger.Debug("Inside E") - e++ - if actualPgReq.Executes[e-1].Portal != mock.Spec.PostgresRequests[0].Executes[e-1].Portal { - return false, nil - } - if actualPgReq.Executes[e-1].MaxRows != mock.Spec.PostgresRequests[0].Executes[e-1].MaxRows { - return false, nil - } - - case "c": - if actualPgReq.CopyDone != mock.Spec.PostgresRequests[0].CopyDone { - return false, nil - } - case "H": - if actualPgReq.CopyFail.Message != mock.Spec.PostgresRequests[0].CopyFail.Message { - return false, nil - } - case "Q": - if actualPgReq.Query.String != mock.Spec.PostgresRequests[0].Query.String { - if LaevensteinDistance(actualPgReq.Query.String, mock.Spec.PostgresRequests[0].Query.String) { - logger.Debug("The strings are more than 90%% similar.") - } - - return false, nil - } - default: - return false, nil - } - } - return true, nil -} - -func LaevensteinDistance(str1, str2 string) bool { - // Compute the Levenshtein distance - distance := levenshtein.ComputeDistance(str1, str2) - maxLength := max(len(str1), len(str2)) - similarity := (1 - float64(distance)/float64(maxLength)) * 100 - - // Check if similarity is greater than 90% - return similarity > 90 - -} - -// make this in such a way if it returns -1 then we will continue with the original mock -func validateMock(tcsMocks []*models.Mock, idx int, requestBuffers [][]byte, logger *zap.Logger) (bool, *models.Mock) { - - actualPgReq := decodePgRequest(requestBuffers[0], logger) - if actualPgReq == nil { - return true, nil - } - mock := tcsMocks[idx].Spec.PostgresRequests[0] - if len(mock.PacketTypes) == len(actualPgReq.PacketTypes) { - if reflect.DeepEqual(tcsMocks[idx].Spec.PostgresRequests[0].PacketTypes, []string{"B", "E", "P", "B", "D", "E"}) { - if mock.Parses[0].Query == actualPgReq.Parses[0].Query { - return true, nil - } - } - if reflect.DeepEqual(mock.PacketTypes, []string{"B", "E", "B", "E"}) { - // logger.Debug("Inside Validate Mock for B, E, B, E") - return true, nil - } - if reflect.DeepEqual(mock.PacketTypes, []string{"B", "E"}) { - // logger.Debug("Inside Validate Mock for B, E") - copyMock := *tcsMocks[idx] - copyMock.Spec.PostgresResponses[0].PacketTypes = []string{"2", "C", "Z"} - copyMock.Spec.PostgresResponses[0].Payload = "" - return false, ©Mock - } - if reflect.DeepEqual(mock.PacketTypes, []string{"P", "B", "D", "E"}) { - // logger.Debug("Inside Validate Mock for P, B, D, E") - copyMock := *tcsMocks[idx] - copyMock.Spec.PostgresResponses[0].PacketTypes = []string{"1", "2", "T", "C", "Z"} - copyMock.Spec.PostgresResponses[0].Payload = "" - return false, ©Mock - } - } else { - // [B, E, P, B, D, E] => [ P, B, D, E] - if reflect.DeepEqual(mock.PacketTypes, []string{"B", "E", "P", "B", "D", "E"}) && reflect.DeepEqual(actualPgReq.PacketTypes, []string{"P", "B", "D", "E"}) { - // logger.Debug("Inside Validate Mock for B, E, B, E") - if mock.Parses[0].Query == actualPgReq.Parses[0].Query { - // no need to do anything - - copyMock := *tcsMocks[idx] - copyMock.Spec.PostgresResponses[0].PacketTypes = []string{"1", "2", "T", "C", "Z"} - copyMock.Spec.PostgresResponses[0].Payload = "" - copyMock.Spec.PostgresResponses[0].CommandCompletes = copyMock.Spec.PostgresResponses[0].CommandCompletes[1:] - return false, ©Mock - } - } - } - return true, nil -} - -func isTimestamp(byteArray []byte) bool { - // Convert byte array to string - s := string(byteArray) - - // Define a regex for ISO 8601 timestamps - timestampRegex := regexp.MustCompile(`\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(.\d+)?(Z)?`) - return timestampRegex.MatchString(s) -} - -func isBcryptHash(byteArray []byte) bool { - // Convert byte array to string - s := string(byteArray) - - // Define a regex for bcrypt hashes - bcryptRegex := regexp.MustCompile(`^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$`) - return bcryptRegex.MatchString(s) -} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/postgres.go b/keploy/pkg/core/proxy/integrations/postgres/v1/postgres.go deleted file mode 100755 index 20a2a54..0000000 --- a/keploy/pkg/core/proxy/integrations/postgres/v1/postgres.go +++ /dev/null @@ -1,87 +0,0 @@ -//go:build linux - -package v1 - -import ( - "context" - "encoding/binary" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/utils" - - "go.keploy.io/server/v2/pkg/models" - - "go.uber.org/zap" -) - -func init() { - integrations.Register(integrations.POSTGRES_V1, &integrations.Parsers{ - Initializer: New, - Priority: 100, - }) -} - -type PostgresV1 struct { - logger *zap.Logger -} - -func New(logger *zap.Logger) integrations.Integrations { - return &PostgresV1{ - logger: logger, - } -} - -// MatchType determines if the outgoing network call is Postgres by comparing the -// message format with that of a Postgres text message. -func (p *PostgresV1) MatchType(_ context.Context, reqBuf []byte) bool { - const ProtocolVersion = 0x00030000 // Protocol version 3.0 - - if len(reqBuf) < 8 { - // Not enough data for a complete header - return false - } - - // The first four bytes are the message length, but we don't need to check those - // The next four bytes are the protocol version - version := binary.BigEndian.Uint32(reqBuf[4:8]) - if version == 80877103 { - return true - } - return version == ProtocolVersion -} - -func (p *PostgresV1) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - logger := p.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial postgres message") - return err - } - err = encodePostgres(ctx, logger, reqBuf, src, dst, mocks, opts) - if err != nil { - // TODO: why debug log? - logger.Debug("failed to encode the postgres message into the yaml") - return err - } - return nil - -} - -func (p *PostgresV1) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { - logger := p.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial postgres message") - return err - } - - err = decodePostgres(ctx, logger, reqBuf, src, dstCfg, mockDb, opts) - if err != nil { - logger.Debug("failed to decode the postgres message from the yaml") - return err - } - return nil -} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/transcoder.go b/keploy/pkg/core/proxy/integrations/postgres/v1/transcoder.go deleted file mode 100644 index 9e1ce08..0000000 --- a/keploy/pkg/core/proxy/integrations/postgres/v1/transcoder.go +++ /dev/null @@ -1,313 +0,0 @@ -package v1 - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - - "github.com/jackc/pgproto3/v2" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type BackendWrapper struct { - BackendWrapper models.Backend -} - -type FrontendWrapper struct { - FrontendWrapper models.Frontend -} - -func NewBackend() *BackendWrapper { - return &BackendWrapper{} -} - -func NewFrontend() *FrontendWrapper { - return &FrontendWrapper{} -} - -// PG Response Packet Transcoder -func (b *BackendWrapper) translateToReadableBackend(msgBody []byte) (pgproto3.FrontendMessage, error) { - // fmt.Println("msgType", b.BackendWrapper.MsgType) - var msg pgproto3.FrontendMessage - switch b.BackendWrapper.MsgType { - case 'B': - msg = &b.BackendWrapper.Bind - case 'C': - msg = &b.BackendWrapper.Close - case 'D': - msg = &b.BackendWrapper.Describe - case 'E': - msg = &b.BackendWrapper.Execute - case 'F': - msg = &b.BackendWrapper.FunctionCall - case 'f': - msg = &b.BackendWrapper.CopyFail - case 'd': - msg = &b.BackendWrapper.CopyData - case 'c': - msg = &b.BackendWrapper.CopyDone - case 'H': - msg = &b.BackendWrapper.Flush - case 'P': - msg = &b.BackendWrapper.Parse - case 'p': - switch b.BackendWrapper.AuthType { - case pgproto3.AuthTypeSASL: - msg = &pgproto3.SASLInitialResponse{} - case pgproto3.AuthTypeSASLContinue: - msg = &pgproto3.SASLResponse{} - case pgproto3.AuthTypeSASLFinal: - msg = &pgproto3.SASLResponse{} - case pgproto3.AuthTypeGSS, pgproto3.AuthTypeGSSCont: - msg = &pgproto3.GSSResponse{} - case pgproto3.AuthTypeCleartextPassword, pgproto3.AuthTypeMD5Password: - fallthrough - default: - // to maintain backwards compatability - msg = &pgproto3.PasswordMessage{} - } - case 'Q': - msg = &b.BackendWrapper.Query - case 'S': - msg = &b.BackendWrapper.Sync - case 'X': - msg = &b.BackendWrapper.Terminate - default: - return nil, fmt.Errorf("unknown message type: %c", b.BackendWrapper.MsgType) - } - err := msg.Decode(msgBody[5:]) - if b.BackendWrapper.MsgType == 'P' { - *msg.(*pgproto3.Parse) = b.BackendWrapper.Parse - } - - return msg, err -} - -func (f *FrontendWrapper) translateToReadableResponse(logger *zap.Logger, msgBody []byte) (pgproto3.BackendMessage, error) { - f.FrontendWrapper.BodyLen = int(binary.BigEndian.Uint32(msgBody[1:])) - 4 - f.FrontendWrapper.MsgType = msgBody[0] - var msg pgproto3.BackendMessage - switch f.FrontendWrapper.MsgType { - case '1': - msg = &f.FrontendWrapper.ParseComplete - case '2': - msg = &f.FrontendWrapper.BindComplete - case '3': - msg = &f.FrontendWrapper.CloseComplete - case 'A': - msg = &f.FrontendWrapper.NotificationResponse - case 'c': - msg = &f.FrontendWrapper.CopyDone - case 'C': - msg = &f.FrontendWrapper.CommandComplete - case 'd': - msg = &f.FrontendWrapper.CopyData - case 'D': - msg = &f.FrontendWrapper.DataRow - logger.Debug("Data Row", zap.String("data", string(msgBody))) - case 'E': - msg = &f.FrontendWrapper.ErrorResponse - case 'G': - msg = &f.FrontendWrapper.CopyInResponse - case 'H': - msg = &f.FrontendWrapper.CopyOutResponse - case 'I': - msg = &f.FrontendWrapper.EmptyQueryResponse - case 'K': - msg = &f.FrontendWrapper.BackendKeyData - case 'n': - msg = &f.FrontendWrapper.NoData - case 'N': - msg = &f.FrontendWrapper.NoticeResponse - case 'R': - var err error - msg, err = f.findAuthMsgType(msgBody) - if err != nil { - return nil, err - } - case 's': - msg = &f.FrontendWrapper.PortalSuspended - case 'S': - msg = &f.FrontendWrapper.ParameterStatus - case 't': - msg = &f.FrontendWrapper.ParameterDescription - case 'T': - msg = &f.FrontendWrapper.RowDescription - case 'V': - msg = &f.FrontendWrapper.FunctionCallResponse - case 'W': - msg = &f.FrontendWrapper.CopyBothResponse - case 'Z': - msg = &f.FrontendWrapper.ReadyForQuery - default: - return nil, fmt.Errorf("unknown message type: %c", f.FrontendWrapper.MsgType) - } - - logger.Debug("msgFrontend", zap.String("msgFrontend", string(msgBody))) - - err := msg.Decode(msgBody[5:]) - if err != nil { - utils.LogError(logger, err, "Error from decoding request message") - } - - bits := msg.Encode([]byte{}) - // println("Length of bits", len(bits), "Length of msgBody", len(msgBody)) - if len(bits) != len(msgBody) { - logger.Debug("Encoded Data doesn't match the original data ..") - } - - return msg, err -} - -const ( - minStartupPacketLen = 4 // minStartupPacketLen is a single 32-bit int version or code. - maxStartupPacketLen = 10000 // maxStartupPacketLen is MAX_STARTUP_PACKET_LENGTH from PG source. - sslRequestNumber = 80877103 - cancelRequestCode = 80877102 - gssEncReqNumber = 80877104 -) - -// ProtocolVersionNumber Replace with actual version number if different -const ProtocolVersionNumber uint32 = 196608 - -func (b *BackendWrapper) decodeStartupMessage(buf []byte) (pgproto3.FrontendMessage, error) { - - reader := pgproto3.NewByteReader(buf) - buf, err := reader.Next(4) - - if err != nil { - return nil, err - } - msgSize := int(binary.BigEndian.Uint32(buf) - 4) - - if msgSize < minStartupPacketLen || msgSize > maxStartupPacketLen { - return nil, fmt.Errorf("invalid length of startup packet: %d", msgSize) - } - - buf, err = reader.Next(msgSize) - if err != nil { - return nil, fmt.Errorf("invalid length of startup packet: %d", msgSize) - } - - code := binary.BigEndian.Uint32(buf) - - switch code { - case ProtocolVersionNumber: - err := b.BackendWrapper.StartupMessage.Decode(buf) - if err != nil { - return nil, err - } - return &b.BackendWrapper.StartupMessage, nil - case sslRequestNumber: - err := b.BackendWrapper.SSlRequest.Decode(buf) - if err != nil { - return nil, err - } - return &b.BackendWrapper.SSlRequest, nil - case cancelRequestCode: - err := b.BackendWrapper.CancelRequest.Decode(buf) - if err != nil { - return nil, err - } - return &b.BackendWrapper.CancelRequest, nil - case gssEncReqNumber: - err := b.BackendWrapper.GssEncRequest.Decode(buf) - if err != nil { - return nil, err - } - return &b.BackendWrapper.GssEncRequest, nil - default: - return nil, fmt.Errorf("unknown startup message code: %d", code) - } -} - -// constants for the authentication message types -const ( - AuthTypeOk = 0 - AuthTypeCleartextPassword = 3 - AuthTypeMD5Password = 5 - AuthTypeSCMCreds = 6 - AuthTypeGSS = 7 - AuthTypeGSSCont = 8 - AuthTypeSSPI = 9 - AuthTypeSASL = 10 - AuthTypeSASLContinue = 11 - AuthTypeSASLFinal = 12 -) - -func (f *FrontendWrapper) findAuthMsgType(src []byte) (pgproto3.BackendMessage, error) { - if len(src) < 4 { - return nil, errors.New("authentication message too short") - } - - authType, err := parseAuthType(src) - if err != nil { - return nil, err - - } - - f.FrontendWrapper.AuthType = authType - switch f.FrontendWrapper.AuthType { - case pgproto3.AuthTypeOk: - return &f.FrontendWrapper.AuthenticationOk, nil - case pgproto3.AuthTypeCleartextPassword: - return &f.FrontendWrapper.AuthenticationCleartextPassword, nil - case pgproto3.AuthTypeMD5Password: - return &f.FrontendWrapper.AuthenticationMD5Password, nil - case pgproto3.AuthTypeSCMCreds: - return nil, errors.New("AuthTypeSCMCreds is unimplemented") - case pgproto3.AuthTypeGSS: - return &f.FrontendWrapper.AuthenticationGSS, nil - case pgproto3.AuthTypeGSSCont: - return &f.FrontendWrapper.AuthenticationGSSContinue, nil - case pgproto3.AuthTypeSSPI: - return nil, errors.New("AuthTypeSSPI is unimplemented") - case pgproto3.AuthTypeSASL: - return &f.FrontendWrapper.AuthenticationSASL, nil - case pgproto3.AuthTypeSASLContinue: - return &f.FrontendWrapper.AuthenticationSASLContinue, nil - case pgproto3.AuthTypeSASLFinal: - return &f.FrontendWrapper.AuthenticationSASLFinal, nil - default: - return nil, fmt.Errorf("unknown authentication type: %d", f.FrontendWrapper.AuthType) - } - -} - -// GetAuthType returns the authType used in the current state of the frontend. -// See SetAuthType for more information. -func parseAuthType(buffer []byte) (int32, error) { - // Create a bytes reader from the buffer - reader := bytes.NewReader(buffer) - - // Skip the message type (1 byte) as you know it's 'R' - _, err := reader.Seek(1, 0) - if err != nil { - return 0, err - } - - // Read the length of the message (4 bytes) - var length int32 - err = binary.Read(reader, binary.BigEndian, &length) - if err != nil { - return 0, err - } - - // Read the auth type code (4 bytes) - var authType int32 - err = binary.Read(reader, binary.BigEndian, &authType) - if err != nil { - return 0, err - } - - return authType, nil -} - -func isStartupPacket(packet []byte) bool { - protocolVersion := binary.BigEndian.Uint32(packet[4:8]) - // printStartupPacketDetails(packet) - return protocolVersion == 196608 // 3.0 in PostgreSQL -} diff --git a/keploy/pkg/core/proxy/integrations/postgres/v1/util.go b/keploy/pkg/core/proxy/integrations/postgres/v1/util.go deleted file mode 100755 index 09d812d..0000000 --- a/keploy/pkg/core/proxy/integrations/postgres/v1/util.go +++ /dev/null @@ -1,500 +0,0 @@ -//go:build linux - -package v1 - -import ( - "encoding/binary" - "errors" - "fmt" - "strconv" - "time" - - "github.com/jackc/pgproto3/v2" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -func postgresDecoderFrontend(response models.Frontend) ([]byte, error) { - var resbuffer []byte - // list of packets available in the buffer - packets := response.PacketTypes - var cc, dtr, ps = 0, 0, 0 - for _, packet := range packets { - var msg pgproto3.BackendMessage - - switch packet { - case string('1'): - msg = &pgproto3.ParseComplete{} - case string('2'): - msg = &pgproto3.BindComplete{} - case string('3'): - msg = &pgproto3.CloseComplete{} - case string('A'): - msg = &pgproto3.NotificationResponse{ - PID: response.NotificationResponse.PID, - Channel: response.NotificationResponse.Channel, - Payload: response.NotificationResponse.Payload, - } - case string('c'): - msg = &pgproto3.CopyDone{} - case string('C'): - if len(response.CommandCompletes) == 0 { - cc++ - continue - } - msg = &pgproto3.CommandComplete{ - CommandTag: response.CommandCompletes[cc].CommandTag, - CommandTagType: response.CommandCompletes[cc].CommandTagType, - } - cc++ - case string('d'): - msg = &pgproto3.CopyData{ - Data: response.CopyData.Data, - } - case string('D'): - msg = &pgproto3.DataRow{ - RowValues: response.DataRows[dtr].RowValues, - Values: response.DataRows[dtr].Values, - } - dtr++ - case string('E'): - msg = &pgproto3.ErrorResponse{ - Severity: response.ErrorResponse.Severity, - Code: response.ErrorResponse.Code, - Message: response.ErrorResponse.Message, - Detail: response.ErrorResponse.Detail, - Hint: response.ErrorResponse.Hint, - Position: response.ErrorResponse.Position, - InternalPosition: response.ErrorResponse.InternalPosition, - InternalQuery: response.ErrorResponse.InternalQuery, - Where: response.ErrorResponse.Where, - SchemaName: response.ErrorResponse.SchemaName, - TableName: response.ErrorResponse.TableName, - ColumnName: response.ErrorResponse.ColumnName, - DataTypeName: response.ErrorResponse.DataTypeName, - ConstraintName: response.ErrorResponse.ConstraintName, - File: response.ErrorResponse.File, - Line: response.ErrorResponse.Line, - Routine: response.ErrorResponse.Routine, - } - case string('G'): - msg = &pgproto3.CopyInResponse{ - OverallFormat: response.CopyInResponse.OverallFormat, - ColumnFormatCodes: response.CopyInResponse.ColumnFormatCodes, - } - case string('H'): - msg = &pgproto3.CopyOutResponse{ - OverallFormat: response.CopyOutResponse.OverallFormat, - ColumnFormatCodes: response.CopyOutResponse.ColumnFormatCodes, - } - case string('I'): - msg = &pgproto3.EmptyQueryResponse{} - case string('K'): - msg = &pgproto3.BackendKeyData{ - ProcessID: response.BackendKeyData.ProcessID, - SecretKey: response.BackendKeyData.SecretKey, - } - case string('n'): - msg = &pgproto3.NoData{} - case string('N'): - msg = &pgproto3.NoticeResponse{ - Severity: response.NoticeResponse.Severity, - Code: response.NoticeResponse.Code, - Message: response.NoticeResponse.Message, - Detail: response.NoticeResponse.Detail, - Hint: response.NoticeResponse.Hint, - Position: response.NoticeResponse.Position, - InternalPosition: response.NoticeResponse.InternalPosition, - InternalQuery: response.NoticeResponse.InternalQuery, - Where: response.NoticeResponse.Where, - SchemaName: response.NoticeResponse.SchemaName, - TableName: response.NoticeResponse.TableName, - ColumnName: response.NoticeResponse.ColumnName, - DataTypeName: response.NoticeResponse.DataTypeName, - ConstraintName: response.NoticeResponse.ConstraintName, - File: response.NoticeResponse.File, - Line: response.NoticeResponse.Line, - Routine: response.NoticeResponse.Routine, - } - - case string('R'): - switch response.AuthType { - case AuthTypeOk: - msg = &pgproto3.AuthenticationOk{} - case AuthTypeCleartextPassword: - msg = &pgproto3.AuthenticationCleartextPassword{} - case AuthTypeMD5Password: - msg = &pgproto3.AuthenticationMD5Password{} - case AuthTypeSCMCreds: - return nil, errors.New("AuthTypeSCMCreds is unimplemented") - case AuthTypeGSS: - return nil, errors.New("AuthTypeGSS is unimplemented") - case AuthTypeGSSCont: - msg = &pgproto3.AuthenticationGSSContinue{} - case AuthTypeSSPI: - return nil, errors.New("AuthTypeSSPI is unimplemented") - case AuthTypeSASL: - msg = &pgproto3.AuthenticationSASL{} - case AuthTypeSASLContinue: - msg = &pgproto3.AuthenticationSASLContinue{} - case AuthTypeSASLFinal: - msg = &pgproto3.AuthenticationSASLFinal{} - default: - return nil, fmt.Errorf("unknown authentication type: %d", response.AuthType) - } - - case string('s'): - msg = &pgproto3.PortalSuspended{} - case string('S'): - msg = &pgproto3.ParameterStatus{ - Name: response.ParameterStatusCombined[ps].Name, - Value: response.ParameterStatusCombined[ps].Value, - } - ps++ - - case string('t'): - msg = &pgproto3.ParameterDescription{ - ParameterOIDs: response.ParameterDescription.ParameterOIDs, - } - case string('T'): - msg = &pgproto3.RowDescription{ - Fields: response.RowDescription.Fields, - } - case string('V'): - msg = &pgproto3.FunctionCallResponse{ - Result: response.FunctionCallResponse.Result, - } - case string('W'): - msg = &pgproto3.CopyBothResponse{ - OverallFormat: response.CopyBothResponse.OverallFormat, - ColumnFormatCodes: response.CopyBothResponse.ColumnFormatCodes, - } - case string('Z'): - msg = &pgproto3.ReadyForQuery{ - TxStatus: response.ReadyForQuery.TxStatus, - } - default: - return nil, fmt.Errorf("unknown message type: %q", packet) - } - - encoded := msg.Encode([]byte{}) - resbuffer = append(resbuffer, encoded...) - } - return resbuffer, nil -} - -func postgresDecoderBackend(request models.Backend) ([]byte, error) { - // take each object , try to make it frontend or backend message so that it can call it's corresponding encode function - // and then append it to the buffer, for a particular mock .. - - var reqbuffer []byte - // list of packets available in the buffer - var b, e, p = 0, 0, 0 - packets := request.PacketTypes - for _, packet := range packets { - var msg pgproto3.FrontendMessage - switch packet { - case string('B'): - msg = &pgproto3.Bind{ - DestinationPortal: request.Binds[b].DestinationPortal, - PreparedStatement: request.Binds[b].PreparedStatement, - ParameterFormatCodes: request.Binds[b].ParameterFormatCodes, - Parameters: request.Binds[b].Parameters, - ResultFormatCodes: request.Binds[b].ResultFormatCodes, - } - b++ - case string('C'): - msg = &pgproto3.Close{ - Object_Type: request.Close.Object_Type, - Name: request.Close.Name, - } - case string('D'): - msg = &pgproto3.Describe{ - ObjectType: request.Describe.ObjectType, - Name: request.Describe.Name, - } - case string('E'): - msg = &pgproto3.Execute{ - Portal: request.Executes[e].Portal, - MaxRows: request.Executes[e].MaxRows, - } - e++ - case string('F'): - // *msg.(*pgproto3.Flush) = request.Flush - msg = &pgproto3.Flush{} - case string('f'): - // *msg.(*pgproto3.FunctionCall) = request.FunctionCall - msg = &pgproto3.FunctionCall{ - Function: request.FunctionCall.Function, - Arguments: request.FunctionCall.Arguments, - ArgFormatCodes: request.FunctionCall.ArgFormatCodes, - ResultFormatCode: request.FunctionCall.ResultFormatCode, - } - case string('d'): - msg = &pgproto3.CopyData{ - Data: request.CopyData.Data, - } - case string('c'): - msg = &pgproto3.CopyDone{} - case string('H'): - msg = &pgproto3.CopyFail{ - Message: request.CopyFail.Message, - } - case string('P'): - msg = &pgproto3.Parse{ - Name: request.Parses[p].Name, - Query: request.Parses[p].Query, - ParameterOIDs: request.Parses[p].ParameterOIDs, - } - p++ - case string('p'): - switch request.AuthType { - case pgproto3.AuthTypeSASL: - msg = &pgproto3.SASLInitialResponse{ - AuthMechanism: request.SASLInitialResponse.AuthMechanism, - Data: request.SASLInitialResponse.Data, - } - case pgproto3.AuthTypeSASLContinue: - msg = &pgproto3.SASLResponse{ - Data: request.SASLResponse.Data, - } - case pgproto3.AuthTypeSASLFinal: - msg = &pgproto3.SASLResponse{ - Data: request.SASLResponse.Data, - } - case pgproto3.AuthTypeGSS, pgproto3.AuthTypeGSSCont: - msg = &pgproto3.GSSResponse{ - Data: []byte{}, // TODO: implement - } - case pgproto3.AuthTypeCleartextPassword, pgproto3.AuthTypeMD5Password: - fallthrough - default: - // to maintain backwards compatability - msg = &pgproto3.PasswordMessage{Password: request.PasswordMessage.Password} - } - case string('Q'): - msg = &pgproto3.Query{ - String: request.Query.String, - } - case string('S'): - msg = &pgproto3.Sync{} - case string('X'): - // *msg.(*pgproto3.Terminate) = request.Terminate - msg = &pgproto3.Terminate{} - default: - return nil, fmt.Errorf("unknown message type: %q", packet) - } - if msg == nil { - return nil, errors.New("msg is nil") - } - encoded := msg.Encode([]byte{}) - - reqbuffer = append(reqbuffer, encoded...) - } - return reqbuffer, nil -} - -func checkIfps(array []string) bool { - n := len(array) - if n%2 != 0 { - // If the array length is odd, it cannot match the pattern - return false - } - - for i := 0; i < n; i += 2 { - // Check if consecutive elements are "B" and "E" - if array[i] != "B" || array[i+1] != "E" { - return false - } - } - - return true -} - -func sliceCommandTag(mock *models.Mock, logger *zap.Logger, prep []QueryData, actualPgReq *models.Backend, psCase int) *models.Mock { - - logger.Debug("Inside Slice Command Tag for ", zap.Int("psCase", psCase)) - logger.Debug("Prep Query Data", zap.Any("prep", prep)) - switch psCase { - case 1: - - copyMock := *mock - // fmt.Println("Inside Slice Command Tag for ", psCase) - mockPackets := copyMock.Spec.PostgresResponses[0].PacketTypes - for idx, v := range mockPackets { - if v == "1" { - mockPackets = append(mockPackets[:idx], mockPackets[idx+1:]...) - } - } - copyMock.Spec.PostgresResponses[0].Payload = "" - copyMock.Spec.PostgresResponses[0].PacketTypes = mockPackets - - return ©Mock - case 2: - // ["2", D, C, Z] - copyMock := *mock - // fmt.Println("Inside Slice Command Tag for ", psCase) - mockPackets := copyMock.Spec.PostgresResponses[0].PacketTypes - for idx, v := range mockPackets { - if v == "1" || v == "T" { - mockPackets = append(mockPackets[:idx], mockPackets[idx+1:]...) - } - } - copyMock.Spec.PostgresResponses[0].Payload = "" - copyMock.Spec.PostgresResponses[0].PacketTypes = mockPackets - rsFormat := actualPgReq.Bind.ResultFormatCodes - - for idx, datarow := range copyMock.Spec.PostgresResponses[0].DataRows { - for column, rowVal := range datarow.RowValues { - // fmt.Println("datarow.RowValues", len(datarow.RowValues)) - if rsFormat[column] == 1 { - // datarows := make([]byte, 4) - newRow, _ := getChandedDataRow(rowVal) - // logger.Info("New Row Value", zap.String("newRow", newRow)) - copyMock.Spec.PostgresResponses[0].DataRows[idx].RowValues[column] = newRow - } - } - } - return ©Mock - default: - } - return nil -} - -func getChandedDataRow(input string) (string, error) { - // Convert input1 (integer input as string) to integer - buffer := make([]byte, 4) - if uintValue, err := strconv.ParseUint(input, 10, 32); err == nil { - - binary.BigEndian.PutUint32(buffer, uint32(uintValue)) - return "b64:" + util.EncodeBase64(buffer), nil - } else if dateValue, err := time.Parse("2006-01-02", input); err == nil { - // Perform additional operations on the date - epoch := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) - difference := dateValue.Sub(epoch).Hours() / 24 - // fmt.Printf("Difference in days from epoch: %.2f days\n", difference) - binary.BigEndian.PutUint32(buffer, uint32(difference)) - return "b64:" + util.EncodeBase64(buffer), nil - } - return "b64:AAAAAA==", errors.New("invalid input") - -} - -func decodePgRequest(buffer []byte, logger *zap.Logger) *models.Backend { - - pg := NewBackend() - - if !isStartupPacket(buffer) && len(buffer) > 5 { - bufferCopy := buffer - for i := 0; i < len(bufferCopy)-5; { - pg.BackendWrapper.MsgType = buffer[i] - pg.BackendWrapper.BodyLen = int(binary.BigEndian.Uint32(buffer[i+1:])) - 4 - if len(buffer) < (i + pg.BackendWrapper.BodyLen + 5) { - logger.Debug("failed to translate the postgres request message due to shorter network packet buffer") - break - } - msg, err := pg.translateToReadableBackend(buffer[i:(i + pg.BackendWrapper.BodyLen + 5)]) - if err != nil && buffer[i] != 112 { - logger.Debug("failed to translate the request message to readable", zap.Error(err)) - } - if pg.BackendWrapper.MsgType == 'p' { - pg.BackendWrapper.PasswordMessage = *msg.(*pgproto3.PasswordMessage) - } - - if pg.BackendWrapper.MsgType == 'P' { - pg.BackendWrapper.Parse = *msg.(*pgproto3.Parse) - pg.BackendWrapper.Parses = append(pg.BackendWrapper.Parses, pg.BackendWrapper.Parse) - } - - if pg.BackendWrapper.MsgType == 'B' { - pg.BackendWrapper.Bind = *msg.(*pgproto3.Bind) - pg.BackendWrapper.Binds = append(pg.BackendWrapper.Binds, pg.BackendWrapper.Bind) - } - - if pg.BackendWrapper.MsgType == 'E' { - pg.BackendWrapper.Execute = *msg.(*pgproto3.Execute) - pg.BackendWrapper.Executes = append(pg.BackendWrapper.Executes, pg.BackendWrapper.Execute) - } - - pg.BackendWrapper.PacketTypes = append(pg.BackendWrapper.PacketTypes, string(pg.BackendWrapper.MsgType)) - - i += 5 + pg.BackendWrapper.BodyLen - } - - pgMock := &models.Backend{ - PacketTypes: pg.BackendWrapper.PacketTypes, - Identfier: "ClientRequest", - Length: uint32(len(buffer)), - // Payload: bufStr, - Bind: pg.BackendWrapper.Bind, - Binds: pg.BackendWrapper.Binds, - PasswordMessage: pg.BackendWrapper.PasswordMessage, - CancelRequest: pg.BackendWrapper.CancelRequest, - Close: pg.BackendWrapper.Close, - CopyData: pg.BackendWrapper.CopyData, - CopyDone: pg.BackendWrapper.CopyDone, - CopyFail: pg.BackendWrapper.CopyFail, - Describe: pg.BackendWrapper.Describe, - Execute: pg.BackendWrapper.Execute, - Executes: pg.BackendWrapper.Executes, - Flush: pg.BackendWrapper.Flush, - FunctionCall: pg.BackendWrapper.FunctionCall, - GssEncRequest: pg.BackendWrapper.GssEncRequest, - Parse: pg.BackendWrapper.Parse, - Parses: pg.BackendWrapper.Parses, - Query: pg.BackendWrapper.Query, - SSlRequest: pg.BackendWrapper.SSlRequest, - StartupMessage: pg.BackendWrapper.StartupMessage, - SASLInitialResponse: pg.BackendWrapper.SASLInitialResponse, - SASLResponse: pg.BackendWrapper.SASLResponse, - Sync: pg.BackendWrapper.Sync, - Terminate: pg.BackendWrapper.Terminate, - MsgType: pg.BackendWrapper.MsgType, - AuthType: pg.BackendWrapper.AuthType, - } - return pgMock - } - - return nil -} - -func mergePgRequests(requestBuffers [][]byte, logger *zap.Logger) [][]byte { - // Check for PBDE first - var mergeBuff []byte - for _, v := range requestBuffers { - backend := decodePgRequest(v, logger) - - if backend == nil { - logger.Debug("Rerurning nil while merging ") - break - } - buf, _ := postgresDecoderBackend(*backend) - mergeBuff = append(mergeBuff, buf...) - } - if len(mergeBuff) > 0 { - return [][]byte{mergeBuff} - } - - return requestBuffers -} - -func mergeMocks(pgmocks []models.Backend, logger *zap.Logger) []models.Backend { - if len(pgmocks) == 0 { - return pgmocks - } - // Check for PBDE first - if len(pgmocks[0].PacketTypes) == 0 || pgmocks[0].PacketTypes[0] != "P" { - return pgmocks - } - var mergeBuff []byte - for _, v := range pgmocks { - buf, _ := postgresDecoderBackend(v) - mergeBuff = append(mergeBuff, buf...) - } - if len(mergeBuff) > 0 { - return []models.Backend{*decodePgRequest(mergeBuff, logger)} - } - - return pgmocks -} diff --git a/keploy/pkg/core/proxy/integrations/redis/decode.go b/keploy/pkg/core/proxy/integrations/redis/decode.go deleted file mode 100644 index 6f945db..0000000 --- a/keploy/pkg/core/proxy/integrations/redis/decode.go +++ /dev/null @@ -1,125 +0,0 @@ -//go:build linux - -// Package redis is the decode point for the redis application. -package redis - -import ( - "context" - "io" - "net" - "time" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func decodeRedis(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, _ models.OutgoingOptions) error { - redisRequests := [][]byte{reqBuf} - logger.Debug("Into the redis parser in test mode") - errCh := make(chan error, 1) - - go func(errCh chan error, redisRequests [][]byte) { - defer pUtil.Recover(logger, clientConn, nil) - defer close(errCh) - for { - - // Read the stream of request packets from the client - for len(redisRequests) == 0 { - err := clientConn.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) - if err != nil { - utils.LogError(logger, err, "failed to set the read deadline for the client conn") - return - } - buffer, err := pUtil.ReadBytes(ctx, logger, clientConn) - // Applied this nolint to ignore the staticcheck error here because of readability - // nolint:staticcheck - if netErr, ok := err.(net.Error); !(ok && netErr.Timeout()) && err != nil && err.Error() != "EOF" { - logger.Debug("failed to read the request message in proxy for redis dependency") - return - } - if netErr, ok := err.(net.Error); (ok && netErr.Timeout()) || (err != nil && err.Error() == "EOF") { - logger.Debug("timeout for client read in redis or EOF") - break - } - if len(buffer) > 0 { - redisRequests = append(redisRequests, buffer) - break - } - } - - if len(redisRequests) == 0 { - logger.Debug("redis request buffer is empty") - continue - } - - // Fuzzy match to get the best matched redis mock - matched, redisResponses, err := fuzzyMatch(ctx, redisRequests, mockDb) - if err != nil { - utils.LogError(logger, err, "error while matching redis mocks") - } - - if !matched { - err := clientConn.SetReadDeadline(time.Time{}) - if err != nil { - utils.LogError(logger, err, "failed to set the read deadline for the client conn") - return - } - - logger.Debug("redisRequests before pass through:", zap.Any("length", len(redisRequests))) - for _, redReq := range redisRequests { - logger.Debug("redisRequests:", zap.Any("h", string(redReq))) - } - - reqBuffer, err := pUtil.PassThrough(ctx, logger, clientConn, dstCfg, redisRequests) - if err != nil { - utils.LogError(logger, err, "failed to passthrough the redis request") - return - } - - redisRequests = [][]byte{} - logger.Debug("request buffer after pass through in redis:", zap.Any("buffer", string(reqBuffer))) - if len(reqBuffer) > 0 { - redisRequests = [][]byte{reqBuffer} - } - logger.Debug("length of redisRequests after passThrough:", zap.Any("length", len(redisRequests))) - continue - } - for _, redisResponse := range redisResponses { - encoded := []byte(redisResponse.Message[0].Data) - if redisResponse.Message[0].Type != models.String { - encoded, err = util.DecodeBase64(redisResponse.Message[0].Data) - if err != nil { - utils.LogError(logger, err, "failed to decode the base64 response") - return - } - } - _, err := clientConn.Write(encoded) - if err != nil { - if ctx.Err() != nil { - return - } - utils.LogError(logger, err, "failed to write the response message to the client application") - return - } - } - - // Clear the redisRequests buffer for the next dependency call - redisRequests = [][]byte{} - logger.Debug("redisRequests after the iteration:", zap.Any("length", len(redisRequests))) - } - }(errCh, redisRequests) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - return nil - } - return err - } -} diff --git a/keploy/pkg/core/proxy/integrations/redis/encode.go b/keploy/pkg/core/proxy/integrations/redis/encode.go deleted file mode 100644 index 90e25c1..0000000 --- a/keploy/pkg/core/proxy/integrations/redis/encode.go +++ /dev/null @@ -1,188 +0,0 @@ -//go:build linux - -package redis - -import ( - "context" - "errors" - "io" - "net" - "time" - - "golang.org/x/sync/errgroup" - - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func encodeRedis(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientConn, destConn net.Conn, mocks chan<- *models.Mock, _ models.OutgoingOptions) error { - - var redisRequests []models.Payload - var redisResponses []models.Payload - - bufStr := string(reqBuf) - dataType := models.String - - if bufStr != "" { - redisRequests = append(redisRequests, models.Payload{ - Origin: models.FromClient, - Message: []models.OutputBinary{ - { - Type: dataType, - Data: bufStr, - }, - }, - }) - } - _, err := destConn.Write(reqBuf) - if err != nil { - utils.LogError(logger, err, "failed to write request message to the destination server") - return err - } - errCh := make(chan error, 1) - - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context") - } - - reqTimestampMock := time.Now() - - // Read and process responses from the destination server - g.Go(func() error { - defer pUtil.Recover(logger, clientConn, destConn) - defer close(errCh) - - for { - // Read the response from the destination server - resp, err := pUtil.ReadBytes(ctx, logger, destConn) - if err != nil { - if err == io.EOF { - logger.Debug("Response complete, exiting the loop.") - // if there is any buffer left before EOF, we must send it to the client and save this as mock - if len(resp) != 0 { - resTimestampMock := time.Now() - _, err = clientConn.Write(resp) - if err != nil { - utils.LogError(logger, err, "failed to write response message to the client") - errCh <- err - return nil - } - processBuffer(resp, models.FromServer, &redisResponses) - saveMock(ctx, redisRequests, redisResponses, reqTimestampMock, resTimestampMock, mocks) - } - break - } - utils.LogError(logger, err, "failed to read the response message from the destination server") - errCh <- err - return nil - } - - // Write the response message to the client - _, err = clientConn.Write(resp) - if err != nil { - utils.LogError(logger, err, "failed to write response message to the client") - errCh <- err - return nil - } - - resTimestampMock := time.Now() - processBuffer(resp, models.FromServer, &redisResponses) - - // Save the mock with both request and response - if len(redisRequests) > 0 && len(redisResponses) > 0 { - saveMock(ctx, redisRequests, redisResponses, reqTimestampMock, resTimestampMock, mocks) - redisRequests = []models.Payload{} - redisResponses = []models.Payload{} - } - - // Read the next request from the client - reqBuf, err = pUtil.ReadBytes(ctx, logger, clientConn) - if err != nil { - if err != io.EOF { - utils.LogError(logger, err, "failed to read the request message from the client") - errCh <- err - return nil - } - errCh <- err - return nil - } - - bufStr := string(reqBuf) - dataType := models.String - - if bufStr != "" { - redisRequests = append(redisRequests, models.Payload{ - Origin: models.FromClient, - Message: []models.OutputBinary{ - { - Type: dataType, - Data: bufStr, - }, - }, - }) - } - _, err = destConn.Write(reqBuf) - if err != nil { - utils.LogError(logger, err, "failed to write request message to the destination server") - errCh <- err - return nil - } - reqTimestampMock = time.Now() - } - return nil - }) - - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - if err == io.EOF { - return nil - } - return err - } -} - -func processBuffer(buffer []byte, origin models.OriginType, payloads *[]models.Payload) { - bufStr := string(buffer) - buffDataType := models.String - - if bufStr != "" { - *payloads = append(*payloads, models.Payload{ - Origin: origin, - Message: []models.OutputBinary{ - { - Type: buffDataType, - Data: bufStr, - }, - }, - }) - } -} - -func saveMock(ctx context.Context, requests, responses []models.Payload, reqTimestampMock, resTimestampMock time.Time, mocks chan<- *models.Mock) { - redisRequestsCopy := make([]models.Payload, len(requests)) - redisResponsesCopy := make([]models.Payload, len(responses)) - copy(redisResponsesCopy, responses) - copy(redisRequestsCopy, requests) - - metadata := make(map[string]string) - metadata["type"] = "config" - metadata["connID"] = ctx.Value(models.ClientConnectionIDKey).(string) - - mocks <- &models.Mock{ - Version: models.GetVersion(), - Name: "mocks", - Kind: models.REDIS, - Spec: models.MockSpec{ - RedisRequests: redisRequestsCopy, - RedisResponses: redisResponsesCopy, - ReqTimestampMock: reqTimestampMock, - ResTimestampMock: resTimestampMock, - Metadata: metadata, - }, - } -} diff --git a/keploy/pkg/core/proxy/integrations/redis/match.go b/keploy/pkg/core/proxy/integrations/redis/match.go deleted file mode 100755 index ecc4977..0000000 --- a/keploy/pkg/core/proxy/integrations/redis/match.go +++ /dev/null @@ -1,151 +0,0 @@ -//go:build linux - -package redis - -import ( - "context" - "fmt" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.keploy.io/server/v2/pkg/models" -) - -// fuzzyMatch performs a fuzzy matching algorithm to find the best matching mock for the given request. -// It takes a context, a request buffer, and a mock database as input parameters. -// The function iterates over the mocks in the database and applies the fuzzy matching algorithm to find the best match. -// If a match is found, it returns the corresponding response mock and a boolean value indicating success. -// If no match is found, it returns false and a nil response. -// If an error occurs during the matching process, it returns an error. -func fuzzyMatch(ctx context.Context, reqBuff [][]byte, mockDb integrations.MockMemDb) (bool, []models.Payload, error) { - for { - select { - case <-ctx.Done(): - return false, nil, ctx.Err() - default: - mocks, err := mockDb.GetUnFilteredMocks() - if err != nil { - return false, nil, fmt.Errorf("error while getting unfiltered mocks %v", err) - } - - var filteredMocks []*models.Mock - var unfilteredMocks []*models.Mock - - for _, mock := range mocks { - if mock.Kind != "Redis" { - continue - } - if mock.TestModeInfo.IsFiltered { - filteredMocks = append(filteredMocks, mock) - } else { - unfilteredMocks = append(unfilteredMocks, mock) - } - } - - index := findExactMatch(filteredMocks, reqBuff) - - if index == -1 { - index = findBinaryMatch(filteredMocks, reqBuff, 0.9) - } - - if index != -1 { - responseMock := make([]models.Payload, len(filteredMocks[index].Spec.RedisResponses)) - copy(responseMock, filteredMocks[index].Spec.RedisResponses) - originalFilteredMock := *filteredMocks[index] - filteredMocks[index].TestModeInfo.IsFiltered = false - filteredMocks[index].TestModeInfo.SortOrder = pkg.GetNextSortNum() - isUpdated := mockDb.UpdateUnFilteredMock(&originalFilteredMock, filteredMocks[index]) - if !isUpdated { - continue - } - return true, responseMock, nil - } - - index = findExactMatch(unfilteredMocks, reqBuff) - - if index != -1 { - responseMock := make([]models.Payload, len(unfilteredMocks[index].Spec.RedisResponses)) - copy(responseMock, unfilteredMocks[index].Spec.RedisResponses) - return true, responseMock, nil - } - - totalMocks := append(filteredMocks, unfilteredMocks...) - index = findBinaryMatch(totalMocks, reqBuff, 0.4) - - if index != -1 { - responseMock := make([]models.Payload, len(totalMocks[index].Spec.RedisResponses)) - copy(responseMock, totalMocks[index].Spec.RedisResponses) - originalFilteredMock := *totalMocks[index] - if totalMocks[index].TestModeInfo.IsFiltered { - totalMocks[index].TestModeInfo.IsFiltered = false - totalMocks[index].TestModeInfo.SortOrder = pkg.GetNextSortNum() - isUpdated := mockDb.UpdateUnFilteredMock(&originalFilteredMock, totalMocks[index]) - if !isUpdated { - continue - } - } - return true, responseMock, nil - } - - return false, nil, nil - } - } -} - -// TODO: need to generalize this function for different types of integrations. -func findBinaryMatch(tcsMocks []*models.Mock, reqBuffs [][]byte, mxSim float64) int { - // TODO: need find a proper similarity index to set a benchmark for matching or need to find another way to do approximate matching - mxIdx := -1 - for idx, mock := range tcsMocks { - if len(mock.Spec.RedisRequests) == len(reqBuffs) { - for requestIndex, reqBuff := range reqBuffs { - mockReq, err := util.DecodeBase64(mock.Spec.RedisRequests[requestIndex].Message[0].Data) - if err != nil { - mockReq = []byte(mock.Spec.RedisRequests[requestIndex].Message[0].Data) - } - - similarity := fuzzyCheck(mockReq, reqBuff) - if mxSim < similarity { - mxSim = similarity - mxIdx = idx - } - } - - } - } - return mxIdx -} - -func fuzzyCheck(encoded, reqBuf []byte) float64 { - k := util.AdaptiveK(len(reqBuf), 3, 8, 5) - shingles1 := util.CreateShingles(encoded, k) - shingles2 := util.CreateShingles(reqBuf, k) - similarity := util.JaccardSimilarity(shingles1, shingles2) - return similarity -} - -func findExactMatch(tcsMocks []*models.Mock, reqBuffs [][]byte) int { - for idx, mock := range tcsMocks { - if len(mock.Spec.RedisRequests) == len(reqBuffs) { - matched := true // Flag to track if all requests match - - for requestIndex, reqBuff := range reqBuffs { - - bufStr := string(reqBuff) - - // Compare the encoded data - if mock.Spec.RedisRequests[requestIndex].Message[0].Data != bufStr { - matched = false - break // Exit the loop if any request doesn't match - } - } - - if matched { - return idx - } - } - } - return -1 -} diff --git a/keploy/pkg/core/proxy/integrations/redis/redis.go b/keploy/pkg/core/proxy/integrations/redis/redis.go deleted file mode 100755 index 2356d2b..0000000 --- a/keploy/pkg/core/proxy/integrations/redis/redis.go +++ /dev/null @@ -1,78 +0,0 @@ -//go:build linux - -package redis - -import ( - "context" - "net" - - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// func init() { -// integrations.Register(integrations.REDIS, &integrations.Parsers{ -// Initializer: New, -// Priority: 100, -// }) -// } - -type Redis struct { - logger *zap.Logger -} - -func New(logger *zap.Logger) integrations.Integrations { - return &Redis{ - logger: logger, - } -} - -func (r *Redis) MatchType(_ context.Context, buf []byte) bool { - if len(buf) == 0 { - return false - } - - // Check the first byte to determine the RESP data type - switch buf[0] { - case '+', '-', ':', '$', '*', '_', '#', ',', '(', '!', '=', '%', '~', '>': - return true - default: - return false - } -} - -func (r *Redis) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - logger := r.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial redis message") - return err - } - - err = encodeRedis(ctx, logger, reqBuf, src, dst, mocks, opts) - if err != nil { - utils.LogError(logger, err, "failed to encode the redis message into the yaml") - return err - } - return nil -} - -func (r *Redis) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *models.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { - logger := r.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", src.RemoteAddr().String())) - reqBuf, err := util.ReadInitialBuf(ctx, logger, src) - if err != nil { - utils.LogError(logger, err, "failed to read the initial redis message") - return err - } - - err = decodeRedis(ctx, logger, reqBuf, src, dstCfg, mockDb, opts) - if err != nil { - utils.LogError(logger, err, "failed to decode the redis message") - return err - } - return nil -} diff --git a/keploy/pkg/core/proxy/integrations/scram/scram.go b/keploy/pkg/core/proxy/integrations/scram/scram.go deleted file mode 100644 index e5853fe..0000000 --- a/keploy/pkg/core/proxy/integrations/scram/scram.go +++ /dev/null @@ -1,137 +0,0 @@ -//go:build linux - -// Package scram provides functionality for SCRAM authentication. -package scram - -import ( - "encoding/base64" - "errors" - "fmt" - "strings" - - "github.com/xdg-go/pbkdf2" - "github.com/xdg-go/scram" - "github.com/xdg-go/stringprep" - "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// GenerateServerFinalMessage generates the server's final message (i.e., the server proof) -// for SCRAM authentication, using a default password and given authentication message, mechanism, salt, iteration count. -func GenerateServerFinalMessage(authMessage, mechanism, password, salt string, itr int, logger *zap.Logger) (string, error) { - var ( - // Declare a variable to hold the hash generation function based on the chosen mechanism. - hashGen scram.HashGeneratorFcn - // normalised password is used in the salted password - passwordDigest string - ) - - username, err := extractUsername(authMessage) - if err != nil { - return "", err - } - - // Switch based on the provided mechanism to determine the hash function to be used. - switch mechanism { - case util.SCRAM_SHA_1: - hashGen = scram.SHA1 - passwordDigest = mongoPasswordDigest(username, password) - case util.SCRAM_SHA_256: - hashGen = scram.SHA256 - passwordDigest, err = stringprep.SASLprep.Prepare(password) - if err != nil { - return "", fmt.Errorf("error SASLprepping password for SCRAM-SHA-256 with password: %s. error: %v", password, err.Error()) - } - default: - // If the mechanism isn't supported, return an error. - return "", errors.New("unsupported authentication mechanism by keploy") - } - - // Get the hash function instance based on the determined generator. - h := hashGen() - - // Compute the salted password using the PBKDF2 function with the provided salt and iteration count. - // It uses the given password. This is the key derivation step. - logger.Debug("the input for generating the salted password", zap.Any("normalised password", passwordDigest), zap.Any("salt", salt), zap.Any("iteration", itr), zap.Any("hash size", h.Size()), zap.Any("mechanism", mechanism)) - saltedPassword := pbkdf2.Key([]byte(passwordDigest), []byte(salt), itr, h.Size(), hashGen) - logger.Debug("after generating the salted password", zap.Any("salted password", saltedPassword)) - - // Compute the server key using HMAC with the derived salted password and the string "Server Key". - serverKey := computeHMAC(hashGen, saltedPassword, []byte("Server Key")) - logger.Debug("generating the server using the salted password", zap.Any("server key", serverKey)) - - // Compute the server signature (server proof) using HMAC with the server key and the provided authMessage. - serverSignature := computeHMAC(hashGen, serverKey, []byte(authMessage)) - logger.Debug("the new server proof for the second auth request", zap.Any("server signature", base64.StdEncoding.EncodeToString(serverSignature)), zap.Any("derived from auth message", authMessage)) - - return base64.StdEncoding.EncodeToString(serverSignature), nil -} - -// GenerateServerFirstMessage generates the server's first response message for SCRAM authentication. -// It replaces the expected nonce from the recorded request with the actual nonce from the received request. -// -// Parameters: -// - recordedRequestMsg: The byte slice containing the recorded client's first message. -// - receivedRequestMsg: The byte slice containing the received client's first message. -// - firstResponseMsg: The byte slice containing the server's initial response message. -// - log: An instance of a log from the zap package. -// -// Returns: -// - A modified server's first response message with the nonce replaced. -// - An error if nonce extraction or replacement fails. -func GenerateServerFirstMessage(recordedRequestMsg, receivedRequestMsg, firstResponseMsg []byte, logger *zap.Logger) (string, error) { - expectedNonce, err := extractClientNonce(string(recordedRequestMsg)) - if err != nil { - utils.LogError(logger, err, "failed to extract the client nonce from the recorded first message") - return "", err - } - actualNonce, err := extractClientNonce(string(receivedRequestMsg)) - if err != nil { - utils.LogError(logger, err, "failed to extract the client nonce from the received first message") - return "", err - } - // Since, the nonce are randomlly generated string. so, each session have unique nonce. - // Thus, the mocked server response should be updated according to the current nonce - updatedResponse := strings.ReplaceAll(string(firstResponseMsg), expectedNonce, actualNonce) - - logger.Debug("Updated server first message after nonce substitution", zap.String("updatedResponse", updatedResponse)) - - return updatedResponse, nil -} - -// GenerateAuthMessage creates an authentication message based on the initial -// client request and the server's first response. The function extracts the GS2 -// header and the client's nonce from the provided strings and then concatenates -// them to form the complete authentication message. -// -// Parameters: -// - firstRequest: The initial request string from the client. -// - firstResponse: The server's first response string. -// - log: An instance of a log for logging errors and activities. -// -// Returns: -// - A string representing the complete authentication message. If there's an -// error during extraction or message creation, the function logs the error -// and returns an empty string. -func GenerateAuthMessage(firstRequest, firstResponse string, logger *zap.Logger) string { - gs2, err := extractAuthID(firstRequest) - if err != nil { - utils.LogError(logger, err, "failed to extract the client gs2 header from the received first message") - return "" - } - authMsg := firstRequest[len(gs2):] + "," + firstResponse + "," - nonce, err := extractClientNonce(firstResponse) - if err != nil { - utils.LogError(logger, err, "failed to extract the client nonce from the recorded first message") - return "" - } - - authMsg += fmt.Sprintf( - "c=%s,r=%s", - base64.StdEncoding.EncodeToString([]byte(gs2)), - nonce, - ) - - return authMsg -} diff --git a/keploy/pkg/core/proxy/integrations/scram/util.go b/keploy/pkg/core/proxy/integrations/scram/util.go deleted file mode 100644 index ee960b1..0000000 --- a/keploy/pkg/core/proxy/integrations/scram/util.go +++ /dev/null @@ -1,90 +0,0 @@ -//go:build linux - -package scram - -import ( - "crypto/hmac" - "crypto/md5" - "errors" - "fmt" - "io" - "regexp" - "strings" - - "github.com/xdg-go/scram" -) - -// extractClientNonce extracts the nonce value from a SCRAM authentication first message. -// -// Parameters: -// - firstMsg: The SCRAM authentication message string, which should contain key-value pairs -// separated by commas, e.g., "n,,n=username,r=nonce". -// -// Returns: -// - The extracted nonce value as a string. -// - An error if the nonce ("r=") cannot be found in the provided message. -func extractClientNonce(firstMsg string) (string, error) { - // Split the string based on "," - parts := strings.Split(firstMsg, ",") - - // Iterate over the parts to find the one starting with "r=" - for _, part := range parts { - if strings.HasPrefix(part, "r=") { - // Split based on "=" and get the value of "r" - // value := strings.Split(part, "=")[1] - value := strings.TrimPrefix(part, "r=") - if value == part { - return "", fmt.Errorf("error parsing '%s' for fetching client nonce", part) - } - return value, nil - } - } - return "", errors.New("nonce not found") -} - -// computeHMAC computes the HMAC (Hash-based Message Authentication Code) of the provided data -// using the specified hash generation function and key. -// -// Parameters: -// - hg: A function to generate the desired hash (like SHA-1 or SHA-256). -// - key: The secret key to use for the HMAC computation. -// - data: The input data for which the HMAC is to be computed. -// -// Returns: -// - A byte slice representing the computed HMAC value. -func computeHMAC(hg scram.HashGeneratorFcn, key, data []byte) []byte { - mac := hmac.New(hg, key) - mac.Write(data) - return mac.Sum(nil) -} - -func mongoPasswordDigest(username, password string) string { - // Ignore gosec warning "Use of weak cryptographic primitive". We need to use MD5 here to - // implement the SCRAM specification. - /* #nosec G401 */ - h := md5.New() - _, _ = io.WriteString(h, username) - _, _ = io.WriteString(h, ":mongo:") - _, _ = io.WriteString(h, password) - return fmt.Sprintf("%x", h.Sum(nil)) -} - -func extractUsername(authMessage string) (string, error) { - parts := strings.Split(authMessage, ",") - for _, part := range parts { - if strings.HasPrefix(part, "n=") { - nValue := strings.TrimPrefix(part, "n=") - return nValue, nil - } - } - return "", fmt.Errorf("no username found in the auth message") -} - -func extractAuthID(input string) (string, error) { - re := regexp.MustCompile(`n,([^,]*),`) // Regular expression to match "n,," or "n,SOMETHING," - matches := re.FindStringSubmatch(input) - if len(matches) >= 2 { - return "n," + matches[1] + ",", nil - } - return "", fmt.Errorf("no match found") -} diff --git a/keploy/pkg/core/proxy/integrations/util/util.go b/keploy/pkg/core/proxy/integrations/util/util.go deleted file mode 100644 index dd9aec4..0000000 --- a/keploy/pkg/core/proxy/integrations/util/util.go +++ /dev/null @@ -1,88 +0,0 @@ -//go:build linux - -// Package util provides utility functions for the integration package. -package util - -import ( - "encoding/base64" - "unicode" - - "go.keploy.io/server/v2/pkg/models" -) - -func IsASCII(s string) bool { - for _, r := range s { - if r > unicode.MaxASCII { - return false - } - } - return true -} - -func DecodeBase64(encoded string) ([]byte, error) { - // Decode the base64 encoded string to buffer - data, err := base64.StdEncoding.DecodeString(encoded) - if err != nil { - return nil, err - } - return data, nil -} - -func EncodeBase64(decoded []byte) string { - // Encode the []byte string to encoded string - return base64.StdEncoding.EncodeToString(decoded) -} - -// Functions related to fuzzy matching - -func AdaptiveK(length, min, max, N int) int { - k := length / N - if k < min { - return min - } else if k > max { - return max - } - return k -} - -func CreateShingles(data []byte, k int) map[string]struct{} { - shingles := make(map[string]struct{}) - for i := 0; i < len(data)-k+1; i++ { - shingle := string(data[i : i+k]) - shingles[shingle] = struct{}{} - } - return shingles -} - -// JaccardSimilarity computes the Jaccard similarity between two sets of shingles. -func JaccardSimilarity(setA, setB map[string]struct{}) float64 { - intersectionSize := 0 - for k := range setA { - if _, exists := setB[k]; exists { - intersectionSize++ - } - } - - unionSize := len(setA) + len(setB) - intersectionSize - - if unionSize == 0 { - return 0.0 - } - return float64(intersectionSize) / float64(unionSize) -} - -func GetMockByKind(mocks []*models.Mock, kind string) []*models.Mock { - var filteredMocks []*models.Mock - for _, mock := range mocks { - if mock.Kind == models.Kind(kind) { - filteredMocks = append(filteredMocks, mock) - } - } - return filteredMocks -} - -// scram enum values -const ( - SCRAM_SHA_1 = "SCRAM-SHA-1" - SCRAM_SHA_256 = "SCRAM-SHA-256" -) diff --git a/keploy/pkg/core/proxy/mockmanager.go b/keploy/pkg/core/proxy/mockmanager.go deleted file mode 100644 index ff721fd..0000000 --- a/keploy/pkg/core/proxy/mockmanager.go +++ /dev/null @@ -1,514 +0,0 @@ -//go:build linux - -package proxy - -import ( - "fmt" - "sort" - "strconv" - "strings" - "sync" - "sync/atomic" - - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -// ---------------- MockManager (kind-aware) ---------------- - -type MockManager struct { - // legacy "all" trees (kept for compatibility with existing callers) - filtered *TreeDb - unfiltered *TreeDb - - // global revision (legacy) - rev uint64 - - // NEW: per-kind revisions - revMu sync.RWMutex - revByKind map[models.Kind]*uint64 - - // NEW: per-kind trees (guarded by treesMu) - treesMu sync.RWMutex - filteredByKind map[models.Kind]*TreeDb - unfilteredByKind map[models.Kind]*TreeDb - - logger *zap.Logger - consumedMocks sync.Map // zero value is ready-to-use; no explicit init required -} - -func NewMockManager(filtered, unfiltered *TreeDb, logger *zap.Logger) *MockManager { - if filtered == nil { - filtered = NewTreeDb(customComparator) - } - if unfiltered == nil { - unfiltered = NewTreeDb(customComparator) - } - return &MockManager{ - filtered: filtered, - unfiltered: unfiltered, - filteredByKind: make(map[models.Kind]*TreeDb), - unfilteredByKind: make(map[models.Kind]*TreeDb), - revByKind: make(map[models.Kind]*uint64), - logger: logger, - } -} - -// ---------- revision helpers ---------- - -func (m *MockManager) Revision() uint64 { - return atomic.LoadUint64(&m.rev) -} - -func (m *MockManager) bumpRevisionAll() { - atomic.AddUint64(&m.rev, 1) -} - -func (m *MockManager) RevisionByKind(kind models.Kind) uint64 { - m.revMu.RLock() - ptr := m.revByKind[kind] - m.revMu.RUnlock() - if ptr == nil { - return 0 - } - return atomic.LoadUint64(ptr) -} - -func (m *MockManager) bumpRevisionKind(kind models.Kind) { - m.revMu.Lock() - ptr := m.revByKind[kind] - if ptr == nil { - var v uint64 - // Store pointer in map; safe to use after unlocking as we mutate via atomics. - ptr = &v - m.revByKind[kind] = ptr - } - m.revMu.Unlock() - atomic.AddUint64(ptr, 1) -} - -// ensureKindTrees returns per-kind trees, creating them if missing. -// It is safe for concurrent use. -func (m *MockManager) ensureKindTrees(kind models.Kind) (f *TreeDb, u *TreeDb) { - // Fast path: read lock - m.treesMu.RLock() - f = m.filteredByKind[kind] - u = m.unfilteredByKind[kind] - m.treesMu.RUnlock() - if f != nil && u != nil { - return f, u - } - - // Slow path: upgrade to write lock and double-check - m.treesMu.Lock() - if f = m.filteredByKind[kind]; f == nil { - f = NewTreeDb(customComparator) - m.filteredByKind[kind] = f - } - if u = m.unfilteredByKind[kind]; u == nil { - u = NewTreeDb(customComparator) - m.unfilteredByKind[kind] = u - } - m.treesMu.Unlock() - return f, u -} - -// ---------- getters ---------- - -func (m *MockManager) GetFilteredMocks() ([]*models.Mock, error) { - results := make([]*models.Mock, 0, 64) - m.filtered.rangeValues(func(v interface{}) bool { - if mock, ok := v.(*models.Mock); ok && mock != nil { - results = append(results, mock) - } - return true - }) - return results, nil -} - -func (m *MockManager) GetUnFilteredMocks() ([]*models.Mock, error) { - results := make([]*models.Mock, 0, 128) - m.unfiltered.rangeValues(func(v interface{}) bool { - if mock, ok := v.(*models.Mock); ok && mock != nil { - results = append(results, mock) - } - return true - }) - return results, nil -} - -// NEW: kind-scoped getters used by Redis matcher -func (m *MockManager) GetFilteredMocksByKind(kind models.Kind) ([]*models.Mock, error) { - // Fetch pointer safely; the tree itself is responsible for its own safety. - m.treesMu.RLock() - flt := m.filteredByKind[kind] - m.treesMu.RUnlock() - if flt == nil { - flt, _ = m.ensureKindTrees(kind) - } - - results := make([]*models.Mock, 0, 64) - flt.rangeValues(func(v interface{}) bool { - if mock, ok := v.(*models.Mock); ok && mock != nil { - results = append(results, mock) - } - return true - }) - return results, nil -} - -func (m *MockManager) GetUnFilteredMocksByKind(kind models.Kind) ([]*models.Mock, error) { - m.treesMu.RLock() - unf := m.unfilteredByKind[kind] - m.treesMu.RUnlock() - if unf == nil { - _, unf = m.ensureKindTrees(kind) - } - - results := make([]*models.Mock, 0, 128) - unf.rangeValues(func(v interface{}) bool { - if mock, ok := v.(*models.Mock); ok && mock != nil { - results = append(results, mock) - } - return true - }) - return results, nil -} - -// ---------- setters (populate both legacy + per-kind) ---------- - -func (m *MockManager) SetFilteredMocks(mocks []*models.Mock) { - // legacy rebuild - m.filtered.deleteAll() - - // rebuild per-kind filtered maps from scratch to avoid stale entries - newFilteredByKind := make(map[models.Kind]*TreeDb, len(m.filteredByKind)) - touched := map[models.Kind]struct{}{} - - for index, mock := range mocks { - if mock.TestModeInfo.SortOrder == 0 { - mock.TestModeInfo.SortOrder = int64(index) + 1 - } - mock.TestModeInfo.ID = index - m.filtered.insert(mock.TestModeInfo, mock) - - k := mock.Kind - td := newFilteredByKind[k] - if td == nil { - td = NewTreeDb(customComparator) - newFilteredByKind[k] = td - } - td.insert(mock.TestModeInfo, mock) - touched[k] = struct{}{} - } - - // atomically swap the per-kind map - m.treesMu.Lock() - m.filteredByKind = newFilteredByKind - m.treesMu.Unlock() - - for k := range touched { - m.bumpRevisionKind(k) - } - m.bumpRevisionAll() -} - -func (m *MockManager) SetUnFilteredMocks(mocks []*models.Mock) { - // legacy rebuild - m.unfiltered.deleteAll() - - // rebuild per-kind unfiltered maps from scratch to avoid stale entries - newUnfilteredByKind := make(map[models.Kind]*TreeDb, len(m.unfilteredByKind)) - touched := map[models.Kind]struct{}{} - - for index, mock := range mocks { - if mock.TestModeInfo.SortOrder == 0 { - mock.TestModeInfo.SortOrder = int64(index) + 1 - } - mock.TestModeInfo.ID = index - m.unfiltered.insert(mock.TestModeInfo, mock) - - k := mock.Kind - td := newUnfilteredByKind[k] - if td == nil { - td = NewTreeDb(customComparator) - newUnfilteredByKind[k] = td - } - td.insert(mock.TestModeInfo, mock) - touched[k] = struct{}{} - } - - // atomically swap the per-kind map - m.treesMu.Lock() - m.unfilteredByKind = newUnfilteredByKind - m.treesMu.Unlock() - - for k := range touched { - m.bumpRevisionKind(k) - } - m.bumpRevisionAll() -} - -// ---------- point updates / deletes (keep per-kind in sync) ---------- - -func (m *MockManager) UpdateUnFilteredMock(old *models.Mock, new *models.Mock) bool { - // Update legacy/global tree first - updatedGlobal := m.unfiltered.update(old.TestModeInfo, new.TestModeInfo, new) - - oldK, newK := old.Kind, new.Kind - var updatedOldKind, updatedNewKind bool - - if oldK == newK { - // Same kind: update the per-kind tree under lock - _, unf := m.ensureKindTrees(newK) - m.treesMu.Lock() - updatedNewKind = unf.update(old.TestModeInfo, new.TestModeInfo, new) - - // Self-heal if global updated but per-kind missed (e.g., not present yet) - if updatedGlobal && !updatedNewKind { - if m.logger != nil { - m.logger.Warn("self-healing per-kind tree: global update succeeded but per-kind missed", - zap.String("kind", string(newK)), - zap.String("mockName", new.Name), - zap.Any("testModeInfo", new.TestModeInfo), - ) - } - unf.insert(new.TestModeInfo, new) - updatedNewKind = true - } - m.treesMu.Unlock() - } else { - // Kind changed: remove from old kind tree, insert/update in new kind tree under one lock - _, oldUnf := m.ensureKindTrees(oldK) - _, newUnf := m.ensureKindTrees(newK) - m.treesMu.Lock() - updatedOldKind = oldUnf.delete(old.TestModeInfo) - updatedNewKind = newUnf.update(old.TestModeInfo, new.TestModeInfo, new) - if !updatedNewKind { - newUnf.insert(new.TestModeInfo, new) - updatedNewKind = true - } - m.treesMu.Unlock() - if m.logger != nil { - m.logger.Info("moved mock across kinds", - zap.String("mockName", new.Name), - zap.String("fromKind", string(oldK)), - zap.String("toKind", string(newK)), - ) - } - } - - // Mark usage if global changed (legacy behavior) - if updatedGlobal { - if err := m.flagMockAsUsed(models.MockState{ - Name: new.Name, - Usage: models.Updated, - IsFiltered: new.TestModeInfo.IsFiltered, - SortOrder: new.TestModeInfo.SortOrder, - }); err != nil { - m.logger.Error("failed to flag mock as used", zap.Error(err)) - } - } - - // Bump revisions accurately: - // - global only if the global tree changed - // - per-kind only for kinds whose per-kind tree changed - if oldK != newK { - if updatedOldKind { - m.bumpRevisionKind(oldK) - } - if updatedNewKind { - m.bumpRevisionKind(newK) - } - } else if updatedNewKind { - m.bumpRevisionKind(newK) - } - if updatedGlobal { - m.bumpRevisionAll() - } - return updatedGlobal -} - -func (m *MockManager) DeleteFilteredMock(mock models.Mock) bool { - deletedGlobal := m.filtered.delete(mock.TestModeInfo) - - // per-kind - k := mock.Kind - flt, _ := m.ensureKindTrees(k) - m.treesMu.Lock() - deletedKind := flt.delete(mock.TestModeInfo) - m.treesMu.Unlock() - - if deletedGlobal { - if err := m.flagMockAsUsed(models.MockState{ - Name: mock.Name, - Usage: models.Deleted, - IsFiltered: mock.TestModeInfo.IsFiltered, - SortOrder: mock.TestModeInfo.SortOrder, - }); err != nil { - m.logger.Error("failed to flag mock as used", zap.Error(err)) - } - } - - // Bump per-kind only if that tree changed; global only if global changed - if deletedKind { - m.bumpRevisionKind(k) - } - if deletedGlobal { - m.bumpRevisionAll() - } - return deletedGlobal -} - -func (m *MockManager) DeleteUnFilteredMock(mock models.Mock) bool { - deletedGlobal := m.unfiltered.delete(mock.TestModeInfo) - - // per-kind - k := mock.Kind - _, unf := m.ensureKindTrees(k) - m.treesMu.Lock() - deletedKind := unf.delete(mock.TestModeInfo) - m.treesMu.Unlock() - - if deletedGlobal { - if err := m.flagMockAsUsed(models.MockState{ - Name: mock.Name, - Usage: models.Deleted, - IsFiltered: mock.TestModeInfo.IsFiltered, - SortOrder: mock.TestModeInfo.SortOrder, - }); err != nil { - m.logger.Error("failed to flag mock as used", zap.Error(err)) - } - } - - // Bump per-kind only if that tree changed; global only if global changed - if deletedKind { - m.bumpRevisionKind(k) - } - if deletedGlobal { - m.bumpRevisionAll() - } - return deletedGlobal -} - -// ---------- bookkeeping ---------- - -func (m *MockManager) flagMockAsUsed(mock models.MockState) error { - if mock.Name == "" { - return fmt.Errorf("mock is empty") - } - m.consumedMocks.Store(mock.Name, mock) - return nil -} - -func (m *MockManager) GetConsumedMocks() []models.MockState { - var out []models.MockState - - // Snapshot & collect first (no deletes during Range). We intentionally drain only what existed at snapshot time. - m.consumedMocks.Range(func(key, val interface{}) bool { - k, ok := key.(string) - if !ok { - if m.logger != nil { - m.logger.Warn("unexpected key type in consumedMocks; skipping", - zap.Any("keyType", fmt.Sprintf("%T", key))) - } - return true // skip this entry - } - if st, ok := val.(models.MockState); ok { - out = append(out, st) - } else if m.logger != nil { - m.logger.Warn("unexpected value type in consumedMocks; skipping", - zap.String("key", k), - zap.Any("valueType", fmt.Sprintf("%T", val))) - } - return true - }) - - // Sort: prefer numeric suffix after the last '-' (e.g., name-123); else lexicographic - type withSuffix struct { - st models.MockState - name string - num int - has bool - } - numericSuffix := func(name string) (int, bool) { - i := strings.LastIndexByte(name, '-') - if i < 0 || i+1 >= len(name) { - return 0, false - } - n, err := strconv.Atoi(name[i+1:]) - if err != nil { - return 0, false - } - return n, true - } - - ws := make([]withSuffix, len(out)) - for i, st := range out { - n, ok := numericSuffix(st.Name) - ws[i] = withSuffix{st: st, name: st.Name, num: n, has: ok} - } - sort.SliceStable(ws, func(i, j int) bool { - a, b := ws[i], ws[j] - if a.has && b.has { - if a.num != b.num { - return a.num < b.num - } - // tie-break numerics by name for determinism - return a.name < b.name - } - return a.name < b.name - }) - for i := range out { - out[i] = ws[i].st - } - - // Now clear those entries from the map we just drained - for _, st := range out { - m.consumedMocks.Delete(st.Name) - } - return out -} - -// GetMySQLCounts computes counts of MySQL mocks. -// Uses the per-kind unfiltered tree if available, otherwise falls back -// to scanning the legacy unfiltered tree. -func (m *MockManager) GetMySQLCounts() (total, config, data int) { - // Fast path: snapshot the per-kind tree pointer under lock - m.treesMu.RLock() - tree := m.unfilteredByKind[models.MySQL] - m.treesMu.RUnlock() - - if tree != nil { - tree.rangeValues(func(v interface{}) bool { - mock, ok := v.(*models.Mock) - if !ok || mock == nil { - return true - } - total++ - if mock.Spec.Metadata["type"] == "config" { - config++ - } else { - data++ - } - return true - }) - return - } - - // Fallback: legacy scan of the combined tree - m.unfiltered.rangeValues(func(v interface{}) bool { - mock, ok := v.(*models.Mock) - if !ok || mock == nil || mock.Kind != models.MySQL { - return true - } - total++ - if mock.Spec.Metadata["type"] == "config" { - config++ - } else { - data++ - } - return true - }) - return -} diff --git a/keploy/pkg/core/proxy/options.go b/keploy/pkg/core/proxy/options.go deleted file mode 100755 index 3783649..0000000 --- a/keploy/pkg/core/proxy/options.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build linux - -package proxy - -// TODO: what is the need of this? currently it is not being used anywhere. - -// Option provides a means to initiate the proxy based on user input. -type Option struct { - Port uint32 - DNSPort uint32 - MongoPassword string -} diff --git a/keploy/pkg/core/proxy/parsers.go b/keploy/pkg/core/proxy/parsers.go deleted file mode 100644 index b6cfe8b..0000000 --- a/keploy/pkg/core/proxy/parsers.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build linux - -package proxy - -import ( - // import all the integrations - _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/generic" - _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/grpc" - _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/grpcV2" - _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/http" - _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/mongo" - _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql" - _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/postgres/v1" - _ "go.keploy.io/server/v2/pkg/core/proxy/integrations/redis" -) diff --git a/keploy/pkg/core/proxy/proxy.go b/keploy/pkg/core/proxy/proxy.go deleted file mode 100755 index b2a3ddc..0000000 --- a/keploy/pkg/core/proxy/proxy.go +++ /dev/null @@ -1,731 +0,0 @@ -//go:build linux - -// Package proxy handles all the outgoing network calls and captures/forwards the request and response messages. -// It also handles the DNS resolution mechanism. -package proxy - -import ( - "bufio" - "bytes" - "context" - "crypto/tls" - "errors" - "fmt" - "io" - "net" - "sort" - "strings" - "sync" - "time" - - "golang.org/x/sync/errgroup" - - "github.com/miekg/dns" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/core" - "go.keploy.io/server/v2/pkg/core/proxy/integrations" - pTls "go.keploy.io/server/v2/pkg/core/proxy/tls" - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type ParserPriority struct { - Priority int - ParserType integrations.IntegrationType -} - -type Proxy struct { - logger *zap.Logger - - IP4 string - IP6 string - Port uint32 - DNSPort uint32 - - DestInfo core.DestInfo - Integrations map[integrations.IntegrationType]integrations.Integrations - - MockManagers sync.Map - integrationsPriority []ParserPriority - - sessions *core.Sessions - - connMutex *sync.Mutex - ipMutex *sync.Mutex - - clientConnections []net.Conn - - Listener net.Listener - - //to store the nsswitch.conf file data - nsswitchData []byte // in test mode we change the configuration of "hosts" in nsswitch.conf file to disable resolution over unix socket - UDPDNSServer *dns.Server - TCPDNSServer *dns.Server -} - -func New(logger *zap.Logger, info core.DestInfo, opts *config.Config) *Proxy { - return &Proxy{ - logger: logger, - Port: opts.ProxyPort, // default: 16789 - DNSPort: opts.DNSPort, // default: 26789 - IP4: "127.0.0.1", // default: "127.0.0.1" <-> (2130706433) - IP6: "::1", //default: "::1" <-> ([4]uint32{0000, 0000, 0000, 0001}) - ipMutex: &sync.Mutex{}, - connMutex: &sync.Mutex{}, - DestInfo: info, - sessions: core.NewSessions(), - MockManagers: sync.Map{}, - Integrations: make(map[integrations.IntegrationType]integrations.Integrations), - } -} - -func (p *Proxy) InitIntegrations(_ context.Context) error { - // initialize the integrations - for parserType, parser := range integrations.Registered { - logger := p.logger.With(zap.Any("Type", parserType)) - prs := parser.Initializer(logger) - p.Integrations[parserType] = prs - p.integrationsPriority = append(p.integrationsPriority, ParserPriority{Priority: parser.Priority, ParserType: parserType}) - } - sort.Slice(p.integrationsPriority, func(i, j int) bool { - return p.integrationsPriority[i].Priority > p.integrationsPriority[j].Priority - }) - return nil -} - -func (p *Proxy) StartProxy(ctx context.Context, opts core.ProxyOptions) error { - - //first initialize the integrations - err := p.InitIntegrations(ctx) - if err != nil { - utils.LogError(p.logger, err, "failed to initialize the integrations") - return err - } - - // set up the CA for tls connections - err = pTls.SetupCA(ctx, p.logger) - if err != nil { - // log the error and continue - p.logger.Warn("failed to setup CA", zap.Error(err)) - } - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context") - } - // Create a channel to signal readiness of each server - readyChan := make(chan error, 1) - - // start the proxy server - g.Go(func() error { - defer utils.Recover(p.logger) - err := p.start(ctx, readyChan) - readyChan <- err - if err != nil { - utils.LogError(p.logger, err, "error while running the proxy server") - return err - } - return nil - }) - - //change the ip4 and ip6 if provided in the opts in case of docker environment - if len(opts.DNSIPv4Addr) != 0 { - p.IP4 = opts.DNSIPv4Addr - } - - if len(opts.DNSIPv6Addr) != 0 { - p.IP6 = opts.DNSIPv6Addr - } - - // start the TCP DNS server - p.logger.Debug("Starting Tcp Dns Server for handling Dns queries over TCP") - g.Go(func() error { - defer utils.Recover(p.logger) - errCh := make(chan error, 1) - go func(errCh chan error) { - defer utils.Recover(p.logger) - err := p.startTCPDNSServer(ctx) - if err != nil { - errCh <- err - } - }(errCh) - - select { - case <-ctx.Done(): - err := p.TCPDNSServer.Shutdown() - if err != nil { - utils.LogError(p.logger, err, "failed to shutdown tcp dns server") - return err - } - return nil - case err := <-errCh: - return err - } - }) - - // start the UDP DNS server - p.logger.Debug("Starting Udp Dns Server for handling Dns queries over UDP") - g.Go(func() error { - defer utils.Recover(p.logger) - errCh := make(chan error, 1) - go func(errCh chan error) { - defer utils.Recover(p.logger) - err := p.startUDPDNSServer(ctx) - if err != nil { - errCh <- err - } - }(errCh) - - select { - case <-ctx.Done(): - err := p.UDPDNSServer.Shutdown() - if err != nil { - utils.LogError(p.logger, err, "failed to shutdown tcp dns server") - return err - } - return nil - case err := <-errCh: - return err - } - }) - // Wait for the proxy server to be ready or fail - err = <-readyChan - if err != nil { - return err - } - p.logger.Info("Keploy has taken control of the DNS resolution mechanism, your application may misbehave if you have provided wrong domain name in your application code.") - - p.logger.Info(fmt.Sprintf("Proxy started at port:%v", p.Port)) - return nil -} - -// start function starts the proxy server on the idle local port -func (p *Proxy) start(ctx context.Context, readyChan chan<- error) error { - - // It will listen on all the interfaces - listener, err := net.Listen("tcp", fmt.Sprintf(":%v", p.Port)) - if err != nil { - utils.LogError(p.logger, err, fmt.Sprintf("failed to start proxy on port:%v", p.Port)) - // Notify failure - readyChan <- err - return err - } - p.Listener = listener - p.logger.Debug(fmt.Sprintf("Proxy server is listening on %s", fmt.Sprintf(":%v", listener.Addr()))) - // Signal that the server is ready - readyChan <- nil - defer func(listener net.Listener) { - err := listener.Close() - - if err != nil { - p.logger.Error("failed to close the listener", zap.Error(err)) - } - p.logger.Info("proxy stopped...") - }(listener) - - clientConnCtx, clientConnCancel := context.WithCancel(ctx) - clientConnErrGrp, _ := errgroup.WithContext(clientConnCtx) - defer func() { - clientConnCancel() - err := clientConnErrGrp.Wait() - if err != nil { - p.logger.Debug("failed to handle the client connection", zap.Error(err)) - } - //closing all the mock channels (if any in record mode) - for _, mc := range p.sessions.GetAllMC() { - if mc != nil { - close(mc) - } - } - - if string(p.nsswitchData) != "" { - // reset the hosts config in nsswitch.conf of the system (in test mode) - err = p.resetNsSwitchConfig() - if err != nil { - utils.LogError(p.logger, err, "failed to reset the nsswitch config") - } - } - }() - - for { - clientConnCh := make(chan net.Conn, 1) - errCh := make(chan error, 1) - go func() { - defer utils.Recover(p.logger) - conn, err := listener.Accept() - if err != nil { - if strings.Contains(err.Error(), "use of closed network connection") { - errCh <- nil - return - } - utils.LogError(p.logger, err, "failed to accept connection to the proxy") - errCh <- err - return - } - clientConnCh <- conn - }() - select { - case <-ctx.Done(): - return nil - case err := <-errCh: - return err - // handle the client connection - case clientConn := <-clientConnCh: - clientConnErrGrp.Go(func() error { - defer util.Recover(p.logger, clientConn, nil) - err := p.handleConnection(clientConnCtx, clientConn) - if err != nil && err != io.EOF { - utils.LogError(p.logger, err, "failed to handle the client connection") - } - return nil - }) - } - } -} - -// handleConnection function executes the actual outgoing network call and captures/forwards the request and response messages. -func (p *Proxy) handleConnection(ctx context.Context, srcConn net.Conn) error { - //checking how much time proxy takes to execute the flow. - start := time.Now() - - // making a new client connection id for each client connection - clientConnID := util.GetNextID() - - defer func(start time.Time) { - duration := time.Since(start) - p.logger.Debug("time taken by proxy to execute the flow", zap.Any("Client ConnectionID", clientConnID), zap.Any("Duration(ms)", duration.Milliseconds())) - }(start) - - // dstConn stores conn with actual destination for the outgoing network call - var dstConn net.Conn - - //Dialing for tls conn - destConnID := util.GetNextID() - - remoteAddr := srcConn.RemoteAddr().(*net.TCPAddr) - sourcePort := remoteAddr.Port - - p.logger.Debug("Inside handleConnection of proxyServer", zap.Any("source port", sourcePort), zap.Any("Time", time.Now().Unix())) - - destInfo, err := p.DestInfo.Get(ctx, uint16(sourcePort)) - if err != nil { - utils.LogError(p.logger, err, "failed to fetch the destination info", zap.Any("Source port", sourcePort)) - return err - } - - // releases the occupied source port when done fetching the destination info - err = p.DestInfo.Delete(ctx, uint16(sourcePort)) - if err != nil { - utils.LogError(p.logger, err, "failed to delete the destination info", zap.Any("Source port", sourcePort)) - return err - } - - //get the session rule - rule, ok := p.sessions.Get(destInfo.AppID) - if !ok { - utils.LogError(p.logger, nil, "failed to fetch the session rule", zap.Any("AppID", destInfo.AppID)) - return err - } - - var dstAddr string - - switch destInfo.Version { - case 4: - p.logger.Debug("the destination is ipv4") - dstAddr = fmt.Sprintf("%v:%v", util.ToIP4AddressStr(destInfo.IPv4Addr), destInfo.Port) - p.logger.Debug("", zap.Any("DestIp4", destInfo.IPv4Addr), zap.Any("DestPort", destInfo.Port)) - case 6: - p.logger.Debug("the destination is ipv6") - dstAddr = fmt.Sprintf("[%v]:%v", util.ToIPv6AddressStr(destInfo.IPv6Addr), destInfo.Port) - p.logger.Debug("", zap.Any("DestIp6", destInfo.IPv6Addr), zap.Any("DestPort", destInfo.Port)) - } - - // This is used to handle the parser errors - parserErrGrp, parserCtx := errgroup.WithContext(ctx) - parserCtx = context.WithValue(parserCtx, models.ErrGroupKey, parserErrGrp) - parserCtx = context.WithValue(parserCtx, models.ClientConnectionIDKey, fmt.Sprint(clientConnID)) - parserCtx = context.WithValue(parserCtx, models.DestConnectionIDKey, fmt.Sprint(destConnID)) - parserCtx, parserCtxCancel := context.WithCancel(parserCtx) - defer func() { - parserCtxCancel() - - if srcConn != nil { - err := srcConn.Close() - if err != nil { - if !strings.Contains(err.Error(), "use of closed network connection") { - utils.LogError(p.logger, err, "failed to close the source connection", zap.Any("clientConnID", clientConnID)) - } - return - } - } - - if dstConn != nil { - err = dstConn.Close() - if err != nil { - // Use string matching as a last resort to check for the specific error - if !strings.Contains(err.Error(), "use of closed network connection") { - // Log other errors - utils.LogError(p.logger, err, "failed to close the destination connection") - } - return - } - } - - err = parserErrGrp.Wait() - if err != nil { - utils.LogError(p.logger, err, "failed to handle the parser cleanUp") - } - }() - - //check for global passthrough in test mode - if !rule.Mocking && rule.Mode == models.MODE_TEST { - - dstConn, err = net.Dial("tcp", dstAddr) - if err != nil { - utils.LogError(p.logger, err, "failed to dial the conn to destination server", zap.Any("proxy port", p.Port), zap.Any("server address", dstAddr)) - return err - } - - err = p.globalPassThrough(parserCtx, srcConn, dstConn) - if err != nil { - utils.LogError(p.logger, err, "failed to handle the global pass through") - return err - } - return nil - } - - //checking for the destination port of "mysql" - if destInfo.Port == 3306 { - if rule.Mode != models.MODE_TEST { - dstConn, err = net.Dial("tcp", dstAddr) - if err != nil { - utils.LogError(p.logger, err, "failed to dial the conn to destination server", zap.Any("proxy port", p.Port), zap.Any("server address", dstAddr)) - return err - } - - dstCfg := &models.ConditionalDstCfg{ - Port: uint(destInfo.Port), - } - rule.DstCfg = dstCfg - - // Record the outgoing message into a mock - err := p.Integrations[integrations.MYSQL].RecordOutgoing(parserCtx, srcConn, dstConn, rule.MC, rule.OutgoingOptions) - if err != nil { - utils.LogError(p.logger, err, "failed to record the outgoing message") - return err - } - return nil - } - - m, ok := p.MockManagers.Load(destInfo.AppID) - if !ok { - utils.LogError(p.logger, nil, "failed to fetch the mock manager", zap.Any("AppID", destInfo.AppID)) - return err - } - - //mock the outgoing message - err := p.Integrations[integrations.MYSQL].MockOutgoing(parserCtx, srcConn, &models.ConditionalDstCfg{Addr: dstAddr}, m.(*MockManager), rule.OutgoingOptions) - if err != nil { - utils.LogError(p.logger, err, "failed to mock the outgoing message") - return err - } - return nil - } - - reader := bufio.NewReader(srcConn) - initialData := make([]byte, 5) - // reading the initial data from the client connection to determine if the connection is a TLS handshake - testBuffer, err := reader.Peek(len(initialData)) - if err != nil { - if err == io.EOF && len(testBuffer) == 0 { - p.logger.Debug("received EOF, closing conn", zap.Any("connectionID", clientConnID), zap.Error(err)) - return nil - } - utils.LogError(p.logger, err, "failed to peek the request message in proxy", zap.Any("proxy port", p.Port)) - return err - } - - multiReader := io.MultiReader(reader, srcConn) - srcConn = &util.Conn{ - Conn: srcConn, - Reader: multiReader, - Logger: p.logger, - } - - isTLS := pTls.IsTLSHandshake(testBuffer) - if isTLS { - srcConn, err = pTls.HandleTLSConnection(ctx, p.logger, srcConn, rule.Backdate) - if err != nil { - utils.LogError(p.logger, err, "failed to handle TLS conn") - return err - } - } - - clientID, ok := parserCtx.Value(models.ClientConnectionIDKey).(string) - if !ok { - utils.LogError(p.logger, err, "failed to fetch the client connection id") - return err - } - destID, ok := parserCtx.Value(models.DestConnectionIDKey).(string) - if !ok { - utils.LogError(p.logger, err, "failed to fetch the destination connection id") - return err - } - - logger := p.logger.With(zap.Any("Client ConnectionID", clientID), zap.Any("Destination ConnectionID", destID), zap.Any("Destination IP Address", dstAddr), zap.Any("Client IP Address", srcConn.RemoteAddr().String())) - - var initialBuf []byte - // attempt to read conn until buffer is either filled or conn is closed - initialBuf, err = util.ReadInitialBuf(parserCtx, p.logger, srcConn) - if err != nil { - utils.LogError(logger, err, "failed to read the initial buffer") - return err - } - - if util.IsHTTPReq(initialBuf) && !util.HasCompleteHTTPHeaders(initialBuf) { - // HTTP headers are never chunked according to the HTTP protocol, - // but at the TCP layer, we cannot be sure if we have received the entire - // header in the first buffer chunk. This is why we check if the headers are complete - // and read more data if needed. - - // Some HTTP requests, like AWS SQS, may require special handling in Keploy Enterprise. - // These cases may send partial headers in multiple chunks, so we need to read until - // we get the complete headers. - - logger.Debug("Partial HTTP headers detected, reading more data to get complete headers") - - // Read more data from the TCP connection to get the complete HTTP headers. - headerBuf, err := util.ReadHTTPHeadersUntilEnd(parserCtx, p.logger, srcConn) - if err != nil { - // Log the error if we fail to read the complete HTTP headers. - utils.LogError(logger, err, "failed to read the complete HTTP headers from client") - return err - } - // Append the additional data to the initial buffer. - initialBuf = append(initialBuf, headerBuf...) - } - - //update the src connection to have the initial buffer - srcConn = &util.Conn{ - Conn: srcConn, - Reader: io.MultiReader(bytes.NewReader(initialBuf), srcConn), - Logger: p.logger, - } - - dstCfg := &models.ConditionalDstCfg{ - Port: uint(destInfo.Port), - } - - //make new connection to the destination server - if isTLS { - - // get the destinationUrl from the map for the tls connection - url, ok := pTls.SrcPortToDstURL.Load(sourcePort) - if !ok { - utils.LogError(logger, err, "failed to fetch the destination url") - return err - } - //type case the dstUrl to string - dstURL, ok := url.(string) - if !ok { - utils.LogError(logger, err, "failed to type cast the destination url") - return err - } - - logger.Debug("the external call is tls-encrypted", zap.Any("isTLS", isTLS)) - cfg := &tls.Config{ - InsecureSkipVerify: true, - ServerName: dstURL, - } - - addr := fmt.Sprintf("%v:%v", dstURL, destInfo.Port) - if rule.Mode != models.MODE_TEST { - dstConn, err = tls.Dial("tcp", addr, cfg) - if err != nil { - utils.LogError(logger, err, "failed to dial the conn to destination server", zap.Any("proxy port", p.Port), zap.Any("server address", dstAddr)) - return err - } - } - - dstCfg.TLSCfg = cfg - dstCfg.Addr = addr - - } else { - if rule.Mode != models.MODE_TEST { - dstConn, err = net.Dial("tcp", dstAddr) - if err != nil { - utils.LogError(logger, err, "failed to dial the conn to destination server", zap.Any("proxy port", p.Port), zap.Any("server address", dstAddr)) - return err - } - } - dstCfg.Addr = dstAddr - } - - // get the mock manager for the current app - m, ok := p.MockManagers.Load(destInfo.AppID) - if !ok { - utils.LogError(logger, err, "failed to fetch the mock manager", zap.Any("AppID", destInfo.AppID)) - return err - } - - generic := true - - var matchedParser integrations.Integrations - var parserType integrations.IntegrationType - - //Checking for all the parsers according to their priority. - for _, parserPair := range p.integrationsPriority { // Iterate over ordered priority list - parser, exists := p.Integrations[parserPair.ParserType] - if !exists { - continue // Skip if parser not found - } - - p.logger.Debug("Checking for the parser", zap.Any("ParserType", parserPair.ParserType)) - if parser.MatchType(parserCtx, initialBuf) { - matchedParser = parser - parserType = parserPair.ParserType - generic = false - break - } - } - - if !generic { - p.logger.Debug("The external dependency is supported. Hence using the parser", zap.Any("ParserType", parserType)) - switch rule.Mode { - case models.MODE_RECORD: - err := matchedParser.RecordOutgoing(parserCtx, srcConn, dstConn, rule.MC, rule.OutgoingOptions) - if err != nil { - utils.LogError(logger, err, "failed to record the outgoing message") - return err - } - case models.MODE_TEST: - err := matchedParser.MockOutgoing(parserCtx, srcConn, dstCfg, m.(*MockManager), rule.OutgoingOptions) - if err != nil && err != io.EOF { - utils.LogError(logger, err, "failed to mock the outgoing message") - return err - } - } - } - - if generic { - logger.Debug("The external dependency is not supported. Hence using generic parser") - if rule.Mode == models.MODE_RECORD { - err := p.Integrations[integrations.GENERIC].RecordOutgoing(parserCtx, srcConn, dstConn, rule.MC, rule.OutgoingOptions) - if err != nil { - utils.LogError(logger, err, "failed to record the outgoing message") - return err - } - } else { - err := p.Integrations[integrations.GENERIC].MockOutgoing(parserCtx, srcConn, dstCfg, m.(*MockManager), rule.OutgoingOptions) - if err != nil { - utils.LogError(logger, err, "failed to mock the outgoing message") - return err - } - } - } - return nil -} - -func (p *Proxy) StopProxyServer(ctx context.Context) { - <-ctx.Done() - - p.logger.Info("stopping proxy server...") - - p.connMutex.Lock() - for _, clientConn := range p.clientConnections { - err := clientConn.Close() - if err != nil { - return - } - } - p.connMutex.Unlock() - - if p.Listener != nil { - err := p.Listener.Close() - if err != nil { - utils.LogError(p.logger, err, "failed to stop proxy server") - } - } - - // stop dns servers - err := p.stopDNSServers(ctx) - if err != nil { - utils.LogError(p.logger, err, "failed to stop the dns servers") - return - } - - p.logger.Info("proxy stopped...") -} - -func (p *Proxy) Record(_ context.Context, id uint64, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { - p.sessions.Set(id, &core.Session{ - ID: id, - Mode: models.MODE_RECORD, - MC: mocks, - OutgoingOptions: opts, - }) - - p.MockManagers.Store(id, NewMockManager(NewTreeDb(customComparator), NewTreeDb(customComparator), p.logger)) - - ////set the new proxy ip:port for a new session - //err := p.setProxyIP(opts.DnsIPv4Addr, opts.DnsIPv6Addr) - //if err != nil { - // return errors.New("failed to record the outgoing message") - //} - - return nil -} - -func (p *Proxy) Mock(_ context.Context, id uint64, opts models.OutgoingOptions) error { - p.sessions.Set(id, &core.Session{ - ID: id, - Mode: models.MODE_TEST, - OutgoingOptions: opts, - }) - p.MockManagers.Store(id, NewMockManager(NewTreeDb(customComparator), NewTreeDb(customComparator), p.logger)) - - if !opts.Mocking { - p.logger.Info("🔀 Mocking is disabled, the response will be fetched from the actual service") - } - - if string(p.nsswitchData) == "" { - // setup the nsswitch config to redirect the DNS queries to the proxy - err := p.setupNsswitchConfig() - if err != nil { - utils.LogError(p.logger, err, "failed to setup nsswitch config") - return errors.New("failed to mock the outgoing message") - } - } - - ////set the new proxy ip:port for a new session - //err := p.setProxyIP(opts.DnsIPv4Addr, opts.DnsIPv6Addr) - //if err != nil { - // return errors.New("failed to mock the outgoing message") - //} - - return nil -} - -func (p *Proxy) SetMocks(_ context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error { - //session, ok := p.sessions.Get(id) - //if !ok { - // return fmt.Errorf("session not found") - //} - m, ok := p.MockManagers.Load(id) - if ok { - m.(*MockManager).SetFilteredMocks(filtered) - m.(*MockManager).SetUnFilteredMocks(unFiltered) - } - - return nil -} - -// GetConsumedMocks returns the consumed filtered mocks for a given app id -func (p *Proxy) GetConsumedMocks(_ context.Context, id uint64) ([]models.MockState, error) { - m, ok := p.MockManagers.Load(id) - if !ok { - return nil, fmt.Errorf("mock manager not found to get consumed filtered mocks") - } - return m.(*MockManager).GetConsumedMocks(), nil -} diff --git a/keploy/pkg/core/proxy/tls/asset/ca.crt b/keploy/pkg/core/proxy/tls/asset/ca.crt deleted file mode 100755 index f42e240..0000000 --- a/keploy/pkg/core/proxy/tls/asset/ca.crt +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBczCCARigAwIBAgIUe1yXSfGefdyA09reeQpit2S7TXMwCgYIKoZIzj0EAwIw -FzEVMBMGA1UEAxMMTXkgQ3VzdG9tIENBMB4XDTIzMDQyMDExMDYwMFoXDTMzMDQx -NzExMDYwMFowFzEVMBMGA1UEAxMMTXkgQ3VzdG9tIENBMFkwEwYHKoZIzj0CAQYI -KoZIzj0DAQcDQgAE+QJh3KtWSGRe9NtzZGcJQBcn0ipsjzpo7YlLvbaSFeI62H1b -5EPUjnbDInG4K0nO1Dq/gPtnsVXZcJx06oHj9qNCMEAwDgYDVR0PAQH/BAQDAgEG -MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFDejUAabmE7NFoKSOo5uHAHMumal -MAoGCCqGSM49BAMCA0kAMEYCIQDkGU6Oku9vLo7fLLquHWXR1rB/1YgWIBghnQXJ -fF5GawIhAK/0BaJCVm6rkRuYCdtahkN1N5DtTPr8AQoDV+MT0UmL ------END CERTIFICATE----- diff --git a/keploy/pkg/core/proxy/tls/asset/ca.key b/keploy/pkg/core/proxy/tls/asset/ca.key deleted file mode 100755 index de51e1a..0000000 --- a/keploy/pkg/core/proxy/tls/asset/ca.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIHtVJjlhxefhiK/LyFlFUHgaA51mbtTLYJPdGWBZHeDSoAoGCCqGSM49 -AwEHoUQDQgAE+QJh3KtWSGRe9NtzZGcJQBcn0ipsjzpo7YlLvbaSFeI62H1b5EPU -jnbDInG4K0nO1Dq/gPtnsVXZcJx06oHj9g== ------END EC PRIVATE KEY----- diff --git a/keploy/pkg/core/proxy/tls/asset/setup_ca.sh b/keploy/pkg/core/proxy/tls/asset/setup_ca.sh deleted file mode 100755 index 76042a4..0000000 --- a/keploy/pkg/core/proxy/tls/asset/setup_ca.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/bash - -# Path to the CA certificate -caCertPath="./ca.crt" - -# Paths to check for CA store -caStorePaths=( - "/usr/local/share/ca-certificates/" - "/etc/pki/ca-trust/source/anchors/" - "/etc/ca-certificates/trust-source/anchors/" - "/etc/pki/trust/anchors/" - "/usr/local/share/certs/" - "/etc/ssl/certs/" -) -# Commands to tools the CA store -caStoreUpdateCmds=( - "update-ca-certificates" - "update-ca-trust" - "trust extract-compat" - "update-ca-trust extract" - "certctl rehash" -) - -# Java related variables -storePass="changeit" -alias="keployCA" - -# Check if directory exists -directory_exists() { - [[ -d $1 ]] -} - -# Check if command exists -command_exists() { - command -v $1 &> /dev/null -} - -# Check if Java is installed -is_java_installed() { - command_exists "java" -} - -# Update the CA store -update_ca_store() { - for cmd in "${caStoreUpdateCmds[@]}"; do - if command_exists "$cmd"; then - $cmd - fi - done -} - -# Install Java CA -install_java_ca() { - caPath=$1 - - if is_java_installed; then - javaHome=$(java -XshowSettings:properties -version 2>&1 > /dev/null | grep 'java.home' | awk -F'=' '{print $2}' | xargs) - cacertsPath="${javaHome}/lib/security/cacerts" - - keytool -list -keystore $cacertsPath -storepass $storePass -alias $alias &> /dev/null - if [ $? -eq 0 ]; then - echo "Java detected and CA already exists, cacertsPath:$cacertsPath" - return - fi - - keytool -import -trustcacerts -keystore $cacertsPath -storepass $storePass -noprompt -alias $alias -file $caPath - if [ $? -eq 0 ]; then - echo "Java detected and successfully imported CA, path:$cacertsPath" - else - echo "Java detected but failed to import CA" - fi - else - echo "Java is not installed on the system" - fi -} - -# Handle TLS setup -handle_tls_setup() { - for path in "${caStorePaths[@]}"; do - if directory_exists "$path"; then - caPath="${path}ca.crt" - cp $caCertPath $caPath - install_java_ca $caPath - fi - done - update_ca_store - -# Set the NODE_EXTRA_CA_CERTS environment variable - export NODE_EXTRA_CA_CERTS="/tmp/ca.crt" - cat $caCertPath > $NODE_EXTRA_CA_CERTS - -# Change the file permissions to allow read and write access for all users - chmod 0666 $NODE_EXTRA_CA_CERTS - -# Log the NODE_EXTRA_CA_CERTS environment variable - echo "NODE_EXTRA_CA_CERTS is set to: $NODE_EXTRA_CA_CERTS" - - # Set the REQUESTS_CA_BUNDLE to the same value as NODE_EXTRA_CA_CERTS for python - export REQUESTS_CA_BUNDLE=$NODE_EXTRA_CA_CERTS - - # Log the REQUESTS_CA_BUNDLE environment variable - echo "REQUESTS_CA_BUNDLE is set to: $REQUESTS_CA_BUNDLE" - - echo "Setup successful" -} - -# Execute the main function -handle_tls_setup \ No newline at end of file diff --git a/keploy/pkg/core/proxy/tls/ca.go b/keploy/pkg/core/proxy/tls/ca.go deleted file mode 100644 index 0220e0a..0000000 --- a/keploy/pkg/core/proxy/tls/ca.go +++ /dev/null @@ -1,345 +0,0 @@ -//go:build linux - -// Package tls provides functionality for handling tls connetions. -package tls - -import ( - "context" - "crypto" - "crypto/tls" - "crypto/x509" - "embed" - "fmt" - "net" - "os" - "os/exec" - "path/filepath" - "sync" - "time" - - "github.com/cloudflare/cfssl/csr" - cfsslLog "github.com/cloudflare/cfssl/log" - "github.com/cloudflare/cfssl/signer" - "github.com/cloudflare/cfssl/signer/local" - "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -//go:embed asset/ca.crt -var caCrt []byte //certificate - -//go:embed asset/ca.key -var caPKey []byte //private key - -//go:embed asset -var _ embed.FS - -var caStorePath = []string{ - "/usr/local/share/ca-certificates/", - "/etc/pki/ca-trust/source/anchors/", - "/etc/ca-certificates/trust-source/anchors/", - "/etc/pki/trust/anchors/", - "/usr/local/share/certs/", - "/etc/ssl/certs/", -} - -var caStoreUpdateCmd = []string{ - "update-ca-certificates", - "update-ca-trust", - "trust extract-compat", - "tools-ca-trust extract", - "certctl rehash", -} - -func commandExists(cmd string) bool { - _, err := exec.LookPath(cmd) - return err == nil -} - -func updateCaStore(ctx context.Context) error { - commandRun := false - for _, cmd := range caStoreUpdateCmd { - if commandExists(cmd) { - commandRun = true - c := exec.CommandContext(ctx, cmd) - _, err := c.CombinedOutput() - if err != nil { - select { - case <-ctx.Done(): - return ctx.Err() - default: - return err - } - } - } - } - if !commandRun { - return fmt.Errorf("no valid CA store tools command found") - } - return nil -} - -func getCaPaths() ([]string, error) { - var caPaths []string - for _, dir := range caStorePath { - if util.IsDirectoryExist(dir) { - caPaths = append(caPaths, dir) - } - } - if len(caPaths) == 0 { - return nil, fmt.Errorf("no valid CA store path found") - } - return caPaths, nil -} - -// to extract ca certificate to temp -func extractCertToTemp() (string, error) { - tempFile, err := os.CreateTemp("", "ca.crt") - - if err != nil { - return "", err - } - defer func(tempFile *os.File) { - err := tempFile.Close() - if err != nil { - return - } - }(tempFile) - - // Change the file permissions to allow read access for all users - err = os.Chmod(tempFile.Name(), 0666) - if err != nil { - return "", err - } - - // Write to the file - _, err = tempFile.Write(caCrt) - if err != nil { - return "", err - } - - // Close the file - err = tempFile.Close() - if err != nil { - return "", err - } - return tempFile.Name(), nil -} - -// isJavaCAExist checks if the CA is already installed in the specified Java keystore -func isJavaCAExist(ctx context.Context, alias, storepass, cacertsPath string) bool { - cmd := exec.CommandContext(ctx, "keytool", "-list", "-keystore", cacertsPath, "-storepass", storepass, "-alias", alias) - - err := cmd.Run() - select { - case <-ctx.Done(): - return false - default: - } - return err == nil -} - -// installJavaCA installs the CA in the Java keystore -func installJavaCA(ctx context.Context, logger *zap.Logger, caPath string) error { - // check if java is installed - if util.IsJavaInstalled() { - logger.Debug("checking java path from default java home") - javaHome, err := util.GetJavaHome(ctx) - - if err != nil { - utils.LogError(logger, err, "Java detected but failed to find JAVA_HOME") - return err - } - - // Assuming modern Java structure (without /jre/) - cacertsPath := fmt.Sprintf("%s/lib/security/cacerts", javaHome) - // You can modify these as per your requirements - storePass := "changeit" - alias := "keployCA" - - logger.Debug("", zap.Any("java_home", javaHome), zap.Any("caCertsPath", cacertsPath), zap.Any("caPath", caPath)) - - if isJavaCAExist(ctx, alias, storePass, cacertsPath) { - logger.Debug("Java detected and CA already exists", zap.String("path", cacertsPath)) - return nil - } - - cmd := exec.CommandContext(ctx, "keytool", "-import", "-trustcacerts", "-keystore", cacertsPath, "-storepass", storePass, "-noprompt", "-alias", alias, "-file", caPath) - cmdOutput, err := cmd.CombinedOutput() - - if err != nil { - select { - case <-ctx.Done(): - return ctx.Err() - default: - utils.LogError(logger, err, "Java detected but failed to import CA", zap.String("output", string(cmdOutput))) - return err - } - } - - logger.Debug("Java detected and successfully imported CA", zap.String("path", cacertsPath), zap.String("output", string(cmdOutput))) - logger.Debug("Successfully imported CA", zap.Any("", cmdOutput)) - } else { - logger.Debug("Java is not installed on the system") - } - return nil -} - -// TODO: This function should be used even before starting the proxy server. It should be called just after the keploy is started. -// because the custom ca in case of NODE is set via env variable NODE_EXTRA_CA_CERTS and env variables can be set only on startup. -// As in case of unit test integration, we are starting the proxy via api. - -// SetupCA setups custom certificate authority to handle TLS connections -func SetupCA(ctx context.Context, logger *zap.Logger) error { - caPaths, err := getCaPaths() - if err != nil { - utils.LogError(logger, err, "Failed to find the CA store path") - return err - } - - for _, path := range caPaths { - caPath := filepath.Join(path, "ca.crt") - - fs, err := os.Create(caPath) - if err != nil { - utils.LogError(logger, err, "Failed to create path for ca certificate", zap.Any("root store path", path)) - return err - } - - _, err = fs.Write(caCrt) - if err != nil { - utils.LogError(logger, err, "Failed to write custom ca certificate", zap.Any("root store path", path)) - return err - } - - // install CA in the java keystore if java is installed - err = installJavaCA(ctx, logger, caPath) - if err != nil { - utils.LogError(logger, err, "Failed to install CA in the java keystore") - return err - } - } - - // Update the trusted CAs store - err = updateCaStore(ctx) - if err != nil { - utils.LogError(logger, err, "Failed to update the CA store") - return err - } - - tempCertPath, err := extractCertToTemp() - if err != nil { - utils.LogError(logger, err, "Failed to extract certificate to tmp folder") - return err - } - - // for node - err = os.Setenv("NODE_EXTRA_CA_CERTS", tempCertPath) - if err != nil { - utils.LogError(logger, err, "Failed to set environment variable NODE_EXTRA_CA_CERTS") - return err - } - - // for python - err = os.Setenv("REQUESTS_CA_BUNDLE", tempCertPath) - if err != nil { - utils.LogError(logger, err, "Failed to set environment variable REQUESTS_CA_BUNDLE") - return err - } - return nil -} - -// SrcPortToDstURL map is used to store the mapping between source port and DstURL for the TLS connection -var SrcPortToDstURL = sync.Map{} - -var setLogLevelOnce sync.Once - -func CertForClient(logger *zap.Logger, clientHello *tls.ClientHelloInfo, caPrivKey any, caCertParsed *x509.Certificate, backdate time.Time) (*tls.Certificate, error) { - - // Ensure log level is set only once - - /* - * Since multiple goroutines can call this function concurrently, we need to ensure that the log level is set only once. - */ - setLogLevelOnce.Do(func() { - - // * Set the log level to error to avoid unnecessary logs. like below... - - // 2025/03/18 20:54:25 [INFO] received CSR - // 2025/03/18 20:54:25 [INFO] generating key: ecdsa-256 - // 2025/03/18 20:54:25 [INFO] received CSR - // 2025/03/18 20:54:25 [INFO] generating key: ecdsa-256 - // 2025/03/18 20:54:25 [INFO] encoded CSR - // 2025/03/18 20:54:25 [INFO] encoded CSR - // 2025/03/18 20:54:25 [INFO] signed certificate with serial number 435398774381835435678674951099961010543769077102 - - cfsslLog.Level = cfsslLog.LevelError - }) - - // Generate a new server certificate and private key for the given hostname - dstURL := clientHello.ServerName - - remoteAddr := clientHello.Conn.RemoteAddr().(*net.TCPAddr) - sourcePort := remoteAddr.Port - - SrcPortToDstURL.Store(sourcePort, dstURL) - - serverReq := &csr.CertificateRequest{ - //Make the name accordng to the ip of the request - CN: clientHello.ServerName, - Hosts: []string{ - clientHello.ServerName, - }, - KeyRequest: csr.NewKeyRequest(), - } - - serverCsr, serverKey, err := csr.ParseRequest(serverReq) - if err != nil { - return nil, fmt.Errorf("failed to create server CSR: %v", err) - } - cryptoSigner, ok := caPrivKey.(crypto.Signer) - if !ok { - return nil, fmt.Errorf("failed to typecast the caPrivKey") - } - signerd, err := local.NewSigner(cryptoSigner, caCertParsed, signer.DefaultSigAlgo(cryptoSigner), nil) - if err != nil { - return nil, fmt.Errorf("failed to create signer: %v", err) - } - - if backdate.IsZero() { - logger.Debug("backdate is zero, using current time") - backdate = time.Now() - } - - // Case: time freezing (an Ent. feature) is enabled, - // If application time is frozen in past, and the certificate is signed today, then the certificate will be invalid. - // This results in a certificate error during tls handshake. - // To avoid this, we set the certificate’s validity period (NotBefore and NotAfter) - // by referencing the testcase request time of the application (backdate) instead of the current real time. - // - // Note: If you have recorded test cases before April 20, 2024 (http://www.sslchecker.com/certdecoder?su=269725513dfeb137f6f29b8488f17ca9) - // and are using time freezing, please reach out to us if you get tls handshake error. - signReq := signer.SignRequest{ - Hosts: serverReq.Hosts, - Request: string(serverCsr), - Profile: "web", - NotBefore: backdate.AddDate(-1, 0, 0), - NotAfter: time.Now().AddDate(1, 0, 0), - } - - serverCert, err := signerd.Sign(signReq) - if err != nil { - return nil, fmt.Errorf("failed to sign server certificate: %v", err) - } - - logger.Debug("signed the certificate for a duration of 2 years", zap.Any("notBefore", signReq.NotBefore.String()), zap.Any("notAfter", signReq.NotAfter.String())) - - // Load the server certificate and private key - serverTLSCert, err := tls.X509KeyPair(serverCert, serverKey) - if err != nil { - return nil, fmt.Errorf("failed to load server certificate and key: %v", err) - } - - return &serverTLSCert, nil -} diff --git a/keploy/pkg/core/proxy/tls/tls.go b/keploy/pkg/core/proxy/tls/tls.go deleted file mode 100644 index 8c019f8..0000000 --- a/keploy/pkg/core/proxy/tls/tls.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build linux - -package tls - -import ( - "context" - "crypto/tls" - "net" - "time" - - "github.com/cloudflare/cfssl/helpers" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func IsTLSHandshake(data []byte) bool { - if len(data) < 5 { - return false - } - return data[0] == 0x16 && data[1] == 0x03 && (data[2] == 0x00 || data[2] == 0x01 || data[2] == 0x02 || data[2] == 0x03) -} - -func HandleTLSConnection(_ context.Context, logger *zap.Logger, conn net.Conn, backdate time.Time) (net.Conn, error) { - //Load the CA certificate and private key - - caPrivKey, err := helpers.ParsePrivateKeyPEM(caPKey) - if err != nil { - utils.LogError(logger, err, "Failed to parse CA private key") - return nil, err - } - caCertParsed, err := helpers.ParseCertificatePEM(caCrt) - if err != nil { - utils.LogError(logger, err, "Failed to parse CA certificate") - return nil, err - } - - // Create a TLS configuration - config := &tls.Config{ - GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { - return CertForClient(logger, clientHello, caPrivKey, caCertParsed, backdate) - }, - } - - // Wrap the TCP conn with TLS - tlsConn := tls.Server(conn, config) - // Perform the handshake - err = tlsConn.Handshake() - - if err != nil { - utils.LogError(logger, err, "failed to complete TLS handshake with the client") - return nil, err - } - // Use the tlsConn for further communication - // For example, you can read and write data using tlsConn.Read() and tlsConn.Write() - - // Here, we simply close the conn - return tlsConn, nil -} diff --git a/keploy/pkg/core/proxy/treedb.go b/keploy/pkg/core/proxy/treedb.go deleted file mode 100644 index 513d01a..0000000 --- a/keploy/pkg/core/proxy/treedb.go +++ /dev/null @@ -1,88 +0,0 @@ -//go:build linux - -package proxy - -// treeDb is a simple wrapper around redblacktree to provide thread safety -// Here it is used to handle the mocks. - -import ( - "sync" - - "github.com/emirpasic/gods/trees/redblacktree" - "go.keploy.io/server/v2/pkg/models" -) - -// customComparator is a custom comparator function for the tree db -var customComparator = func(a, b interface{}) int { - aStruct := a.(models.TestModeInfo) - bStruct := b.(models.TestModeInfo) - if aStruct.SortOrder < bStruct.SortOrder { - return -1 - } else if aStruct.SortOrder > bStruct.SortOrder { - return 1 - } - if aStruct.ID < bStruct.ID { - return -1 - } else if aStruct.ID > bStruct.ID { - return 1 - } - return 0 -} - -type TreeDb struct { - rbt *redblacktree.Tree - mu sync.RWMutex // RWMutex: many reads, few writes -} - -func NewTreeDb(comparator func(a, b interface{}) int) *TreeDb { - return &TreeDb{ - rbt: redblacktree.NewWith(comparator), - } -} - -func (db *TreeDb) insert(key interface{}, obj interface{}) { - db.mu.Lock() - db.rbt.Put(key, obj) - db.mu.Unlock() -} - -func (db *TreeDb) delete(key interface{}) bool { - db.mu.Lock() - defer db.mu.Unlock() - _, found := db.rbt.Get(key) - if !found { - return false - } - db.rbt.Remove(key) - return true -} - -func (db *TreeDb) update(oldKey interface{}, newKey interface{}, newObj interface{}) bool { - db.mu.Lock() - defer db.mu.Unlock() - _, found := db.rbt.Get(oldKey) - if !found { - return false - } - db.rbt.Remove(oldKey) - db.rbt.Put(newKey, newObj) - return true -} - -func (db *TreeDb) deleteAll() { - db.mu.Lock() - db.rbt.Clear() - db.mu.Unlock() -} - -// rangeValues iterates without allocating a []interface{} snapshot. -func (db *TreeDb) rangeValues(fn func(v interface{}) bool) { - db.mu.RLock() - it := db.rbt.Iterator() - for it.Next() { - if !fn(it.Value()) { - break - } - } - db.mu.RUnlock() -} diff --git a/keploy/pkg/core/proxy/util.go b/keploy/pkg/core/proxy/util.go deleted file mode 100644 index e6d1359..0000000 --- a/keploy/pkg/core/proxy/util.go +++ /dev/null @@ -1,75 +0,0 @@ -//go:build linux - -package proxy - -import ( - "context" - "fmt" - "io" - "net" - "os" - - pUtil "go.keploy.io/server/v2/pkg/core/proxy/util" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// writeNsswitchConfig writes the content to nsswitch.conf file -func writeNsswitchConfig(logger *zap.Logger, nsSwitchConfig string, data []byte, perm os.FileMode) error { - - err := os.WriteFile(nsSwitchConfig, data, perm) - if err != nil { - logger.Error("failed to write the configuration to the nsswitch.conf file to redirect the DNS queries to proxy", zap.Error(err)) - return err - } - return nil -} - -func (p *Proxy) globalPassThrough(ctx context.Context, client, dest net.Conn) error { - - logger := p.logger.With(zap.Any("Client ConnectionID", ctx.Value(models.ClientConnectionIDKey).(string)), zap.Any("Destination ConnectionID", ctx.Value(models.DestConnectionIDKey).(string)), zap.Any("Client IP Address", client.RemoteAddr().String())) - - clientBuffChan := make(chan []byte) - destBuffChan := make(chan []byte) - errChan := make(chan error, 2) - - // read requests from client - err := pUtil.ReadFromPeer(ctx, logger, client, clientBuffChan, errChan, pUtil.Client) - if err != nil { - return fmt.Errorf("error reading from client:%v", err) - } - - // read responses from destination - err = pUtil.ReadFromPeer(ctx, logger, dest, destBuffChan, errChan, pUtil.Destination) - if err != nil { - return fmt.Errorf("error reading from destination:%v", err) - } - - //write the request or response buffer to the respective destination - for { - select { - case <-ctx.Done(): - return ctx.Err() - case buffer := <-clientBuffChan: - // Write the request message to the destination - _, err := dest.Write(buffer) - if err != nil { - utils.LogError(logger, err, "failed to write request message to the destination server") - return fmt.Errorf("error writing to destination") - } - case buffer := <-destBuffChan: - // Write the response message to the client - _, err := client.Write(buffer) - if err != nil { - utils.LogError(logger, err, "failed to write response message to the client") - return fmt.Errorf("error writing to client") - } - case err := <-errChan: - if err == io.EOF { - return nil - } - return err - } - } -} diff --git a/keploy/pkg/core/proxy/util/util.go b/keploy/pkg/core/proxy/util/util.go deleted file mode 100755 index 2bd3d33..0000000 --- a/keploy/pkg/core/proxy/util/util.go +++ /dev/null @@ -1,648 +0,0 @@ -// Package util provides utility functions for the proxy package. -package util - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/binary" - "errors" - "fmt" - "io" - "os" - "os/exec" - "sync" - "sync/atomic" - "time" - - "github.com/getsentry/sentry-go" - "go.keploy.io/server/v2/pkg/models" - "golang.org/x/sync/errgroup" - - "go.keploy.io/server/v2/utils" - - "go.uber.org/zap" - - // "math/rand" - "net" - "strconv" - "strings" -) - -var Emoji = "\U0001F430" + " Keploy:" - -// idCounter is used to generate random ID for each request -var idCounter int64 = -1 - -func GetNextID() int64 { - return atomic.AddInt64(&idCounter, 1) -} - -// Conn is helpful for multiple reads from the same connection -type Conn struct { - net.Conn - Reader io.Reader - Logger *zap.Logger - mu sync.Mutex -} - -func (c *Conn) Read(p []byte) (int, error) { - c.mu.Lock() - defer c.mu.Unlock() - if len(p) == 0 { - c.Logger.Debug("the length is 0 for the reading from customConn") - } - return c.Reader.Read(p) -} - -type Peer string - -// Peer constants -const ( - Client Peer = "client" - Destination Peer = "destination" -) - -func HasCompleteHTTPHeaders(buf []byte) bool { - - // Check for the presence of the end of headers sequence "\r\n\r\n" - endOfHeaders := []byte("\r\n\r\n") - if len(buf) < len(endOfHeaders) { - return false - } - - // Check if the buffer contains the end of headers sequence - return bytes.Contains(buf, endOfHeaders) -} - -func IsHTTPReq(buf []byte) bool { - isHTTP := bytes.HasPrefix(buf[:], []byte("HTTP/")) || - bytes.HasPrefix(buf[:], []byte("GET ")) || - bytes.HasPrefix(buf[:], []byte("POST ")) || - bytes.HasPrefix(buf[:], []byte("PUT ")) || - bytes.HasPrefix(buf[:], []byte("PATCH ")) || - bytes.HasPrefix(buf[:], []byte("DELETE ")) || - bytes.HasPrefix(buf[:], []byte("OPTIONS ")) || - bytes.HasPrefix(buf[:], []byte("HEAD ")) || - bytes.HasPrefix(buf[:], []byte("CONNECT ")) - - return isHTTP -} - -// ReadBuffConn is used to read the buffer from the connection -func ReadBuffConn(ctx context.Context, logger *zap.Logger, conn net.Conn, bufferChannel chan []byte, errChannel chan error) { - //TODO: where to close the errChannel - for { - select { - case <-ctx.Done(): - // errChannel <- ctx.Err() - return - default: - if conn == nil { - logger.Debug("the conn is nil") - } - buffer, err := ReadBytes(ctx, logger, conn) - if err != nil { - if ctx.Err() != nil { // to avoid sending buffer to closed channel if the context is cancelled - return - } - if err != io.EOF { - utils.LogError(logger, err, "failed to read the packet message in proxy") - } - errChannel <- err - return - } - if ctx.Err() != nil { // to avoid sending buffer to closed channel if the context is cancelled - return - } - bufferChannel <- buffer - } - } -} - -func ReadHTTPHeadersUntilEnd(ctx context.Context, logger *zap.Logger, conn net.Conn) ([]byte, error) { - readErr := errors.New("failed to read HTTP headers") - - // Read the incoming data (headers) - initialBuf, err := ReadBytes(ctx, logger, conn) - - // Early return if we receive EOF with no data - if err == io.EOF && len(initialBuf) == 0 { - logger.Debug("received EOF, closing conn", zap.Error(err)) - return nil, readErr - } - - // Handle errors other than EOF - if err != nil && err != io.EOF { - utils.LogError(logger, err, "failed to read HTTP headers") - return nil, readErr - } - - // Check if the initial buffer already contains complete headers - if HasCompleteHTTPHeaders(initialBuf) { - logger.Debug("received complete HTTP headers in initial buffer", zap.Any("size", len(initialBuf)), zap.Any("headers", string(initialBuf))) - return initialBuf, nil - } - - // If not, continue reading until we find the end of headers - var buffer []byte - buffer = append(buffer, initialBuf...) - - for { - select { - case <-ctx.Done(): - return buffer, ctx.Err() - default: - // Check if the connection is nil - if conn == nil { - logger.Debug("the conn is nil") - return nil, readErr - } - // Read more data until we find the header end sequence - part, err := ReadBytes(ctx, logger, conn) - if err != nil { - if err == io.EOF && len(part) == 0 { - break // EOF reached, but nothing more to read - } - utils.LogError(logger, err, "error while reading HTTP headers") - return nil, readErr - } - - // Append the new data to the buffer - buffer = append(buffer, part...) - - // Check if we reached the end of headers - if HasCompleteHTTPHeaders(buffer) { - logger.Debug("received complete HTTP headers", zap.Any("size", len(buffer)), zap.Any("headers", string(buffer))) - return buffer, nil - } - } - } -} - -func ReadInitialBuf(ctx context.Context, logger *zap.Logger, conn net.Conn) ([]byte, error) { - readErr := errors.New("failed to read the initial request buffer") - - initialBuf, err := ReadBytes(ctx, logger, conn) - - if err == io.EOF && len(initialBuf) == 0 { - logger.Debug("received EOF, closing conn", zap.Error(err)) - return nil, readErr - } - - if err != nil && err != io.EOF { - utils.LogError(logger, err, "failed to read the request message in proxy") - return nil, readErr - } - - logger.Debug("received initial buffer", zap.Any("size", len(initialBuf)), zap.Any("initial buffer", initialBuf)) - return initialBuf, nil -} - -// ReadBytes function is utilized to read the complete message from the reader until the end of the file (EOF). -// It returns the content as a byte array. -func ReadBytes(ctx context.Context, logger *zap.Logger, reader io.Reader) ([]byte, error) { - var buffer []byte - const maxEmptyReads = 5 - emptyReads := 0 - - // Channel to communicate read results - readResult := make(chan struct { - n int - err error - buf []byte - }) - - g, ctx := errgroup.WithContext(ctx) - - defer func() { - err := g.Wait() - if err != nil { - utils.LogError(logger, err, "failed to read the request message in proxy") - } - close(readResult) - }() - - for { - // Start a goroutine to perform the read operation - g.Go(func() error { - defer Recover(logger, nil, nil) - buf := make([]byte, 1024) - n, err := reader.Read(buf) - if ctx.Err() != nil { - return nil - } - readResult <- struct { - n int - err error - buf []byte - }{n, err, buf} - return nil - }) - - // Use a select statement to wait for either the read result or context cancellation - select { - case <-ctx.Done(): - return buffer, ctx.Err() - case result := <-readResult: - if result.n > 0 { - buffer = append(buffer, result.buf[:result.n]...) - emptyReads = 0 // Reset the counter because we got some data - } - - if result.err != nil { - if result.err == io.EOF { - emptyReads++ - if emptyReads >= maxEmptyReads { - return buffer, result.err // Multiple EOFs in a row, probably a true EOF - } - time.Sleep(time.Millisecond * 100) // Sleep before trying again - continue - } - return buffer, result.err - } - if result.n < len(result.buf) { - return buffer, nil - } - } - } -} - -// ReadRequiredBytes ReadBytes function is utilized to read the required number of bytes from the reader. -// It returns the content as a byte array. -func ReadRequiredBytes(ctx context.Context, logger *zap.Logger, reader io.Reader, numBytes int) ([]byte, error) { - var buffer []byte - const maxEmptyReads = 5 - emptyReads := 0 - - // Channel to communicate read results - readResult := make(chan struct { - n int - err error - buf []byte - }) - - g, ctx := errgroup.WithContext(ctx) - - defer func() { - err := g.Wait() - if err != nil { - utils.LogError(logger, err, "failed to read the request message in proxy") - } - close(readResult) - }() - - for numBytes > 0 { - // Start a goroutine to perform the read operation - g.Go(func() error { - defer Recover(logger, nil, nil) - buf := make([]byte, numBytes) - n, err := reader.Read(buf) - if ctx.Err() != nil { - return nil - } - readResult <- struct { - n int - err error - buf []byte - }{n, err, buf} - return nil - }) - - // Use a select statement to wait for either the read result or context cancellation with timeout - select { - case <-ctx.Done(): - return buffer, ctx.Err() - // case <-time.After(5 * time.Second): - // logger.Error("timeout occurred while reading the packet") - // return buffer, context.DeadlineExceeded - case result := <-readResult: - if result.n > 0 { - buffer = append(buffer, result.buf[:result.n]...) - numBytes -= result.n - emptyReads = 0 // Reset the counter because we got some data - } - - if result.err != nil { - if result.err == io.EOF { - emptyReads++ - if emptyReads >= maxEmptyReads { - return buffer, result.err // Multiple EOFs in a row, probably a true EOF - } - time.Sleep(time.Millisecond * 100) // Sleep before trying again - continue - } - return buffer, result.err - } - - if numBytes == 0 { - return buffer, nil - } - } - } - - return buffer, nil -} - -// ReadFromPeer function is used to read the buffer from the peer connection. The peer can be either the client or the destination. -func ReadFromPeer(ctx context.Context, logger *zap.Logger, conn net.Conn, buffChan chan []byte, errChan chan error, peer Peer) error { - //get the error group from the context - g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group) - if !ok { - return errors.New("failed to get the error group from the context while reading from peer") - } - - var client, dest net.Conn - - if peer == Client { - client = conn - } else { - dest = conn - } - - g.Go(func() error { - defer Recover(logger, client, dest) - defer close(buffChan) - ReadBuffConn(ctx, logger, conn, buffChan, errChan) - return nil - }) - - return nil -} - -// PassThrough function is used to pass the network traffic to the destination connection. -// It also closes the destination connection if the function returns an error. -func PassThrough(ctx context.Context, logger *zap.Logger, clientConn net.Conn, dstCfg *models.ConditionalDstCfg, requestBuffer [][]byte) ([]byte, error) { - logger.Debug("passing through the network traffic to the destination server", zap.Any("Destination Addr", dstCfg.Addr)) - // making destConn - var destConn net.Conn - var err error - if dstCfg.TLSCfg != nil { - logger.Debug("trying to establish a TLS connection with the destination server", zap.Any("Destination Addr", dstCfg.Addr)) - - destConn, err = tls.Dial("tcp", dstCfg.Addr, dstCfg.TLSCfg) - if err != nil { - utils.LogError(logger, err, "failed to dial the conn to destination server", zap.Any("server address", dstCfg.Addr)) - return nil, err - } - logger.Debug("TLS connection established with the destination server", zap.Any("Destination Addr", destConn.RemoteAddr().String())) - } else { - logger.Debug("trying to establish a connection with the destination server", zap.Any("Destination Addr", dstCfg.Addr)) - destConn, err = net.Dial("tcp", dstCfg.Addr) - if err != nil { - utils.LogError(logger, err, "failed to dial the destination server") - return nil, err - } - logger.Debug("connection established with the destination server", zap.Any("Destination Addr", destConn.RemoteAddr().String())) - } - - logger.Debug("trying to forward requests to target", zap.Any("Destination Addr", destConn.RemoteAddr().String())) - for _, v := range requestBuffer { - _, err := destConn.Write(v) - if err != nil { - utils.LogError(logger, err, "failed to write request message to the destination server", zap.Any("Destination Addr", destConn.RemoteAddr().String())) - return nil, err - } - } - - // channels for writing messages from proxy to destination or client - destBufferChannel := make(chan []byte) - errChannel := make(chan error, 1) - - go func() { - defer Recover(logger, clientConn, nil) - defer close(destBufferChannel) - defer close(errChannel) - defer func(destConn net.Conn) { - err := destConn.Close() - if err != nil { - utils.LogError(logger, err, "failed to close the destination connection") - } - }(destConn) - - ReadBuffConn(ctx, logger, destConn, destBufferChannel, errChannel) - }() - - select { - case buffer := <-destBufferChannel: - // Write the response message to the client - _, err := clientConn.Write(buffer) - if err != nil { - if ctx.Err() != nil { - return nil, ctx.Err() - } - utils.LogError(logger, err, "failed to write response to the client") - return nil, err - } - - logger.Debug("the iteration for the generic response ends with responses:"+strconv.Itoa(len(buffer)), zap.Any("buffer", buffer)) - case err := <-errChannel: - // Applied this nolint to ignore the staticcheck error here because of readability - // nolint:staticcheck - if netErr, ok := err.(net.Error); !(ok && netErr.Timeout()) && err != nil { - return nil, err - } - return nil, nil - - case <-ctx.Done(): - return nil, ctx.Err() - } - - return nil, nil -} - -// ToIP4AddressStr converts the integer IP4 Address to the octet format -func ToIP4AddressStr(ip uint32) string { - // convert the IP address to a 32-bit binary number - ipBinary := fmt.Sprintf("%032b", ip) - - // divide the binary number into four 8-bit segments - firstByte, _ := strconv.ParseUint(ipBinary[0:8], 2, 64) - secondByte, _ := strconv.ParseUint(ipBinary[8:16], 2, 64) - thirdByte, _ := strconv.ParseUint(ipBinary[16:24], 2, 64) - fourthByte, _ := strconv.ParseUint(ipBinary[24:32], 2, 64) - - // concatenate the four decimal segments with a dot separator to form the dot-decimal string - return fmt.Sprintf("%d.%d.%d.%d", firstByte, secondByte, thirdByte, fourthByte) -} - -func ToIPv6AddressStr(ip [4]uint32) string { - // construct a byte slice - ipBytes := make([]byte, 16) // IPv6 address is 128 bits or 16 bytes long - for i := 0; i < 4; i++ { - // for each uint32, extract its four bytes and put them into the byte slice - ipBytes[i*4] = byte(ip[i] >> 24) - ipBytes[i*4+1] = byte(ip[i] >> 16) - ipBytes[i*4+2] = byte(ip[i] >> 8) - ipBytes[i*4+3] = byte(ip[i]) - } - // net.IP is a byte slice, so it can be directly used to construct an IPv6 address - ipv6Addr := net.IP(ipBytes) - return ipv6Addr.String() -} - -func GetLocalIPv4() (net.IP, error) { - ifaces, err := net.Interfaces() - if err != nil { - return nil, err - } - - for _, iface := range ifaces { - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - - for _, addr := range addrs { - ipNet, ok := addr.(*net.IPNet) - if ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil { - return ipNet.IP, nil - } - } - } - - return nil, fmt.Errorf("no valid IP address found") -} - -func ToIPV4(ip net.IP) (uint32, bool) { - ipv4 := ip.To4() - if ipv4 == nil { - return 0, false // Return 0 or handle the error accordingly - } - - return uint32(ipv4[0])<<24 | uint32(ipv4[1])<<16 | uint32(ipv4[2])<<8 | uint32(ipv4[3]), true -} - -func GetLocalIPv6() (net.IP, error) { - ifaces, err := net.Interfaces() - if err != nil { - return nil, err - } - - for _, iface := range ifaces { - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - - for _, addr := range addrs { - ipNet, ok := addr.(*net.IPNet) - if ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() == nil && ipNet.IP.To16() != nil { - return ipNet.IP, nil - } - } - } - - return nil, fmt.Errorf("no valid IPv6 address found") -} - -func IPv6ToUint32Array(ip net.IP) ([4]uint32, error) { - ip = ip.To16() - if ip == nil { - return [4]uint32{}, errors.New("invalid IPv6 address") - } - - return [4]uint32{ - binary.BigEndian.Uint32(ip[0:4]), - binary.BigEndian.Uint32(ip[4:8]), - binary.BigEndian.Uint32(ip[8:12]), - binary.BigEndian.Uint32(ip[12:16]), - }, nil -} - -func IPToDotDecimal(ip net.IP) string { - ipStr := ip.String() - if ip.To4() != nil { - ipStr = ip.To4().String() - } - return ipStr -} - -func IsDirectoryExist(path string) bool { - info, err := os.Stat(path) - if os.IsNotExist(err) { - return false - } - return info.IsDir() -} - -func IsJava(input string) bool { - // Convert the input string and the search term "java" to lowercase for a case-insensitive comparison. - inputLower := strings.ToLower(input) - searchTerm := "java" - searchTermLower := strings.ToLower(searchTerm) - - // Use strings.Contains to check if the lowercase input contains the lowercase search term. - return strings.Contains(inputLower, searchTermLower) -} - -// IsJavaInstalled checks if java is installed on the system -func IsJavaInstalled() bool { - _, err := exec.LookPath("java") - return err == nil -} - -// GetJavaHome returns the JAVA_HOME path -func GetJavaHome(ctx context.Context) (string, error) { - cmd := exec.CommandContext(ctx, "java", "-XshowSettings:properties", "-version") - var out bytes.Buffer - cmd.Stderr = &out // The output we need is printed to STDERR - - err := cmd.Run() - if err != nil { - select { - case <-ctx.Done(): - return "", ctx.Err() - default: - return "", err - } - } - - for _, line := range strings.Split(out.String(), "\n") { - if strings.Contains(line, "java.home") { - parts := strings.Split(line, "=") - if len(parts) > 1 { - return strings.TrimSpace(parts[1]), nil - } - } - } - - return "", fmt.Errorf("java.home not found in command output") -} - -// Recover recovers from a panic in any parser and logs the stack trace to Sentry. -// It also closes the client and destination connection. -func Recover(logger *zap.Logger, client, dest net.Conn) { - if logger == nil { - fmt.Println(Emoji + "Failed to recover from panic. Logger is nil.") - return - } - - sentry.Flush(2 * time.Second) - if r := recover(); r != nil { - logger.Error("Recovered from panic in parser, closing active connections") - if client != nil { - err := client.Close() - if err != nil { - // Use string matching as a last resort to check for the specific error - if !strings.Contains(err.Error(), "use of closed network connection") { - // Log other errors - utils.LogError(logger, err, "failed to close the client connection") - } - } - } - - if dest != nil { - err := dest.Close() - if err != nil { - // Use string matching as a last resort to check for the specific error - if !strings.Contains(err.Error(), "use of closed network connection") { - // Log other errors - utils.LogError(logger, err, "failed to close the destination connection") - } - } - } - utils.HandleRecovery(logger, r, "Recovered from panic") - sentry.Flush(time.Second * 2) - } -} diff --git a/keploy/pkg/core/record.go b/keploy/pkg/core/record.go deleted file mode 100644 index 471ebf4..0000000 --- a/keploy/pkg/core/record.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build linux - -package core - -import ( - "context" - - "go.keploy.io/server/v2/pkg/models" -) - -func (c *Core) GetIncoming(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) { - return c.Hooks.Record(ctx, id, opts) -} - -func (c *Core) GetOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) (<-chan *models.Mock, error) { - m := make(chan *models.Mock, 500) - - err := c.Proxy.Record(ctx, id, m, opts) - if err != nil { - return nil, err - } - - return m, nil -} diff --git a/keploy/pkg/core/replay.go b/keploy/pkg/core/replay.go deleted file mode 100644 index e1a4d68..0000000 --- a/keploy/pkg/core/replay.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build linux - -package core - -import ( - "context" - - "go.keploy.io/server/v2/pkg/models" -) - -func (c *Core) MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error { - - err := c.Mock(ctx, id, opts) - if err != nil { - return err - } - - return nil -} diff --git a/keploy/pkg/core/service.go b/keploy/pkg/core/service.go deleted file mode 100644 index 31f941d..0000000 --- a/keploy/pkg/core/service.go +++ /dev/null @@ -1,142 +0,0 @@ -//go:build linux - -package core - -import ( - "context" - "sync" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/core/app" - "go.keploy.io/server/v2/pkg/core/hooks/structs" - "go.keploy.io/server/v2/utils" - - "go.keploy.io/server/v2/pkg/models" -) - -type Hooks interface { - AppInfo - DestInfo - OutgoingInfo - Load(ctx context.Context, id uint64, cfg HookCfg) error - Record(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) - GetUnloadDone() <-chan struct{} -} - -type HookCfg struct { - AppID uint64 - Pid uint32 - IsDocker bool - KeployIPV4 string - Mode models.Mode - Rules []config.BypassRule - E2E bool - Port uint32 -} - -type App interface { - Setup(ctx context.Context, opts app.Options) error - Run(ctx context.Context, inodeChan chan uint64, opts app.Options) error - Kind(ctx context.Context) utils.CmdType - KeployIPv4Addr() string -} - -// Proxy listens on all available interfaces and forwards traffic to the destination -type Proxy interface { - StartProxy(ctx context.Context, opts ProxyOptions) error - Record(ctx context.Context, id uint64, mocks chan<- *models.Mock, opts models.OutgoingOptions) error - Mock(ctx context.Context, id uint64, opts models.OutgoingOptions) error - SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error - GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) -} - -type ProxyOptions struct { - // DNSIPv4Addr is the proxy IP returned by the DNS server. default is loopback address - DNSIPv4Addr string - // DNSIPv6Addr is the proxy IP returned by the DNS server. default is loopback address - DNSIPv6Addr string -} - -type DestInfo interface { - Get(ctx context.Context, srcPort uint16) (*NetworkAddress, error) - Delete(ctx context.Context, srcPort uint16) error -} - -type AppInfo interface { - SendDockerAppInfo(id uint64, dockerAppInfo structs.DockerAppInfo) error -} - -// For keploy test bench - -type Tester interface { - Setup(ctx context.Context, opts models.TestingOptions) error -} -type TestBenchInfo interface { - // SendKeployPids(key models.ModeKey, pid uint32) error - // SendKeployPorts(key models.ModeKey, port uint32) error -} - -// ---------------------- - -type OutgoingInfo interface { -} - -type NetworkAddress struct { - AppID uint64 - Version uint32 - IPv4Addr uint32 - IPv6Addr [4]uint32 - Port uint32 -} - -type Sessions struct { - sessions sync.Map -} - -func NewSessions() *Sessions { - return &Sessions{ - sessions: sync.Map{}, - } -} - -func (s *Sessions) Get(id uint64) (*Session, bool) { - v, ok := s.sessions.Load(id) - if !ok { - return nil, false - } - return v.(*Session), true -} - -func (s *Sessions) Set(id uint64, session *Session) { - s.sessions.Store(id, session) -} - -func (s *Sessions) Delete(id uint64) { - s.sessions.Delete(id) -} - -func (s *Sessions) getAll() map[uint64]*Session { - sessions := map[uint64]*Session{} - s.sessions.Range(func(k, v interface{}) bool { - sessions[k.(uint64)] = v.(*Session) - return true - }) - return sessions -} - -func (s *Sessions) GetAllMC() []chan<- *models.Mock { - sessions := s.getAll() - var mc []chan<- *models.Mock - for _, session := range sessions { - mc = append(mc, session.MC) - } - return mc -} - -type Session struct { - ID uint64 - Mode models.Mode - TC chan<- *models.TestCase - MC chan<- *models.Mock - models.OutgoingOptions -} diff --git a/keploy/pkg/core/tester/tester.go b/keploy/pkg/core/tester/tester.go deleted file mode 100644 index 68fbeb0..0000000 --- a/keploy/pkg/core/tester/tester.go +++ /dev/null @@ -1,134 +0,0 @@ -//go:build linux - -// Package tester provides functionality for testing keploy with itself -package tester - -import ( - "context" - "errors" - "fmt" - "time" - - "go.keploy.io/server/v2/pkg/core" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type Tester struct { - logger *zap.Logger - testBenchInfo core.TestBenchInfo -} - -func New(logger *zap.Logger, testBenchInfo core.TestBenchInfo) *Tester { - return &Tester{ - logger: logger, - testBenchInfo: testBenchInfo, - } -} - -const ( - testPort = 56789 - recordPort = 36789 -) - -func (t *Tester) Setup(ctx context.Context, opts models.TestingOptions) error { - t.logger.Info("🧪 setting up environment for testing keploy with itself") - - if opts.Mode == models.MODE_TEST { - err := t.setupReplay(ctx) - if err != nil { - return err - } - return nil - } - - err := t.setupRecord(ctx) - if err != nil { - return err - } - return nil -} - -func (t *Tester) setupReplay(ctx context.Context) error { - setUpErr := errors.New("failed to setup the keploy replay testing") - - recordPid, err := utils.GetPIDFromPort(ctx, t.logger, recordPort) - if err != nil { - t.logger.Error("failed to get the keployRecord pid", zap.Error(err)) - utils.LogError(t.logger, err, "failed to get the keployRecord pid from port", zap.Any("port", recordPort)) - return setUpErr - } - - t.logger.Debug(fmt.Sprintf("keployRecord pid:%v", recordPid)) - - // err = t.testBenchInfo.SendKeployPids(models.RecordKey, recordPid) - // if err != nil { - // utils.LogError(t.logger, err, fmt.Sprintf("failed to send keploy %v server pid to the epbf program", models.MODE_RECORD), zap.Any("Keploy Pid", recordPid)) - // return setUpErr - // } - - // err = t.testBenchInfo.SendKeployPorts(models.RecordKey, recordPort) - // if err != nil { - // utils.LogError(t.logger, err, fmt.Sprintf("failed to send keploy %v server port to the epbf program", models.MODE_RECORD), zap.Any("Keploy server port", recordPort)) - // return setUpErr - // } - - // err = t.testBenchInfo.SendKeployPorts(models.TestKey, testPort) - // if err != nil { - // utils.LogError(t.logger, err, fmt.Sprintf("failed to send keploy %v server port to the epbf program", models.MODE_TEST), zap.Any("Keploy server port", testPort)) - // return setUpErr - // } - - // to get the pid of keployTest binary in keployRecord binary, we have to wait for some time till the proxy server is started - // TODO: find other way to filter child process (keployTest) pid in parent process binary (keployRecord) - time.Sleep(10 * time.Second) // just for test bench. - - return nil -} - -func (t *Tester) setupRecord(ctx context.Context) error { - - go func() { - defer utils.Recover(t.logger) - - timeout := 30 * time.Second - startTime := time.Now() - - ticker := time.NewTicker(500 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - testPid, err := utils.GetPIDFromPort(ctx, t.logger, testPort) - if err != nil { - t.logger.Debug("failed to get the keploytest pid", zap.Error(err)) - continue - } - - if testPid == 0 { - continue - } - - t.logger.Debug("keploytest pid", zap.Uint32("pid", testPid)) - - // // sending keploytest binary pid in keployrecord binary to filter out ingress/egress calls related to keploytest binary. - // err = t.testBenchInfo.SendKeployPids(models.TestKey, testPid) - // if err != nil { - // utils.LogError(t.logger, err, fmt.Sprintf("failed to send keploy %v server pid to the epbf program", models.MODE_TEST), zap.Any("Keploy Pid", testPid)) - // } - return - case <-time.After(timeout - time.Since(startTime)): - t.logger.Info("Timeout reached, exiting loop from setupRecordTesting") - return // Exit the goroutine - - case <-ctx.Done(): - t.logger.Debug("Context cancelled, exiting loop from setupRecordTesting") - return // Exit the goroutine - } - } - }() - - return nil -} diff --git a/keploy/pkg/http2.go b/keploy/pkg/http2.go deleted file mode 100644 index 0d415a6..0000000 --- a/keploy/pkg/http2.go +++ /dev/null @@ -1,741 +0,0 @@ -// Package pkg provides common utilities for gRPC and HTTP/2 handling -package pkg - -import ( - "bytes" - "context" - "encoding/binary" - "fmt" - "io" - "net" - "strings" - "sync" - "time" - - "github.com/protocolbuffers/protoscope" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" - "golang.org/x/net/http2" - "golang.org/x/net/http2/hpack" -) - -// HTTP/2 constants -const ( - // DefaultMaxFrameSize is the default maximum frame size as per HTTP/2 spec (16KB) - DefaultMaxFrameSize = 16384 - // MaxFrameSize is the maximum allowed frame size (16MB) - MaxFrameSize = 16777215 // 2^24 - 1 - // HTTP2Preface is the HTTP/2 connection preface - HTTP2Preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" - MaxDynamicTableSize = 8192 -) - -// ExtractHTTP2Frame attempts to extract an HTTP/2 frame from raw bytes -func ExtractHTTP2Frame(data []byte) (http2.Frame, int, error) { - if len(data) < 9 { // Minimum frame size - return nil, 0, fmt.Errorf("incomplete frame: got %d bytes, need at least 9", len(data)) - } - - // First, read the frame header to determine the full frame length - // Length is a 24-bit unsigned integer - length := (uint32(data[0]) << 16) | (uint32(data[1]) << 8) | uint32(data[2]) - - // Validate length before proceeding - if length > MaxFrameSize { - return nil, 0, fmt.Errorf("frame length %d exceeds maximum allowed size %d", length, MaxFrameSize) - } - - // Check if we have the complete frame - totalSize := int(length) + 9 // frame header (9 bytes) + payload - if len(data) < totalSize { - return nil, 0, fmt.Errorf("incomplete frame: got %d bytes, need %d", len(data), totalSize) - } - - // Create a reader for just this frame - frameReader := bytes.NewReader(data[:totalSize]) - framer := http2.NewFramer(nil, frameReader) - - // Read the frame - frame, err := framer.ReadFrame() - if err != nil { - return nil, 0, fmt.Errorf("failed to read frame: %v", err) - } - - return frame, totalSize, nil -} - -// HTTP2StreamState represents the state of an HTTP/2 stream -type HTTP2StreamState struct { - // Stream identification - ID uint32 - RequestID string - ParentID string - isRequest bool - - // Timing - startTime time.Time - endTime time.Time - - // Message state - headerBlockFragments [][]byte - dataFrames [][]byte - isComplete bool - endStreamReceived bool - requestComplete bool - - // gRPC specific state - grpcReq *models.GrpcReq - grpcResp *models.GrpcResp - - // Header state - headersReceived bool - trailersReceived bool -} - -// HTTP2Stream represents a complete HTTP/2 stream -type HTTP2Stream struct { - ID uint32 - GRPCReq *models.GrpcReq - GRPCResp *models.GrpcResp -} - -// DefaultStreamManager implements stream management -type DefaultStreamManager struct { - mutex sync.RWMutex - streams map[uint32]*HTTP2StreamState - // completed []*HTTP2Stream - buffer []byte - logger *zap.Logger - decoder *hpack.Decoder - // Context-aware header tables - requestHeaders map[string]string // For request headers - responseHeaders map[string]string // For response headers - trailerHeaders map[string]string // For trailer headers -} - -// NewStreamManager creates a new stream manager -func NewStreamManager(logger *zap.Logger) *DefaultStreamManager { - return &DefaultStreamManager{ - streams: make(map[uint32]*HTTP2StreamState), - buffer: make([]byte, 0, DefaultMaxFrameSize), - logger: logger, - decoder: hpack.NewDecoder(MaxDynamicTableSize, nil), - // Initialize separate header tables - requestHeaders: make(map[string]string), - responseHeaders: make(map[string]string), - trailerHeaders: make(map[string]string), - } -} - -// storeHeaders stores headers in the appropriate connection header table based on context -func (sm *DefaultStreamManager) storeHeaders(headers *models.GrpcHeaders, isRequest bool, isTrailer bool) { - if headers == nil { - return - } - - // Select the appropriate header table - var headerTable map[string]string - if isTrailer { - headerTable = sm.trailerHeaders - } else if isRequest { - headerTable = sm.requestHeaders - } else { - headerTable = sm.responseHeaders - } - - // Store pseudo headers - for k, v := range headers.PseudoHeaders { - headerTable[k] = v - } - - // Store ordinary headers - for k, v := range headers.OrdinaryHeaders { - headerTable[k] = v - } -} - -// rehydrateHeaders adds missing headers from the appropriate connection header table -func (sm *DefaultStreamManager) rehydrateHeaders(headers *models.GrpcHeaders, isRequest bool, isTrailer bool) { - if headers == nil { - return - } - - // Select the appropriate header table - var headerTable map[string]string - if isTrailer { - headerTable = sm.trailerHeaders - } else if isRequest { - headerTable = sm.requestHeaders - } else { - headerTable = sm.responseHeaders - } - - // Initialize maps if nil - if headers.PseudoHeaders == nil { - headers.PseudoHeaders = make(map[string]string) - } - if headers.OrdinaryHeaders == nil { - headers.OrdinaryHeaders = make(map[string]string) - } - - // Add missing headers from the connection's header table - for k, v := range headerTable { - if strings.HasPrefix(k, ":") { - // This is a pseudo-header - if _, exists := headers.PseudoHeaders[k]; !exists { - headers.PseudoHeaders[k] = v - } - } else { - // This is an ordinary header - if _, exists := headers.OrdinaryHeaders[k]; !exists { - headers.OrdinaryHeaders[k] = v - } - } - } -} - -// HandleFrame processes an HTTP/2 frame -func (sm *DefaultStreamManager) HandleFrame(frame http2.Frame, isOutgoing bool, frameTime time.Time) error { - sm.mutex.Lock() - defer sm.mutex.Unlock() - - streamID := frame.Header().StreamID - - // Initialize stream if it doesn't exist - if _, exists := sm.streams[streamID]; !exists { - prefix := "Incoming_" - if isOutgoing { - prefix = "Outgoing_" - } - requestID := fmt.Sprintf(prefix+"%d", streamID) - sm.streams[streamID] = &HTTP2StreamState{ - ID: streamID, - RequestID: requestID, - isRequest: streamID != 0, // Stream 0 is for connection control - startTime: frameTime, - } - } - - stream := sm.streams[streamID] - - switch f := frame.(type) { - case *http2.HeadersFrame: - // Store header block fragments for later processing - stream.headerBlockFragments = append(stream.headerBlockFragments, f.HeaderBlockFragment()) - - if f.HeadersEnded() { - // Process complete headers - headerBlock := bytes.Join(stream.headerBlockFragments, nil) - stream.headerBlockFragments = nil - fields, err := sm.decoder.DecodeFull(headerBlock) - if err != nil { - return fmt.Errorf("failed to decode headers: %v", err) - } - - for _, field := range fields { - if field.Name == ":status" { - stream.isRequest = false - } - } - - headers := ProcessHeaders(fields) - - if !stream.headersReceived { - // These are initial headers - if stream.isRequest { - // Rehydrate from request headers - sm.rehydrateHeaders(headers, true, false) - - stream.grpcReq = &models.GrpcReq{ - Headers: *headers, - } - // Store headers for future requests - sm.storeHeaders(headers, true, false) - } else { - // Rehydrate from response headers - sm.rehydrateHeaders(headers, false, false) - - stream.grpcResp = &models.GrpcResp{ - Headers: *headers, - } - // Store headers for future responses - sm.storeHeaders(headers, false, false) - } - stream.headersReceived = true - } else if !stream.trailersReceived && !stream.isRequest { - // These are trailers (only for responses) - // Rehydrate from trailer headers - sm.rehydrateHeaders(headers, false, true) - - if stream.grpcResp != nil { - stream.grpcResp.Trailers = *headers - // Store headers for future trailers - sm.storeHeaders(headers, false, true) - } - stream.trailersReceived = true - } - } - - if f.StreamEnded() { - stream.endStreamReceived = true - // Process the complete message - if err := sm.processCompleteMessage(stream); err != nil { - return err - } - sm.checkStreamCompletion(streamID) - - // Clear header fragments and data frames after processing request part - if stream.isRequest { - stream.headerBlockFragments = nil - stream.endStreamReceived = false - stream.headersReceived = false - stream.trailersReceived = false - } else { - stream.endTime = frameTime - } - } - - case *http2.DataFrame: - // Store data frames for later processing - stream.dataFrames = append(stream.dataFrames, f.Data()) - - if f.StreamEnded() { - stream.endStreamReceived = true - // Process the complete message - if err := sm.processCompleteMessage(stream); err != nil { - return err - } - sm.checkStreamCompletion(streamID) - - // Clear header fragments and data frames after processing request part - if stream.isRequest { - stream.headerBlockFragments = nil - stream.endStreamReceived = false - stream.headersReceived = false - stream.trailersReceived = false - } else { - stream.endTime = frameTime - } - } - } - - return nil -} - -// GetCompleteStreams returns all completed HTTP/2 streams -func (sm *DefaultStreamManager) GetCompleteStreams() []*HTTP2Stream { - sm.mutex.Lock() - defer sm.mutex.Unlock() - - var completed []*HTTP2Stream - - for id, stream := range sm.streams { - if stream.isComplete { - stream.grpcReq.Timestamp = stream.startTime - stream.grpcResp.Timestamp = stream.endTime - http2Stream := &HTTP2Stream{ - ID: id, - GRPCReq: stream.grpcReq, - GRPCResp: stream.grpcResp, - } - completed = append(completed, http2Stream) - } - } - - return completed -} - -// CleanupStream removes a stream from the manager -func (sm *DefaultStreamManager) CleanupStream(streamID uint32) { - sm.mutex.Lock() - defer sm.mutex.Unlock() - delete(sm.streams, streamID) -} - -// processCompleteMessage processes a complete HTTP/2 message -func (sm *DefaultStreamManager) processCompleteMessage(stream *HTTP2StreamState) error { - if stream == nil { - return fmt.Errorf("nil stream") - } - - if len(stream.dataFrames) == 0 { - return nil - } - - // Combine all data frame fragments - data := bytes.Join(stream.dataFrames, nil) - - // Process the complete gRPC message - msg := CreateLengthPrefixedMessageFromPayload(data) - - if stream.isRequest { - if stream.grpcReq == nil { - stream.grpcReq = &models.GrpcReq{} - } - stream.grpcReq.Body = msg - } else { - if stream.grpcResp == nil { - stream.grpcResp = &models.GrpcResp{} - } - stream.grpcResp.Body = msg - } - - // Clear the data frames after processing - stream.dataFrames = nil - return nil -} - -// checkStreamCompletion checks if a stream is complete and processes it accordingly -func (sm *DefaultStreamManager) checkStreamCompletion(streamID uint32) { - stream := sm.streams[streamID] - - // For requests: mark the message part as complete but don't complete the stream - if stream.isRequest { - if stream.endStreamReceived && stream.headersReceived { - // Mark request part as complete but keep stream open for response - stream.requestComplete = true - } - return // Don't mark stream as complete yet - } - - // For responses: check if both request and response are complete - if !stream.isRequest && stream.requestComplete { - if stream.endStreamReceived && stream.headersReceived && stream.trailersReceived { // For gRPC, ensure trailers are received - - sm.logger.Debug("Stream completed", zap.Any("stream", stream)) - - stream.isComplete = true - } - } -} - -// ProcessHeaders converts HPACK header fields to a map -func ProcessHeaders(fields []hpack.HeaderField) *models.GrpcHeaders { - headers := &models.GrpcHeaders{ - PseudoHeaders: make(map[string]string), - OrdinaryHeaders: make(map[string]string), - } - - for _, field := range fields { - if len(field.Name) > 0 && field.Name[0] == ':' { - headers.PseudoHeaders[field.Name] = field.Value - } else { - headers.OrdinaryHeaders[field.Name] = field.Value - } - } - - return headers -} - -// IsGRPCGatewayRequest checks if the stream appears to be from gRPC-gateway that proxies http requests to grpc services -func IsGRPCGatewayRequest(stream *HTTP2Stream) bool { - if stream == nil || stream.GRPCReq == nil { - return false - } - - // Check for HTTP gateway specific headers - headers := stream.GRPCReq.Headers.OrdinaryHeaders - gatewayHeaders := []string{ - "grpc-gateway-user-agent", - } - - for _, header := range gatewayHeaders { - if _, exists := headers[header]; exists { - return true - } - } - - return false -} - -// SimulateGRPC simulates a gRPC call and returns the response -// This is a standalone version of the simulateGRPC method from Hooks -func SimulateGRPC(_ context.Context, tc *models.TestCase, testSetID string, logger *zap.Logger) (*models.GrpcResp, error) { - grpcReq := tc.GrpcReq - - logger.Info("starting test for of", zap.Any("test case", models.HighlightString(tc.Name)), zap.Any("test set", models.HighlightString(testSetID))) - - // Create a TCP connection - conn, err := net.Dial("tcp", grpcReq.Headers.PseudoHeaders[":authority"]) - if err != nil { - return nil, fmt.Errorf("failed to dial: %w", err) - } - defer func() { - if cerr := conn.Close(); cerr != nil { - logger.Error("failed to close connection", zap.Error(cerr)) - } - }() - - // Write HTTP/2 connection preface - if _, err := conn.Write([]byte(http2.ClientPreface)); err != nil { - return nil, fmt.Errorf("failed to write client preface: %w", err) - } - - // Create HTTP/2 client connection - framer := http2.NewFramer(conn, conn) - framer.AllowIllegalWrites = true // Allow HTTP/2 without TLS - - // Initial sequence of frames that gRPC sends: - // 1. Empty SETTINGS frame - // 2. Wait for server SETTINGS and ACK - // 3. Send SETTINGS ACK - // 4. HEADERS frame - // 5. DATA frame - // 6. WINDOW_UPDATE frame (connection-level) - // 7. PING frame - - // Send initial empty SETTINGS frame - err = framer.WriteSettings() - if err != nil { - return nil, fmt.Errorf("failed to write settings: %w", err) - } - - // Wait for server's SETTINGS and SETTINGS ACK - settingsReceived := false - settingsAckReceived := false - - for !settingsReceived || !settingsAckReceived { - frame, err := framer.ReadFrame() - if err != nil { - return nil, fmt.Errorf("failed to read server settings: %w", err) - } - - switch f := frame.(type) { - case *http2.SettingsFrame: - if f.IsAck() { - settingsAckReceived = true - } else { - settingsReceived = true - // Send ACK for server SETTINGS - if err := framer.WriteSettingsAck(); err != nil { - return nil, fmt.Errorf("failed to write settings ack: %w", err) - } - } - } - } - - // Create stream ID (client streams are odd-numbered) - streamID := uint32(1) - - // Prepare HEADERS frame - headerBuf := new(bytes.Buffer) - encoder := hpack.NewEncoder(headerBuf) - - // Write pseudo-headers first (order matters in HTTP/2) - pseudoHeaders := []struct { - name, value string - }{ - {":method", grpcReq.Headers.PseudoHeaders[":method"]}, - {":scheme", grpcReq.Headers.PseudoHeaders[":scheme"]}, - {":authority", grpcReq.Headers.PseudoHeaders[":authority"]}, - {":path", grpcReq.Headers.PseudoHeaders[":path"]}, - } - - for _, ph := range pseudoHeaders { - if err := encoder.WriteField(hpack.HeaderField{Name: ph.name, Value: ph.value}); err != nil { - return nil, fmt.Errorf("failed to encode pseudo-header %s: %w", ph.name, err) - } - } - - // Write regular headers in a specific order - orderedHeaders := []struct { - name, value string - }{} - - // Add any remaining headers from the request - for k, v := range grpcReq.Headers.OrdinaryHeaders { - orderedHeaders = append(orderedHeaders, struct{ name, value string }{k, v}) - } - - for _, h := range orderedHeaders { - if err := encoder.WriteField(hpack.HeaderField{Name: h.name, Value: h.value}); err != nil { - return nil, fmt.Errorf("failed to encode header %s: %w", h.name, err) - } - } - - // Send HEADERS frame - if err := framer.WriteHeaders(http2.HeadersFrameParam{ - StreamID: streamID, - BlockFragment: headerBuf.Bytes(), - EndHeaders: true, - EndStream: false, - }); err != nil { - return nil, fmt.Errorf("failed to write headers: %w", err) - } - - // Create and send DATA frame - payload, err := CreatePayloadFromLengthPrefixedMessage(grpcReq.Body) - if err != nil { - return nil, fmt.Errorf("failed to create payload: %w", err) - } - - if err := framer.WriteData(streamID, true, payload); err != nil { - return nil, fmt.Errorf("failed to write data: %w", err) - } - - // Send WINDOW_UPDATE frame for connection (stream 0) - if err := framer.WriteWindowUpdate(0, 983041); err != nil { - return nil, fmt.Errorf("failed to write window update: %w", err) - } - - // Send PING frame - pingData := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} // Example ping data - if err := framer.WritePing(false, pingData); err != nil { - return nil, fmt.Errorf("failed to write ping: %w", err) - } - - // Read response frames - grpcResp := &models.GrpcResp{ - Headers: models.GrpcHeaders{ - PseudoHeaders: make(map[string]string), - OrdinaryHeaders: make(map[string]string), - }, - Body: models.GrpcLengthPrefixedMessage{}, - Trailers: models.GrpcHeaders{ - PseudoHeaders: make(map[string]string), - OrdinaryHeaders: make(map[string]string), - }, - } - - // Read frames until we get the end of stream - streamEnded := false - for !streamEnded { - frame, err := framer.ReadFrame() - if err != nil { - if err == io.EOF { - break - } - return nil, fmt.Errorf("failed to read frame: %w", err) - } - - switch f := frame.(type) { - case *http2.HeadersFrame: - // If we already have headers, this must be trailers - if len(grpcResp.Headers.OrdinaryHeaders) > 0 || len(grpcResp.Headers.PseudoHeaders) > 0 { - decoder := hpack.NewDecoder(MaxDynamicTableSize, func(f hpack.HeaderField) { - if strings.HasPrefix(f.Name, ":") { - grpcResp.Trailers.PseudoHeaders[f.Name] = f.Value - } else { - grpcResp.Trailers.OrdinaryHeaders[f.Name] = f.Value - } - }) - if _, err := decoder.Write(f.HeaderBlockFragment()); err != nil { - return nil, fmt.Errorf("failed to decode trailers: %w", err) - } - } else { - // These are headers - decoder := hpack.NewDecoder(MaxDynamicTableSize, func(f hpack.HeaderField) { - if strings.HasPrefix(f.Name, ":") { - grpcResp.Headers.PseudoHeaders[f.Name] = f.Value - } else { - grpcResp.Headers.OrdinaryHeaders[f.Name] = f.Value - } - }) - if _, err := decoder.Write(f.HeaderBlockFragment()); err != nil { - return nil, fmt.Errorf("failed to decode headers: %w", err) - } - } - if f.StreamEnded() { - streamEnded = true - } - - case *http2.DataFrame: - frame, err := ReadGRPCFrame(bytes.NewReader(f.Data())) - if err != nil { - return nil, fmt.Errorf("failed to read gRPC frame: %w", err) - } - - grpcResp.Body = CreateLengthPrefixedMessageFromPayload(frame) - if f.StreamEnded() { - streamEnded = true - } - - case *http2.RSTStreamFrame: - // Stream was reset by peer - streamEnded = true - - case *http2.GoAwayFrame: - // Connection is being closed - streamEnded = true - } - } - - return grpcResp, nil -} - -// CreateLengthPrefixedMessageFromPayload creates a GrpcLengthPrefixedMessage from raw payload data -func CreateLengthPrefixedMessageFromPayload(data []byte) models.GrpcLengthPrefixedMessage { - msg := models.GrpcLengthPrefixedMessage{} - - // If the body is not length prefixed, we return the default value. - if len(data) < 5 { - return msg - } - - // The first byte is the compression flag. - msg.CompressionFlag = uint(data[0]) - - // The next 4 bytes are message length. - msg.MessageLength = binary.BigEndian.Uint32(data[1:5]) - - // The payload could be empty. We only parse it if it is present. - if len(data) > 5 { - // Use protoscope to decode the message. - msg.DecodedData = protoscope.Write(data[5:], protoscope.WriterOptions{}) - } - - return msg -} - -// CreatePayloadFromLengthPrefixedMessage converts a GrpcLengthPrefixedMessage to raw payload bytes -func CreatePayloadFromLengthPrefixedMessage(msg models.GrpcLengthPrefixedMessage) ([]byte, error) { - scanner := protoscope.NewScanner(msg.DecodedData) - encodedData, err := scanner.Exec() - if err != nil { - return nil, fmt.Errorf("could not encode grpc msg using protoscope: %v", err) - } - - // Note that the encoded length is present in the msg, but it is also equal to the len of encodedData. - // We should give the preference to the length of encodedData, since the mocks might have been altered. - - // Reserve 1 byte for compression flag, 4 bytes for length capture. - payload := make([]byte, 1+4) - payload[0] = uint8(msg.CompressionFlag) - binary.BigEndian.PutUint32(payload[1:5], uint32(len(encodedData))) - payload = append(payload, encodedData...) - - return payload, nil -} - -// ReadGRPCFrame reads a gRPC frame from the given reader and returns it as a byte array -func ReadGRPCFrame(r io.Reader) ([]byte, error) { - // Read compression flag (1 byte) - compFlag := make([]byte, 1) - if _, err := io.ReadFull(r, compFlag); err != nil { - if err == io.EOF { - return nil, err - } - return nil, fmt.Errorf("failed to read compression flag: %w", err) - } - - // Read message length (4 bytes) - lenBuf := make([]byte, 4) - if _, err := io.ReadFull(r, lenBuf); err != nil { - return nil, fmt.Errorf("failed to read message length: %w", err) - } - msgLen := binary.BigEndian.Uint32(lenBuf) - - // Read message data - msgBuf := make([]byte, msgLen) - if _, err := io.ReadFull(r, msgBuf); err != nil { - return nil, fmt.Errorf("failed to read message: %w", err) - } - - // Combine all parts - frame := make([]byte, 5+msgLen) - frame[0] = compFlag[0] - copy(frame[1:5], lenBuf) - copy(frame[5:], msgBuf) - - return frame, nil -} diff --git a/keploy/pkg/matcher/grpc/canonical.go b/keploy/pkg/matcher/grpc/canonical.go deleted file mode 100644 index 26183d6..0000000 --- a/keploy/pkg/matcher/grpc/canonical.go +++ /dev/null @@ -1,282 +0,0 @@ -package grpc - -import ( - "sort" - "strings" -) - -// Hard guards to prevent pathological work on giant or adversarial inputs. -const ( - maxCanonDepth = 64 // reasonable for nested protos - maxCanonBytes = 1 << 20 // 1 MiB per side; beyond this we bail out - maxBlocks = 10_000 // safety for degenerate line/block splits -) - -// canonicalizeTopLevelBlocks makes protoscope-like text order-insensitive at *all* levels by: -// - splitting into top-level field blocks -// - recursively canonicalizing the content of each "{...}" block -// - sorting blocks lexicographically -// - joining them back together -func CanonicalizeTopLevelBlocks(s string) string { - if len(s) > maxCanonBytes { - // Too large to safely canonicalize – return normalized but otherwise unchanged. - return normalizeWhitespace(s) - } - return canonicalizeRecursive(s, 0) -} - -func canonicalizeRecursive(s string, depth int) string { - if depth > maxCanonDepth { - return normalizeWhitespace(s) - } - trimmed := strings.TrimSpace(s) - if trimmed == "" { - return "" - } - - blocks := splitTopLevelBlocks(trimmed) - if len(blocks) > maxBlocks { - // Degenerate input: avoid n^2 sorts/work. - return normalizeWhitespace(s) - } - // Recursively canonicalize inner bodies of each block - for i := range blocks { - blocks[i] = canonicalizeBlock(blocks[i], depth) - blocks[i] = normalizeWhitespace(blocks[i]) - } - - // Order-insensitive among siblings - sort.Strings(blocks) - return strings.Join(blocks, "\n") -} - -// splitTopLevelBlocks groups lines into top-level "field blocks". -// A new block starts when depth==0 and the line matches ^\s*\d+: -// Bare tokens like "64", "ai64", etc. stay with the preceding block. -func splitTopLevelBlocks(s string) []string { - lines := strings.Split(s, "\n") - var blocks []string - var cur []string - depth := 0 - - isFieldLine := func(line string) bool { - line = strings.TrimLeft(line, " \t") - if line == "" { - return false - } - i := 0 - for i < len(line) && line[i] >= '0' && line[i] <= '9' { - i++ - } - return i > 0 && i < len(line) && line[i] == ':' - } - - flush := func() { - if len(cur) == 0 { - return - } - blk := strings.TrimRight(strings.Join(cur, "\n"), "\n") - if blk != "" { - blocks = append(blocks, blk) - } - cur = cur[:0] - } - - for i, ln := range lines { - // If we see a new top-level field start, flush previous block - if depth == 0 && isFieldLine(ln) && len(cur) > 0 { - flush() - } - cur = append(cur, ln) - depth += braceDeltaIgnoringStrings(ln) - if i == len(lines)-1 { - flush() - } - if len(blocks) > maxBlocks { - // Stop early; upstream will bail out. - return blocks - } - } - return blocks -} - -// canonicalizeBlock finds the outermost "{...}" of this block (if any), -// recursively canonicalizes the inside, and reassembles the block. -// If there is no brace, returns the block as-is (after whitespace normalize by caller). -func canonicalizeBlock(block string, depth int) string { - // Find first '{' outside strings/backticks. - open := indexFirstOpenBrace(block) - if open < 0 { - return block - } - - // Find its matching '}' (depth-aware, strings-safe). - closeIdx := findMatchingCloseBrace(block, open) - if closeIdx < 0 { - // Unbalanced; fallback to original to avoid mangling. - return block - } - - inner := block[open+1 : closeIdx] - innerCanon := canonicalizeRecursive(inner, depth+1) - - // Reassemble: keep exactly the same outer text, replace inner with canonical form. - return block[:open+1] + innerCanon + block[closeIdx:] -} - -// indexFirstOpenBrace returns the index of the first '{' not inside quotes/backticks. -func indexFirstOpenBrace(s string) int { - inDq := false // " - inBt := false // ` - esc := false - - for i := 0; i < len(s); i++ { - ch := s[i] - - if inBt { - if ch == '`' { - inBt = false - } - continue - } - if inDq { - if esc { - esc = false - continue - } - if ch == '\\' { - esc = true - continue - } - if ch == '"' { - inDq = false - } - continue - } - - // Not in string - if ch == '`' { - inBt = true - continue - } - if ch == '"' { - inDq = true - continue - } - if ch == '{' { - return i - } - } - return -1 -} - -// findMatchingCloseBrace finds the matching '}' for the '{' at openIdx, -// counting only braces that are outside quotes/backticks. -func findMatchingCloseBrace(s string, openIdx int) int { - inDq := false - inBt := false - esc := false - depth := 0 - - for i := openIdx; i < len(s); i++ { - ch := s[i] - - if inBt { - if ch == '`' { - inBt = false - } - continue - } - if inDq { - if esc { - esc = false - continue - } - if ch == '\\' { - esc = true - continue - } - if ch == '"' { - inDq = false - } - continue - } - - switch ch { - case '`': - inBt = true - case '"': - inDq = true - case '{': - depth++ - case '}': - depth-- - if depth == 0 { - return i - } - } - } - return -1 -} - -// braceDeltaIgnoringStrings returns net brace count change for the line, -// ignoring any braces that appear inside quotes/backticks. -func braceDeltaIgnoringStrings(line string) int { - inDq := false - inBt := false - esc := false - delta := 0 - - for i := 0; i < len(line); i++ { - ch := line[i] - - if inBt { - if ch == '`' { - inBt = false - } - continue - } - if inDq { - if esc { - esc = false - continue - } - if ch == '\\' { - esc = true - continue - } - if ch == '"' { - inDq = false - } - continue - } - - switch ch { - case '`': - inBt = true - case '"': - inDq = true - case '{': - delta++ - case '}': - delta-- - } - } - return delta -} - -func normalizeWhitespace(s string) string { - lines := strings.Split(s, "\n") - out := make([]string, 0, len(lines)) - prevBlank := false - for _, ln := range lines { - ln = strings.TrimRight(ln, " \t") - blank := strings.TrimSpace(ln) == "" - if blank && prevBlank { - continue - } - out = append(out, ln) - prevBlank = blank - } - return strings.Join(out, "\n") -} diff --git a/keploy/pkg/matcher/grpc/match.go b/keploy/pkg/matcher/grpc/match.go deleted file mode 100644 index c0f65c0..0000000 --- a/keploy/pkg/matcher/grpc/match.go +++ /dev/null @@ -1,234 +0,0 @@ -// Package grpc provides gRPC response matching functionality -package grpc - -import ( - "fmt" - "strings" - - "github.com/k0kubun/pp/v3" - "go.keploy.io/server/v2/pkg/matcher" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// Match compares an expected gRPC response with an actual response and returns whether they match -// along with detailed comparison results -func Match(tc *models.TestCase, actualResp *models.GrpcResp, noiseConfig map[string]map[string][]string, logger *zap.Logger) (bool, *models.Result) { - expectedResp := tc.GrpcResp - result := &models.Result{ - HeadersResult: make([]models.HeaderResult, 0), - BodyResult: make([]models.BodyResult, 0), - TrailerResult: make([]models.HeaderResult, 0), - } - - // Local variables to track overall match status - differences := make(map[string]struct { - Expected string - Actual string - Message string - }) - - // Only compare :status in pseudo headers - if expectedStatus, ok := expectedResp.Headers.PseudoHeaders[":status"]; ok { - actualStatus, exists := actualResp.Headers.PseudoHeaders[":status"] - headerResult := models.HeaderResult{ - Expected: models.Header{ - Key: ":status", - Value: []string{expectedStatus}, - }, - Actual: models.Header{ - Key: ":status", - Value: []string{}, - }, - } - - if !exists { - differences["headers.pseudo_headers.:status"] = struct { - Expected string - Actual string - Message string - }{ - Expected: expectedStatus, - Actual: "", - Message: "missing status header in response", - } - headerResult.Normal = false - } else { - headerResult.Actual.Value = []string{actualStatus} - headerResult.Normal = expectedStatus == actualStatus - - if !headerResult.Normal { - differences["headers.pseudo_headers.:status"] = struct { - Expected string - Actual string - Message string - }{ - Expected: expectedStatus, - Actual: actualStatus, - Message: "status header value mismatch", - } - } - } - - result.HeadersResult = append(result.HeadersResult, headerResult) - } - - // Compare Body - using specialized body types for gRPC - // Compare compression flag - compressionFlagNormal := expectedResp.Body.CompressionFlag == actualResp.Body.CompressionFlag - if !compressionFlagNormal { - differences["body.compression_flag"] = struct { - Expected string - Actual string - Message string - }{ - Expected: fmt.Sprintf("%d", expectedResp.Body.CompressionFlag), - Actual: fmt.Sprintf("%d", actualResp.Body.CompressionFlag), - Message: "compression flag mismatch", - } - } - result.BodyResult = append(result.BodyResult, models.BodyResult{ - Normal: compressionFlagNormal, - Type: models.GrpcCompression, - Expected: fmt.Sprintf("%d", expectedResp.Body.CompressionFlag), - Actual: fmt.Sprintf("%d", actualResp.Body.CompressionFlag), - }) - - // Compare message length - messageLengthNormal := expectedResp.Body.MessageLength == actualResp.Body.MessageLength - if !messageLengthNormal { - differences["body.message_length"] = struct { - Expected string - Actual string - Message string - }{ - Expected: fmt.Sprintf("%d", expectedResp.Body.MessageLength), - Actual: fmt.Sprintf("%d", actualResp.Body.MessageLength), - Message: "message length mismatch", - } - } - result.BodyResult = append(result.BodyResult, models.BodyResult{ - Normal: messageLengthNormal, - Type: models.GrpcLength, - Expected: fmt.Sprintf("%d", expectedResp.Body.MessageLength), - Actual: fmt.Sprintf("%d", actualResp.Body.MessageLength), - }) - - // Compare decoded data - expCanon := CanonicalizeTopLevelBlocks(expectedResp.Body.DecodedData) - actCanon := CanonicalizeTopLevelBlocks(actualResp.Body.DecodedData) - decodedDataNormal := expCanon == actCanon - - if !decodedDataNormal { - differences["body.decoded_data"] = struct { - Expected string - Actual string - Message string - }{ - Expected: expCanon, - Actual: actCanon, - Message: "decoded data mismatch", - } - } - result.BodyResult = append(result.BodyResult, models.BodyResult{ - Normal: decodedDataNormal, - Type: models.GrpcData, - Expected: expCanon, - Actual: actCanon, - }) - - // Handle noise configuration - var ( - bodyNoise = noiseConfig["body"] - headerNoise = noiseConfig["header"] - ) - - if bodyNoise == nil { - bodyNoise = map[string][]string{} - } - if headerNoise == nil { - headerNoise = map[string][]string{} - } - - // Apply noise configuration to ignore specified differences - for path := range differences { - pathParts := strings.Split(path, ".") - if len(pathParts) > 1 { - if pathParts[0] == "body" && len(bodyNoise) > 0 { - if _, found := bodyNoise[strings.Join(pathParts[1:], ".")]; found { - delete(differences, path) - } - } else if pathParts[0] == "headers" && len(headerNoise) > 0 { - if _, found := headerNoise[pathParts[len(pathParts)-1]]; found { - delete(differences, path) - } - } - } - } - - // Calculate final match status based on remaining differences - matched := len(differences) == 0 - - if !matched { - // Display differences to the user, similar to HTTP matcher - logDiffs := matcher.NewDiffsPrinter(tc.Name) - newLogger := pp.New() - newLogger.WithLineInfo = false - newLogger.SetColorScheme(models.GetFailingColorScheme()) - var logs = "" - - logs = logs + newLogger.Sprintf("Testrun failed for testcase with id: %s\n\n--------------------------------------------------------------------\n\n", tc.Name) - - // Display gRPC differences - if len(differences) > 0 { - for path, diff := range differences { - if strings.HasPrefix(path, "headers.") { - // Header differences - header := strings.TrimPrefix(path, "headers.") - logDiffs.PushHeaderDiff(diff.Expected, diff.Actual, header, headerNoise) - } else if strings.HasPrefix(path, "body.") { - bodyPart := strings.TrimPrefix(path, "body.") - switch bodyPart { - case "message_length": - // Message length is a good indicator of difference for gRPC - logDiffs.PushHeaderDiff(diff.Expected, diff.Actual, "message_length (body)", bodyNoise) - case "compression_flag": - // Compression flag - logDiffs.PushHeaderDiff(diff.Expected, diff.Actual, "compression_flag (body)", bodyNoise) - default: - // Any other body differences - logDiffs.PushBodyDiff(diff.Expected, diff.Actual, bodyNoise) - } - } - } - } else { - // If there are no specific differences but match still failed, show a generic message - logDiffs.PushHeaderDiff("See logs for details", "Matching failed", "gRPC", nil) - } - - // Print the differences - _, err := newLogger.Printf(logs) - if err != nil { - utils.LogError(logger, err, "failed to print the logs") - } - - err = logDiffs.Render() - if err != nil { - utils.LogError(logger, err, "failed to render the diffs") - } - } else { - // Display success message - newLogger := pp.New() - newLogger.WithLineInfo = false - newLogger.SetColorScheme(models.GetPassingColorScheme()) - var log2 = "" - log2 += newLogger.Sprintf("Testrun passed for testcase with id: %s\n\n--------------------------------------------------------------------\n\n", tc.Name) - _, err := newLogger.Printf(log2) - if err != nil { - utils.LogError(logger, err, "failed to print the logs") - } - } - - return matched, result -} diff --git a/keploy/pkg/matcher/http/absmatch.go b/keploy/pkg/matcher/http/absmatch.go deleted file mode 100644 index 4094f0f..0000000 --- a/keploy/pkg/matcher/http/absmatch.go +++ /dev/null @@ -1,472 +0,0 @@ -package http - -import ( - "encoding/json" - "strings" - - "go.keploy.io/server/v2/pkg" - matcher "go.keploy.io/server/v2/pkg/matcher" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -// AbsMatch (Absolute Match) compares two test cases and returns a boolean value indicating whether they are equal or not. -// It also returns a AbsResult object which contains the results of the comparison. -// Parameters: tcs1, tcs2, noiseConfig, ignoreOrdering, logger -// Returns: bool, *models.AbsResult -func AbsMatch(tcs1, tcs2 *models.TestCase, noiseConfig map[string]map[string][]string, ignoreOrdering bool, logger *zap.Logger) (bool, bool, bool, *models.AbsResult) { - if tcs1 == nil || tcs2 == nil { - logger.Error("test case is nil", zap.Any("tcs1", tcs1), zap.Any("tcs2", tcs2)) - return false, false, false, nil - } - - pass := true - absResult := &models.AbsResult{} - - kindResult := models.StringResult{ - Normal: true, - Expected: string(tcs1.Kind), - Actual: string(tcs2.Kind), - } - - nameResult := models.StringResult{ - Normal: true, - Expected: tcs1.Name, - Actual: tcs2.Name, - } - - // curlResult := models.StringResult{ - // Normal: true, - // Expected: tcs1.Curl, - // Actual: tcs2.Curl, - // } - - //compare kind - if tcs1.Kind != tcs2.Kind { - kindResult.Normal = false - logger.Info("test case kind is not equal", zap.Any("tcs1Kind", tcs1.Kind), zap.Any("tcs2Kind", tcs2.Kind)) - pass = false - } - - //compare name (just for debugging) - if tcs1.Name != tcs2.Name { - nameResult.Normal = false - logger.Debug("test case name is not equal", zap.Any("tcs1Name", tcs1.Name), zap.Any("tcs2Name", tcs2.Name)) - } - - //compare curl - // ok := CompareCurl(tcs1.Curl, tcs2.Curl, logger) - // if !ok { - // curlResult.Normal = false - // logger.Info("test case curl is not equal", zap.Any("tcs1Curl", tcs1.Curl), zap.Any("tcs2Curl", tcs2.Curl)) - // pass = false - // } - - //compare http req - reqPass, reqCompare := CompareHTTPReq(tcs1, tcs2, noiseConfig, ignoreOrdering, logger) - if !reqPass { - logger.Info("test case http req is not equal", zap.Any("tcs1HttpReq", tcs1.HTTPReq), zap.Any("tcs2HttpReq", tcs2.HTTPReq)) - pass = false - } - - //compare http resp - respPass, respCompare := CompareHTTPResp(tcs1, tcs2, noiseConfig, ignoreOrdering, logger) - if !respPass { - logger.Info("test case http resp is not equal", zap.Any("tcs1HttpResp", tcs1.HTTPResp), zap.Any("tcs2HttpResp", tcs2.HTTPResp)) - pass = false - } - - absResult.Name = nameResult - absResult.Kind = kindResult - absResult.Req = reqCompare - absResult.Resp = respCompare - // absResult.CurlResult = curlResult - - return pass, reqPass, respPass, absResult -} - -// CompareHTTPReq compares two http requests and returns a boolean value indicating whether they are equal or not. -func CompareHTTPReq(tcs1, tcs2 *models.TestCase, _ models.GlobalNoise, ignoreOrdering bool, logger *zap.Logger) (bool, models.ReqCompare) { - pass := true - //compare http req - reqCompare := models.ReqCompare{ - MethodResult: models.StringResult{ - Normal: true, - Expected: string(tcs1.HTTPReq.Method), - Actual: string(tcs2.HTTPReq.Method), - }, - URLResult: models.StringResult{ - Normal: true, - Expected: tcs1.HTTPReq.URL, - Actual: tcs2.HTTPReq.URL, - }, - URLParamsResult: []models.URLParamsResult{}, - ProtoMajor: models.IntResult{ - Normal: true, - Expected: tcs1.HTTPReq.ProtoMajor, - Actual: tcs2.HTTPReq.ProtoMajor, - }, - ProtoMinor: models.IntResult{ - Normal: true, - Expected: tcs1.HTTPReq.ProtoMinor, - Actual: tcs2.HTTPReq.ProtoMinor, - }, - HeaderResult: []models.HeaderResult{}, - BodyResult: models.BodyResult{ - Normal: true, - Expected: tcs1.HTTPReq.Body, - Actual: tcs2.HTTPReq.Body, - }, - } - - if tcs1.HTTPReq.Method != tcs2.HTTPReq.Method { - reqCompare.MethodResult.Normal = false - logger.Debug("test case http req method is not equal", zap.Any("tcs1HttpReqMethod", tcs1.HTTPReq.Method), zap.Any("tcs2HttpReqMethod", tcs2.HTTPReq.Method)) - pass = false - } - - if tcs1.HTTPReq.URL != tcs2.HTTPReq.URL { - reqCompare.URLResult.Normal = false - logger.Debug("test case http req url is not equal", zap.Any("tcs1HttpReqURL", tcs1.HTTPReq.URL), zap.Any("tcs2HttpReqURL", tcs2.HTTPReq.URL)) - pass = false - } - - if tcs1.HTTPReq.ProtoMajor != tcs2.HTTPReq.ProtoMajor { - reqCompare.ProtoMajor.Normal = false - logger.Debug("test case http req proto major is not equal", zap.Any("tcs1HttpReqProtoMajor", tcs1.HTTPReq.ProtoMajor), zap.Any("tcs2HttpReqProtoMajor", tcs2.HTTPReq.ProtoMajor)) - pass = false - } - - if tcs1.HTTPReq.ProtoMinor != tcs2.HTTPReq.ProtoMinor { - reqCompare.ProtoMinor.Normal = false - logger.Debug("test case http req proto minor is not equal", zap.Any("tcs1HttpReqProtoMinor", tcs1.HTTPReq.ProtoMinor), zap.Any("tcs2HttpReqProtoMinor", tcs2.HTTPReq.ProtoMinor)) - pass = false - } - - //compare url params - urlParams1 := tcs1.HTTPReq.URLParams - urlParams2 := tcs2.HTTPReq.URLParams - if len(urlParams1) == len(urlParams2) { - ok := CompareURLParams(urlParams1, urlParams2, &reqCompare.URLParamsResult) - if !ok { - logger.Debug("test case http req url params are not equal", zap.Any("tcs1HttpReqURLParams", tcs1.HTTPReq.URLParams), zap.Any("tcs2HttpReqURLParams", tcs2.HTTPReq.URLParams)) - pass = false - } - } else { - logger.Debug("test case http req url params are not equal", zap.Any("tcs1HttpReqURLParams", tcs1.HTTPReq.URLParams), zap.Any("tcs2HttpReqURLParams", tcs2.HTTPReq.URLParams)) - pass = false - } - - reqHeaderNoise := map[string][]string{} - reqHeaderNoise["keploy-test-id"] = []string{} - reqHeaderNoise["keploy-test-set-id"] = []string{} - tcs1.HTTPReq.Header["Keploy-Test-Id"] = "dummyTest" - tcs1.HTTPReq.Header["Keploy-Test-Set-Id"] = "dummyTestSet" - - // compare http req headers - ok := matcher.CompareHeaders(pkg.ToHTTPHeader(tcs1.HTTPReq.Header), pkg.ToHTTPHeader(tcs2.HTTPReq.Header), &reqCompare.HeaderResult, reqHeaderNoise) - if !ok { - logger.Debug("test case http req headers are not equal", zap.Any("tcs1HttpReqHeaders", tcs1.HTTPReq.Header), zap.Any("tcs2HttpReqHeaders", tcs2.HTTPReq.Header)) - pass = false - } - - reqBodyNoise := map[string][]string{} - - // compare http req body - bodyType1 := models.Plain - if json.Valid([]byte(tcs1.HTTPReq.Body)) { - bodyType1 = models.JSON - } - - bodyType2 := models.Plain - if json.Valid([]byte(tcs2.HTTPReq.Body)) { - bodyType2 = models.JSON - } - - if bodyType1 != bodyType2 { - logger.Debug("test case http req body type is not equal", zap.Any("tcs1HttpReqBodyType", bodyType1), zap.Any("tcs2HttpReqBodyType", bodyType2)) - pass = false - reqCompare.BodyResult.Normal = false - return pass, reqCompare - } - - bodyRes := true - // stores the json body after removing the noise - cleanExp, cleanAct := tcs1.HTTPReq.Body, tcs2.HTTPReq.Body - var jsonComparisonResult matcher.JSONComparisonResult - if !matcher.Contains(matcher.MapToArray(reqBodyNoise), "body") && bodyType1 == models.JSON { - //validate the stored json - validatedJSON, err := matcher.ValidateAndMarshalJSON(logger, &cleanExp, &cleanAct) - if err != nil { - logger.Error("failed to validate and marshal json", zap.Error(err)) - reqCompare.BodyResult.Normal = false - return false, reqCompare - } - if validatedJSON.IsIdentical() { - jsonComparisonResult, err = matcher.JSONDiffWithNoiseControl(validatedJSON, reqBodyNoise, ignoreOrdering) - exact := jsonComparisonResult.IsExact() - if err != nil { - logger.Error("failed to compare json", zap.Error(err)) - reqCompare.BodyResult.Normal = false - return false, reqCompare - } - - if !exact { - pass = false - bodyRes = false - } - } else { - pass = false - bodyRes = false - } - - // debug log for cleanExp and cleanAct - logger.Debug("cleanExp", zap.Any("", cleanExp)) - logger.Debug("cleanAct", zap.Any("", cleanAct)) - } else { - if !matcher.Contains(matcher.MapToArray(reqBodyNoise), "body") && tcs1.HTTPReq.Body != tcs2.HTTPReq.Body { - pass = false - bodyRes = false - } - } - - if !bodyRes { - reqCompare.BodyResult.Normal = false - } - - return pass, reqCompare -} - -// CompareHTTPResp compares two http responses and returns a boolean value indicating whether they are equal or not. -func CompareHTTPResp(tcs1, tcs2 *models.TestCase, noiseConfig models.GlobalNoise, ignoreOrdering bool, logger *zap.Logger) (bool, models.RespCompare) { - pass := true - //compare http resp - respCompare := models.RespCompare{ - StatusCode: models.IntResult{ - Normal: true, - Expected: tcs1.HTTPResp.StatusCode, - Actual: tcs2.HTTPResp.StatusCode, - }, - HeadersResult: []models.HeaderResult{}, - BodyResult: models.BodyResult{ - Normal: true, - Expected: tcs1.HTTPResp.Body, - Actual: tcs2.HTTPResp.Body, - }, - } - - if tcs1.HTTPResp.StatusCode != tcs2.HTTPResp.StatusCode { - respCompare.StatusCode.Normal = false - logger.Debug("test case http resp status code is not equal", zap.Any("tcs1HttpRespStatusCode", tcs1.HTTPResp.StatusCode), zap.Any("tcs2HttpRespStatusCode", tcs2.HTTPResp.StatusCode)) - pass = false - } - - //compare the auto added noise in test case - noise1 := tcs1.Noise - noise2 := tcs2.Noise - ok := CompareNoise(noise1, noise2) - if !ok { - logger.Debug("test case noise is not equal", zap.Any("tcs1Noise", tcs1.Noise), zap.Any("tcs2Noise", tcs2.Noise)) - logger.Debug("response body and headers can not be compared because noise is not equal") - pass = false - respCompare.BodyResult.Normal = false - return pass, respCompare - } - - noise := noise1 - - var ( - bodyNoise = noiseConfig["body"] - headerNoise = noiseConfig["header"] - ) - - if bodyNoise == nil { - bodyNoise = map[string][]string{} - } - if headerNoise == nil { - headerNoise = map[string][]string{} - } - - for field, regexArr := range noise { - a := strings.Split(field, ".") - if len(a) > 1 && a[0] == "body" { - x := strings.Join(a[1:], ".") - bodyNoise[strings.ToLower(x)] = regexArr - } else if a[0] == "header" { - headerNoise[strings.ToLower(a[len(a)-1])] = regexArr - } - } - - // compare http resp headers - ok = matcher.CompareHeaders(pkg.ToHTTPHeader(tcs1.HTTPResp.Header), pkg.ToHTTPHeader(tcs2.HTTPResp.Header), &respCompare.HeadersResult, headerNoise) - if !ok { - logger.Debug("test case http resp headers are not equal", zap.Any("tcs1HttpRespHeaders", tcs1.HTTPResp.Header), zap.Any("tcs2HttpRespHeaders", tcs2.HTTPResp.Header)) - pass = false - } - - // compare http resp body - bodyType1 := models.Plain - if json.Valid([]byte(tcs1.HTTPResp.Body)) { - bodyType1 = models.JSON - } - - bodyType2 := models.Plain - if json.Valid([]byte(tcs2.HTTPResp.Body)) { - bodyType2 = models.JSON - } - - if bodyType1 != bodyType2 { - logger.Debug("test case http resp body type is not equal", zap.Any("tcs1HttpRespBodyType", bodyType1), zap.Any("tcs2HttpRespBodyType", bodyType2)) - pass = false - respCompare.BodyResult.Normal = false - return pass, respCompare - } - - bodyRes := true - - // stores the json body after removing the noise - cleanExp, cleanAct := tcs1.HTTPResp.Body, tcs2.HTTPResp.Body - var jsonComparisonResult matcher.JSONComparisonResult - if !matcher.Contains(matcher.MapToArray(noise), "body") && bodyType1 == models.JSON { - //validate the stored json - validatedJSON, err := matcher.ValidateAndMarshalJSON(logger, &cleanExp, &cleanAct) - if err != nil { - logger.Error("failed to validate and marshal json", zap.Error(err)) - respCompare.BodyResult.Normal = false - return false, respCompare - } - if validatedJSON.IsIdentical() { - jsonComparisonResult, err = matcher.JSONDiffWithNoiseControl(validatedJSON, bodyNoise, ignoreOrdering) - exact := jsonComparisonResult.IsExact() - if err != nil { - logger.Error("failed to compare json", zap.Error(err)) - respCompare.BodyResult.Normal = false - return false, respCompare - } - if !exact { - pass = false - bodyRes = false - } - } else { - pass = false - bodyRes = false - } - - // debug log for cleanExp and cleanAct - logger.Debug("cleanExp", zap.Any("", cleanExp)) - logger.Debug("cleanAct", zap.Any("", cleanAct)) - } else { - if !matcher.Contains(matcher.MapToArray(noise), "body") && tcs1.HTTPResp.Body != tcs2.HTTPResp.Body { - pass = false - bodyRes = false - } - } - - if !bodyRes { - respCompare.BodyResult.Normal = false - } - return pass, respCompare -} - -func CompareURLParams(urlParams1, urlParams2 map[string]string, urlParamsResult *[]models.URLParamsResult) bool { - pass := true - for k, v := range urlParams1 { - if v2, ok := urlParams2[k]; ok { - - if v != v2 { - pass = false - } - - *urlParamsResult = append(*urlParamsResult, models.URLParamsResult{ - Normal: v == v2, - Expected: models.Params{ - Key: k, - Value: v, - }, - Actual: models.Params{ - Key: k, - Value: v2, - }, - }) - } else { - pass = false - } - } - return pass -} - -func CompareNoise(noise1, noise2 map[string][]string) bool { - pass := true - for k, v := range noise1 { - if v2, ok := noise2[k]; ok { - if len(v) != len(v2) { - pass = false - } else { - for i := 0; i < len(v); i++ { - if v[i] != v2[i] { - pass = false - } - } - } - } else { - pass = false - } - } - return pass -} - -func CompareCurl(curl1, curl2 string, logger *zap.Logger) bool { - // Parse the values into method, URL, headers, and data - method1, url1, headers1, data1 := parseCurlString(curl1) - method2, url2, headers2, data2 := parseCurlString(curl2) - - // Compare method, URL, and data - if method1 != method2 || url1 != url2 || data1 != data2 { - return false - } - - // remove any quotes from the header keys (if any due to parsing issues) - for k, v := range headers1 { - delete(headers1, k) - headers1[strings.Trim(k, "'")] = v - } - for k, v := range headers2 { - delete(headers2, k) - headers2[strings.Trim(k, "'")] = v - } - - curlHeaderNoise := map[string][]string{} - curlHeaderNoise["keploy-test-id"] = []string{} - curlHeaderNoise["keploy-test-set-id"] = []string{} - headers1["Keploy-Test-Id"] = "dummyTest" - headers1["Keploy-Test-Set-Id"] = "dummyTestSet" - - hres := []models.HeaderResult{} - ok := matcher.CompareHeaders(pkg.ToHTTPHeader(headers1), pkg.ToHTTPHeader(headers2), &hres, curlHeaderNoise) - if !ok { - logger.Debug("test case curl headers are not equal", zap.Any("curlHeaderResult", hres)) - return false - } - return true -} - -func parseCurlString(curlString string) (method, url string, headers map[string]string, data string) { - lines := strings.Split(curlString, "\\") - headers = make(map[string]string) - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "--request") { - method = strings.TrimSpace(strings.Split(line, " ")[1]) - } else if strings.HasPrefix(line, "--url") { - url = strings.TrimSpace(strings.Split(line, " ")[1]) - } else if strings.HasPrefix(line, "--header") { - headerParts := strings.SplitN(strings.TrimSpace(strings.TrimPrefix(line, "--header")), ":", 2) - if len(headerParts) == 2 { - headers[strings.TrimSpace(headerParts[0])] = strings.TrimSpace(headerParts[1]) - } - } else if strings.HasPrefix(line, "--data") { - data = strings.TrimSpace(strings.TrimPrefix(line, "--data")) - } - } - return -} diff --git a/keploy/pkg/matcher/http/match.go b/keploy/pkg/matcher/http/match.go deleted file mode 100644 index 4489c39..0000000 --- a/keploy/pkg/matcher/http/match.go +++ /dev/null @@ -1,536 +0,0 @@ -// Package http for http matching -package http - -import ( - "encoding/json" - "fmt" - "net/http" - "regexp" - "strconv" - "strings" - - "go.uber.org/zap" - - "github.com/k0kubun/pp/v3" - "github.com/wI2L/jsondiff" - "go.keploy.io/server/v2/pkg" - matcherUtils "go.keploy.io/server/v2/pkg/matcher" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/service/tools" - "go.keploy.io/server/v2/utils" -) - -// Assignable global variables for system and utility functions -var jsonValid234 = json.Valid -var fmtSprint234 = fmt.Sprint -var ppNew234 = pp.New -var jsonMarshal234 = json.Marshal -var jsonUnmarshal234 = json.Unmarshal - -func Match(tc *models.TestCase, actualResponse *models.HTTPResp, noiseConfig map[string]map[string][]string, ignoreOrdering bool, logger *zap.Logger) (bool, *models.Result) { - bodyType := models.Plain - if jsonValid234([]byte(actualResponse.Body)) { - bodyType = models.JSON - } - - pass := true - hRes := &[]models.HeaderResult{} - res := &models.Result{ - StatusCode: models.IntResult{ - Normal: false, - Expected: tc.HTTPResp.StatusCode, - Actual: actualResponse.StatusCode, - }, - BodyResult: []models.BodyResult{{ - Normal: false, - Type: bodyType, - Expected: tc.HTTPResp.Body, - Actual: actualResponse.Body, - }}, - } - noise := tc.Noise - var ( - bodyNoise = noiseConfig["body"] - headerNoise = noiseConfig["header"] - ) - if bodyNoise != nil { - if ignoreFields, ok := bodyNoise["*"]; ok && len(ignoreFields) > 0 && ignoreFields[0] == "*" { - if noise["body"] == nil { - noise["body"] = make([]string, 0) - } - } - } else { - bodyNoise = map[string][]string{} - } - if headerNoise == nil { - headerNoise = map[string][]string{} - } - - for field, regexArr := range noise { - a := strings.Split(field, ".") - if len(a) > 1 && a[0] == "body" { - x := strings.Join(a[1:], ".") - bodyNoise[strings.ToLower(x)] = regexArr - } else if a[0] == "header" { - headerNoise[strings.ToLower(a[len(a)-1])] = regexArr - } - } - - // stores the json body after removing the noise - cleanExp, cleanAct := tc.HTTPResp.Body, actualResponse.Body - var jsonComparisonResult matcherUtils.JSONComparisonResult - if !matcherUtils.Contains(matcherUtils.MapToArray(noise), "body") && bodyType == models.JSON && jsonValid234([]byte(tc.HTTPResp.Body)) { - //validate the stored json - validatedJSON, err := matcherUtils.ValidateAndMarshalJSON(logger, &cleanExp, &cleanAct) - if err != nil { - return false, res - } - if validatedJSON.IsIdentical() { - jsonComparisonResult, err = matcherUtils.JSONDiffWithNoiseControl(validatedJSON, bodyNoise, ignoreOrdering) - pass = jsonComparisonResult.IsExact() - if err != nil { - return false, res - } - } else { - pass = false - } - - // debug log for cleanExp and cleanAct - logger.Debug("cleanExp", zap.Any("", cleanExp)) - logger.Debug("cleanAct", zap.Any("", cleanAct)) - } else { - if !matcherUtils.Contains(matcherUtils.MapToArray(noise), "body") && tc.HTTPResp.Body != actualResponse.Body { - pass = false - } - } - - res.BodyResult[0].Normal = pass - - if !matcherUtils.CompareHeaders(pkg.ToHTTPHeader(tc.HTTPResp.Header), pkg.ToHTTPHeader(actualResponse.Header), hRes, headerNoise) { - pass = false - } - - res.HeadersResult = *hRes - if tc.HTTPResp.StatusCode == actualResponse.StatusCode { - res.StatusCode.Normal = true - } else { - pass = false - } - - skipSuccessMsg := false - if !pass { - isStatusMismatch := false - isHeaderMismatch := false - isBodyMismatch := false - - logDiffs := matcherUtils.NewDiffsPrinter(tc.Name) - newLogger := ppNew234() - newLogger.WithLineInfo = false - newLogger.SetColorScheme(models.GetFailingColorScheme()) - var logs = "" - - logs = logs + newLogger.Sprintf("Testrun failed for testcase with id: %s\n\n--------------------------------------------------------------------\n\n", tc.Name) - - // ------------ DIFFS RELATED CODE ----------- - if !res.StatusCode.Normal { - logDiffs.PushStatusDiff(fmtSprint234(res.StatusCode.Expected), fmtSprint234(res.StatusCode.Actual)) - isStatusMismatch = true - } else { - isStatusMismatch = false - } - - var ( - actualHeader = map[string][]string{} - expectedHeader = map[string][]string{} - ) - - for _, j := range res.HeadersResult { - var actualValue []string - var expectedValue []string - if !j.Normal { - for _, v := range j.Actual.Value { - _, temp, err := tools.RenderIfTemplatized(v) - if err != nil { - utils.LogError(logger, err, "failed to render the actual header") - return false, nil - } - val, ok := temp.(string) - if !ok { - utils.LogError(logger, fmt.Errorf("failed to convert the actual header value to string while templatizing"), "") - return false, nil - } - actualValue = append(actualValue, val) - } - for _, v := range j.Expected.Value { - _, temp, err := tools.RenderIfTemplatized(v) - if err != nil { - utils.LogError(logger, err, "failed to render the expected header") - return false, nil - } - val, ok := temp.(string) - if !ok { - utils.LogError(logger, fmt.Errorf("failed to convert the expected header value to string while templatizing"), "") - return false, nil - } - expectedValue = append(expectedValue, val) - } - } - if len(actualValue) != len(expectedValue) { - isHeaderMismatch = true - actualHeader[j.Actual.Key] = actualValue - expectedHeader[j.Expected.Key] = expectedValue - } else { - for i, v := range actualValue { - if v != expectedValue[i] { - isHeaderMismatch = true - actualHeader[j.Actual.Key] = actualValue - expectedHeader[j.Expected.Key] = expectedValue - break - } - } - } - } - - if isHeaderMismatch { - for i, j := range expectedHeader { - logDiffs.PushHeaderDiff(fmtSprint234(j), fmtSprint234(actualHeader[i]), i, headerNoise) - } - } - - actRespBodyType := pkg.GuessContentType([]byte(actualResponse.Body)) - expRespBodyType := pkg.GuessContentType([]byte(tc.HTTPResp.Body)) - - if !res.BodyResult[0].Normal { - if actRespBodyType != expRespBodyType { - actRespBodyType = models.UnknownType - } - - switch actRespBodyType { - case models.JSON: - patch, err := jsondiff.Compare(cleanExp, cleanAct) - if err != nil { - logger.Warn("failed to compute json diff", zap.Error(err)) - } - - // Checking for templatized values. - for _, val := range patch { - // Parse the value in map. - expStringVal, ok := val.OldValue.(string) - if !ok { - continue - } - // Parse the body into json. - expResponse, err := matcherUtils.ParseIntoJSON(expStringVal) - if err != nil { - utils.LogError(logger, err, "failed to parse the exp response into json") - break - } - - actStringVal, ok := val.Value.(string) - if !ok { - continue - } - - actResponse, err := matcherUtils.ParseIntoJSON(actStringVal) - if err != nil { - utils.LogError(logger, err, "failed to parse the act response into json") - break - } - matcherUtils.CompareResponses(&expResponse, &actResponse, "") - jsonBytes, err := jsonMarshal234(expResponse) - if err != nil { - return false, nil - } - actJSONBytes, err := jsonMarshal234(actResponse) - if err != nil { - return false, nil - } - cleanExp = string(jsonBytes) - cleanAct = string(actJSONBytes) - } - validatedJSON, err := matcherUtils.ValidateAndMarshalJSON(logger, &cleanExp, &cleanAct) - if err != nil { - return false, res - } - isBodyMismatch = false - if validatedJSON.IsIdentical() { - jsonComparisonResult, err = matcherUtils.JSONDiffWithNoiseControl(validatedJSON, bodyNoise, ignoreOrdering) - if err != nil { - return false, res - } - if !jsonComparisonResult.IsExact() { - isBodyMismatch = true - } - } else { - isBodyMismatch = true - } - // Comparing the body again after updating the expected - patch, err = jsondiff.Compare(cleanExp, cleanAct) - if err != nil { - logger.Warn("failed to compute json diff", zap.Error(err)) - } - for _, op := range patch { - if jsonComparisonResult.Matches() { - logDiffs.SetHasarrayIndexMismatch(true) - logDiffs.PushFooterDiff(strings.Join(jsonComparisonResult.Differences(), ", ")) - } - logDiffs.PushBodyDiff(fmtSprint234(op.OldValue), fmtSprint234(op.Value), bodyNoise) - } - default: // right now for every other type we would do a simple comparison, till we don't have dedicated logic for other types. - if tc.HTTPResp.Body != actualResponse.Body { - isBodyMismatch = true - } - logDiffs.PushBodyDiff(fmtSprint234(tc.HTTPResp.Body), fmtSprint234(actualResponse.Body), bodyNoise) - } - } - - if isStatusMismatch || isHeaderMismatch || isBodyMismatch { - skipSuccessMsg = true - _, err := newLogger.Printf(logs) - if err != nil { - utils.LogError(logger, err, "failed to print the logs") - } - - err = logDiffs.Render() - if err != nil { - utils.LogError(logger, err, "failed to render the diffs") - } - } else { - pass = true - } - } - - if !skipSuccessMsg { - newLogger := ppNew234() - newLogger.WithLineInfo = false - newLogger.SetColorScheme(models.GetPassingColorScheme()) - var log2 = "" - log2 += newLogger.Sprintf("Testrun passed for testcase with id: %s\n\n--------------------------------------------------------------------\n\n", tc.Name) - _, err := newLogger.Printf(log2) - if err != nil { - utils.LogError(logger, err, "failed to print the logs") - } - } - - if len(tc.Assertions) > 1 || (len(tc.Assertions) == 1 && tc.Assertions[models.NoiseAssertion] == nil) { - return AssertionMatch(tc, actualResponse, logger) - } - - return pass, res -} - -// AssertionMatch checks the assertions in the test case against the actual response, if all of the assertions pass, it returns true, it doesn't care about other parameters of the response, -// and make the test case pass. - -// Assignable global variables for system and utility functions -var fmtSprintf234 = fmt.Sprintf -var strconvAtoi234 = strconv.Atoi - -func AssertionMatch(tc *models.TestCase, actualResponse *models.HTTPResp, logger *zap.Logger) (bool, *models.Result) { - pass := true - res := &models.Result{ - StatusCode: models.IntResult{ - Normal: false, - Expected: tc.HTTPResp.StatusCode, - Actual: actualResponse.StatusCode, - }, - BodyResult: []models.BodyResult{{ - Normal: false, - Expected: tc.HTTPResp.Body, - Actual: actualResponse.Body, - }}, - } - - for assertionName, value := range tc.Assertions { - switch assertionName { - - case models.StatusCode: - expected, err := toInt(value) - if err != nil || expected != actualResponse.StatusCode { - pass = false - logger.Error("status_code assertion failed", zap.Int("expected", expected), zap.Int("actual", actualResponse.StatusCode)) - } else { - res.StatusCode.Normal = true - } - - case models.StatusCodeClass: - class := toString(value) - var classStr string - if len(class) == 3 { - // handle if class given is status code without xx, e.g. 200 - if class[1:] != "xx" { - classStr = fmtSprintf234("%cxx", class[0]) - } else { - classStr = class - } - } else { - classStr = class - } - actualClass := fmtSprintf234("%dxx", 200/100) - if classStr != actualClass { - pass = false - logger.Error("status_code_class assertion failed", zap.String("expected", class), zap.String("actual", actualClass)) - } - - case models.StatusCodeIn: - codes := toStringSlice(value) - var ints []int - for _, s := range codes { - if i, err := strconvAtoi234(s); err == nil { - ints = append(ints, i) - } - } - found := false - for _, c := range ints { - if c == actualResponse.StatusCode { - found = true - break - } - } - if !found { - pass = false - logger.Error("status_code_in assertion failed", zap.Any("expectedCodes", ints), zap.Int("actual", actualResponse.StatusCode)) - } - - case models.HeaderEqual: - // value should be a map[string]interface{} → we convert to map[string]string - hm := toStringMap(value) - for header, exp := range hm { - act, ok := actualResponse.Header[header] - if !ok || act != exp { - pass = false - logger.Error("header_equal assertion failed", - zap.String("header", header), - zap.String("expected", exp), - zap.String("actual", act), - ) - } - logger.Info("header_equal assertion failed", - zap.String("header", header), - zap.String("expected", exp), - zap.String("actual", act), - ) - } - - case models.HeaderContains: - hm := toStringMap(value) - for header, exp := range hm { - act, ok := actualResponse.Header[header] - if !ok || !strings.Contains(act, exp) { - pass = false - logger.Error("header_contains assertion failed", - zap.String("header", header), - zap.String("expected_substr", exp), - zap.String("actual", act), - ) - } - } - - case models.HeaderExists: - switch v := value.(type) { - - // a flat slice of header names - case []interface{}: - for _, item := range v { - hdr := fmtSprint234(item) - if _, ok := actualResponse.Header[hdr]; !ok { - pass = false - logger.Error("header_exists assertion failed", zap.String("header", hdr)) - } - } - - // a map[string]… where the keys are header names - case map[string]interface{}: - for hdr := range v { - if _, ok := actualResponse.Header[hdr]; !ok { - pass = false - logger.Error("header_exists assertion failed", zap.String("header", hdr)) - } - } - - case map[models.AssertionType]interface{}: - for kt := range v { - hdr := string(kt) - if _, ok := actualResponse.Header[hdr]; !ok { - pass = false - logger.Error("header_exists assertion failed", zap.String("header", hdr)) - } - } - - default: - pass = false - logger.Error("header_exists: unsupported format, expected slice or map", zap.Any("value", value)) - } - - case models.HeaderMatches: - // value should be a map[string]interface{} → convert to map[string]string - hm := toStringMap(value) - for header, pattern := range hm { - act, ok := actualResponse.Header[header] - if !ok { - pass = false - logger.Error("header_matches: header not found", zap.String("header", header)) - continue - } - if matched, err := regexp.MatchString(pattern, act); err != nil || !matched { - pass = false - logger.Error("header_matches assertion failed", - zap.String("header", header), - zap.String("pattern", pattern), - zap.String("actual", act), - zap.Error(err), - ) - } - } - - case models.JsonEqual: - expJSON := tc.HTTPResp.Body - actJSON := actualResponse.Body - if expJSON != actJSON { - pass = false - logger.Error("json_equal assertion failed", zap.String("expected", expJSON), zap.String("actual", actJSON)) - } - - case models.JsonContains: - var expectedMap map[string]interface{} - switch v := value.(type) { - case map[string]interface{}: - expectedMap = v - case string: - _ = jsonUnmarshal234([]byte(v), &expectedMap) - default: - pass = false - logger.Error("json_contains: unexpected format", zap.Any("value", value)) - continue - } - if ok, _ := matcherUtils.JsonContains(actualResponse.Body, expectedMap); !ok { - pass = false - logger.Error("json_contains assertion failed", zap.Any("expected", expectedMap)) - } - - default: - if assertionName != models.NoiseAssertion { - logger.Warn("unhandled assertion type", zap.String("name", string(assertionName))) - } - } - } - - if pass { - res.StatusCode.Normal = true - res.BodyResult[0].Normal = true - } - - return pass, res -} - -func FlattenHTTPResponse(h http.Header, body string) (map[string][]string, error) { - m := map[string][]string{} - for k, v := range h { - m["header."+k] = []string{strings.Join(v, "")} - } - err := matcherUtils.AddHTTPBodyToMap(body, m) - if err != nil { - return m, err - } - return m, nil -} diff --git a/keploy/pkg/matcher/http/match_test.go b/keploy/pkg/matcher/http/match_test.go deleted file mode 100644 index 979448b..0000000 --- a/keploy/pkg/matcher/http/match_test.go +++ /dev/null @@ -1,236 +0,0 @@ -package http - -import ( - "testing" - - "errors" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -// TestMatch_HeaderNoiseUpdate_123 ensures that the `headerNoise` map is updated correctly when the `noise` map contains a "header" key. -func TestMatch_HeaderNoiseUpdate_123(t *testing.T) { - // Arrange - logger := zap.NewNop() - tc := &models.TestCase{ - HTTPResp: models.HTTPResp{ - StatusCode: 200, - Header: map[string]string{"Content-Type": "application/json"}, - Body: `{"key":"value"}`, - }, - Noise: map[string][]string{ - "header.Content-Type": {"regex"}, - }, - } - actualResponse := &models.HTTPResp{ - StatusCode: 200, - Header: map[string]string{"Content-Type": "application/json"}, - Body: `{"key":"value"}`, - } - noiseConfig := map[string]map[string][]string{ - "header": {}, - } - ignoreOrdering := false - - // Act - pass, result := Match(tc, actualResponse, noiseConfig, ignoreOrdering, logger) - - // Assert - require.NotNil(t, result) - assert.True(t, pass) - assert.Contains(t, noiseConfig["header"], "content-type") - assert.Equal(t, []string{"regex"}, noiseConfig["header"]["content-type"]) -} - -// TestMatch_FailureAndDiffLogging_890 tests the Match function with comprehensive failures -// in status code, headers, and JSON body to ensure that the diff logging mechanism is triggered. -func TestMatch_FailureAndDiffLogging_890(t *testing.T) { - // Arrange - logger := zap.NewNop() - tc := &models.TestCase{ - Name: "test-comprehensive-fail", - HTTPResp: models.HTTPResp{ - StatusCode: 200, - Header: map[string]string{"Expected-Header": "value1"}, - Body: `{"id": 1, "value": "expected"}`, - }, - } - actualResponse := &models.HTTPResp{ - StatusCode: 404, // Mismatch - Header: map[string]string{"Actual-Header": "value2"}, - Body: `{"id": 2, "value": "actual"}`, // Mismatch - } - noiseConfig := map[string]map[string][]string{} - ignoreOrdering := false - - // Act - pass, result := Match(tc, actualResponse, noiseConfig, ignoreOrdering, logger) - - // Assert - assert.False(t, pass, "Should fail due to multiple mismatches") - require.NotNil(t, result) - assert.False(t, result.StatusCode.Normal) - assert.False(t, result.BodyResult[0].Normal) - // We can't easily assert the console output, but by running this - // we exercise the entire diff generation logic in lines 121-301. -} - -// TestMatch_BodyNoiseFromTestCase_124 verifies that the Match function correctly applies -// noise rules defined within the TestCase's Noise field to ignore specific JSON body fields. -func TestMatch_BodyNoiseFromTestCase_124(t *testing.T) { - // Arrange - logger := zap.NewNop() - tc := &models.TestCase{ - Name: "test-body-noise-from-tc", - HTTPResp: models.HTTPResp{ - StatusCode: 200, - Body: `{"id": 123, "name": "expected"}`, - }, - Noise: map[string][]string{ - "body.id": {".*"}, // Ignore the 'id' field - }, - } - actualResponse := &models.HTTPResp{ - StatusCode: 200, - Body: `{"id": 456, "name": "expected"}`, // Only 'id' is different - } - noiseConfig := map[string]map[string][]string{} - ignoreOrdering := false - - // Act - pass, result := Match(tc, actualResponse, noiseConfig, ignoreOrdering, logger) - - // Assert - assert.True(t, pass, "Should pass because the 'id' field difference is covered by noise") - require.NotNil(t, result) - assert.True(t, result.StatusCode.Normal) - assert.True(t, result.BodyResult[0].Normal) -} - -// TestMatch_RedirectToAssertionMatch_567 ensures that if a TestCase contains assertions, -// the Match function correctly calls AssertionMatch and returns its result. -func TestMatch_RedirectToAssertionMatch_567(t *testing.T) { - // Arrange - logger := zap.NewNop() - tc := &models.TestCase{ - Name: "test-redirect-to-assertion", - HTTPResp: models.HTTPResp{ - StatusCode: 201, // Deliberate mismatch to show normal matching would fail - Body: `{"key":"wrong"}`, - }, - Assertions: map[models.AssertionType]interface{}{ - models.StatusCode: 200, - models.JsonContains: map[string]interface{}{ - "key": "value", - }, - }, - } - actualResponse := &models.HTTPResp{ - StatusCode: 200, - Body: `{"key":"value", "other": "stuff"}`, - } - noiseConfig := map[string]map[string][]string{} - ignoreOrdering := false - - // Act - pass, result := Match(tc, actualResponse, noiseConfig, ignoreOrdering, logger) - - // Assert - assert.True(t, pass, "AssertionMatch should be called and return true") - require.NotNil(t, result) - assert.True(t, result.StatusCode.Normal) - assert.True(t, result.BodyResult[0].Normal) -} - -// TestMatch_InvalidJSONBody_321 ensures that when the actual response body is not valid JSON, -// it is treated as plain text and compared directly, leading to a mismatch if different. -func TestMatch_InvalidJSONBody_321(t *testing.T) { - logger := zap.NewNop() - tc := &models.TestCase{ - HTTPResp: models.HTTPResp{ - StatusCode: 200, - Body: `{"id": "123", "name": "keploy"}`, - }, - } - actualResponse := &models.HTTPResp{ - StatusCode: 200, - Body: `{"id": "123", "name": "keploy"`, // Invalid JSON - } - noiseConfig := map[string]map[string][]string{} - - pass, res := Match(tc, actualResponse, noiseConfig, false, logger) - - assert.False(t, pass) - assert.False(t, res.BodyResult[0].Normal) - assert.Equal(t, models.Plain, res.BodyResult[0].Type) -} - -// TestMatch_JsonMarshalErrorInDiff_987 simulates a failure in json.Marshal when generating -// diffs for a failed test case to ensure the error is handled gracefully. -func TestMatch_JsonMarshalErrorInDiff_987(t *testing.T) { - logger := zap.NewNop() - tc := &models.TestCase{ - Name: "test-marshal-error", - HTTPResp: models.HTTPResp{ - StatusCode: 200, - Body: `{"id": 1, "value": "expected"}`, - }, - } - actualResponse := &models.HTTPResp{ - StatusCode: 200, - Body: `{"id": 1, "value": "actual"}`, - } - noiseConfig := map[string]map[string][]string{} - - originalJSONMarshal := jsonMarshal234 - jsonMarshal234 = func(v interface{}) ([]byte, error) { - // This mock will fail the first time json.Marshal is called within the diffing logic. - return nil, errors.New("mock marshal error") - } - defer func() { jsonMarshal234 = originalJSONMarshal }() - - pass, res := Match(tc, actualResponse, noiseConfig, false, logger) - - // The function returns (false, nil) on this specific error path - assert.False(t, pass) - assert.Nil(t, res) -} - -// TestMatch_BodyNoiseWildcard_789 tests the scenario where a global noise configuration -// specifies that the entire body should be ignored ("*": "*"). Even if the actual -// response body is completely different from the expected one, the match should pass. -// It also verifies that the test case's noise map for the body is initialized. -func TestMatch_BodyNoiseWildcard_789(t *testing.T) { - logger := zap.NewNop() - tc := &models.TestCase{ - Name: "test-wildcard-noise", - HTTPResp: models.HTTPResp{ - StatusCode: 200, - Body: `{"id": 1, "name": "keploy"}`, - }, - Noise: map[string][]string{}, // Noise is empty in TC - } - actualResponse := &models.HTTPResp{ - StatusCode: 200, - Body: `{"id": 2, "name": "keploy-test"}`, // Body is completely different - } - // Global noise config says to ignore the entire body - noiseConfig := map[string]map[string][]string{ - "body": {"*": {"*"}}, - } - ignoreOrdering := false - - // Act - pass, result := Match(tc, actualResponse, noiseConfig, ignoreOrdering, logger) - - // Assert - assert.True(t, pass, "Should pass because the entire body is ignored by wildcard noise") - require.NotNil(t, result) - assert.True(t, result.StatusCode.Normal) - assert.True(t, result.BodyResult[0].Normal) - // Check that tc.Noise["body"] was initialized - assert.NotNil(t, tc.Noise["body"]) -} diff --git a/keploy/pkg/matcher/http/utils.go b/keploy/pkg/matcher/http/utils.go deleted file mode 100644 index 7b39571..0000000 --- a/keploy/pkg/matcher/http/utils.go +++ /dev/null @@ -1,103 +0,0 @@ -package http - -import ( - "encoding/json" - "fmt" - "strconv" - - "strings" - - "go.keploy.io/server/v2/pkg/models" -) - -func toInt(v interface{}) (int, error) { - switch x := v.(type) { - case int: - return x, nil - case float64: - return int(x), nil - case json.Number: - i64, err := x.Int64() - return int(i64), err - case string: - i64, err := strconv.ParseInt(x, 10, 64) - if err != nil { - return 0, err - } - const maxInt = int64(^uint(0) >> 1) // Maximum value for int - const minInt = -maxInt - 1 // Minimum value for int - if i64 > maxInt || i64 < minInt { - return 0, fmt.Errorf("value out of range for int: %d", i64) - } - return int(i64), nil - default: - return 0, fmt.Errorf("cannot convert %T to int", v) - } -} - -func toString(v interface{}) string { - switch x := v.(type) { - case string: - return x - case json.Number: - return x.String() - case float64: - return strconv.FormatFloat(x, 'f', -1, 64) - case int: - return strconv.Itoa(x) - default: - return fmt.Sprintf("%v", v) - } -} - -func toStringSlice(v interface{}) []string { - var out []string - switch x := v.(type) { - case []interface{}: - for _, e := range x { - out = append(out, toString(e)) - } - case string: - for _, part := range strings.Split(x, ",") { - out = append(out, strings.TrimSpace(part)) - } - } - return out -} - -func toStringMap(val interface{}) map[string]string { - out := make(map[string]string) - switch m := val.(type) { - case map[string]interface{}: - for k, v := range m { - out[k] = fmt.Sprint(v) - } - - case map[string]string: - // already the right shape - for k, v := range m { - out[k] = v - } - - case map[models.AssertionType]interface{}: - for kType, v := range m { - out[string(kType)] = fmt.Sprint(v) - } - - case map[models.AssertionType]string: - for kType, v := range m { - out[string(kType)] = v - } - - case map[interface{}]interface{}: - // sometimes YAML v3 gives you this - for ki, vi := range m { - key := fmt.Sprint(ki) - out[key] = fmt.Sprint(vi) - } - - default: - // not a map we know—return empty - } - return out -} diff --git a/keploy/pkg/matcher/schema/match.go b/keploy/pkg/matcher/schema/match.go deleted file mode 100644 index d87662c..0000000 --- a/keploy/pkg/matcher/schema/match.go +++ /dev/null @@ -1,262 +0,0 @@ -// Package schema for schema matching -package schema - -import ( - "encoding/json" - "fmt" - "reflect" - "strings" - - "github.com/k0kubun/pp/v3" - "github.com/wI2L/jsondiff" - matcher "go.keploy.io/server/v2/pkg/matcher" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type ValidatedJSONWrapper struct { - Expected interface{} `json:"expected"` - Actual interface{} `json:"actual"` - IsIdentical bool `json:"isIdentical"` -} -type JSONComparisonResultWrapper struct { - Matches bool `json:"matches"` - IsExact bool `json:"isExact"` - Differences []string `json:"differences"` -} - -const NOTCANDIDATE = -1.0 - -func compareOperationTypes(mockOperationType, testOperationType string) (bool, error) { - pass := true - if mockOperationType != testOperationType { - pass = false - return pass, nil - - } - return pass, nil -} -func compareRequestBodies(mockOperation, testOperation *models.Operation, logDiffs matcher.DiffsPrinter, newLogger *pp.PrettyPrinter, logger *zap.Logger, testName, mockName, testSetID, mockSetID string) (bool, error) { - pass := false - var score float64 - mockRequestBodyStr, testRequestBodyStr, err := matcher.MarshalRequestBodies(mockOperation, testOperation) - if err != nil { - return false, err - } - - validatedJSON, err := matcher.ValidateAndMarshalJSON(logger, &mockRequestBodyStr, &testRequestBodyStr) - if err != nil { - return false, err - } - - if validatedJSON.IsIdentical() { - if score, pass, err = handleJSONDiff(validatedJSON, logDiffs, newLogger, logger, testName, mockName, testSetID, mockSetID, mockRequestBodyStr, testRequestBodyStr, "request", 0); err != nil { - return false, err - } - if score == NOTCANDIDATE { - return false, nil - } - - } else { - pass = false - return pass, nil - - } - return pass, nil -} - -func compareParameters(mockParameters, testParameters []models.Parameter) (bool, error) { - pass := true - - for _, mockParam := range mockParameters { - if mockParam.In == "header" { - continue - } - found := false - for _, testParam := range testParameters { - if mockParam.Name == testParam.Name && mockParam.In == testParam.In { - found = true - if mockParam.Schema.Type != testParam.Schema.Type { - pass = false - return pass, nil - } - } - } - if !found { - pass = false - return pass, nil - } - } - - return pass, nil -} - -func compareResponseBodies(status string, mockOperation, testOperation *models.Operation, logDiffs matcher.DiffsPrinter, newLogger *pp.PrettyPrinter, logger *zap.Logger, testName, mockName, testSetID, mockSetID string, mode models.SchemaMatchMode) (float64, bool, bool, error) { - pass := true - overallScore := 0.0 - matched := false - differencesCount := 0.0 - if _, ok := testOperation.Responses[status]; ok { - mockResponseBodyStr, testResponseBodyStr, err := matcher.MarshalResponseBodies(status, mockOperation, testOperation) - if err != nil { - return differencesCount, false, false, err - } - overallScore = float64(len(mockOperation.Responses[status].Content["application/json"].Schema.Properties)) - validatedJSON, err := matcher.ValidateAndMarshalJSON(logger, &mockResponseBodyStr, &testResponseBodyStr) - if err != nil { - return differencesCount, false, false, err - } - - if validatedJSON.IsIdentical() { - switch mode { - case models.CompareMode: - if _, matched, err = handleJSONDiff(validatedJSON, logDiffs, newLogger, logger, testName, mockName, testSetID, mockSetID, mockResponseBodyStr, testResponseBodyStr, "response", mode); err != nil { - return differencesCount, false, false, err - } - case models.IdentifyMode: - differencesCount, err = calculateSimilarityScore(mockOperation, testOperation, status) - if err != nil { - return differencesCount, false, false, err - } - } - } else { - differencesCount = overallScore - - if mode == models.CompareMode { - logDiffs.PushTypeDiff(fmt.Sprint(reflect.TypeOf(validatedJSON.Expected())), fmt.Sprint(reflect.TypeOf(validatedJSON.Actual()))) - logs := newLogger.Sprintf("Contract Check failed for test: %s (%s) / mock: %s (%s) \n\n--------------------------------------------------------------------\n\n", testName, testSetID, mockName, mockSetID) - - if err := printAndRenderLogs(logs, newLogger, logDiffs, logger); err != nil { - return differencesCount, false, false, err - } - } - } - } else { - pass = false - differencesCount = -1 - - } - return differencesCount / overallScore, pass, matched, nil -} -func Match(mock, test models.OpenAPI, testSetID string, mockSetID string, logger *zap.Logger, mode models.SchemaMatchMode) (float64, bool, error) { - pass := false - - candidateScore := -1.0 - newLogger := pp.New() - newLogger.WithLineInfo = false - newLogger.SetColorScheme(models.GetFailingColorScheme()) - - for path, mockItem := range mock.Paths { - logDiffs := matcher.NewDiffsPrinter(test.Info.Title + "/" + mock.Info.Title) - var err error - if testItem, found := test.Paths[path]; found { - mockOperation, mockOperationType := matcher.FindOperation(mockItem) - testOperation, testOperationType := matcher.FindOperation(testItem) - if mode == models.IdentifyMode { - if pass, err = compareOperationTypes(mockOperationType, testOperationType); err != nil { - return candidateScore, false, err - } - if !pass { - continue - } - if pass, err = compareParameters(mockOperation.Parameters, testOperation.Parameters); err != nil { - return candidateScore, false, err - } - if !pass { - continue - } - if pass, err = compareRequestBodies(mockOperation, testOperation, logDiffs, newLogger, logger, test.Info.Title, mock.Info.Title, testSetID, mockSetID); err != nil { - return candidateScore, false, err - } - if !pass { - continue - } - } - var statusCode string - for status := range mockOperation.Responses { - statusCode = status - break - - } - - if candidateScore, pass, _, err = compareResponseBodies(statusCode, mockOperation, testOperation, logDiffs, newLogger, logger, test.Info.Title, mock.Info.Title, testSetID, mockSetID, mode); err != nil { - return candidateScore, false, err - } - - } else { - pass = false - - } - - } - - return candidateScore, pass, nil -} -func calculateSimilarityScore(mockOperation, testOperation *models.Operation, status string) (float64, error) { - testParameters := testOperation.Responses[status].Content["application/json"].Schema.Properties - mockParameters := mockOperation.Responses[status].Content["application/json"].Schema.Properties - score := 0.0 - for key, testParam := range testParameters { - if _, ok := mockParameters[key]; ok { - if testParam["type"] == mockParameters[key]["type"] { - score++ - } - } - } - return score, nil -} - -func handleJSONDiff(validatedJSON matcher.ValidatedJSON, logDiffs matcher.DiffsPrinter, newLogger *pp.PrettyPrinter, logger *zap.Logger, _ string, _ string, _ string, _ string, mockBodyStr string, testBodyStr string, diffType string, mode models.SchemaMatchMode) (float64, bool, error) { - pass := true - differencesCount := 0.0 - jsonComparisonResult, err := matcher.JSONDiffWithNoiseControl(validatedJSON, nil, false) - if err != nil { - return differencesCount, false, err - } - if !jsonComparisonResult.IsExact() { - pass = false - // logs := newLogger.Sprintf("Contract Check failed for test: %s (%s) / mock: %s (%s) \n\n--------------------------------------------------------------------\n\n", testName, testSetID, mockName, mockSetID) - if json.Valid([]byte(mockBodyStr)) { - patch, err := jsondiff.Compare(testBodyStr, mockBodyStr) - if err != nil { - logger.Warn("failed to compute json diff", zap.Error(err)) - return differencesCount, false, err - } - differencesCount = float64(len(patch)) - if diffType == "request" && differencesCount > 1 { - return -1.0, false, nil - } - if diffType == "response" { - for _, op := range patch { - if jsonComparisonResult.Matches() { - logDiffs.SetHasarrayIndexMismatch(true) - logDiffs.PushFooterDiff(strings.Join(jsonComparisonResult.Differences(), ", ")) - } - - logDiffs.PushBodyDiff(fmt.Sprint(op.OldValue), fmt.Sprint(op.Value), nil) - - } - } - } - if diffType == "response" && mode == models.CompareMode { - if err := printAndRenderLogs("", newLogger, logDiffs, logger); err != nil { - return differencesCount, false, err - } - - } - } - return differencesCount, pass, nil -} - -func printAndRenderLogs(logs string, newLogger *pp.PrettyPrinter, logDiffs matcher.DiffsPrinter, logger *zap.Logger) error { - if _, err := newLogger.Printf(logs); err != nil { - utils.LogError(logger, err, "failed to print the logs") - return err - } - if err := logDiffs.RenderAppender(); err != nil { - utils.LogError(logger, err, "failed to render the diffs") - return err - } - return nil -} diff --git a/keploy/pkg/matcher/utils.go b/keploy/pkg/matcher/utils.go deleted file mode 100644 index 13da402..0000000 --- a/keploy/pkg/matcher/utils.go +++ /dev/null @@ -1,1207 +0,0 @@ -// Package matcher for matching utilities -package matcher - -import ( - "bufio" - "bytes" - "encoding/json" - "errors" - "fmt" - "net/http" - "os" - "reflect" - "regexp" - "strconv" - "strings" - "sync" - - "github.com/7sDream/geko" - "github.com/fatih/color" - jsonDiff "github.com/keploy/jsonDiff" - "github.com/olekukonko/tablewriter" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -var ( - regexCacheMu sync.RWMutex - regexCache = make(map[string]*regexp.Regexp) -) - -// getCompiled returns a cached compiled regexp for pattern. -// It never panics: on invalid patterns, it returns a "never matches" regex (?!) and caches it. -func getCompiled(pattern string) *regexp.Regexp { - regexCacheMu.RLock() - re := regexCache[pattern] - regexCacheMu.RUnlock() - if re != nil { - return re - } - - // Compile outside the read lock. - compiled, err := regexp.Compile(pattern) - if err != nil { - // Fallback to a regex that never matches to avoid panics / repeated compiles - compiled, _ = regexp.Compile(`(?!)`) - } - - regexCacheMu.Lock() - // Double-check to avoid races. - if old := regexCache[pattern]; old == nil { - regexCache[pattern] = compiled - } else { - compiled = old - } - regexCacheMu.Unlock() - return compiled -} - -func MatchesAnyRegex(str string, regexArray []string) (bool, string) { - for _, pattern := range regexArray { - if getCompiled(pattern).MatchString(str) { - return true, pattern - } - } - return false, "" -} - -type noiseEntry struct { - keyLower string - regexps []*regexp.Regexp // empty => ignore subtree -} -type noiseIndex struct { - entries []noiseEntry -} - -func buildNoiseIndex(mp map[string][]string) noiseIndex { - if mp == nil { - return noiseIndex{} - } - out := noiseIndex{entries: make([]noiseEntry, 0, len(mp))} - for k, arr := range mp { - e := noiseEntry{keyLower: strings.ToLower(k)} - if len(arr) > 0 { - e.regexps = make([]*regexp.Regexp, 0, len(arr)) - for _, p := range arr { - e.regexps = append(e.regexps, getCompiled(p)) - } - } - out.entries = append(out.entries, e) - } - return out -} - -func (ni noiseIndex) match(keyLower string) (regs []*regexp.Regexp, isNoisy bool) { - for _, e := range ni.entries { - if strings.Contains(keyLower, e.keyLower) { - return e.regexps, true - } - } - return nil, false -} - -func JSONDiffWithNoiseControl(validatedJSON ValidatedJSON, noise map[string][]string, ignoreOrdering bool) (JSONComparisonResult, error) { - idx := buildNoiseIndex(noise) - return matchJSONWithNoiseHandlingIndexed("", validatedJSON.expected, validatedJSON.actual, idx, ignoreOrdering) -} - -// Back-compat shim used by a few spots internally (if any). -func matchJSONWithNoiseHandling(key string, expected, actual interface{}, noiseMap map[string][]string, ignoreOrdering bool) (JSONComparisonResult, error) { - return matchJSONWithNoiseHandlingIndexed(key, expected, actual, buildNoiseIndex(noiseMap), ignoreOrdering) -} - -// New optimized implementation. -func matchJSONWithNoiseHandlingIndexed(key string, expected, actual interface{}, ni noiseIndex, ignoreOrdering bool) (JSONComparisonResult, error) { - var out JSONComparisonResult - // Type check fast-path (JSON unmarshal produces these concrete types). - switch e := expected.(type) { - case nil: - if actual == nil { - out.matches, out.isExact = true, true - } - return out, nil - - case string: - a, ok := actual.(string) - if !ok { - return out, errors.New("type not matched") - } - regs, noisy := ni.match(strings.ToLower(key)) - if noisy && len(regs) != 0 { - if anyRegexpMatchStr(a, regs) { - out.matches, out.isExact = true, true - return out, nil - } - } - if e == a || noisy { - out.matches, out.isExact = true, true - } - return out, nil - - case bool: - a, ok := actual.(bool) - if !ok { - return out, errors.New("type not matched") - } - _, noisy := ni.match(strings.ToLower(key)) - if e == a || noisy { - out.matches, out.isExact = true, true - } - return out, nil - - case float64: - a, ok := actual.(float64) - if !ok { - return out, errors.New("type not matched") - } - _, noisy := ni.match(strings.ToLower(key)) - if e == a || noisy { - out.matches, out.isExact = true, true - } - return out, nil - - case map[string]interface{}: - a, ok := actual.(map[string]interface{}) - if !ok { - return out, errors.New("type not matched") - } - - // If whole subtree is noisy (no regex guard), accept. - if regs, noisy := ni.match(strings.ToLower(key)); noisy && len(regs) == 0 { - out.matches, out.isExact = true, true - return out, nil - } - - // Quick length check — allows early exit if extra/missing keys (ignoring noisy children). - // We still need to walk to account for noisy exclusions; so we won't return solely on len. - isExact := true - - // Lowercased prefix once. - prefix := "" - if key != "" { - prefix = key + "." - } - prefixLower := strings.ToLower(prefix) - - // 1) All expected keys must be present & match. - for k, v := range e { - val, ok := a[k] - if !ok { - return out, nil - } - childKeyLower := prefixLower + strings.ToLower(k) - - // If child subtree is entirely noisy, skip deep compare. - if regs, noisy := ni.match(childKeyLower); noisy && len(regs) == 0 { - continue - } - - res, err := matchJSONWithNoiseHandlingIndexed(prefix+k, v, val, ni, ignoreOrdering) - if err != nil || !res.matches { - return out, nil - } - if !res.isExact { - isExact = false - out.differences = append(out.differences, k) - out.differences = append(out.differences, res.differences...) - } - } - - // 2) No unexpected non-noisy keys in actual. - for k := range a { - if _, ok := e[k]; ok { - continue - } - childKeyLower := prefixLower + strings.ToLower(k) - if regs, noisy := ni.match(childKeyLower); noisy && len(regs) == 0 { - continue // ignore unexpected but noisy subtree - } - return out, nil - } - - out.matches, out.isExact = true, isExact - return out, nil - - case []interface{}: - a, ok := actual.([]interface{}) - if !ok { - return out, errors.New("type not matched") - } - if len(e) != len(a) { - return out, nil - } - - // If the whole slice is marked noisy-without-regex, accept. - if regs, noisy := ni.match(strings.ToLower(key)); noisy && len(regs) == 0 { - out.matches, out.isExact = true, true - return out, nil - } - - // Fast path: if ordering matters, avoid O(n²). - if !ignoreOrdering { - isExact := true - for i := 0; i < len(e); i++ { - res, err := matchJSONWithNoiseHandlingIndexed(key, e[i], a[i], ni, ignoreOrdering) - if err != nil || !res.matches { - return out, nil - } - if !res.isExact { - isExact = false - } - } - out.matches, out.isExact = true, isExact - return out, nil - } - - // ignoreOrdering == true: greedy matching with "used" flags to avoid reusing elements. - used := make([]bool, len(a)) - isExact := true - - for i := 0; i < len(e); i++ { - matched := false - // Try primitive fast match first to reduce recursion. - if j, ok := findAndClaimPrimitiveEqual(e[i], a, used); ok { - used[j] = true - matched = true - } else { - // Fallback to structural match. - for j := 0; j < len(a); j++ { - if used[j] { - continue - } - childKey := "" // by design: no index prefix at root mixed objects - res, err := matchJSONWithNoiseHandlingIndexed(childKey, e[i], a[j], ni, ignoreOrdering) - if err == nil && res.matches { - if !res.isExact { - isExact = false - for _, v := range res.differences { - if childKey != "" { - v = childKey + "." + v - } - out.differences = append(out.differences, v) - } - } - used[j] = true - matched = true - break - } - } - } - if !matched { - out.matches, out.isExact = false, false - return out, nil - } - } - - out.matches, out.isExact = true, isExact - return out, nil - } - - return out, errors.New("type not registered for json") -} - -func anyRegexpMatchStr(s string, regs []*regexp.Regexp) bool { - for _, re := range regs { - if re.MatchString(s) { - return true - } - } - return false -} - -func findAndClaimPrimitiveEqual(x interface{}, arr []interface{}, used []bool) (int, bool) { - switch v := x.(type) { - case string: - for i, y := range arr { - if used[i] { - continue - } - if ys, ok := y.(string); ok && ys == v { - return i, true - } - } - case float64: - for i, y := range arr { - if used[i] { - continue - } - if yf, ok := y.(float64); ok && yf == v { - return i, true - } - } - case bool: - for i, y := range arr { - if used[i] { - continue - } - if yb, ok := y.(bool); ok && yb == v { - return i, true - } - } - } - return -1, false -} - -type ValidatedJSON struct { - expected interface{} // The expected JSON - actual interface{} // The actual JSON - isIdentical bool -} - -func (v *ValidatedJSON) IsIdentical() bool { - return v.isIdentical -} -func (v *ValidatedJSON) Expected() interface{} { - return v.expected -} -func (v *ValidatedJSON) Actual() interface{} { - return v.actual -} - -type JSONComparisonResult struct { - matches bool // Indicates if the JSON strings match according to the criteria - isExact bool // Indicates if the match is exact, considering ordering and noise - differences []string // Lists the keys or indices of values that are not the same -} - -func (v *JSONComparisonResult) IsExact() bool { - return v.isExact -} -func (v *JSONComparisonResult) Matches() bool { - return v.matches -} -func (v *JSONComparisonResult) Differences() []string { - return v.differences -} -func MarshalRequestBodies(mockOperation, testOperation *models.Operation) (string, string, error) { - var mockRequestBody []byte - var testRequestBody []byte - var err error - if mockOperation.RequestBody != nil { - mockRequestBody, err = json.Marshal(mockOperation.RequestBody.Content["application/json"].Schema.Properties) - if err != nil { - return "", "", fmt.Errorf("error marshalling mock RequestBody: %v", err) - } - } - if testOperation.RequestBody != nil { - testRequestBody, err = json.Marshal(testOperation.RequestBody.Content["application/json"].Schema.Properties) - if err != nil { - return "", "", fmt.Errorf("error marshalling test RequestBody: %v", err) - } - } - return string(mockRequestBody), string(testRequestBody), nil -} - -func MarshalResponseBodies(status string, mockOperation, testOperation *models.Operation) (string, string, error) { - var mockResponseBody []byte - var testResponseBody []byte - var err error - if mockOperation.Responses[status].Content != nil { - mockResponseBody, err = json.Marshal(mockOperation.Responses[status].Content["application/json"].Schema.Properties) - if err != nil { - return "", "", fmt.Errorf("error marshalling mock ResponseBody: %v", err) - } - } - if testOperation.Responses[status].Content != nil { - testResponseBody, err = json.Marshal(testOperation.Responses[status].Content["application/json"].Schema.Properties) - if err != nil { - return "", "", fmt.Errorf("error marshalling test ResponseBody: %v", err) - } - } - return string(mockResponseBody), string(testResponseBody), nil -} -func FindOperation(item models.PathItem) (*models.Operation, string) { - operations := map[string]*models.Operation{ - "GET": item.Get, - "POST": item.Post, - "PUT": item.Put, - "DELETE": item.Delete, - "PATCH": item.Patch, - } - - for method, operation := range operations { - if operation != nil { - return operation, method - } - } - return nil, "" -} - -// ParseIntoJSON Parse the json string into a geko type variable, it will maintain the order of the keys in the json. -func ParseIntoJSON(response string) (interface{}, error) { - // Parse the response into a json object. - if response == "" { - return nil, nil - } - result, err := geko.JSONUnmarshal([]byte(response)) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal the response: %v", err) - } - return result, nil -} - -// CompareResponses compares the two responses, if there is any difference in the values, -// It checks in the templatized values map if the value is already present, it will update the value in the map. -// It also changes the expected value to the actual value in the response1 (expected body) -func CompareResponses(response1, response2 *interface{}, key string) { - switch v1 := (*response1).(type) { - case geko.Array: - for _, val1 := range v1.List { - CompareResponses(&val1, response2, "") - } - case geko.ObjectItems: - keys := v1.Keys() - vals := v1.Values() - for i := range keys { - CompareResponses(&vals[i], response2, keys[i]) - v1.SetValueByIndex(i, vals[i]) // in order to change the expected value if required - } - case map[string]interface{}: - for key, val := range v1 { - CompareResponses(&val, response2, key) - v1[key] = val // in order to change the expected value if required - } - case string: - compareSecondResponse(&v1, response2, key, "") - *response1 = v1 - case float64, int64, int, float32: - v1String := utils.ToString(v1) - compareSecondResponse(&(v1String), response2, key, "") - // Retain the original type - switch (*response1).(type) { - case float64: - *response1 = utils.ToFloat(v1String) - case int: - *response1 = utils.ToInt(v1String) - case int64: - *response1 = utils.ToInt(v1String) - case float32: - f := utils.ToFloat(v1String) - *response1 = float32(f) - default: - *response1 = v1 // Keep it unchanged if it’s an unexpected type - } - } -} - -// Simplify the second response into type string for comparison. -func compareSecondResponse(val1 *string, response2 *interface{}, key1 string, key2 string) { - switch v2 := (*response2).(type) { - case geko.Array: - for _, val2 := range v2.List { - compareSecondResponse(val1, &val2, key1, "") - } - - case geko.ObjectItems: - keys := v2.Keys() - vals := v2.Values() - for i := range keys { - compareSecondResponse(val1, &vals[i], key1, keys[i]) - } - case map[string]interface{}: - for key, val := range v2 { - compareSecondResponse(val1, &val, key1, key) - } - case string: - if *val1 != v2 { - // Reverse the templatized values map. - revMap := reverseMap(utils.TemplatizedValues) - if _, ok := revMap[*val1]; ok && key1 == key2 { - key := revMap[*val1] - utils.TemplatizedValues[key] = v2 - *val1 = v2 - } - } - case float64, int64, int, float32: - if *val1 != v2 { - - // Reverse the templatized values map. - revMap := reverseMap(utils.TemplatizedValues) - if _, ok := revMap[*val1]; ok && key1 == key2 { - key := revMap[*val1] - utils.TemplatizedValues[key] = v2 - *val1 = utils.ToString(v2) - return - } - // 1) try integer parse - if i, err := strconv.Atoi(*val1); err == nil { - if _, ok := revMap[i]; ok && key1 == key2 { - key := revMap[i] - utils.TemplatizedValues[key] = v2 - *val1 = utils.ToString(v2) - } - } - if f, err := strconv.ParseFloat(*val1, 32); err == nil { - if _, ok := revMap[f]; ok && key1 == key2 { - key := revMap[f] - utils.TemplatizedValues[key] = v2 - *val1 = utils.ToString(v2) - } - } - if f, err := strconv.ParseFloat(*val1, 64); err == nil { - if _, ok := revMap[f]; ok && key1 == key2 { - - key := revMap[*val1] - utils.TemplatizedValues[key] = v2 - *val1 = utils.ToString(v2) - } - } - - } - } -} -func reverseMap(m map[string]interface{}) map[interface{}]string { - var reverseMap = make(map[interface{}]string) - for key, val := range m { - reverseMap[val] = key - } - return reverseMap -} - -func ValidateAndMarshalJSON(log *zap.Logger, exp, act *string) (ValidatedJSON, error) { - var validatedJSON ValidatedJSON - var expected interface{} - var actual interface{} - var err error - if *exp != "" { - expected, err = UnmarshallJSON(*exp, log) - if err != nil { - return validatedJSON, err - } - } - if *act != "" { - actual, err = UnmarshallJSON(*act, log) - if err != nil { - return validatedJSON, err - } - } - validatedJSON.expected = expected - validatedJSON.actual = actual - if reflect.TypeOf(expected) != reflect.TypeOf(actual) { - validatedJSON.isIdentical = false - return validatedJSON, nil - } - cleanExp, err := json.Marshal(expected) - if err != nil { - return validatedJSON, err - } - cleanAct, err := json.Marshal(actual) - if err != nil { - return validatedJSON, err - } - *exp = string(cleanExp) - *act = string(cleanAct) - validatedJSON.isIdentical = true - return validatedJSON, nil -} - -// UnmarshallJSON returns unmarshalled JSON object. -func UnmarshallJSON(s string, log *zap.Logger) (interface{}, error) { - var result interface{} - if s == "" { - return nil, nil - } - if err := json.Unmarshal([]byte(s), &result); err != nil { - utils.LogError(log, err, "cannot convert json string into json object", zap.String("json", s)) - return nil, err - } - return result, nil -} - -// maxLineLength is chars PER expected/actual string. Can be changed no problem -const maxLineLength = 50 - -// ansiRegex is compiled at init-time; if compilation fails, falls back to a never-matches regex. -var ansiRegex *regexp.Regexp - -func init() { - re, err := regexp.Compile(`\x1b\[[0-9;]*[a-zA-Z]`) - if err != nil { - re, _ = regexp.Compile(`(?!)`) - } - ansiRegex = re -} - -var ansiResetCode = "\x1b[0m" - -type DiffsPrinter struct { - testCase string - statusExp string - statusAct string - headerExp map[string]string - headerAct map[string]string - bodyExp string - bodyAct string - bodyNoise map[string][]string - headNoise map[string][]string - hasarrayIndexMismatch bool - text string - typeExp string - typeAct string -} - -func (d *DiffsPrinter) SetHasarrayIndexMismatch(has bool) { - d.hasarrayIndexMismatch = has -} - -func NewDiffsPrinter(testCase string) DiffsPrinter { - return DiffsPrinter{testCase, "", "", map[string]string{}, map[string]string{}, "", "", map[string][]string{}, map[string][]string{}, false, "", "", ""} -} -func (d *DiffsPrinter) PushTypeDiff(exp, act string) { - d.typeExp, d.typeAct = exp, act -} -func (d *DiffsPrinter) PushStatusDiff(exp, act string) { - d.statusExp, d.statusAct = exp, act -} - -func (d *DiffsPrinter) PushFooterDiff(key string) { - d.hasarrayIndexMismatch = true - d.text = key -} - -func (d *DiffsPrinter) PushHeaderDiff(exp, act, key string, noise map[string][]string) { - d.headerExp[key], d.headerAct[key], d.headNoise = exp, act, noise -} - -func (d *DiffsPrinter) PushBodyDiff(exp, act string, noise map[string][]string) { - d.bodyExp, d.bodyAct, d.bodyNoise = exp, act, noise -} - -// Render will display and colorize diffs side-by-side -func (d *DiffsPrinter) Render() error { - diffs := []string{} - - if d.statusExp != d.statusAct { - diffs = append(diffs, sprintDiff(d.statusExp, d.statusAct, "status")) - } - - diffs = append(diffs, sprintDiffHeader(d.headerExp, d.headerAct)) - if len(d.bodyExp) != 0 || len(d.bodyAct) != 0 { - bE, bA := []byte(d.bodyExp), []byte(d.bodyAct) - if json.Valid(bE) && json.Valid(bA) { - difference, err := sprintJSONDiff(bE, bA, "body", d.bodyNoise) - if err != nil { - difference = sprintDiff(d.bodyExp, d.bodyAct, "body") - } - diffs = append(diffs, difference) - } else { - diffs = append(diffs, sprintDiff(d.bodyExp, d.bodyAct, "body")) - } - - } - - table := tablewriter.NewWriter(os.Stdout) - table.SetAutoWrapText(false) - table.SetHeader([]string{fmt.Sprintf("Diffs %v", d.testCase)}) - table.SetHeaderColor(tablewriter.Colors{tablewriter.FgHiRedColor}) - table.SetAlignment(tablewriter.ALIGN_CENTER) - - for _, e := range diffs { - table.Append([]string{e}) - } - if d.hasarrayIndexMismatch { - yellowPaint := color.New(color.FgYellow).SprintFunc() - redPaint := color.New(color.FgRed).SprintFunc() - startPart := " Expected and actual value" - var midPartpaint string - if len(d.text) > 0 { - midPartpaint = redPaint(d.text) - startPart += " of " - } - initalPart := yellowPaint(utils.WarningSign + startPart) - - endPaint := yellowPaint(" are in different order but have the same objects") - table.SetHeader([]string{initalPart + midPartpaint + endPaint}) - table.SetAlignment(tablewriter.ALIGN_CENTER) - table.Append([]string{initalPart + midPartpaint + endPaint}) - } - table.Render() - return nil -} -func (d *DiffsPrinter) TableWriter(diffs []string) error { - - table := tablewriter.NewWriter(os.Stdout) - table.SetAutoWrapText(false) - table.SetHeader([]string{fmt.Sprintf("Diffs %v", d.testCase)}) - table.SetHeaderColor(tablewriter.Colors{tablewriter.FgHiRedColor}) - table.SetAlignment(tablewriter.ALIGN_CENTER) - - for _, e := range diffs { - table.Append([]string{e}) - } - if d.hasarrayIndexMismatch { - yellowPaint := color.New(color.FgYellow).SprintFunc() - redPaint := color.New(color.FgRed).SprintFunc() - startPart := " Expected and actual value" - var midPartpaint string - if len(d.text) > 0 { - midPartpaint = redPaint(d.text) - startPart += " of " - } - initalPart := yellowPaint(utils.WarningSign + startPart) - - endPaint := yellowPaint(" are in different order but have the same objects") - table.SetHeader([]string{initalPart + midPartpaint + endPaint}) - table.SetAlignment(tablewriter.ALIGN_CENTER) - table.Append([]string{initalPart + midPartpaint + endPaint}) - } - table.Render() - return nil -} -func (d *DiffsPrinter) RenderAppender() error { - //Only show difference for the response body - diffs := []string{} - pass := true - - if d.typeExp != d.typeAct { - diffs = append(diffs, sprintDiff(d.typeExp, d.typeAct, "request body type")) - pass = false - } - if !pass { - err := d.TableWriter(diffs) - if err != nil { - return err - } - return nil - } - - if len(d.bodyExp) != 0 || len(d.bodyAct) != 0 { - pass = false - bE, bA := []byte(d.bodyExp), []byte(d.bodyAct) - if json.Valid(bE) && json.Valid(bA) { - difference, err := sprintJSONDiff(bE, bA, "response", d.bodyNoise) - if err != nil { - difference = sprintDiff(d.bodyExp, d.bodyAct, "response") - } - diffs = append(diffs, difference) - } else { - diffs = append(diffs, sprintDiff(d.bodyExp, d.bodyAct, "response")) - } - } - if !pass { - err := d.TableWriter(diffs) - if err != nil { - return err - } - - } - - return nil -} - -/* - * Returns a nice diff table where the left is the expect and the right - * is the actual. each entry in expect and actual will contain the key - * and the corresponding value. - */ -func sprintDiffHeader(expect, actual map[string]string) string { - - diff := jsonDiff.CompareHeaders(expect, actual) - - if len(expect) > maxLineLength || len(actual) > maxLineLength { - return expectActualTable(diff.Expected, diff.Actual, "header", false) // Don't centerize - } - return expectActualTable(diff.Expected, diff.Actual, "header", true) -} - -/* - * Returns a nice diff table where the left is the expect and the right - * is the actual. For JSON-based diffs use SprintJSONDiff - * field: body, status... - */ -func sprintDiff(expect, actual, field string) string { - - diff := jsonDiff.Compare(expect, actual) - - if len(expect) > maxLineLength || len(actual) > maxLineLength { - return expectActualTable(diff.Expected, diff.Actual, field, false) - } - return expectActualTable(diff.Expected, diff.Actual, field, true) -} - -/* This will return the json diffs in a beautifull way. It will in fact - * create a colorized table-based expect-response string and return it. - * on the left-side there'll be the expect and on the right the actual - * response. Its important to mention the inputs must to be a json. If - * the body isnt in the rest-api formats (what means it is not json-based) - * its better to use a generic diff output as the SprintDiff. - */ -func sprintJSONDiff(json1 []byte, json2 []byte, field string, noise map[string][]string) (string, error) { - diff, err := jsonDiff.CompareJSON(json1, json2, noise, false) - if err != nil { - return "", err - } - result := expectActualTable(diff.Expected, diff.Actual, field, false) - return result, nil -} - -func wrapTextWithAnsi(input string) string { - scanner := bufio.NewScanner(strings.NewReader(input)) // Create a scanner to read the input string line by line. - var wrappedBuilder strings.Builder // Builder for the resulting wrapped text. - currentAnsiCode := "" // Variable to hold the current ANSI escape sequence. - lastAnsiCode := "" // Variable to hold the last ANSI escape sequence. - - // Iterate over each line in the input string. - for scanner.Scan() { - line := scanner.Text() // Get the current line. - - // If there is a current ANSI code, append it to the builder. - if currentAnsiCode != "" { - wrappedBuilder.WriteString(currentAnsiCode) - } - - // Find all ANSI escape sequences in the current line. - startAnsiCodes := ansiRegex.FindAllString(line, -1) - if len(startAnsiCodes) > 0 { - // Update the last ANSI escape sequence to the last one found in the line. - lastAnsiCode = startAnsiCodes[len(startAnsiCodes)-1] - } - - // Append the current line to the builder. - wrappedBuilder.WriteString(line) - - // Check if the current ANSI code needs to be reset or updated. - if (currentAnsiCode != "" && !strings.HasSuffix(line, ansiResetCode)) || len(startAnsiCodes) > 0 { - // If the current line does not end with a reset code or if there are ANSI codes, append a reset code. - wrappedBuilder.WriteString(ansiResetCode) - // Update the current ANSI code to the last one found in the line. - currentAnsiCode = lastAnsiCode - } else { - // If no ANSI codes need to be maintained, reset the current ANSI code. - currentAnsiCode = "" - } - - // Append a newline character to the builder. - wrappedBuilder.WriteString("\n") - } - - // Return the processed string with properly wrapped ANSI escape sequences. - return wrappedBuilder.String() -} - -func expectActualTable(exp string, act string, field string, centerize bool) string { - buf := &bytes.Buffer{} - table := tablewriter.NewWriter(buf) - - if centerize { - table.SetAlignment(tablewriter.ALIGN_CENTER) - } else { - table.SetAlignment(tablewriter.ALIGN_LEFT) - } - // jsonDiff.JsonDiff() - exp = wrapTextWithAnsi(exp) - act = wrapTextWithAnsi(act) - table.SetHeader([]string{fmt.Sprintf("Expect %v", field), fmt.Sprintf("Actual %v", field)}) - table.SetAutoWrapText(false) - table.SetBorder(false) - table.SetColMinWidth(0, maxLineLength) - table.SetColMinWidth(1, maxLineLength) - table.Append([]string{exp, act}) - table.Render() - return buf.String() -} - -func Contains(elems []string, v string) bool { - for _, s := range elems { - if v == s { - return true - } - } - return false -} - -func checkKey(res *[]models.HeaderResult, key string) bool { - for _, v := range *res { - if key == v.Expected.Key { - return false - } - } - return true -} - -func CompareHeaders(h1 http.Header, h2 http.Header, res *[]models.HeaderResult, noise map[string][]string) bool { - if res == nil { - return false - } - match := true - _, isHeaderNoisy := noise["header"] - for k, v := range h1 { - regexArr, isNoisy := SubstringKeyMatch(strings.ToLower(k), noise) - if isNoisy && len(regexArr) != 0 { - isNoisy, _ = MatchesAnyRegex(v[0], regexArr) - } - isNoisy = isNoisy || isHeaderNoisy - val, ok := h2[k] - if !isNoisy { - if !ok { - if checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: false, - Expected: models.Header{ - Key: k, - Value: v, - }, - Actual: models.Header{ - Key: k, - Value: nil, - }, - }) - } - - match = false - continue - } - if len(v) != len(val) { - if checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: false, - Expected: models.Header{ - Key: k, - Value: v, - }, - Actual: models.Header{ - Key: k, - Value: val, - }, - }) - } - match = false - continue - } - for i, e := range v { - if val[i] != e { - if checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: false, - Expected: models.Header{ - Key: k, - Value: v, - }, - Actual: models.Header{ - Key: k, - Value: val, - }, - }) - } - match = false - continue - } - } - } - if checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: true, - Expected: models.Header{ - Key: k, - Value: v, - }, - Actual: models.Header{ - Key: k, - Value: val, - }, - }) - } - } - for k, v := range h2 { - regexArr, isNoisy := SubstringKeyMatch(strings.ToLower(k), noise) - if isNoisy && len(regexArr) != 0 { - isNoisy, _ = MatchesAnyRegex(v[0], regexArr) - } - isNoisy = isNoisy || isHeaderNoisy - val, ok := h1[k] - if isNoisy && checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: true, - Expected: models.Header{ - Key: k, - Value: val, - }, - Actual: models.Header{ - Key: k, - Value: v, - }, - }) - continue - } - if !ok { - if checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: false, - Expected: models.Header{ - Key: k, - Value: nil, - }, - Actual: models.Header{ - Key: k, - Value: v, - }, - }) - } - - match = false - } - } - return match -} - -func MapToArray(mp map[string][]string) []string { - var result []string - for k := range mp { - result = append(result, k) - } - return result -} - -func SubstringKeyMatch(s string, mp map[string][]string) ([]string, bool) { - for key, val := range mp { - if strings.Contains(s, key) { - return val, true - } - } - return []string{}, false -} - -// func CheckStringExist(s string, mp map[string][]string) ([]string, bool) { -// if val, ok := mp[s]; ok { -// return val, ok -// } -// return []string{}, false -// } - -func AddHTTPBodyToMap(body string, m map[string][]string) error { - // add body - if json.Valid([]byte(body)) { - var result interface{} - - err := json.Unmarshal([]byte(body), &result) - if err != nil { - return err - } - j := Flatten(result) - for k, v := range j { - nk := "body" - if k != "" { - nk = nk + "." + k - } - m[nk] = v - } - } else { - // add it as raw text - m["body"] = []string{body} - } - return nil -} - -// Flatten takes a map and returns a new one where nested maps are replaced -// by dot-delimited keys. -// examples of valid jsons - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#examples -func Flatten(j interface{}) map[string][]string { - if j == nil { - return map[string][]string{"": {""}} - } - o := make(map[string][]string) - x := reflect.ValueOf(j) - switch x.Kind() { - case reflect.Map: - m, ok := j.(map[string]interface{}) - if !ok { - return map[string][]string{} - } - for k, v := range m { - nm := Flatten(v) - for nk, nv := range nm { - fk := k - if nk != "" { - fk = fk + "." + nk - } - o[fk] = nv - } - } - case reflect.Bool: - o[""] = []string{strconv.FormatBool(x.Bool())} - case reflect.Float64: - o[""] = []string{strconv.FormatFloat(x.Float(), 'E', -1, 64)} - case reflect.String: - o[""] = []string{x.String()} - case reflect.Slice: - child, ok := j.([]interface{}) - if !ok { - return map[string][]string{} - } - for _, av := range child { - nm := Flatten(av) - for nk, nv := range nm { - if ov, exists := o[nk]; exists { - o[nk] = append(ov, nv...) - } else { - o[nk] = nv - } - } - } - } - return o -} - -func ArrayToMap(arr []string) map[string]bool { - res := map[string]bool{} - for i := range arr { - res[arr[i]] = true - } - return res -} - -func InterfaceToString(val interface{}) string { - switch v := val.(type) { - case int: - return fmt.Sprintf("%d", v) - case float64: - return fmt.Sprintf("%f", v) - case bool: - return fmt.Sprintf("%t", v) - case string: - return v - default: - return fmt.Sprintf("%v", v) - } -} - -func JsonContains(actualJSON string, expectedJSON map[string]interface{}) (bool, error) { - var actual interface{} - err := json.Unmarshal([]byte(actualJSON), &actual) - if err != nil { - return false, fmt.Errorf("failed to unmarshal actual JSON: %v", err) - } - - return containsRecursive(actual, expectedJSON), nil -} - -// containsRecursive recursively checks if the expected data is in the actual data. -func containsRecursive(actual interface{}, expected map[string]interface{}) bool { - actualMap, ok := actual.(map[string]interface{}) - if !ok { - return false - } - for key, expectedValue := range expected { - actualValue, exists := actualMap[key] - if !exists { - return false - } - - switch v := expectedValue.(type) { - case map[string]interface{}: - if actualMapVal, ok := actualValue.(map[string]interface{}); ok { - if !containsRecursive(actualMapVal, v) { - return false - } - } else { - return false - } - default: - if !reflect.DeepEqual(actualValue, expectedValue) { - return false - } - } - } - return true -} diff --git a/keploy/pkg/models/README.md b/keploy/pkg/models/README.md deleted file mode 100755 index 18c3566..0000000 --- a/keploy/pkg/models/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Models Package Documentation - -This package defines all the Go structs used for storing the captured data. It is designed as an independent module. \ No newline at end of file diff --git a/keploy/pkg/models/assertions.go b/keploy/pkg/models/assertions.go deleted file mode 100644 index 5a2f52f..0000000 --- a/keploy/pkg/models/assertions.go +++ /dev/null @@ -1,17 +0,0 @@ -package models - -// AssertionType defines a custom type for supported assertion keys. -type AssertionType string - -const ( - NoiseAssertion AssertionType = "noise" - StatusCode AssertionType = "status_code" - StatusCodeClass AssertionType = "status_code_class" - StatusCodeIn AssertionType = "status_code_in" - HeaderEqual AssertionType = "header_equal" - HeaderContains AssertionType = "header_contains" - HeaderExists AssertionType = "header_exists" - HeaderMatches AssertionType = "header_matches" - JsonEqual AssertionType = "json_equal" - JsonContains AssertionType = "json_contains" -) diff --git a/keploy/pkg/models/auth.go b/keploy/pkg/models/auth.go deleted file mode 100644 index 297988e..0000000 --- a/keploy/pkg/models/auth.go +++ /dev/null @@ -1,14 +0,0 @@ -package models - -type AuthReq struct { - InstallationID string `json:"installationID"` - GitHubToken string `json:"gitHubtoken"` - Platform string `json:"platform"` -} - -type AuthResp struct { - IsValid bool `json:"isValid"` - EmailID string `json:"email"` - JwtToken string `json:"jwtToken"` - Error string `json:"error"` -} diff --git a/keploy/pkg/models/config.go b/keploy/pkg/models/config.go deleted file mode 100644 index e74d79e..0000000 --- a/keploy/pkg/models/config.go +++ /dev/null @@ -1,33 +0,0 @@ -// Package models provides data models for the keploy. -package models - -type TestSet struct { - PreScript string `json:"pre_script" bson:"pre_script" yaml:"preScript"` - PostScript string `json:"post_script" bson:"post_script" yaml:"postScript"` - AppCommand string `json:"app_command" bson:"app_command" yaml:"appCommand"` - Template map[string]interface{} `json:"template" bson:"template" yaml:"template"` - Secret map[string]interface{} `json:"secret" bson:"secret" yaml:"secret,omitempty"` - MockRegistry *MockRegistry `yaml:"mockRegistry" bson:"mock_registry" json:"mockRegistry,omitempty"` - Metadata map[string]interface{} `json:"metadata" bson:"metadata" yaml:"metadata"` -} - -// Secret interface for types that support secret configuration. -type Secret interface { - SetSecrets(secrets map[string]interface{}) -} - -func (ts *TestSet) SetSecrets(secrets map[string]interface{}) { - ts.Secret = secrets -} - -type MockRegistry struct { - Mock string `json:"mock" bson:"mock" yaml:"mock,omitempty"` - App string `json:"app" bson:"app" yaml:"app,omitempty"` - User string `json:"user" bson:"user" yaml:"user,omitempty"` -} - -type Plan struct { - Type string `json:"type"` - Status string `json:"status"` - KUnits int `json:"kunits,omitempty"` -} diff --git a/keploy/pkg/models/const.go b/keploy/pkg/models/const.go deleted file mode 100755 index 9ad2b97..0000000 --- a/keploy/pkg/models/const.go +++ /dev/null @@ -1,128 +0,0 @@ -package models - -import ( - "fmt" - "time" - - "github.com/fatih/color" - "github.com/k0kubun/pp/v3" - "go.keploy.io/server/v2/config" -) - -// Patterns for different usecases in keploy -const ( - NoSQLDB string = "NO_SQL_DB" - SQLDB string = "SQL_DB" - GRPC string = "GRPC" - HTTPClient string = "HTTP_CLIENT" - TestSetPattern string = "test-set-" - String string = "string" - TestRunTemplateName string = "test-run-" -) - -const ( - Unknown config.Language = "Unknown" // Unknown language - Go config.Language = "go" // Go language - Java config.Language = "java" // Java language - Python config.Language = "python" // Python language - Javascript config.Language = "javascript" // Javascript language -) - -var ( - PassThroughHosts = []string{"^dc\\.services\\.visualstudio\\.com$"} -) - -var orangeColorSGR = []color.Attribute{38, 5, 208} - -var BaseTime = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) - -var IsAnsiDisabled = false - -var HighlightString = func(a ...interface{}) string { - if IsAnsiDisabled { - return fmt.Sprint(a) - } - return color.New(orangeColorSGR...).SprintFunc()(a) -} - -var HighlightPassingString = func(a ...interface{}) string { - if IsAnsiDisabled { - return fmt.Sprint(a) - } - return color.New(color.FgGreen).SprintFunc()(a) -} - -var HighlightFailingString = func(a ...interface{}) string { - if IsAnsiDisabled { - return fmt.Sprint(a) - } - return color.New(color.FgRed).SprintFunc()(a) -} - -var HighlightGrayString = func(a ...interface{}) string { - if IsAnsiDisabled { - return fmt.Sprint(a) - } - return color.New(color.FgHiBlack).SprintFunc()(a) -} - -var defaultColorScheme = pp.ColorScheme{ - Bool: pp.NoColor, - Integer: pp.NoColor, - Float: pp.NoColor, - String: pp.NoColor, - StringQuotation: pp.NoColor, - EscapedChar: pp.NoColor, - FieldName: pp.NoColor, - PointerAdress: pp.NoColor, - Nil: pp.NoColor, - Time: pp.NoColor, - StructName: pp.NoColor, - ObjectLength: pp.NoColor, -} - -var GetPassingColorScheme = func() pp.ColorScheme { - if IsAnsiDisabled { - return defaultColorScheme - } - return pp.ColorScheme{ - String: pp.Green, - StringQuotation: pp.Green | pp.Bold, - FieldName: pp.White, - Integer: pp.Blue | pp.Bold, - StructName: pp.NoColor, - Bool: pp.Cyan | pp.Bold, - Float: pp.Magenta | pp.Bold, - EscapedChar: pp.Magenta | pp.Bold, - PointerAdress: pp.Blue | pp.Bold, - Nil: pp.Cyan | pp.Bold, - Time: pp.Blue | pp.Bold, - ObjectLength: pp.Blue, - } -} - -var GetFailingColorScheme = func() pp.ColorScheme { - if IsAnsiDisabled { - return defaultColorScheme - } - return pp.ColorScheme{ - Bool: pp.Cyan | pp.Bold, - Integer: pp.Blue | pp.Bold, - Float: pp.Magenta | pp.Bold, - String: pp.Red, - StringQuotation: pp.Red | pp.Bold, - EscapedChar: pp.Magenta | pp.Bold, - FieldName: pp.Yellow, - PointerAdress: pp.Blue | pp.Bold, - Nil: pp.Cyan | pp.Bold, - Time: pp.Blue | pp.Bold, - StructName: pp.White, - ObjectLength: pp.Blue, - } -} - -type contextKey string - -const ErrGroupKey contextKey = "errGroup" -const ClientConnectionIDKey contextKey = "clientConnectionId" -const DestConnectionIDKey contextKey = "destConnectionId" diff --git a/keploy/pkg/models/errors.go b/keploy/pkg/models/errors.go deleted file mode 100644 index 3722a5a..0000000 --- a/keploy/pkg/models/errors.go +++ /dev/null @@ -1,27 +0,0 @@ -package models - -import "fmt" - -type AppError struct { - AppErrorType AppErrorType - Err error -} - -type AppErrorType string - -func (e AppError) Error() string { - if e.Err != nil { - return fmt.Sprintf("%s: %v", e.AppErrorType, e.Err) - } - return string(e.AppErrorType) -} - -// AppErrorType is a type of error that can be returned by the application -const ( - ErrCommandError AppErrorType = "exited due to command error" - ErrUnExpected AppErrorType = "an unexpected error occurred" - ErrInternal AppErrorType = "an internal error occurred" - ErrAppStopped AppErrorType = "app stopped" - ErrCtxCanceled AppErrorType = "context canceled" - ErrTestBinStopped AppErrorType = "test binary stopped" -) diff --git a/keploy/pkg/models/generic.go b/keploy/pkg/models/generic.go deleted file mode 100644 index 0327c0c..0000000 --- a/keploy/pkg/models/generic.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -import ( - "time" -) - -type GenericSchema struct { - Metadata map[string]string `json:"metadata" yaml:"metadata"` - GenericRequests []Payload `json:"RequestBin,omitempty"` - GenericResponses []Payload `json:"ResponseBin,omitempty"` - ReqTimestampMock time.Time `json:"reqTimestampMock,omitempty"` - ResTimestampMock time.Time `json:"resTimestampMock,omitempty"` -} diff --git a/keploy/pkg/models/github.go b/keploy/pkg/models/github.go deleted file mode 100644 index 47aa509..0000000 --- a/keploy/pkg/models/github.go +++ /dev/null @@ -1,20 +0,0 @@ -package models - -type DeviceCodeResponse struct { - DeviceCode string `json:"device_code"` - UserCode string `json:"user_code"` - VerificationURI string `json:"verification_uri"` - Interval int `json:"interval"` -} - -type AccessTokenResponse struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - Scope string `json:"scope"` -} - -// GitHub URLs -const ( - DeviceCodeURL = "https://github.com/login/device/code" - TokenURL = "https://github.com/login/oauth/access_token" -) diff --git a/keploy/pkg/models/grpc.go b/keploy/pkg/models/grpc.go deleted file mode 100644 index bf0ca62..0000000 --- a/keploy/pkg/models/grpc.go +++ /dev/null @@ -1,79 +0,0 @@ -package models - -import ( - "time" -) - -type GrpcSpec struct { - Metadata map[string]string `json:"metadata" yaml:"metadata"` - GrpcReq GrpcReq `json:"grpcReq" yaml:"grpcReq"` - GrpcResp GrpcResp `json:"grpcResp" yaml:"grpcResp"` - Created int64 `json:"created" yaml:"created"` - Assertions map[AssertionType]interface{} `json:"assertions" yaml:"assertions"` - ReqTimestampMock time.Time `json:"reqTimestampMock" yaml:"reqTimestampMock,omitempty"` - ResTimestampMock time.Time `json:"resTimestampMock" yaml:"resTimestampMock,omitempty"` -} - -type GrpcHeaders struct { - PseudoHeaders map[string]string `json:"pseudo_headers" yaml:"pseudo_headers"` - OrdinaryHeaders map[string]string `json:"ordinary_headers" yaml:"ordinary_headers"` -} - -type GrpcLengthPrefixedMessage struct { - CompressionFlag uint `json:"compression_flag" yaml:"compression_flag"` - MessageLength uint32 `json:"message_length" yaml:"message_length"` - DecodedData string `json:"decoded_data" yaml:"decoded_data"` -} - -type GrpcReq struct { - Headers GrpcHeaders `json:"headers" yaml:"headers"` - Body GrpcLengthPrefixedMessage `json:"body" yaml:"body"` - Timestamp time.Time `json:"timestamp" yaml:"timestamp"` -} - -type GrpcResp struct { - Headers GrpcHeaders `json:"headers" yaml:"headers"` - Body GrpcLengthPrefixedMessage `json:"body" yaml:"body"` - Trailers GrpcHeaders `json:"trailers" yaml:"trailers"` - Timestamp time.Time `json:"timestamp" yaml:"timestamp"` -} - -// GrpcStream is a helper function to combine the request-response model in a single struct -type GrpcStream struct { - StreamID uint32 - GrpcReq GrpcReq - GrpcResp GrpcResp - - // to handle request (coming in multiple frames) - ReqRawData []byte - ReqPrefixParsed bool - ReqExpectedLength uint32 - - // to handle response (coming in multiple frames) - RespRawData []byte - RespPrefixParsed bool - RespExpectedLength uint32 -} - -// NewGrpcStream returns a GrpcStream with all the nested maps initialised. -func NewGrpcStream(streamID uint32) GrpcStream { - return GrpcStream{ - StreamID: streamID, - GrpcReq: GrpcReq{ - Headers: GrpcHeaders{ - PseudoHeaders: make(map[string]string), - OrdinaryHeaders: make(map[string]string), - }, - }, - GrpcResp: GrpcResp{ - Headers: GrpcHeaders{ - PseudoHeaders: make(map[string]string), - OrdinaryHeaders: make(map[string]string), - }, - Trailers: GrpcHeaders{ - PseudoHeaders: make(map[string]string), - OrdinaryHeaders: make(map[string]string), - }, - }, - } -} diff --git a/keploy/pkg/models/http.go b/keploy/pkg/models/http.go deleted file mode 100755 index e7403bd..0000000 --- a/keploy/pkg/models/http.go +++ /dev/null @@ -1,48 +0,0 @@ -package models - -import ( - "time" -) - -type Method string - -type HTTPReq struct { - Method Method `json:"method" yaml:"method"` - ProtoMajor int `json:"proto_major" yaml:"proto_major"` // e.g. 1 - ProtoMinor int `json:"proto_minor" yaml:"proto_minor"` // e.g. 0 - URL string `json:"url" yaml:"url"` - URLParams map[string]string `json:"url_params" yaml:"url_params,omitempty"` - Header map[string]string `json:"header" yaml:"header"` - Body string `json:"body" yaml:"body"` - Binary string `json:"binary" yaml:"binary,omitempty"` - Form []FormData `json:"form" yaml:"form,omitempty"` - Timestamp time.Time `json:"timestamp" yaml:"timestamp"` -} - -type HTTPSchema struct { - Metadata map[string]string `json:"metadata" yaml:"metadata"` - Request HTTPReq `json:"req" yaml:"req"` - Response HTTPResp `json:"resp" yaml:"resp"` - Objects []*OutputBinary `json:"objects" yaml:"objects"` - Assertions map[AssertionType]interface{} `json:"assertions" yaml:"assertions,omitempty"` - Created int64 `json:"created" yaml:"created,omitempty"` - ReqTimestampMock time.Time `json:"reqTimestampMock" yaml:"reqTimestampMock,omitempty"` - ResTimestampMock time.Time `json:"resTimestampMock" yaml:"resTimestampMock,omitempty"` -} - -type FormData struct { - Key string `json:"key" bson:"key" yaml:"key"` - Values []string `json:"values" bson:"values,omitempty" yaml:"values,omitempty"` - Paths []string `json:"paths" bson:"paths,omitempty" yaml:"paths,omitempty"` -} - -type HTTPResp struct { - StatusCode int `json:"status_code" yaml:"status_code"` // e.g. 200 - Header map[string]string `json:"header" yaml:"header"` - Body string `json:"body" yaml:"body"` - StatusMessage string `json:"status_message" yaml:"status_message"` - ProtoMajor int `json:"proto_major" yaml:"proto_major"` - ProtoMinor int `json:"proto_minor" yaml:"proto_minor"` - Binary string `json:"binary" yaml:"binary,omitempty"` - Timestamp time.Time `json:"timestamp" yaml:"timestamp"` -} diff --git a/keploy/pkg/models/instrument.go b/keploy/pkg/models/instrument.go deleted file mode 100644 index 1078eba..0000000 --- a/keploy/pkg/models/instrument.go +++ /dev/null @@ -1,63 +0,0 @@ -package models - -import ( - "crypto/tls" - "time" - - "go.keploy.io/server/v2/config" -) - -type HookOptions struct { - Rules []config.BypassRule - Mode Mode - EnableTesting bool - E2E bool - Port uint32 // used for e2e filtering -} - -type OutgoingOptions struct { - Rules []config.BypassRule - MongoPassword string - // TODO: role of SQLDelay should be mentioned in the comments. - SQLDelay time.Duration // This is the same as Application delay. - FallBackOnMiss bool // this enables to pass the request to the actual server if no mock is found during test mode. - Mocking bool // used to enable/disable mocking - DstCfg *ConditionalDstCfg - Backdate time.Time // used to set backdate in cacert request -} - -type ConditionalDstCfg struct { - Addr string // Destination Addr (ip:port) - Port uint - TLSCfg *tls.Config -} - -type IncomingOptions struct { - Filters []config.Filter - BasePath string -} - -type SetupOptions struct { - Container string - DockerNetwork string - DockerDelay uint64 -} - -type RunOptions struct { - //IgnoreErrors bool - AppCommand string // command to run the application -} - -//For test bench - -type ModeKey uint32 - -// These are the keys used to send the keploy record and test ports and pids to the ebpf program when testbench is enabled -const ( - RecordKey ModeKey = 0 - TestKey ModeKey = 1 -) - -type TestingOptions struct { - Mode Mode -} diff --git a/keploy/pkg/models/mock.go b/keploy/pkg/models/mock.go deleted file mode 100755 index 2611084..0000000 --- a/keploy/pkg/models/mock.go +++ /dev/null @@ -1,92 +0,0 @@ -package models - -import ( - "time" - - "go.keploy.io/server/v2/pkg/models/mysql" -) - -type Kind string - -const ( - HTTP Kind = "Http" - GENERIC Kind = "Generic" - REDIS Kind = "Redis" - MySQL Kind = "MySQL" - Postgres Kind = "Postgres" - GRPC_EXPORT Kind = "gRPC" - Mongo Kind = "Mongo" -) - -type Mock struct { - Version Version `json:"Version,omitempty" bson:"Version,omitempty"` - Name string `json:"Name,omitempty" bson:"Name,omitempty"` - Kind Kind `json:"Kind,omitempty" bson:"Kind,omitempty"` - Spec MockSpec `json:"Spec,omitempty" bson:"Spec,omitempty"` - TestModeInfo TestModeInfo `json:"TestModeInfo,omitempty" bson:"TestModeInfo,omitempty"` // Map for additional test mode information - ConnectionID string `json:"ConnectionId,omitempty" bson:"ConnectionId,omitempty"` -} - -type TestModeInfo struct { - ID int `json:"Id,omitempty" bson:"Id,omitempty"` - IsFiltered bool `json:"isFiltered,omitempty" bson:"isFiltered,omitempty"` - SortOrder int64 `json:"sortOrder,omitempty" bson:"SortOrder,omitempty"` -} - -func (m *Mock) GetKind() string { - return string(m.Kind) -} - -type MockSpec struct { - Metadata map[string]string `json:"Metadata,omitempty" bson:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - GenericRequests []Payload `json:"RequestBin,omitempty" bson:"generic_requests,omitempty"` - GenericResponses []Payload `json:"ResponseBin,omitempty" bson:"generic_responses,omitempty"` - RedisRequests []Payload `json:"redisRequests,omitempty" bson:"redis_requests,omitempty"` - RedisResponses []Payload `json:"redisResponses,omitempty" bson:"redis_responses,omitempty"` - HTTPReq *HTTPReq `json:"Req,omitempty" bson:"http_req,omitempty"` - HTTPResp *HTTPResp `json:"Res,omitempty" bson:"http_resp,omitempty"` - Created int64 `json:"Created,omitempty" bson:"created,omitempty"` - MongoRequests []MongoRequest `json:"MongoRequests,omitempty" bson:"mongo_requests,omitempty"` - MongoResponses []MongoResponse `json:"MongoResponses,omitempty" bson:"mongo_responses,omitempty"` - PostgresRequests []Backend `json:"postgresRequests,omitempty" bson:"postgres_requests,omitempty"` - PostgresResponses []Frontend `json:"postgresResponses,omitempty" bson:"postgres_responses,omitempty"` - GRPCReq *GrpcReq `json:"gRPCRequest,omitempty" bson:"grpc_req,omitempty"` - GRPCResp *GrpcResp `json:"grpcResponse,omitempty" bson:"grpc_resp,omitempty"` - MySQLRequests []mysql.Request `json:"MySqlRequests,omitempty" bson:"my_sql_requests,omitempty"` - MySQLResponses []mysql.Response `json:"MySqlResponses,omitempty" bson:"my_sql_responses,omitempty"` - ReqTimestampMock time.Time `json:"ReqTimestampMock,omitempty" bson:"req_timestamp_mock,omitempty"` - ResTimestampMock time.Time `json:"ResTimestampMock,omitempty" bson:"res_timestamp_mock,omitempty"` -} - -// OutputBinary store the encoded binary output of the egress calls as base64-encoded strings -type OutputBinary struct { - Type string `json:"type" bson:"type" yaml:"type"` - Data string `json:"data" bson:"data" yaml:"data"` -} - -type OriginType string - -// constant for mock origin -const ( - FromServer OriginType = "server" - FromClient OriginType = "client" -) - -type MockUsage string - -const ( - Updated MockUsage = "updated" - Deleted MockUsage = "deleted" -) - -type Payload struct { - Origin OriginType `json:"Origin,omitempty" yaml:"origin" bson:"origin,omitempty"` - Message []OutputBinary `json:"Message,omitempty" yaml:"message" bson:"message,omitempty"` -} - -type MockState struct { - Name string `json:"name"` - Usage MockUsage `json:"usage"` - IsFiltered bool `json:"isFiltered"` - SortOrder int64 `json:"sortOrder"` -} diff --git a/keploy/pkg/models/mock_Secret.go b/keploy/pkg/models/mock_Secret.go deleted file mode 100644 index ccfaa4c..0000000 --- a/keploy/pkg/models/mock_Secret.go +++ /dev/null @@ -1,29 +0,0 @@ -// Code generated by mockery v2.53.2. DO NOT EDIT. - -package models - -import mock "github.com/stretchr/testify/mock" - -// MockSecret is an autogenerated mock type for the Secret type -type MockSecret struct { - mock.Mock -} - -// SetSecrets provides a mock function with given fields: secrets -func (_m *MockSecret) SetSecrets(secrets map[string]interface{}) { - _m.Called(secrets) -} - -// NewMockSecret creates a new instance of MockSecret. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockSecret(t interface { - mock.TestingT - Cleanup(func()) -}) *MockSecret { - mock := &MockSecret{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/keploy/pkg/models/mode.go b/keploy/pkg/models/mode.go deleted file mode 100755 index ceb642b..0000000 --- a/keploy/pkg/models/mode.go +++ /dev/null @@ -1,52 +0,0 @@ -package models - -import "errors" - -// Mode represents the mode at which the SDK is operating -// MODE_RECORD is for recording API calls to generate testcases -// MODE_TEST is for testing the application on previous recorded testcases -// MODE_OFF disables keploy SDK automatically from the application -type Mode string - -type KctxType string - -// constants for keploy mode -const ( - MODE_RECORD Mode = "record" - MODE_TEST Mode = "test" - MODE_OFF Mode = "off" - KCTX KctxType = "KeployContext" - KTime KctxType = "KeployTime" -) - -var ( - mode = MODE_OFF -) - -// Valid checks if the provided mode is valid -func (m Mode) Valid() bool { - if m == MODE_RECORD || m == MODE_TEST || m == MODE_OFF { - return true - } - return false -} - -// GetMode returns the mode of the keploy SDK -func GetMode() Mode { - return mode -} - -// SetTestMode sets the keploy SDK mode to MODE_TEST -func SetTestMode() { - _ = SetMode(MODE_TEST) -} - -// SetMode sets the keploy SDK mode -// error is returned if the mode is invalid -func SetMode(m Mode) error { - if !m.Valid() { - return errors.New("invalid mode: " + string(m)) - } - mode = m - return nil -} diff --git a/keploy/pkg/models/mongo.go b/keploy/pkg/models/mongo.go deleted file mode 100755 index ce2db77..0000000 --- a/keploy/pkg/models/mongo.go +++ /dev/null @@ -1,285 +0,0 @@ -package models - -import ( - "encoding/json" - "errors" - "time" - - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" - "gopkg.in/yaml.v3" -) - -type MongoSpec struct { - Metadata map[string]string `json:"metadata" yaml:"metadata"` - Requests []RequestYaml `json:"requests" yaml:"requests"` - Response []ResponseYaml `json:"responses" yaml:"responses"` - CreatedAt int64 `json:"created" yaml:"created,omitempty"` - ReqTimestampMock time.Time `json:"reqTimestampMock" yaml:"reqTimestampMock,omitempty"` - ResTimestampMock time.Time `json:"resTimestampMock" yaml:"resTimestampMock,omitempty"` -} - -type RequestYaml struct { - Header *MongoHeader `json:"header,omitempty" yaml:"header"` - Message yaml.Node `json:"message,omitempty" yaml:"message"` - ReadDelay int64 `json:"read_delay,omitempty" yaml:"read_delay,omitempty"` -} - -type ResponseYaml struct { - Header *MongoHeader `json:"header,omitempty" yaml:"header"` - Message yaml.Node `json:"message,omitempty" yaml:"message"` - ReadDelay int64 `json:"read_delay,omitempty" yaml:"read_delay,omitempty"` -} - -type MongoOpMessage struct { - FlagBits int `json:"flagBits" yaml:"flagBits" bson:"flagBits"` - Sections []string `json:"sections" yaml:"sections" bson:"sections"` - Checksum int `json:"checksum" yaml:"checksum" bson:"checksum"` -} - -type MongoOpQuery struct { - Flags int32 `json:"flags" yaml:"flags" bson:"flags"` - FullCollectionName string `json:"collection_name" yaml:"collection_name" bson:"collection_name"` - NumberToSkip int32 `json:"number_to_skip" yaml:"number_to_skip" bson:"number_to_skip"` - NumberToReturn int32 `json:"number_to_return" yaml:"number_to_return" bson:"number_to_return"` - Query string `json:"query" yaml:"query" bson:"query"` - ReturnFieldsSelector string `json:"return_fields_selector" yaml:"return_fields_selector" bson:"return_fields_selector"` -} - -type MongoOpReply struct { - ResponseFlags int32 `json:"response_flags" yaml:"response_flags" bson:"response_flags"` - CursorID int64 `json:"cursor_id" yaml:"cursor_id" bson:"cursor_id"` - StartingFrom int32 `json:"starting_from" yaml:"starting_from" bson:"starting_from"` - NumberReturned int32 `json:"number_returned" yaml:"number_returned" bson:"number_returned"` - Documents []string `json:"documents" yaml:"documents" bson:"documents"` -} - -type MongoHeader struct { - Length int32 `json:"length" yaml:"length" bson:"length"` - RequestID int32 `json:"requestId" yaml:"requestId" bson:"request_id"` - ResponseTo int32 `json:"responseTo" yaml:"responseTo" bson:"response_to"` - Opcode wiremessage.OpCode `json:"Opcode" yaml:"Opcode" bson:"opcode"` -} - -type MongoRequest struct { - Header *MongoHeader `json:"header,omitempty" yaml:"header,omitempty" bson:"header,omitempty"` - Message interface{} `json:"message,omitempty" yaml:"message,omitempty" bson:"message,omitempty"` - ReadDelay int64 `json:"read_delay,omitempty" yaml:"read_delay,omitempty" bson:"read_delay,omitempty"` -} - -// UnmarshalBSON implements bson.Unmarshaler for mongoRequests because of interface typeof field -func (mr *MongoRequest) UnmarshalBSON(data []byte) error { - - // duplicate struct to avoid infinite recursion - type MongoRequestAlias struct { - Header *MongoHeader `bson:"header,omitempty"` - Message bson.Raw `bson:"message,omitempty"` - ReadDelay int64 `bson:"read_delay,omitempty"` - } - var aux MongoRequestAlias - - if err := bson.Unmarshal(data, &aux); err != nil { - return err - } - - // assign the unmarshalled data to the original data - mr.Header = aux.Header - mr.ReadDelay = aux.ReadDelay - - // unmarshal the message into the correct type - switch mr.Header.Opcode { - case wiremessage.OpMsg: - var msg MongoOpMessage - if err := bson.Unmarshal(aux.Message, &msg); err != nil { - return err - } - mr.Message = &msg - case wiremessage.OpQuery: - var msg MongoOpQuery - if err := bson.Unmarshal(aux.Message, &msg); err != nil { - return err - } - mr.Message = &msg - default: - return errors.New("failed to unmarshal unknown opcode") - } - return nil -} - -// UnmarshalJSON implements json.Unmarshaler for mongoRequests because of interface typeof field -func (mr *MongoRequest) UnmarshalJSON(data []byte) error { - // duplicate struct to avoid infinite recursion - type MongoRequestAlias struct { - Header *MongoHeader `json:"header"` - Message json.RawMessage `json:"message"` - ReadDelay int64 `json:"read_delay"` - } - var aux MongoRequestAlias - - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - - // assign the unmarshalled data to the original data - mr.Header = aux.Header - mr.ReadDelay = aux.ReadDelay - - // unmarshal the message into the correct type - switch mr.Header.Opcode { - case wiremessage.OpMsg: - var msg MongoOpMessage - if err := json.Unmarshal(aux.Message, &msg); err != nil { - return err - } - mr.Message = &msg - case wiremessage.OpQuery: - var msg MongoOpQuery - if err := json.Unmarshal(aux.Message, &msg); err != nil { - return err - } - mr.Message = &msg - default: - return errors.New("failed to unmarshal unknown opcode") - } - - return nil -} - -// MarshalJSON implements json.Marshaler for mongoRequests because of interface typeof field -func (mr *MongoRequest) MarshalJSON() ([]byte, error) { - // duplicate struct to avoid infinite recursion - type MongoRequestAlias struct { - Header *MongoHeader `json:"header"` - Message json.RawMessage `json:"message"` - ReadDelay int64 `json:"read_delay"` - } - - aux := MongoRequestAlias{ - Header: mr.Header, - Message: json.RawMessage(nil), - ReadDelay: mr.ReadDelay, - } - - if mr.Message != nil { - // Marshal the message interface{} into JSON - msgJSON, err := json.Marshal(mr.Message) - if err != nil { - return nil, err - } - aux.Message = msgJSON - } - - return json.Marshal(aux) -} - -type MongoResponse struct { - Header *MongoHeader `json:"header,omitempty" yaml:"header,omitempty" bson:"header,omitempty"` - Message interface{} `json:"message,omitempty" yaml:"message,omitempty" bson:"message,omitempty"` - ReadDelay int64 `json:"read_delay,omitempty" yaml:"read_delay,omitempty" bson:"read_delay,omitempty"` -} - -// UnmarshalBSON implements bson.Unmarshaler for mongoResponses because of interface typeof field -func (mr *MongoResponse) UnmarshalBSON(data []byte) error { - // duplicate struct to avoid infinite recursion - type MongoResponseAlias struct { - Header *MongoHeader `bson:"header,omitempty"` - Message bson.Raw `bson:"message,omitempty"` - ReadDelay int64 `bson:"read_delay,omitempty"` - } - var aux MongoResponseAlias - - if err := bson.Unmarshal(data, &aux); err != nil { - return err - } - - // assign the unmarshalled data to the original data - mr.Header = aux.Header - mr.ReadDelay = aux.ReadDelay - - // unmarshal the message into the correct type - switch mr.Header.Opcode { - case wiremessage.OpMsg: - var msg MongoOpMessage - if err := bson.Unmarshal(aux.Message, &msg); err != nil { - return err - } - mr.Message = &msg - case wiremessage.OpReply: - var msg MongoOpReply - if err := bson.Unmarshal(aux.Message, &msg); err != nil { - return err - } - mr.Message = &msg - default: - return errors.New("failed to unmarshal unknown opcode") - } - - return nil -} - -// UnmarshalJSON implements json.Unmarshaler for mongoResponses because of interface typeof field -func (mr *MongoResponse) UnmarshalJSON(data []byte) error { - // duplicate struct to avoid infinite recursion - type MongoResponseAlias struct { - Header *MongoHeader `json:"header"` - Message json.RawMessage `json:"message"` - ReadDelay int64 `json:"read_delay"` - } - var aux MongoResponseAlias - - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - - // assign the unmarshalled data to the original data - mr.Header = aux.Header - mr.ReadDelay = aux.ReadDelay - - // unmarshal the message into the correct type - switch mr.Header.Opcode { - case wiremessage.OpMsg: - var msg MongoOpMessage - if err := json.Unmarshal(aux.Message, &msg); err != nil { - return err - } - mr.Message = &msg - case wiremessage.OpReply: - var msg MongoOpReply - if err := json.Unmarshal(aux.Message, &msg); err != nil { - return err - } - mr.Message = &msg - default: - return errors.New("failed to unmarshal unknown opcode") - } - - return nil - -} - -// MarshalJSON implements json.Marshaler for mongoResponses because of interface typeof field -func (mr *MongoResponse) MarshalJSON() ([]byte, error) { - // duplicate struct to avoid infinite recursion - type MongoResponseAlias struct { - Header *MongoHeader `json:"header"` - Message json.RawMessage `json:"message"` - ReadDelay int64 `json:"read_delay"` - } - - aux := MongoResponseAlias{ - Header: mr.Header, - Message: json.RawMessage(nil), - ReadDelay: mr.ReadDelay, - } - - if mr.Message != nil { - // Marshal the message interface{} into JSON - msgJSON, err := json.Marshal(mr.Message) - if err != nil { - return nil, err - } - aux.Message = msgJSON - } - - return json.Marshal(aux) -} diff --git a/keploy/pkg/models/mysql/comm.go b/keploy/pkg/models/mysql/comm.go deleted file mode 100644 index 14d9b5e..0000000 --- a/keploy/pkg/models/mysql/comm.go +++ /dev/null @@ -1,228 +0,0 @@ -// Package mysql in models provides realted structs for mysql protocol -package mysql - -// This file contains struct for command phase packets -/* refer: - -(i) https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_command_phase_text.html -(ii) https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_command_phase_utility.html -(iii) https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_command_phase_ps.html - -*/ - -// COM_QUERY packet - -type QueryPacket struct { - Command byte `yaml:"command"` - ParameterCount int `yaml:"parameter_count"` - NullBitmap []byte `yaml:"null_bitmap"` - NewParamsBindFlag byte `yaml:"new_params_bind_flag"` - Parameters []Parameter `yaml:"parameters"` - Query string `yaml:"query"` -} - -// LocalInFileRequestPacket is used to send local file request to server, currently not supported -type LocalInFileRequestPacket struct { - PacketType byte `yaml:"command"` - Filename string -} - -// TextResultSet is used as a response packet for COM_QUERY -type TextResultSet struct { - ColumnCount uint64 `yaml:"columnCount"` - Columns []*ColumnDefinition41 `yaml:"columns"` - EOFAfterColumns []byte `yaml:"eofAfterColumns"` - Rows []*TextRow `yaml:"rows"` - FinalResponse *GenericResponse `yaml:"FinalResponse"` -} - -// BinaryProtocolResultSet is used as a response packet for COM_STMT_EXECUTE -type BinaryProtocolResultSet struct { - ColumnCount uint64 `yaml:"columnCount"` - Columns []*ColumnDefinition41 `yaml:"columns"` - EOFAfterColumns []byte `yaml:"eofAfterColumns"` - Rows []*BinaryRow `yaml:"rows"` - FinalResponse *GenericResponse `yaml:"FinalResponse"` -} - -type GenericResponse struct { - Data []byte `yaml:"data"` - Type string `yaml:"type"` -} - -// Columns - -type ColumnCount struct { - // Header Header `yaml:"header"` - Count uint64 `yaml:"count"` -} - -type ColumnDefinition41 struct { - Header Header `yaml:"header"` - Catalog string `yaml:"catalog"` - Schema string `yaml:"schema"` - Table string `yaml:"table"` - OrgTable string `yaml:"org_table"` - Name string `yaml:"name"` - OrgName string `yaml:"org_name"` - FixedLength byte `yaml:"fixed_length"` - CharacterSet uint16 `yaml:"character_set"` - ColumnLength uint32 `yaml:"column_length"` - Type byte `yaml:"type"` - Flags uint16 `yaml:"flags"` - Decimals byte `yaml:"decimals"` - Filler []byte `yaml:"filler"` - DefaultValue string `yaml:"defaultValue"` -} - -//Rows - -type TextRow struct { - Header Header `yaml:"header"` - Values []ColumnEntry `yaml:"values"` -} - -type BinaryRow struct { - Header Header `yaml:"header"` - Values []ColumnEntry `yaml:"values"` - OkAfterRow bool `yaml:"okAfterRow"` - RowNullBuffer []byte `yaml:"rowNullBuffer"` -} - -type ColumnEntry struct { - Type FieldType `yaml:"type"` - Name string `yaml:"name"` - Value interface{} `yaml:"value"` - Unsigned bool `yaml:"unsigned"` -} - -// COM_STMT_PREPARE packet - -type StmtPreparePacket struct { - Command byte `yaml:"command"` - Query string `yaml:"query"` -} - -// COM_STMT_PREPARE_OK packet - -type StmtPrepareOkPacket struct { - Status byte `yaml:"status"` - StatementID uint32 `yaml:"statement_id"` - NumColumns uint16 `yaml:"num_columns"` - NumParams uint16 `yaml:"num_params"` - Filler byte `yaml:"filler"` - WarningAvailable bool `yaml:"warning_available"` - WarningCount uint16 `yaml:"warning_count"` - MetaFollowsAvailable bool `yaml:"meta_follows_available"` - MetaFollows byte `yaml:"meta_follows"` - - ParamDefs []*ColumnDefinition41 `yaml:"param_definitions"` - EOFAfterParamDefs []byte `yaml:"eofAfterParamDefs"` - ColumnDefs []*ColumnDefinition41 `yaml:"column_definitions"` - EOFAfterColumnDefs []byte `yaml:"eofAfterColumnDefs"` -} - -// COM_STMT_EXECUTE packet - -type StmtExecutePacket struct { - Status byte `yaml:"status"` - StatementID uint32 `yaml:"statement_id"` - Flags byte `yaml:"flags"` - IterationCount uint32 `yaml:"iteration_count"` - ParameterCount int `yaml:"parameter_count"` - NullBitmap []byte `yaml:"null_bitmap"` - NewParamsBindFlag byte `yaml:"new_params_bind_flag"` - Parameters []Parameter `yaml:"parameters"` -} - -type Parameter struct { - Type uint16 `yaml:"type"` - Unsigned bool `yaml:"unsigned"` - Name string `yaml:"name,omitempty"` - Value any `yaml:"value"` -} - -// COM_STMT_FETCH packet is not currently supported because its response involves multi-resultset - -type StmtFetchPacket struct { - Status byte `yaml:"status"` - StatementID uint32 `yaml:"statement_id"` - NumRows uint32 `yaml:"num_rows"` -} - -// COM_STMT_CLOSE packet - -type StmtClosePacket struct { - Status byte `yaml:"status"` - StatementID uint32 `yaml:"statement_id"` -} - -// COM_STMT_RESET packet - -type StmtResetPacket struct { - Status byte `yaml:"status"` - StatementID uint32 `yaml:"statement_id"` -} - -// COM_STMT_SEND_LONG_DATA packet - -type StmtSendLongDataPacket struct { - Status byte `yaml:"status"` - StatementID uint32 `yaml:"statement_id"` - ParameterID uint16 `yaml:"parameter_id"` - Data []byte `yaml:"data"` -} - -// Utility commands - -// COM_QUIT packet - -type QuitPacket struct { - Command byte `yaml:"command"` -} - -// COM_INIT_DB packet - -type InitDBPacket struct { - Command byte `yaml:"command"` - Schema string `yaml:"schema"` -} - -// COM_STATISTICS packet - -type StatisticsPacket struct { - Command byte `yaml:"command"` -} - -// COM_DEBUG packet - -type DebugPacket struct { - Command byte `yaml:"command"` -} - -// COM_PING packet - -type PingPacket struct { - Command byte `yaml:"command"` -} - -// COM_RESET_CONNECTION packet - -type ResetConnectionPacket struct { - Command byte `yaml:"command"` -} - -// COM_SET_OPTION packet - -type SetOptionPacket struct { - Status byte `yaml:"status"` - Option uint16 `yaml:"option"` -} - -// COM_CHANGE_USER packet (Not completed/supported as of now) -//refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_change_user.html - -type ChangeUserPacket struct { - Command byte `yaml:"command"` - // rest of the fields are not present as the packet is not supported -} diff --git a/keploy/pkg/models/mysql/conn.go b/keploy/pkg/models/mysql/conn.go deleted file mode 100644 index 51af3a6..0000000 --- a/keploy/pkg/models/mysql/conn.go +++ /dev/null @@ -1,68 +0,0 @@ -package mysql - -// This file contains struct for connection phase packets -// refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets.html - -// Initial Handshake Packets - -// HandshakeV10Packet represents the initial handshake packet sent by the server to the client -type HandshakeV10Packet struct { - ProtocolVersion uint8 `yaml:"protocol_version"` - ServerVersion string `yaml:"server_version"` - ConnectionID uint32 `yaml:"connection_id"` - AuthPluginData []byte `yaml:"auth_plugin_data,omitempty,flow"` - Filler byte `yaml:"filler"` - CapabilityFlags uint32 `yaml:"capability_flags"` - CharacterSet uint8 `yaml:"character_set"` - StatusFlags uint16 `yaml:"status_flags"` - AuthPluginName string `yaml:"auth_plugin_name"` -} - -// HandshakeResponse41Packet represents the response packet sent by the client to the server after receiving the HandshakeV10Packet -type HandshakeResponse41Packet struct { - CapabilityFlags uint32 `yaml:"capability_flags"` - MaxPacketSize uint32 `yaml:"max_packet_size"` - CharacterSet uint8 `yaml:"character_set"` - Filler [23]byte `yaml:"filler,omitempty,flow"` - Username string `yaml:"username"` - AuthResponse []byte `yaml:"auth_response,omitempty,flow"` - Database string `yaml:"database"` - AuthPluginName string `yaml:"auth_plugin_name"` - ConnectionAttributes map[string]string `yaml:"connection_attributes,omitempty"` - ZstdCompressionLevel byte `yaml:"zstdcompressionlevel"` -} - -type SSLRequestPacket struct { - CapabilityFlags uint32 `yaml:"capability_flags"` - MaxPacketSize uint32 `yaml:"max_packet_size"` - CharacterSet uint8 `yaml:"character_set"` - Filler [23]byte `yaml:"filler,omitempty,flow"` -} - -// Authentication Packets - -// AuthSwitchRequestPacket represents the packet sent by the server to the client to switch to a different authentication method -type AuthSwitchRequestPacket struct { - StatusTag byte `yaml:"status_tag"` - PluginName string `yaml:"plugin_name"` - PluginData string `yaml:"plugin_data"` -} - -// AuthSwitchResponsePacket represents the packet sent by the client to the server in response to an AuthSwitchRequestPacket. -// Note: If the server sends an AuthMoreDataPacket, the client will continue sending AuthSwitchResponsePackets until the server sends an OK packet or an ERR packet. -type AuthSwitchResponsePacket struct { - Data string `yaml:"data"` -} - -// AuthMoreDataPacket represents the packet sent by the server to the client to request additional data for authentication -type AuthMoreDataPacket struct { - StatusTag byte `yaml:"status_tag"` - Data string `yaml:"data"` -} - -// AuthNextFactorPacket represents the packet sent by the server to the client to request the next factor for multi-factor authentication -type AuthNextFactorPacket struct { - PacketType byte `yaml:"packet_type"` - PluginName string `yaml:"plugin_name"` - PluginData string `yaml:"plugin_data"` -} diff --git a/keploy/pkg/models/mysql/const.go b/keploy/pkg/models/mysql/const.go deleted file mode 100644 index 8b05206..0000000 --- a/keploy/pkg/models/mysql/const.go +++ /dev/null @@ -1,267 +0,0 @@ -package mysql - -// Basic Response Packet Status -const ( - OK byte = 0x00 - ERR byte = 0xff - EOF byte = 0xfe -) - -// LocalInFile Request packet type is not supported -const LocalInFile = 0xfb - -// Auth Packet Status -const ( - AuthSwitchRequest byte = 0xfe - AuthMoreData byte = 0x01 - AuthNextFactor byte = 0x02 - HandshakeV10 byte = 0x0a -) - -// Some constants for MySQL -const ( - HandshakeResponse41 = "HandshakeResponse41" - SSLRequest = "SSLRequest" - COM_STMT_PREPARE_OK = "COM_STMT_PREPARE_OK" -) - -type CachingSha2Password byte - -// MySQL authentication methods - -type AuthPluginName string - -// AuthPluginName constants -const ( - Native AuthPluginName = "mysql_native_password" - CachingSha2 AuthPluginName = "caching_sha2_password" - Sha256 AuthPluginName = "sha256_password" -) - -// Some constants for MySQL -const ( - EncryptedPassword = "encrypted_password" - PlainPassword = "plain_password" - AuthSwithResponse = "AuthSwitchResponse" -) - -// CachingSha2Password constants -const ( - RequestPublicKey CachingSha2Password = 2 - FastAuthSuccess CachingSha2Password = 3 - PerformFullAuthentication CachingSha2Password = 4 -) - -// Client Capability Flags -const ( - // https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__capabilities__flags.html - - CLIENT_LONG_PASSWORD uint32 = 1 << iota - CLIENT_FOUND_ROWS - CLIENT_LONG_FLAG - CLIENT_CONNECT_WITH_DB - CLIENT_NO_SCHEMA - CLIENT_COMPRESS - CLIENT_ODBC - CLIENT_LOCAL_FILES - CLIENT_IGNORE_SPACE - CLIENT_PROTOCOL_41 - CLIENT_INTERACTIVE - CLIENT_SSL - CLIENT_IGNORE_SIGPIPE - CLIENT_TRANSACTIONS - CLIENT_RESERVED - CLIENT_SECURE_CONNECTION - CLIENT_MULTI_STATEMENTS - CLIENT_MULTI_RESULTS - CLIENT_PS_MULTI_RESULTS - CLIENT_PLUGIN_AUTH - CLIENT_CONNECT_ATTRS - CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA - CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS - CLIENT_SESSION_TRACK - CLIENT_DEPRECATE_EOF - CLIENT_OPTIONAL_RESULTSET_METADATA - CLIENT_ZSTD_COMPRESSION_ALGORITHM - CLIENT_QUERY_ATTRIBUTES - MULTI_FACTOR_AUTHENTICATION - CLIENT_CAPABILITY_EXTENSION - CLIENT_SSL_VERIFY_SERVER_CERT - CLIENT_REMEMBER_OPTIONS -) - -type FieldType byte - -// Field Types -const ( - FieldTypeDecimal FieldType = iota - FieldTypeTiny - FieldTypeShort - FieldTypeLong - FieldTypeFloat - FieldTypeDouble - FieldTypeNULL - FieldTypeTimestamp - FieldTypeLongLong - FieldTypeInt24 - FieldTypeDate - FieldTypeTime - FieldTypeDateTime - FieldTypeYear - FieldTypeNewDate - FieldTypeVarChar - FieldTypeBit -) - -// Additional Field Types -const ( - FieldTypeJSON FieldType = iota + 0xf5 - FieldTypeNewDecimal - FieldTypeEnum - FieldTypeSet - FieldTypeTinyBLOB - FieldTypeMediumBLOB - FieldTypeLongBLOB - FieldTypeBLOB - FieldTypeVarString - FieldTypeString - FieldTypeGeometry -) - -// Field Flags -const ( - NOT_NULL_FLAG = 1 - PRI_KEY_FLAG = 2 - UNIQUE_KEY_FLAG = 4 - BLOB_FLAG = 16 - UNSIGNED_FLAG = 32 - ZEROFILL_FLAG = 64 - BINARY_FLAG = 128 - ENUM_FLAG = 256 - AUTO_INCREMENT_FLAG = 512 - TIMESTAMP_FLAG = 1024 - SET_FLAG = 2048 - NUM_FLAG = 32768 - PART_KEY_FLAG = 16384 - GROUP_FLAG = 32768 - UNIQUE_FLAG = 65536 -) - -// Cursor types for prepared statements -const ( - CURSOR_TYPE_NO_CURSOR byte = 0x0 - CURSOR_TYPE_READ_ONLY byte = 0x1 - CURSOR_TYPE_FOR_UPDATE byte = 0x2 - CURSOR_TYPE_SCROLLABLE byte = 0x4 - PARAMETER_COUNT_AVAILABLE byte = 0x8 -) - -// Utility command Packet Status -const ( - COM_QUIT byte = 0x01 - COM_INIT_DB byte = 0x02 - COM_FIELD_LIST byte = 0x04 - COM_STATISTICS byte = 0x08 - COM_DEBUG byte = 0x0d - COM_PING byte = 0x0e - COM_CHANGE_USER byte = 0x11 - COM_RESET_CONNECTION byte = 0x1f - // COM_SET_OPTION byte = 0x1a -) - -// Command Packet Status -const ( - COM_QUERY byte = 0x03 - COM_STMT_PREPARE byte = 0x16 - COM_STMT_EXECUTE byte = 0x17 - // COM_STMT_FETCH byte = 0x19 - COM_STMT_CLOSE byte = 0x19 - COM_STMT_RESET byte = 0x1a - COM_STMT_SEND_LONG_DATA byte = 0x18 - COM_TIME byte = 0x0f -) - -// ResultSet packets -type ResultSet string - -// ResultSet types -const ( - Binary ResultSet = "BinaryProtocolResultSet" - Text ResultSet = "TextResultSet" -) - -// Define the maps for basic response packets -var statusToString = map[byte]string{ - OK: "OK", - ERR: "ERR", - EOF: "EOF", - LocalInFile: "LocalInFile", -} - -// Define the maps for auth packet status -var authStatusToString = map[byte]string{ - AuthSwitchRequest: "AuthSwitchRequest", - AuthMoreData: "AuthMoreData", - AuthNextFactor: "AuthNextFactor", - HandshakeV10: "HandshakeV10", -} - -// Define the map for command packet status -var commandStatusToString = map[byte]string{ - //utility command - COM_QUIT: "COM_QUIT", - COM_INIT_DB: "COM_INIT_DB", - COM_FIELD_LIST: "COM_FIELD_LIST", - COM_STATISTICS: "COM_STATISTICS", - COM_DEBUG: "COM_DEBUG", - COM_PING: "COM_PING", - COM_CHANGE_USER: "COM_CHANGE_USER", - COM_RESET_CONNECTION: "COM_RESET_CONNECTION", - // COM_SET_OPTION: "COM_SET_OPTION", - // command - COM_QUERY: "COM_QUERY", - COM_STMT_PREPARE: "COM_STMT_PREPARE", - COM_STMT_EXECUTE: "COM_STMT_EXECUTE", - // COM_STMT_FETCH: "COM_STMT_FETCH", - COM_STMT_CLOSE: "COM_STMT_CLOSE", - COM_STMT_RESET: "COM_STMT_RESET", - COM_STMT_SEND_LONG_DATA: "COM_STMT_SEND_LONG_DATA", - - //Some extra commands - COM_TIME: "COM_TIME", -} - -// Define the map for cachingSha2Password -var cachingSha2PasswordToString = map[CachingSha2Password]string{ - RequestPublicKey: "RequestPublicKey", - FastAuthSuccess: "FastAuthSuccess", - PerformFullAuthentication: "PerformFullAuthentication", -} - -func StatusToString(status byte) string { - if str, ok := statusToString[status]; ok { - return str - } - return "UNKNOWN" -} - -func AuthStatusToString(status byte) string { - if str, ok := authStatusToString[status]; ok { - return str - } - return "UNKNOWN" -} - -func CommandStatusToString(status byte) string { - if str, ok := commandStatusToString[status]; ok { - return str - } - return "UNKNOWN" -} - -func CachingSha2PasswordToString(status CachingSha2Password) string { - if str, ok := cachingSha2PasswordToString[status]; ok { - return str - } - return "UNKNOWN" -} diff --git a/keploy/pkg/models/mysql/generic.go b/keploy/pkg/models/mysql/generic.go deleted file mode 100644 index 07be522..0000000 --- a/keploy/pkg/models/mysql/generic.go +++ /dev/null @@ -1,30 +0,0 @@ -package mysql - -// This file contains structs for mysql generic response packets -//refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_response_packets.html - -// OKPacket represents the OK packet sent by the server to the client, it represents a successful completion of a command -type OKPacket struct { - Header byte `json:"header" yaml:"header"` - AffectedRows uint64 `json:"affected_rows,omitempty" yaml:"affected_rows"` - LastInsertID uint64 `json:"last_insert_id,omitempty" yaml:"last_insert_id"` - StatusFlags uint16 `json:"status_flags,omitempty" yaml:"status_flags"` - Warnings uint16 `json:"warnings,omitempty" yaml:"warnings"` - Info string `json:"info,omitempty" yaml:"info"` -} - -// ERRPacket represents the ERR packet sent by the server to the client, it represents an error occurred during the execution of a command -type ERRPacket struct { - Header byte `yaml:"header"` - ErrorCode uint16 `yaml:"error_code"` - SQLStateMarker string `yaml:"sql_state_marker"` - SQLState string `yaml:"sql_state"` - ErrorMessage string `yaml:"error_message"` -} - -// EOFPacket represents the EOF packet sent by the server to the client, it represents the end of a query execution result -type EOFPacket struct { - Header byte `yaml:"header"` - Warnings uint16 `yaml:"warnings"` - StatusFlags uint16 `yaml:"status_flags"` -} diff --git a/keploy/pkg/models/mysql/mysql.go b/keploy/pkg/models/mysql/mysql.go deleted file mode 100644 index c1f6389..0000000 --- a/keploy/pkg/models/mysql/mysql.go +++ /dev/null @@ -1,61 +0,0 @@ -package mysql - -import ( - "time" - - "gopkg.in/yaml.v3" -) - -type Spec struct { - Metadata map[string]string `json:"metadata" yaml:"metadata"` - Requests []RequestYaml `json:"requests" yaml:"requests"` - Response []ResponseYaml `json:"responses" yaml:"responses"` - CreatedAt int64 `json:"created" yaml:"created,omitempty"` - ReqTimestampMock time.Time `json:"ReqTimestampMock,omitempty"` - ResTimestampMock time.Time `json:"ResTimestampMock,omitempty"` -} - -type RequestYaml struct { - Header *PacketInfo `json:"header,omitempty" yaml:"header"` - Meta map[string]string `json:"meta,omitempty" yaml:"meta,omitempty"` - Message yaml.Node `json:"message,omitempty" yaml:"message"` -} - -type ResponseYaml struct { - Header *PacketInfo `json:"header,omitempty" yaml:"header"` - Meta map[string]string `json:"meta,omitempty" yaml:"meta,omitempty"` - Message yaml.Node `json:"message,omitempty" yaml:"message"` -} - -type PacketInfo struct { - Header *Header `json:"header" yaml:"header"` - Type string `json:"packet_type" yaml:"packet_type"` -} - -type Request struct { - PacketBundle `json:"packet_bundle" yaml:"packet_bundle"` -} - -type Response struct { - PacketBundle `json:"packet_bundle" yaml:"packet_bundle"` - Payload string `json:"payload,omitempty" yaml:"payload,omitempty"` -} - -type PacketBundle struct { - Header *PacketInfo `json:"header" yaml:"header"` - Message interface{} `json:"message" yaml:"message"` - Meta map[string]string `json:"meta,omitempty" yaml:"meta,omitempty"` -} - -// MySql Packet -//refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_packets.html - -type Packet struct { - Header Header `json:"header" yaml:"header"` - Payload []byte `json:"payload,omitempty" yaml:"payload,omitempty"` -} - -type Header struct { - PayloadLength uint32 `json:"payload_length" yaml:"payload_length"` - SequenceID uint8 `json:"sequence_id" yaml:"sequence_id"` -} diff --git a/keploy/pkg/models/openapi.go b/keploy/pkg/models/openapi.go deleted file mode 100644 index f6945e4..0000000 --- a/keploy/pkg/models/openapi.go +++ /dev/null @@ -1,142 +0,0 @@ -package models - -// SchemaMatchMode defines the possible modes for schema matching. -type SchemaMatchMode int - -const ( - // IdentifyMode is used for identifying schema. - IdentifyMode SchemaMatchMode = iota - - // CompareMode is used for comparing schemas. - CompareMode -) - -// String returns the string representation of the SchemaMatchMode. -func (s SchemaMatchMode) String() string { - schemaModes := []string{"IDENTIFYMODE", "COMPAREMODE"} - - if int(s) < 0 || int(s) >= len(schemaModes) { - return "UNKNOWN" - } - - return schemaModes[s] -} - -// DrivenMode defines the possible modes for driven contexts. -type DrivenMode int - -const ( - // ConsumerMode is used for a consumer context. - ConsumerMode DrivenMode = iota - - // ProviderMode is used for a provider context. - ProviderMode -) - -// String returns the string representation of the DrivenMode. -func (d DrivenMode) String() string { - drivenModes := []string{"consumer", "provider"} - - if int(d) < 0 || int(d) >= len(drivenModes) { - return "UNKNOWN" - } - - return drivenModes[d] -} - -type HTTPDoc struct { - Version string `json:"version" yaml:"version"` - Kind string `json:"kind" yaml:"kind"` - Name string `json:"name" yaml:"name"` - Spec HTTPSchema `json:"spec" yaml:"spec"` -} - -type OpenAPI struct { - OpenAPI string `json:"openapi" yaml:"openapi"` - Info Info `json:"info" yaml:"info"` - Servers []map[string]string `json:"servers" yaml:"servers"` - Paths map[string]PathItem `json:"paths" yaml:"paths"` - Components map[string]interface{} `json:"components" yaml:"components"` -} - -type Info struct { - Title string `json:"title" yaml:"title"` - Version string `json:"version" yaml:"version"` - Description string `json:"description" yaml:"description"` -} - -type PathItem struct { - Get *Operation `json:"get,omitempty" yaml:"get,omitempty"` - Post *Operation `json:"post,omitempty" yaml:"post,omitempty"` - Put *Operation `json:"put,omitempty" yaml:"put,omitempty"` - Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"` - Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"` -} - -type Operation struct { - Summary string `json:"summary" yaml:"summary"` - Description string `json:"description" yaml:"description"` - Parameters []Parameter `json:"parameters" yaml:"parameters"` - OperationID string `json:"operationId" yaml:"operationId"` - RequestBody *RequestBody `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` - Responses map[string]ResponseItem `json:"responses" yaml:"responses"` -} - -type Parameter struct { - Name string `json:"name" yaml:"name"` - In string `json:"in" yaml:"in"` - Required bool `json:"required" yaml:"required"` - Schema ParamSchema `json:"schema" yaml:"schema"` - Example string `json:"example" yaml:"example"` -} - -type RequestBody struct { - Content map[string]MediaType `json:"content" yaml:"content"` -} - -type MediaType struct { - Schema Schema `json:"schema" yaml:"schema"` - Example map[string]interface{} `json:"example" yaml:"example"` -} - -type ResponseItem struct { - Description string `json:"description" yaml:"description"` - Content map[string]MediaType `json:"content" yaml:"content"` -} - -type Schema struct { - Type string `json:"type" yaml:"type"` - Properties map[string]map[string]interface{} `json:"properties" yaml:"properties"` -} - -type ParamSchema struct { - Type string `json:"type" yaml:"type"` -} -type SchemaInfo struct { - Service string `json:"service" yaml:"service"` - TestSetID string `json:"testSetId" yaml:"testSetId"` - Name string `json:"name" yaml:"name"` - Score float64 `json:"score" yaml:"score"` - Data OpenAPI `json:"data" yaml:"data"` -} -type Summary struct { - ServicesSummary []ServiceSummary `json:"servicesSummary" yaml:"servicesSummary"` -} -type ServiceSummary struct { - Service string `json:"service" yaml:"service"` - TestSets map[string]Status `json:"testSets" yaml:"testSets"` - PassedCount int `json:"passedCount" yaml:"passedCount"` - FailedCount int `json:"failedCount" yaml:"failedCount"` - MissedCount int `json:"missedCount" yaml:"missedCount"` -} -type Status struct { - Failed []string `json:"failed" yaml:"failed"` - Passed []string `json:"passed" yaml:"passed"` - Missed []string `json:"missed" yaml:"missed"` -} - -type MockMapping struct { - Service string `json:"service" yaml:"service"` - TestSetID string `json:"testSetId" yaml:"testSetId"` - Mocks []*OpenAPI `json:"mocks" yaml:"mocks"` -} diff --git a/keploy/pkg/models/postgres.go b/keploy/pkg/models/postgres.go deleted file mode 100755 index bb8306b..0000000 --- a/keploy/pkg/models/postgres.go +++ /dev/null @@ -1,114 +0,0 @@ -package models - -import ( - "time" - - "github.com/jackc/pgproto3/v2" -) - -// ProtocolVersionNumber should be replaced with actual version number if different -const ProtocolVersionNumber uint32 = 196608 - -type PostgresSpec struct { - Metadata map[string]string `json:"metadata" yaml:"metadata"` - - // Objects []*models.OutputBinary `json:"objects" yaml:"objects"` - PostgresRequests []Backend `json:"RequestBin,omitempty"` - PostgresResponses []Frontend `json:"ResponseBin,omitempty"` - - ReqTimestampMock time.Time `json:"ReqTimestampMock,omitempty"` - ResTimestampMock time.Time `json:"ResTimestampMock,omitempty"` -} - -// Backend is PG Request Packet Transcoder -type Backend struct { - PacketTypes []string `json:"header,omitempty" yaml:"header,omitempty,flow"` - Identfier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` - Length uint32 `json:"length,omitempty" yaml:"length,omitempty"` - Payload string `json:"payload,omitempty" yaml:"payload,omitempty"` - Bind pgproto3.Bind `yaml:"-"` - Binds []pgproto3.Bind `json:"bind,omitempty" yaml:"bind,omitempty"` - CancelRequest pgproto3.CancelRequest `json:"cancel_request,omitempty" yaml:"cancel_request,omitempty"` - Close pgproto3.Close `json:"close,omitempty" yaml:"close,omitempty"` - CopyFail pgproto3.CopyFail `json:"copy_fail,omitempty" yaml:"copy_fail,omitempty"` - CopyData pgproto3.CopyData `json:"copy_data,omitempty" yaml:"copy_data,omitempty"` - CopyDone pgproto3.CopyDone `json:"copy_done,omitempty" yaml:"copy_done,omitempty"` - Describe pgproto3.Describe `json:"describe,omitempty" yaml:"describe,omitempty"` - Execute pgproto3.Execute `yaml:"-"` - Executes []pgproto3.Execute `json:"execute,omitempty" yaml:"execute,omitempty"` - Flush pgproto3.Flush `json:"flush,omitempty" yaml:"flush,omitempty"` - FunctionCall pgproto3.FunctionCall `json:"function_call,omitempty" yaml:"function_call,omitempty"` - GssEncRequest pgproto3.GSSEncRequest `json:"gss_enc_request,omitempty" yaml:"gss_enc_request,omitempty"` - Parse pgproto3.Parse `yaml:"-"` - Parses []pgproto3.Parse `json:"parse,omitempty" yaml:"parse,omitempty"` - Query pgproto3.Query `json:"query,omitempty" yaml:"query,omitempty"` - SSlRequest pgproto3.SSLRequest `json:"ssl_request,omitempty" yaml:"ssl_request,omitempty"` - StartupMessage pgproto3.StartupMessage `json:"startup_message,omitempty" yaml:"startup_message,omitempty"` - Sync pgproto3.Sync `json:"sync,omitempty" yaml:"sync,omitempty"` - Terminate pgproto3.Terminate `json:"terminate,omitempty" yaml:"terminate,omitempty"` - SASLInitialResponse pgproto3.SASLInitialResponse `json:"sasl_initial_response,omitempty" yaml:"sasl_initial_response,omitempty"` - SASLResponse pgproto3.SASLResponse `json:"sasl_response,omitempty" yaml:"sasl_response,omitempty"` - PasswordMessage pgproto3.PasswordMessage `json:"password_message,omitempty" yaml:"password_message,omitempty"` - MsgType byte `json:"msg_type,omitempty" yaml:"msg_type,omitempty"` - PartialMsg bool `json:"partial_msg,omitempty" yaml:"partial_msg,omitempty"` - AuthType int32 `json:"auth_type" yaml:"auth_type"` - BodyLen int `json:"body_len,omitempty" yaml:"body_len,omitempty"` - // AuthMechanism string `json:"auth_mechanism,omitempty" yaml:"auth_mechanism,omitempty"` -} - -type Frontend struct { - PacketTypes []string `json:"header,omitempty" yaml:"header,omitempty,flow"` - Identfier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` - Length uint32 `json:"length,omitempty" yaml:"length,omitempty"` - Payload string `json:"payload,omitempty" yaml:"payload,omitempty"` - AuthenticationOk pgproto3.AuthenticationOk `json:"authentication_ok,omitempty" yaml:"authentication_ok,omitempty"` - AuthenticationCleartextPassword pgproto3.AuthenticationCleartextPassword `json:"authentication_cleartext_password,omitempty" yaml:"authentication_cleartext_password,omitempty"` - AuthenticationMD5Password pgproto3.AuthenticationMD5Password `json:"authentication_md5_password,omitempty" yaml:"authentication_md5_password,omitempty"` - AuthenticationGSS pgproto3.AuthenticationGSS `json:"authentication_gss,omitempty" yaml:"authentication_gss,omitempty"` - AuthenticationGSSContinue pgproto3.AuthenticationGSSContinue `json:"authentication_gss_continue,omitempty" yaml:"authentication_gss_continue,omitempty"` - AuthenticationSASL pgproto3.AuthenticationSASL `json:"authentication_sasl,omitempty" yaml:"authentication_sasl,omitempty"` - AuthenticationSASLContinue pgproto3.AuthenticationSASLContinue `json:"authentication_sasl_continue,omitempty" yaml:"authentication_sasl_continue,omitempty,flow"` - AuthenticationSASLFinal pgproto3.AuthenticationSASLFinal `json:"authentication_sasl_final,omitempty" yaml:"authentication_sasl_final,omitempty,flow"` - BackendKeyData pgproto3.BackendKeyData `json:"backend_key_data,omitempty" yaml:"backend_key_data,omitempty"` - BindComplete pgproto3.BindComplete `yaml:"-"` - BindCompletes []pgproto3.BindComplete `json:"bind_complete,omitempty" yaml:"bind_complete,omitempty"` - CloseComplete pgproto3.CloseComplete `json:"close_complete,omitempty" yaml:"close_complete,omitempty"` - CommandComplete pgproto3.CommandComplete `yaml:"-"` - CommandCompletes []pgproto3.CommandComplete `json:"command_complete,omitempty" yaml:"command_complete,omitempty"` - CopyBothResponse pgproto3.CopyBothResponse `json:"copy_both_response,omitempty" yaml:"copy_both_response,omitempty"` - CopyData pgproto3.CopyData `json:"copy_data,omitempty" yaml:"copy_data,omitempty"` - CopyInResponse pgproto3.CopyInResponse `json:"copy_in_response,omitempty" yaml:"copy_in_response,omitempty"` - CopyOutResponse pgproto3.CopyOutResponse `json:"copy_out_response,omitempty" yaml:"copy_out_response,omitempty"` - CopyDone pgproto3.CopyDone `json:"copy_done,omitempty" yaml:"copy_done,omitempty"` - DataRow pgproto3.DataRow `yaml:"-"` - DataRows []pgproto3.DataRow `json:"data_row,omitempty" yaml:"data_row,omitempty,flow"` - EmptyQueryResponse pgproto3.EmptyQueryResponse `json:"empty_query_response,omitempty" yaml:"empty_query_response,omitempty"` - ErrorResponse pgproto3.ErrorResponse `json:"error_response,omitempty" yaml:"error_response,omitempty"` - FunctionCallResponse pgproto3.FunctionCallResponse `json:"function_call_response,omitempty" yaml:"function_call_response,omitempty"` - NoData pgproto3.NoData `json:"no_data,omitempty" yaml:"no_data,omitempty"` - NoticeResponse pgproto3.NoticeResponse `json:"notice_response,omitempty" yaml:"notice_response,omitempty"` - NotificationResponse pgproto3.NotificationResponse `json:"notification_response,omitempty" yaml:"notification_response,omitempty"` - ParameterDescription pgproto3.ParameterDescription `json:"parameter_description,omitempty" yaml:"parameter_description,omitempty"` - ParameterStatus pgproto3.ParameterStatus `yaml:"-"` - ParameterStatusCombined []pgproto3.ParameterStatus `json:"parameter_status,omitempty" yaml:"parameter_status,omitempty"` - ParseComplete pgproto3.ParseComplete `yaml:"-"` - ParseCompletes []pgproto3.ParseComplete `json:"parse_complete,omitempty" yaml:"parse_complete,omitempty"` - ReadyForQuery pgproto3.ReadyForQuery `json:"ready_for_query,omitempty" yaml:"ready_for_query,omitempty"` - RowDescription pgproto3.RowDescription `json:"row_description,omitempty" yaml:"row_description,omitempty,flow"` - PortalSuspended pgproto3.PortalSuspended `json:"portal_suspended,omitempty" yaml:"portal_suspended,omitempty"` - MsgType byte `json:"msg_type,omitempty" yaml:"msg_type,omitempty"` - AuthType int32 `json:"auth_type" yaml:"auth_type"` - // AuthMechanism string `json:"auth_mechanism,omitempty" yaml:"auth_mechanism,omitempty"` - BodyLen int `json:"body_len,omitempty" yaml:"body_len,omitempty"` -} - -type StartupPacket struct { - Length uint32 - ProtocolVersion uint32 -} - -type RegularPacket struct { - Identifier byte - Length uint32 - Payload []byte -} diff --git a/keploy/pkg/models/redis.go b/keploy/pkg/models/redis.go deleted file mode 100644 index 98aa94e..0000000 --- a/keploy/pkg/models/redis.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -import ( - "time" -) - -type RedisSchema struct { - Metadata map[string]string `json:"metadata" yaml:"metadata"` - RedisRequests []Payload `json:"RequestBin,omitempty"` - RedisResponses []Payload `json:"ResponseBin,omitempty"` - ReqTimestampMock time.Time `json:"reqTimestampMock,omitempty"` - ResTimestampMock time.Time `json:"resTimestampMock,omitempty"` -} diff --git a/keploy/pkg/models/tele.go b/keploy/pkg/models/tele.go deleted file mode 100755 index b30f250..0000000 --- a/keploy/pkg/models/tele.go +++ /dev/null @@ -1,14 +0,0 @@ -package models - -import "sync" - -type TeleEvent struct { - InstallationID string `json:"installationId"` - EventType string `json:"eventType"` - Meta *sync.Map `json:"meta"` - CreatedAt int64 `json:"createdAt"` - TeleCheck bool `json:"tele_check"` - OS string `json:"os"` - KeployVersion string `json:"keploy_version"` - Arch string `json:"arch"` -} diff --git a/keploy/pkg/models/testcase.go b/keploy/pkg/models/testcase.go deleted file mode 100755 index 94778af..0000000 --- a/keploy/pkg/models/testcase.go +++ /dev/null @@ -1,73 +0,0 @@ -package models - -type BodyType string -type Version string - -const V1Beta1 = Version("api.keploy.io/v1beta1") - -// BodyType constants for HTTP and gRPC -const ( - JSON BodyType = "JSON" - XML BodyType = "XML" - HTML BodyType = "HTML" - CSV BodyType = "CSV" - Plain BodyType = "PLAIN" - Utf8 BodyType = "utf-8" - Binary BodyType = "BINARY" - GrpcCompression BodyType = "GRPC_COMPRESSION" - GrpcLength BodyType = "GRPC_LENGTH" - GrpcData BodyType = "GRPC_DATA" - UnknownType BodyType = "UNKNOWN" -) - -var ( - currentVersion = V1Beta1 -) - -func SetVersion(V1 string) { - currentVersion = Version(V1) -} - -func GetVersion() (V1 Version) { - return currentVersion -} - -type TestCase struct { - Version Version `json:"version" bson:"version"` - Kind Kind `json:"kind" bson:"kind"` - Name string `json:"name" bson:"name"` - Description string `json:"description" bson:"description"` - Created int64 `json:"created" bson:"created"` - Updated int64 `json:"updated" bson:"updated"` - Captured int64 `json:"captured" bson:"captured"` - HTTPReq HTTPReq `json:"http_req" bson:"http_req"` - HTTPResp HTTPResp `json:"http_resp" bson:"http_resp"` - AllKeys map[string][]string `json:"all_keys" bson:"all_keys"` - GrpcResp GrpcResp `json:"grpcResp" bson:"grpcResp"` - GrpcReq GrpcReq `json:"grpcReq" bson:"grpcReq"` - Anchors map[string][]string `json:"anchors" bson:"anchors"` - Noise map[string][]string `json:"noise" bson:"noise"` - Mocks []*Mock `json:"mocks" bson:"mocks"` - Type string `json:"type" bson:"type"` - Curl string `json:"curl" bson:"curl"` - IsLast bool `json:"is_last" bson:"is_last"` - Assertions map[AssertionType]interface{} `json:"assertion" bson:"assertion"` -} - -func (tc *TestCase) GetKind() string { - return string(tc.Kind) -} - -type NoiseParams struct { - TestCaseID string `json:"testCaseID"` - EditedBy string `json:"editedBy"` - Assertion map[string][]string `json:"assertion"` - Ops string `json:"ops"` - AfterNoise map[string][]string `json:"afterNoise"` -} - -// enum for ops -const ( - OpsAdd = "ADD" - OpsRemove = "REMOVE" -) diff --git a/keploy/pkg/models/testcompare.go b/keploy/pkg/models/testcompare.go deleted file mode 100644 index 3c8ec0b..0000000 --- a/keploy/pkg/models/testcompare.go +++ /dev/null @@ -1,42 +0,0 @@ -package models - -type AbsResult struct { - Kind StringResult `json:"kind" bson:"kind" yaml:"kind"` - Name StringResult `json:"name" bson:"name" yaml:"name"` - Req ReqCompare `json:"req" bson:"req" yaml:"req"` - Resp RespCompare `json:"resp" bson:"resp" yaml:"resp"` - // CurlResult StringResult `json:"curl_result" bson:"curl_result" yaml:"curl_result"` -} - -type ReqCompare struct { - MethodResult StringResult `json:"method_result" bson:"method_result" yaml:"method_result"` - URLResult StringResult `json:"url_result" bson:"url_result" yaml:"url_result"` - URLParamsResult []URLParamsResult `json:"url_params_result" bson:"url_params_result" yaml:"url_params_result"` - ProtoMajor IntResult `json:"proto_major" bson:"proto_major" yaml:"proto_major"` - ProtoMinor IntResult `json:"proto_minor" bson:"proto_minor" yaml:"proto_minor"` - HeaderResult []HeaderResult `json:"headers_result" bson:"headers_result" yaml:"headers_result"` - BodyResult BodyResult `json:"body_result" bson:"body_result" yaml:"body_result"` -} - -type RespCompare struct { - StatusCode IntResult `json:"status_code" bson:"status_code" yaml:"status_code"` - HeadersResult []HeaderResult `json:"headers_result" bson:"headers_result" yaml:"headers_result"` - BodyResult BodyResult `json:"body_result" bson:"body_result" yaml:"body_result"` -} - -type StringResult struct { - Normal bool `json:"normal" bson:"normal" yaml:"normal"` - Expected string `json:"expected" bson:"expected" yaml:"expected"` - Actual string `json:"actual" bson:"actual" yaml:"actual"` -} - -type URLParamsResult struct { - Normal bool `json:"normal" bson:"normal" yaml:"normal"` - Expected Params `json:"expected" bson:"expected" yaml:"expected"` - Actual Params `json:"actual" bson:"actual" yaml:"actual"` -} - -type Params struct { - Key string `json:"key" bson:"key" yaml:"key"` - Value string `json:"value" bson:"value" yaml:"value"` -} diff --git a/keploy/pkg/models/testrun.go b/keploy/pkg/models/testrun.go deleted file mode 100755 index 06241f7..0000000 --- a/keploy/pkg/models/testrun.go +++ /dev/null @@ -1,155 +0,0 @@ -package models - -import ( - "errors" -) - -type TestReport struct { - Version Version `json:"version" yaml:"version"` - Name string `json:"name" yaml:"name"` - Status string `json:"status" yaml:"status"` - Success int `json:"success" yaml:"success"` - Failure int `json:"failure" yaml:"failure"` - Ignored int `json:"ignored" yaml:"ignored"` - Total int `json:"total" yaml:"total"` - Tests []TestResult `json:"tests" yaml:"tests,omitempty"` - TestSet string `json:"testSet" yaml:"test_set"` - CreatedAt int64 `json:"created_at" yaml:"created_at"` -} - -type TestCoverage struct { - FileCov map[string]string `json:"fileCoverage" yaml:"file_coverage"` - TotalCov string `json:"totalCoverage" yaml:"total_coverage"` - Loc Loc `json:"loc" yaml:"loc"` -} - -type Loc struct { - Total int `json:"total" yaml:"total"` - Covered int `json:"covered" yaml:"covered"` -} - -func (tr *TestReport) GetKind() string { - return "TestReport" -} - -type TestResult struct { - Kind Kind `json:"kind" yaml:"kind"` - Name string `json:"name" yaml:"name"` - Status TestStatus `json:"status" yaml:"status"` - Started int64 `json:"started" yaml:"started"` - Completed int64 `json:"completed" yaml:"completed"` - TestCasePath string `json:"testCasePath" yaml:"test_case_path"` - MockPath string `json:"mockPath" yaml:"mock_path"` - TestCaseID string `json:"testCaseID" yaml:"test_case_id"` - Req HTTPReq `json:"req" yaml:"req,omitempty"` - Res HTTPResp `json:"resp" yaml:"resp,omitempty"` - GrpcReq GrpcReq `json:"grpcReq,omitempty" yaml:"grpcReq,omitempty"` - GrpcRes GrpcResp `json:"grpcRes,omitempty" yaml:"grpcRes,omitempty"` - Noise Noise `json:"noise" yaml:"noise,omitempty"` - Result Result `json:"result" yaml:"result"` -} - -func (tr *TestResult) GetKind() string { - return string(tr.Kind) -} - -type TestSetStatus string - -// constants for testSet status -const ( - TestSetStatusRunning TestSetStatus = "RUNNING" - TestSetStatusFailed TestSetStatus = "FAILED" - TestSetStatusPassed TestSetStatus = "PASSED" - TestSetStatusAppHalted TestSetStatus = "APP_HALTED" - TestSetStatusUserAbort TestSetStatus = "USER_ABORT" - TestSetStatusFaultUserApp TestSetStatus = "APP_FAULT" - TestSetStatusInternalErr TestSetStatus = "INTERNAL_ERR" - TestSetStatusFaultScript TestSetStatus = "SCRIPT_FAULT" - TestSetStatusIgnored TestSetStatus = "IGNORED" - TestSetStatusNoTestsToRun TestSetStatus = "NO_TESTS_TO_RUN" -) - -func StringToTestSetStatus(s string) (TestSetStatus, error) { - switch s { - case "RUNNING": - return TestSetStatusRunning, nil - case "FAILED": - return TestSetStatusFailed, nil - case "PASSED": - return TestSetStatusPassed, nil - case "APP_HALTED": - return TestSetStatusAppHalted, nil - case "USER_ABORT": - return TestSetStatusUserAbort, nil - case "APP_FAULT": - return TestSetStatusFaultUserApp, nil - case "INTERNAL_ERR": - return TestSetStatusInternalErr, nil - case "NO_TESTS_TO_RUN": - return TestSetStatusNoTestsToRun, nil - default: - return "", errors.New("invalid TestSetStatus value") - } -} - -type Result struct { - StatusCode IntResult `json:"status_code" bson:"status_code" yaml:"status_code"` - HeadersResult []HeaderResult `json:"headers_result" bson:"headers_result" yaml:"headers_result"` - BodyResult []BodyResult `json:"body_result" bson:"body_result" yaml:"body_result"` - DepResult []DepResult `json:"dep_result" bson:"dep_result" yaml:"dep_result"` - TrailerResult []HeaderResult `json:"trailer_result,omitempty" bson:"trailer_result,omitempty" yaml:"trailer_result,omitempty"` -} - -type DepResult struct { - Name string `json:"name" bson:"name" yaml:"name"` - Type string `json:"type" bson:"type" yaml:"type"` - Meta []DepMetaResult `json:"meta" bson:"meta" yaml:"meta"` -} - -type DepMetaResult struct { - Normal bool `json:"normal" bson:"normal" yaml:"normal"` - Key string `json:"key" bson:"key" yaml:"key"` - Expected string `json:"expected" bson:"expected" yaml:"expected"` - Actual string `json:"actual" bson:"actual" yaml:"actual"` -} - -type IntResult struct { - Normal bool `json:"normal" bson:"normal" yaml:"normal"` - Expected int `json:"expected" bson:"expected" yaml:"expected"` - Actual int `json:"actual" bson:"actual" yaml:"actual"` -} - -type HeaderResult struct { - Normal bool `json:"normal" bson:"normal" yaml:"normal"` - Expected Header `json:"expected" bson:"expected" yaml:"expected"` - Actual Header `json:"actual" bson:"actual" yaml:"actual"` -} - -type Header struct { - Key string `json:"key" bson:"key" yaml:"key"` - Value []string `json:"value" bson:"value" yaml:"value"` -} - -type BodyResult struct { - Normal bool `json:"normal" bson:"normal" yaml:"normal"` - Type BodyType `json:"type" bson:"type" yaml:"type"` - Expected string `json:"expected" bson:"expected" yaml:"expected"` - Actual string `json:"actual" bson:"actual" yaml:"actual"` -} - -type TestStatus string - -// constants for test status -const ( - TestStatusPending TestStatus = "PENDING" - TestStatusRunning TestStatus = "RUNNING" - TestStatusFailed TestStatus = "FAILED" - TestStatusPassed TestStatus = "PASSED" - TestStatusIgnored TestStatus = "IGNORED" -) - -type ( - Noise map[string][]string - GlobalNoise map[string]map[string][]string - TestsetNoise map[string]map[string]map[string][]string -) diff --git a/keploy/pkg/models/ut.go b/keploy/pkg/models/ut.go deleted file mode 100644 index 4495830..0000000 --- a/keploy/pkg/models/ut.go +++ /dev/null @@ -1,133 +0,0 @@ -package models - -import "encoding/xml" - -type UTResult struct { - Status string `yaml:"status"` - Reason string `yaml:"reason"` - ExitCode int `yaml:"exit_code"` - Stderr string `yaml:"stderr"` - Stdout string `yaml:"stdout"` - Test string `yaml:"test"` -} - -type UTDetails struct { - Language string `yaml:"language"` - TestSignature string `yaml:"existing_test_function_signature"` - NewTests []UT `yaml:"new_tests"` - RefactoredSourceCode string `yaml:"refactored_source_code,omitempty"` -} - -type UT struct { - TestBehavior string `yaml:"test_behavior"` - TestName string `yaml:"test_name"` - TestCode string `yaml:"test_code"` - NewImportsCode string `yaml:"new_imports_code"` - LibraryInstallationCode string `yaml:"library_installation_code"` - TestsTags string `yaml:"tests_tags"` -} - -type FailedUT struct { - TestCode string `yaml:"test_code"` - ErrorMsg string `yaml:"error_msg"` - NewImportsCode string `yaml:"imports_code"` - LibraryInstallationCode string `yaml:"library_installation_code"` -} - -type UTIndentationInfo struct { - Language string `yaml:"language"` - TestingFramework string `yaml:"testing_framework"` - NumberOfTests int `yaml:"number_of_tests"` - Indentation int `yaml:"test_headers_indentation"` -} - -type UTInsertionInfo struct { - Language string `yaml:"language"` - TestingFramework string `yaml:"testing_framework"` - NumberOfTests int `yaml:"number_of_tests"` - Line int `yaml:"relevant_line_number_to_insert_after"` -} - -type CoverageResult struct { - LinesCovered []int - LinesMissed []int - Coverage float64 - Files []string - ReportContent string -} - -type Cobertura struct { - XMLName xml.Name `xml:"coverage"` - Sources []string `xml:"sources>source"` - Packages []Package `xml:"packages>package"` -} - -type Package struct { - Name string `xml:"name,attr"` - Classes []Class `xml:"classes>class"` -} - -type Class struct { - Name string `xml:"name,attr"` - FileName string `xml:"filename,attr"` - Lines []Line `xml:"lines>line"` -} - -type Line struct { - Number int `xml:"number,attr"` - Hits int `xml:"hits,attr"` -} - -type Jacoco struct { - Name string `xml:"name,attr"` - XMLName xml.Name `xml:"report"` - Packages []JacocoPackage `xml:"package"` - SessionInfo []SessionInfo `xml:"sessioninfo"` -} - -type SessionInfo struct { - ID string `xml:"id,attr"` - Start string `xml:"start,attr"` - Dump string `xml:"dump,attr"` -} - -type JacocoPackage struct { - Name string `xml:"name,attr"` - Classes []JacocoClass `xml:"class"` - Counters []Counter `xml:"counter"` - SourceFiles []JacocoSourceFile `xml:"sourcefile"` // Adding this field to capture source files -} - -type JacocoSourceFile struct { - Name string `xml:"name,attr"` - Lines []JacocoLine `xml:"line"` - Counters []Counter `xml:"counter"` -} - -type JacocoClass struct { - Name string `xml:"name,attr"` - SourceFile string `xml:"sourcefilename,attr"` - Methods []JacocoMethod `xml:"method"` - Lines []JacocoLine `xml:"line"` // This is where JacocoLine is used -} - -type JacocoMethod struct { - Name string `xml:"name,attr"` - Descriptor string `xml:"desc,attr"` - Line string `xml:"line,attr"` - Counters []Counter `xml:"counter"` -} - -type JacocoLine struct { - Number string `xml:"nr,attr"` - MissedInstructions string `xml:"mi,attr"` - CoveredInstructions string `xml:"ci,attr"` - MissedBranches string `xml:"mb,attr"` - CoveredBranches string `xml:"cb,attr"` -} - -type Counter struct { - Type string `xml:"type,attr"` - Missed string `xml:"missed,attr"` - Covered string `xml:"covered,attr"` -} diff --git a/keploy/pkg/platform/README.md b/keploy/pkg/platform/README.md deleted file mode 100755 index ab693cc..0000000 --- a/keploy/pkg/platform/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Platform Package Documentation - -This package exposes a set of interfaces and methods for common data -store operations such as creating, reading, updating, and deleting -(CRUD) records. - -Implementations for different types of data stores can be added -here, all in one place. These implementations are abstracted from -other logic using interface methods. - -This package depends on the `models` package, using its structs to -perform the CRUD operations. \ No newline at end of file diff --git a/keploy/pkg/platform/auth/auth.go b/keploy/pkg/platform/auth/auth.go deleted file mode 100644 index dc98202..0000000 --- a/keploy/pkg/platform/auth/auth.go +++ /dev/null @@ -1,237 +0,0 @@ -// Package auth defines methods for authenticating with GitHub. -package auth - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "time" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/service" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type Auth struct { - serverURL string - jwtToken string - installationID string - logger *zap.Logger - GitHubClientID string -} - -func New(serverURL string, installationID string, logger *zap.Logger, gitHubClientID string) service.Auth { - return &Auth{ - serverURL: serverURL, - installationID: installationID, - logger: logger, - GitHubClientID: gitHubClientID, - } -} - -func (a *Auth) Login(ctx context.Context) bool { - - deviceCodeResp, err := requestDeviceCode(a.logger, a.GitHubClientID) - if err != nil { - a.logger.Error("Error requesting device code", zap.Error(err)) - return false - } - - promptUser(deviceCodeResp) - - tokenResp, err := pollForAccessToken(ctx, a.logger, deviceCodeResp.DeviceCode, a.GitHubClientID, deviceCodeResp.Interval) - if err != nil { - if ctx.Err() == context.Canceled { - return false - } - utils.LogError(a.logger, err, "failed to poll for access token") - return false - } - - userEmail, isValid, authErr, err := a.Validate(ctx, tokenResp.AccessToken) - if err != nil { - if ctx.Err() == context.Canceled { - return false - } - a.logger.Error("Error checking auth", zap.Error(err)) - return false - } - - if !isValid { - a.logger.Error("Invalid token", zap.Any("error", authErr)) - return false - } - a.logger.Info("Successfully logged in to Keploy using GitHub as " + userEmail) - return true -} - -func (a *Auth) Validate(ctx context.Context, token string) (string, bool, string, error) { - url := fmt.Sprintf("%s/auth/github", a.serverURL) - // this i can directly call the vs code extension - requestBody := &models.AuthReq{ - GitHubToken: token, - InstallationID: a.installationID, - } - requestJSON, err := json.Marshal(requestBody) - if err != nil { - utils.LogError(a.logger, err, "failed to marshal request body for authentication") - return "", false, "", fmt.Errorf("error marshaling request body for authentication: %s", err.Error()) - } - - req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(requestJSON)) - if err != nil { - utils.LogError(a.logger, err, "failed to create request for authentication") - return "", false, "", fmt.Errorf("error creating request for authentication: %s", err.Error()) - } - req.Header.Set("Content-Type", "application/json") - client := &http.Client{} - res, err := client.Do(req) - if err != nil { - return "", false, "", fmt.Errorf("failed to authenticate: %s", err.Error()) - } - - defer func() { - err := res.Body.Close() - if err != nil { - utils.LogError(a.logger, err, "failed to close response body for authentication") - } - }() - - var respBody models.AuthResp - err = json.NewDecoder(res.Body).Decode(&respBody) - if err != nil { - return "", false, "", fmt.Errorf("error unmarshalling the authentication response: %s", err.Error()) - } - - if res.StatusCode != 200 || res.StatusCode >= 300 { - return "", false, "", fmt.Errorf("failed to authenticate: %s", respBody.Error) - } - - a.jwtToken = respBody.JwtToken - return respBody.EmailID, respBody.IsValid, respBody.Error, nil -} - -func (a *Auth) GetToken(ctx context.Context) (string, error) { - if a.jwtToken == "" { - _, _, _, err := a.Validate(ctx, "") - if err != nil { - return "", err - } - } - - if a.jwtToken == "" { - a.logger.Warn("Looks like you are not logged in.") - a.logger.Warn("Please follow the instructions to login.") - isSuccessful := a.Login(ctx) - if !isSuccessful { - return "", fmt.Errorf("failed to login") - } - } - - return a.jwtToken, nil -} - -// requestDeviceCode sends a request to GitHub to get a device code for the users machine to authenticate -func requestDeviceCode(logger *zap.Logger, gitHubClientID string) (*models.DeviceCodeResponse, error) { - data := url.Values{} - data.Set("client_id", gitHubClientID) - data.Set("scope", "read:user") - - resp, err := http.PostForm(models.DeviceCodeURL, data) - if err != nil { - return nil, err - } - defer func() { - err := resp.Body.Close() - if err != nil { - utils.LogError(logger, err, "failed to close response body") - } - }() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - // Parse the URL-encoded response - parsed, err := url.ParseQuery(string(body)) - if err != nil { - return nil, err - } - - // Populate the DeviceCodeResponse struct - deviceCodeResp := &models.DeviceCodeResponse{ - DeviceCode: parsed.Get("device_code"), - UserCode: parsed.Get("user_code"), - VerificationURI: parsed.Get("verification_uri"), - Interval: 5, // Default value; you can parse this from the response if needed - } - - return deviceCodeResp, nil -} - -func promptUser(deviceCodeResp *models.DeviceCodeResponse) { - fmt.Printf("Please go to %s and enter the code: %s\n", deviceCodeResp.VerificationURI, deviceCodeResp.UserCode) -} - -// pollForAccessToken polls GitHub for an access token using the device code provided by requestDeviceCode -// It will poll every interval seconds until the user has approved the device code -func pollForAccessToken(ctx context.Context, logger *zap.Logger, deviceCode, gitHubClientID string, interval int) (*models.AccessTokenResponse, error) { - data := url.Values{} - data.Set("client_id", gitHubClientID) - data.Set("device_code", deviceCode) - data.Set("grant_type", "urn:ietf:params:oauth:grant-type:device_code") - - fmt.Println("waiting for approval (this might take 4-5 sec for approval)...") - - for { - resp, err := http.PostForm(models.TokenURL, data) - if err != nil { - return nil, err - } - defer func() { - err := resp.Body.Close() - if err != nil { - utils.LogError(logger, err, "failed to close response body") - } - }() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) - } - - var tokenResp models.AccessTokenResponse - parsed, err := url.ParseQuery(string(body)) - if err != nil { - return nil, err - } - if parsed.Get("error") == "authorization_pending" { - select { - case <-time.After(time.Duration(interval) * time.Second): - case <-ctx.Done(): - return nil, ctx.Err() - } - continue - } else if parsed.Get("error") != "" { - return nil, fmt.Errorf("error: %s", parsed.Get("error_description")) - } - if accessToken := parsed.Get("access_token"); accessToken != "" { - return &models.AccessTokenResponse{ - AccessToken: accessToken, - TokenType: parsed.Get("token_type"), - Scope: parsed.Get("scope"), - }, nil - } - return &tokenResp, nil - } -} diff --git a/keploy/pkg/platform/coverage/csharp/csharp.go b/keploy/pkg/platform/coverage/csharp/csharp.go deleted file mode 100644 index 5de1cd9..0000000 --- a/keploy/pkg/platform/coverage/csharp/csharp.go +++ /dev/null @@ -1,130 +0,0 @@ -// Package csharp impliments methods for Csharp coverage services. -package csharp - -import ( - "context" - "encoding/xml" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/coverage" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type Csharp struct { - ctx context.Context - logger *zap.Logger - reportdb coverage.ReportDB - cmd string - executable string - dotnetCoveragePath string -} - -type CoverageStructure struct { - LineRate float64 `xml:"line-rate,attr"` - - Packages []struct { - Classes []struct { - FileName string `xml:"filename,attr"` - ClassLineRate float64 `xml:"line-rate,attr"` - ClassBranchRate float64 `xml:"branch-rate,attr"` - } `xml:"classes>class"` - } `xml:"packages>package"` -} - -func New(ctx context.Context, logger *zap.Logger, reportDB coverage.ReportDB, cmd, dotnetCoveragePath, executable string) *Csharp { - return &Csharp{ - ctx: ctx, - logger: logger, - reportdb: reportDB, - cmd: cmd, - dotnetCoveragePath: dotnetCoveragePath, - executable: executable, - } -} - -func (cs *Csharp) PreProcess(_ bool) (string, error) { - // default location for dotnet-coverage - dotnetCoveragePath := "~/.dotnet/tools/dotnet-coverage" - if cs.dotnetCoveragePath != "" { - dotnetCoveragePath = cs.dotnetCoveragePath - } - - isFileExists, err := utils.FileExists(dotnetCoveragePath) - if err != nil { - cs.logger.Warn("error checking dotnet-coverage tool existance: %s", zap.Error(err)) - return cs.cmd, err - } - - if !isFileExists { - return cs.cmd, fmt.Errorf("dotnet coverage tool not found at: %s", dotnetCoveragePath) - } - - cs.cmd = strings.Replace(cs.cmd, cs.executable, fmt.Sprintf("%s collect --output target/${TESTSETID}.cobertura --output-format cobertura", cs.executable), 1) - - // download dotnet coverage - dotnetPath := filepath.Join(os.TempDir(), "dotnet") - err = os.MkdirAll(dotnetPath, 0777) - - if err != nil { - cs.logger.Debug("failed to create dotnet directory: %s", zap.Error(err)) - return cs.cmd, err - } - - err = downloadDotnetCoverage(cs.ctx) - if err != nil { - cs.logger.Debug("failed to download and extract dotnet binaries: %s", zap.Error(err)) - return cs.cmd, err - } - - return cs.cmd, nil -} - -func (cs *Csharp) GetCoverage() (models.TestCoverage, error) { - testCov := models.TestCoverage{ - FileCov: make(map[string]string), - TotalCov: "", - } - - // Define the path to cobertura file - coberturaPath := filepath.Join("target", "site", "KeployE2E", "e2e.cobertura") - - file, err := os.Open(coberturaPath) - if err != nil { - return testCov, fmt.Errorf("failed to open cobertura file: %w", err) - } - defer func() { - if err := file.Close(); err != nil { - utils.LogError(cs.logger, err, "Error closing coverage cobertura file") - } - }() - - // complete line-by-line coverage - coverageStruct := CoverageStructure{} - if err := xml.Unmarshal([]byte(coberturaPath), &coverageStruct); err != nil { - return testCov, fmt.Errorf("failed to unmarshal cobertura file: %w", err) - } - testCov.TotalCov = strconv.FormatFloat(coverageStruct.LineRate*100, 'E', -1, 64) - - // class coverage - totalClasses, classCovered := 0.0, 0.0 - - for _, pkg := range coverageStruct.Packages { - for _, class := range pkg.Classes { - totalClasses++ - if class.ClassLineRate > 0 || class.ClassBranchRate > 0 { - classCovered++ - } - - classCoverage := (classCovered / totalClasses) * 100 - testCov.FileCov[class.FileName] = strconv.FormatFloat(classCoverage, 'E', -1, 64) + "%" - } - } - - return testCov, nil -} diff --git a/keploy/pkg/platform/coverage/csharp/utils.go b/keploy/pkg/platform/coverage/csharp/utils.go deleted file mode 100644 index 38739a1..0000000 --- a/keploy/pkg/platform/coverage/csharp/utils.go +++ /dev/null @@ -1,140 +0,0 @@ -package csharp - -import ( - "context" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - - "go.uber.org/zap" -) - -func downloadDotnetCoverage(ctx context.Context) error { - // Consruct arguments for command to check if dotnet-coverage is already installed or not - checkArgs := []string{ - "dotnet", - "tool", - "list", - "-g", - } - - checkCmd := exec.CommandContext(ctx, checkArgs[0], checkArgs[1:]...) - checkCmd.Stdout = os.Stdout - checkCmd.Stderr = os.Stderr - - if err := checkCmd.Run(); err != nil { - return fmt.Errorf("failed to check for existing dotnet-coverage: %w", err) - } - if strings.Contains(checkCmd.String(), "dotnet-coverage") { - fmt.Println("dotnet-coverage is already installed") - return nil - } - - fmt.Println("dotnet-coverage not found. Installing...") - - // Construct the command arguments to install dotnet-coverage - installArgs := []string{ - "dotnet", - "tool", - "install", - "--global", - "dotnet-coverage", - } - - installCmd := exec.CommandContext(ctx, installArgs[0], installArgs[1:]...) - installCmd.Stdout = os.Stdout - installCmd.Stderr = os.Stderr - - if err := installCmd.Run(); err != nil { - return fmt.Errorf("failed to install dotnet-coverage. Ensure .NET SDK is installed and try again: %w", err) - } - - return nil -} - -func MergeAndGenerateDotnetReport(ctx context.Context, logger *zap.Logger) error { - err := MergeCoverageFiles(ctx) - if err == nil { - err = GenerateCoverageReport(ctx) - - if err != nil { - logger.Debug("failed to generate dotnet-coverage report: %w", zap.Error(err)) - } - } else { - logger.Debug("failed to merge dotnet-coverage data: %w", zap.Error(err)) - } - - if err != nil { - return err - } - - return nil -} - -func MergeCoverageFiles(ctx context.Context) error { - // Find all .cobertura files in the target directory - sourceFiles, err := filepath.Glob("target/*.cobertura") - if err != nil { - return fmt.Errorf("error finding coverage files: %w", err) - } - - if len(sourceFiles) == 0 { - return errors.New("no coverage files found") - } - - // Construct command arguments to merge coverage files - args := []string{ - "dotnet-coverage", - "merge", - "--output", - "target/*.cobertura", - "--output-format", - "cobertura", - } - - cmd := exec.CommandContext(ctx, args[0], args[1:]...) - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to merge coverage files: %w", err) - } - - return nil -} - -func GenerateCoverageReport(ctx context.Context) error { - reportDir := "target/site/keployE2E" - - // Ensure that the report dir exists - if err := os.MkdirAll(reportDir, 0777); err != nil { - return fmt.Errorf("failed to create report directory: %w", err) - } - - // Consturct command arguments to generate the coverage report - args := []string{ - "dotnet-coverage", - "collect", - "--output", - "target/keploy-e2e.cobertura", - "output.coverage", - "--output-format", - "cobertura", - "--", - "dotnet", - "test", - } - - cmd := exec.CommandContext(ctx, args[0], args[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to generate report: %w", err) - } - - return nil -} diff --git a/keploy/pkg/platform/coverage/golang/golang.go b/keploy/pkg/platform/coverage/golang/golang.go deleted file mode 100644 index 9de8042..0000000 --- a/keploy/pkg/platform/coverage/golang/golang.go +++ /dev/null @@ -1,151 +0,0 @@ -// Package golang implements the methods for golang coverage services. -package golang - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "os/exec" - "strconv" - "strings" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/coverage" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type Golang struct { - ctx context.Context - logger *zap.Logger - reportDB coverage.ReportDB - cmd string - coverageReportPath string - commandType string -} - -func New(ctx context.Context, logger *zap.Logger, reportDB coverage.ReportDB, cmd, coverageReportPath, commandType string) *Golang { - return &Golang{ - ctx: ctx, - logger: logger, - reportDB: reportDB, - cmd: cmd, - coverageReportPath: coverageReportPath, - commandType: commandType, - } -} - -func (g *Golang) PreProcess(_ bool) (string, error) { - if !checkForCoverFlag(g.logger, g.cmd) { - return g.cmd, errors.New("binary not coverable") - } - if utils.CmdType(g.commandType) == utils.Native { - goCovPath, err := utils.SetCoveragePath(g.logger, g.coverageReportPath) - if err != nil { - g.logger.Warn("failed to set go coverage path", zap.Error(err)) - return g.cmd, err - } - err = os.Setenv("GOCOVERDIR", goCovPath) - if err != nil { - g.logger.Warn("failed to set GOCOVERDIR", zap.Error(err)) - return g.cmd, err - } - } - return g.cmd, nil -} - -func (g *Golang) GetCoverage() (models.TestCoverage, error) { - testCov := models.TestCoverage{ - FileCov: make(map[string]string), - TotalCov: "", - } - - coverageDir := os.Getenv("GOCOVERDIR") - - f, err := os.Open(coverageDir) - if err != nil { - utils.LogError(g.logger, err, "failed to open coverage directory, skipping coverage calculation") - return testCov, err - } - defer func() { - if err := f.Close(); err != nil { - utils.LogError(g.logger, err, "Error closing coverage directory, skipping coverage calculation") - } - }() - - _, err = f.Readdirnames(1) // Or f.Readdir(1) - if err == io.EOF { - utils.LogError(g.logger, err, fmt.Sprintf("no coverage files found in %s, skipping coverage calculation", coverageDir)) - return testCov, err - } - - generateCovTxtCmd := exec.CommandContext(g.ctx, "go", "tool", "covdata", "textfmt", "-i="+os.Getenv("GOCOVERDIR"), "-o="+os.Getenv("GOCOVERDIR")+"/total-coverage.txt") - _, err = generateCovTxtCmd.Output() - if err != nil { - return testCov, err - } - - coveragePerFileTmp := make(map[string][]int) // filename -> [noOfLines, coveredLines] - covdata, err := os.ReadFile(os.Getenv("GOCOVERDIR") + "/total-coverage.txt") - if err != nil { - return testCov, err - } - // a line is of the form: :.,. - for idx, line := range strings.Split(string(covdata), "\n") { - line = strings.TrimSpace(line) - if strings.Split(line, ":")[0] == "mode" || line == "" { - continue - } - lineFields := strings.Fields(line) - malformedErrMsg := "go coverage file is malformed" - if len(lineFields) == 3 { - noOfLines, err := strconv.Atoi(lineFields[1]) - if err != nil { - return testCov, err - } - coveredOrNot, err := strconv.Atoi(lineFields[2]) - if err != nil { - return testCov, err - } - i := strings.Index(line, ":") - var filename string - if i > 0 { - filename = line[:i] - } else { - return testCov, fmt.Errorf("%s at line %d", malformedErrMsg, idx) - } - - if _, ok := coveragePerFileTmp[filename]; !ok { - coveragePerFileTmp[filename] = make([]int, 2) - } - - coveragePerFileTmp[filename][0] += noOfLines - if coveredOrNot != 0 { - coveragePerFileTmp[filename][1] += noOfLines - } - } else { - return testCov, fmt.Errorf("%s at %d", malformedErrMsg, idx) - } - } - - totalLines := 0 - totalCoveredLines := 0 - for filename, lines := range coveragePerFileTmp { - totalLines += lines[0] - totalCoveredLines += lines[1] - covPercentage := float64(lines[1]*100) / float64(lines[0]) - testCov.FileCov[filename] = strconv.FormatFloat(float64(covPercentage), 'f', 2, 64) + "%" - } - testCov.TotalCov = strconv.FormatFloat(float64(totalCoveredLines*100)/float64(totalLines), 'f', 2, 64) + "%" - testCov.Loc = models.Loc{ - Total: totalLines, - Covered: totalCoveredLines, - } - return testCov, nil -} - -func (g *Golang) AppendCoverage(coverage *models.TestCoverage, testRunID string) error { - return g.reportDB.UpdateReport(g.ctx, testRunID, coverage) -} diff --git a/keploy/pkg/platform/coverage/golang/utils.go b/keploy/pkg/platform/coverage/golang/utils.go deleted file mode 100644 index a54fc6a..0000000 --- a/keploy/pkg/platform/coverage/golang/utils.go +++ /dev/null @@ -1,56 +0,0 @@ -package golang - -import ( - "debug/elf" - "slices" - "strings" - - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// checkForCoverFlag checks if the given Go binary has the coverage flag enabled if one argument -// else check if -cover flag is passed or not -// TODO: use native approach till https://github.com/golang/go/issues/67366 gets resolved -func checkForCoverFlag(logger *zap.Logger, cmd string) bool { - cmdFields := strings.Fields(cmd) - i := 0 - var part string - for i, part = range cmdFields { - if !strings.Contains(part, "=") { - break - } - } - if cmdFields[i] == "go" && len(cmdFields) > 1 { - if slices.Contains(cmdFields, "-cover") { - return true - } - logger.Warn("cover flag not found in command, skipping coverage calculation") - return false - } - file, err := elf.Open(cmdFields[i]) - if err != nil { - utils.LogError(logger, err, "failed to open file, skipping coverage calculation") - return false - } - defer func() { - if err := file.Close(); err != nil { - utils.LogError(logger, err, "failed to close binary file", zap.String("file", cmd)) - } - }() - - symbols, err := file.Symbols() - if err != nil { - utils.LogError(logger, err, "failed to read symbols, skipping coverage calculation") - return false - } - - for _, symbol := range symbols { - // Check for symbols that related to Go coverage instrumentation - if strings.Contains(symbol.Name, "internal/coverage") { - return true - } - } - logger.Warn("go binary was not build with -cover flag", zap.String("file", cmd)) - return false -} diff --git a/keploy/pkg/platform/coverage/java/java.go b/keploy/pkg/platform/coverage/java/java.go deleted file mode 100644 index 7fff377..0000000 --- a/keploy/pkg/platform/coverage/java/java.go +++ /dev/null @@ -1,145 +0,0 @@ -// Package java implements the methods for java coverage services. -package java - -import ( - "context" - "encoding/csv" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/coverage" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type Java struct { - ctx context.Context - logger *zap.Logger - reportDB coverage.ReportDB - cmd string - jacocoAgentPath string - executable string -} - -func New(ctx context.Context, logger *zap.Logger, reportDB coverage.ReportDB, cmd, jacocoAgentPath, executable string) *Java { - return &Java{ - ctx: ctx, - logger: logger, - reportDB: reportDB, - cmd: cmd, - jacocoAgentPath: jacocoAgentPath, - executable: executable, - } -} - -func (j *Java) PreProcess(_ bool) (string, error) { - // default location for jar of jacoco agent - jacocoAgentPath := "~/.m2/repository/org/jacoco/org.jacoco.agent/0.8.8/org.jacoco.agent-0.8.8-runtime.jar" - if j.jacocoAgentPath != "" { - jacocoAgentPath = j.jacocoAgentPath - } - var err error - jacocoAgentPath, err = utils.ExpandPath(jacocoAgentPath) - if err == nil { - isFileExist, err := utils.FileExists(jacocoAgentPath) - if err == nil && isFileExist { - j.cmd = strings.Replace( - j.cmd, - j.executable, - fmt.Sprintf("%s -javaagent:%s=destfile=target/${TESTSETID}.exec", j.executable, jacocoAgentPath), 1, - ) - } - } - if err != nil { - j.logger.Warn("failed to find jacoco agent. If jacoco agent is present in a different path, please set it using --jacocoAgentPath") - return j.cmd, err - } - // downlaod jacoco cli - jacocoPath := filepath.Join(os.TempDir(), "jacoco") - err = os.MkdirAll(jacocoPath, 0777) - if err != nil { - j.logger.Debug("failed to create jacoco directory", zap.Error(err)) - return j.cmd, err - } - err = downloadAndExtractJaCoCoCli(j.logger, "0.8.12", jacocoPath) - if err != nil { - j.logger.Debug("failed to download and extract jacoco binaries", zap.Error(err)) - return j.cmd, err - } - return j.cmd, nil -} - -func (j *Java) GetCoverage() (models.TestCoverage, error) { - testCov := models.TestCoverage{ - FileCov: make(map[string]string), - TotalCov: "", - } - - // Define the path to the CSV file - csvPath := filepath.Join("target", "site", "keployE2E", "e2e.csv") - - file, err := os.Open(csvPath) - if err != nil { - return testCov, fmt.Errorf("failed to open CSV file: %w", err) - } - defer func() { - if err := file.Close(); err != nil { - utils.LogError(j.logger, err, "Error closing coverage csv file") - } - }() - - reader := csv.NewReader(file) - records, err := reader.ReadAll() - if err != nil { - return testCov, fmt.Errorf("failed to read CSV file: %w", err) - } - - var totalInstructions, coveredInstructions int - - // Skip header row and process each record - for i, record := range records { - if i == 0 { - continue // Skip header - } - - // Parse instructions coverage data - instructionsMissed, err := strconv.Atoi(record[3]) - if err != nil { - return testCov, err - } - instructionsCovered, err := strconv.Atoi(record[4]) - if err != nil { - return testCov, err - } - - // Calculate total instructions and covered instructions - totalInstructions += instructionsMissed + instructionsCovered - coveredInstructions += instructionsCovered - - // Calculate coverage percentage for each class - if instructionsCovered > 0 { - coverage := float64(instructionsCovered) / float64(instructionsCovered+instructionsMissed) * 100 - classPath := strings.ReplaceAll(record[1], ".", string(os.PathSeparator)) // Replace dots with path separator - testCov.FileCov[filepath.Join(classPath, record[2])] = fmt.Sprintf("%.2f%%", coverage) // Use class path as key - } - } - if totalInstructions > 0 { - totalCoverage := float64(coveredInstructions) / float64(totalInstructions) * 100 - testCov.TotalCov = fmt.Sprintf("%.2f%%", totalCoverage) - } - - testCov.Loc = models.Loc{ - Total: totalInstructions, - Covered: coveredInstructions, - } - - return testCov, nil -} - -func (j *Java) AppendCoverage(coverage *models.TestCoverage, testRunID string) error { - return j.reportDB.UpdateReport(j.ctx, testRunID, coverage) -} diff --git a/keploy/pkg/platform/coverage/java/utils.go b/keploy/pkg/platform/coverage/java/utils.go deleted file mode 100644 index 36ba64c..0000000 --- a/keploy/pkg/platform/coverage/java/utils.go +++ /dev/null @@ -1,171 +0,0 @@ -package java - -import ( - "archive/zip" - "bytes" - "context" - "errors" - "fmt" - "io" - "net/http" - "os" - "os/exec" - "path/filepath" - "strings" - - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func downloadAndExtractJaCoCoCli(logger *zap.Logger, version, dir string) error { - cliPath := filepath.Join(dir, "jacococli.jar") - - downloadURL := fmt.Sprintf("https://github.com/jacoco/jacoco/releases/download/v%s/jacoco-%s.zip", version, version) - - _, err := os.Stat(cliPath) - if err == nil { - return nil - } - - resp, err := http.Get(downloadURL) - if err != nil { - return err - } - defer func() { - if err := resp.Body.Close(); err != nil { - utils.LogError(logger, err, "failed to close response body") - } - }() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return err - } - - zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body))) - if err != nil { - return err - } - - for _, file := range zipReader.File { - if strings.HasSuffix(file.Name, "jacococli.jar") { - cliFile, err := file.Open() - if err != nil { - return err - } - defer func() { - if err := cliFile.Close(); err != nil { - utils.LogError(logger, err, "failed to close jacoco cli jar file") - } - }() - - outFile, err := os.Create(cliPath) - if err != nil { - return err - } - defer func() { - if err := outFile.Close(); err != nil { - utils.LogError(logger, err, "failed to close the output file for jacoco cli jar") - } - }() - - _, err = io.Copy(outFile, cliFile) - if err != nil { - return err - } - } - } - - cliStat, err := os.Stat(cliPath) - - if os.IsNotExist(err) || cliStat != nil { - return fmt.Errorf("failed to find JaCoCo binaries in the distribution") - } - - return nil -} - -func MergeAndGenerateJacocoReport(ctx context.Context, logger *zap.Logger) error { - jacocoPath := filepath.Join(os.TempDir(), "jacoco") - jacocoCliPath := filepath.Join(jacocoPath, "jacococli.jar") - err := MergeJacocoCoverageFiles(ctx, jacocoCliPath) - if err == nil { - err = generateJacocoReport(ctx, jacocoCliPath) - if err != nil { - logger.Debug("failed to generate jacoco report", zap.Error(err)) - } - } else { - logger.Debug("failed to merge jacoco coverage data", zap.Error(err)) - } - if err != nil { - return err - } - return nil -} - -func MergeJacocoCoverageFiles(ctx context.Context, jacocoCliPath string) error { - // Find all .exec files in the target directory - sourceFiles, err := filepath.Glob("target/*.exec") - if err != nil { - return fmt.Errorf("error finding coverage files: %w", err) - } - if len(sourceFiles) == 0 { - return errors.New("no coverage files found") - } - - // Construct the command arguments - args := []string{ - "java", - "-jar", - jacocoCliPath, - "merge", - } - - args = append(args, sourceFiles...) - - // Specify the output file - args = append(args, "--destfile", "target/keploy-e2e.exec") - - cmd := exec.CommandContext(ctx, args[0], args[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to merge coverage files: %w", err) - } - - return nil -} - -func generateJacocoReport(ctx context.Context, jacocoCliPath string) error { - reportDir := "target/site/keployE2E" - - // Ensure the report directory exists - if err := os.MkdirAll(reportDir, 0777); err != nil { - return fmt.Errorf("failed to create report directory: %w", err) - } - - command := []string{ - "java", - "-jar", - jacocoCliPath, - "report", - "target/keploy-e2e.exec", - "--classfiles", - "target/classes", - "--csv", - reportDir + "/e2e.csv", - "--html", - reportDir, - } - - cmd := exec.CommandContext(ctx, command[0], command[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to generate report: %w", err) - } - - return nil -} diff --git a/keploy/pkg/platform/coverage/javascript/javascript.go b/keploy/pkg/platform/coverage/javascript/javascript.go deleted file mode 100644 index eb5676e..0000000 --- a/keploy/pkg/platform/coverage/javascript/javascript.go +++ /dev/null @@ -1,174 +0,0 @@ -// Package javascript implements the methods for javascript coverage services. -package javascript - -import ( - "context" - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "strconv" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/coverage" - "go.uber.org/zap" -) - -type Javascript struct { - ctx context.Context - logger *zap.Logger - reportDB coverage.ReportDB - cmd string -} - -func New(ctx context.Context, logger *zap.Logger, reportDB coverage.ReportDB, cmd string) *Javascript { - return &Javascript{ - ctx: ctx, - logger: logger, - reportDB: reportDB, - cmd: cmd, - } -} - -func (j *Javascript) PreProcess(disableLineCoverage bool) (string, error) { - cmd := exec.Command("nyc", "--version") - err := cmd.Run() - if err != nil { - j.logger.Warn("coverage tool not found, skipping coverage caluclation. please install coverage tool using 'npm install -g nyc'") - return j.cmd, err - } - nycCmd := "nyc --clean=$CLEAN " - if disableLineCoverage { - nycCmd += "--reporter=none " - } - return nycCmd + j.cmd, nil -} - -type StartTy struct { - Line int `json:"line"` - Column int `json:"column"` -} - -type EndTy struct { - Line int `json:"line"` - Column int `json:"column"` -} - -type Loc struct { - StartTy `json:"start"` - EndTy `json:"end"` -} - -type Coverage map[string]struct { - Path string `json:"path"` - StatementMap map[string]struct { - StartTy `json:"start"` - EndTy `json:"end"` - } `json:"statementMap"` - FnMap map[string]struct { - Name string `json:"name"` - Decl struct { - StartTy `json:"start"` - EndTy `json:"end"` - } `json:"decl"` - Loc `json:"loc"` - Line int `json:"line"` - } `json:"fnMap"` - BranchMap map[string]struct { - Loc `json:"loc"` - Type string `json:"type"` - Locations []struct { - StartTy `json:"start"` - EndTy `json:"end"` - } `json:"locations"` - Line int `json:"line"` - } `json:"branchMap"` - S map[string]interface{} `json:"s"` - F map[string]interface{} `json:"f"` - B map[string]interface{} `json:"b"` - CoverageSchema string `json:"_coverageSchema"` - Hash string `json:"hash"` - ContentHash string `json:"contentHash"` -} - -func (j *Javascript) GetCoverage() (models.TestCoverage, error) { - testCov := models.TestCoverage{ - FileCov: make(map[string]string), - TotalCov: "", - } - - coverageFilePaths, err := getCoverageFilePathsJavascript(filepath.Join(".", ".nyc_output", "processinfo")) - if err != nil { - return testCov, err - } - if len(coverageFilePaths) == 0 { - return testCov, fmt.Errorf("no coverage files found") - } - - // coverage is calculated as: (no of statements covered / total no of statements) * 100 - // no of statements covered is the no of entries in S which has a value greater than 0 - // Total no of statements is len of S - - linesCoveredPerFile := make(map[string]map[string]bool) // filename -> line -> covered/not covered - - for _, coverageFilePath := range coverageFilePaths { - - coverageData, err := os.ReadFile(coverageFilePath) - if err != nil { - return testCov, err - } - var cov Coverage - err = json.Unmarshal(coverageData, &cov) - if err != nil { - return testCov, err - } - - for filename, file := range cov { - if _, ok := linesCoveredPerFile[filename]; !ok { - linesCoveredPerFile[filename] = make(map[string]bool) - } - for line, isStatementCovered := range file.S { - if _, ok := linesCoveredPerFile[filename][line]; !ok { - linesCoveredPerFile[filename][line] = false - } - - switch isExecSegmentCov := isStatementCovered.(type) { - case float64: - if (isExecSegmentCov) > 0 { - linesCoveredPerFile[filename][line] = true - } - default: - linesCoveredPerFile[filename][line] = false - } - } - } - } - - totalLines := 0 - totalCoveredLines := 0 - coveredLinesPerFile := make(map[string]int) // filename -> no of covered lines - for filename, lines := range linesCoveredPerFile { - for _, isCovered := range lines { - totalLines++ - if isCovered { - totalCoveredLines++ - coveredLinesPerFile[filename]++ - } - } - } - - for filename, lines := range linesCoveredPerFile { - testCov.FileCov[filename] = strconv.FormatFloat(float64(coveredLinesPerFile[filename]*100)/float64(len(lines)), 'f', 2, 64) + "%" - } - testCov.TotalCov = strconv.FormatFloat(float64(totalCoveredLines*100)/float64(totalLines), 'f', 2, 64) + "%" - testCov.Loc = models.Loc{ - Total: totalLines, - Covered: totalCoveredLines, - } - return testCov, nil -} - -func (j *Javascript) AppendCoverage(coverage *models.TestCoverage, testRunID string) error { - return j.reportDB.UpdateReport(j.ctx, testRunID, coverage) -} diff --git a/keploy/pkg/platform/coverage/javascript/utils.go b/keploy/pkg/platform/coverage/javascript/utils.go deleted file mode 100644 index fb0a6be..0000000 --- a/keploy/pkg/platform/coverage/javascript/utils.go +++ /dev/null @@ -1,48 +0,0 @@ -package javascript - -import ( - "encoding/json" - "os" - "path/filepath" - "strings" -) - -type ProcessInfo struct { - Parent string `json:"parent"` - Pid int `json:"pid"` - Argv []string `json:"argv"` - ExecArgv []string `json:"execArgv"` - Cwd string `json:"cwd"` - Time int `json:"time"` - Ppid int `json:"ppid"` - CoverageFilename string `json:"coverageFilename"` - ExternalID string `json:"externalId"` - UUID string `json:"uuid"` - Files []string `json:"files"` -} - -func getCoverageFilePathsJavascript(path string) ([]string, error) { - filePaths := []string{} - walkfn := func(path string, info os.FileInfo, _ error) error { - if !info.IsDir() && !strings.HasSuffix(path, "index.json") { - fileData, err := os.ReadFile(path) - if err != nil { - return err - } - var processInfo ProcessInfo - err = json.Unmarshal(fileData, &processInfo) - if err != nil { - return err - } - if len(processInfo.Files) > 0 { - filePaths = append(filePaths, processInfo.CoverageFilename) - } - } - return nil - } - err := filepath.Walk(path, walkfn) - if err != nil { - return nil, err - } - return filePaths, nil -} diff --git a/keploy/pkg/platform/coverage/python/python.go b/keploy/pkg/platform/coverage/python/python.go deleted file mode 100644 index 1a087c7..0000000 --- a/keploy/pkg/platform/coverage/python/python.go +++ /dev/null @@ -1,173 +0,0 @@ -// Package python implements the methods for python coverage services. -package python - -import ( - "context" - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/coverage" - "go.uber.org/zap" -) - -type Python struct { - ctx context.Context - logger *zap.Logger - reportDB coverage.ReportDB - cmd string - executable string -} - -func New(ctx context.Context, logger *zap.Logger, reportDB coverage.ReportDB, cmd, executable string) *Python { - return &Python{ - ctx: ctx, - logger: logger, - reportDB: reportDB, - cmd: cmd, - executable: executable, - } -} - -func (p *Python) PreProcess(_ bool) (string, error) { - cmd := exec.Command("coverage") - err := cmd.Run() - if err != nil { - p.logger.Warn("coverage tool not found, skipping coverage caluclation. Please install coverage tool using 'pip install coverage'") - return p.cmd, err - } - createPyCoverageConfig(p.logger) - - // Split the command into parts to handle environment variables and other prefixes - parts := strings.Fields(p.cmd) - - // Find the index of the executable - executableIndex := -1 - for i, part := range parts { - if part == p.executable { - executableIndex = i - break - } - } - - if executableIndex == -1 { - // Fallback to original behavior if executable not found as separate part - covCmd := fmt.Sprintf("%s -m coverage run", p.executable) - str := strings.Replace(p.cmd, p.executable, covCmd, 1) - p.logger.Debug("PreProcess command for Python coverage (fallback)", zap.String("command", str)) - return str, nil - } - - // Insert coverage flags right after the executable - newParts := make([]string, 0, len(parts)+3) // +3 for "-m", "coverage", "run" - newParts = append(newParts, parts[:executableIndex+1]...) // Include executable - newParts = append(newParts, "-m", "coverage", "run") // Add coverage flags - newParts = append(newParts, parts[executableIndex+1:]...) // Add remaining parts - - str := strings.Join(newParts, " ") - p.logger.Debug("PreProcess command for Python coverage", zap.String("command", str), zap.String("executable", p.executable)) - return str, nil -} - -type pyCoverageFile struct { - Meta struct { - Version string `json:"version"` - Timestamp string `json:"timestamp"` - BranchCoverage bool `json:"branch_coverage"` - ShowContexts bool `json:"show_contexts"` - } `json:"meta"` - Files map[string]struct { - ExecutedLines []int `json:"executed_lines"` - Summary struct { - CoveredLines int `json:"covered_lines"` - NumStatements int `json:"num_statements"` - PercentCovered float64 `json:"percent_covered"` - PercentCoveredDisplay string `json:"percent_covered_display"` - MissingLines int `json:"missing_lines"` - ExcludedLines int `json:"excluded_lines"` - } `json:"summary"` - MissingLines []int `json:"missing_lines"` - ExcludedLines []int `json:"excluded_lines"` - } `json:"files"` - Totals struct { - CoveredLines int `json:"covered_lines"` - NumStatements int `json:"num_statements"` - PercentCovered float64 `json:"percent_covered"` - PercentCoveredDisplay string `json:"percent_covered_display"` - MissingLines int `json:"missing_lines"` - ExcludedLines int `json:"excluded_lines"` - } `json:"totals"` -} - -func (p *Python) GetCoverage() (models.TestCoverage, error) { - testCov := models.TestCoverage{ - FileCov: make(map[string]string), - TotalCov: "", - } - - covFileName := os.Getenv("COVERAGE_FILE") - if covFileName == "" { - covFileName = ".coverage.keploy" - } - - p.logger.Info("Combining coverage from child processes when present; no impact if none exist") - - matches, err := filepath.Glob(".coverage.keploy.*") - if err != nil { - return testCov, fmt.Errorf("glob failed for .coverage.keploy.*: %w", err) - } - if len(matches) == 0 { - p.logger.Warn("no per-process .coverage files found – nothing to combine") - return testCov, nil - } - - args := append([]string{ - "-m", - "coverage", - "combine", - "--data-file=" + covFileName, // final merged file - }, matches...) - - combineCmd := exec.CommandContext(p.ctx, p.executable, args...) - combineCmd.Stdout = os.Stdout - combineCmd.Stderr = os.Stderr - - if err := combineCmd.Run(); err != nil { - p.logger.Error("failed to combine coverage files", zap.Error(err)) - return testCov, err - } - generateCovJSONCmd := exec.CommandContext(p.ctx, p.executable, "-m", "coverage", "json", "--data-file="+covFileName) - generateCovJSONCmd.Stdout = os.Stdout - generateCovJSONCmd.Stderr = os.Stderr - err = generateCovJSONCmd.Run() - if err != nil { - return testCov, err - } - coverageData, err := os.ReadFile("coverage.json") - if err != nil { - return testCov, err - } - var cov pyCoverageFile - err = json.Unmarshal(coverageData, &cov) - if err != nil { - return testCov, err - } - for filename, file := range cov.Files { - testCov.FileCov[filename] = file.Summary.PercentCoveredDisplay + "%" - } - testCov.TotalCov = cov.Totals.PercentCoveredDisplay + "%" - testCov.Loc = models.Loc{ - Total: cov.Totals.NumStatements, - Covered: cov.Totals.CoveredLines, - } - - return testCov, nil -} - -func (p *Python) AppendCoverage(coverage *models.TestCoverage, testRunID string) error { - return p.reportDB.UpdateReport(p.ctx, testRunID, coverage) -} diff --git a/keploy/pkg/platform/coverage/python/utils.go b/keploy/pkg/platform/coverage/python/utils.go deleted file mode 100644 index 0070701..0000000 --- a/keploy/pkg/platform/coverage/python/utils.go +++ /dev/null @@ -1,166 +0,0 @@ -package python - -import ( - "fmt" - "os" - "strings" - - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func createPyCoverageConfig(logger *zap.Logger) { - // Check if .coveragerc already exists - existingConfig, err := readExistingCoverageConfig() - if err != nil && !os.IsNotExist(err) { - utils.LogError(logger, err, "failed to read existing .coveragerc file") - } - - // Merge existing config with Keploy's required settings - mergedConfig := mergeCoverageConfig(existingConfig, logger) - - // Write the merged configuration - file, err := os.Create(".coveragerc") - if err != nil { - utils.LogError(logger, err, "failed to create .coveragerc file") - return - } - defer func() { - if err := file.Close(); err != nil { - utils.LogError(logger, err, "failed to close coveragerc file", zap.String("file", file.Name())) - } - }() - - _, err = file.WriteString(mergedConfig) - if err != nil { - utils.LogError(logger, err, "failed to write to .coveragerc file") - return - } - - logger.Debug("Configuration written to .coveragerc with preserved user settings") -} - -// readExistingCoverageConfig reads and returns the content of existing .coveragerc file -func readExistingCoverageConfig() (string, error) { - content, err := os.ReadFile(".coveragerc") - if err != nil { - return "", err - } - return string(content), nil -} - -// mergeCoverageConfig merges existing configuration with Keploy's required settings -func mergeCoverageConfig(existingConfig string, logger *zap.Logger) string { - // In the below config, in the concurrency section, we are setting the concurrency to multiprocessing and thread. - // Where multiprocessing is for collecting coverage for processes spawned by the Python application, - // and thread is for collecting coverage for the main thread. - keploySettings := map[string]string{ - "sigterm": "true", - "concurrency": "multiprocessing, thread", - "parallel": "true", - "data_file": ".coverage.keploy", - } - - // If no existing config, create with Keploy defaults - if existingConfig == "" { - logger.Info("No existing .coveragerc found, creating with Keploy defaults") - return createDefaultKeployConfig(keploySettings) - } - - logger.Info("Existing .coveragerc found, merging with Keploy settings") - - // Parse existing config and merge with Keploy settings - return parseAndMergeConfig(existingConfig, keploySettings, logger) -} - -// createDefaultKeployConfig creates a basic configuration with only Keploy requirements -func createDefaultKeployConfig(keploySettings map[string]string) string { - config := "[run]\n" - config += "omit =\n /usr/*\n" - // Add all Keploy settings since there are no existing settings to process - emptyProcessedSettings := make(map[string]bool) - settingLines := addMissingKeploySettings(keploySettings, emptyProcessedSettings) - for _, line := range settingLines { - config += line + "\n" - } - return config -} - -// parseAndMergeConfig parses the existing config and merges it with Keploy settings -func parseAndMergeConfig(existingConfig string, keploySettings map[string]string, logger *zap.Logger) string { - lines := strings.Split(existingConfig, "\n") - var result []string - var currentSection string - runSectionFound := false - runSectionProcessed := false - processedSettings := make(map[string]bool) // Track which Keploy settings have been processed - - for _, line := range lines { - trimmedLine := strings.TrimSpace(line) - - // Detect section headers - if strings.HasPrefix(trimmedLine, "[") && strings.HasSuffix(trimmedLine, "]") { - currentSection = trimmedLine - if currentSection == "[run]" { - runSectionFound = true - } else if runSectionFound && !runSectionProcessed { - // We're leaving the [run] section, add any missing Keploy settings - result = append(result, addMissingKeploySettings(keploySettings, processedSettings)...) - runSectionProcessed = true - } - result = append(result, line) - continue - } - - // Process settings within [run] section - if currentSection == "[run]" && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { - settingName := extractSettingName(trimmedLine) - - // Check if this is a Keploy required setting - if keployValue, isKeploySetting := keploySettings[settingName]; isKeploySetting { - if settingName != "omit" { - // Override with Keploy value - result = append(result, fmt.Sprintf("%s = %s", settingName, keployValue)) - logger.Debug("Overriding setting with Keploy requirement", zap.String("setting", settingName), zap.String("value", keployValue)) - processedSettings[settingName] = true // Mark as processed - continue - } - } - } - - // Add the original line if not processed above - result = append(result, line) - } - - // If [run] section was found but not all Keploy settings were processed - if runSectionFound && !runSectionProcessed { - result = append(result, addMissingKeploySettings(keploySettings, processedSettings)...) - } - - // If no [run] section was found, add it with Keploy settings - if !runSectionFound { - result = append([]string{"[run]"}, append(addMissingKeploySettings(keploySettings, processedSettings), result...)...) - } - - return strings.Join(result, "\n") -} - -// extractSettingName extracts the setting name from a configuration line -func extractSettingName(line string) string { - parts := strings.SplitN(line, "=", 2) - if len(parts) > 0 { - return strings.TrimSpace(parts[0]) - } - return "" -} - -// addMissingKeploySettings adds any Keploy settings that weren't found in the existing config -func addMissingKeploySettings(keploySettings map[string]string, processedSettings map[string]bool) []string { - var result []string - for key, value := range keploySettings { - if !processedSettings[key] { - result = append(result, fmt.Sprintf("%s = %s", key, value)) - } - } - return result -} diff --git a/keploy/pkg/platform/coverage/service.go b/keploy/pkg/platform/coverage/service.go deleted file mode 100644 index f256aec..0000000 --- a/keploy/pkg/platform/coverage/service.go +++ /dev/null @@ -1,18 +0,0 @@ -// Package coverage defines the interface for coverage services. -package coverage - -import ( - "context" - - "go.keploy.io/server/v2/pkg/models" -) - -type Service interface { - PreProcess(disableLineCoverage bool) (string, error) - GetCoverage() (models.TestCoverage, error) - AppendCoverage(coverage *models.TestCoverage, testRunID string) error -} - -type ReportDB interface { - UpdateReport(ctx context.Context, testRunID string, coverageReport any) error -} diff --git a/keploy/pkg/platform/docker/docker.go b/keploy/pkg/platform/docker/docker.go deleted file mode 100644 index 4b4a0a1..0000000 --- a/keploy/pkg/platform/docker/docker.go +++ /dev/null @@ -1,585 +0,0 @@ -// Package docker provides functionality for working with Docker containers. -package docker - -import ( - "context" - "fmt" - "os" - "path/filepath" - "time" - - nativeDockerClient "github.com/docker/docker/client" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "gopkg.in/yaml.v3" - - "github.com/docker/docker/api/types/network" - - "github.com/docker/docker/api/types" - dockerContainerPkg "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/volume" -) - -const ( - defaultTimeoutForDockerQuery = 1 * time.Minute -) - -type Impl struct { - nativeDockerClient.APIClient - timeoutForDockerQuery time.Duration - logger *zap.Logger - containerID string -} - -func New(logger *zap.Logger) (Client, error) { - dockerClient, err := nativeDockerClient.NewClientWithOpts(nativeDockerClient.FromEnv, - nativeDockerClient.WithAPIVersionNegotiation()) - if err != nil { - return nil, err - } - return &Impl{ - APIClient: dockerClient, - timeoutForDockerQuery: defaultTimeoutForDockerQuery, - logger: logger, - }, nil -} - -// GetContainerID is a Getter function for containerID -func (idc *Impl) GetContainerID() string { - return idc.containerID -} - -// SetContainerID is a Setter function for containerID -func (idc *Impl) SetContainerID(containerID string) { - idc.containerID = containerID -} - -// ExtractNetworksForContainer returns the list of all the networks that the container is a part of. -// Note that if a user did not explicitly attach the container to a network, the Docker daemon attaches it -// to a network called "bridge". -func (idc *Impl) ExtractNetworksForContainer(containerName string) (map[string]*network.EndpointSettings, error) { - ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) - defer cancel() - - containerJSON, err := idc.ContainerInspect(ctx, containerName) - if err != nil { - utils.LogError(idc.logger, err, "couldn't inspect container via the Docker API", zap.String("containerName", containerName)) - return nil, err - } - - if settings := containerJSON.NetworkSettings; settings != nil { - return settings.Networks, nil - } - // Docker attaches the container to "bridge" network by default. - // If the network list is empty, the docker daemon is possibly misbehaving, - // or the container is in a bad state. - utils.LogError(idc.logger, nil, "The network list for the given container is empty. This is unexpected.", zap.String("containerName", containerName)) - return nil, fmt.Errorf("the container is not attached to any network") -} - -func (idc *Impl) ConnectContainerToNetworks(containerName string, settings map[string]*network.EndpointSettings) error { - if settings == nil { - return fmt.Errorf("provided network settings is empty") - } - - existingNetworks, err := idc.ExtractNetworksForContainer(containerName) - if err != nil { - return fmt.Errorf("could not get existing networks for container %s", containerName) - } - - ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) - defer cancel() - - for networkName, setting := range settings { - // If the container is already part of this network, skip it. - _, ok := existingNetworks[networkName] - if ok { - continue - } - - err := idc.NetworkConnect(ctx, networkName, containerName, setting) - if err != nil { - return err - } - } - - return nil -} - -func (idc *Impl) AttachNetwork(containerName string, networkNames []string) error { - if len(networkNames) == 0 { - return fmt.Errorf("provided network names list is empty") - } - - existingNetworks, err := idc.ExtractNetworksForContainer(containerName) - if err != nil { - return fmt.Errorf("could not get existing networks for container %s", containerName) - } - - ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) - defer cancel() - - for _, networkName := range networkNames { - // If the container is already part of this network, skip it. - _, ok := existingNetworks[networkName] - if ok { - continue - } - - // As there are no specific settings, use nil for the settings parameter. - err := idc.NetworkConnect(ctx, networkName, containerName, nil) - if err != nil { - return err - } - } - - return nil -} - -// StopAndRemoveDockerContainer will Stop and Remove the docker container -func (idc *Impl) StopAndRemoveDockerContainer() error { - dockerClient := idc - containerID := idc.containerID - - container, err := dockerClient.ContainerInspect(context.Background(), containerID) - if err != nil { - return fmt.Errorf("failed to inspect the docker container: %w", err) - } - - if container.State.Status == "running" { - err = dockerClient.ContainerStop(context.Background(), containerID, dockerContainerPkg.StopOptions{}) - if err != nil { - return fmt.Errorf("failed to stop the docker container: %w", err) - } - } - - removeOptions := types.ContainerRemoveOptions{ - RemoveVolumes: true, - Force: true, - } - - err = dockerClient.ContainerRemove(context.Background(), containerID, removeOptions) - if err != nil { - return fmt.Errorf("failed to remove the docker container: %w", err) - } - - idc.logger.Debug("Docker Container stopped and removed successfully.") - - return nil -} - -// NetworkExists checks if the given network exists locally or not -func (idc *Impl) NetworkExists(networkName string) (bool, error) { - ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) - defer cancel() - - // Retrieve all networks. - networks, err := idc.NetworkList(ctx, types.NetworkListOptions{}) - if err != nil { - return false, fmt.Errorf("error retrieving networks: %v", err) - } - - // Check if the specified network is in the list. - for _, net := range networks { - if net.Name == networkName { - return true, nil - } - } - - return false, nil -} - -// CreateNetwork creates a custom docker network of type bridge. -func (idc *Impl) CreateNetwork(networkName string) error { - ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) - defer cancel() - - _, err := idc.NetworkCreate(ctx, networkName, types.NetworkCreate{ - Driver: "bridge", - }) - - return err -} - -// Compose structure to represent all the fields of a Docker Compose file -type Compose struct { - Version string `yaml:"version,omitempty"` - Services yaml.Node `yaml:"services,omitempty"` - Networks yaml.Node `yaml:"networks,omitempty"` - Volumes yaml.Node `yaml:"volumes,omitempty"` - Configs yaml.Node `yaml:"configs,omitempty"` - Secrets yaml.Node `yaml:"secrets,omitempty"` -} - -func (idc *Impl) ReadComposeFile(filePath string) (*Compose, error) { - data, err := os.ReadFile(filePath) - if err != nil { - return nil, err - } - - var compose Compose - err = yaml.Unmarshal(data, &compose) - if err != nil { - return nil, err - } - - return &compose, nil -} - -func (idc *Impl) WriteComposeFile(compose *Compose, path string) error { - data, err := yaml.Marshal(compose) - if err != nil { - return err - } - - // write data to file - - err = os.WriteFile(path, data, 0644) - if err != nil { - return err - } - return nil -} - -// HasRelativePath returns information about whether bind mounts if they are being used contain relative file names or not -func (idc *Impl) HasRelativePath(compose *Compose) bool { - if compose.Services.Content == nil { - return false - } - - for _, service := range compose.Services.Content { - for i, item := range service.Content { - - if i+1 >= len(service.Content) { - break - } - - if item.Value == "volumes" { - // volumeKeyNode := service.Content[i] or item - volumeValueNode := service.Content[i+1] - - // Loop over all the volume mounts - for _, volumeMount := range volumeValueNode.Content { - // If volume mount starts with ./ or ../ then it as a relative path so return true - if volumeMount.Kind == yaml.ScalarNode && (volumeMount.Value[:2] == "./" || volumeMount.Value[:3] == "../") { - return true - } - } - } - } - } - - return false - -} - -// GetNetworkInfo CheckNetworkInfo returns information about network name and also about whether the network is external or not in a docker-compose file. -func (idc *Impl) GetNetworkInfo(compose *Compose) *NetworkInfo { - if compose.Networks.Content == nil { - return nil - } - - var defaultNetwork string - - for i := 0; i < len(compose.Networks.Content); i += 2 { - if i+1 >= len(compose.Networks.Content) { - break - } - networkKeyNode := compose.Networks.Content[i] - networkValueNode := compose.Networks.Content[i+1] - - if defaultNetwork == "" { - defaultNetwork = networkKeyNode.Value - } - - isExternal := false - var externalName string - - for j := 0; j < len(networkValueNode.Content); j += 2 { - if j+1 >= len(networkValueNode.Content) { - break - } - propertyNode := networkValueNode.Content[j] - valueNode := networkValueNode.Content[j+1] - if propertyNode.Value == "external" { - if valueNode.Kind == yaml.ScalarNode && valueNode.Value == "true" { - isExternal = true - break - } else if valueNode.Kind == yaml.MappingNode { - for k := 0; k < len(valueNode.Content); k += 2 { - if k+1 >= len(valueNode.Content) { - break - } - subPropertyNode := valueNode.Content[k] - subValueNode := valueNode.Content[k+1] - if subPropertyNode.Value == "name" { - isExternal = true - externalName = subValueNode.Value - break - } - } - } - break - } - } - - if isExternal { - n := &NetworkInfo{External: true, Name: networkKeyNode.Value} - if externalName != "" { - n.Name = externalName - } - return n - } - } - - if defaultNetwork != "" { - return &NetworkInfo{External: false, Name: defaultNetwork} - } - - return nil -} - -// GetHostWorkingDirectory Inspects Keploy docker container to get bind mount for current directory -func (idc *Impl) GetHostWorkingDirectory() (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) - defer cancel() - - curDir, err := os.Getwd() - if err != nil { - utils.LogError(idc.logger, err, "failed to get current working directory") - return "", err - } - - container, err := idc.ContainerInspect(ctx, "keploy-v2") - if err != nil { - utils.LogError(idc.logger, err, "error inspecting keploy-v2 container") - return "", err - } - containerMounts := container.Mounts - // Loop through container mounts and find the mount for current directory in the container - for _, mount := range containerMounts { - if mount.Destination == curDir { - idc.logger.Debug(fmt.Sprintf("found mount for %s in keploy-v2 container", curDir), zap.Any("mount", mount)) - return mount.Source, nil - } - } - return "", fmt.Errorf("%s", fmt.Sprintf("could not find mount for %s in keploy-v2 container", curDir)) -} - -// ForceAbsolutePath replaces relative paths in bind mounts with absolute paths -func (idc *Impl) ForceAbsolutePath(c *Compose, basePath string) error { - hostWorkingDirectory, err := idc.GetHostWorkingDirectory() - if err != nil { - return err - } - - dockerComposeContext, err := filepath.Abs(filepath.Join(hostWorkingDirectory, basePath)) - if err != nil { - utils.LogError(idc.logger, err, "error getting absolute path for docker compose file") - return err - } - dockerComposeContext = filepath.Dir(dockerComposeContext) - idc.logger.Debug("docker compose file location in host filesystem", zap.Any("dockerComposeContext", dockerComposeContext)) - - // Loop through all services in compose file - for _, service := range c.Services.Content { - - for i, item := range service.Content { - - if i+1 >= len(service.Content) { - break - } - - if item.Value == "volumes" { - // volumeKeyNode := service.Content[i] or item - volumeValueNode := service.Content[i+1] - - // Loop over all the volume mounts - for _, volumeMount := range volumeValueNode.Content { - // If volume mount starts with ./ or ../ then it is a relative path - if volumeMount.Kind == yaml.ScalarNode && (volumeMount.Value[:2] == "./" || volumeMount.Value[:3] == "../") { - - // Replace the relative path with absolute path - absPath, err := filepath.Abs(filepath.Join(dockerComposeContext, volumeMount.Value)) - if err != nil { - return err - } - volumeMount.Value = absPath - } - } - } - } - } - return nil -} - -// MakeNetworkExternal makes the existing network of the user docker compose file external and save it to a new file -func (idc *Impl) MakeNetworkExternal(c *Compose) error { - // Iterate over all networks and check the 'external' flag. - if c.Networks.Content != nil { - for i := 0; i < len(c.Networks.Content); i += 2 { - if i+1 >= len(c.Networks.Content) { - break - } - // networkKeyNode := compose.Networks.Content[i] - networkValueNode := c.Networks.Content[i+1] - - // If it's a shorthand notation or null value, initialize it as an empty mapping node - if (networkValueNode.Kind == yaml.ScalarNode && networkValueNode.Value == "") || networkValueNode.Tag == "!!null" { - networkValueNode.Kind = yaml.MappingNode - networkValueNode.Tag = "" - networkValueNode.Content = make([]*yaml.Node, 0) - } - - externalFound := false - for index, propertyNode := range networkValueNode.Content { - if index+1 >= len(networkValueNode.Content) { - break - } - if propertyNode.Value == "external" { - externalFound = true - valueNode := networkValueNode.Content[index+1] - if valueNode.Kind == yaml.ScalarNode && (valueNode.Value == "false" || valueNode.Value == "") { - valueNode.Value = "true" - } - break - } - } - - if !externalFound { - networkValueNode.Content = append(networkValueNode.Content, - &yaml.Node{Kind: yaml.ScalarNode, Value: "external"}, - &yaml.Node{Kind: yaml.ScalarNode, Value: "true"}, - ) - } - } - } - return nil -} - -// SetKeployNetwork adds the keploy-network network to the new docker compose file and copy rest of the contents from -// existing user docker compose file -func (idc *Impl) SetKeployNetwork(c *Compose) (*NetworkInfo, error) { - - // Ensure that the top-level networks mapping exists. - if c.Networks.Content == nil { - c.Networks.Kind = yaml.MappingNode - c.Networks.Content = make([]*yaml.Node, 0) - } - networkInfo := &NetworkInfo{ - Name: "keploy-network", - External: true, - } - // Check if "keploy-network" already exists - exists := false - for i := 0; i < len(c.Networks.Content); i += 2 { - if c.Networks.Content[i].Value == "keploy-network" { - exists = true - break - } - } - - if !exists { - // Add the keploy-network with external: true - c.Networks.Content = append(c.Networks.Content, - &yaml.Node{Kind: yaml.ScalarNode, Value: "keploy-network"}, - &yaml.Node{Kind: yaml.MappingNode, Content: []*yaml.Node{ - {Kind: yaml.ScalarNode, Value: "external"}, - {Kind: yaml.ScalarNode, Value: "true"}, - }}, - ) - } - - // Add or modify network for each service - for _, service := range c.Services.Content { - networksFound := false - for _, item := range service.Content { - if item.Value == "networks" { - networksFound = true - break - } - } - - if !networksFound { - service.Content = append(service.Content, - &yaml.Node{Kind: yaml.ScalarNode, Value: "networks"}, - &yaml.Node{ - Kind: yaml.SequenceNode, - Content: []*yaml.Node{ - {Kind: yaml.ScalarNode, Value: "keploy-network"}, - }, - }, - ) - } else { - for _, item := range service.Content { - if item.Value == "networks" { - item.Content = append(item.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: "keploy-network"}) - } - } - } - } - return networkInfo, nil -} - -// IsContainerRunning check if the container is already running or not, required for docker start command. -func (idc *Impl) IsContainerRunning(containerName string) (bool, error) { - - ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery) - defer cancel() - - containerJSON, err := idc.ContainerInspect(ctx, containerName) - if err != nil { - return false, err - } - - if containerJSON.State.Running { - return true, nil - } - return false, nil -} - -func (idc *Impl) CreateVolume(ctx context.Context, volumeName string, recreate bool) error { - // Set a timeout for the context - ctx, cancel := context.WithTimeout(ctx, idc.timeoutForDockerQuery) - defer cancel() - - // Check if the 'debugfs' volume exists - filter := filters.NewArgs() - filter.Add("name", volumeName) - volumeList, err := idc.VolumeList(ctx, volume.ListOptions{Filters: filter}) - if err != nil { - idc.logger.Error("failed to list docker volumes", zap.Error(err)) - return err - } - - if len(volumeList.Volumes) > 0 { - if !recreate { - idc.logger.Info("volume already exists", zap.Any("volume", volumeName)) - return err - } - - err := idc.VolumeRemove(ctx, volumeName, false) - if err != nil { - idc.logger.Error("failed to delete volume "+volumeName, zap.Error(err)) - return err - } - } - - // Create the 'debugfs' volume if it doesn't exist - _, err = idc.VolumeCreate(ctx, volume.CreateOptions{ - Name: volumeName, - Driver: "local", - DriverOpts: map[string]string{ - "type": volumeName, // Use "none" for local driver - "device": volumeName, - }, - }) - if err != nil { - idc.logger.Error("failed to create volume", zap.Any("volume", volumeName), zap.Error(err)) - return err - } - - idc.logger.Debug("volume created", zap.Any("volume", volumeName)) - return nil -} diff --git a/keploy/pkg/platform/docker/service.go b/keploy/pkg/platform/docker/service.go deleted file mode 100644 index d41dea9..0000000 --- a/keploy/pkg/platform/docker/service.go +++ /dev/null @@ -1,38 +0,0 @@ -package docker - -import ( - "context" - - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" -) - -type Client interface { - client.APIClient - ExtractNetworksForContainer(containerName string) (map[string]*network.EndpointSettings, error) - ConnectContainerToNetworks(containerName string, settings map[string]*network.EndpointSettings) error - AttachNetwork(containerName string, networkName []string) error - StopAndRemoveDockerContainer() error - GetContainerID() string - SetContainerID(containerID string) - NetworkExists(network string) (bool, error) - - HasRelativePath(c *Compose) bool - ForceAbsolutePath(c *Compose, basePath string) error - - GetNetworkInfo(compose *Compose) *NetworkInfo - - CreateNetwork(network string) error - MakeNetworkExternal(c *Compose) error - SetKeployNetwork(c *Compose) (*NetworkInfo, error) - ReadComposeFile(filePath string) (*Compose, error) - WriteComposeFile(compose *Compose, path string) error - - IsContainerRunning(containerName string) (bool, error) - CreateVolume(ctx context.Context, volumeName string, recreate bool) error -} - -type NetworkInfo struct { - External bool - Name string -} diff --git a/keploy/pkg/platform/docker/util.go b/keploy/pkg/platform/docker/util.go deleted file mode 100644 index f35f46e..0000000 --- a/keploy/pkg/platform/docker/util.go +++ /dev/null @@ -1,53 +0,0 @@ -//go:build linux - -package docker - -import ( - "fmt" - "regexp" - - "go.keploy.io/server/v2/utils" -) - -func ParseDockerCmd(cmd string, kind utils.CmdType, idc Client) (string, string, error) { - - // Regular expression patterns - var containerNamePattern string - switch kind { - case utils.DockerStart: - containerNamePattern = `start\s+(?:-[^\s]+\s+)*([^\s]*)` - default: - containerNamePattern = `--name\s+([^\s]+)` - } - - networkNamePattern := `(--network|--net)\s+([^\s]+)` - - // Extract container name - containerNameRegex := regexp.MustCompile(containerNamePattern) - containerNameMatches := containerNameRegex.FindStringSubmatch(cmd) - if len(containerNameMatches) < 2 { - return "", "", fmt.Errorf("failed to parse container name") - } - containerName := containerNameMatches[1] - - if kind == utils.DockerStart { - networks, err := idc.ExtractNetworksForContainer(containerName) - if err != nil { - return containerName, "", err - } - for i := range networks { - return containerName, i, nil - } - return containerName, "", fmt.Errorf("failed to parse network name") - } - - // Extract network name - networkNameRegex := regexp.MustCompile(networkNamePattern) - networkNameMatches := networkNameRegex.FindStringSubmatch(cmd) - if len(networkNameMatches) < 3 { - return containerName, "", fmt.Errorf("failed to parse network name") - } - networkName := networkNameMatches[2] - - return containerName, networkName, nil -} diff --git a/keploy/pkg/platform/storage/storage.go b/keploy/pkg/platform/storage/storage.go deleted file mode 100644 index 4188771..0000000 --- a/keploy/pkg/platform/storage/storage.go +++ /dev/null @@ -1,204 +0,0 @@ -// Package storage defines methods for storage DB. -package storage - -import ( - "bytes" - "compress/gzip" - "context" - "encoding/json" - "fmt" - "io" - "mime/multipart" - "net/http" - "net/textproto" - "path/filepath" - "strings" - "sync" - "time" - - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type Storage struct { - serverURL string - logger *zap.Logger -} - -type MockUploadResponse struct { - IsSuccess bool `json:"isSuccess"` - Error string `json:"error"` -} - -func New(serverURL string, logger *zap.Logger) *Storage { - return &Storage{ - serverURL: serverURL, - logger: logger, - } -} - -func (s *Storage) Upload(ctx context.Context, file io.Reader, mockName string, appName string, token string) error { - - done := make(chan struct{}) - var once sync.Once - - closeDone := func() { - once.Do(func() { - close(done) - }) - } - - // Spinner goroutine - go func() { - spinnerChars := []rune{'|', '/', '-', '\\'} - i := 0 - for { - select { - case <-done: - fmt.Print("\r") // Clear spinner line after done - return - default: - fmt.Printf("\rUploading... %c", spinnerChars[i%len(spinnerChars)]) - i++ - time.Sleep(100 * time.Millisecond) - } - } - }() - defer closeDone() // Ensure we close the channel when the function exits - - // Create a multipart buffer and writer - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - // Create a custom part header for the file field - partHeader := textproto.MIMEHeader{} - partHeader.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "mock", filepath.Base("mocks.yaml"))) - partHeader.Set("Content-Type", "application/octet-stream") - partHeader.Set("Content-Encoding", "gzip") // Explicitly declare compression - - part, err := writer.CreatePart(partHeader) - if err != nil { - return err - } - - // Compress file data with gzip and write into multipart part - gzipWriter := gzip.NewWriter(part) - if _, err := io.Copy(gzipWriter, file); err != nil { - return err - } - if err := gzipWriter.Close(); err != nil { - return err - } - - // Add other form fields - if err := writer.WriteField("appName", appName); err != nil { - s.logger.Error("Error writing appName field", zap.Error(err)) - return err - } - if err := writer.WriteField("mockName", mockName); err != nil { - s.logger.Error("Error writing mockName field", zap.Error(err)) - return err - } - if err := writer.Close(); err != nil { - s.logger.Error("Error closing writer", zap.Error(err)) - return err - } - - // Prepare the HTTP request - req, err := http.NewRequestWithContext(ctx, "POST", s.serverURL+"/mock/upload", body) - if err != nil { - return err - } - req.Header.Set("Content-Type", writer.FormDataContentType()) - req.Header.Set("Authorization", "Bearer "+token) - - // Execute the HTTP request - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer func() { - if err := resp.Body.Close(); err != nil { - utils.LogError(s.logger, err, "failed to close the http response body") - } - }() - - // Read the raw body - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - utils.LogError(s.logger, err, "failed to read the response body") - return err - } - - // Decode into struct from the raw bytes - var mockUploadResponse MockUploadResponse - if err := json.NewDecoder(bytes.NewReader(bodyBytes)).Decode(&mockUploadResponse); err != nil { - utils.LogError(s.logger, err, "failed to decode the response body", zap.Any("Response", resp), zap.String("body", string(bodyBytes))) - return err - } - - closeDone() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("upload failed with status code: %d and error %s", resp.StatusCode, mockUploadResponse.Error) - } - - if !mockUploadResponse.IsSuccess { - utils.LogError(s.logger, fmt.Errorf("upload failed: %s", mockUploadResponse.Error), "failed to upload the mock") - return fmt.Errorf("upload failed: %s", mockUploadResponse.Error) - } - - s.logger.Info("Mock uploaded successfully") - return nil -} - -func (s *Storage) Download(ctx context.Context, mockName string, appName string, userName string, jwtToken string) (io.Reader, error) { - // Create the HTTP request - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/mock/download?appName=%s&mockName=%s&userName=%s", s.serverURL, appName, mockName, userName), nil) - if err != nil { - return nil, err - } - - req.Header.Set("Authorization", "Bearer "+jwtToken) - - req.Header.Set("Accept-Encoding", "gzip") // Request gzip encoding - - // Execute the request - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - defer func() { - err := resp.Body.Close() - if err != nil { - utils.LogError(s.logger, err, "failed to close the http response body") - } - }() - // Read the response body to get the error message - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read response body and the resp code is: %d", resp.StatusCode) - } - return nil, fmt.Errorf("download failed with status code: %d, message: %s", resp.StatusCode, strings.TrimSpace(string(body))) - } - - // Check if the response is gzipped - if strings.EqualFold(resp.Header.Get("Content-Encoding"), "gzip") { - s.logger.Debug("mock response is gzipped") - gr, err := gzip.NewReader(resp.Body) - if err != nil { - defer func() { - err := resp.Body.Close() - if err != nil { - utils.LogError(s.logger, err, "failed to close the http response body") - } - }() - return nil, fmt.Errorf("failed to create gzip reader: %w", err) - } - return gr, nil // gr is an io.Reader, decompressing transparently - } - - return resp.Body, nil -} diff --git a/keploy/pkg/platform/telemetry/telemetry.go b/keploy/pkg/platform/telemetry/telemetry.go deleted file mode 100644 index 153e05e..0000000 --- a/keploy/pkg/platform/telemetry/telemetry.go +++ /dev/null @@ -1,185 +0,0 @@ -// Package telemetry provides functionality for telemetry data collection. -package telemetry - -import ( - "bytes" - "net/http" - "runtime" - "sync" - "time" - - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -var teleURL = "https://telemetry.keploy.io/analytics" - -type Telemetry struct { - Enabled bool - OffMode bool - logger *zap.Logger - InstallationID string - KeployVersion string - GlobalMap sync.Map - client *http.Client -} - -type Options struct { - Enabled bool - Version string - GlobalMap sync.Map - InstallationID string -} - -func NewTelemetry(logger *zap.Logger, opt Options) *Telemetry { - return &Telemetry{ - Enabled: opt.Enabled, - logger: logger, - KeployVersion: opt.Version, - GlobalMap: opt.GlobalMap, - InstallationID: opt.InstallationID, - client: &http.Client{Timeout: 10 * time.Second}, - } -} - -func (tel *Telemetry) Ping() { - if !tel.Enabled { - return - } - go func() { - for { - tel.SendTelemetry("Ping") - time.Sleep(5 * time.Minute) - } - }() -} - -func (tel *Telemetry) TestSetRun(success int, failure int, testSet string, runStatus string) { - dataMap := &sync.Map{} - dataMap.Store("Passed-Tests", success) - dataMap.Store("Failed-Tests", failure) - dataMap.Store("Test-Set", testSet) - dataMap.Store("Run-Status", runStatus) - go tel.SendTelemetry("TestSetRun", dataMap) -} - -func (tel *Telemetry) TestRun(success int, failure int, testSets int, runStatus string) { - dataMap := &sync.Map{} - dataMap.Store("Passed-Tests", success) - dataMap.Store("Failed-Tests", failure) - dataMap.Store("Test-Sets", testSets) - dataMap.Store("Run-Status", runStatus) - go tel.SendTelemetry("TestRun", dataMap) -} - -// MockTestRun is Telemetry event for the Mocking feature test run -func (tel *Telemetry) MockTestRun(utilizedMocks int) { - dataMap := &sync.Map{} - dataMap.Store("Utilized-Mocks", utilizedMocks) - go tel.SendTelemetry("MockTestRun", dataMap) -} - -// RecordedTestSuite is Telemetry event for the tests and mocks that are recorded -func (tel *Telemetry) RecordedTestSuite(testSet string, testsTotal int, mockTotal map[string]int) { - dataMap := &sync.Map{} - dataMap.Store("test-set", testSet) - dataMap.Store("tests", testsTotal) - - mockMap := &sync.Map{} - for k, v := range mockTotal { - mockMap.Store(k, v) - } - dataMap.Store("mocks", mockMap) - - go tel.SendTelemetry("RecordedTestSuite", dataMap) -} - -func (tel *Telemetry) RecordedTestAndMocks() { - dataMap := &sync.Map{} - mapcheck := make(map[string]int) - dataMap.Store("mocks", mapcheck) - go tel.SendTelemetry("RecordedTestAndMocks", dataMap) -} - -func (tel *Telemetry) GenerateUT() { - dataMap := &sync.Map{} - go tel.SendTelemetry("GenerateUT", dataMap) -} - -// RecordedMocks is Telemetry event for the mocks that are recorded in the mocking feature -func (tel *Telemetry) RecordedMocks(mockTotal map[string]int) { - mockMap := &sync.Map{} - for k, v := range mockTotal { - mockMap.Store(k, v) - } - dataMap := &sync.Map{} - dataMap.Store("mocks", mockMap) - go tel.SendTelemetry("RecordedMocks", dataMap) -} - -func (tel *Telemetry) RecordedTestCaseMock(mockType string) { - dataMap := &sync.Map{} - dataMap.Store("mock", mockType) - go tel.SendTelemetry("RecordedTestCaseMock", dataMap) -} - -func (tel *Telemetry) SendTelemetry(eventType string, output ...*sync.Map) { - if tel.Enabled { - event := models.TeleEvent{ - EventType: eventType, - CreatedAt: time.Now().Unix(), - } - if len(output) > 0 { - event.Meta = output[0] - } else { - event.Meta = &sync.Map{} - } - - hasGlobalMap := false - tel.GlobalMap.Range(func(key, value interface{}) bool { - hasGlobalMap = true - return false // Stop iteration after finding the first element - }) - - if hasGlobalMap { - // event.Meta["global-map"] = syncMapToMap(tel.GlobalMap) - // If you want to nest the global map, you can do this (but the telemetry - // endpoint needs to support nested sync.Maps): - // event.Meta.Store("global-map", tel.GlobalMap) - // Otherwise, merge the global map into the event's meta map - tel.GlobalMap.Range(func(key, value interface{}) bool { - event.Meta.Store(key, value) - return true - }) - } - - event.InstallationID = tel.InstallationID - event.OS = runtime.GOOS - event.KeployVersion = tel.KeployVersion - event.Arch = runtime.GOARCH - bin, err := marshalEvent(event, tel.logger) - if err != nil { - tel.logger.Debug("failed to marshal event", zap.Error(err)) - return - } - - req, err := http.NewRequest(http.MethodPost, teleURL, bytes.NewBuffer(bin)) - if err != nil { - tel.logger.Debug("failed to create request for analytics", zap.Error(err)) - return - } - - req.Header.Set("Content-Type", "application/json; charset=utf-8") - - resp, err := tel.client.Do(req) - if err != nil { - tel.logger.Debug("failed to send request for analytics", zap.Error(err)) - return - } - _, err = unmarshalResp(resp, tel.logger) - if err != nil { - tel.logger.Debug("failed to unmarshal response", zap.Error(err)) - return - } - } -} diff --git a/keploy/pkg/platform/telemetry/utils.go b/keploy/pkg/platform/telemetry/utils.go deleted file mode 100644 index b4229f4..0000000 --- a/keploy/pkg/platform/telemetry/utils.go +++ /dev/null @@ -1,52 +0,0 @@ -package telemetry - -import ( - "encoding/json" - "errors" - "io" - "net/http" - - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -func marshalEvent(event models.TeleEvent, log *zap.Logger) (bin []byte, err error) { - - bin, err = json.Marshal(event) - if err != nil { - log.Fatal("failed to marshal event struct into json", zap.Error(err)) - } - return -} - -func unmarshalResp(resp *http.Response, log *zap.Logger) (id string, err error) { - - defer func(Body io.ReadCloser) { - err = Body.Close() - if err != nil { - log.Debug("failed to close connecton reader", zap.String("url", "https://telemetry.keploy.io/analytics"), zap.Error(err)) - return - } - }(resp.Body) - - var res map[string]string - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Debug("failed to read response from telemetry server", zap.String("url", "https://telemetry.keploy.io/analytics"), zap.Error(err)) - return - } - - err = json.Unmarshal(body, &res) - if err != nil { - log.Debug("failed to read testcases from telemetry server", zap.Error(err)) - return - } - - id, ok := res["InstallationID"] - if !ok { - log.Debug("InstallationID not present") - err = errors.New("InstallationID not present") - return - } - return -} diff --git a/keploy/pkg/platform/yaml/configdb/testset/db.go b/keploy/pkg/platform/yaml/configdb/testset/db.go deleted file mode 100644 index 3cc4bd5..0000000 --- a/keploy/pkg/platform/yaml/configdb/testset/db.go +++ /dev/null @@ -1,96 +0,0 @@ -// Package testset provides functionality for working with keploy testset level configs like templates, post/pre script. -package testset - -import ( - "context" - "os" - "path/filepath" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/yaml" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -// Db is a generic struct to read and write testset config file -type Db[T any] struct { - logger *zap.Logger - path string -} - -func New[T any](logger *zap.Logger, path string) *Db[T] { - return &Db[T]{ - logger: logger, - path: path, - } -} - -func (db *Db[T]) Read(ctx context.Context, testSetID string) (T, error) { - filePath := filepath.Join(db.path, testSetID) - - var config T - data, err := yaml.ReadFile(ctx, db.logger, filePath, "config") - if err != nil { - return config, err - } - if err := yamlLib.Unmarshal(data, &config); err != nil { - utils.LogError(db.logger, err, "failed to unmarshal test-set config file", zap.String("testSet", testSetID)) - return config, err - } - - secretConfig, ok := any(config).(models.Secret) - - if !ok { - return config, nil - } - - secretValues, err := db.ReadSecret(ctx, testSetID) - if err != nil { - db.logger.Warn("Failed to read secret values, continuing without secrets", zap.String("testSet", testSetID), zap.Error(err)) - return config, err - } - - secretConfig.SetSecrets(secretValues) - - return config, nil -} - -func (db *Db[T]) Write(ctx context.Context, testSetID string, config T) error { - filePath := filepath.Join(db.path, testSetID) - data, err := yamlLib.Marshal(config) - if err != nil { - utils.LogError(db.logger, err, "failed to marshal test-set config file", zap.String("testSet", testSetID)) - return err - } - err = yaml.WriteFile(ctx, db.logger, filePath, "config", data, false) - if err != nil { - utils.LogError(db.logger, err, "failed to write test-set configuration in yaml file", zap.String("testSet", testSetID)) - return err - } - - return nil -} - -// ReadSecret reads the secret configuration for a test set -func (db *Db[T]) ReadSecret(ctx context.Context, testSetID string) (map[string]interface{}, error) { - filePath := filepath.Join(db.path, testSetID) - - secretPath := filepath.Join(filePath, "secret.yaml") - if _, err := os.Stat(secretPath); os.IsNotExist(err) { - return make(map[string]interface{}), nil - } - - data, err := yaml.ReadFile(ctx, db.logger, filePath, "secret") - if err != nil { - return nil, err - } - - var secretConfig map[string]interface{} - if err := yamlLib.Unmarshal(data, &secretConfig); err != nil { - utils.LogError(db.logger, err, "failed to unmarshal test-set secret file", zap.String("testSet", testSetID)) - return nil, err - } - - return secretConfig, nil -} diff --git a/keploy/pkg/platform/yaml/configdb/user/db.go b/keploy/pkg/platform/yaml/configdb/user/db.go deleted file mode 100644 index 5dc3fae..0000000 --- a/keploy/pkg/platform/yaml/configdb/user/db.go +++ /dev/null @@ -1,107 +0,0 @@ -// Package user provides functionality for working with keploy user configs like installation id. -package user - -import ( - "context" - "os" - "runtime" - - "github.com/denisbrodbeck/machineid" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "gopkg.in/yaml.v2" - yamlLib "gopkg.in/yaml.v3" -) - -type KeployConfig struct { - UpdatePrompt string `yaml:"updatePrompt" json:"updatePrompt"` -} - -type Db struct { - logger *zap.Logger - cfg *config.Config -} - -func HomeDir() string { - configFolder := "/.keploy" - if runtime.GOOS == "windows" { - home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") - if home == "" { - home = os.Getenv("USERPROFILE") - } - return home + configFolder - } - return os.Getenv("HOME") + configFolder -} - -func (db *Db) ReadKeployConfig() (*KeployConfig, error) { - path := HomeDir() + "/keploy.yaml" - content, err := os.ReadFile(path) - if err != nil { - return nil, err - } - // Decode the yaml file - var data KeployConfig - err = yaml.Unmarshal(content, &data) - if err != nil { - utils.LogError(db.logger, err, "failed to unmarshal keploy.yaml") - return nil, err - } - return &data, nil -} - -func (db *Db) WriteKeployConfig(data *KeployConfig) error { - // Open the keploy.yaml file - path := HomeDir() + "/keploy.yaml" - file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) - if err != nil { - return err - } - defer func() { - if err := file.Close(); err != nil { - db.logger.Error("failed to close file", zap.Error(err)) - } - }() - updatedData, err := yamlLib.Marshal(data) - if err != nil { - return err - } - // Truncate the file before writing to it. - err = file.Truncate(0) - if err != nil { - return err - } - _, err = file.Write(updatedData) - if err != nil { - return err - } - return nil -} - -func New(logger *zap.Logger, cfg *config.Config) *Db { - return &Db{ - logger: logger, - cfg: cfg, - } -} - -func (db *Db) GetInstallationID(_ context.Context) (string, error) { - var id string - var err error - inDocker := os.Getenv("KEPLOY_INDOCKER") - if inDocker == "true" { - id = os.Getenv("INSTALLATION_ID") - } else { - id, err = machineid.ID() - if err != nil { - db.logger.Debug("failed to get machine id", zap.Error(err)) - return "", nil - } - } - if id == "" { - db.logger.Debug("got empty machine id") - return "", nil - } - return id, nil -} diff --git a/keploy/pkg/platform/yaml/mockdb/db.go b/keploy/pkg/platform/yaml/mockdb/db.go deleted file mode 100644 index 22ce031..0000000 --- a/keploy/pkg/platform/yaml/mockdb/db.go +++ /dev/null @@ -1,319 +0,0 @@ -// Package mockdb provides a mock database implementation. -package mockdb - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "sync/atomic" - "time" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/yaml" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -type MockYaml struct { - MockPath string - MockName string - Logger *zap.Logger - idCounter int64 -} - -func New(Logger *zap.Logger, mockPath string, mockName string) *MockYaml { - return &MockYaml{ - MockPath: mockPath, - MockName: mockName, - Logger: Logger, - idCounter: -1, - } -} - -// UpdateMocks deletes the mocks from the mock file with given names -// -// mockNames is a map which contains the name of the mocks as key and a isConfig boolean as value -func (ys *MockYaml) UpdateMocks(ctx context.Context, testSetID string, mockNames map[string]models.MockState) error { - mockFileName := "mocks" - if ys.MockName != "" { - mockFileName = ys.MockName - } - path := filepath.Join(ys.MockPath, testSetID) - ys.Logger.Debug("logging the names of the unused mocks to be removed", zap.Any("mockNames", mockNames), zap.Any("for testset", testSetID), zap.Any("at path", filepath.Join(path, mockFileName+".yaml"))) - - // Read the mocks from the yaml file - mockPath, err := yaml.ValidatePath(filepath.Join(path, mockFileName+".yaml")) - if err != nil { - utils.LogError(ys.Logger, err, "failed to read mocks due to inaccessible path", zap.Any("at path", filepath.Join(path, mockFileName+".yaml"))) - return err - } - if _, err := os.Stat(mockPath); err != nil { - utils.LogError(ys.Logger, err, "failed to find the mocks yaml file") - return err - } - data, err := yaml.ReadFile(ctx, ys.Logger, path, mockFileName) - if err != nil { - utils.LogError(ys.Logger, err, "failed to read the mocks from yaml file", zap.Any("at path", filepath.Join(path, mockFileName+".yaml"))) - return err - } - - // decode the mocks read from the yaml file - dec := yamlLib.NewDecoder(bytes.NewReader(data)) - var mockYamls []*yaml.NetworkTrafficDoc - for { - var doc *yaml.NetworkTrafficDoc - err := dec.Decode(&doc) - if errors.Is(err, io.EOF) { - break - } - if err != nil { - utils.LogError(ys.Logger, err, "failed to decode the yaml file documents", zap.Any("at path", filepath.Join(path, mockFileName+".yaml"))) - return fmt.Errorf("failed to decode the yaml file documents. error: %v", err.Error()) - } - mockYamls = append(mockYamls, doc) - } - mocks, err := decodeMocks(mockYamls, ys.Logger) - if err != nil { - return err - } - var newMocks []*models.Mock - for _, mock := range mocks { - if _, ok := mockNames[mock.Name]; ok { - newMocks = append(newMocks, mock) - continue - } - } - ys.Logger.Debug("logging the names of the used mocks", zap.Any("mockNames", newMocks), zap.Any("for testset", testSetID)) - - // remove the old mock yaml file - err = os.Remove(filepath.Join(path, mockFileName+".yaml")) - if err != nil { - return err - } - - // write the new mocks to the new yaml file - for _, newMock := range newMocks { - mockYaml, err := EncodeMock(newMock, ys.Logger) - if err != nil { - utils.LogError(ys.Logger, err, "failed to encode the mock to yaml", zap.Any("mock", newMock.Name), zap.Any("for testset", testSetID)) - return err - } - data, err = yamlLib.Marshal(&mockYaml) - if err != nil { - utils.LogError(ys.Logger, err, "failed to marshal the mock to yaml", zap.Any("mock", newMock.Name), zap.Any("for testset", testSetID)) - return err - } - err = yaml.WriteFile(ctx, ys.Logger, path, mockFileName, data, true) - if err != nil { - utils.LogError(ys.Logger, err, "failed to write the mock to yaml", zap.Any("mock", newMock.Name), zap.Any("for testset", testSetID)) - return err - } - } - return nil -} - -func (ys *MockYaml) InsertMock(ctx context.Context, mock *models.Mock, testSetID string) error { - mock.Name = fmt.Sprint("mock-", ys.getNextID()) - mockYaml, err := EncodeMock(mock, ys.Logger) - if err != nil { - return err - } - mockPath := filepath.Join(ys.MockPath, testSetID) - mockFileName := ys.MockName - if mockFileName == "" { - mockFileName = "mocks" - } - data, err := yamlLib.Marshal(&mockYaml) - if err != nil { - return err - } - - exists, err := yaml.FileExists(ctx, ys.Logger, mockPath, mockFileName) - if err != nil { - utils.LogError(ys.Logger, err, "failed to find yaml file", zap.String("path directory", mockPath), zap.String("yaml", mockFileName)) - return err - } - - if !exists { - data = append([]byte(utils.GetVersionAsComment()), data...) - } - - err = yaml.WriteFile(ctx, ys.Logger, mockPath, mockFileName, data, true) - if err != nil { - return err - } - return nil -} - -func (ys *MockYaml) GetFilteredMocks(ctx context.Context, testSetID string, afterTime time.Time, beforeTime time.Time) ([]*models.Mock, error) { - - var tcsMocks = make([]*models.Mock, 0) - mockFileName := "mocks" - if ys.MockName != "" { - mockFileName = ys.MockName - } - - path := filepath.Join(ys.MockPath, testSetID) - mockPath, err := yaml.ValidatePath(path + "/" + mockFileName + ".yaml") - if err != nil { - return nil, err - } - - if _, err := os.Stat(mockPath); err == nil { - data, err := yaml.ReadFile(ctx, ys.Logger, path, mockFileName) - if err != nil { - utils.LogError(ys.Logger, err, "failed to read the mocks from yaml file", zap.Any("session", filepath.Base(path)), zap.String("path", mockPath)) - return nil, err - } - if len(data) == 0 { - utils.LogError(ys.Logger, err, "failed to read the mocks from yaml file", zap.Any("session", filepath.Base(path)), zap.String("path", mockPath)) - return nil, fmt.Errorf("failed to get mocks, empty file") - } - dec := yamlLib.NewDecoder(bytes.NewReader(data)) - for { - var doc *yaml.NetworkTrafficDoc - err := dec.Decode(&doc) - if errors.Is(err, io.EOF) { - break - } - if err != nil { - return nil, fmt.Errorf("failed to decode the yaml file documents. error: %v", err.Error()) - } - - // Decode each YAML document into models.Mock as it is read. - mocks, err := decodeMocks([]*yaml.NetworkTrafficDoc{doc}, ys.Logger) - if err != nil { - utils.LogError(ys.Logger, err, "failed to decode the config mocks from yaml doc", zap.Any("session", filepath.Base(path))) - return nil, err - } - - for _, mock := range mocks { - isFilteredMock := true - switch mock.Kind { - case "Generic": - isFilteredMock = false - case "Postgres": - isFilteredMock = false - case "Http": - isFilteredMock = false - case "Redis": - isFilteredMock = false - case "MySQL": - isFilteredMock = false - } - if mock.Spec.Metadata["type"] != "config" && isFilteredMock { - tcsMocks = append(tcsMocks, mock) - } - } - } - } - - filtered := pkg.FilterTcsMocks(ctx, ys.Logger, tcsMocks, afterTime, beforeTime) - return filtered, nil -} - -func (ys *MockYaml) GetUnFilteredMocks(ctx context.Context, testSetID string, afterTime time.Time, beforeTime time.Time) ([]*models.Mock, error) { - - var configMocks = make([]*models.Mock, 0) - - mockName := "mocks" - if ys.MockName != "" { - mockName = ys.MockName - } - - path := filepath.Join(ys.MockPath, testSetID) - - mockPath, err := yaml.ValidatePath(path + "/" + mockName + ".yaml") - if err != nil { - return nil, err - } - - if _, err := os.Stat(mockPath); err == nil { - data, err := yaml.ReadFile(ctx, ys.Logger, path, mockName) - if err != nil { - utils.LogError(ys.Logger, err, "failed to read the mocks from config yaml", zap.Any("session", filepath.Base(path))) - return nil, err - } - dec := yamlLib.NewDecoder(bytes.NewReader(data)) - for { - var doc *yaml.NetworkTrafficDoc - err := dec.Decode(&doc) - if errors.Is(err, io.EOF) { - break - } - if err != nil { - return nil, fmt.Errorf("failed to decode the yaml file documents. error: %v", err.Error()) - } - - // Decode each YAML document into models.Mock as it is read. - mocks, err := decodeMocks([]*yaml.NetworkTrafficDoc{doc}, ys.Logger) - if err != nil { - utils.LogError(ys.Logger, err, "failed to decode the config mocks from yaml doc", zap.Any("session", filepath.Base(path))) - return nil, err - } - - for _, mock := range mocks { - isUnFilteredMock := false - switch mock.Kind { - case "Generic": - isUnFilteredMock = true - case "Postgres": - isUnFilteredMock = true - case "Http": - isUnFilteredMock = true - case "Redis": - isUnFilteredMock = true - case "MySQL": - isUnFilteredMock = true - } - if mock.Spec.Metadata["type"] == "config" || isUnFilteredMock { - configMocks = append(configMocks, mock) - } - } - } - } - - unfiltered := pkg.FilterConfigMocks(ctx, ys.Logger, configMocks, afterTime, beforeTime) - - return unfiltered, nil -} - -func (ys *MockYaml) getNextID() int64 { - return atomic.AddInt64(&ys.idCounter, 1) -} - -func (ys *MockYaml) GetHTTPMocks(ctx context.Context, testSetID string, mockPath string, mockFileName string) ([]*models.HTTPDoc, error) { - - if ys.MockName != "" { - ys.MockName = mockFileName - } - ys.MockPath = mockPath - - tcsMocks, err := ys.GetUnFilteredMocks(ctx, testSetID, time.Time{}, time.Time{}) - if err != nil { - return nil, err - } - - var httpMocks []*models.HTTPDoc - for _, mock := range tcsMocks { - if mock.Kind != "Http" { - continue - } - var httpMock models.HTTPDoc - httpMock.Kind = mock.GetKind() - httpMock.Name = mock.Name - httpMock.Spec.Request = *mock.Spec.HTTPReq - httpMock.Spec.Response = *mock.Spec.HTTPResp - httpMock.Spec.Metadata = mock.Spec.Metadata - httpMock.Version = string(mock.Version) - httpMocks = append(httpMocks, &httpMock) - } - - return httpMocks, nil -} diff --git a/keploy/pkg/platform/yaml/mockdb/util.go b/keploy/pkg/platform/yaml/mockdb/util.go deleted file mode 100644 index c9d62e8..0000000 --- a/keploy/pkg/platform/yaml/mockdb/util.go +++ /dev/null @@ -1,714 +0,0 @@ -package mockdb - -import ( - "context" - "errors" - "strings" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/models/mysql" - "go.keploy.io/server/v2/pkg/platform/yaml" - "go.keploy.io/server/v2/utils" - "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" - "go.uber.org/zap" -) - -func EncodeMock(mock *models.Mock, logger *zap.Logger) (*yaml.NetworkTrafficDoc, error) { - - yamlDoc := yaml.NetworkTrafficDoc{ - Version: mock.Version, - Kind: mock.Kind, - Name: mock.Name, - ConnectionID: mock.ConnectionID, - } - switch mock.Kind { - case models.Mongo: - requests := []models.RequestYaml{} - for _, v := range mock.Spec.MongoRequests { - req := models.RequestYaml{ - Header: v.Header, - ReadDelay: v.ReadDelay, - } - err := req.Message.Encode(v.Message) - if err != nil { - utils.LogError(logger, err, "failed to encode mongo request wiremessage into yaml") - return nil, err - } - requests = append(requests, req) - } - responses := []models.ResponseYaml{} - for _, v := range mock.Spec.MongoResponses { - resp := models.ResponseYaml{ - Header: v.Header, - ReadDelay: v.ReadDelay, - } - err := resp.Message.Encode(v.Message) - if err != nil { - utils.LogError(logger, err, "failed to encode mongo response wiremessage into yaml") - return nil, err - } - responses = append(responses, resp) - } - mongoSpec := models.MongoSpec{ - Metadata: mock.Spec.Metadata, - Requests: requests, - Response: responses, - CreatedAt: mock.Spec.Created, - ReqTimestampMock: mock.Spec.ReqTimestampMock, - ResTimestampMock: mock.Spec.ResTimestampMock, - } - - err := yamlDoc.Spec.Encode(mongoSpec) - if err != nil { - utils.LogError(logger, err, "failed to marshal the mongo input-output as yaml") - return nil, err - } - - case models.HTTP: - httpSpec := models.HTTPSchema{ - Metadata: mock.Spec.Metadata, - Request: *mock.Spec.HTTPReq, - Response: *mock.Spec.HTTPResp, - Created: mock.Spec.Created, - ReqTimestampMock: mock.Spec.ReqTimestampMock, - ResTimestampMock: mock.Spec.ResTimestampMock, - } - err := yamlDoc.Spec.Encode(httpSpec) - if err != nil { - utils.LogError(logger, err, "failed to marshal the http input-output as yaml") - return nil, err - } - case models.GENERIC: - genericSpec := models.GenericSchema{ - Metadata: mock.Spec.Metadata, - GenericRequests: mock.Spec.GenericRequests, - GenericResponses: mock.Spec.GenericResponses, - ReqTimestampMock: mock.Spec.ReqTimestampMock, - ResTimestampMock: mock.Spec.ResTimestampMock, - } - err := yamlDoc.Spec.Encode(genericSpec) - if err != nil { - utils.LogError(logger, err, "failed to marshal the generic input-output as yaml") - return nil, err - } - case models.REDIS: - redisSpec := models.RedisSchema{ - Metadata: mock.Spec.Metadata, - RedisRequests: mock.Spec.RedisRequests, - RedisResponses: mock.Spec.RedisResponses, - ReqTimestampMock: mock.Spec.ReqTimestampMock, - ResTimestampMock: mock.Spec.ResTimestampMock, - } - err := yamlDoc.Spec.Encode(redisSpec) - if err != nil { - utils.LogError(logger, err, "failed to marshal the redis input-output as yaml") - return nil, err - } - case models.Postgres: - // case models.PostgresV2: - - postgresSpec := models.PostgresSpec{ - Metadata: mock.Spec.Metadata, - PostgresRequests: mock.Spec.PostgresRequests, - PostgresResponses: mock.Spec.PostgresResponses, - ReqTimestampMock: mock.Spec.ReqTimestampMock, - ResTimestampMock: mock.Spec.ResTimestampMock, - } - - err := yamlDoc.Spec.Encode(postgresSpec) - if err != nil { - utils.LogError(logger, err, "failed to marshal the postgres input-output as yaml") - return nil, err - } - case models.GRPC_EXPORT: - gRPCSpec := models.GrpcSpec{ - Metadata: mock.Spec.Metadata, - GrpcReq: *mock.Spec.GRPCReq, - GrpcResp: *mock.Spec.GRPCResp, - ReqTimestampMock: mock.Spec.ReqTimestampMock, - ResTimestampMock: mock.Spec.ResTimestampMock, - } - err := yamlDoc.Spec.Encode(gRPCSpec) - if err != nil { - utils.LogError(logger, err, "failed to marshal gRPC of external call into yaml") - return nil, err - } - case models.MySQL: - requests := []mysql.RequestYaml{} - for _, v := range mock.Spec.MySQLRequests { - - req := mysql.RequestYaml{ - Header: v.Header, - Meta: v.Meta, - } - err := req.Message.Encode(v.Message) - if err != nil { - utils.LogError(logger, err, "failed to encode mysql request wiremessage into yaml") - return nil, err - } - requests = append(requests, req) - } - responses := []mysql.ResponseYaml{} - for _, v := range mock.Spec.MySQLResponses { - resp := mysql.ResponseYaml{ - Header: v.Header, - Meta: v.Meta, - } - err := resp.Message.Encode(v.Message) - if err != nil { - utils.LogError(logger, err, "failed to encode mysql response wiremessage into yaml") - return nil, err - } - responses = append(responses, resp) - } - - sqlSpec := mysql.Spec{ - Metadata: mock.Spec.Metadata, - Requests: requests, - Response: responses, - CreatedAt: mock.Spec.Created, - ReqTimestampMock: mock.Spec.ReqTimestampMock, - ResTimestampMock: mock.Spec.ResTimestampMock, - } - err := yamlDoc.Spec.Encode(sqlSpec) - if err != nil { - utils.LogError(logger, err, "failed to marshal the MySQL input-output as yaml") - return nil, err - } - default: - utils.LogError(logger, nil, "failed to marshal the recorded mock into yaml due to invalid kind of mock") - return nil, errors.New("type of mock is invalid") - } - - return &yamlDoc, nil -} - -func decodeMocks(yamlMocks []*yaml.NetworkTrafficDoc, logger *zap.Logger) ([]*models.Mock, error) { - mocks := []*models.Mock{} - - for _, m := range yamlMocks { - mock := models.Mock{ - Version: m.Version, - Name: m.Name, - Kind: m.Kind, - ConnectionID: m.ConnectionID, - } - mockCheck := strings.Split(string(m.Kind), "-") - if len(mockCheck) > 1 { - logger.Debug("This dependency does not belong to open source version, will be skipped", zap.String("mock kind:", string(m.Kind))) - continue - } - switch m.Kind { - case models.HTTP: - httpSpec := models.HTTPSchema{} - err := m.Spec.Decode(&httpSpec) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal a yaml doc into http mock", zap.Any("mock name", m.Name)) - return nil, err - } - - mock.Spec = models.MockSpec{ - Metadata: httpSpec.Metadata, - HTTPReq: &httpSpec.Request, - HTTPResp: &httpSpec.Response, - Created: httpSpec.Created, - ReqTimestampMock: httpSpec.ReqTimestampMock, - ResTimestampMock: httpSpec.ResTimestampMock, - } - case models.Mongo: - mongoSpec := models.MongoSpec{} - err := m.Spec.Decode(&mongoSpec) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal a yaml doc into mongo mock", zap.Any("mock name", m.Name)) - return nil, err - } - - mockSpec, err := decodeMongoMessage(&mongoSpec, logger) - if err != nil { - return nil, err - } - mock.Spec = *mockSpec - case models.GRPC_EXPORT: - grpcSpec := models.GrpcSpec{} - err := m.Spec.Decode(&grpcSpec) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal a yaml doc into http mock", zap.Any("mock name", m.Name)) - return nil, err - } - mock.Spec = models.MockSpec{ - Metadata: grpcSpec.Metadata, - GRPCResp: &grpcSpec.GrpcResp, - GRPCReq: &grpcSpec.GrpcReq, - ReqTimestampMock: grpcSpec.ReqTimestampMock, - ResTimestampMock: grpcSpec.ResTimestampMock, - } - case models.GENERIC: - genericSpec := models.GenericSchema{} - err := m.Spec.Decode(&genericSpec) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal a yaml doc into generic mock", zap.Any("mock name", m.Name)) - return nil, err - } - mock.Spec = models.MockSpec{ - Metadata: genericSpec.Metadata, - GenericRequests: genericSpec.GenericRequests, - GenericResponses: genericSpec.GenericResponses, - ReqTimestampMock: genericSpec.ReqTimestampMock, - ResTimestampMock: genericSpec.ResTimestampMock, - } - case models.REDIS: - redisSpec := models.RedisSchema{} - err := m.Spec.Decode(&redisSpec) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal a yaml doc into redis mock", zap.Any("mock name", m.Name)) - return nil, err - } - mock.Spec = models.MockSpec{ - Metadata: redisSpec.Metadata, - RedisRequests: redisSpec.RedisRequests, - RedisResponses: redisSpec.RedisResponses, - ReqTimestampMock: redisSpec.ReqTimestampMock, - ResTimestampMock: redisSpec.ResTimestampMock, - } - - case models.Postgres: - // case models.PostgresV2: - - PostSpec := models.PostgresSpec{} - err := m.Spec.Decode(&PostSpec) - - if err != nil { - utils.LogError(logger, err, "failed to unmarshal a yaml doc into generic mock", zap.Any("mock name", m.Name)) - return nil, err - } - mock.Spec = models.MockSpec{ - Metadata: PostSpec.Metadata, - // OutputBinary: genericSpec.Objects, - PostgresRequests: PostSpec.PostgresRequests, - PostgresResponses: PostSpec.PostgresResponses, - ReqTimestampMock: PostSpec.ReqTimestampMock, - ResTimestampMock: PostSpec.ResTimestampMock, - } - case models.MySQL: - mySQLSpec := mysql.Spec{} - err := m.Spec.Decode(&mySQLSpec) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal a yaml doc into mysql mock", zap.Any("mock name", m.Name)) - return nil, err - } - - mockSpec, err := decodeMySQLMessage(context.Background(), logger, &mySQLSpec) - if err != nil { - return nil, err - } - mock.Spec = *mockSpec - default: - utils.LogError(logger, nil, "failed to unmarshal a mock yaml doc of unknown type", zap.Any("type", m.Kind)) - continue - } - mocks = append(mocks, &mock) - } - - return mocks, nil -} - -func decodeMySQLMessage(_ context.Context, logger *zap.Logger, yamlSpec *mysql.Spec) (*models.MockSpec, error) { - mockSpec := models.MockSpec{ - Metadata: yamlSpec.Metadata, - Created: yamlSpec.CreatedAt, - ReqTimestampMock: yamlSpec.ReqTimestampMock, - ResTimestampMock: yamlSpec.ResTimestampMock, - } - - // Decode the requests - - requests := []mysql.Request{} - for _, v := range yamlSpec.Requests { - req := mysql.Request{ - PacketBundle: mysql.PacketBundle{ - Header: v.Header, - Meta: v.Meta, - }, - } - - switch v.Header.Type { - // connection phase - - case mysql.SSLRequest: - msg := &mysql.SSLRequestPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql SSLRequestPacket") - return nil, err - } - req.Message = msg - - case mysql.HandshakeResponse41: - msg := &mysql.HandshakeResponse41Packet{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql HandshakeResponse41Packet") - return nil, err - } - req.Message = msg - - case mysql.CachingSha2PasswordToString(mysql.RequestPublicKey): - var msg string - err := v.Message.Decode(&msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql (string) RequestPublicKey") - return nil, err - } - req.Message = msg - - case mysql.EncryptedPassword: - var msg string - err := v.Message.Decode(&msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql (string) encrypted_password") - return nil, err - } - req.Message = msg - case mysql.PlainPassword: - var msg string - err := v.Message.Decode(&msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql (string) plain_password") - return nil, err - } - req.Message = msg - - // command phase - - // utility packets - case mysql.CommandStatusToString(mysql.COM_QUIT): - msg := &mysql.QuitPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql QuitPacket") - return nil, err - } - req.Message = msg - - case mysql.CommandStatusToString(mysql.COM_INIT_DB): - msg := &mysql.InitDBPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql InitDBPacket") - return nil, err - } - req.Message = msg - - case mysql.CommandStatusToString(mysql.COM_STATISTICS): - msg := &mysql.StatisticsPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StatisticsPacket") - return nil, err - } - req.Message = msg - - case mysql.CommandStatusToString(mysql.COM_DEBUG): - msg := &mysql.DebugPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql DebugPacket") - return nil, err - } - req.Message = msg - - case mysql.CommandStatusToString(mysql.COM_PING): - msg := &mysql.PingPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql PingPacket") - return nil, err - } - req.Message = msg - - case mysql.CommandStatusToString(mysql.COM_CHANGE_USER): - msg := &mysql.ChangeUserPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql ChangeUserPacket") - return nil, err - } - req.Message = msg - - case mysql.CommandStatusToString(mysql.COM_RESET_CONNECTION): - msg := &mysql.ResetConnectionPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql ResetConnectionPacket") - return nil, err - } - req.Message = msg - - // case mysql.CommandStatusToString(mysql.COM_SET_OPTION): // not supported yet - - // query packets - case mysql.CommandStatusToString(mysql.COM_QUERY): - msg := &mysql.QueryPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql QueryPacket") - return nil, err - } - req.Message = msg - - case mysql.CommandStatusToString(mysql.COM_STMT_PREPARE): - msg := &mysql.StmtPreparePacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StmtPreparePacket") - return nil, err - } - req.Message = msg - - case mysql.CommandStatusToString(mysql.COM_STMT_EXECUTE): - msg := &mysql.StmtExecutePacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StmtExecutePacket") - return nil, err - } - req.Message = msg - - // case mysql.CommandStatusToString(mysql.COM_FETCH): // not supported yet - - case mysql.CommandStatusToString(mysql.COM_STMT_CLOSE): - msg := &mysql.StmtClosePacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StmtClosePacket") - return nil, err - } - req.Message = msg - - case mysql.CommandStatusToString(mysql.COM_STMT_RESET): - msg := &mysql.StmtResetPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StmtResetPacket") - return nil, err - } - req.Message = msg - - case mysql.CommandStatusToString(mysql.COM_STMT_SEND_LONG_DATA): - msg := &mysql.StmtSendLongDataPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yaml document into mysql StmtSendLongDataPacket") - return nil, err - } - req.Message = msg - } - requests = append(requests, req) - } - - mockSpec.MySQLRequests = requests - - // Decode the responses - - responses := []mysql.Response{} - for _, v := range yamlSpec.Response { - - resp := mysql.Response{ - PacketBundle: mysql.PacketBundle{ - Header: v.Header, - Meta: v.Meta, - }, - } - - switch v.Header.Type { - // generic response - case mysql.StatusToString(mysql.EOF): - msg := &mysql.EOFPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mysql EOFPacket") - return nil, err - } - resp.Message = msg - - case mysql.StatusToString(mysql.ERR): - msg := &mysql.ERRPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mysql ERRPacket") - return nil, err - } - resp.Message = msg - - case mysql.StatusToString(mysql.OK): - msg := &mysql.OKPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mysql OKPacket") - return nil, err - } - resp.Message = msg - - // connection phase - case mysql.AuthStatusToString(mysql.HandshakeV10): - msg := &mysql.HandshakeV10Packet{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mysql HandshakeV10Packet") - return nil, err - } - resp.Message = msg - - case mysql.AuthStatusToString(mysql.AuthSwitchRequest): - msg := &mysql.AuthSwitchRequestPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mysql AuthSwitchRequestPacket") - return nil, err - } - resp.Message = msg - - case mysql.AuthStatusToString(mysql.AuthMoreData): - msg := &mysql.AuthMoreDataPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mysql AuthMoreDataPacket") - return nil, err - } - resp.Message = msg - - case mysql.AuthStatusToString(mysql.AuthNextFactor): // not supported yet - msg := &mysql.AuthNextFactorPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mysql AuthNextFactorPacket") - return nil, err - } - resp.Message = msg - - // command phase - case mysql.COM_STMT_PREPARE_OK: - msg := &mysql.StmtPrepareOkPacket{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mysql StmtPrepareOkPacket") - return nil, err - } - resp.Message = msg - - case string(mysql.Text): - msg := &mysql.TextResultSet{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mysql TextResultSet") - return nil, err - } - resp.Message = msg - - case string(mysql.Binary): - msg := &mysql.BinaryProtocolResultSet{} - err := v.Message.Decode(msg) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mysql BinaryProtocolResultSet") - return nil, err - } - resp.Message = msg - } - responses = append(responses, resp) - } - - mockSpec.MySQLResponses = responses - - return &mockSpec, nil -} - -func decodeMongoMessage(yamlSpec *models.MongoSpec, logger *zap.Logger) (*models.MockSpec, error) { - mockSpec := models.MockSpec{ - Metadata: yamlSpec.Metadata, - Created: yamlSpec.CreatedAt, - ReqTimestampMock: yamlSpec.ReqTimestampMock, - ResTimestampMock: yamlSpec.ResTimestampMock, - } - - // mongo request - requests := []models.MongoRequest{} - for _, v := range yamlSpec.Requests { - req := models.MongoRequest{ - Header: v.Header, - ReadDelay: v.ReadDelay, - } - // decode the yaml document to mongo request wiremessage - switch v.Header.Opcode { - case wiremessage.OpMsg: - requestMessage := &models.MongoOpMessage{} - err := v.Message.Decode(requestMessage) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpMsg request wiremessage") - return nil, err - } - req.Message = requestMessage - case wiremessage.OpReply: - requestMessage := &models.MongoOpReply{} - err := v.Message.Decode(requestMessage) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpReply request wiremessage") - return nil, err - } - req.Message = requestMessage - case wiremessage.OpQuery: - requestMessage := &models.MongoOpQuery{} - err := v.Message.Decode(requestMessage) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpQuery request wiremessage") - // return fmt.Errorf("failed to decode the mongo OpReply of mock with name: %s. error: %s", doc.Name, err.Error()) - return nil, err - } - req.Message = requestMessage - default: - } - requests = append(requests, req) - } - mockSpec.MongoRequests = requests - - // mongo response - responses := []models.MongoResponse{} - for _, v := range yamlSpec.Response { - resp := models.MongoResponse{ - Header: v.Header, - ReadDelay: v.ReadDelay, - } - // decode the yaml document to mongo response wiremessage - switch v.Header.Opcode { - case wiremessage.OpMsg: - responseMessage := &models.MongoOpMessage{} - err := v.Message.Decode(responseMessage) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpMsg response wiremessage") - // return fmt.Errorf("failed to decode the mongo OpMsg of mock with name: %s. error: %s", doc.Name, err.Error()) - return nil, err - } - resp.Message = responseMessage - case wiremessage.OpReply: - responseMessage := &models.MongoOpReply{} - err := v.Message.Decode(responseMessage) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpMsg response wiremessage") - return nil, err - } - resp.Message = responseMessage - case wiremessage.OpQuery: - responseMessage := &models.MongoOpQuery{} - err := v.Message.Decode(responseMessage) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal yml document into mongo OpMsg response wiremessage") - // return fmt.Errorf("failed to decode the mongo OpMsg of mock with name: %s. error: %s", doc.Name, err.Error()) - return nil, err - } - resp.Message = responseMessage - default: - } - responses = append(responses, resp) - } - mockSpec.MongoResponses = responses - return &mockSpec, nil -} diff --git a/keploy/pkg/platform/yaml/openapidb/db.go b/keploy/pkg/platform/yaml/openapidb/db.go deleted file mode 100644 index a9e2998..0000000 --- a/keploy/pkg/platform/yaml/openapidb/db.go +++ /dev/null @@ -1,152 +0,0 @@ -// Package openapidb provides a openAPI database implementation. -package openapidb - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "io/fs" - "os" - "path/filepath" - "strings" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/yaml" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -type OpenAPIYaml struct { - OpenAPIPath string - logger *zap.Logger -} - -func New(logger *zap.Logger, openAPIPath string) *OpenAPIYaml { - return &OpenAPIYaml{ - OpenAPIPath: openAPIPath, - logger: logger, - } -} -func (ts *OpenAPIYaml) GetTestCasesSchema(ctx context.Context, testSetID string, testPath string) ([]*models.OpenAPI, error) { - var path string - if testPath == "" { - path = filepath.Join(ts.OpenAPIPath, testSetID) - - } else { - path = filepath.Join(testPath, testSetID) - } - - tcs := []*models.OpenAPI{} - TestPath, err := yaml.ValidatePath(path) - if err != nil { - return nil, err - } - _, err = os.Stat(TestPath) - if err != nil { - ts.logger.Debug("no tests are recorded for the session", zap.String("index", testSetID)) - return nil, nil - } - dir, err := yaml.ReadDir(TestPath, fs.ModePerm) - if err != nil { - utils.LogError(ts.logger, err, "failed to open the directory containing yaml testcases", zap.Any("path", TestPath)) - return nil, err - } - files, err := dir.ReadDir(0) - if err != nil { - utils.LogError(ts.logger, err, "failed to read the file names of yaml testcases", zap.Any("path", TestPath)) - return nil, err - } - for _, j := range files { - - name := strings.TrimSuffix(j.Name(), filepath.Ext(j.Name())) - data, err := yaml.ReadFile(ctx, ts.logger, TestPath, name) - if err != nil { - utils.LogError(ts.logger, err, "failed to read the testcase from yaml") - return nil, err - } - - var testCase *models.OpenAPI - err = yamlLib.Unmarshal(data, &testCase) - if err != nil { - utils.LogError(ts.logger, err, "failed to unmarshall YAML data") - return nil, err - } - - tcs = append(tcs, testCase) - } - - return tcs, nil -} - -func (ts *OpenAPIYaml) GetMocksSchemas(ctx context.Context, testSetID string, mockPath string, mockFileName string) ([]*models.OpenAPI, error) { - - var tcsMocks = make([]*models.OpenAPI, 0) - - path := filepath.Join(mockPath, testSetID) - mockPath, err := yaml.ValidatePath(path + "/" + mockFileName + ".yaml") - if err != nil { - return nil, err - } - - if _, err := os.Stat(mockPath); err == nil { - var mockYamls []*models.OpenAPI - data, err := yaml.ReadFile(ctx, ts.logger, path, mockFileName) - if err != nil { - utils.LogError(ts.logger, err, "failed to read the mocks from config yaml", zap.Any("session", filepath.Base(path))) - return nil, err - } - dec := yamlLib.NewDecoder(bytes.NewReader(data)) - for { - var doc *models.OpenAPI - err := dec.Decode(&doc) - if errors.Is(err, io.EOF) { - break - } - if err != nil { - return nil, fmt.Errorf("failed to decode the yaml file documents. error: %v", err.Error()) - } - mockYamls = append(mockYamls, doc) - } - if err != nil { - utils.LogError(ts.logger, err, "failed to decode the config mocks from yaml docs", zap.Any("session", filepath.Base(path))) - return nil, err - } - tcsMocks = mockYamls - } - - return tcsMocks, nil -} -func (ts *OpenAPIYaml) ChangePath(path string) { - - // ts.OpenAPIPath = "./keploy/" - ts.OpenAPIPath = path -} - -func (ts *OpenAPIYaml) WriteSchema(ctx context.Context, logger *zap.Logger, outputPath, name string, openapi models.OpenAPI, isAppend bool) error { - openapiYAML, err := yamlLib.Marshal(openapi) - if err != nil { - return err - } - _, err = os.Stat(outputPath) - if os.IsNotExist(err) { - err = os.MkdirAll(outputPath, os.ModePerm) - if err != nil { - utils.LogError(logger, err, "failed to create directory", zap.String("directory", outputPath)) - return err - } - logger.Info("Directory created", zap.String("directory", outputPath)) - } - - err = yaml.WriteFile(ctx, logger, outputPath, name, openapiYAML, isAppend) - if err != nil { - utils.LogError(logger, err, "failed to write OpenAPI YAML to a file", zap.String("outputPath", outputPath), zap.String("name", name)) - return err - } - - outputFilePath := outputPath + "/" + name + ".yaml" - logger.Info("OpenAPI YAML has been saved to ", zap.String("path", outputFilePath)) - return nil -} diff --git a/keploy/pkg/platform/yaml/reportdb/db.go b/keploy/pkg/platform/yaml/reportdb/db.go deleted file mode 100755 index 83ac851..0000000 --- a/keploy/pkg/platform/yaml/reportdb/db.go +++ /dev/null @@ -1,138 +0,0 @@ -// Package reportdb provides functionality for managing test reports in a database. -package reportdb - -import ( - "bytes" - "context" - "fmt" - "path/filepath" - "sync" - "time" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/yaml" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -type TestReport struct { - tests map[string]map[string][]models.TestResult - m sync.Mutex - Logger *zap.Logger - Path string - Name string -} - -func New(logger *zap.Logger, reportPath string) *TestReport { - return &TestReport{ - tests: make(map[string]map[string][]models.TestResult), - m: sync.Mutex{}, - Logger: logger, - Path: reportPath, - } -} - -func (fe *TestReport) ClearTestCaseResults(_ context.Context, testRunID string, testSetID string) { - fe.m.Lock() - defer fe.m.Unlock() - - fe.tests[testRunID] = make(map[string][]models.TestResult) -} - -func (fe *TestReport) GetAllTestRunIDs(ctx context.Context) ([]string, error) { - return yaml.ReadSessionIndices(ctx, fe.Path, fe.Logger, yaml.ModeDir) -} - -func (fe *TestReport) InsertTestCaseResult(_ context.Context, testRunID string, testSetID string, result *models.TestResult) error { - fe.m.Lock() - defer fe.m.Unlock() - - testSet := fe.tests[testRunID] - if testSet == nil { - testSet = make(map[string][]models.TestResult) - testSet[testSetID] = []models.TestResult{*result} - } else { - testSet[testSetID] = append(testSet[testSetID], *result) - } - fe.tests[testRunID] = testSet - return nil -} - -func (fe *TestReport) GetTestCaseResults(_ context.Context, testRunID string, testSetID string) ([]models.TestResult, error) { - testRun, ok := fe.tests[testRunID] - if !ok { - return []models.TestResult{}, fmt.Errorf("%s found no test results for test report with id: %s", utils.Emoji, testRunID) - } - testSetResults, ok := testRun[testSetID] - if !ok { - return []models.TestResult{}, fmt.Errorf("%s found no test results for test set with id: %s", utils.Emoji, testSetID) - } - return testSetResults, nil -} - -func (fe *TestReport) GetReport(ctx context.Context, testRunID string, testSetID string) (*models.TestReport, error) { - path := filepath.Join(fe.Path, testRunID) - reportName := testSetID + "-report" - _, err := yaml.ValidatePath(filepath.Join(path, reportName+".yaml")) - if err != nil { - return nil, err - } - data, err := yaml.ReadFile(ctx, fe.Logger, path, reportName) - if err != nil { - utils.LogError(fe.Logger, err, "failed to read the test-set report", zap.Any("reportName", reportName), zap.Any("session", filepath.Base(path))) - return nil, err - } - - decoder := yamlLib.NewDecoder(bytes.NewReader(data)) - var doc models.TestReport - err = decoder.Decode(&doc) - if err != nil { - return &models.TestReport{}, fmt.Errorf("%s failed to decode the yaml file documents. error: %v", utils.Emoji, err.Error()) - } - return &doc, nil -} - -func (fe *TestReport) InsertReport(ctx context.Context, testRunID string, testSetID string, testReport *models.TestReport) error { - - reportPath := filepath.Join(fe.Path, testRunID) - - if testReport.Name == "" { - testReport.Name = testSetID + "-report" - } - - testReport.CreatedAt = time.Now().Unix() - - data := []byte{} - d, err := yamlLib.Marshal(&testReport) - if err != nil { - return fmt.Errorf("%s failed to marshal document to yaml. error: %s", utils.Emoji, err.Error()) - } - data = append(data, d...) - data = append([]byte(utils.GetVersionAsComment()), data...) - - err = yaml.WriteFile(ctx, fe.Logger, reportPath, testReport.Name, data, false) - if err != nil { - utils.LogError(fe.Logger, err, "failed to write the report to yaml", zap.Any("session", filepath.Base(reportPath))) - return err - } - return nil -} - -func (fe *TestReport) UpdateReport(ctx context.Context, testRunID string, coverageReport any) error { - reportPath := filepath.Join(fe.Path, testRunID) - - data := []byte{} - d, err := yamlLib.Marshal(&coverageReport) - if err != nil { - return fmt.Errorf("%s failed to marshal document to yaml. error: %s", utils.Emoji, err.Error()) - } - data = append(data, d...) - - err = yaml.WriteFile(ctx, fe.Logger, reportPath, "coverage", data, false) - if err != nil { - utils.LogError(fe.Logger, err, "failed to write the coverage report to yaml", zap.Any("session", filepath.Base(reportPath))) - return err - } - return nil -} diff --git a/keploy/pkg/platform/yaml/testdb/db.go b/keploy/pkg/platform/yaml/testdb/db.go deleted file mode 100644 index e542734..0000000 --- a/keploy/pkg/platform/yaml/testdb/db.go +++ /dev/null @@ -1,262 +0,0 @@ -// Package testdb provides functionality for working with test databases. -package testdb - -import ( - "bytes" - "context" - "fmt" - "io/fs" - "os" - "path/filepath" - "sort" - "strings" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/yaml" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -type TestYaml struct { - TcsPath string - logger *zap.Logger -} - -func New(logger *zap.Logger, tcsPath string) *TestYaml { - return &TestYaml{ - TcsPath: tcsPath, - logger: logger, - } -} - -type tcsInfo struct { - name string - path string -} - -func (ts *TestYaml) InsertTestCase(ctx context.Context, tc *models.TestCase, testSetID string, enableLog bool) error { - tcsInfo, err := ts.upsert(ctx, testSetID, tc) - if err != nil { - return err - } - - if enableLog { - ts.logger.Info("🟠 Keploy has captured test cases for the user's application.", zap.String("path", tcsInfo.path), zap.String("testcase name", tcsInfo.name)) - } - - return nil -} - -func (ts *TestYaml) GetAllTestSetIDs(ctx context.Context) ([]string, error) { - return yaml.ReadSessionIndices(ctx, ts.TcsPath, ts.logger, yaml.ModeDir) -} - -func (ts *TestYaml) GetReportTestSets(ctx context.Context, latestRunID string) ([]string, error) { - if latestRunID == "" { - ts.logger.Warn("No latest run ID provided, returning empty test set IDs") - return []string{}, nil - } - - runReportPath := filepath.Join(ts.TcsPath, "reports", latestRunID) - - return yaml.ReadSessionIndices(ctx, runReportPath, ts.logger, yaml.ModeFile) -} - -func (ts *TestYaml) GetTestCases(ctx context.Context, testSetID string) ([]*models.TestCase, error) { - path := filepath.Join(ts.TcsPath, testSetID, "tests") - - tcs := []*models.TestCase{} - TestPath, err := yaml.ValidatePath(path) - if err != nil { - return nil, err - } - _, err = os.Stat(TestPath) - if err != nil { - ts.logger.Debug("no tests are recorded for the session", zap.String("index", testSetID)) - return nil, nil - } - dir, err := yaml.ReadDir(TestPath, fs.ModePerm) - if err != nil { - utils.LogError(ts.logger, err, "failed to open the directory containing yaml testcases", zap.Any("path", TestPath)) - return nil, err - } - files, err := dir.ReadDir(0) - if err != nil { - utils.LogError(ts.logger, err, "failed to read the file names of yaml testcases", zap.Any("path", TestPath)) - return nil, err - } - for _, j := range files { - if filepath.Ext(j.Name()) != ".yaml" || strings.Contains(j.Name(), "mocks") { - continue - } - - name := strings.TrimSuffix(j.Name(), filepath.Ext(j.Name())) - data, err := yaml.ReadFile(ctx, ts.logger, TestPath, name) - if err != nil { - utils.LogError(ts.logger, err, "failed to read the testcase from yaml") - return nil, err - } - - if len(data) == 0 { - ts.logger.Warn("skipping empty testcase", zap.String("testcase name", name)) - continue - } - - var testCase *yaml.NetworkTrafficDoc - err = yamlLib.Unmarshal(data, &testCase) - if err != nil { - utils.LogError(ts.logger, err, "failed to unmarshall YAML data") - return nil, err - } - - if testCase == nil { - ts.logger.Warn("skipping invalid testCase yaml", zap.String("testcase name", name)) - continue - } - - tc, err := Decode(testCase, ts.logger) - if err != nil { - utils.LogError(ts.logger, err, "failed to decode the testcase") - return nil, err - } - tcs = append(tcs, tc) - } - sort.SliceStable(tcs, func(i, j int) bool { - return tcs[i].HTTPReq.Timestamp.Before(tcs[j].HTTPReq.Timestamp) - }) - return tcs, nil -} - -func (ts *TestYaml) UpdateTestCase(ctx context.Context, tc *models.TestCase, testSetID string, enableLog bool) error { - - tcsInfo, err := ts.upsert(ctx, testSetID, tc) - if err != nil { - return err - } - - if enableLog { - ts.logger.Info("🔄 Keploy has updated the test cases for the user's application.", zap.String("path", tcsInfo.path), zap.String("testcase name", tcsInfo.name)) - } - return nil -} - -func (ts *TestYaml) upsert(ctx context.Context, testSetID string, tc *models.TestCase) (tcsInfo, error) { - tcsPath := filepath.Join(ts.TcsPath, testSetID, "tests") - var tcsName string - if tc.Name == "" { - lastIndx, err := yaml.FindLastIndex(tcsPath, ts.logger) - if err != nil { - return tcsInfo{name: "", path: tcsPath}, err - } - tcsName = fmt.Sprintf("test-%v", lastIndx) - } else { - tcsName = tc.Name - } - yamlTc, err := EncodeTestcase(*tc, ts.logger) - if err != nil { - return tcsInfo{name: tcsName, path: tcsPath}, err - } - yamlTc.Name = tcsName - - var buf bytes.Buffer - encoder := yamlLib.NewEncoder(&buf) - encoder.SetIndent(2) // Set indent to 2 spaces to match the original style - err = encoder.Encode(&yamlTc) - if err != nil { - return tcsInfo{name: tcsName, path: tcsPath}, err - } - data := buf.Bytes() - - _, err = yaml.FileExists(ctx, ts.logger, tcsPath, tcsName) - if err != nil { - utils.LogError(ts.logger, err, "failed to find yaml file", zap.String("path directory", tcsPath), zap.String("yaml", tcsName)) - return tcsInfo{name: tcsName, path: tcsPath}, err - } - - data = append([]byte(utils.GetVersionAsComment()), data...) - - err = yaml.WriteFile(ctx, ts.logger, tcsPath, tcsName, data, false) - if err != nil { - utils.LogError(ts.logger, err, "failed to write testcase yaml file") - return tcsInfo{name: tcsName, path: tcsPath}, err - } - - return tcsInfo{name: tcsName, path: tcsPath}, nil -} - -func (ts *TestYaml) DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error { - path := filepath.Join(ts.TcsPath, testSetID, "tests") - for _, testCaseID := range testCaseIDs { - err := yaml.DeleteFile(ctx, ts.logger, path, testCaseID) - if err != nil { - ts.logger.Error("failed to delete the testcase", zap.String("testcase id", testCaseID), zap.String("testset id", testSetID)) - return err - } - } - return nil -} - -func (ts *TestYaml) DeleteTestSet(ctx context.Context, testSetID string) error { - path := filepath.Join(ts.TcsPath, testSetID) - err := yaml.DeleteDir(ctx, ts.logger, path) - if err != nil { - ts.logger.Error("failed to delete the testset", zap.String("testset id", testSetID)) - return err - } - return nil -} -func (ts *TestYaml) ChangePath(path string) { - - ts.TcsPath = path -} - -func (ts *TestYaml) UpdateAssertions(ctx context.Context, testCaseID string, testSetID string, assertions map[models.AssertionType]interface{}) error { - // get the test case and fill the assertion and update the test case - tcsPath := filepath.Join(ts.TcsPath, testSetID, "tests") - data, err := yaml.ReadFile(ctx, ts.logger, tcsPath, testCaseID) - if err != nil { - utils.LogError(ts.logger, err, "failed to read the testcase from yaml") - return err - } - if len(data) == 0 { - ts.logger.Warn("skipping empty testcase", zap.String("testcase name", testCaseID)) - return nil - } - var testCase *yaml.NetworkTrafficDoc - - err = yamlLib.Unmarshal(data, &testCase) - if err != nil { - utils.LogError(ts.logger, err, "failed to unmarshall YAML data") - return err - } - - if testCase == nil { - ts.logger.Warn("skipping invalid testCase yaml", zap.String("testcase name", testCaseID)) - return nil - } - - tc, err := Decode(testCase, ts.logger) - if err != nil { - utils.LogError(ts.logger, err, "failed to decode the testcase") - return err - } - tc.Assertions = assertions - yamlTc, err := EncodeTestcase(*tc, ts.logger) - if err != nil { - utils.LogError(ts.logger, err, "failed to encode the testcase") - return err - } - yamlTc.Name = testCaseID - data, err = yamlLib.Marshal(&yamlTc) - if err != nil { - utils.LogError(ts.logger, err, "failed to marshall the testcase") - return err - } - err = yaml.WriteFile(ctx, ts.logger, tcsPath, testCaseID, data, false) - if err != nil { - utils.LogError(ts.logger, err, "failed to write testcase yaml file") - return err - } - return nil -} diff --git a/keploy/pkg/platform/yaml/testdb/util.go b/keploy/pkg/platform/yaml/testdb/util.go deleted file mode 100644 index 170f55d..0000000 --- a/keploy/pkg/platform/yaml/testdb/util.go +++ /dev/null @@ -1,384 +0,0 @@ -package testdb - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "reflect" - "regexp" - "strconv" - "strings" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/yaml" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -func EncodeTestcase(tc models.TestCase, logger *zap.Logger) (*yaml.NetworkTrafficDoc, error) { - logger.Debug("Starting test case encoding", - zap.String("kind", string(tc.Kind)), - zap.String("name", tc.Name)) - - doc := &yaml.NetworkTrafficDoc{ - Version: tc.Version, - Kind: tc.Kind, - Name: tc.Name, - } - - var noise map[string][]string - switch tc.Kind { - case models.HTTP: - logger.Debug("Encoding HTTP test case") - doc.Curl = tc.Curl - - // find noisy fields only for HTTP responses - m, err := FlattenHTTPResponse(pkg.ToHTTPHeader(tc.HTTPResp.Header), tc.HTTPResp.Body) - if err != nil { - msg := "error in flattening http response" - utils.LogError(logger, err, msg) - } - noise = tc.Noise - - if tc.Name == "" { - noiseFieldsFound := FindNoisyFields(m, func(_ string, vals []string) bool { - // check if k is date - for _, v := range vals { - if pkg.IsTime(v) { - return true - } - } - // maybe we need to concatenate the values - return pkg.IsTime(strings.Join(vals, ", ")) - }) - - for _, v := range noiseFieldsFound { - noise[v] = []string{} - } - } - - httpSchema := models.HTTPSchema{ - Request: tc.HTTPReq, - Response: tc.HTTPResp, - Created: tc.Created, - // need to check here for type here as well as push in other custom assertions - Assertions: func() map[models.AssertionType]interface{} { - a := map[models.AssertionType]interface{}{} - for k, v := range tc.Assertions { - a[k] = v - } - - if len(noise) > 0 { - a[models.NoiseAssertion] = noise - } - - // Optionally add other custom assertions if needed here - // Example: - // a[models.StatusCode] = tc.HTTPResp.StatusCode - - return a - }(), - } - if tc.Description != "" { - httpSchema.Metadata = map[string]string{ - "description": tc.Description, - } - } - err = doc.Spec.Encode(httpSchema) - if err != nil { - utils.LogError(logger, err, "failed to encode testcase into a yaml doc") - return nil, err - } - case models.GRPC_EXPORT: - logger.Debug("Encoding gRPC test case") - // For gRPC, use the noise directly from the test case - noise = tc.Noise - - // Create a YAML node for the gRPC schema - grpcSpec := models.GrpcSpec{ - GrpcReq: tc.GrpcReq, - GrpcResp: tc.GrpcResp, - Created: tc.Created, - // need to check here for type here as well as push in other custom assertions - Assertions: func() map[models.AssertionType]interface{} { - a := map[models.AssertionType]interface{}{} - if len(noise) > 0 { - a[models.NoiseAssertion] = noise - } - // Optionally add other custom assertions if needed here - // Example: - // a[models.StatusCode] = tc.HTTPResp.StatusCode - - return a - }(), - } - - logger.Debug("gRPC schema created", - zap.Any("request_headers", grpcSpec.GrpcReq.Headers), - zap.Any("response_headers", grpcSpec.GrpcResp.Headers), - zap.Int("request_body_length", len(grpcSpec.GrpcReq.Body.DecodedData)), - zap.Int("response_body_length", len(grpcSpec.GrpcResp.Body.DecodedData))) - - // Create a new YAML node and encode the gRPC schema - var node yamlLib.Node - err := node.Encode(grpcSpec) - if err != nil { - utils.LogError(logger, err, "failed to encode gRPC schema to YAML node") - return nil, err - } - - // Set the node as the spec - doc.Spec = node - logger.Debug("Successfully encoded gRPC test case") - default: - utils.LogError(logger, nil, "failed to marshal the testcase into yaml due to invalid kind of testcase") - return nil, errors.New("type of testcases is invalid") - } - return doc, nil -} - -func FindNoisyFields(m map[string][]string, comparator func(string, []string) bool) []string { - var noise []string - for k, v := range m { - if comparator(k, v) { - noise = append(noise, k) - } - } - return noise -} - -func FlattenHTTPResponse(h http.Header, body string) (map[string][]string, error) { - m := map[string][]string{} - for k, v := range h { - m["header."+k] = []string{strings.Join(v, "")} - } - err := AddHTTPBodyToMap(body, m) - if err != nil { - return m, err - } - return m, nil -} - -func AddHTTPBodyToMap(body string, m map[string][]string) error { - // add body - if json.Valid([]byte(body)) { - var result interface{} - - err := json.Unmarshal([]byte(body), &result) - if err != nil { - return err - } - j := Flatten(result) - for k, v := range j { - nk := "body" - if k != "" { - nk = nk + "." + k - } - m[nk] = v - } - } else { - // add it as raw text - m["body"] = []string{body} - } - return nil -} - -// Flatten takes a map and returns a new one where nested maps are replaced -// by dot-delimited keys. -// examples of valid jsons - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#examples -func Flatten(j interface{}) map[string][]string { - if j == nil { - return map[string][]string{"": {""}} - } - o := make(map[string][]string) - x := reflect.ValueOf(j) - switch x.Kind() { - case reflect.Map: - m, ok := j.(map[string]interface{}) - if !ok { - return map[string][]string{} - } - for k, v := range m { - nm := Flatten(v) - for nk, nv := range nm { - fk := k - if nk != "" { - fk = fk + "." + nk - } - o[fk] = nv - } - } - case reflect.Bool: - o[""] = []string{strconv.FormatBool(x.Bool())} - case reflect.Float64: - o[""] = []string{strconv.FormatFloat(x.Float(), 'E', -1, 64)} - case reflect.String: - o[""] = []string{x.String()} - case reflect.Slice: - child, ok := j.([]interface{}) - if !ok { - return map[string][]string{} - } - for _, av := range child { - nm := Flatten(av) - for nk, nv := range nm { - if ov, exists := o[nk]; exists { - o[nk] = append(ov, nv...) - } else { - o[nk] = nv - } - } - } - default: - fmt.Println(utils.Emoji, "found invalid value in json", j, x.Kind()) - } - return o -} - -func ContainsMatchingURL(urlMethods []string, urlStr string, requestURL string, requestMethod models.Method) (bool, error) { - urlMatched := false - parsedURL, err := url.Parse(requestURL) - if err != nil { - return false, err - } - - // Check for URL path and method - regex, err := regexp.Compile(urlStr) - if err != nil { - return false, err - } - - urlMatch := regex.MatchString(parsedURL.Path) - - if urlMatch && len(urlStr) != 0 { - urlMatched = true - } - - if len(urlMethods) != 0 && urlMatched { - urlMatched = false - for _, method := range urlMethods { - if string(method) == string(requestMethod) { - urlMatched = true - } - } - } - - return urlMatched, nil -} - -func HasBannedHeaders(object map[string]string, bannedHeaders map[string]string) (bool, error) { - for headerName, headerNameValue := range object { - for bannedHeaderName, bannedHeaderValue := range bannedHeaders { - regex, err := regexp.Compile(headerName) - if err != nil { - return false, err - } - - headerNameMatch := regex.MatchString(bannedHeaderName) - regex, err = regexp.Compile(bannedHeaderValue) - if err != nil { - return false, err - } - headerValueMatch := regex.MatchString(headerNameValue) - if headerNameMatch && headerValueMatch { - return true, nil - } - } - } - return false, nil -} - -func Decode(yamlTestcase *yaml.NetworkTrafficDoc, logger *zap.Logger) (*models.TestCase, error) { - tc := &models.TestCase{ - Version: yamlTestcase.Version, - Kind: yamlTestcase.Kind, - Name: yamlTestcase.Name, - Curl: yamlTestcase.Curl, - Noise: make(map[string][]string), - Assertions: make(map[models.AssertionType]interface{}), - } - - switch tc.Kind { - case models.HTTP: - - var httpSpec models.HTTPSchema - if err := yamlTestcase.Spec.Decode(&httpSpec); err != nil { - utils.LogError(logger, err, "failed to decode HTTP JSON spec") - return nil, err - } - tc.Created = httpSpec.Created - tc.HTTPReq = httpSpec.Request - tc.HTTPResp = httpSpec.Response - tc.Description = httpSpec.Metadata["description"] - - // single map-based loop for all assertions - for key, raw := range httpSpec.Assertions { - tc.Assertions[key] = raw - if key == models.NoiseAssertion { - noiseMap, ok := raw.(map[models.AssertionType]interface{}) - if !ok { - logger.Warn("noise assertion not in expected map[AssertionType]interface{}", zap.Any("raw", raw)) - continue - } - for kt, inner := range noiseMap { - field := string(kt) - // initialize slice - tc.Noise[field] = []string{} - arr, ok := inner.([]interface{}) - if !ok { - continue - } - for _, item := range arr { - if s, ok2 := item.(string); ok2 && s != "" { - tc.Noise[field] = append(tc.Noise[field], s) - } - } - } - } - } - - case models.GRPC_EXPORT: - var grpcSpec models.GrpcSpec - if err := yamlTestcase.Spec.Decode(&grpcSpec); err != nil { - utils.LogError(logger, err, "failed to decode gRPC spec") - return nil, err - } - tc.Created = grpcSpec.Created - tc.GrpcReq = grpcSpec.GrpcReq - tc.GrpcResp = grpcSpec.GrpcResp - - for key, raw := range grpcSpec.Assertions { - tc.Assertions[key] = raw - if key == models.NoiseAssertion { - noiseMap, ok := raw.(map[models.AssertionType]interface{}) - if !ok { - logger.Warn("noise assertion not in expected map[AssertionType]interface{}", zap.Any("raw", raw)) - continue - } - for kt, inner := range noiseMap { - field := string(kt) - tc.Noise[field] = []string{} - arr, ok := inner.([]interface{}) - if !ok { - continue - } - for _, item := range arr { - if s, ok2 := item.(string); ok2 && s != "" { - tc.Noise[field] = append(tc.Noise[field], s) - } - } - } - } - } - - default: - utils.LogError(logger, nil, "invalid testcase kind", zap.String("kind", string(tc.Kind))) - return nil, errors.New("invalid testcase kind") - } - - return tc, nil -} diff --git a/keploy/pkg/platform/yaml/utils.go b/keploy/pkg/platform/yaml/utils.go deleted file mode 100755 index cab8bc8..0000000 --- a/keploy/pkg/platform/yaml/utils.go +++ /dev/null @@ -1,403 +0,0 @@ -// Package yaml provides utility functions for working with YAML files. -package yaml - -import ( - "context" - "errors" - "fmt" - "io" - "io/fs" - "net/http" - "os" - "path/filepath" - - "strconv" - "strings" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -func CompareHeaders(h1 http.Header, h2 http.Header, res *[]models.HeaderResult, noise map[string]string) bool { - if res == nil { - return false - } - match := true - _, isHeaderNoisy := noise["header"] - for k, v := range h1 { - _, isNoisy := noise[k] - isNoisy = isNoisy || isHeaderNoisy - val, ok := h2[k] - if !isNoisy { - if !ok { - if checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: false, - Expected: models.Header{ - Key: k, - Value: v, - }, - Actual: models.Header{ - Key: k, - Value: nil, - }, - }) - } - - match = false - continue - } - if len(v) != len(val) { - if checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: false, - Expected: models.Header{ - Key: k, - Value: v, - }, - Actual: models.Header{ - Key: k, - Value: val, - }, - }) - } - match = false - continue - } - for i, e := range v { - if val[i] != e { - if checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: false, - Expected: models.Header{ - Key: k, - Value: v, - }, - Actual: models.Header{ - Key: k, - Value: val, - }, - }) - } - match = false - continue - } - } - } - if checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: true, - Expected: models.Header{ - Key: k, - Value: v, - }, - Actual: models.Header{ - Key: k, - Value: val, - }, - }) - } - } - for k, v := range h2 { - _, isNoisy := noise[k] - isNoisy = isNoisy || isHeaderNoisy - val, ok := h1[k] - if isNoisy && checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: true, - Expected: models.Header{ - Key: k, - Value: val, - }, - Actual: models.Header{ - Key: k, - Value: v, - }, - }) - continue - } - if !ok { - if checkKey(res, k) { - *res = append(*res, models.HeaderResult{ - Normal: false, - Expected: models.Header{ - Key: k, - Value: nil, - }, - Actual: models.Header{ - Key: k, - Value: v, - }, - }) - } - - match = false - } - } - return match -} - -func checkKey(res *[]models.HeaderResult, key string) bool { - for _, v := range *res { - if key == v.Expected.Key { - return false - } - } - return true -} - -func Contains(elems []string, v string) bool { - for _, s := range elems { - if v == s { - return true - } - } - return false -} - -func NewSessionIndex(path string, Logger *zap.Logger) (string, error) { - indx := 0 - dir, err := ReadDir(path, fs.FileMode(os.O_RDONLY)) - if err != nil { - Logger.Debug("creating a folder for the keploy generated testcases", zap.Error(err)) - return fmt.Sprintf("%s%v", models.TestSetPattern, indx), nil - } - - files, err := dir.ReadDir(0) - if err != nil { - return "", err - } - - for _, v := range files { - // fmt.Println("name for the file", v.Name()) - fileName := filepath.Base(v.Name()) - fileNamePackets := strings.Split(fileName, "-") - if len(fileNamePackets) == 3 { - fileIndx, err := strconv.Atoi(fileNamePackets[2]) - if err != nil { - Logger.Debug("failed to convert the index string to integer", zap.Error(err)) - continue - } - if indx < fileIndx+1 { - indx = fileIndx + 1 - } - } - } - return fmt.Sprintf("%s%v", models.TestSetPattern, indx), nil -} - -func ValidatePath(path string) (string, error) { - // Validate the input to prevent directory traversal attack - if strings.Contains(path, "..") { - return "", errors.New("invalid path: contains '..' indicating directory traversal") - } - return path, nil -} - -// FindLastIndex returns the index for the new yaml file by reading the yaml file names in the given path directory -func FindLastIndex(path string, _ *zap.Logger) (int, error) { - dir, err := ReadDir(path, fs.FileMode(os.O_RDONLY)) - if err != nil { - return 1, nil - } - files, err := dir.ReadDir(0) - if err != nil { - return 1, nil - } - - lastIndex := 0 - for _, v := range files { - if v.Name() == "mocks.yaml" || v.Name() == "config.yaml" { - continue - } - fileName := filepath.Base(v.Name()) - fileNameWithoutExt := fileName[:len(fileName)-len(filepath.Ext(fileName))] - fileNameParts := strings.Split(fileNameWithoutExt, "-") - if len(fileNameParts) != 2 || (fileNameParts[0] != "test" && fileNameParts[0] != "report") { - continue - } - indxStr := fileNameParts[1] - indx, err := strconv.Atoi(indxStr) - if err != nil { - continue - } - if indx > lastIndex { - lastIndex = indx - } - } - lastIndex++ - - return lastIndex, nil -} - -func ReadDir(path string, fileMode fs.FileMode) (*os.File, error) { - dir, err := os.OpenFile(path, os.O_RDONLY, fileMode) - if err != nil { - return nil, err - } - return dir, nil -} - -// CreateDir to create a directory if it doesn't exist -func CreateDir(path string, logger *zap.Logger) error { - if _, err := os.Stat(path); os.IsNotExist(err) { - err := os.MkdirAll(path, os.ModePerm) - if err != nil { - utils.LogError(logger, err, "failed to create directory", zap.String("directory", path)) - return err - } - } - return nil -} - -// ReadYAMLFile to read and parse YAML file -func ReadYAMLFile(ctx context.Context, logger *zap.Logger, filePath string, fileName string, v interface{}, extType bool) error { - if !extType { - filePath = filepath.Join(filePath, fileName+".yml") - - } else { - filePath = filepath.Join(filePath, fileName+".yaml") - } - file, err := os.Open(filePath) - if err != nil { - return fmt.Errorf("failed to read the file: %v", err) - } - - defer func() { - if err := file.Close(); err != nil { - utils.LogError(logger, err, "failed to close file", zap.String("file", filePath)) - } - }() - - cr := &ctxReader{ - ctx: ctx, - r: file, - } - - configData, err := io.ReadAll(cr) - if err != nil { - if err == ctx.Err() { - return err // Ignore context cancellation error - } - return fmt.Errorf("failed to read the file: %v", err) - } - - err = yaml.Unmarshal(configData, v) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal YAML", zap.String("file", filePath)) - return err - } - return nil -} - -// CopyFile copies a single file from src to dst -func CopyFile(src, dst string, rename bool, logger *zap.Logger) error { - srcFile, err := os.Open(src) - - if err != nil { - return err - } - defer func() { - if err := srcFile.Close(); err != nil { - utils.LogError(logger, err, "failed to close file", zap.String("file", srcFile.Name())) - } - }() - // If rename is true, generate a new name for the destination file - if rename { - dst = generateSchemaName(dst) - } - dstFile, err := os.Create(dst) - if err != nil { - return err - } - defer func() { - if err := dstFile.Close(); err != nil { - utils.LogError(logger, err, "failed to close file", zap.String("file", dstFile.Name())) - } - }() - - _, err = io.Copy(dstFile, srcFile) - if err != nil { - return err - } - - // Ensure the copied file has the same permissions as the original file - srcInfo, err := os.Stat(src) - if err != nil { - return err - } - err = os.Chmod(dst, srcInfo.Mode()) - if err != nil { - return err - } - - return nil -} - -// CopyDir recursively copies a directory tree, attempting to preserve permissions -func CopyDir(srcDir, destDir string, rename bool, logger *zap.Logger) error { - // Ensure the destination directory exists - if _, err := os.Stat(destDir); os.IsNotExist(err) { - err := os.MkdirAll(destDir, os.ModePerm) - if err != nil { - return err - } - } - entries, err := os.ReadDir(srcDir) - if err != nil { - return err - } - - for _, entry := range entries { - srcPath := filepath.Join(srcDir, entry.Name()) - destPath := filepath.Join(destDir, entry.Name()) - - info, err := entry.Info() - if err != nil { - return err - } - - if info.IsDir() { - err = os.MkdirAll(destPath, info.Mode()) - if err != nil { - return err - } - err = CopyDir(srcPath, destPath, rename, logger) - if err != nil { - return err - } - } else { - err = CopyFile(srcPath, destPath, rename, logger) - if err != nil { - return err - } - } - } - return nil -} - -// generateSchemaName generates a new schema name -func generateSchemaName(src string) string { - dir := filepath.Dir(src) - newName := "schema" + filepath.Ext(src) - return filepath.Join(dir, newName) -} - -func FileExists(_ context.Context, logger *zap.Logger, path string, fileName string) (bool, error) { - yamlPath, err := ValidatePath(filepath.Join(path, fileName+".yaml")) - if err != nil { - utils.LogError(logger, err, "failed to validate the yaml file path", zap.String("path directory", path), zap.String("yaml", fileName)) - return false, err - } - if _, err := os.Stat(yamlPath); err != nil { - if os.IsNotExist(err) { - return false, nil - } - utils.LogError(logger, err, "failed to check if the yaml file exists", zap.String("path directory", path), zap.String("yaml", fileName)) - return false, err - } - - return true, nil -} diff --git a/keploy/pkg/platform/yaml/yaml.go b/keploy/pkg/platform/yaml/yaml.go deleted file mode 100755 index b1dc314..0000000 --- a/keploy/pkg/platform/yaml/yaml.go +++ /dev/null @@ -1,232 +0,0 @@ -package yaml - -import ( - "context" - "fmt" - "io" - "io/fs" - "os" - "path/filepath" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -type IndexMode string - -const ( - ModeDir IndexMode = "dir" - ModeFile IndexMode = "file" -) - -// Ignored folders -const ( - FolderReports = "reports" - FolderTestReports = "testReports" - FolderSchema = "schema" -) - -// NetworkTrafficDoc stores the request-response data of a network call (ingress or egress) -type NetworkTrafficDoc struct { - Version models.Version `json:"version" yaml:"version"` - Kind models.Kind `json:"kind" yaml:"kind"` - Name string `json:"name" yaml:"name"` - Spec yamlLib.Node `json:"spec" yaml:"spec"` - Curl string `json:"curl" yaml:"curl,omitempty"` - ConnectionID string `json:"connectionId" yaml:"connectionId,omitempty"` -} - -// ctxReader wraps an io.Reader with a context for cancellation support -type ctxReader struct { - ctx context.Context - r io.Reader -} - -func (cr *ctxReader) Read(p []byte) (n int, err error) { - select { - case <-cr.ctx.Done(): - return 0, cr.ctx.Err() - default: - return cr.r.Read(p) - } -} - -// ctxWriter wraps an io.Writer with a context for cancellation support -type ctxWriter struct { - ctx context.Context - writer io.Writer -} - -func (cw *ctxWriter) Write(p []byte) (n int, err error) { - for len(p) > 0 { - var written int - written, err = cw.writer.Write(p) - n += written - if err != nil { - return n, err - } - p = p[written:] - } - return n, nil -} - -func WriteFile(ctx context.Context, logger *zap.Logger, path, fileName string, docData []byte, isAppend bool) error { - isFileEmpty, err := CreateYamlFile(ctx, logger, path, fileName) - if err != nil { - utils.LogError(logger, err, "failed to create a yaml file", zap.String("path directory", path), zap.String("yaml", fileName)) - return err - } - flag := os.O_WRONLY | os.O_TRUNC - if isAppend { - data := []byte("---\n") - if isFileEmpty { - data = []byte{} - } - docData = append(data, docData...) - flag = os.O_WRONLY | os.O_APPEND - } - yamlPath := filepath.Join(path, fileName+".yaml") - file, err := os.OpenFile(yamlPath, flag, fs.ModePerm) - if err != nil { - utils.LogError(logger, err, "failed to open file for writing", zap.String("file", yamlPath)) - return err - } - defer func() { - if err := file.Close(); err != nil { - utils.LogError(logger, err, "failed to close file", zap.String("file", yamlPath)) - } - }() - - cw := &ctxWriter{ - ctx: ctx, - writer: file, - } - - _, err = cw.Write(docData) - if err != nil { - if err == ctx.Err() { - return nil // Ignore context cancellation error - } - utils.LogError(logger, err, "failed to write the yaml document", zap.String("yaml file name", fileName)) - return err - } - return nil -} - -func ReadFile(ctx context.Context, logger *zap.Logger, path, name string) ([]byte, error) { - filePath := filepath.Join(path, name+".yaml") - file, err := os.Open(filePath) - if err != nil { - return nil, fmt.Errorf("failed to read the file: %v", err) - } - - defer func() { - if err := file.Close(); err != nil { - utils.LogError(logger, err, "failed to close file", zap.String("file", filePath)) - } - }() - - cr := &ctxReader{ - ctx: ctx, - r: file, - } - - data, err := io.ReadAll(cr) - if err != nil { - if err == ctx.Err() { - return nil, err // Ignore context cancellation error - } - return nil, fmt.Errorf("failed to read the file: %v", err) - } - return data, nil -} - -func CreateYamlFile(ctx context.Context, Logger *zap.Logger, path string, fileName string) (bool, error) { - yamlPath, err := ValidatePath(filepath.Join(path, fileName+".yaml")) - if err != nil { - utils.LogError(Logger, err, "failed to validate the yaml file path", zap.String("path directory", path), zap.String("yaml", fileName)) - return false, err - } - - if _, err := os.Stat(yamlPath); err != nil { - if ctx.Err() == nil || ctx.Err() == context.Canceled { - err = os.MkdirAll(filepath.Join(path), 0777) - if err != nil { - utils.LogError(Logger, err, "failed to create a directory for the yaml file", zap.String("path directory", path), zap.String("yaml", fileName)) - return false, err - } - file, err := os.OpenFile(yamlPath, os.O_CREATE, 0777) // Set file permissions to 777 - if err != nil { - utils.LogError(Logger, err, "failed to create a yaml file", zap.String("path directory", path), zap.String("yaml", fileName)) - return false, err - } - err = file.Close() - if err != nil { - utils.LogError(Logger, err, "failed to close the yaml file", zap.String("path directory", path), zap.String("yaml", fileName)) - return false, err - } - return true, nil - } - return false, err - } - return false, nil -} - -func ReadSessionIndices(ctx context.Context, path string, logger *zap.Logger, mode IndexMode) ([]string, error) { - var indices []string - - dir, err := ReadDir(path, fs.FileMode(os.O_RDONLY)) - if err != nil { - logger.Debug("creating a folder for the keploy generated testcases", zap.Error(err)) - return indices, nil - } - - files, err := dir.ReadDir(0) - if err != nil { - return indices, err - } - - for _, v := range files { - // Skip ignored folders - if v.Name() == FolderReports || v.Name() == FolderTestReports || v.Name() == FolderSchema { - continue - } - - name := v.Name() - - switch mode { - case ModeDir: - if v.IsDir() { - indices = append(indices, name) - } - case ModeFile: - if ext := filepath.Ext(name); ext != "" { - name = name[:len(name)-len(ext)] - } - indices = append(indices, name) - } - } - - return indices, nil -} - -func DeleteFile(_ context.Context, logger *zap.Logger, path, name string) error { - filePath := filepath.Join(path, name+".yaml") - err := os.Remove(filePath) - if err != nil { - utils.LogError(logger, err, "failed to delete the file", zap.String("file", filePath)) - return fmt.Errorf("failed to delete the file: %v", err) - } - return nil -} - -func DeleteDir(_ context.Context, logger *zap.Logger, path string) error { - err := os.RemoveAll(path) - if err != nil { - utils.LogError(logger, err, "failed to delete the directory", zap.String("path", path)) - return fmt.Errorf("failed to delete the directory: %v", err) - } - return nil -} diff --git a/keploy/pkg/service/README.md b/keploy/pkg/service/README.md deleted file mode 100755 index 26a04d6..0000000 --- a/keploy/pkg/service/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Service Package Documentation - -This package focuses on encapsulating core business operations in a -maintainable and reusable manner. This package calls the `platform` interface methods -to store the output of the service methods. \ No newline at end of file diff --git a/keploy/pkg/service/contract/consumer/consumer.go b/keploy/pkg/service/contract/consumer/consumer.go deleted file mode 100644 index 21cb307..0000000 --- a/keploy/pkg/service/contract/consumer/consumer.go +++ /dev/null @@ -1,298 +0,0 @@ -// Package consumer is a package for consumer driven contract testing -package consumer - -import ( - "fmt" - "os" - - "github.com/fatih/color" - "github.com/olekukonko/tablewriter" - "go.keploy.io/server/v2/config" - schemaMatcher "go.keploy.io/server/v2/pkg/matcher/schema" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type consumer struct { - logger *zap.Logger - config *config.Config -} - -// New creates a new instance of the consumer service -func New(logger *zap.Logger, config *config.Config) Service { - return &consumer{ - logger: logger, - config: config, - } -} -func (s *consumer) ValidateSchema(testsMapping map[string]map[string]*models.OpenAPI, mocksMapping []models.MockMapping) error { - - // Retrieve mocks and calculate scores for each service - scores, err := s.getMockScores(testsMapping, mocksMapping) - if err != nil { - return err - } - // Compare the scores and generate a summary - summary, err := s.ValidateMockAgainstTests(scores, testsMapping) - if err != nil { - return err - } - // Print the summary - generateSummaryTable(summary) - - return nil -} - -// getMockScores retrieves mocks and compares them with test cases, calculating scores. -func (s *consumer) getMockScores(testsMapping map[string]map[string]*models.OpenAPI, mocksMapping []models.MockMapping) (map[string]map[string]map[string]models.SchemaInfo, error) { - - // Initialize a map to store the scores for each service, mock set, and mock. - scores := make(map[string]map[string]map[string]models.SchemaInfo) - - for _, mapping := range mocksMapping { - // Initialize the service entry in the scores map if it doesn't already exist. - if scores[mapping.Service] == nil { - scores[mapping.Service] = make(map[string]map[string]models.SchemaInfo) - } - // Initialize the mock set entry if it hasn't been initialized yet. - if scores[mapping.Service][mapping.TestSetID] == nil { - scores[mapping.Service][mapping.TestSetID] = make(map[string]models.SchemaInfo) - } - - // Compare the mocks with test cases and calculate scores. - // The result is stored in the scores map under the respective service and mock set ID. - s.scoresForMocks(mapping.Mocks, scores[mapping.Service][mapping.TestSetID], testsMapping, mapping.TestSetID) - - } - - // Return the calculated scores. - return scores, nil -} - -// scoresForMocks compares mocks to test cases and assigns scores. -func (s *consumer) scoresForMocks(mocks []*models.OpenAPI, mockSet map[string]models.SchemaInfo, testsMapping map[string]map[string]*models.OpenAPI, mockSetID string) { - // Ensure mockSet is initialized before assigning - if mockSet == nil { - mockSet = make(map[string]models.SchemaInfo) - } - // Loop through each mock in the provided list of mocks. - for _, mock := range mocks { - // Initialize the mock's score to 0.0 and store the mock's data in the mockSet map. - // 'mockSet' is a map where the key is the mock title and the value is the SchemaInfo structure containing score and data. - mockSet[mock.Info.Title] = models.SchemaInfo{ - Score: 0.0, - Data: *mock, // Store the mock data here. - } - - // Loop through each test set (testSetID) in the testsMapping. - // testsMapping maps test set IDs to test case titles. - for testSetID, tests := range testsMapping { - // Loop through each test in the current test set. - for _, test := range tests { - // Call 'match2' to compare the mock with the current test. - // This function returns a candidateScore (how well the mock matches the test) and a pass boolean. - candidateScore, pass, err := schemaMatcher.Match(*mock, *test, testSetID, mockSetID, s.logger, models.IdentifyMode) - // Handle any errors encountered during the comparison process. - if err != nil { - // Log the error and continue with the next iteration, skipping the current comparison. - utils.LogError(s.logger, err, "Error in matching the two models") - continue - } - - // If the mock passed the comparison and the candidate score is greater than the current score: - if pass && candidateScore > mockSet[mock.Info.Title].Score { - // Update the mock's score and store the test case information in the mockSet. - // This keeps track of the best matching test case for the current mock. - mockSet[mock.Info.Title] = models.SchemaInfo{ - Service: "", // Optional: could store service info if needed. - TestSetID: testSetID, // Store the test set ID that provided the highest score. - Name: test.Info.Title, // Store the test case name (title). - Score: candidateScore, // Update the score with the highest candidate score. - Data: *mock, // Store the mock data. - } - } - } - } - } -} - -// ValidateMockAgainstTests compares mock results with test cases and generates a summary report -func (s *consumer) ValidateMockAgainstTests(scores map[string]map[string]map[string]models.SchemaInfo, testsMapping map[string]map[string]*models.OpenAPI) (models.Summary, error) { - var summary models.Summary - - // Defining color schemes for success, failure, and other statuses - notMatchedColor := color.New(color.FgHiRed).SprintFunc() - missedColor := color.New(color.FgHiYellow).SprintFunc() - successColor := color.New(color.FgHiGreen).SprintFunc() - serviceColor := color.New(color.FgHiBlue).SprintFunc() - - // Loop through the services in the scores map - // Each "service" represents a consumer service being validated - for service, mockSetIDs := range scores { - // Create a new service summary for each service - var serviceSummary models.ServiceSummary - serviceSummary.TestSets = make(map[string]models.Status) - serviceSummary.Service = service // Store the service name - - // Output the beginning of the validation for the current service - fmt.Println("==========================================") - fmt.Print("Starting Validation for Consumer Service: ") - fmt.Print(serviceColor(service)) // Print service name in blue - fmt.Println(" ....") - fmt.Println("==========================================") - - // Iterate over the mockSetIDs for each service (mock set contains multiple mocks) - for mockSetID, mockTest := range mockSetIDs { - if _, ok := serviceSummary.TestSets[mockSetID]; !ok { - // Initialize the Status struct if it doesn't already exist for the mockSetID - serviceSummary.TestSets[mockSetID] = models.Status{} - } - - // Iterate over each mock in the mockTest map - for _, mockInfo := range mockTest { - - // Print validation information only if the score is not zero - if mockInfo.Score != 0.0 { - fmt.Print("Validating '") - fmt.Print(serviceColor(service)) // Print the service name in blue - fmt.Printf("': (%s)/%s for (%s)/%s\n", mockSetID, mockInfo.Data.Info.Title, mockInfo.TestSetID, mockInfo.Name) - - } - - // Case 1: If the score is 1.0, the mock passed the validation - if mockInfo.Score == 1.0 { - // Retrieve the Status struct for the given mockSetID - status := serviceSummary.TestSets[mockSetID] - - // Append the passed mock title - status.Passed = append(status.Passed, mockInfo.Data.Info.Title) - - // Reassign the updated status back to the map - serviceSummary.TestSets[mockSetID] = status - serviceSummary.PassedCount++ // Increment the passed count - - // Print a success message in green - fmt.Print("Contract check ") - fmt.Print(successColor("passed")) // Print "passed" in green - fmt.Printf(" for the test '%s' / mock '%s'\n", mockInfo.Name, mockInfo.Data.Info.Title) - fmt.Println("--------------------------------------------------------------------") - // Case 2: If the score is between 0 and 1.0, the mock failed the validation - } else if mockInfo.Score > 0.0 { - // Retrieve the Status struct for the given mockSetID - status := serviceSummary.TestSets[mockSetID] - - // Append the failed mock title - status.Failed = append(status.Failed, mockInfo.Data.Info.Title) - - // Reassign the updated status back to the map - serviceSummary.TestSets[mockSetID] = status - serviceSummary.FailedCount++ // Increment the failed count - - // Print a failure message in red - fmt.Print("Contract check") - fmt.Print(notMatchedColor(" failed")) // Print "failed" in red - fmt.Printf(" for the test '%s' / mock '%s'\n", mockInfo.Name, mockInfo.Data.Info.Title) - - fmt.Println() - - // Additional information: Print consumer and current service comparison - fmt.Printf(" Current %s || Consumer %s\n", serviceColor(s.config.Contract.Mappings.Self), serviceColor(service)) - - // Perform comparison between the mock and test case again - _, _, err := schemaMatcher.Match(mockInfo.Data, *testsMapping[mockInfo.TestSetID][mockInfo.Name], mockInfo.TestSetID, mockSetID, s.logger, models.CompareMode) - if err != nil { - // If an error occurs during comparison, return it - utils.LogError(s.logger, err, "Error in matching the two models") - return models.Summary{}, err - } - - // Case 3: If the score is 0.0, there was no matching test case found - } else if mockInfo.Score == 0.0 { - // Retrieve the Status struct for the given mockSetID - status := serviceSummary.TestSets[mockSetID] - - // Append the missed mock title - status.Missed = append(status.Missed, mockInfo.Data.Info.Title) - - // Reassign the updated status back to the map - serviceSummary.TestSets[mockSetID] = status - serviceSummary.MissedCount++ // Increment the missed count - - // Print a "missed" message in yellow - fmt.Println(missedColor(fmt.Sprintf("No ideal test case found for the (%s)/'%s'", mockSetID, mockInfo.Data.Info.Title))) - fmt.Println("--------------------------------------------------------------------") - } - } - } - - // Append the completed service summary to the overall summary - summary.ServicesSummary = append(summary.ServicesSummary, serviceSummary) - } - - // Return the overall summary containing details of all services validated - return summary, nil -} - -func generateSummaryTable(summary models.Summary) { - notMatchedColor := color.New(color.FgHiRed).SprintFunc() - missedColor := color.New(color.FgHiYellow).SprintFunc() - successColor := color.New(color.FgHiGreen).SprintFunc() - serviceColor := color.New(color.FgHiBlue).SprintFunc() - - // Create a new tablewriter to format the output as a table - table := tablewriter.NewWriter(os.Stdout) - - // Set table headers - table.SetHeader([]string{"Consumer Service", "Consumer Service Test-set", "Mock-name", "Failed", "Passed", "Missed"}) - table.SetAlignment(tablewriter.ALIGN_CENTER) - table.SetAutoMergeCells(true) - // Loop through each service summary to populate the table - for idx, serviceSummary := range summary.ServicesSummary { - failedCount := serviceSummary.FailedCount - passedCount := serviceSummary.PassedCount - missedCount := serviceSummary.MissedCount - table.Append([]string{ - serviceColor(serviceSummary.Service), - "", - "", - notMatchedColor(failedCount), - successColor(passedCount), - missedColor(missedCount), - }) - for testSet, status := range serviceSummary.TestSets { - for _, mock := range status.Failed { - // Add rows for failed mocks - table.Append([]string{ - "", - testSet, - notMatchedColor(mock), - "", - "", "", - }) - } - - for _, mock := range status.Missed { - table.Append([]string{ - "", - testSet, - missedColor(mock), "", - "", "", - }) - } - table.Append([]string{ - "", - "", - "", "", - "", "", - }) - } - // Add a blank line (or border) after each service - if idx < len(summary.ServicesSummary)-1 { - table.Append([]string{"----------------", "----------------", "----------------", "----------------", "----------------", "----------------"}) - } - } - - // Render the table to stdout - table.Render() -} diff --git a/keploy/pkg/service/contract/consumer/service.go b/keploy/pkg/service/contract/consumer/service.go deleted file mode 100644 index 4453b0c..0000000 --- a/keploy/pkg/service/contract/consumer/service.go +++ /dev/null @@ -1,8 +0,0 @@ -package consumer - -import "go.keploy.io/server/v2/pkg/models" - -// Service defines the consumer service interface -type Service interface { - ValidateSchema(testsMapping map[string]map[string]*models.OpenAPI, mocksMapping []models.MockMapping) error -} diff --git a/keploy/pkg/service/contract/contract.go b/keploy/pkg/service/contract/contract.go deleted file mode 100644 index bec2603..0000000 --- a/keploy/pkg/service/contract/contract.go +++ /dev/null @@ -1,685 +0,0 @@ -// Package contract provides the implementation of the contract service -package contract - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/fatih/color" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/yaml" - "go.keploy.io/server/v2/pkg/service/contract/consumer" - "go.keploy.io/server/v2/pkg/service/contract/provider" - "go.keploy.io/server/v2/utils" - - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -// contractService implements the Service interface -type contract struct { - logger *zap.Logger - testDB TestDB - mockDB MockDB - openAPIDB OpenAPIDB - config *config.Config - consumer consumer.Service - provider provider.Service -} - -func New(logger *zap.Logger, testDB TestDB, mockDB MockDB, openAPIDB OpenAPIDB, config *config.Config) Service { - return &contract{ - logger: logger, - testDB: testDB, - mockDB: mockDB, - openAPIDB: openAPIDB, - config: config, - consumer: consumer.New(logger, config), - provider: provider.New(logger, config), - } -} - -func (s *contract) HTTPDocToOpenAPI(logger *zap.Logger, custom models.HTTPDoc) (models.OpenAPI, error) { - - var err error - // Convert response body to an object - var responseBodyObject map[string]interface{} - if custom.Spec.Response.Body != "" { - err := json.Unmarshal([]byte(custom.Spec.Response.Body), &responseBodyObject) - if err != nil { - return models.OpenAPI{}, err - } - - } - - // Get the type of each value in the response body object - responseTypes := ExtractVariableTypes(responseBodyObject) - - // Convert request body to an object - var requestBodyObject map[string]interface{} - if custom.Spec.Request.Body != "" { - err := json.Unmarshal([]byte(custom.Spec.Request.Body), &requestBodyObject) - if err != nil { - return models.OpenAPI{}, err - } - - } - - // Get the type of each value in the request body object - requestTypes := ExtractVariableTypes(requestBodyObject) - - // Generate response by status code - byCode := GenerateResponse(Response{ - Code: custom.Spec.Response.StatusCode, - Message: custom.Spec.Response.StatusMessage, - Types: responseTypes, - Body: responseBodyObject, - }) - - // Add parameters to the request - parameters := GenerateHeader(custom.Spec.Request.Header) - - // Extract In Path parameters - identifiers := ExtractIdentifiers(custom.Spec.Request.URL) - // Generate Dummy Names for the identifiers - dummyNames := GenerateDummyNamesForIdentifiers(identifiers) - // Add In Path parameters to the parameters - if len(identifiers) > 0 { - parameters = AppendInParameters(parameters, dummyNames, "path") - } - // Extract Query parameters - queryParams, err := ExtractQueryParams(custom.Spec.Request.URL) - if err != nil { - utils.LogError(logger, err, "failed to extract query parameters") - return models.OpenAPI{}, err - } - // Add Query parameters to the parameters - if len(queryParams) > 0 { - parameters = AppendInParameters(parameters, queryParams, "query") - } - // Generate Operation ID - operationID := generateUniqueID() - if operationID == "" { - err := fmt.Errorf("failed to generate unique ID") - utils.LogError(logger, err, "failed to generate unique ID") - return models.OpenAPI{}, err - } - // Determine if the request method is GET or POST - var pathItem models.PathItem - switch custom.Spec.Request.Method { - case "GET": - pathItem = models.PathItem{ - Get: &models.Operation{ - Summary: "Auto-generated operation", - Description: "Auto-generated from custom format", - OperationID: operationID, - Parameters: parameters, - Responses: byCode, - }, - } - case "POST": - pathItem = models.PathItem{ - Post: &models.Operation{ - Summary: "Auto-generated operation", - Description: "Auto-generated from custom format", - Parameters: parameters, - OperationID: operationID, - RequestBody: &models.RequestBody{ - Content: map[string]models.MediaType{ - "application/json": { - Schema: models.Schema{ - Type: "object", - Properties: requestTypes, - }, - Example: requestBodyObject, - }, - }, - }, - Responses: byCode, - }, - } - case "PUT": - pathItem.Put = &models.Operation{ - Summary: "Update an employee by ID", - Description: "Update an employee by ID", - Parameters: parameters, - OperationID: operationID, - RequestBody: &models.RequestBody{ - Content: map[string]models.MediaType{ - "application/json": { - Schema: models.Schema{ - Type: "object", - Properties: requestTypes, - }, - Example: (requestBodyObject), - }, - }, - }, - Responses: byCode, - } - case "PATCH": - pathItem.Patch = &models.Operation{ - Summary: "Auto-generated operation", - Description: "Auto-generated from custom format", - Parameters: parameters, - OperationID: operationID, - RequestBody: &models.RequestBody{ - Content: map[string]models.MediaType{ - "application/json": { - Schema: models.Schema{ - Type: "object", - Properties: requestTypes, - }, - Example: (requestBodyObject), - }, - }, - }, - Responses: byCode, - } - case "DELETE": - pathItem.Delete = &models.Operation{ - Summary: "Delete an employee by ID", - Description: "Delete an employee by ID", - OperationID: operationID, - Parameters: parameters, - Responses: byCode, - } - default: - utils.LogError(logger, err, "Unsupported Method") - return models.OpenAPI{}, err - } - - // Extract the URL path - parsedURL, hostName := ExtractURLPath(custom.Spec.Request.URL) - if parsedURL == "" { - utils.LogError(logger, err, "failed to extract URL path") - return models.OpenAPI{}, err - } - // Replace numeric identifiers in the path with dummy names (if exists) - parsedURL = ReplacePathIdentifiers(parsedURL, dummyNames) - //If it's mock so there is no hostname so put it temp - if hostName == "" { - hostName = "temp" - } - // Convert to OpenAPI format - openapi := models.OpenAPI{ - OpenAPI: "3.0.0", - Info: models.Info{ - Title: custom.Name, - Version: custom.Version, - Description: custom.Kind, - }, - Servers: []map[string]string{ - { - "url": hostName, - }, - }, - - Paths: map[string]models.PathItem{ - parsedURL: pathItem, - }, - Components: map[string]interface{}{}, - } - - return openapi, nil -} - -// GenerateMocksSchemas generates mock schemas based on the provided services and mappings. -func (s *contract) GenerateMocksSchemas(ctx context.Context, services []string, mappings map[string][]string) error { - - // Retrieve all test set IDs from the test database. - testSetsIDs, err := s.testDB.GetAllTestSetIDs(ctx) - if err != nil { - // Log and return error if test set IDs retrieval fails. - utils.LogError(s.logger, err, "failed to get test set IDs") - return err - } - - // If specific services are provided, ensure they exist in the mappings. - if len(services) != 0 { - for _, service := range services { - if _, exists := mappings[service]; !exists { - // Warn if the service is not found in the services mapping. - s.logger.Warn("Service not found in services mapping, no contract generation", zap.String("service", service)) - } - } - } - - // Loop through each test set ID to process the HTTP mocks. - for _, testSetID := range testSetsIDs { - // Retrieve HTTP mocks for the test set from the mock database. - httpMocks, err := s.mockDB.GetHTTPMocks(ctx, testSetID, s.config.Path, "mocks") - if err != nil { - // Log and return error if HTTP mock retrieval fails. - utils.LogError(s.logger, err, "failed to get HTTP mocks", zap.String("testSetID", testSetID)) - return err - } - - // Track duplicate mocks to avoid generating the same schema multiple times. - var duplicateServices []string - - // Loop through each HTTP mock to generate OpenAPI documentation. - for _, mock := range httpMocks { - var isAppend bool // Flag to indicate whether to append to existing mocks. - - // Loop through services and their mappings to find the relevant mock. - for service, serviceMappings := range mappings { - // If a specific service list is provided, skip services not in the list. - if !yaml.Contains(services, service) && len(services) != 0 { - continue - } - - var mappingFound bool // Flag to track if the mapping for the service is found. - - // Check if the mock's URL matches any service mapping. - for _, mapping := range serviceMappings { - if mapping == mock.Spec.Request.URL { - - // Check for duplicate services to append the mock to the existing mocks.yaml before. - if yaml.Contains(duplicateServices, service) { - isAppend = true - } else { - duplicateServices = append(duplicateServices, service) - } - - // Convert the HTTP mock to OpenAPI documentation. - openapi, err := s.HTTPDocToOpenAPI(s.logger, *mock) - if err != nil { - utils.LogError(s.logger, err, "failed to convert the yaml file to openapi") - return fmt.Errorf("failed to convert the yaml file to openapi") - } - - // Validate the generated OpenAPI schema. - err = validateSchema(openapi) - if err != nil { - utils.LogError(s.logger, err, "failed to validate the OpenAPI schema") - return err - } - - // Write the OpenAPI document to the specified directory. - err = s.openAPIDB.WriteSchema(ctx, s.logger, filepath.Join(s.config.Path, "schema", "mocks", service, testSetID), "mocks", openapi, isAppend) - if err != nil { - utils.LogError(s.logger, err, "failed to write the OpenAPI schema") - return err - } - - mappingFound = true // Mark the mapping as found. - break - } - } - - // Break the outer loop if the relevant mapping is found. - if mappingFound { - break - } - } - } - } - - return nil // Return nil if the function completes successfully. -} - -func (s *contract) GenerateTestsSchemas(ctx context.Context, selectedTests []string) error { - testSetsIDs, err := s.testDB.GetAllTestSetIDs(ctx) - if err != nil { - utils.LogError(s.logger, err, "failed to get test set IDs") - return err - } - - for _, testSetID := range testSetsIDs { - if !yaml.Contains(selectedTests, testSetID) && len(selectedTests) != 0 { - continue - } - - testCases, err := s.testDB.GetTestCases(ctx, testSetID) - if err != nil { - utils.LogError(s.logger, err, "failed to get test cases", zap.String("testSetID", testSetID)) - return err - } - for _, tc := range testCases { - var httpSpec models.HTTPDoc - httpSpec.Kind = string(tc.Kind) - httpSpec.Name = tc.Name - httpSpec.Spec.Request = tc.HTTPReq - httpSpec.Spec.Response = tc.HTTPResp - httpSpec.Version = string(tc.Version) - - openapi, err := s.HTTPDocToOpenAPI(s.logger, httpSpec) - if err != nil { - utils.LogError(s.logger, err, "failed to convert the yaml file to openapi") - return fmt.Errorf("failed to convert the yaml file to openapi") - } - // Validate the OpenAPI document - err = validateSchema(openapi) - if err != nil { - utils.LogError(s.logger, err, "failed to validate the OpenAPI schema") - return err - } - // Save it using the OpenAPIDB - err = s.openAPIDB.WriteSchema(ctx, s.logger, filepath.Join(s.config.Path, "schema", "tests", testSetID), tc.Name, openapi, false) - if err != nil { - utils.LogError(s.logger, err, "failed to write the OpenAPI schema") - return err - } - - } - - } - return nil -} - -func (s *contract) Generate(ctx context.Context, checkConfig bool) error { - if checkConfig && checkConfigFile(s.config.Contract.Mappings.ServicesMapping) != nil { - utils.LogError(s.logger, fmt.Errorf("unable to find services mappings in the config file"), "Unable to find services mappings in the config file") - return fmt.Errorf("unable to find services mappings in the config file") - } - - mappings := s.config.Contract.Mappings.ServicesMapping - serviceColor := color.New(color.FgYellow).SprintFunc() - fmt.Println(serviceColor("==========================================")) - fmt.Println(serviceColor(fmt.Sprintf("Starting Generating OpenAPI Schemas for Current Service: %s ....", s.config.Contract.Mappings.Self))) - fmt.Println(serviceColor("==========================================")) - - err := s.GenerateTestsSchemas(ctx, s.config.Contract.Tests) - if err != nil { - utils.LogError(s.logger, err, "failed to generate tests schemas") - return err - } - err = s.GenerateMocksSchemas(ctx, s.config.Contract.Services, mappings) - if err != nil { - utils.LogError(s.logger, err, "failed to generate mocks schemas") - return err - } - if err := saveServiceMappings(s.config.Contract.Mappings, filepath.Join(s.config.Path, "schema")); err != nil { - utils.LogError(s.logger, err, "failed to save service mappings") - return err - } - - return nil -} - -func (s *contract) DownloadTests(_ string) error { - - targetPath := "./Download/Tests" - if err := yaml.CreateDir(targetPath, s.logger); err != nil { - utils.LogError(s.logger, err, "failed to create directory", zap.String("directory", targetPath)) - return err - } - - cprFolder, err := filepath.Abs("../VirtualCPR") - if err != nil { - utils.LogError(s.logger, err, "failed to get absolute path", zap.String("path", "../VirtualCPR")) - return err - } - - // Loop through the services in the mappings in the config file - for service := range s.config.Contract.Mappings.ServicesMapping { - // Fetch the tests of those services from virtual cpr - testsPath := filepath.Join(cprFolder, service, "keploy", "schema", "tests") - // Copy this dir to the target path - serviceFolder := filepath.Join(targetPath, service) - if err := yaml.CopyDir(testsPath, serviceFolder, false, s.logger); err != nil { - utils.LogError(s.logger, err, "failed to copy directory", zap.String("directory", testsPath)) - return err - } - s.logger.Info("Service's tests (contracts) downloaded", zap.String("service", service)) - // Copy the Keploy version (HTTP) tests - keployTestsPath := filepath.Join(cprFolder, service, "keploy") - testEntries, err := os.ReadDir(keployTestsPath) - if err != nil { - utils.LogError(s.logger, err, "failed to read directory", zap.String("directory", keployTestsPath)) - return err - } - for _, testSetID := range testEntries { - if !testSetID.IsDir() || !strings.Contains(testSetID.Name(), "test") { - continue - } - // Copy the directory to the target path - if err := yaml.CopyDir(filepath.Join(keployTestsPath, testSetID.Name(), "tests"), filepath.Join(serviceFolder, "schema", testSetID.Name()), false, s.logger); err != nil { - utils.LogError(s.logger, err, "failed to copy directory", zap.String("directory", filepath.Join(keployTestsPath, testSetID.Name(), "tests"))) - return err - } - s.logger.Info("Service's HTTP tests downloaded", zap.String("service", service), zap.String("tests", testSetID.Name())) - - } - - } - return nil -} - -// DownloadMocks downloads the mocks for a specific service and stores them in the target path. -// The mocks are extracted from the VirtualCPR folder and saved in the "Download/Mocks" directory. -func (s *contract) DownloadMocks(ctx context.Context, _ string) error { - // Set the target path where the downloaded mocks will be stored - targetPath := "./Download/Mocks" - - // Create the target directory if it doesn't already exist - if err := yaml.CreateDir(targetPath, s.logger); err != nil { - utils.LogError(s.logger, err, "failed to create directory", zap.String("directory", targetPath)) - return err - } - - // Get the absolute path to the VirtualCPR folder - cprFolder, err := filepath.Abs("../VirtualCPR") - if err != nil { - utils.LogError(s.logger, err, "failed to get absolute path", zap.String("path", "../VirtualCPR")) - return err - } - - // Read all entries (files and directories) in the VirtualCPR folder - entries, err := os.ReadDir(cprFolder) - if err != nil { - utils.LogError(s.logger, err, "failed to read directory", zap.String("directory", cprFolder)) - return err - } - - // Loop through each entry in the VirtualCPR folder - for _, entry := range entries { - // If the entry is not a directory, skip it - if !entry.IsDir() { - continue - } - - // Extract the name of the current service (the one being processed) - var self = s.config.Contract.Mappings.Self - var schemaConfigMappings config.Mappings - - // Construct the path to the schema configuration file for the current service - configFilePath := filepath.Join(cprFolder, entry.Name(), "keploy", "schema") - - // Read the schema configuration YAML schemaConfigMappings - if err := yaml.ReadYAMLFile(ctx, s.logger, configFilePath, "serviceMappings", &schemaConfigMappings, true); err != nil { - utils.LogError(s.logger, err, "failed to read the schema configuration file", zap.String("file", "serviceMappings")) - return err - } - - // Check if the current service exists in the service mapping from the schema configuration - serviceFound := false - if _, exists := schemaConfigMappings.ServicesMapping[self]; exists { - serviceFound = true - } - - // If the service is not found in the mapping, skip to the next service - if !serviceFound { - continue - } - - // Create a directory for the current service inside the target path - serviceFolder := filepath.Join(targetPath, schemaConfigMappings.Self) - if err := yaml.CreateDir(serviceFolder, s.logger); err != nil { - utils.LogError(s.logger, err, "failed to create directory", zap.String("directory", serviceFolder)) - return err - } - - // Construct the path to the mock files for the current service - mocksSourcePath := filepath.Join(cprFolder, entry.Name(), "keploy", "schema", "mocks", self) - - // Log and display the start of the mock download process for the service - serviceColor := color.New(color.FgYellow).SprintFunc() - fmt.Println(serviceColor("==========================================")) - fmt.Println(serviceColor(fmt.Sprintf("Starting Downloading Mocks for Service: %s ....", entry.Name()))) - fmt.Println(serviceColor("==========================================")) - - // Copy the mock files from the source directory to the target directory - if err := yaml.CopyDir(mocksSourcePath, serviceFolder, true, s.logger); err != nil { - utils.LogError(s.logger, err, "failed to copy directory", zap.String("directory", mocksSourcePath)) - return err - } - - // Log that the mocks for the service have been downloaded - s.logger.Info("Service's schema mocks contracts downloaded", zap.String("service", entry.Name()), zap.String("mocks", mocksSourcePath)) - - // Move the Keploy version-specific mocks - // Read the contents of the "keploy" folder to find the mock folders - mocksFolders, err := os.ReadDir(filepath.Join(cprFolder, entry.Name(), "keploy")) - if err != nil { - utils.LogError(s.logger, err, "failed to read directory", zap.String("directory", cprFolder), zap.Error(err)) - return err - } - - // Loop through each folder inside the "keploy" folder - for _, mockFolder := range mocksFolders { - // If the folder is not a directory or does not contain "test" in its name, skip it - if !mockFolder.IsDir() || !strings.Contains(mockFolder.Name(), "test") { - continue - } - - // Retrieve the HTTP mocks from the mock database for the current test set - httpMocks, err := s.mockDB.GetHTTPMocks(ctx, mockFolder.Name(), filepath.Join(cprFolder, entry.Name(), "keploy"), "mocks") - if err != nil { - utils.LogError(s.logger, err, "failed to get HTTP mocks", zap.String("testSetID", mockFolder.Name()), zap.Error(err)) - return err - } - - // Filter the HTTP mocks based on the service URL mappings - var filteredMocks []*models.HTTPDoc - for _, mock := range httpMocks { - for _, service := range schemaConfigMappings.ServicesMapping[self] { - // Add the mock to the filtered list if the service URL matches - if service == mock.Spec.Request.URL { - filteredMocks = append(filteredMocks, mock) - break - } - } - } - - // Write the filtered mocks to the appropriate folder - var initialMock = true - for _, mock := range filteredMocks { - // Marshal the mock data to YAML format - mockYAML, err := yamlLib.Marshal(mock) - if err != nil { - utils.LogError(s.logger, err, "failed to marshal mock data", zap.Any("mock", mock)) - return err - } - - // Write the mock YAML file to the target service folder - err = yaml.WriteFile(ctx, s.logger, filepath.Join(serviceFolder, mockFolder.Name()), "mocks", mockYAML, !initialMock) - if err != nil { - utils.LogError(s.logger, err, "failed to write mock file", zap.String("service", entry.Name()), zap.String("testSetID", mockFolder.Name())) - return err - } - - // Ensure only the first file is marked as the initial mock - if initialMock { - initialMock = false - } - } - - // Log that the HTTP mocks for the service have been downloaded - s.logger.Info("Service's HTTP mocks contracts downloaded", zap.String("service", entry.Name()), zap.String("mocks", mockFolder.Name())) - } - } - - // Return nil to indicate success - return nil -} - -func (s *contract) Download(ctx context.Context, checkConfig bool) error { - - if checkConfig && checkConfigFile(s.config.Contract.Mappings.ServicesMapping) != nil { - utils.LogError(s.logger, fmt.Errorf("unable to find services mappings in the config file"), "Unable to find services mappings in the config file") - return fmt.Errorf("unable to find services mappings in the config file") - } - path := s.config.Contract.Path - // Validate the path - path, err := yaml.ValidatePath(path) - if err != nil { - utils.LogError(s.logger, err, "failed to validate path") - return fmt.Errorf("error in validating path") - } - driven := s.config.Contract.Driven - if driven == models.ProviderMode.String() { - err = s.DownloadTests(path) - if err != nil { - utils.LogError(s.logger, err, "failed to download tests") - return err - } - - } else if driven == models.ConsumerMode.String() { - err = s.DownloadMocks(ctx, path) - if err != nil { - utils.LogError(s.logger, err, "failed to download mocks") - return err - } - - } - - return nil -} - -func (s *contract) Validate(ctx context.Context) error { - if s.config.Contract.Mappings.Self == "" { - utils.LogError(s.logger, fmt.Errorf("self service is not defined in the config file"), "Self service is not defined in the config file") - return fmt.Errorf("self service is not defined in the config file") - } - - if s.config.Contract.Generate { - err := s.Generate(ctx, false) - if err != nil { - utils.LogError(s.logger, err, "failed to generate contract") - return err - } - } - if s.config.Contract.Download { - err := s.Download(ctx, false) - if err != nil { - utils.LogError(s.logger, err, "failed to download contract") - return err - } - } - if s.config.Contract.Driven == models.ConsumerMode.String() { - - // Retrieve tests from the schema folder - testsMapping, err := s.GetAllTestsSchema(ctx) - if err != nil { - utils.LogError(s.logger, err, "failed to get tests from schema") - return err - } - // Retrieve mocks of each service from the download folder - mocksSchemasDownloaded, err := s.GetAllDownloadedMocksSchemas(ctx) - if err != nil { - utils.LogError(s.logger, err, "failed to get downloaded mocks schemas") - return err - } - err = s.consumer.ValidateSchema(testsMapping, mocksSchemasDownloaded) - if err != nil { - utils.LogError(s.logger, err, "failed to validate schema") - return err - } - } else if s.config.Contract.Driven == models.ProviderMode.String() { - err := s.provider.ValidateSchema(ctx) - if err != nil { - utils.LogError(s.logger, err, "failed to validate schema") - return err - } - fmt.Println("Provider driven validation is not implemented yet") - } - - return nil -} diff --git a/keploy/pkg/service/contract/provider/provider.go b/keploy/pkg/service/contract/provider/provider.go deleted file mode 100644 index 3f0c0e8..0000000 --- a/keploy/pkg/service/contract/provider/provider.go +++ /dev/null @@ -1,127 +0,0 @@ -// Package provider is a package for provider driven contract testing -package provider - -import ( - "context" - - "go.keploy.io/server/v2/config" - "go.uber.org/zap" -) - -const IDENTIFYMODE = 0 -const COMPAREMODE = 1 - -type provider struct { - logger *zap.Logger - - config *config.Config -} - -// New creates a new instance of the consumer service -func New(logger *zap.Logger, config *config.Config) Service { - return &provider{ - logger: logger, - - config: config, - } -} - -func (s *provider) ValidateSchema(_ context.Context) error { - // downloadTestsFolder := filepath.Join("./Download", "Tests") - // entries, err := os.ReadDir(downloadTestsFolder) - // if err != nil { - // s.logger.Error("Failed to read directory", zap.String("directory", downloadTestsFolder), zap.Error(err)) - // return err - // } - // mocksFolder := filepath.Join("./keploy", "schema", "mocks") - // s.openAPIDB.ChangeTcPath(mocksFolder) - // services, err := os.ReadDir(mocksFolder) - // if err != nil { - // s.logger.Error("Failed to read directory", zap.String("directory", mocksFolder), zap.Error(err)) - // return err - // } - // var mocksMapping map[string]map[string]map[string]*models.OpenAPI = make(map[string]map[string]map[string]*models.OpenAPI) - // var mocks []*models.OpenAPI - // for _, service := range services { - // if !service.IsDir() { - // continue - // } - // testSetIDs, err := os.ReadDir(filepath.Join(mocksFolder, service.Name())) - // if err != nil { - // s.logger.Error("Failed to read directory", zap.String("directory", service.Name()), zap.Error(err)) - // return err - // } - // mocksMapping[service.Name()] = make(map[string]map[string]*models.OpenAPI) - // for _, testSetID := range testSetIDs { - - // if !testSetID.IsDir() { - // continue - // } - // mocks, err = s.openAPIDB.GetMocksSchemas(ctx, filepath.Join(service.Name(), testSetID.Name()), mocksFolder, "mocks") - // if err != nil { - // s.logger.Error("Failed to get HTTP mocks", zap.String("testSetID", testSetID.Name()), zap.Error(err)) - // return err - // } - // mocksMapping[service.Name()][testSetID.Name()] = make(map[string]*models.OpenAPI) - // for _, mock := range mocks { - // mocksMapping[service.Name()][testSetID.Name()][mock.Info.Title] = mock - // } - // } - // } - // var scores map[string]map[string]map[string]models.SchemaInfo = make(map[string]map[string]map[string]models.SchemaInfo) - // for _, entry := range entries { - // if entry.IsDir() { - // serviceFolder := filepath.Join(downloadTestsFolder, entry.Name()) - // testSetIDs, err := os.ReadDir(filepath.Join(serviceFolder, "schema", "tests")) - // if err != nil { - // s.logger.Error("Failed to read directory", zap.String("directory", serviceFolder), zap.Error(err)) - // return err - // } - // scores[entry.Name()] = make(map[string]map[string]models.SchemaInfo) - // for _, testSetID := range testSetIDs { - // if !testSetID.IsDir() { - // continue - // } - // tests, err := s.openAPIDB.GetTestCasesSchema(ctx, testSetID.Name(), filepath.Join(serviceFolder, "schema", "tests")) - // if err != nil { - // s.logger.Error("Failed to get test cases", zap.String("testSetID", testSetID.Name()), zap.Error(err)) - // return err - // } - // scores[entry.Name()][testSetID.Name()] = make(map[string]models.SchemaInfo) - // for _, test := range tests { - // // Take each test and get the ideal mock for it - // scores[entry.Name()][testSetID.Name()][test.Info.Title] = models.SchemaInfo{Score: 0.0, Data: *test} - // for providerService, mockSetIDs := range mocksMapping { - // for mockSetID, mocks := range mockSetIDs { - // for _, mock := range mocks { - // candidateScore, pass, err := match2(*test, *mock, mockSetID, testSetID.Name(), s.logger, IDENTIFYMODE) - // if err != nil { - // s.logger.Error("Error in matching the two models", zap.Error(err)) - // fmt.Println("test-set-id: ", testSetID.Name(), ", mock-set-id: ", mockSetID) - // return err - // } - // if pass && candidateScore > 0 { - // if candidateScore > scores[entry.Name()][testSetID.Name()][test.Info.Title].Score { - // idealMock := models.SchemaInfo{ - // Service: providerService, - // TestSetID: mockSetID, - // Name: mock.Info.Title, - // Score: candidateScore, - // Data: *test, - // } - // scores[entry.Name()][testSetID.Name()][test.Info.Title] = idealMock - // } - // } - - // } - // } - // } - - // } - // } - // } - // } - // // TODO: Validate the scores and generate a summary - - return nil -} diff --git a/keploy/pkg/service/contract/provider/service.go b/keploy/pkg/service/contract/provider/service.go deleted file mode 100644 index 9d95383..0000000 --- a/keploy/pkg/service/contract/provider/service.go +++ /dev/null @@ -1,10 +0,0 @@ -package provider - -import ( - "context" -) - -// Service defines the provider service interface -type Service interface { - ValidateSchema(ctx context.Context) error -} diff --git a/keploy/pkg/service/contract/schema.go b/keploy/pkg/service/contract/schema.go deleted file mode 100644 index 9696045..0000000 --- a/keploy/pkg/service/contract/schema.go +++ /dev/null @@ -1,111 +0,0 @@ -package contract - -import ( - "context" - "fmt" - "os" - "path/filepath" - - "github.com/getkin/kin-openapi/openapi3" - "go.keploy.io/server/v2/pkg/models" - yamlLib "gopkg.in/yaml.v3" -) - -func validateSchema(openapi models.OpenAPI) error { - openapiYAML, err := yamlLib.Marshal(openapi) - if err != nil { - return err - } - // Validate using kin-openapi - loader := openapi3.NewLoader() - doc, err := loader.LoadFromData(openapiYAML) - if err != nil { - return err - - } - // Validate the OpenAPI document - if err := doc.Validate(context.Background()); err != nil { - return err - } - - return nil -} - -// GetAllTestsSchema retrieves all the tests schema from the schema folder. -func (s *contract) GetAllTestsSchema(ctx context.Context) (map[string]map[string]*models.OpenAPI, error) { - testsFolder := filepath.Join("./keploy", "schema", "tests") - - s.openAPIDB.ChangePath(testsFolder) - testSetIDs, err := os.ReadDir(testsFolder) - if err != nil { - return nil, fmt.Errorf("failed to read tests directory: %w", err) - } - - testsMapping := make(map[string]map[string]*models.OpenAPI) - for _, testSetID := range testSetIDs { - if !testSetID.IsDir() { - continue - } - - tests, err := s.openAPIDB.GetTestCasesSchema(ctx, testSetID.Name(), "") - if err != nil { - return nil, fmt.Errorf("failed to get test cases for testSetID %s: %w", testSetID.Name(), err) - } - - testsMapping[testSetID.Name()] = make(map[string]*models.OpenAPI) - for _, test := range tests { - testsMapping[testSetID.Name()][test.Info.Title] = test - } - } - - return testsMapping, nil -} - -func (s *contract) GetAllDownloadedMocksSchemas(ctx context.Context) ([]models.MockMapping, error) { - // ***** TODO: See what part can be moved to DB layer ***** - downloadMocksFolder := filepath.Join("./Download", "Mocks") - - // Read the contents of the Download Mocks folder to get all service directories. - entries, err := os.ReadDir(downloadMocksFolder) - if err != nil { - // If there's an error reading the directory, return it. - return nil, fmt.Errorf("failed to read mocks directory: %w", err) - } - var mocksSchemasMapping []models.MockMapping - // Loop over each entry in the Download Mocks folder. - for _, entry := range entries { - // Check if the entry is a directory (indicating a service folder). - if entry.IsDir() { - // Define the path to the service folder (e.g., Download/Mocks/service-name). - serviceFolder := filepath.Join(downloadMocksFolder, entry.Name()) - - // Read the contents of the service folder to get mock set IDs (subdirectories). - mockSetIDs, err := os.ReadDir(serviceFolder) - if err != nil { - // If there's an error reading the service folder, return it. - return nil, fmt.Errorf("failed to read service directory %s: %w", serviceFolder, err) - } - - // Loop over each mock set ID in the service folder. - for _, mockSetID := range mockSetIDs { - // Ensure the mock set ID is a directory. - if !mockSetID.IsDir() { - continue - } - - // Retrieve the mocks for the given mock set ID (e.g., schema files in the folder). - mocks, err := s.openAPIDB.GetMocksSchemas(ctx, mockSetID.Name(), serviceFolder, "schema") - if err != nil { - // If there's an error retrieving mocks, return it. - return nil, fmt.Errorf("failed to get HTTP mocks for mockSetID %s: %w", mockSetID.Name(), err) - } - mocksSchemasMapping = append(mocksSchemasMapping, models.MockMapping{ - Service: entry.Name(), - TestSetID: mockSetID.Name(), - Mocks: mocks, - }) - } - } - } - return mocksSchemasMapping, nil -} diff --git a/keploy/pkg/service/contract/service.go b/keploy/pkg/service/contract/service.go deleted file mode 100644 index 5fcbad5..0000000 --- a/keploy/pkg/service/contract/service.go +++ /dev/null @@ -1,30 +0,0 @@ -package contract - -import ( - "context" - - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -// Service defines the contract service interface -type Service interface { - Generate(ctx context.Context, checkConfig bool) error - Download(ctx context.Context, checkConfig bool) error - Validate(ctx context.Context) error -} - -type TestDB interface { - GetAllTestSetIDs(ctx context.Context) ([]string, error) - GetTestCases(ctx context.Context, testSetID string) ([]*models.TestCase, error) - ChangePath(path string) -} -type MockDB interface { - GetHTTPMocks(ctx context.Context, testSetID string, mockPath string, mockFileName string) ([]*models.HTTPDoc, error) -} -type OpenAPIDB interface { - GetTestCasesSchema(ctx context.Context, testSetID string, testPath string) ([]*models.OpenAPI, error) - GetMocksSchemas(ctx context.Context, testSetID string, mockPath string, mockFileName string) ([]*models.OpenAPI, error) - WriteSchema(ctx context.Context, logger *zap.Logger, outputPath, name string, openapi models.OpenAPI, isAppend bool) error - ChangePath(path string) -} diff --git a/keploy/pkg/service/contract/utils.go b/keploy/pkg/service/contract/utils.go deleted file mode 100644 index 413f50c..0000000 --- a/keploy/pkg/service/contract/utils.go +++ /dev/null @@ -1,261 +0,0 @@ -package contract - -import ( - "context" - "crypto/rand" - "encoding/hex" - "fmt" - "net/url" - "strconv" - "strings" - "time" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/yaml" - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -type Response struct { - Code int - Message string - Types map[string]map[string]interface{} - Body map[string]interface{} -} - -// ExtractVariableTypes returns the type of each variable in the object. -func ExtractVariableTypes(obj map[string]interface{}) map[string]map[string]interface{} { - types := make(map[string]map[string]interface{}, len(obj)) - - getType := func(value interface{}) string { - switch value.(type) { - case float64: - return "number" - case int, int32, int64: - return "integer" - case string: - return "string" - case bool: - return "boolean" - case map[string]interface{}: - return "object" - case []interface{}: - return "array" - default: - return "string" - } - } - - for key, value := range obj { - valueType := getType(value) - responseType := map[string]interface{}{ - "type": valueType, - } - - switch valueType { - case "object": - responseType["properties"] = ExtractVariableTypes(value.(map[string]interface{})) - case "array": - arrayItems := value.([]interface{}) - arrayType := "string" // Default to string if array is empty - - if len(arrayItems) > 0 { - firstElement := arrayItems[0] - arrayType = getType(firstElement) - if arrayType == "object" { - responseType["items"] = map[string]interface{}{ - "type": arrayType, - "properties": ExtractVariableTypes(firstElement.(map[string]interface{})), - } - types[key] = responseType - continue - } - } - responseType["items"] = map[string]interface{}{ - "type": arrayType, - } - } - - types[key] = responseType - } - - return types -} - -func GenerateResponse(response Response) map[string]models.ResponseItem { - byCode := map[string]models.ResponseItem{ - fmt.Sprintf("%d", response.Code): { - Description: response.Message, - Content: map[string]models.MediaType{ - "application/json": { - Schema: models.Schema{ - Type: "object", - Properties: response.Types, - }, - Example: (response.Body), - }, - }, - }, - } - return byCode -} - -func ExtractURLPath(URL string) (string, string) { - parsedURL, err := url.Parse(URL) - - if err != nil { - return "", "" - } - return parsedURL.Path, parsedURL.Host -} - -func GenerateHeader(header map[string]string) []models.Parameter { - var parameters []models.Parameter - for key, value := range header { - parameters = append(parameters, models.Parameter{ - Name: key, - In: "header", - Required: true, - Schema: models.ParamSchema{Type: "string"}, - Example: value, - }) - } - return parameters -} - -// isNumeric checks if a string is a valid numeric value (integer or float). -func isNumeric(s string) bool { - if _, err := strconv.Atoi(s); err == nil { - return true - } - if _, err := strconv.ParseFloat(s, 64); err == nil { - return true - } - return false -} - -// ExtractIdentifiers extracts numeric identifiers (integers or floats) from the path. -func ExtractIdentifiers(path string) []string { - segments := strings.Split(path, "/") - segments2 := strings.Split(segments[len(segments)-1], "?") - segments = append(segments[:len(segments)-1], segments2[0]) - var identifiers []string - for _, segment := range segments { - if segment != "" { - // Check if the segment is numeric (integer or float) - if isNumeric(segment) { - identifiers = append(identifiers, segment) - } - } - } - - return identifiers -} - -// ExtractQueryParams extracts the query parameters and their names from the URL. -func ExtractQueryParams(rawURL string) (map[string]string, error) { - parsedURL, err := url.Parse(rawURL) - if err != nil { - return nil, err - } - queryParams := parsedURL.Query() - params := make(map[string]string) - for key, values := range queryParams { - if len(values) > 0 { - // Take the first value if multiple are present - params[key] = values[0] - } - } - return params, nil -} - -// GenerateDummyNamesForIdentifiers generates dummy names for the path identifiers. -func GenerateDummyNamesForIdentifiers(identifiers []string) map[string]string { - dummyNames := make(map[string]string) - for i, id := range identifiers { - dummyName := fmt.Sprintf("param%d", i+1) - dummyNames[dummyName] = id - } - return dummyNames -} -func AppendInParameters(parameters []models.Parameter, inParameters map[string]string, paramType string) []models.Parameter { - - for key, value := range inParameters { - parameters = append(parameters, models.Parameter{ - Name: key, - In: paramType, - Required: true, - Schema: models.ParamSchema{Type: "string"}, - Example: value, - }) - } - - return parameters -} - -// ReplacePathIdentifiers replaces numeric identifiers in the path with their corresponding dummy names. -func ReplacePathIdentifiers(path string, dummyNames map[string]string) string { - segments := strings.Split(path, "/") - var replacedPath []string - for _, segment := range segments { - if segment != "" { - // Check if the segment is numeric (integer or float) - if isNumeric(segment) { - dummyName := "" - for key, value := range dummyNames { - if value == segment { - // i want to put '{' and '}' around the key - dummyName = "{" + key + "}" - break - } - } - if dummyName != "" { - replacedPath = append(replacedPath, dummyName) - } else { - replacedPath = append(replacedPath, segment) - } - } else { - replacedPath = append(replacedPath, segment) - } - } - } - finalPath := strings.Join(replacedPath, "/") - // Add slash at the beginning of the path - finalPath = "/" + finalPath - return finalPath -} - -func generateUniqueID() string { - b := make([]byte, 16) - _, err := rand.Read(b) - if err != nil { - // handle error - return "" - } - return hex.EncodeToString(b) + "-" + time.Now().Format("20060102150405") -} - -func checkConfigFile(servicesMapping map[string][]string) error { - // Check if the size of servicesMapping is less than 1 - if len(servicesMapping) < 1 { - return fmt.Errorf("services mapping must contain at least 1 services") - } - return nil -} - -func saveServiceMappings(servicesMapping config.Mappings, filePath string) error { - // Marshal the services mapping to YAML - servicesMappingYAML, err := yamlLib.Marshal(servicesMapping) - if err != nil { - return fmt.Errorf("failed to marshal services mapping: %w", err) - } - - // Write the services mapping to the specified file path - err = yaml.WriteFile(context.Background(), zap.NewNop(), filePath, "serviceMappings", servicesMappingYAML, false) - if err != nil { - return fmt.Errorf("failed to write services mapping to file: %w", err) - } - - return nil -} diff --git a/keploy/pkg/service/export/export.go b/keploy/pkg/service/export/export.go deleted file mode 100644 index 00928c3..0000000 --- a/keploy/pkg/service/export/export.go +++ /dev/null @@ -1,269 +0,0 @@ -// Package export contains the implementation of the export service which exports the curl commands from the YAML testcases to a Postman collection. -package export - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/fs" - "net/url" - "os" - "path/filepath" - "sort" - "strings" - - "github.com/google/uuid" - yamlLib "gopkg.in/yaml.v3" - - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/yaml" - postmanimport "go.keploy.io/server/v2/pkg/service/import" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func ConvertKeployHTTPToPostmanCollection(logger *zap.Logger, http *models.HTTPSchema) map[string]interface{} { - var request postmanimport.PostmanRequest - var response postmanimport.PostmanResponse - - // Extract URL from the HTTP schema - extractedURL := http.Request.URL - - parsedURL, err := url.Parse(extractedURL) - if err != nil || parsedURL.Hostname() == "" { - utils.LogError(logger, err, "error parsing URL") - return nil - } - - request.URL = map[string]interface{}{ - "raw": parsedURL.String(), - "protocol": parsedURL.Scheme, - "host": []string{parsedURL.Hostname()}, - "port": parsedURL.Port(), - "path": []string{strings.TrimLeft(parsedURL.Path, "/")}, - "query": http.Request.URLParams, - } - request.Method = string(http.Request.Method) - - for key, header := range http.Request.Header { - request.Header = append(request.Header, map[string]interface{}{ - "key": key, - "value": header, - }) - } - - if http.Request.Form != nil { - formDataArray := []map[string]interface{}{} - for _, form := range http.Request.Form { - formDataArray = append(formDataArray, map[string]interface{}{ - "key": form.Key, - "values": form.Values, - }) - } - - request.Body.Mode = "formdata" - request.Body.Formdata = formDataArray - } else { - request.Body.Mode = "raw" - request.Body.Raw = http.Request.Body - } - - if strings.Contains(http.Request.Header["Content-Type"], "application/json") { - request.Body.Options = map[string]interface{}{ - "raw": map[string]interface{}{ - "headerFamily": "json", - "language": "json", - }, - } - } - - // Extract Response Headers - for key, header := range http.Response.Header { - response.Header = append(response.Header, map[string]string{ - "key": key, - "value": header, - }) - } - - response.Code = http.Response.StatusCode - response.Status = http.Response.StatusMessage - response.Body = http.Response.Body - response.OriginalRequest = &request - response.Name = http.Response.StatusMessage - - if strings.Contains(http.Response.Header["Content-Type"], "application/json") { - request.Body.Options = map[string]interface{}{ - "raw": map[string]interface{}{ - "headerFamily": "json", - "language": "json", - }, - } - } - - // Extract the last segment of the path as the name - pathSegments := strings.Split(strings.Trim(parsedURL.Path, "/"), "/") - // Create the name by joining segments with dashes - name := strings.Join(pathSegments, "-") - - return map[string]interface{}{ - "name": name, - "protocolProfileBehavior": map[string]interface{}{ - "disableBodyPruning": true, - }, - "request": request, - "response": []postmanimport.PostmanResponse{response}, - } -} - -type PostmanCollection struct { - Info struct { - PostmanID string `json:"_postman_id"` - Name string `json:"name"` - Schema string `json:"schema"` - } `json:"info"` - Items []interface{} `json:"item"` -} - -func Export(_ context.Context, logger *zap.Logger) error { - cwd, err := os.Getwd() - if err != nil { - utils.LogError(logger, err, "failed to get current working directory") - return err - } - // Correctly format the directory path to include "keploy" - keployDir := filepath.Join(cwd, "keploy") - - // Check if the directory exists - if _, err := os.Stat(keployDir); os.IsNotExist(err) { - utils.LogError(logger, err, "keploy directory does not exist") - return err - - } - dir, err := yaml.ReadDir(keployDir, fs.FileMode(os.O_RDONLY)) - if err != nil { - utils.LogError(logger, err, "failed to open the directory containing yaml testcases", zap.Any("path", keployDir)) - return err - } - - files, err := dir.ReadDir(0) - if err != nil { - utils.LogError(logger, err, "failed to read the file names of yaml testcases", zap.Any("path", keployDir)) - return err - - } - folderName := filepath.Base(cwd) - - collection := PostmanCollection{ - Info: struct { - PostmanID string `json:"_postman_id"` - Name string `json:"name"` - Schema string `json:"schema"` - }{ - PostmanID: uuid.New().String(), - Name: folderName, - Schema: "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", - }, - } - for _, v := range files { - if v.Name() != "reports" && v.Name() != "testReports" && v.IsDir() { - testsDir := filepath.Join(keployDir, v.Name(), "tests") - if _, err := os.Stat(testsDir); os.IsNotExist(err) { - logger.Info("No tests found. Skipping export.", zap.String("path", testsDir)) - continue - } - // Read the "tests" subfolder - testFiles, err := os.ReadDir(testsDir) - if err != nil { - utils.LogError(logger, err, "failed to read the test files", zap.String("path", testsDir)) - continue - } - keployRequests := make(map[interface{}]int, 0) - for _, testFile := range testFiles { - if filepath.Ext(testFile.Name()) == ".yaml" { - filePath := filepath.Join(testsDir, testFile.Name()) - - // Read the YAML file - data, err := os.ReadFile(filePath) - if err != nil { - utils.LogError(logger, err, "failed to read the YAML file", zap.String("path", filePath)) - continue - } - - if len(data) == 0 { - logger.Warn("skippping empty testcase", zap.String("testcase name", testFile.Name())) - continue - } - - var testCase *yaml.NetworkTrafficDoc - err = yamlLib.Unmarshal(data, &testCase) - if err != nil { - utils.LogError(logger, err, "failed to unmarshall YAML data") - continue - } - if testCase == nil { - logger.Warn("skipping invalid testCase yaml", zap.String("testcase name", testFile.Name())) - continue - } - - var httpSchema models.HTTPSchema - - err = testCase.Spec.Decode(&httpSchema) - if err != nil { - utils.LogError(logger, err, "failed to decode the HTTP schema") - continue - } - - requestJSON := ConvertKeployHTTPToPostmanCollection(logger, &httpSchema) - // Convert the requestJSON to a string (assuming it's a map or complex type) - requestJSONString, err := json.Marshal(requestJSON) - if err != nil { - utils.LogError(logger, err, "failed to marshal requestJSON to string") - continue - } - keployRequests[string(requestJSONString)]++ - } - } - var uniqueRequests []interface{} - for request := range keployRequests { - var curlRequest interface{} - err := json.Unmarshal([]byte(request.(string)), &curlRequest) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the request JSON") - continue - } - uniqueRequests = append(uniqueRequests, curlRequest) - } - requestFile := map[string]interface{}{ - "name": v.Name(), - "item": uniqueRequests, - } - collection.Items = append(collection.Items, requestFile) - } - } - sort.SliceStable(collection.Items, func(i, j int) bool { - return collection.Items[i].(map[string]interface{})["name"].(string) < collection.Items[j].(map[string]interface{})["name"].(string) - }) - - var buf bytes.Buffer - encoder := json.NewEncoder(&buf) - encoder.SetEscapeHTML(false) // Disable HTML escaping - encoder.SetIndent("", " ") - - err = encoder.Encode(collection) - if err != nil { - utils.LogError(logger, err, "failed to encode the Postman collection") - return err - } - - outputData := buf.Bytes() - - if err := os.WriteFile("output.json", outputData, 0644); err != nil { - utils.LogError(logger, err, "failed to write the output JSON file") - return err - } - - fmt.Println("✅ Curls successfully exported to output.json 🎉") - - return nil -} diff --git a/keploy/pkg/service/import/import.go b/keploy/pkg/service/import/import.go deleted file mode 100644 index 57c2ee4..0000000 --- a/keploy/pkg/service/import/import.go +++ /dev/null @@ -1,569 +0,0 @@ -// Package postmanimport implements the import of a Postman collection to Keploy tests. -package postmanimport - -import ( - "bufio" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "regexp" - "strings" - "time" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/yaml" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -const ( - postmanSchemaVersion = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" - testSetNamePrefix = "test-set-" -) - -type PostmanImporter struct { - logger *zap.Logger - ctx context.Context - toCapture bool -} - -func NewPostmanImporter(ctx context.Context, logger *zap.Logger) *PostmanImporter { - return &PostmanImporter{ - logger: logger, - ctx: ctx, - toCapture: true, - } -} - -func (pi *PostmanImporter) Import(collectionPath, basePath string) error { - if err := pi.validateCollectionPath(collectionPath); err != nil { - return err - } - - collectionBytes, err := os.ReadFile(collectionPath) - if err != nil { - return fmt.Errorf("failed to read Postman collection file: %w", err) - } - - postmanCollection, err := pi.parsePostmanCollection(collectionBytes) - if err != nil { - return err - } - - pi.validatePostmanSchema(postmanCollection.Info.Schema) - - globalVariables := pi.extractGlobalVariables(postmanCollection.Variables) - - // Check for empty responses if basePath is not provided - emptyResponsesExist := pi.scanForEmptyResponses(postmanCollection) - if emptyResponsesExist { - if !pi.promptUserForCapture() { - pi.toCapture = false - pi.logger.Warn("Few test cases will be skipped as responses are missing from the collection") - } - } - - if err := pi.importTestSets(postmanCollection, globalVariables, basePath); err != nil { - return err - } - - pi.logger.Info("✅ Postman Collection Successfully Imported To Keploy Tests 🎉") - return nil -} - -func (pi *PostmanImporter) validateCollectionPath(path string) error { - if path == "" { - return fmt.Errorf("path to Postman collection cannot be empty") - } - - if !strings.HasSuffix(path, ".json") { - return fmt.Errorf("invalid file type: expected .json Postman collection") - } - - return nil -} - -func (pi *PostmanImporter) parsePostmanCollection(collectionBytes []byte) (*PostmanCollectionStruct, error) { - var postmanCollection PostmanCollectionStruct - if err := json.Unmarshal(collectionBytes, &postmanCollection); err != nil { - return nil, fmt.Errorf("failed to unmarshal Postman collection JSON: %w", err) - } - return &postmanCollection, nil -} - -func (pi *PostmanImporter) validatePostmanSchema(schema string) { - if schema != postmanSchemaVersion { - pi.logger.Warn("Postman Collection schema mismatch", zap.String("expected", postmanSchemaVersion), zap.String("actual", schema)) - } -} - -func (pi *PostmanImporter) extractGlobalVariables(variables []map[string]interface{}) map[string]string { - globalVariables := make(map[string]string) - - for _, variable := range variables { - // Skip disabled variables - if variable["disabled"] != nil && variable["disabled"].(bool) { - continue - } - - // Extract and validate variable key - key, ok := variable["key"].(string) - if !ok { - pi.logger.Error("Global variable key is not a string", zap.Any("key", variable["key"])) - continue - } - - // Extract and validate variable value - value, ok := variable["value"].(string) - if !ok { - pi.logger.Error("Global variable value is not a string", zap.Any("value", variable["value"])) - continue - } - - globalVariables[key] = value - } - - // Resolve variable dependencies - for key, value := range globalVariables { - globalVariables[key] = replaceTemplateVars(value, globalVariables) - } - - return globalVariables -} - -func (pi *PostmanImporter) sendRequest(req models.HTTPReq, basePath string) (models.HTTPResp, error) { - - var err error - if basePath != "" { - req.URL, err = utils.ReplaceBaseURL(req.URL, basePath) - if err != nil { - pi.logger.Error("failed to replace hostname", zap.Error(err)) - return models.HTTPResp{}, err - } - } - - httpReq, err := http.NewRequest(string(req.Method), req.URL, strings.NewReader(req.Body)) - if err != nil { - pi.logger.Error("failed to create HTTP request", zap.Error(err)) - return models.HTTPResp{}, err - } - - for key, value := range req.Header { - httpReq.Header.Set(key, value) - } - - // add timeout to the request - client := &http.Client{ - Timeout: 60 * time.Second, - } - - if req.ProtoMajor != 0 || req.ProtoMinor != 0 { - httpReq.ProtoMajor = req.ProtoMajor - httpReq.ProtoMinor = req.ProtoMinor - } - - resp, err := client.Do(httpReq) - if err != nil { - pi.logger.Error("failed to send HTTP request", zap.Error(err)) - return models.HTTPResp{}, err - } - - defer func() { - if cerr := resp.Body.Close(); cerr != nil { - pi.logger.Error("failed to close response body", zap.Error(cerr)) - } - }() - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - pi.logger.Error("failed to read response body", zap.Error(err)) - return models.HTTPResp{}, err - } - - response := models.HTTPResp{ - StatusCode: resp.StatusCode, - StatusMessage: resp.Status, - Header: make(map[string]string), - Body: string(responseBody), - } - - for key, value := range resp.Header { - response.Header[key] = strings.Join(value, ",") - } - // Use the response.Body field to avoid unused write error - pi.logger.Info("Response Body", zap.String("body", response.Body)) - - return response, nil -} - -func (pi *PostmanImporter) importTestSets(collection *PostmanCollectionStruct, globalVariables map[string]string, basePath string) error { - cwd, err := os.Getwd() - if err != nil { - return fmt.Errorf("failed to get current working directory: %w", err) - } - - testCounter := 0 - var itemsToProcess []TestData - - if len(collection.Items.PostmanItems) > 0 { - for _, item := range collection.Items.PostmanItems { - if len(item.Item) == 0 { - continue - } - - testSet := item.Name - if item.Name == "" { - testSet = pi.generateTestSetName() - } - - testSetPath := filepath.Join(cwd, "keploy", testSet) - testsPath := filepath.Join(testSetPath, "tests") - - for _, testItem := range item.Item { - if err := pi.processEmptyResponse(&testItem, globalVariables, basePath); err != nil { - return err - } - - if err := pi.writeTestData(testItem, testsPath, globalVariables, &testCounter); err != nil { - return fmt.Errorf("failed to write test data: %w", err) - } - } - - testCounter = 0 - } - } - - if len(collection.Items.TestDataItems) > 0 { - testSetName := pi.generateTestSetName() - testSetPath := filepath.Join(cwd, "keploy", testSetName) - testsPath := filepath.Join(testSetPath, "tests") - itemsToProcess = collection.Items.TestDataItems - - for _, testItem := range itemsToProcess { - if err := pi.processEmptyResponse(&testItem, globalVariables, basePath); err != nil { - return err - } - - if err := pi.writeTestData(testItem, testsPath, globalVariables, &testCounter); err != nil { - return fmt.Errorf("failed to write test data: %w", err) - } - } - - return nil - } - - return nil -} - -func (pi *PostmanImporter) generateTestSetName() string { - maxTestSetNumber := 0 - files, err := os.ReadDir(filepath.Join("keploy")) - if err == nil { - for _, file := range files { - if file.IsDir() && strings.HasPrefix(file.Name(), testSetNamePrefix) { - var testSetNumber int - if _, err := fmt.Sscanf(file.Name(), testSetNamePrefix+"%d", &testSetNumber); err == nil && testSetNumber > maxTestSetNumber { - maxTestSetNumber = testSetNumber - } - } - } - } - return fmt.Sprintf("%s%d", testSetNamePrefix, maxTestSetNumber+1) -} - -func (pi *PostmanImporter) scanForEmptyResponses(collection *PostmanCollectionStruct) bool { - - for _, item := range collection.Items.PostmanItems { - for _, testItem := range item.Item { - if len(testItem.Response) == 0 { - pi.logger.Warn("Empty response found", zap.String("testItem", testItem.Name)) - return true - } - } - } - for _, testItem := range collection.Items.TestDataItems { - if len(testItem.Response) == 0 { - pi.logger.Warn("Empty response found", zap.String("testItem", testItem.Name)) - return true - } - } - return false -} - -func (pi *PostmanImporter) promptUserForCapture() bool { - reader := bufio.NewReader(os.Stdin) - fmt.Print("Some responses are empty. We need to hit the server to record these responses. Is your server running? (yes/no): ") - response, err := reader.ReadString('\n') - if err != nil { - pi.logger.Error("Failed to read user input", zap.Error(err)) - return false - } - response = strings.TrimSpace(strings.ToLower(response)) - return response == "yes" -} - -func (pi *PostmanImporter) processEmptyResponse(testItem *TestData, globalVariables map[string]string, basePath string) error { - if len(testItem.Response) != 0 { - return nil - } - - if !pi.toCapture { - pi.logger.Info("Skipping request capture as basePath is not provided") - return nil - } - - req := constructRequest(&testItem.Request, globalVariables) - if req.URL != "" { - resp, err := pi.sendRequest(req, basePath) - if err != nil { - return fmt.Errorf("failed to send request: %w", err) - } - - var result []map[string]string - for key, value := range resp.Header { - result = append(result, map[string]string{ - "key": key, - "value": value, - }) - } - - response := PostmanResponse{ - Name: "New Request", - Body: resp.Body, - Status: resp.StatusMessage, - Code: resp.StatusCode, - OriginalRequest: &testItem.Request, - Header: result, - } - testItem.Response = append(testItem.Response, response) - return nil - } - pi.logger.Error("URL is empty", zap.String("testItem", testItem.Name)) - return fmt.Errorf("URL is empty") -} - -func (pi *PostmanImporter) writeTestData(testItem TestData, testsPath string, globalVariables map[string]string, testCounter *int) error { - for _, response := range testItem.Response { - testName := fmt.Sprintf("test-%d", *testCounter+1) - - requestSchema := constructRequest(response.OriginalRequest, globalVariables) - if response.OriginalRequest == nil { - requestSchema = constructRequest(&testItem.Request, globalVariables) - } - - responseSchema := constructResponse(response) - - testCase := &yaml.NetworkTrafficDoc{ - Version: models.GetVersion(), - Kind: models.HTTP, - Name: testItem.Name, - } - - if err := testCase.Spec.Encode(&models.HTTPSchema{ - Request: requestSchema, - Response: responseSchema, - }); err != nil { - return fmt.Errorf("failed to encode test case: %w", err) - } - - testCase.Curl = pkg.MakeCurlCommand(requestSchema) - - testResultBytes, err := yamlLib.Marshal(testCase) - if err != nil { - return fmt.Errorf("failed to marshal test result: %w", err) - } - - if err := yaml.WriteFile(pi.ctx, pi.logger, testsPath, testName, testResultBytes, false); err != nil { - return fmt.Errorf("failed to write test result: %w", err) - } - - (*testCounter)++ - } - return nil -} - -func constructRequest(req *PostmanRequest, variables map[string]string) models.HTTPReq { - if req == nil { - return models.HTTPReq{} - } - - headers := extractHeaders(req.Header) - url := extractURL(req.URL) - - requestSchema := models.HTTPReq{ - URL: replaceTemplateVars(url, variables), - Method: models.Method(req.Method), - Header: headers, - } - - // Process request body based on mode - switch req.Body.Mode { - case "raw": - requestSchema.Body = req.Body.Raw - case "urlencoded": - requestSchema.Body = processUrlencodedBody(req.Body.Urlencoded) - case "formdata": - requestSchema.Form = processFormdataBody(req.Body.Formdata) - } - - return requestSchema -} - -func extractHeaders(headers []map[string]interface{}) map[string]string { - headersMap := make(map[string]string) - for _, header := range headers { - headersMap[header["key"].(string)] = header["value"].(string) - } - return headersMap -} - -func extractURL(url interface{}) string { - switch v := url.(type) { - case string: - return v - case map[string]interface{}: - url := v["raw"].(string) - return url - default: - return "" - } -} - -func processUrlencodedBody(body []map[string]interface{}) string { - keyValues := []string{} - for _, item := range body { - keyValues = append(keyValues, item["key"].(string)+"="+item["value"].(string)) - } - return strings.Join(keyValues, "&") -} - -func processFormdataBody(body []map[string]interface{}) []models.FormData { - form := []models.FormData{} - for _, formData := range body { - form = append(form, models.FormData{ - Key: formData["key"].(string), - Values: []string{formData["value"].(string)}, - }) - } - return form -} - -func constructResponse(res PostmanResponse) models.HTTPResp { - headers := make(map[string]string) - for _, header := range res.Header { - headers[header["key"]] = header["value"] - } - - return models.HTTPResp{ - Body: res.Body, - StatusMessage: res.Status, - StatusCode: res.Code, - Header: headers, - } -} - -func replaceTemplateVars(input string, variables map[string]string) string { - re := regexp.MustCompile(`\{\{\s*(\w+)\s*\}\}`) - - return re.ReplaceAllStringFunc(input, func(match string) string { - submatches := re.FindStringSubmatch(match) - if len(submatches) > 1 { - if replacement, exists := variables[submatches[1]]; exists { - return replacement - } - } - return match - }) -} - -type PostmanCollectionStruct struct { - Info struct { - PostmanID string `json:"_postman_id"` - Name string `json:"name"` - Schema string `json:"schema"` - } `json:"info"` - Items ItemsContainer `json:"item"` - Variables []map[string]interface{} `json:"variable"` -} - -type PostmanCollection struct { - Info struct { - PostmanID string `json:"_postman_id"` - Name string `json:"name"` - Schema string `json:"schema"` - } `json:"info"` - Items json.RawMessage `json:"item"` - Variables []map[string]interface{} `json:"variable"` -} - -type ItemsContainer struct { - PostmanItems []PostmanItem - TestDataItems []TestData -} - -type PostmanItem struct { - Name string `json:"name"` - Variables []map[string]string `json:"variable"` - Item []TestData `json:"item"` -} - -type TestData struct { - Name string `json:"name"` - Request PostmanRequest `json:"request"` - Response []PostmanResponse `json:"response"` - Variables []map[string]interface{} `json:"variable"` -} - -type PostmanRequest struct { - Method string `json:"method"` - Header []map[string]interface{} `json:"header"` - Body PostmanRequestBody `json:"body"` - URL interface{} `json:"url"` -} - -type PostmanRequestBody struct { - Mode string `json:"mode"` - Raw string `json:"raw"` - Urlencoded []map[string]interface{} `json:"urlencoded"` - Formdata []map[string]interface{} `json:"formdata"` - Options map[string]interface{} `json:"options"` -} - -type PostmanResponse struct { - Name string `json:"name"` - Body string `json:"body"` - Status string `json:"status"` - Code int `json:"code"` - OriginalRequest *PostmanRequest `json:"originalRequest,omitempty"` - Header []map[string]string `json:"header"` -} - -func (ic *ItemsContainer) UnmarshalJSON(data []byte) error { - var postmanItems []PostmanItem - if err := json.Unmarshal(data, &postmanItems); err == nil { - ic.PostmanItems = postmanItems - } - - var items []TestData - - if err := json.Unmarshal(data, &items); err != nil { - return err - } - - ic.TestDataItems = items - - return nil -} - -func (ic ItemsContainer) MarshalJSON() ([]byte, error) { - if len(ic.PostmanItems) > 0 { - return json.Marshal(ic.PostmanItems) - } - return json.Marshal(ic.TestDataItems) -} diff --git a/keploy/pkg/service/load/dashboard_exposer.go b/keploy/pkg/service/load/dashboard_exposer.go deleted file mode 100644 index d05a1f6..0000000 --- a/keploy/pkg/service/load/dashboard_exposer.go +++ /dev/null @@ -1,118 +0,0 @@ -package load - -import ( - "context" - "embed" - "io/fs" - "log" - "net/http" - "os" - "os/exec" - "path" - "strconv" - "strings" - - "github.com/pkg/browser" - "go.keploy.io/server/v2/config" - "go.uber.org/zap" -) - -type DashboardExposer struct { - config *config.Config - logger *zap.Logger - port int - instanceID string - instanceURL string -} - -func NewDashboardExposer(cfg *config.Config, logger *zap.Logger, loadTestID string) *DashboardExposer { - return &DashboardExposer{ - config: cfg, - logger: logger, - port: 3000, // Default port for the dashboard, until it's configured otherwise // TODO - instanceID: loadTestID, - instanceURL: "http://localhost:3000/?dashboard=" + loadTestID, - } -} - -func (de *DashboardExposer) Expose(ctx context.Context) { - dashboardServer := &http.Server{ - Addr: ":" + strconv.Itoa(de.port), - Handler: de.handler(), - } - - de.logger.Info("Starting dashboard server", zap.String("URL", "http://localhost:"+strconv.Itoa(de.port))) - - go func() { - if err := dashboardServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - de.logger.Error("Dashboard server failed", zap.Error(err)) - } - }() - - de.logger.Info("Opening browser on dashboard", zap.String("URL", de.instanceURL)) - de.openBrowser() - - go func() { - <-ctx.Done() - de.logger.Info("Shutting down dashboard exposer...") - if err := dashboardServer.Shutdown(context.Background()); err != nil { - de.logger.Error("Failed to shutdown dashboard server", zap.Error(err)) - } - }() -} - -// ========================================================================================================= - -func (de *DashboardExposer) openBrowser() { - if isWSL() { - err := exec.Command("cmd.exe", "/c", "start", de.instanceURL).Run() - if err != nil { - de.logger.Error("Failed to open browser in WSL", zap.Error(err)) - } - } else { - err := browser.OpenURL(de.instanceURL) - if err != nil { - de.logger.Error("Failed to open browser", zap.Error(err)) - } - } -} - -func isWSL() bool { - data, err := os.ReadFile("/proc/version") - if err != nil { - return false - } - content := strings.ToLower(string(data)) - return strings.Contains(content, "microsoft") || strings.Contains(content, "wsl") -} - -// ========================================================================================================= - -//go:embed out/* -var content embed.FS - -// fileSystem returns an http.FileSystem rooted at /. -func (de *DashboardExposer) fileSystem() http.FileSystem { - fsys, err := fs.Sub(content, "out") - if err != nil { - log.Fatal("embed fs setup failed:", err) - } - return http.FS(fsys) -} - -// Handler returns a ready-to-use http.Handler that: -// - Serves compressed assets transparently (if the browser supports it) -// - Falls back to index.html for unknown routes (handy for client-side routers) -func (de *DashboardExposer) handler() http.Handler { - fs := de.fileSystem() - fileServer := http.FileServer(fs) - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Try the exact path first - if _, err := fs.Open(path.Clean(r.URL.Path)); err != nil { - // Not found? send SPA entry point - r.URL.Path = "/" - } - fileServer.ServeHTTP(w, r) - }) -} diff --git a/keploy/pkg/service/load/exporter.go b/keploy/pkg/service/load/exporter.go deleted file mode 100644 index a187155..0000000 --- a/keploy/pkg/service/load/exporter.go +++ /dev/null @@ -1,205 +0,0 @@ -package load - -import ( - "context" - "encoding/json" - "net" - "net/http" - "strconv" - "sync" - "time" - - "github.com/gorilla/mux" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/service/secure" - "go.keploy.io/server/v2/pkg/service/testsuite" - "go.uber.org/zap" -) - -// Exporter Load Test Token, it contains unique identifier for the load test with the load test information. -type LTToken struct { - ID string `json:"id"` - URL string `json:"url"` - Title string `json:"title"` - CreatedAt time.Time `json:"created_at"` - Description string `json:"description"` - LoadOptions testsuite.LoadOptions `json:"load_options"` - SecurityReport *secure.SecurityReport `json:"security_report,omitempty"` -} - -type Exporter struct { - config *config.Config - logger *zap.Logger - ltToken *LTToken - isServed bool - vusReport []VUReport - mu sync.RWMutex -} - -// Exporter is responsible for providing endpoint to export the load test report in JSON format. -func NewExporter(cfg *config.Config, logger *zap.Logger, vus int, ltToken *LTToken) *Exporter { - return &Exporter{ - config: cfg, - logger: logger, - ltToken: ltToken, - isServed: false, - vusReport: make([]VUReport, vus), - } -} - -func (e *Exporter) GetMetrics(vuReport VUReport) { - e.mu.Lock() - defer e.mu.Unlock() - e.vusReport[vuReport.VUID] = vuReport - e.logger.Debug("VU Report collected", zap.Int("VUID", vuReport.VUID)) -} - -func (e *Exporter) StartServer(ctx context.Context) error { - // To serve LT Token to the dashboard - // ========================================================================================== - tokenRouter := mux.NewRouter() - tokenRouter.HandleFunc("/dashboards", e.HandleGETDashboards).Methods("GET") - - tokenServer := &http.Server{ - Addr: ":2345", - Handler: tokenRouter, - } - go func() { - tokenPortOK := false - for !tokenPortOK { - listener, err := net.Listen("tcp", ":2345") - if err != nil { - time.Sleep(100 * time.Millisecond) - continue - } - tokenPortOK = true - listener.Close() - } - go func() { - for { - e.mu.RLock() - isServed := e.isServed - e.mu.RUnlock() - - if isServed { - e.logger.Info("Dashboard token served successfully, shutting down token server") - tokenServer.Shutdown(context.Background()) - return - } - time.Sleep(100 * time.Millisecond) - } - }() - e.logger.Info("Starting token server on port 2345") - err := tokenServer.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - e.logger.Error("Failed to start token server", zap.Error(err)) - } - }() - // ========================================================================================== - - metricsRouter := mux.NewRouter() - metricsRouter.HandleFunc("/metrics", e.metricsHandler).Methods("GET") - - port := 9090 - portOK := false - for !portOK { - listener, err := net.Listen("tcp", ":"+strconv.Itoa(port)) - if err != nil { - e.logger.Warn("Failed to listen on port", zap.String("port", strconv.Itoa(port)), zap.Error(err)) - port++ - continue - } - portOK = true - listener.Close() - } - - metricsServer := &http.Server{ - Addr: ":" + strconv.Itoa(port), - Handler: metricsRouter, - } - - e.ltToken.URL = "http://localhost:" + strconv.Itoa(port) + "/metrics" - - go func() { - defer func() { - if r := recover(); r != nil { - e.logger.Error("Metrics server panicked", zap.Any("recover", r)) - } - }() - e.logger.Info("Metrics server starting on port", zap.Int("port", port)) - err := metricsServer.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - e.logger.Error("Failed to start metrics server", zap.Error(err)) - } - }() - - go func() { - <-ctx.Done() - e.logger.Info("Shutting down metrics server...") - // wait 1 second for the server to shutdown gracefully - ctxShutdown, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - if err := metricsServer.Shutdown(ctxShutdown); err != nil { - e.logger.Error("Failed to shutdown metrics server", zap.Error(err)) - } - }() - - return nil -} - -func (e *Exporter) metricsHandler(res http.ResponseWriter, req *http.Request) { - res.Header().Set("Access-Control-Allow-Origin", "*") - res.Header().Set("Access-Control-Allow-Methods", "GET") - res.Header().Set("Content-Type", "application/json") - - // vusReportCopy := make([]VUReport, len(e.vusReport)) - // for i, report := range e.vusReport { - // vusReportCopy[i].VUID = report.VUID - // vusReportCopy[i].TSExecCount = report.TSExecCount - // vusReportCopy[i].TSExecFailure = report.TSExecFailure - // vusReportCopy[i].TSExecTime = make([]time.Duration, len(report.TSExecTime)) - // copy(vusReportCopy[i].TSExecTime, report.TSExecTime) - // vusReportCopy[i].Steps = make([]StepReport, len(report.Steps)) - // for j, step := range report.Steps { - // vusReportCopy[i].Steps[j].StepName = step.StepName - // vusReportCopy[i].Steps[j].StepCount = step.StepCount - // vusReportCopy[i].Steps[j].StepFailure = step.StepFailure - // vusReportCopy[i].Steps[j].StepResponseTime = make([]time.Duration, len(step.StepResponseTime)) - // copy(vusReportCopy[i].Steps[j].StepResponseTime, step.StepResponseTime) - // vusReportCopy[i].Steps[j].StepBytesIn = step.StepBytesIn - // vusReportCopy[i].Steps[j].StepBytesOut = step.StepBytesOut - // } - // } - - encoder := json.NewEncoder(res) - encoder.SetEscapeHTML(false) - e.mu.RLock() - err := encoder.Encode(e.vusReport) - defer e.mu.RUnlock() - if err != nil { - e.logger.Error("Failed to encode VU reports", zap.Error(err)) - res.WriteHeader(http.StatusInternalServerError) - return - } -} - -func (e *Exporter) HandleGETDashboards(res http.ResponseWriter, req *http.Request) { - res.Header().Set("Access-Control-Allow-Origin", "*") - res.Header().Set("Access-Control-Allow-Methods", "GET") - res.Header().Set("Content-Type", "application/json") - - e.mu.Lock() - defer e.mu.Unlock() - - // Marshal the LTToken to JSON - tokenData, err := json.Marshal(e.ltToken) - if err != nil { - e.logger.Error("Failed to marshal LTToken", zap.Error(err)) - res.WriteHeader(http.StatusInternalServerError) - e.isServed = true - return - } - - res.Write(tokenData) - e.isServed = true -} diff --git a/keploy/pkg/service/load/load.go b/keploy/pkg/service/load/load.go deleted file mode 100644 index b60d233..0000000 --- a/keploy/pkg/service/load/load.go +++ /dev/null @@ -1,239 +0,0 @@ -package load - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/service/secure" - "go.keploy.io/server/v2/pkg/service/testsuite" - "go.uber.org/zap" -) - -type LTReport struct { - TestSuiteFile string `json:"test_suite_file"` - VUs int `json:"vus"` - Duration string `json:"duration"` - RPS int `json:"rps"` - Steps []StepThresholdReport `json:"steps"` -} - -type LoadTester struct { - config *config.Config - logger *zap.Logger - testsuite *testsuite.TestSuite - loadTestID string - profile string - vus int - duration string - rps int -} - -func NewLoadTester(cfg *config.Config, logger *zap.Logger) (*LoadTester, error) { - testsuitePath := filepath.Join(cfg.TestSuite.TSPath, cfg.TestSuite.TSFile) - logger.Info("Parsing TestSuite File", zap.String("path", testsuitePath)) - - testsuite, err := testsuite.TSParser(testsuitePath) - if err != nil { - logger.Error("Failed to parse TestSuite file", zap.Error(err)) - return nil, fmt.Errorf("failed to parse TestSuite file: %w", err) - } - - if testsuite.Spec.Load.Profile == "" { - testsuite.Spec.Load.Profile = "constant_vus" - logger.Info("Load profile not specified, defaulting to 'constant_vus'") - } - - return &LoadTester{ - config: cfg, - logger: logger, - testsuite: &testsuite, - loadTestID: time.Now().Format("20060102_150405"), - profile: testsuite.Spec.Load.Profile, - vus: testsuite.Spec.Load.VUs, - duration: testsuite.Spec.Load.Duration, - rps: testsuite.Spec.Load.RPS, - }, nil -} - -func (lt *LoadTester) Start(ctx context.Context) error { - // looks for CLI overrides - if ctx.Value("vus") != nil && ctx.Value("vus") != 1 && lt.profile == "constant_vus" { - lt.vus = ctx.Value("vus").(int) - lt.logger.Debug("Overriding VUs from CLI", zap.Int("vus", lt.vus)) - } - if ctx.Value("duration") != nil && ctx.Value("duration") != "" && lt.profile == "constant_vus" { - lt.duration = ctx.Value("duration").(string) - lt.logger.Debug("Overriding duration from CLI", zap.String("duration", lt.duration)) - } - if ctx.Value("rps") != nil && ctx.Value("rps") != 0 && lt.profile == "constant_vus" { - lt.rps = ctx.Value("rps").(int) - lt.logger.Debug("Overriding RPS from CLI", zap.Int("rps", lt.rps)) - } - - // init load options with values from testsuite spec and CLI overrides - loadOptions := &testsuite.LoadOptions{ - Profile: lt.profile, - VUs: lt.vus, - Duration: lt.duration, - RPS: lt.rps, - Stages: lt.testsuite.Spec.Load.Stages, - Thresholds: lt.testsuite.Spec.Load.Thresholds, - } - - ltToken := <Token{ - ID: lt.loadTestID, - URL: "http://localhost:9090/metrics", - Title: lt.testsuite.Name, - Description: lt.testsuite.Spec.Metadata.Description, - LoadOptions: *loadOptions, - } - - lt.logger.Info("Starting load test", - zap.Int("vus", lt.vus), - zap.String("duration", lt.duration), - zap.Int("rps", lt.rps), - ) - - securityChecker, err := secure.NewSecurityChecker(lt.config, lt.logger) - if err != nil { - lt.logger.Error("Failed to create security checker", zap.Error(err)) - } - - securityReport, err := securityChecker.Start(ctx) - if err != nil { - lt.logger.Error("Failed to start security checker", zap.Error(err)) - } - - ltToken.SecurityReport = securityReport - - dashboardExposer := NewDashboardExposer(lt.config, lt.logger, lt.loadTestID) - exporter := NewExporter(lt.config, lt.logger, lt.vus, ltToken) - mc := NewMetricsCollector(lt.config, lt.logger, lt.vus) - scheduler := NewScheduler(lt.logger, lt.config, loadOptions, lt.testsuite, mc) - - if err := scheduler.Run(ctx, exporter, dashboardExposer); err != nil { - lt.logger.Error("Failed to run load test", zap.Error(err)) - return fmt.Errorf("failed to run load test: %w", err) - } - - steps := mc.SetStepsMetrics() - te := NewThresholdEvaluator(lt.config, lt.logger, lt.testsuite) - report := te.Evaluate(steps) - - lt.printCLISummary(report) - - ltReport := LTReport{ - TestSuiteFile: lt.config.TestSuite.TSFile, - VUs: lt.vus, - Duration: lt.duration, - RPS: lt.rps, - Steps: report, - } - - err = lt.saveJSONReport(ltReport) - if err != nil { - lt.logger.Error("Failed to save JSON report", zap.Error(err)) - } - - lt.logger.Info("Load test completed", zap.String("tsFile", lt.config.TestSuite.TSFile)) - return nil -} - -func (lt *LoadTester) printCLISummary(report []StepThresholdReport) { - lt.logger.Info("Load test summary", - zap.String("tsFile", lt.config.TestSuite.TSFile), - zap.Int("vus", lt.vus), - zap.String("duration", lt.duration), - zap.Int("rps", lt.rps), - ) - - // Total Requests: 3,000 - // Failures: 18 (0.6%) - // P95 Latency: 460ms - // Data Sent: 1.2 MB - // Data Received: 5.4 MB - - // Thresholds: - // ✓ http_req_duration_p95 < 500ms - // ✗ http_req_failed_rate <= 1% - // ✓ data_received > 1MB - - // Test Result: ❌ FAILED (1 critical threshold breached) - - for _, stepReport := range report { - thresholdStatus := make(map[string]int) - testResultStatus := "PASSED" - - fmt.Println("Step:", stepReport.StepName) - fmt.Printf(" Total Requests: %d\n", stepReport.TotalRequests) - fmt.Printf(" Failures: %d (%.2f%%)\n", stepReport.TotalFailures, - float64(stepReport.TotalFailures)/float64(stepReport.TotalRequests)*100) - fmt.Printf(" P95 Latency: %s\n", stepReport.P95Latency) - fmt.Printf(" Data Sent: %.2f MB\n", float64(stepReport.TotalBytesOut)/(1024*1024)) - fmt.Printf(" Data Received: %.2f MB\n", float64(stepReport.TotalBytesIn)/(1024*1024)) - fmt.Println(" Thresholds:") - - for _, th := range stepReport.Thresholds { - status := "✓" - if !th.Pass { - status = "✗" - thresholdStatus[th.Severity]++ - testResultStatus = "FAILED" - } - fmt.Printf(" %s %-25s %-25s \tActual(%v)\n", status, th.Metric, fmt.Sprintf("condition(%s)", th.Condition), th.Actual) - } - - if testResultStatus == "FAILED" { - fmt.Printf(" Test Result: ❌ %s ", testResultStatus) - if thresholdStatus["critical"] > 0 { - fmt.Printf("(%d critical threshold breached) ", thresholdStatus["critical"]) - } - if thresholdStatus["high"] > 0 { - fmt.Printf("(%d high threshold breached) ", thresholdStatus["high"]) - } - if thresholdStatus["medium"] > 0 { - fmt.Printf("(%d medium threshold breached) ", thresholdStatus["medium"]) - } - if thresholdStatus["low"] > 0 { - fmt.Printf("(%d low threshold breached) ", thresholdStatus["low"]) - } - fmt.Printf("\n") - } else { - fmt.Printf(" Test Result: ✅ %s\n", testResultStatus) - } - fmt.Println(strings.Repeat("-", 100)) - } -} - -func (lt *LoadTester) saveJSONReport(report LTReport) error { - err := os.MkdirAll(filepath.Join("keploy", "load", "reports"), 0755) - if err != nil { - lt.logger.Error("Failed to create reports directory", zap.Error(err)) - return fmt.Errorf("failed to create reports directory: %w", err) - } - filePath := filepath.Join("keploy", "load", "reports", - fmt.Sprintf("%s_%s.json", time.Now().Format("20060102_150405"), strings.TrimSuffix(lt.config.TestSuite.TSFile, filepath.Ext(lt.config.TestSuite.TSFile)))) - file, err := os.Create(filePath) - if err != nil { - lt.logger.Error("Failed to create output file", zap.Error(err)) - return fmt.Errorf("failed to create output file: %w", err) - } - defer file.Close() - - encoder := json.NewEncoder(file) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", " ") - if err := encoder.Encode(report); err != nil { - lt.logger.Error("Failed to encode report to JSON", zap.Error(err)) - return fmt.Errorf("failed to encode report to JSON: %w", err) - } - - lt.logger.Info("Report saved successfully", zap.String("output", filePath)) - return nil -} diff --git a/keploy/pkg/service/load/metrics_collector.go b/keploy/pkg/service/load/metrics_collector.go deleted file mode 100644 index c259051..0000000 --- a/keploy/pkg/service/load/metrics_collector.go +++ /dev/null @@ -1,92 +0,0 @@ -package load - -import ( - "sync" - "time" - - "go.keploy.io/server/v2/config" - "go.uber.org/zap" -) - -type MetricsCollector struct { - config *config.Config - logger *zap.Logger - VUsReports []VUReport - mu sync.RWMutex -} - -type StepMetrics struct { - StepName string - StepCount int - StepFailure int - StepResponseTime []time.Duration - StepBytesIn int64 - StepBytesOut int64 -} - -func NewMetricsCollector(config *config.Config, logger *zap.Logger, vus int) *MetricsCollector { - return &MetricsCollector{ - config: config, - logger: logger, - VUsReports: make([]VUReport, vus), - } -} - -func (mc *MetricsCollector) SetStepsMetrics() []StepMetrics { - mc.mu.RLock() - defer mc.mu.RUnlock() - - if len(mc.VUsReports) == 0 || len(mc.VUsReports[0].Steps) == 0 { - mc.logger.Warn("No VU reports or steps available for metrics calculation") - return nil - } - - steps := make([]StepMetrics, len(mc.VUsReports[0].Steps)) - for i, vuReport := range mc.VUsReports { - for j, step := range vuReport.Steps { - // Initialize per step metrics. step 1, 2, 3 and so on. it the same step across all VUs but with different results. - if i == 0 { - steps[j] = StepMetrics{ - StepName: step.StepName, - StepCount: 0, - StepFailure: 0, - StepResponseTime: make([]time.Duration, 0), - StepBytesIn: 0, - StepBytesOut: 0, - } - } - // collecting the results from different VUs into one place to operate on later. - steps[j].StepCount += step.StepCount - steps[j].StepFailure += step.StepFailure - steps[j].StepResponseTime = append(steps[j].StepResponseTime, step.StepResponseTime...) - steps[j].StepBytesIn += step.StepBytesIn - steps[j].StepBytesOut += step.StepBytesOut - } - } - - for _, step := range steps { - mc.logger.Debug("Step Metrics", - zap.String("stepName", step.StepName), - zap.Int("stepCount", step.StepCount), - zap.Int("stepFailure", step.StepFailure), - zap.Any("stepResponseTime", step.StepResponseTime), - zap.Int64("stepBytesIn", step.StepBytesIn), - zap.Int64("stepBytesOut", step.StepBytesOut), - ) - } - - return steps -} - -func (mc *MetricsCollector) CollectVUReport(vuReport *VUReport) { - mc.mu.Lock() - defer mc.mu.Unlock() - mc.VUsReports[vuReport.VUID] = *vuReport - mc.logger.Debug("VU Report collected", - zap.Int("vuID", vuReport.VUID), - zap.Int("tsExecCount", vuReport.TSExecCount), - zap.Int("tsExecFailure", vuReport.TSExecFailure), - zap.Any("tsExecTime", vuReport.TSExecTime), - zap.Int("totalVUs", len(mc.VUsReports)), - ) -} diff --git a/keploy/pkg/service/load/out/404.html b/keploy/pkg/service/load/out/404.html deleted file mode 100644 index f9517cb..0000000 --- a/keploy/pkg/service/load/out/404.html +++ /dev/null @@ -1 +0,0 @@ -404: This page could not be found.KLT Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/keploy/pkg/service/load/out/404/index.html b/keploy/pkg/service/load/out/404/index.html deleted file mode 100644 index f9517cb..0000000 --- a/keploy/pkg/service/load/out/404/index.html +++ /dev/null @@ -1 +0,0 @@ -404: This page could not be found.KLT Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_buildManifest.js b/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_buildManifest.js deleted file mode 100644 index d1a8a87..0000000 --- a/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_buildManifest.js +++ /dev/null @@ -1 +0,0 @@ -self.__BUILD_MANIFEST=function(e,r,t){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:2,errorRate:1e-4,numBits:39,numHashes:14,bitArray:[0,1,1,0,r,e,e,r,r,e,e,r,e,e,e,r,r,e,e,e,e,r,e,r,r,r,r,e,e,e,r,e,r,e,r,e,e,e,r]},__routerFilterDynamic:{numItems:r,errorRate:1e-4,numBits:r,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-03529f2c21436739.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB(); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_ssgManifest.js b/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_ssgManifest.js deleted file mode 100644 index 5b3ff59..0000000 --- a/keploy/pkg/service/load/out/_next/static/S6A95ZgpgxS8RHL4D2qnv/_ssgManifest.js +++ /dev/null @@ -1 +0,0 @@ -self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB() \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js b/keploy/pkg/service/load/out/_next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js deleted file mode 100644 index 697b728..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[441],{9248:(e,n,t)=>{var r,l=t(9509),a=t(6206),o=t(2115),u=t(7650);function i(e){var n="https://react.dev/errors/"+e;if(1I||(e.current=R[I],R[I]=null,I--)}function j(e,n){R[++I]=e.current,e.current=n}var V=U(null),H=U(null),Q=U(null),B=U(null);function W(e,n){switch(j(Q,n),j(H,e),j(V,null),n.nodeType){case 9:case 11:e=(e=n.documentElement)&&(e=e.namespaceURI)?sh(e):0;break;default:if(e=n.tagName,n=n.namespaceURI)e=sg(n=sh(n),e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}$(V),j(V,e)}function q(){$(V),$(H),$(Q)}function K(e){null!==e.memoizedState&&j(B,e);var n=V.current,t=sg(n,e.type);n!==t&&(j(H,e),j(V,t))}function Y(e){H.current===e&&($(V),$(H)),B.current===e&&($(B),s9._currentValue=A)}function X(e){if(void 0===nI)try{throw Error()}catch(e){var n=e.stack.trim().match(/\n( *(at )?)/);nI=n&&n[1]||"",nU=-1)":-1l||i[r]!==s[l]){var c="\n"+i[r].replace(" at new "," at ");return e.displayName&&c.includes("")&&(c=c.replace("",e.displayName)),c}while(1<=r&&0<=l);break}}}finally{G=!1,Error.prepareStackTrace=t}return(t=e?e.displayName||e.name:"")?X(t):""}function J(e){try{var n="",t=null;do n+=function(e,n){switch(e.tag){case 26:case 27:case 5:return X(e.type);case 16:return X("Lazy");case 13:return e.child!==n&&null!==n?X("Suspense Fallback"):X("Suspense");case 19:return X("SuspenseList");case 0:case 15:return Z(e.type,!1);case 11:return Z(e.type.render,!1);case 1:return Z(e.type,!0);case 31:return X("Activity");default:return""}}(e,t),t=e,e=e.return;while(e);return n}catch(e){return"\nError generating stack: "+e.message+"\n"+e.stack}}var ee=Object.prototype.hasOwnProperty,en=a.unstable_scheduleCallback,et=a.unstable_cancelCallback,er=a.unstable_shouldYield,el=a.unstable_requestPaint,ea=a.unstable_now,eo=a.unstable_getCurrentPriorityLevel,eu=a.unstable_ImmediatePriority,ei=a.unstable_UserBlockingPriority,es=a.unstable_NormalPriority,ec=a.unstable_LowPriority,ef=a.unstable_IdlePriority,ed=a.log,ep=a.unstable_setDisableYieldValue,em=null,eh=null;function eg(e){if("function"==typeof ed&&ep(e),eh&&"function"==typeof eh.setStrictMode)try{eh.setStrictMode(em,e)}catch(e){}}var ey=Math.clz32?Math.clz32:function(e){return 0==(e>>>=0)?32:31-(ev(e)/eb|0)|0},ev=Math.log,eb=Math.LN2,ek=256,ew=4194304;function eS(e){var n=42&e;if(0!==n)return n;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return 4194048&e;case 4194304:case 8388608:case 0x1000000:case 0x2000000:return 0x3c00000&e;case 0x4000000:return 0x4000000;case 0x8000000:return 0x8000000;case 0x10000000:return 0x10000000;case 0x20000000:return 0x20000000;case 0x40000000:return 0;default:return e}}function ex(e,n,t){var r=e.pendingLanes;if(0===r)return 0;var l=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var u=0x7ffffff&r;return 0!==u?0!=(r=u&~a)?l=eS(r):0!=(o&=u)?l=eS(o):t||0!=(t=u&~e)&&(l=eS(t)):0!=(u=r&~a)?l=eS(u):0!==o?l=eS(o):t||0!=(t=r&~e)&&(l=eS(t)),0===l?0:0!==n&&n!==l&&0==(n&a)&&((a=l&-l)>=(t=n&-n)||32===a&&0!=(4194048&t))?n:l}function eE(e,n){return 0==(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&n)}function ez(){var e=ek;return 0==(4194048&(ek<<=1))&&(ek=256),e}function eC(){var e=ew;return 0==(0x3c00000&(ew<<=1))&&(ew=4194304),e}function eP(e){for(var n=[],t=0;31>t;t++)n.push(e);return n}function eN(e,n){e.pendingLanes|=n,0x10000000!==n&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function eL(e,n,t){e.pendingLanes|=n,e.suspendedLanes&=~n;var r=31-ey(n);e.entangledLanes|=n,e.entanglements[r]=0x40000000|e.entanglements[r]|4194090&t}function eT(e,n){var t=e.entangledLanes|=n;for(e=e.entanglements;t;){var r=31-ey(t),l=1<=tr),to=!1;function tu(e,n){switch(e){case"keyup":return -1!==tn.indexOf(n.keyCode);case"keydown":return 229!==n.keyCode;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function ti(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var ts=!1,tc={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function tf(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===n?!!tc[e.type]:"textarea"===n}function td(e,n,t,r){nw?nS?nS.push(r):nS=[r]:nw=r,0<(n=st(n,"onChange")).length&&(t=new nQ("onChange","change",null,t,r),e.push({event:t,listeners:n}))}var tp=null,tm=null;function th(e){i4(e,0)}function tg(e){if(ne(eK(e)))return e}function ty(e,n){if("change"===e)return n}var tv=!1;if(nP){if(nP){var tb="oninput"in document;if(!tb){var tk=document.createElement("div");tk.setAttribute("oninput","return;"),tb="function"==typeof tk.oninput}r=tb}else r=!1;tv=r&&(!document.documentMode||9=n)return{node:r,offset:n-e};e=t}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=tL(r)}}function t_(e){e=null!=e&&null!=e.ownerDocument&&null!=e.ownerDocument.defaultView?e.ownerDocument.defaultView:window;for(var n=nn(e.document);n instanceof e.HTMLIFrameElement;){try{var t="string"==typeof n.contentWindow.location.href}catch(e){t=!1}if(t)e=n.contentWindow;else break;n=nn(e.document)}return n}function tF(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return n&&("input"===n&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===n||"true"===e.contentEditable)}var tD=nP&&"documentMode"in document&&11>=document.documentMode,tO=null,tM=null,tA=null,tR=!1;function tI(e,n,t){var r=t.window===t?t.document:9===t.nodeType?t:t.ownerDocument;tR||null==tO||tO!==nn(r)||(r="selectionStart"in(r=tO)&&tF(r)?{start:r.selectionStart,end:r.selectionEnd}:{anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},tA&&tN(tA,r)||(tA=r,0<(r=st(tM,"onSelect")).length&&(n=new nQ("onSelect","select",null,n,t),e.push({event:n,listeners:r}),n.target=tO)))}function tU(e,n){var t={};return t[e.toLowerCase()]=n.toLowerCase(),t["Webkit"+e]="webkit"+n,t["Moz"+e]="moz"+n,t}var t$={animationend:tU("Animation","AnimationEnd"),animationiteration:tU("Animation","AnimationIteration"),animationstart:tU("Animation","AnimationStart"),transitionrun:tU("Transition","TransitionRun"),transitionstart:tU("Transition","TransitionStart"),transitioncancel:tU("Transition","TransitionCancel"),transitionend:tU("Transition","TransitionEnd")},tj={},tV={};function tH(e){if(tj[e])return tj[e];if(!t$[e])return e;var n,t=t$[e];for(n in t)if(t.hasOwnProperty(n)&&n in tV)return tj[e]=t[n];return e}nP&&(tV=document.createElement("div").style,"AnimationEvent"in window||(delete t$.animationend.animation,delete t$.animationiteration.animation,delete t$.animationstart.animation),"TransitionEvent"in window||delete t$.transitionend.transition);var tQ=tH("animationend"),tB=tH("animationiteration"),tW=tH("animationstart"),tq=tH("transitionrun"),tK=tH("transitionstart"),tY=tH("transitioncancel"),tX=tH("transitionend"),tG=new Map,tZ="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function tJ(e,n){tG.set(e,n),eJ(n,[e])}tZ.push("scrollEnd");var t0="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var n=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(n))return}else if("object"==typeof l&&"function"==typeof l.emit)return void l.emit("uncaughtException",e);console.error(e)},t1=[],t2=0,t3=0;function t4(){for(var e=t2,n=t3=t2=0;n>=o,l-=o,rk=1<<32-ey(n)+l|t<h?(g=f,f=null):g=f.sibling;var y=p(l,f,u[h],i);if(null===y){null===f&&(f=g);break}e&&f&&null===y.alternate&&n(l,f),o=a(y,o,h),null===c?s=y:c.sibling=y,c=y,f=g}if(h===u.length)return t(l,f),rL&&rS(l,h),s;if(null===f){for(;hg?(y=h,h=null):y=h.sibling;var b=p(l,h,v.value,s);if(null===b){null===h&&(h=y);break}e&&h&&null===b.alternate&&n(l,h),o=a(b,o,g),null===f?c=b:f.sibling=b,f=b,h=y}if(v.done)return t(l,h),rL&&rS(l,g),c;if(null===h){for(;!v.done;g++,v=u.next())null!==(v=d(l,v.value,s))&&(o=a(v,o,g),null===f?c=v:f.sibling=v,f=v);return rL&&rS(l,g),c}for(h=r(h);!v.done;g++,v=u.next())null!==(v=m(h,l,g,v.value,s))&&(e&&null!==v.alternate&&h.delete(null===v.key?g:v.key),o=a(v,o,g),null===f?c=v:f.sibling=v,f=v);return e&&h.forEach(function(e){return n(l,e)}),rL&&rS(l,g),c}(s,c,f=b.call(f),h)}if("function"==typeof f.then)return u(s,c,lg(f),h);if(f.$$typeof===S)return u(s,c,rG(s,f),h);lv(s,f)}return"string"==typeof f&&""!==f||"number"==typeof f||"bigint"==typeof f?(f=""+f,null!==c&&6===c.tag?(t(s,c.sibling),(h=l(c,f)).return=s):(t(s,c),(h=ri(f,s.mode,h)).return=s),o(s=h)):t(s,c)}(u,s,c,f);return lm=null,h}catch(e){if(e===la||e===lu)throw e;var b=rt(29,e,null,u.mode);return b.lanes=f,b.return=u,b}finally{}}}var lw=lk(!0),lS=lk(!1),lx=!1;function lE(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function lz(e,n){e=e.updateQueue,n.updateQueue===e&&(n.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function lC(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function lP(e,n,t){var r=e.updateQueue;if(null===r)return null;if(r=r.shared,0!=(2&uO)){var l=r.pending;return null===l?n.next=n:(n.next=l.next,l.next=n),r.pending=n,n=t7(e),t9(e,null,t),n}return t8(e,r,n,t),t7(e)}function lN(e,n,t){if(null!==(n=n.updateQueue)&&(n=n.shared,0!=(4194048&t))){var r=n.lanes;r&=e.pendingLanes,t|=r,n.lanes=t,eT(e,t)}}function lL(e,n){var t=e.updateQueue,r=e.alternate;if(null!==r&&t===(r=r.updateQueue)){var l=null,a=null;if(null!==(t=t.firstBaseUpdate)){do{var o={lane:t.lane,tag:t.tag,payload:t.payload,callback:null,next:null};null===a?l=a=o:a=a.next=o,t=t.next}while(null!==t);null===a?l=a=n:a=a.next=n}else l=a=n;t={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=t;return}null===(e=t.lastBaseUpdate)?t.firstBaseUpdate=n:e.next=n,t.lastBaseUpdate=n}var lT=!1;function l_(){if(lT){var e=r9;if(null!==e)throw e}}function lF(e,n,t,r){lT=!1;var l=e.updateQueue;lx=!1;var a=l.firstBaseUpdate,o=l.lastBaseUpdate,u=l.shared.pending;if(null!==u){l.shared.pending=null;var i=u,s=i.next;i.next=null,null===o?a=s:o.next=s,o=i;var c=e.alternate;null!==c&&(u=(c=c.updateQueue).lastBaseUpdate)!==o&&(null===u?c.firstBaseUpdate=s:u.next=s,c.lastBaseUpdate=i)}if(null!==a){var f=l.baseState;for(o=0,c=s=i=null,u=a;;){var d=-0x20000001&u.lane,p=d!==u.lane;if(p?(uR&d)===d:(r&d)===d){0!==d&&d===r6&&(lT=!0),null!==c&&(c=c.next={lane:0,tag:u.tag,payload:u.payload,callback:null,next:null});e:{var h=e,g=u;switch(d=n,g.tag){case 1:if("function"==typeof(h=g.payload)){f=h.call(t,f,d);break e}f=h;break e;case 3:h.flags=-65537&h.flags|128;case 0:if(null==(d="function"==typeof(h=g.payload)?h.call(t,f,d):h))break e;f=m({},f,d);break e;case 2:lx=!0}}null!==(d=u.callback)&&(e.flags|=64,p&&(e.flags|=8192),null===(p=l.callbacks)?l.callbacks=[d]:p.push(d))}else p={lane:d,tag:u.tag,payload:u.payload,callback:u.callback,next:null},null===c?(s=c=p,i=f):c=c.next=p,o|=d;if(null===(u=u.next))if(null===(u=l.shared.pending))break;else u=(p=u).next,p.next=null,l.lastBaseUpdate=p,l.shared.pending=null}null===c&&(i=f),l.baseState=i,l.firstBaseUpdate=s,l.lastBaseUpdate=c,null===a&&(l.shared.lanes=0),uB|=o,e.lanes=o,e.memoizedState=f}}function lD(e,n){if("function"!=typeof e)throw Error(i(191,e));e.call(n)}function lO(e,n){var t=e.callbacks;if(null!==t)for(e.callbacks=null,e=0;ea?a:8;var o=O.T,u={};O.T=u,a9(e,!1,n,t);try{var i=l(),s=O.S;if(null!==s&&s(u,i),null!==i&&"object"==typeof i&&"function"==typeof i.then){var c,f,d=(c=[],f={status:"pending",value:null,reason:null,then:function(e){c.push(e)}},i.then(function(){f.status="fulfilled",f.value=r;for(var e=0;e title"))),sf(a,r,t),a[eR]=e,eX(a),r=a;break e;case"link":var o=s0("link","href",l).get(r+(t.href||""));if(o){for(var u=0;u<\/script>",a=a.removeChild(a.firstChild);break;case"select":a="string"==typeof r.is?o.createElement("select",{is:r.is}):o.createElement("select"),r.multiple?a.multiple=!0:r.size&&(a.size=r.size);break;default:a="string"==typeof r.is?o.createElement(l,{is:r.is}):o.createElement(l)}}a[eR]=n,a[eI]=r;e:for(o=n.child;null!==o;){if(5===o.tag||6===o.tag)a.appendChild(o.stateNode);else if(4!==o.tag&&27!==o.tag&&null!==o.child){o.child.return=o,o=o.child;continue}if(o===n)break;for(;null===o.sibling;){if(null===o.return||o.return===n)break e;o=o.return}o.sibling.return=o.return,o=o.sibling}switch(n.stateNode=a,sf(a,l,r),l){case"button":case"input":case"select":case"textarea":r=!!r.autoFocus;break;case"img":r=!0;break;default:r=!1}r&&oq(n)}}return oZ(n),oK(n,n.type,null===e?null:e.memoizedProps,n.pendingProps,t),null;case 6:if(e&&null!=n.stateNode)e.memoizedProps!==r&&oq(n);else{if("string"!=typeof r&&null===n.stateNode)throw Error(i(166));if(e=Q.current,rA(n)){if(e=n.stateNode,t=n.memoizedProps,r=null,null!==(l=rP))switch(l.tag){case 27:case 5:r=l.memoizedProps}e[eR]=n,(e=!!(e.nodeValue===t||null!==r&&!0===r.suppressHydrationWarning||si(e.nodeValue,t)))||rD(n,!0)}else(e=sm(e).createTextNode(r))[eR]=n,n.stateNode=e}return oZ(n),null;case 31:if(t=n.memoizedState,null===e||null!==e.memoizedState){if(r=rA(n),null!==t){if(null===e){if(!r)throw Error(i(318));if(!(e=null!==(e=n.memoizedState)?e.dehydrated:null))throw Error(i(557));e[eR]=n}else rR(),0==(128&n.flags)&&(n.memoizedState=null),n.flags|=4;oZ(n),e=!1}else t=rI(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=t),e=!0;if(!e){if(256&n.flags)return lW(n),n;return lW(n),null}if(0!=(128&n.flags))throw Error(i(558))}return oZ(n),null;case 13:if(r=n.memoizedState,null===e||null!==e.memoizedState&&null!==e.memoizedState.dehydrated){if(l=rA(n),null!==r&&null!==r.dehydrated){if(null===e){if(!l)throw Error(i(318));if(!(l=null!==(l=n.memoizedState)?l.dehydrated:null))throw Error(i(317));l[eR]=n}else rR(),0==(128&n.flags)&&(n.memoizedState=null),n.flags|=4;oZ(n),l=!1}else l=rI(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=l),l=!0;if(!l){if(256&n.flags)return lW(n),n;return lW(n),null}}if(lW(n),0!=(128&n.flags))return n.lanes=t,n;return t=null!==r,e=null!==e&&null!==e.memoizedState,t&&(r=n.child,l=null,null!==r.alternate&&null!==r.alternate.memoizedState&&null!==r.alternate.memoizedState.cachePool&&(l=r.alternate.memoizedState.cachePool.pool),a=null,null!==r.memoizedState&&null!==r.memoizedState.cachePool&&(a=r.memoizedState.cachePool.pool),a!==l&&(r.flags|=2048)),t!==e&&t&&(n.child.flags|=8192),oX(n,n.updateQueue),oZ(n),null;case 4:return q(),null===e&&i9(n.stateNode.containerInfo),oZ(n),null;case 10:return rQ(n.type),oZ(n),null;case 19:if($(lq),null===(r=n.memoizedState))return oZ(n),null;if(l=0!=(128&n.flags),null===(a=r.rendering))if(l)oG(r,!1);else{if(0!==uQ||null!==e&&0!=(128&e.flags))for(e=n.child;null!==e;){if(null!==(a=lK(e))){for(n.flags|=128,oG(r,!1),n.updateQueue=e=a.updateQueue,oX(n,e),n.subtreeFlags=0,e=t,t=n.child;null!==t;)ra(t,e),t=t.sibling;return j(lq,1&lq.current|2),rL&&rS(n,r.treeForkCount),n.child}e=e.sibling}null!==r.tail&&ea()>u0&&(n.flags|=128,l=!0,oG(r,!1),n.lanes=4194304)}else{if(!l)if(null!==(e=lK(a))){if(n.flags|=128,l=!0,n.updateQueue=e=e.updateQueue,oX(n,e),oG(r,!0),null===r.tail&&"hidden"===r.tailMode&&!a.alternate&&!rL)return oZ(n),null}else 2*ea()-r.renderingStartTime>u0&&0x20000000!==t&&(n.flags|=128,l=!0,oG(r,!1),n.lanes=4194304);r.isBackwards?(a.sibling=n.child,n.child=a):(null!==(e=r.last)?e.sibling=a:n.child=a,r.last=a)}if(null!==r.tail)return e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=ea(),e.sibling=null,t=lq.current,j(lq,l?1&t|2:1&t),rL&&rS(n,r.treeForkCount),e;return oZ(n),null;case 22:case 23:return lW(n),lU(),r=null!==n.memoizedState,null!==e?null!==e.memoizedState!==r&&(n.flags|=8192):r&&(n.flags|=8192),r?0!=(0x20000000&t)&&0==(128&n.flags)&&(oZ(n),6&n.subtreeFlags&&(n.flags|=8192)):oZ(n),null!==(t=n.updateQueue)&&oX(n,t.retryQueue),t=null,null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(t=e.memoizedState.cachePool.pool),r=null,null!==n.memoizedState&&null!==n.memoizedState.cachePool&&(r=n.memoizedState.cachePool.pool),r!==t&&(n.flags|=2048),null!==e&&$(ln),null;case 24:return t=null,null!==e&&(t=e.memoizedState.cache),n.memoizedState.cache!==t&&(n.flags|=2048),rQ(r2),oZ(n),null;case 25:case 30:return null}throw Error(i(156,n.tag))}(n.alternate,n,uH);if(null!==t){uA=t;return}if(null!==(n=n.sibling)){uA=n;return}uA=n=e}while(null!==n);0===uQ&&(uQ=5)}function ix(e,n){do{var t=function(e,n){switch(rz(n),n.tag){case 1:return 65536&(e=n.flags)?(n.flags=-65537&e|128,n):null;case 3:return rQ(r2),q(),0!=(65536&(e=n.flags))&&0==(128&e)?(n.flags=-65537&e|128,n):null;case 26:case 27:case 5:return Y(n),null;case 31:if(null!==n.memoizedState){if(lW(n),null===n.alternate)throw Error(i(340));rR()}return 65536&(e=n.flags)?(n.flags=-65537&e|128,n):null;case 13:if(lW(n),null!==(e=n.memoizedState)&&null!==e.dehydrated){if(null===n.alternate)throw Error(i(340));rR()}return 65536&(e=n.flags)?(n.flags=-65537&e|128,n):null;case 19:return $(lq),null;case 4:return q(),null;case 10:return rQ(n.type),null;case 22:case 23:return lW(n),lU(),null!==e&&$(ln),65536&(e=n.flags)?(n.flags=-65537&e|128,n):null;case 24:return rQ(r2),null;default:return null}}(e.alternate,e);if(null!==t){t.flags&=32767,uA=t;return}if(null!==(t=e.return)&&(t.flags|=32768,t.subtreeFlags=0,t.deletions=null),!n&&null!==(e=e.sibling)){uA=e;return}uA=e=t}while(null!==e);uQ=6,uA=null}function iE(e,n,t,r,l,a,o,u,s){e.cancelPendingCommit=null;do iL();while(0!==u3);if(0!=(6&uO))throw Error(i(327));if(null!==n){if(n===e.current)throw Error(i(177));if(!function(e,n,t,r,l,a){var o=e.pendingLanes;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=t,e.entangledLanes&=t,e.errorRecoveryDisabledLanes&=t,e.shellSuspendCounter=0;var u=e.entanglements,i=e.expirationTimes,s=e.hiddenUpdates;for(t=o&~t;0g&&(o=g,g=h,h=o);var y=tT(u,h),v=tT(u,g);if(y&&v&&(1!==p.rangeCount||p.anchorNode!==y.node||p.anchorOffset!==y.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var b=f.createRange();b.setStart(y.node,y.offset),p.removeAllRanges(),h>g?(p.addRange(b),p.extend(v.node,v.offset)):(b.setEnd(v.node,v.offset),p.addRange(b))}}}}for(f=[],p=u;p=p.parentNode;)1===p.nodeType&&f.push({element:p,left:p.scrollLeft,top:p.scrollTop});for("function"==typeof u.focus&&u.focus(),u=0;ut?32:t,O.T=null,t=u9,u9=null;var a=u4,o=u5;if(u3=0,u8=u4=null,u5=0,0!=(6&uO))throw Error(i(331));var u=uO;if(uO|=4,uT(a.current),ux(a,a.current,o,t),uO=u,iB(0,!1),eh&&"function"==typeof eh.onPostCommitFiberRoot)try{eh.onPostCommitFiberRoot(em,a)}catch(e){}return!0}finally{M.p=l,O.T=r,iN(e,n)}}function i_(e,n,t){n=rd(t,n),n=og(e.stateNode,n,2),null!==(e=lP(e,n,2))&&(eN(e,2),iQ(e))}function iF(e,n,t){if(3===e.tag)i_(e,e,t);else for(;null!==n;){if(3===n.tag){i_(n,e,t);break}if(1===n.tag){var r=n.stateNode;if("function"==typeof n.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===u2||!u2.has(r))){e=rd(t,e),null!==(r=lP(n,t=oy(2),2))&&(ov(t,r,n,e),eN(r,2),iQ(r));break}}n=n.return}}function iD(e,n,t){var r=e.pingCache;if(null===r){r=e.pingCache=new uD;var l=new Set;r.set(n,l)}else void 0===(l=r.get(n))&&(l=new Set,r.set(n,l));l.has(t)||(uV=!0,l.add(t),e=iO.bind(null,e,n,t),n.then(e,e))}function iO(e,n,t){var r=e.pingCache;null!==r&&r.delete(n),e.pingedLanes|=e.suspendedLanes&t,e.warmLanes&=~t,uM===e&&(uR&t)===t&&(4===uQ||3===uQ&&(0x3c00000&uR)===uR&&300>ea()-uJ?0==(2&uO)&&id(e,0):uq|=t,uY===uR&&(uY=0)),iQ(e)}function iM(e,n){0===n&&(n=eC()),null!==(e=t6(e,n))&&(eN(e,n),iQ(e))}function iA(e){var n=e.memoizedState,t=0;null!==n&&(t=n.retryLane),iM(e,t)}function iR(e,n){var t=0;switch(e.tag){case 31:case 13:var r=e.stateNode,l=e.memoizedState;null!==l&&(t=l.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}null!==r&&r.delete(n),iM(e,t)}var iI=null,iU=null,i$=!1,ij=!1,iV=!1,iH=0;function iQ(e){e!==iU&&null===e.next&&(null===iU?iI=iU=e:iU=iU.next=e),ij=!0,i$||(i$=!0,sS(function(){0!=(6&uO)?en(eu,iW):iq()}))}function iB(e,n){if(!iV&&ij){iV=!0;do for(var t=!1,r=iI;null!==r;){if(!n)if(0!==e){var l=r.pendingLanes;if(0===l)var a=0;else{var o=r.suspendedLanes,u=r.pingedLanes;a=0xc000095&(a=(1<<31-ey(42|e)+1)-1&(l&~(o&~u)))?0xc000095&a|1:a?2|a:0}0!==a&&(t=!0,iX(r,a))}else a=uR,0==(3&(a=ex(r,r===uM?a:0,null!==r.cancelPendingCommit||-1!==r.timeoutHandle)))||eE(r,a)||(t=!0,iX(r,a));r=r.next}while(t);iV=!1}}function iW(){iq()}function iq(){ij=i$=!1;var e,n=0;0===iH||((e=window.event)&&"popstate"===e.type?e===sv||(sv=e,0):(sv=null,1))||(n=iH);for(var t=ea(),r=null,l=iI;null!==l;){var a=l.next,o=iK(l,t);0===o?(l.next=null,null===r?iI=a:r.next=a,null===a&&(iU=r)):(r=l,(0!==n||0!=(3&o))&&(ij=!0)),l=a}0!==u3&&5!==u3||iB(n,!1),0!==iH&&(iH=0)}function iK(e,n){for(var t=e.suspendedLanes,r=e.pingedLanes,l=e.expirationTimes,a=-0x3c00001&e.pendingLanes;0 title"):null)}function s2(e){return"stylesheet"!==e.type||0!=(3&e.state.loading)}var s3=null;function s4(){if(this.count--,0===this.count){if(this.stylesheets)s5(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var s8=null;function s5(e,n){e.stylesheets=null,null!==e.unsuspend&&(e.count++,s8=new Map,n.forEach(s6,e),s8=null,s4.call(e))}function s6(e,n){if(!(4&n.state.loading)){var t=s8.get(e);if(t)var r=t.get(null);else{t=new Map,s8.set(e,t);for(var l=e.querySelectorAll("link[data-precedence],style[data-precedence]"),a=0;a{"use strict";r.d(t,{B8:()=>j,bL:()=>A,l9:()=>T});var n=r(2115),i=r(5185),o=r(6081),a=r(9196),l=r(6101),u=r(2712),s=e=>{let{present:t,children:r}=e,i=function(e){var t,r;let[i,o]=n.useState(),a=n.useRef(null),l=n.useRef(e),s=n.useRef("none"),[f,d]=(t=e?"mounted":"unmounted",r={mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}},n.useReducer((e,t)=>{let n=r[e][t];return null!=n?n:e},t));return n.useEffect(()=>{let e=c(a.current);s.current="mounted"===f?e:"none"},[f]),(0,u.N)(()=>{let t=a.current,r=l.current;if(r!==e){let n=s.current,i=c(t);e?d("MOUNT"):"none"===i||(null==t?void 0:t.display)==="none"?d("UNMOUNT"):r&&n!==i?d("ANIMATION_OUT"):d("UNMOUNT"),l.current=e}},[e,d]),(0,u.N)(()=>{if(i){var e;let t,r=null!=(e=i.ownerDocument.defaultView)?e:window,n=e=>{let n=c(a.current).includes(e.animationName);if(e.target===i&&n&&(d("ANIMATION_END"),!l.current)){let e=i.style.animationFillMode;i.style.animationFillMode="forwards",t=r.setTimeout(()=>{"forwards"===i.style.animationFillMode&&(i.style.animationFillMode=e)})}},o=e=>{e.target===i&&(s.current=c(a.current))};return i.addEventListener("animationstart",o),i.addEventListener("animationcancel",n),i.addEventListener("animationend",n),()=>{r.clearTimeout(t),i.removeEventListener("animationstart",o),i.removeEventListener("animationcancel",n),i.removeEventListener("animationend",n)}}d("ANIMATION_END")},[i,d]),{isPresent:["mounted","unmountSuspended"].includes(f),ref:n.useCallback(e=>{a.current=e?getComputedStyle(e):null,o(e)},[])}}(t),o="function"==typeof r?r({present:i.isPresent}):n.Children.only(r),a=(0,l.s)(i.ref,function(e){var t,r;let n=null==(t=Object.getOwnPropertyDescriptor(e.props,"ref"))?void 0:t.get,i=n&&"isReactWarning"in n&&n.isReactWarning;return i?e.ref:(i=(n=null==(r=Object.getOwnPropertyDescriptor(e,"ref"))?void 0:r.get)&&"isReactWarning"in n&&n.isReactWarning)?e.props.ref:e.props.ref||e.ref}(o));return"function"==typeof r||i.isPresent?n.cloneElement(o,{ref:a}):null};function c(e){return(null==e?void 0:e.animationName)||"none"}s.displayName="Presence";var f=r(3655),d=r(4315),p=r(5845),h=r(1285),g=r(5155),v="Tabs",[m,y]=(0,o.A)(v,[a.RG]),b=(0,a.RG)(),[w,x]=m(v),S=n.forwardRef((e,t)=>{let{__scopeTabs:r,value:n,onValueChange:i,defaultValue:o,orientation:a="horizontal",dir:l,activationMode:u="automatic",...s}=e,c=(0,d.jH)(l),[m,y]=(0,p.i)({prop:n,onChange:i,defaultProp:null!=o?o:"",caller:v});return(0,g.jsx)(w,{scope:r,baseId:(0,h.B)(),value:m,onValueChange:y,orientation:a,dir:c,activationMode:u,children:(0,g.jsx)(f.sG.div,{dir:c,"data-orientation":a,...s,ref:t})})});S.displayName=v;var O="TabsList",E=n.forwardRef((e,t)=>{let{__scopeTabs:r,loop:n=!0,...i}=e,o=x(O,r),l=b(r);return(0,g.jsx)(a.bL,{asChild:!0,...l,orientation:o.orientation,dir:o.dir,loop:n,children:(0,g.jsx)(f.sG.div,{role:"tablist","aria-orientation":o.orientation,...i,ref:t})})});E.displayName=O;var C="TabsTrigger",M=n.forwardRef((e,t)=>{let{__scopeTabs:r,value:n,disabled:o=!1,...l}=e,u=x(C,r),s=b(r),c=_(u.baseId,n),d=P(u.baseId,n),p=n===u.value;return(0,g.jsx)(a.q7,{asChild:!0,...s,focusable:!o,active:p,children:(0,g.jsx)(f.sG.button,{type:"button",role:"tab","aria-selected":p,"aria-controls":d,"data-state":p?"active":"inactive","data-disabled":o?"":void 0,disabled:o,id:c,...l,ref:t,onMouseDown:(0,i.m)(e.onMouseDown,e=>{o||0!==e.button||!1!==e.ctrlKey?e.preventDefault():u.onValueChange(n)}),onKeyDown:(0,i.m)(e.onKeyDown,e=>{[" ","Enter"].includes(e.key)&&u.onValueChange(n)}),onFocus:(0,i.m)(e.onFocus,()=>{let e="manual"!==u.activationMode;p||o||!e||u.onValueChange(n)})})})});M.displayName=C;var k="TabsContent";function _(e,t){return"".concat(e,"-trigger-").concat(t)}function P(e,t){return"".concat(e,"-content-").concat(t)}n.forwardRef((e,t)=>{let{__scopeTabs:r,value:i,forceMount:o,children:a,...l}=e,u=x(k,r),c=_(u.baseId,i),d=P(u.baseId,i),p=i===u.value,h=n.useRef(p);return n.useEffect(()=>{let e=requestAnimationFrame(()=>h.current=!1);return()=>cancelAnimationFrame(e)},[]),(0,g.jsx)(s,{present:o||p,children:r=>{let{present:n}=r;return(0,g.jsx)(f.sG.div,{"data-state":p?"active":"inactive","data-orientation":u.orientation,role:"tabpanel","aria-labelledby":c,hidden:!n,id:d,tabIndex:0,...l,ref:t,style:{...e.style,animationDuration:h.current?"0s":void 0},children:n&&a})}})}).displayName=k;var A=S,j=E,T=M},52:(e,t,r)=>{"use strict";function n(e){return`Minified Redux error #${e}; visit https://redux.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}r.d(t,{HY:()=>s,Qd:()=>l,Tw:()=>f,Zz:()=>c,ve:()=>d,y$:()=>u});var i="function"==typeof Symbol&&Symbol.observable||"@@observable",o=()=>Math.random().toString(36).substring(7).split("").join("."),a={INIT:`@@redux/INIT${o()}`,REPLACE:`@@redux/REPLACE${o()}`,PROBE_UNKNOWN_ACTION:()=>`@@redux/PROBE_UNKNOWN_ACTION${o()}`};function l(e){if("object"!=typeof e||null===e)return!1;let t=e;for(;null!==Object.getPrototypeOf(t);)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t||null===Object.getPrototypeOf(e)}function u(e,t,r){if("function"!=typeof e)throw Error(n(2));if("function"==typeof t&&"function"==typeof r||"function"==typeof r&&"function"==typeof arguments[3])throw Error(n(0));if("function"==typeof t&&void 0===r&&(r=t,t=void 0),void 0!==r){if("function"!=typeof r)throw Error(n(1));return r(u)(e,t)}let o=e,s=t,c=new Map,f=c,d=0,p=!1;function h(){f===c&&(f=new Map,c.forEach((e,t)=>{f.set(t,e)}))}function g(){if(p)throw Error(n(3));return s}function v(e){if("function"!=typeof e)throw Error(n(4));if(p)throw Error(n(5));let t=!0;h();let r=d++;return f.set(r,e),function(){if(t){if(p)throw Error(n(6));t=!1,h(),f.delete(r),c=null}}}function m(e){if(!l(e))throw Error(n(7));if(void 0===e.type)throw Error(n(8));if("string"!=typeof e.type)throw Error(n(17));if(p)throw Error(n(9));try{p=!0,s=o(s,e)}finally{p=!1}return(c=f).forEach(e=>{e()}),e}return m({type:a.INIT}),{dispatch:m,subscribe:v,getState:g,replaceReducer:function(e){if("function"!=typeof e)throw Error(n(10));o=e,m({type:a.REPLACE})},[i]:function(){return{subscribe(e){if("object"!=typeof e||null===e)throw Error(n(11));function t(){e.next&&e.next(g())}return t(),{unsubscribe:v(t)}},[i](){return this}}}}}function s(e){let t,r=Object.keys(e),i={};for(let t=0;t{let t=i[e];if(void 0===t(void 0,{type:a.INIT}))throw Error(n(12));if(void 0===t(void 0,{type:a.PROBE_UNKNOWN_ACTION()}))throw Error(n(13))})}catch(e){t=e}return function(e={},r){if(t)throw t;let a=!1,l={};for(let t=0;te:1===e.length?e[0]:e.reduce((e,t)=>(...r)=>e(t(...r)))}function f(...e){return t=>(r,i)=>{let o=t(r,i),a=()=>{throw Error(n(15))},l={getState:o.getState,dispatch:(e,...t)=>a(e,...t)};return a=c(...e.map(e=>e(l)))(o.dispatch),{...o,dispatch:a}}}function d(e){return l(e)&&"type"in e&&"string"==typeof e.type}},177:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(5160);t.isArguments=function(e){return null!==e&&"object"==typeof e&&"[object Arguments]"===n.getTag(e)}},215:(e,t,r)=>{"use strict";r.d(t,{BZ:()=>eo,eE:()=>es,Xb:()=>ea,JG:()=>ed,A2:()=>ei,yn:()=>ec,Dn:()=>E,gL:()=>q,fl:()=>Y,R4:()=>Q,Re:()=>S,n4:()=>A});var n=r(8924),i=r(2183),o=r(7238),a=r(9827),l=r(356),u=r(8478),s=r(6377),c=r(8190),f=r(6523),d=r(530),p=r(1928),h=r(841),g=r(4968),v=r(2589),m=r(6124),y=r(5146),b=r(6670),w=r(5714),x=r(4013),S=e=>{var t=(0,o.fz)(e);return"horizontal"===t?"xAxis":"vertical"===t?"yAxis":"centric"===t?"angleAxis":"radiusAxis"},O=e=>e.tooltip.settings.axisId,E=e=>{var t=S(e),r=O(e);return(0,i.Hd)(e,t,r)},C=(0,n.Mz)([E,o.fz,i.um,u.iO,S],i.sr),M=(0,n.Mz)([e=>e.graphicalItems.cartesianItems,e=>e.graphicalItems.polarItems],(e,t)=>[...e,...t]),k=(0,n.Mz)([S,O],i.eo),_=(0,n.Mz)([M,E,k],i.ec),P=(0,n.Mz)([_],i.rj),A=(0,n.Mz)([P,l.LF],i.Nk),j=(0,n.Mz)([A,E,_],i.fb),T=(0,n.Mz)([E],i.S5),R=(0,n.Mz)([A,_,u.eC],i.MK),z=(0,n.Mz)([R,l.LF,S],i.pM),D=(0,n.Mz)([_],i.IO),I=(0,n.Mz)([A,E,D,S],i.kz),N=(0,n.Mz)([i.Kr,S,O],i.P9),L=(0,n.Mz)([N,S],i.Oz),F=(0,n.Mz)([i.gT,S,O],i.P9),$=(0,n.Mz)([F,S],i.q),B=(0,n.Mz)([i.$X,S,O],i.P9),V=(0,n.Mz)([B,S],i.bb),U=(0,n.Mz)([L,V,$],i.yi),H=(0,n.Mz)([E,T,z,I,U],i.wL),G=(0,n.Mz)([E,o.fz,A,j,u.eC,S,H],i.tP),Z=(0,n.Mz)([G,E,C],i.xp),W=(0,n.Mz)([E,G,Z,S],i.g1),K=e=>{var t=S(e),r=O(e);return(0,i.D5)(e,t,r,!1)},q=(0,n.Mz)([E,K],c.I),Y=(0,n.Mz)([E,C,W,q],i.Qn),X=(0,n.Mz)([o.fz,j,E,S],i.tF),J=(0,n.Mz)([o.fz,j,E,S],i.iv),Q=(0,n.Mz)([o.fz,E,C,Y,K,X,J,S],(e,t,r,n,i,o,l,u)=>{if(t){var{type:c}=t,f=(0,a._L)(e,u);if(n){var d="scaleBand"===r&&n.bandwidth?n.bandwidth()/2:2,p="category"===c&&n.bandwidth?n.bandwidth()/d:0;return(p="angleAxis"===u&&null!=i&&(null==i?void 0:i.length)>=2?2*(0,s.sA)(i[0]-i[1])*p:p,f&&l)?l.map((e,t)=>({coordinate:n(e)+p,value:e,index:t,offset:p})):n.domain().map((e,t)=>({coordinate:n(e)+p,value:o?o[e]:e,index:t,offset:p}))}}}),ee=(0,n.Mz)([f.xH,f.Hw,e=>e.tooltip.settings],(e,t,r)=>(0,f.$g)(r.shared,e,t)),et=e=>e.tooltip.settings.trigger,er=e=>e.tooltip.settings.defaultIndex,en=(0,n.Mz)([w.J,ee,et,er],p.i),ei=(0,n.Mz)([en,A],h.P),eo=(0,n.Mz)([Q,ei],d.E),ea=(0,n.Mz)([en],e=>{if(e)return e.dataKey}),el=(0,n.Mz)([w.J,ee,et,er],y.q),eu=(0,n.Mz)([v.Lp,v.A$,o.fz,m.HZ,Q,er,el,b.x],g.o),es=(0,n.Mz)([en,eu],(e,t)=>null!=e&&e.coordinate?e.coordinate:t),ec=(0,n.Mz)([en],e=>e.active),ef=(0,n.Mz)([el,ei,l.LF,E,eo,b.x,ee],x.N),ed=(0,n.Mz)([ef],e=>{if(null!=e)return Array.from(new Set(e.map(e=>e.payload).filter(e=>null!=e)))})},220:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.last=function(e){return e[e.length-1]}},241:(e,t,r)=>{e.exports=r(2434).sortBy},294:(e,t)=>{"use strict";var r="function"==typeof Symbol&&Symbol.for,n=r?Symbol.for("react.element"):60103,i=r?Symbol.for("react.portal"):60106,o=r?Symbol.for("react.fragment"):60107,a=r?Symbol.for("react.strict_mode"):60108,l=r?Symbol.for("react.profiler"):60114,u=r?Symbol.for("react.provider"):60109,s=r?Symbol.for("react.context"):60110,c=r?Symbol.for("react.async_mode"):60111,f=r?Symbol.for("react.concurrent_mode"):60111,d=r?Symbol.for("react.forward_ref"):60112,p=r?Symbol.for("react.suspense"):60113,h=(r&&Symbol.for("react.suspense_list"),r?Symbol.for("react.memo"):60115),g=r?Symbol.for("react.lazy"):60116;function v(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case n:switch(e=e.type){case c:case f:case o:case l:case a:case p:return e;default:switch(e=e&&e.$$typeof){case s:case d:case g:case h:case u:return e;default:return t}}case i:return t}}}r&&Symbol.for("react.block"),r&&Symbol.for("react.fundamental"),r&&Symbol.for("react.responder"),r&&Symbol.for("react.scope");t.isFragment=function(e){return v(e)===o}},330:(e,t,r)=>{"use strict";e.exports=r(294)},356:(e,t,r)=>{"use strict";r.d(t,{HS:()=>a,LF:()=>i});var n=r(8924),i=e=>e.chartData,o=(0,n.Mz)([i],e=>{var t=null!=e.chartData?e.chartData.length-1:0;return{chartData:e.chartData,computedData:e.computedData,dataEndIndex:t,dataStartIndex:0}}),a=(e,t,r,n)=>n?o(e):i(e)},379:(e,t,r)=>{"use strict";r.d(t,{J:()=>m,Z:()=>v});var n=r(2115),i=r(2596),o=r(9095),a=r(788),l=r(6377),u=r(5641),s=r(7238),c=["offset"],f=["labelRef"];function d(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;nnull!=e&&"function"==typeof e;function m(e){var t,{offset:r=5}=e,p=h({offset:r},d(e,c)),{viewBox:v,position:m,value:y,children:b,content:w,className:x="",textBreakAll:S,labelRef:O}=p,E=(0,s.sk)(),C=v||E;if(!C||(0,l.uy)(y)&&(0,l.uy)(b)&&!(0,n.isValidElement)(w)&&"function"!=typeof w)return null;if((0,n.isValidElement)(w)){var{labelRef:M}=p,k=d(p,f);return(0,n.cloneElement)(w,k)}if("function"==typeof w){if(t=(0,n.createElement)(w,p),(0,n.isValidElement)(t))return t}else t=(e=>{var{value:t,formatter:r}=e,n=(0,l.uy)(e.children)?t:e.children;return"function"==typeof r?r(n):n})(p);var _="cx"in C&&(0,l.Et)(C.cx),P=(0,a.J9)(p,!0);if(_&&("insideStart"===m||"insideEnd"===m||"end"===m))return((e,t,r)=>{let o,a;var s,c,{position:f,viewBox:d,offset:p,className:h}=e,{cx:v,cy:m,innerRadius:y,outerRadius:b,startAngle:w,endAngle:x,clockWise:S}=d,O=(y+b)/2,E=(o=w,a=x,(0,l.sA)(a-o)*Math.min(Math.abs(a-o),360)),C=E>=0?1:-1;"insideStart"===f?(s=w+C*p,c=S):"insideEnd"===f?(s=x-C*p,c=!S):"end"===f&&(s=x+C*p,c=S),c=E<=0?c:!c;var M=(0,u.IZ)(v,m,O,s),k=(0,u.IZ)(v,m,O,s+(c?1:-1)*359),_="M".concat(M.x,",").concat(M.y,"\n A").concat(O,",").concat(O,",0,1,").concat(+!c,",\n ").concat(k.x,",").concat(k.y),P=(0,l.uy)(e.id)?(0,l.NF)("recharts-radial-line-"):e.id;return n.createElement("text",g({},r,{dominantBaseline:"central",className:(0,i.$)("recharts-radial-bar-label",h)}),n.createElement("defs",null,n.createElement("path",{id:P,d:_})),n.createElement("textPath",{xlinkHref:"#".concat(P)},t))})(p,t,P);var A=_?(e=>{var{viewBox:t,offset:r,position:n}=e,{cx:i,cy:o,innerRadius:a,outerRadius:l,startAngle:s,endAngle:c}=t,f=(s+c)/2;if("outside"===n){var{x:d,y:p}=(0,u.IZ)(i,o,l+r,f);return{x:d,y:p,textAnchor:d>=i?"start":"end",verticalAnchor:"middle"}}if("center"===n)return{x:i,y:o,textAnchor:"middle",verticalAnchor:"middle"};if("centerTop"===n)return{x:i,y:o,textAnchor:"middle",verticalAnchor:"start"};if("centerBottom"===n)return{x:i,y:o,textAnchor:"middle",verticalAnchor:"end"};var{x:h,y:g}=(0,u.IZ)(i,o,(a+l)/2,f);return{x:h,y:g,textAnchor:"middle",verticalAnchor:"middle"}})(p):((e,t)=>{var{parentViewBox:r,offset:n,position:i}=e,{x:o,y:a,width:u,height:s}=t,c=s>=0?1:-1,f=c*n,d=c>0?"end":"start",p=c>0?"start":"end",g=u>=0?1:-1,v=g*n,m=g>0?"end":"start",y=g>0?"start":"end";if("top"===i)return h(h({},{x:o+u/2,y:a-c*n,textAnchor:"middle",verticalAnchor:d}),r?{height:Math.max(a-r.y,0),width:u}:{});if("bottom"===i)return h(h({},{x:o+u/2,y:a+s+f,textAnchor:"middle",verticalAnchor:p}),r?{height:Math.max(r.y+r.height-(a+s),0),width:u}:{});if("left"===i){var b={x:o-v,y:a+s/2,textAnchor:m,verticalAnchor:"middle"};return h(h({},b),r?{width:Math.max(b.x-r.x,0),height:s}:{})}if("right"===i){var w={x:o+u+v,y:a+s/2,textAnchor:y,verticalAnchor:"middle"};return h(h({},w),r?{width:Math.max(r.x+r.width-w.x,0),height:s}:{})}var x=r?{width:u,height:s}:{};return"insideLeft"===i?h({x:o+v,y:a+s/2,textAnchor:y,verticalAnchor:"middle"},x):"insideRight"===i?h({x:o+u-v,y:a+s/2,textAnchor:m,verticalAnchor:"middle"},x):"insideTop"===i?h({x:o+u/2,y:a+f,textAnchor:"middle",verticalAnchor:p},x):"insideBottom"===i?h({x:o+u/2,y:a+s-f,textAnchor:"middle",verticalAnchor:d},x):"insideTopLeft"===i?h({x:o+v,y:a+f,textAnchor:y,verticalAnchor:p},x):"insideTopRight"===i?h({x:o+u-v,y:a+f,textAnchor:m,verticalAnchor:p},x):"insideBottomLeft"===i?h({x:o+v,y:a+s-f,textAnchor:y,verticalAnchor:d},x):"insideBottomRight"===i?h({x:o+u-v,y:a+s-f,textAnchor:m,verticalAnchor:d},x):i&&"object"==typeof i&&((0,l.Et)(i.x)||(0,l._3)(i.x))&&((0,l.Et)(i.y)||(0,l._3)(i.y))?h({x:o+(0,l.F4)(i.x,u),y:a+(0,l.F4)(i.y,s),textAnchor:"end",verticalAnchor:"end"},x):h({x:o+u/2,y:a+s/2,textAnchor:"middle",verticalAnchor:"middle"},x)})(p,C);return n.createElement(o.E,g({ref:O,className:(0,i.$)("recharts-label",x)},P,A,{breakAll:S}),t)}m.displayName="Label";var y=e=>{var{cx:t,cy:r,angle:n,startAngle:i,endAngle:o,r:a,radius:u,innerRadius:s,outerRadius:c,x:f,y:d,top:p,left:h,width:g,height:v,clockWise:m,labelViewBox:y}=e;if(y)return y;if((0,l.Et)(g)&&(0,l.Et)(v)){if((0,l.Et)(f)&&(0,l.Et)(d))return{x:f,y:d,width:g,height:v};if((0,l.Et)(p)&&(0,l.Et)(h))return{x:p,y:h,width:g,height:v}}return(0,l.Et)(f)&&(0,l.Et)(d)?{x:f,y:d,width:0,height:0}:(0,l.Et)(t)&&(0,l.Et)(r)?{cx:t,cy:r,startAngle:i||n||0,endAngle:o||n||0,innerRadius:s||0,outerRadius:c||u||a||0,clockWise:m}:e.viewBox?e.viewBox:void 0};m.parseViewBox=y,m.renderCallByParent=function(e,t){var r=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(!e||!e.children&&r&&!e.label)return null;var{children:i,labelRef:o}=e,u=y(e),s=(0,a.aS)(i,m).map((e,r)=>(0,n.cloneElement)(e,{viewBox:t||u,key:"label-".concat(r)}));return r?[((e,t,r)=>{if(!e)return null;var i={viewBox:t,labelRef:r};return!0===e?n.createElement(m,g({key:"label-implicit"},i)):(0,l.vh)(e)?n.createElement(m,g({key:"label-implicit",value:e},i)):(0,n.isValidElement)(e)?e.type===m?(0,n.cloneElement)(e,h({key:"label-implicit"},i)):n.createElement(m,g({key:"label-implicit",content:e},i)):v(e)?n.createElement(m,g({key:"label-implicit",content:e},i)):e&&"object"==typeof e?n.createElement(m,g({},e,{key:"label-implicit"},i)):null})(e.label,t||u,o),...s]:s}},400:(e,t,r)=>{e.exports=r(2962).throttle},402:(e,t,r)=>{"use strict";r.d(t,{_G:()=>c,be:()=>a,gB:()=>p,gl:()=>w});var n=r(2115),i=r(5143),o=r(8266);function a(e,t,r){let n=e.slice();return n.splice(r<0?n.length+r:r,0,n.splice(t,1)[0]),n}function l(e){return null!==e&&e>=0}let u=e=>{let{rects:t,activeIndex:r,overIndex:n,index:i}=e,o=a(t,n,r),l=t[i],u=o[i];return u&&l?{x:u.left-l.left,y:u.top-l.top,scaleX:u.width/l.width,scaleY:u.height/l.height}:null},s={scaleX:1,scaleY:1},c=e=>{var t;let{activeIndex:r,activeNodeRect:n,index:i,rects:o,overIndex:a}=e,l=null!=(t=o[r])?t:n;if(!l)return null;if(i===r){let e=o[a];return e?{x:0,y:rr&&i<=a?{x:0,y:-l.height-u,...s}:i=a?{x:0,y:l.height+u,...s}:{x:0,y:0,...s}},f="Sortable",d=n.createContext({activeIndex:-1,containerId:f,disableTransforms:!1,items:[],overIndex:-1,useDragOverlay:!1,sortedRects:[],strategy:u,disabled:{draggable:!1,droppable:!1}});function p(e){let{children:t,id:r,items:a,strategy:l=u,disabled:s=!1}=e,{active:c,dragOverlay:p,droppableRects:h,over:g,measureDroppableContainers:v}=(0,i.fF)(),m=(0,o.YG)(f,r),y=null!==p.rect,b=(0,n.useMemo)(()=>a.map(e=>"object"==typeof e&&"id"in e?e.id:e),[a]),w=null!=c,x=c?b.indexOf(c.id):-1,S=g?b.indexOf(g.id):-1,O=(0,n.useRef)(b),E=!function(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let r=0;r{E&&w&&v(b)},[E,b,w,v]),(0,n.useEffect)(()=>{O.current=b},[b]);let k=(0,n.useMemo)(()=>({activeIndex:x,containerId:m,disabled:M,disableTransforms:C,items:b,overIndex:S,useDragOverlay:y,sortedRects:b.reduce((e,t,r)=>{let n=h.get(t);return n&&(e[r]=n),e},Array(b.length)),strategy:l}),[x,m,M.draggable,M.droppable,C,b,S,h,y,l]);return n.createElement(d.Provider,{value:k},t)}let h=e=>{let{id:t,items:r,activeIndex:n,overIndex:i}=e;return a(r,n,i).indexOf(t)},g=e=>{let{containerId:t,isSorting:r,wasDragging:n,index:i,items:o,newIndex:a,previousItems:l,previousContainerId:u,transition:s}=e;return!!s&&!!n&&(l===o||i!==a)&&(!!r||a!==i&&t===u)},v={duration:200,easing:"ease"},m="transform",y=o.Ks.Transition.toString({property:m,duration:0,easing:"linear"}),b={roleDescription:"sortable"};function w(e){var t,r,a,u;let{animateLayoutChanges:s=g,attributes:c,disabled:f,data:p,getNewIndex:w=h,id:x,strategy:S,resizeObserverConfig:O,transition:E=v}=e,{items:C,containerId:M,activeIndex:k,disabled:_,disableTransforms:P,sortedRects:A,overIndex:j,useDragOverlay:T,strategy:R}=(0,n.useContext)(d),z=(t=f,r=_,"boolean"==typeof t?{draggable:t,droppable:!1}:{draggable:null!=(a=null==t?void 0:t.draggable)?a:r.draggable,droppable:null!=(u=null==t?void 0:t.droppable)?u:r.droppable}),D=C.indexOf(x),I=(0,n.useMemo)(()=>({sortable:{containerId:M,index:D,items:C},...p}),[M,p,D,C]),N=(0,n.useMemo)(()=>C.slice(C.indexOf(x)),[C,x]),{rect:L,node:F,isOver:$,setNodeRef:B}=(0,i.zM)({id:x,data:I,disabled:z.droppable,resizeObserverConfig:{updateMeasurementsFor:N,...O}}),{active:V,activatorEvent:U,activeNodeRect:H,attributes:G,setNodeRef:Z,listeners:W,isDragging:K,over:q,setActivatorNodeRef:Y,transform:X}=(0,i.PM)({id:x,data:I,attributes:{...b,...c},disabled:z.draggable}),J=(0,o.jn)(B,Z),Q=!!V,ee=Q&&!P&&l(k)&&l(j),et=!T&&K,er=et&&ee?X:null,en=ee?null!=er?er:(null!=S?S:R)({rects:A,activeNodeRect:H,activeIndex:k,overIndex:j,index:D}):null,ei=l(k)&&l(j)?w({id:x,items:C,activeIndex:k,overIndex:j}):D,eo=null==V?void 0:V.id,ea=(0,n.useRef)({activeId:eo,items:C,newIndex:ei,containerId:M}),el=C!==ea.current.items,eu=s({active:V,containerId:M,isDragging:K,isSorting:Q,id:x,index:D,items:C,newIndex:ea.current.newIndex,previousItems:ea.current.items,previousContainerId:ea.current.containerId,transition:E,wasDragging:null!=ea.current.activeId}),es=function(e){let{disabled:t,index:r,node:a,rect:l}=e,[u,s]=(0,n.useState)(null),c=(0,n.useRef)(r);return(0,o.Es)(()=>{if(!t&&r!==c.current&&a.current){let e=l.current;if(e){let t=(0,i.Sj)(a.current,{ignoreTransform:!0}),r={x:e.left-t.left,y:e.top-t.top,scaleX:e.width/t.width,scaleY:e.height/t.height};(r.x||r.y)&&s(r)}}r!==c.current&&(c.current=r)},[t,r,a,l]),(0,n.useEffect)(()=>{u&&s(null)},[u]),u}({disabled:!eu,index:D,node:F,rect:L});return(0,n.useEffect)(()=>{Q&&ea.current.newIndex!==ei&&(ea.current.newIndex=ei),M!==ea.current.containerId&&(ea.current.containerId=M),C!==ea.current.items&&(ea.current.items=C)},[Q,ei,M,C]),(0,n.useEffect)(()=>{if(eo===ea.current.activeId)return;if(null!=eo&&null==ea.current.activeId){ea.current.activeId=eo;return}let e=setTimeout(()=>{ea.current.activeId=eo},50);return()=>clearTimeout(e)},[eo]),{active:V,activeIndex:k,attributes:G,data:I,rect:L,index:D,newIndex:ei,items:C,isOver:$,isSorting:Q,isDragging:K,listeners:W,node:F,overIndex:j,over:q,setNodeRef:J,setActivatorNodeRef:Y,setDroppableNodeRef:B,setDraggableNodeRef:Z,transform:null!=es?es:en,transition:es||el&&ea.current.newIndex===D?y:(!et||(0,o.kx)(U))&&E&&(Q||eu)?o.Ks.Transition.toString({...E,property:m}):void 0}}i.vL.Down,i.vL.Right,i.vL.Up,i.vL.Left},464:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("rabbit",[["path",{d:"M13 16a3 3 0 0 1 2.24 5",key:"1epib5"}],["path",{d:"M18 12h.01",key:"yjnet6"}],["path",{d:"M18 21h-8a4 4 0 0 1-4-4 7 7 0 0 1 7-7h.2L9.6 6.4a1 1 0 1 1 2.8-2.8L15.8 7h.2c3.3 0 6 2.7 6 6v1a2 2 0 0 1-2 2h-1a3 3 0 0 0-3 3",key:"ue9ozu"}],["path",{d:"M20 8.54V4a2 2 0 1 0-4 0v3",key:"49iql8"}],["path",{d:"M7.612 12.524a3 3 0 1 0-1.6 4.3",key:"1e33i0"}]])},512:(e,t,r)=>{e.exports=r(7547).uniqBy},530:(e,t,r)=>{"use strict";r.d(t,{E:()=>i});var n=r(6377),i=(e,t)=>{var r,i=Number(t);if(!(0,n.M8)(i)&&null!=t)return i>=0?null==e||null==(r=e[i])?void 0:r.value:void 0}},574:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chart-area",[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"M7 11.207a.5.5 0 0 1 .146-.353l2-2a.5.5 0 0 1 .708 0l3.292 3.292a.5.5 0 0 0 .708 0l4.292-4.292a.5.5 0 0 1 .854.353V16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1z",key:"q0gr47"}]])},646:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("circle-check-big",[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]])},656:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8179),i=r(9279);t.isArrayLikeObject=function(e){return i.isObjectLike(e)&&n.isArrayLike(e)}},668:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isSymbol=function(e){return"symbol"==typeof e||e instanceof Symbol}},675:(e,t,r)=>{"use strict";r.d(t,{R:()=>n});var n=function(e,t){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i{"use strict";r.d(t,{I:()=>B});var n=r(2115);function i(){}function o(e,t,r){e._context.bezierCurveTo((2*e._x0+e._x1)/3,(2*e._y0+e._y1)/3,(e._x0+2*e._x1)/3,(e._y0+2*e._y1)/3,(e._x0+4*e._x1+t)/6,(e._y0+4*e._y1+r)/6)}function a(e){this._context=e}function l(e){this._context=e}function u(e){this._context=e}a.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:o(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e*=1,t*=1,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:o(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}},l.prototype={areaStart:i,areaEnd:i,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(e,t){switch(e*=1,t*=1,this._point){case 0:this._point=1,this._x2=e,this._y2=t;break;case 1:this._point=2,this._x3=e,this._y3=t;break;case 2:this._point=3,this._x4=e,this._y4=t,this._context.moveTo((this._x0+4*this._x1+e)/6,(this._y0+4*this._y1+t)/6);break;default:o(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}},u.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e*=1,t*=1,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var r=(this._x0+4*this._x1+e)/6,n=(this._y0+4*this._y1+t)/6;this._line?this._context.lineTo(r,n):this._context.moveTo(r,n);break;case 3:this._point=4;default:o(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};class s{constructor(e,t){this._context=e,this._x=t}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line}point(e,t){switch(e*=1,t*=1,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._x?this._context.bezierCurveTo(this._x0=(this._x0+e)/2,this._y0,this._x0,t,e,t):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+t)/2,e,this._y0,e,t)}this._x0=e,this._y0=t}}function c(e){this._context=e}function f(e){this._context=e}function d(e){return new f(e)}c.prototype={areaStart:i,areaEnd:i,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(e,t){e*=1,t*=1,this._point?this._context.lineTo(e,t):(this._point=1,this._context.moveTo(e,t))}};function p(e,t,r){var n=e._x1-e._x0,i=t-e._x1,o=(e._y1-e._y0)/(n||i<0&&-0),a=(r-e._y1)/(i||n<0&&-0);return((o<0?-1:1)+(a<0?-1:1))*Math.min(Math.abs(o),Math.abs(a),.5*Math.abs((o*i+a*n)/(n+i)))||0}function h(e,t){var r=e._x1-e._x0;return r?(3*(e._y1-e._y0)/r-t)/2:t}function g(e,t,r){var n=e._x0,i=e._y0,o=e._x1,a=e._y1,l=(o-n)/3;e._context.bezierCurveTo(n+l,i+l*t,o-l,a-l*r,o,a)}function v(e){this._context=e}function m(e){this._context=new y(e)}function y(e){this._context=e}function b(e){this._context=e}function w(e){var t,r,n=e.length-1,i=Array(n),o=Array(n),a=Array(n);for(i[0]=0,o[0]=2,a[0]=e[0]+2*e[1],t=1;t=0;--t)i[t]=(a[t]-i[t+1])/o[t];for(t=0,o[n-1]=(e[n]+i[n-1])/2;t=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(e,t){switch(e*=1,t*=1,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,t),this._context.lineTo(e,t);else{var r=this._x*(1-this._t)+e*this._t;this._context.lineTo(r,this._y),this._context.lineTo(r,t)}}this._x=e,this._y=t}};var S=r(9819),O=r(5654),E=r(1847);function C(e){return e[0]}function M(e){return e[1]}function k(e,t){var r=(0,O.A)(!0),n=null,i=d,o=null,a=(0,E.i)(l);function l(l){var u,s,c,f=(l=(0,S.A)(l)).length,d=!1;for(null==n&&(o=i(c=a())),u=0;u<=f;++u)!(u=f;--d)l.point(m[d],y[d]);l.lineEnd(),l.areaEnd()}v&&(m[c]=+e(p,c,s),y[c]=+t(p,c,s),l.point(n?+n(p,c,s):m[c],r?+r(p,c,s):y[c]))}if(h)return l=null,h+""||null}function c(){return k().defined(i).curve(a).context(o)}return e="function"==typeof e?e:void 0===e?C:(0,O.A)(+e),t="function"==typeof t?t:void 0===t?(0,O.A)(0):(0,O.A)(+t),r="function"==typeof r?r:void 0===r?M:(0,O.A)(+r),s.x=function(t){return arguments.length?(e="function"==typeof t?t:(0,O.A)(+t),n=null,s):e},s.x0=function(t){return arguments.length?(e="function"==typeof t?t:(0,O.A)(+t),s):e},s.x1=function(e){return arguments.length?(n=null==e?null:"function"==typeof e?e:(0,O.A)(+e),s):n},s.y=function(e){return arguments.length?(t="function"==typeof e?e:(0,O.A)(+e),r=null,s):t},s.y0=function(e){return arguments.length?(t="function"==typeof e?e:(0,O.A)(+e),s):t},s.y1=function(e){return arguments.length?(r=null==e?null:"function"==typeof e?e:(0,O.A)(+e),s):r},s.lineX0=s.lineY0=function(){return c().x(e).y(t)},s.lineY1=function(){return c().x(e).y(r)},s.lineX1=function(){return c().x(n).y(t)},s.defined=function(e){return arguments.length?(i="function"==typeof e?e:(0,O.A)(!!e),s):i},s.curve=function(e){return arguments.length?(a=e,null!=o&&(l=a(o)),s):a},s.context=function(e){return arguments.length?(null==e?o=l=null:l=a(o=e),s):o},s}var P=r(2596),A=r(3597),j=r(788),T=r(6377),R=r(8892);function z(){return(z=Object.assign?Object.assign.bind():function(e){for(var t=1;t(0,R.H)(e.x)&&(0,R.H)(e.y),F=e=>e.x,$=e=>e.y,B=e=>{var{className:t,points:r,path:i,pathRef:o}=e;if((!r||!r.length)&&!i)return null;var a=r&&r.length?(e=>{var t,{type:r="linear",points:n=[],baseLine:i,layout:o,connectNulls:a=!1}=e,l=((e,t)=>{if("function"==typeof e)return e;var r="curve".concat((0,T.Zb)(e));return("curveMonotone"===r||"curveBump"===r)&&t?N["".concat(r).concat("vertical"===t?"Y":"X")]:N[r]||d})(r,o),u=a?n.filter(L):n;if(Array.isArray(i)){var s=a?i.filter(e=>L(e)):i,c=u.map((e,t)=>I(I({},e),{},{base:s[t]}));return(t="vertical"===o?_().y($).x1(F).x0(e=>e.base.x):_().x(F).y1($).y0(e=>e.base.y)).defined(L).curve(l),t(c)}return(t="vertical"===o&&(0,T.Et)(i)?_().y($).x1(F).x0(i):(0,T.Et)(i)?_().x(F).y1($).y0(i):k().x(F).y($)).defined(L).curve(l),t(u)})(e):i;return n.createElement("path",z({},(0,j.J9)(e,!1),(0,A._U)(e),{className:(0,P.$)("recharts-curve",t),d:null===a?void 0:a,ref:o}))}},788:(e,t,r)=>{"use strict";r.d(t,{J9:()=>g,aS:()=>p,y$:()=>h});var n=r(5672),i=r.n(n),o=r(2115),a=r(330),l=r(6377),u=r(3597),s=e=>"string"==typeof e?e:e?e.displayName||e.name||"Component":"",c=null,f=null,d=e=>{if(e===c&&Array.isArray(f))return f;var t=[];return o.Children.forEach(e,e=>{(0,l.uy)(e)||((0,a.isFragment)(e)?t=t.concat(d(e.props.children)):t.push(e))}),f=t,c=e,t};function p(e,t){var r=[],n=[];return n=Array.isArray(t)?t.map(e=>s(e)):[s(t)],d(e).forEach(e=>{var t=i()(e,"type.displayName")||i()(e,"type.name");-1!==n.indexOf(t)&&r.push(e)}),r}var h=e=>!e||"object"!=typeof e||!("clipDot"in e)||!!e.clipDot,g=(e,t,r)=>{if(!e||"function"==typeof e||"boolean"==typeof e)return null;var n=e;if((0,o.isValidElement)(e)&&(n=e.props),"object"!=typeof n&&"function"!=typeof n)return null;var i={};return Object.keys(n).forEach(e=>{var o;((e,t,r,n)=>{var i,o=null!=(i=n&&(null===u.VU||void 0===u.VU?void 0:u.VU[n]))?i:[];return t.startsWith("data-")||"function"!=typeof e&&(n&&o.includes(t)||u.QQ.includes(t))||r&&u.j2.includes(t)})(null==(o=n)?void 0:o[e],e,t,r)&&(i[e]=n[e])}),i}},841:(e,t,r)=>{"use strict";r.d(t,{P:()=>i});var n=r(8892),i=(e,t)=>{var r=null==e?void 0:e.index;if(null==r)return null;var i=Number(r);if(!(0,n.H)(i))return r;var o=Infinity;return t.length>0&&(o=t.length-1),String(Math.max(0,Math.min(i,o)))}},885:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isTypedArray=function(e){return ArrayBuffer.isView(e)&&!(e instanceof DataView)}},921:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(7040),i=r(4545),o=r(6200),a=r(4072);t.get=function e(t,r,l){if(null==t)return l;switch(typeof r){case"string":{if(n.isUnsafeProperty(r))return l;let o=t[r];if(void 0===o)if(i.isDeepKey(r))return e(t,a.toPath(r),l);else return l;return o}case"number":case"symbol":{"number"==typeof r&&(r=o.toKey(r));let e=t[r];if(void 0===e)return l;return e}default:{if(Array.isArray(r)){var u=t,s=r,c=l;if(0===s.length)return c;let e=u;for(let t=0;t{"use strict";r.d(t,{b:()=>l});var n=r(2115),i=r(3655),o=r(5155),a=n.forwardRef((e,t)=>(0,o.jsx)(i.sG.label,{...e,ref:t,onMouseDown:t=>{var r;t.target.closest("button, input, select, textarea")||(null==(r=e.onMouseDown)||r.call(e,t),!t.defaultPrevented&&t.detail>1&&t.preventDefault())}}));a.displayName="Label";var l=a},972:(e,t,r)=>{"use strict";r.d(t,{C:()=>l,U:()=>u});var n=r(8924),i=r(6124),o=r(2589),a=r(6377),l=e=>e.brush,u=(0,n.Mz)([l,i.HZ,o.HK],(e,t,r)=>({height:e.height,x:(0,a.Et)(e.x)?e.x:t.left,y:(0,a.Et)(e.y)?e.y:t.top+t.height+t.brushBottom-((null==r?void 0:r.bottom)||0),width:(0,a.Et)(e.width)?e.width:t.width}))},1032:(e,t,r)=>{"use strict";function n(e,t){return"function"==typeof e?e(t):e}function i(e,t){return r=>{t.setState(t=>({...t,[e]:n(r,t[e])}))}}function o(e){return e instanceof Function}function a(e,t,r){let n,i=[];return o=>{let a,l;r.key&&r.debug&&(a=Date.now());let u=e(o);if(!(u.length!==i.length||u.some((e,t)=>i[t]!==e)))return n;if(i=u,r.key&&r.debug&&(l=Date.now()),n=t(...u),null==r||null==r.onChange||r.onChange(n),r.key&&r.debug&&null!=r&&r.debug()){let e=Math.round((Date.now()-a)*100)/100,t=Math.round((Date.now()-l)*100)/100,n=t/16,i=(e,t)=>{for(e=String(e);e.length{var r;return null!=(r=null==e?void 0:e.debugAll)?r:e[t]},key:!1,onChange:n}}r.d(t,{HT:()=>H,ZR:()=>U,h5:()=>Y,hM:()=>K,kQ:()=>Z,kW:()=>q,oS:()=>W});let u="debugHeaders";function s(e,t,r){var n;let i={id:null!=(n=r.id)?n:t.id,column:t,index:r.index,isPlaceholder:!!r.isPlaceholder,placeholderId:r.placeholderId,depth:r.depth,subHeaders:[],colSpan:0,rowSpan:0,headerGroup:null,getLeafHeaders:()=>{let e=[],t=r=>{r.subHeaders&&r.subHeaders.length&&r.subHeaders.map(t),e.push(r)};return t(i),e},getContext:()=>({table:e,header:i,column:t})};return e._features.forEach(t=>{null==t.createHeader||t.createHeader(i,e)}),i}function c(e,t,r,n){var i,o;let a=0,l=function(e,t){void 0===t&&(t=1),a=Math.max(a,t),e.filter(e=>e.getIsVisible()).forEach(e=>{var r;null!=(r=e.columns)&&r.length&&l(e.columns,t+1)},0)};l(e);let u=[],c=(e,t)=>{let i={depth:t,id:[n,`${t}`].filter(Boolean).join("_"),headers:[]},o=[];e.forEach(e=>{let a,l=[...o].reverse()[0],u=e.column.depth===i.depth,c=!1;if(u&&e.column.parent?a=e.column.parent:(a=e.column,c=!0),l&&(null==l?void 0:l.column)===a)l.subHeaders.push(e);else{let i=s(r,a,{id:[n,t,a.id,null==e?void 0:e.id].filter(Boolean).join("_"),isPlaceholder:c,placeholderId:c?`${o.filter(e=>e.column===a).length}`:void 0,depth:t,index:o.length});i.subHeaders.push(e),o.push(i)}i.headers.push(e),e.headerGroup=i}),u.push(i),t>0&&c(o,t-1)};c(t.map((e,t)=>s(r,e,{depth:a,index:t})),a-1),u.reverse();let f=e=>e.filter(e=>e.column.getIsVisible()).map(e=>{let t=0,r=0,n=[0];return e.subHeaders&&e.subHeaders.length?(n=[],f(e.subHeaders).forEach(e=>{let{colSpan:r,rowSpan:i}=e;t+=r,n.push(i)})):t=1,r+=Math.min(...n),e.colSpan=t,e.rowSpan=r,{colSpan:t,rowSpan:r}});return f(null!=(i=null==(o=u[0])?void 0:o.headers)?i:[]),u}let f=(e,t,r,n,i,o,u)=>{let s={id:t,index:n,original:r,depth:i,parentId:u,_valuesCache:{},_uniqueValuesCache:{},getValue:t=>{if(s._valuesCache.hasOwnProperty(t))return s._valuesCache[t];let r=e.getColumn(t);if(null!=r&&r.accessorFn)return s._valuesCache[t]=r.accessorFn(s.original,n),s._valuesCache[t]},getUniqueValues:t=>{if(s._uniqueValuesCache.hasOwnProperty(t))return s._uniqueValuesCache[t];let r=e.getColumn(t);if(null!=r&&r.accessorFn)return r.columnDef.getUniqueValues?s._uniqueValuesCache[t]=r.columnDef.getUniqueValues(s.original,n):s._uniqueValuesCache[t]=[s.getValue(t)],s._uniqueValuesCache[t]},renderValue:t=>{var r;return null!=(r=s.getValue(t))?r:e.options.renderFallbackValue},subRows:null!=o?o:[],getLeafRows:()=>(function(e,t){let r=[],n=e=>{e.forEach(e=>{r.push(e);let i=t(e);null!=i&&i.length&&n(i)})};return n(e),r})(s.subRows,e=>e.subRows),getParentRow:()=>s.parentId?e.getRow(s.parentId,!0):void 0,getParentRows:()=>{let e=[],t=s;for(;;){let r=t.getParentRow();if(!r)break;e.push(r),t=r}return e.reverse()},getAllCells:a(()=>[e.getAllLeafColumns()],t=>t.map(t=>(function(e,t,r,n){let i={id:`${t.id}_${r.id}`,row:t,column:r,getValue:()=>t.getValue(n),renderValue:()=>{var t;return null!=(t=i.getValue())?t:e.options.renderFallbackValue},getContext:a(()=>[e,r,t,i],(e,t,r,n)=>({table:e,column:t,row:r,cell:n,getValue:n.getValue,renderValue:n.renderValue}),l(e.options,"debugCells","cell.getContext"))};return e._features.forEach(n=>{null==n.createCell||n.createCell(i,r,t,e)},{}),i})(e,s,t,t.id)),l(e.options,"debugRows","getAllCells")),_getAllCellsByColumnId:a(()=>[s.getAllCells()],e=>e.reduce((e,t)=>(e[t.column.id]=t,e),{}),l(e.options,"debugRows","getAllCellsByColumnId"))};for(let t=0;t{var n,i;let o=null==r||null==(n=r.toString())?void 0:n.toLowerCase();return!!(null==(i=e.getValue(t))||null==(i=i.toString())||null==(i=i.toLowerCase())?void 0:i.includes(o))};d.autoRemove=e=>S(e);let p=(e,t,r)=>{var n;return!!(null==(n=e.getValue(t))||null==(n=n.toString())?void 0:n.includes(r))};p.autoRemove=e=>S(e);let h=(e,t,r)=>{var n;return(null==(n=e.getValue(t))||null==(n=n.toString())?void 0:n.toLowerCase())===(null==r?void 0:r.toLowerCase())};h.autoRemove=e=>S(e);let g=(e,t,r)=>{var n;return null==(n=e.getValue(t))?void 0:n.includes(r)};g.autoRemove=e=>S(e);let v=(e,t,r)=>!r.some(r=>{var n;return!(null!=(n=e.getValue(t))&&n.includes(r))});v.autoRemove=e=>S(e)||!(null!=e&&e.length);let m=(e,t,r)=>r.some(r=>{var n;return null==(n=e.getValue(t))?void 0:n.includes(r)});m.autoRemove=e=>S(e)||!(null!=e&&e.length);let y=(e,t,r)=>e.getValue(t)===r;y.autoRemove=e=>S(e);let b=(e,t,r)=>e.getValue(t)==r;b.autoRemove=e=>S(e);let w=(e,t,r)=>{let[n,i]=r,o=e.getValue(t);return o>=n&&o<=i};w.resolveFilterValue=e=>{let[t,r]=e,n="number"!=typeof t?parseFloat(t):t,i="number"!=typeof r?parseFloat(r):r,o=null===t||Number.isNaN(n)?-1/0:n,a=null===r||Number.isNaN(i)?1/0:i;if(o>a){let e=o;o=a,a=e}return[o,a]},w.autoRemove=e=>S(e)||S(e[0])&&S(e[1]);let x={includesString:d,includesStringSensitive:p,equalsString:h,arrIncludes:g,arrIncludesAll:v,arrIncludesSome:m,equals:y,weakEquals:b,inNumberRange:w};function S(e){return null==e||""===e}function O(e,t,r){return!!e&&!!e.autoRemove&&e.autoRemove(t,r)||void 0===t||"string"==typeof t&&!t}let E={sum:(e,t,r)=>r.reduce((t,r)=>{let n=r.getValue(e);return t+("number"==typeof n?n:0)},0),min:(e,t,r)=>{let n;return r.forEach(t=>{let r=t.getValue(e);null!=r&&(n>r||void 0===n&&r>=r)&&(n=r)}),n},max:(e,t,r)=>{let n;return r.forEach(t=>{let r=t.getValue(e);null!=r&&(n=r)&&(n=r)}),n},extent:(e,t,r)=>{let n,i;return r.forEach(t=>{let r=t.getValue(e);null!=r&&(void 0===n?r>=r&&(n=i=r):(n>r&&(n=r),i{let r=0,n=0;if(t.forEach(t=>{let i=t.getValue(e);null!=i&&(i*=1)>=i&&(++r,n+=i)}),r)return n/r},median:(e,t)=>{if(!t.length)return;let r=t.map(t=>t.getValue(e));if(!function(e){return Array.isArray(e)&&e.every(e=>"number"==typeof e)}(r))return;if(1===r.length)return r[0];let n=Math.floor(r.length/2),i=r.sort((e,t)=>e-t);return r.length%2!=0?i[n]:(i[n-1]+i[n])/2},unique:(e,t)=>Array.from(new Set(t.map(t=>t.getValue(e))).values()),uniqueCount:(e,t)=>new Set(t.map(t=>t.getValue(e))).size,count:(e,t)=>t.length},C=()=>({left:[],right:[]}),M={size:150,minSize:20,maxSize:Number.MAX_SAFE_INTEGER},k=()=>({startOffset:null,startSize:null,deltaOffset:null,deltaPercentage:null,isResizingColumn:!1,columnSizingStart:[]}),_=null;function P(e){return"touchstart"===e.type}function A(e,t){return t?"center"===t?e.getCenterVisibleLeafColumns():"left"===t?e.getLeftVisibleLeafColumns():e.getRightVisibleLeafColumns():e.getVisibleLeafColumns()}let j=()=>({pageIndex:0,pageSize:10}),T=()=>({top:[],bottom:[]}),R=(e,t,r,n,i)=>{var o;let a=i.getRow(t,!0);r?(a.getCanMultiSelect()||Object.keys(e).forEach(t=>delete e[t]),a.getCanSelect()&&(e[t]=!0)):delete e[t],n&&null!=(o=a.subRows)&&o.length&&a.getCanSelectSubRows()&&a.subRows.forEach(t=>R(e,t.id,r,n,i))};function z(e,t){let r=e.getState().rowSelection,n=[],i={},o=function(e,t){return e.map(e=>{var t;let a=D(e,r);if(a&&(n.push(e),i[e.id]=e),null!=(t=e.subRows)&&t.length&&(e={...e,subRows:o(e.subRows)}),a)return e}).filter(Boolean)};return{rows:o(t.rows),flatRows:n,rowsById:i}}function D(e,t){var r;return null!=(r=t[e.id])&&r}function I(e,t,r){var n;if(!(null!=(n=e.subRows)&&n.length))return!1;let i=!0,o=!1;return e.subRows.forEach(e=>{if((!o||i)&&(e.getCanSelect()&&(D(e,t)?o=!0:i=!1),e.subRows&&e.subRows.length)){let r=I(e,t);"all"===r?o=!0:("some"===r&&(o=!0),i=!1)}}),i?"all":!!o&&"some"}let N=/([0-9]+)/gm;function L(e,t){return e===t?0:e>t?1:-1}function F(e){return"number"==typeof e?isNaN(e)||e===1/0||e===-1/0?"":String(e):"string"==typeof e?e:""}function $(e,t){let r=e.split(N).filter(Boolean),n=t.split(N).filter(Boolean);for(;r.length&&n.length;){let e=r.shift(),t=n.shift(),i=parseInt(e,10),o=parseInt(t,10),a=[i,o].sort();if(isNaN(a[0])){if(e>t)return 1;if(t>e)return -1;continue}if(isNaN(a[1]))return isNaN(i)?-1:1;if(i>o)return 1;if(o>i)return -1}return r.length-n.length}let B={alphanumeric:(e,t,r)=>$(F(e.getValue(r)).toLowerCase(),F(t.getValue(r)).toLowerCase()),alphanumericCaseSensitive:(e,t,r)=>$(F(e.getValue(r)),F(t.getValue(r))),text:(e,t,r)=>L(F(e.getValue(r)).toLowerCase(),F(t.getValue(r)).toLowerCase()),textCaseSensitive:(e,t,r)=>L(F(e.getValue(r)),F(t.getValue(r))),datetime:(e,t,r)=>{let n=e.getValue(r),i=t.getValue(r);return n>i?1:nL(e.getValue(r),t.getValue(r))},V=[{createTable:e=>{e.getHeaderGroups=a(()=>[e.getAllColumns(),e.getVisibleLeafColumns(),e.getState().columnPinning.left,e.getState().columnPinning.right],(t,r,n,i)=>{var o,a;let l=null!=(o=null==n?void 0:n.map(e=>r.find(t=>t.id===e)).filter(Boolean))?o:[],u=null!=(a=null==i?void 0:i.map(e=>r.find(t=>t.id===e)).filter(Boolean))?a:[];return c(t,[...l,...r.filter(e=>!(null!=n&&n.includes(e.id))&&!(null!=i&&i.includes(e.id))),...u],e)},l(e.options,u,"getHeaderGroups")),e.getCenterHeaderGroups=a(()=>[e.getAllColumns(),e.getVisibleLeafColumns(),e.getState().columnPinning.left,e.getState().columnPinning.right],(t,r,n,i)=>c(t,r=r.filter(e=>!(null!=n&&n.includes(e.id))&&!(null!=i&&i.includes(e.id))),e,"center"),l(e.options,u,"getCenterHeaderGroups")),e.getLeftHeaderGroups=a(()=>[e.getAllColumns(),e.getVisibleLeafColumns(),e.getState().columnPinning.left],(t,r,n)=>{var i;return c(t,null!=(i=null==n?void 0:n.map(e=>r.find(t=>t.id===e)).filter(Boolean))?i:[],e,"left")},l(e.options,u,"getLeftHeaderGroups")),e.getRightHeaderGroups=a(()=>[e.getAllColumns(),e.getVisibleLeafColumns(),e.getState().columnPinning.right],(t,r,n)=>{var i;return c(t,null!=(i=null==n?void 0:n.map(e=>r.find(t=>t.id===e)).filter(Boolean))?i:[],e,"right")},l(e.options,u,"getRightHeaderGroups")),e.getFooterGroups=a(()=>[e.getHeaderGroups()],e=>[...e].reverse(),l(e.options,u,"getFooterGroups")),e.getLeftFooterGroups=a(()=>[e.getLeftHeaderGroups()],e=>[...e].reverse(),l(e.options,u,"getLeftFooterGroups")),e.getCenterFooterGroups=a(()=>[e.getCenterHeaderGroups()],e=>[...e].reverse(),l(e.options,u,"getCenterFooterGroups")),e.getRightFooterGroups=a(()=>[e.getRightHeaderGroups()],e=>[...e].reverse(),l(e.options,u,"getRightFooterGroups")),e.getFlatHeaders=a(()=>[e.getHeaderGroups()],e=>e.map(e=>e.headers).flat(),l(e.options,u,"getFlatHeaders")),e.getLeftFlatHeaders=a(()=>[e.getLeftHeaderGroups()],e=>e.map(e=>e.headers).flat(),l(e.options,u,"getLeftFlatHeaders")),e.getCenterFlatHeaders=a(()=>[e.getCenterHeaderGroups()],e=>e.map(e=>e.headers).flat(),l(e.options,u,"getCenterFlatHeaders")),e.getRightFlatHeaders=a(()=>[e.getRightHeaderGroups()],e=>e.map(e=>e.headers).flat(),l(e.options,u,"getRightFlatHeaders")),e.getCenterLeafHeaders=a(()=>[e.getCenterFlatHeaders()],e=>e.filter(e=>{var t;return!(null!=(t=e.subHeaders)&&t.length)}),l(e.options,u,"getCenterLeafHeaders")),e.getLeftLeafHeaders=a(()=>[e.getLeftFlatHeaders()],e=>e.filter(e=>{var t;return!(null!=(t=e.subHeaders)&&t.length)}),l(e.options,u,"getLeftLeafHeaders")),e.getRightLeafHeaders=a(()=>[e.getRightFlatHeaders()],e=>e.filter(e=>{var t;return!(null!=(t=e.subHeaders)&&t.length)}),l(e.options,u,"getRightLeafHeaders")),e.getLeafHeaders=a(()=>[e.getLeftHeaderGroups(),e.getCenterHeaderGroups(),e.getRightHeaderGroups()],(e,t,r)=>{var n,i,o,a,l,u;return[...null!=(n=null==(i=e[0])?void 0:i.headers)?n:[],...null!=(o=null==(a=t[0])?void 0:a.headers)?o:[],...null!=(l=null==(u=r[0])?void 0:u.headers)?l:[]].map(e=>e.getLeafHeaders()).flat()},l(e.options,u,"getLeafHeaders"))}},{getInitialState:e=>({columnVisibility:{},...e}),getDefaultOptions:e=>({onColumnVisibilityChange:i("columnVisibility",e)}),createColumn:(e,t)=>{e.toggleVisibility=r=>{e.getCanHide()&&t.setColumnVisibility(t=>({...t,[e.id]:null!=r?r:!e.getIsVisible()}))},e.getIsVisible=()=>{var r,n;let i=e.columns;return null==(r=i.length?i.some(e=>e.getIsVisible()):null==(n=t.getState().columnVisibility)?void 0:n[e.id])||r},e.getCanHide=()=>{var r,n;return(null==(r=e.columnDef.enableHiding)||r)&&(null==(n=t.options.enableHiding)||n)},e.getToggleVisibilityHandler=()=>t=>{null==e.toggleVisibility||e.toggleVisibility(t.target.checked)}},createRow:(e,t)=>{e._getAllVisibleCells=a(()=>[e.getAllCells(),t.getState().columnVisibility],e=>e.filter(e=>e.column.getIsVisible()),l(t.options,"debugRows","_getAllVisibleCells")),e.getVisibleCells=a(()=>[e.getLeftVisibleCells(),e.getCenterVisibleCells(),e.getRightVisibleCells()],(e,t,r)=>[...e,...t,...r],l(t.options,"debugRows","getVisibleCells"))},createTable:e=>{let t=(t,r)=>a(()=>[r(),r().filter(e=>e.getIsVisible()).map(e=>e.id).join("_")],e=>e.filter(e=>null==e.getIsVisible?void 0:e.getIsVisible()),l(e.options,"debugColumns",t));e.getVisibleFlatColumns=t("getVisibleFlatColumns",()=>e.getAllFlatColumns()),e.getVisibleLeafColumns=t("getVisibleLeafColumns",()=>e.getAllLeafColumns()),e.getLeftVisibleLeafColumns=t("getLeftVisibleLeafColumns",()=>e.getLeftLeafColumns()),e.getRightVisibleLeafColumns=t("getRightVisibleLeafColumns",()=>e.getRightLeafColumns()),e.getCenterVisibleLeafColumns=t("getCenterVisibleLeafColumns",()=>e.getCenterLeafColumns()),e.setColumnVisibility=t=>null==e.options.onColumnVisibilityChange?void 0:e.options.onColumnVisibilityChange(t),e.resetColumnVisibility=t=>{var r;e.setColumnVisibility(t?{}:null!=(r=e.initialState.columnVisibility)?r:{})},e.toggleAllColumnsVisible=t=>{var r;t=null!=(r=t)?r:!e.getIsAllColumnsVisible(),e.setColumnVisibility(e.getAllLeafColumns().reduce((e,r)=>({...e,[r.id]:t||!(null!=r.getCanHide&&r.getCanHide())}),{}))},e.getIsAllColumnsVisible=()=>!e.getAllLeafColumns().some(e=>!(null!=e.getIsVisible&&e.getIsVisible())),e.getIsSomeColumnsVisible=()=>e.getAllLeafColumns().some(e=>null==e.getIsVisible?void 0:e.getIsVisible()),e.getToggleAllColumnsVisibilityHandler=()=>t=>{var r;e.toggleAllColumnsVisible(null==(r=t.target)?void 0:r.checked)}}},{getInitialState:e=>({columnOrder:[],...e}),getDefaultOptions:e=>({onColumnOrderChange:i("columnOrder",e)}),createColumn:(e,t)=>{e.getIndex=a(e=>[A(t,e)],t=>t.findIndex(t=>t.id===e.id),l(t.options,"debugColumns","getIndex")),e.getIsFirstColumn=r=>{var n;return(null==(n=A(t,r)[0])?void 0:n.id)===e.id},e.getIsLastColumn=r=>{var n;let i=A(t,r);return(null==(n=i[i.length-1])?void 0:n.id)===e.id}},createTable:e=>{e.setColumnOrder=t=>null==e.options.onColumnOrderChange?void 0:e.options.onColumnOrderChange(t),e.resetColumnOrder=t=>{var r;e.setColumnOrder(t?[]:null!=(r=e.initialState.columnOrder)?r:[])},e._getOrderColumnsFn=a(()=>[e.getState().columnOrder,e.getState().grouping,e.options.groupedColumnMode],(e,t,r)=>n=>{let i=[];if(null!=e&&e.length){let t=[...e],r=[...n];for(;r.length&&t.length;){let e=t.shift(),n=r.findIndex(t=>t.id===e);n>-1&&i.push(r.splice(n,1)[0])}i=[...i,...r]}else i=n;return function(e,t,r){if(!(null!=t&&t.length)||!r)return e;let n=e.filter(e=>!t.includes(e.id));return"remove"===r?n:[...t.map(t=>e.find(e=>e.id===t)).filter(Boolean),...n]}(i,t,r)},l(e.options,"debugTable","_getOrderColumnsFn"))}},{getInitialState:e=>({columnPinning:C(),...e}),getDefaultOptions:e=>({onColumnPinningChange:i("columnPinning",e)}),createColumn:(e,t)=>{e.pin=r=>{let n=e.getLeafColumns().map(e=>e.id).filter(Boolean);t.setColumnPinning(e=>{var t,i,o,a,l,u;return"right"===r?{left:(null!=(o=null==e?void 0:e.left)?o:[]).filter(e=>!(null!=n&&n.includes(e))),right:[...(null!=(a=null==e?void 0:e.right)?a:[]).filter(e=>!(null!=n&&n.includes(e))),...n]}:"left"===r?{left:[...(null!=(l=null==e?void 0:e.left)?l:[]).filter(e=>!(null!=n&&n.includes(e))),...n],right:(null!=(u=null==e?void 0:e.right)?u:[]).filter(e=>!(null!=n&&n.includes(e)))}:{left:(null!=(t=null==e?void 0:e.left)?t:[]).filter(e=>!(null!=n&&n.includes(e))),right:(null!=(i=null==e?void 0:e.right)?i:[]).filter(e=>!(null!=n&&n.includes(e)))}})},e.getCanPin=()=>e.getLeafColumns().some(e=>{var r,n,i;return(null==(r=e.columnDef.enablePinning)||r)&&(null==(n=null!=(i=t.options.enableColumnPinning)?i:t.options.enablePinning)||n)}),e.getIsPinned=()=>{let r=e.getLeafColumns().map(e=>e.id),{left:n,right:i}=t.getState().columnPinning,o=r.some(e=>null==n?void 0:n.includes(e)),a=r.some(e=>null==i?void 0:i.includes(e));return o?"left":!!a&&"right"},e.getPinnedIndex=()=>{var r,n;let i=e.getIsPinned();return i?null!=(r=null==(n=t.getState().columnPinning)||null==(n=n[i])?void 0:n.indexOf(e.id))?r:-1:0}},createRow:(e,t)=>{e.getCenterVisibleCells=a(()=>[e._getAllVisibleCells(),t.getState().columnPinning.left,t.getState().columnPinning.right],(e,t,r)=>{let n=[...null!=t?t:[],...null!=r?r:[]];return e.filter(e=>!n.includes(e.column.id))},l(t.options,"debugRows","getCenterVisibleCells")),e.getLeftVisibleCells=a(()=>[e._getAllVisibleCells(),t.getState().columnPinning.left],(e,t)=>(null!=t?t:[]).map(t=>e.find(e=>e.column.id===t)).filter(Boolean).map(e=>({...e,position:"left"})),l(t.options,"debugRows","getLeftVisibleCells")),e.getRightVisibleCells=a(()=>[e._getAllVisibleCells(),t.getState().columnPinning.right],(e,t)=>(null!=t?t:[]).map(t=>e.find(e=>e.column.id===t)).filter(Boolean).map(e=>({...e,position:"right"})),l(t.options,"debugRows","getRightVisibleCells"))},createTable:e=>{e.setColumnPinning=t=>null==e.options.onColumnPinningChange?void 0:e.options.onColumnPinningChange(t),e.resetColumnPinning=t=>{var r,n;return e.setColumnPinning(t?C():null!=(r=null==(n=e.initialState)?void 0:n.columnPinning)?r:C())},e.getIsSomeColumnsPinned=t=>{var r,n,i;let o=e.getState().columnPinning;return t?!!(null==(r=o[t])?void 0:r.length):!!((null==(n=o.left)?void 0:n.length)||(null==(i=o.right)?void 0:i.length))},e.getLeftLeafColumns=a(()=>[e.getAllLeafColumns(),e.getState().columnPinning.left],(e,t)=>(null!=t?t:[]).map(t=>e.find(e=>e.id===t)).filter(Boolean),l(e.options,"debugColumns","getLeftLeafColumns")),e.getRightLeafColumns=a(()=>[e.getAllLeafColumns(),e.getState().columnPinning.right],(e,t)=>(null!=t?t:[]).map(t=>e.find(e=>e.id===t)).filter(Boolean),l(e.options,"debugColumns","getRightLeafColumns")),e.getCenterLeafColumns=a(()=>[e.getAllLeafColumns(),e.getState().columnPinning.left,e.getState().columnPinning.right],(e,t,r)=>{let n=[...null!=t?t:[],...null!=r?r:[]];return e.filter(e=>!n.includes(e.id))},l(e.options,"debugColumns","getCenterLeafColumns"))}},{createColumn:(e,t)=>{e._getFacetedRowModel=t.options.getFacetedRowModel&&t.options.getFacetedRowModel(t,e.id),e.getFacetedRowModel=()=>e._getFacetedRowModel?e._getFacetedRowModel():t.getPreFilteredRowModel(),e._getFacetedUniqueValues=t.options.getFacetedUniqueValues&&t.options.getFacetedUniqueValues(t,e.id),e.getFacetedUniqueValues=()=>e._getFacetedUniqueValues?e._getFacetedUniqueValues():new Map,e._getFacetedMinMaxValues=t.options.getFacetedMinMaxValues&&t.options.getFacetedMinMaxValues(t,e.id),e.getFacetedMinMaxValues=()=>{if(e._getFacetedMinMaxValues)return e._getFacetedMinMaxValues()}}},{getDefaultColumnDef:()=>({filterFn:"auto"}),getInitialState:e=>({columnFilters:[],...e}),getDefaultOptions:e=>({onColumnFiltersChange:i("columnFilters",e),filterFromLeafRows:!1,maxLeafRowFilterDepth:100}),createColumn:(e,t)=>{e.getAutoFilterFn=()=>{let r=t.getCoreRowModel().flatRows[0],n=null==r?void 0:r.getValue(e.id);return"string"==typeof n?x.includesString:"number"==typeof n?x.inNumberRange:"boolean"==typeof n||null!==n&&"object"==typeof n?x.equals:Array.isArray(n)?x.arrIncludes:x.weakEquals},e.getFilterFn=()=>{var r,n;return o(e.columnDef.filterFn)?e.columnDef.filterFn:"auto"===e.columnDef.filterFn?e.getAutoFilterFn():null!=(r=null==(n=t.options.filterFns)?void 0:n[e.columnDef.filterFn])?r:x[e.columnDef.filterFn]},e.getCanFilter=()=>{var r,n,i;return(null==(r=e.columnDef.enableColumnFilter)||r)&&(null==(n=t.options.enableColumnFilters)||n)&&(null==(i=t.options.enableFilters)||i)&&!!e.accessorFn},e.getIsFiltered=()=>e.getFilterIndex()>-1,e.getFilterValue=()=>{var r;return null==(r=t.getState().columnFilters)||null==(r=r.find(t=>t.id===e.id))?void 0:r.value},e.getFilterIndex=()=>{var r,n;return null!=(r=null==(n=t.getState().columnFilters)?void 0:n.findIndex(t=>t.id===e.id))?r:-1},e.setFilterValue=r=>{t.setColumnFilters(t=>{var i,o;let a=e.getFilterFn(),l=null==t?void 0:t.find(t=>t.id===e.id),u=n(r,l?l.value:void 0);if(O(a,u,e))return null!=(i=null==t?void 0:t.filter(t=>t.id!==e.id))?i:[];let s={id:e.id,value:u};return l?null!=(o=null==t?void 0:t.map(t=>t.id===e.id?s:t))?o:[]:null!=t&&t.length?[...t,s]:[s]})}},createRow:(e,t)=>{e.columnFilters={},e.columnFiltersMeta={}},createTable:e=>{e.setColumnFilters=t=>{let r=e.getAllLeafColumns();null==e.options.onColumnFiltersChange||e.options.onColumnFiltersChange(e=>{var i;return null==(i=n(t,e))?void 0:i.filter(e=>{let t=r.find(t=>t.id===e.id);return!(t&&O(t.getFilterFn(),e.value,t))&&!0})})},e.resetColumnFilters=t=>{var r,n;e.setColumnFilters(t?[]:null!=(r=null==(n=e.initialState)?void 0:n.columnFilters)?r:[])},e.getPreFilteredRowModel=()=>e.getCoreRowModel(),e.getFilteredRowModel=()=>(!e._getFilteredRowModel&&e.options.getFilteredRowModel&&(e._getFilteredRowModel=e.options.getFilteredRowModel(e)),e.options.manualFiltering||!e._getFilteredRowModel)?e.getPreFilteredRowModel():e._getFilteredRowModel()}},{createTable:e=>{e._getGlobalFacetedRowModel=e.options.getFacetedRowModel&&e.options.getFacetedRowModel(e,"__global__"),e.getGlobalFacetedRowModel=()=>e.options.manualFiltering||!e._getGlobalFacetedRowModel?e.getPreFilteredRowModel():e._getGlobalFacetedRowModel(),e._getGlobalFacetedUniqueValues=e.options.getFacetedUniqueValues&&e.options.getFacetedUniqueValues(e,"__global__"),e.getGlobalFacetedUniqueValues=()=>e._getGlobalFacetedUniqueValues?e._getGlobalFacetedUniqueValues():new Map,e._getGlobalFacetedMinMaxValues=e.options.getFacetedMinMaxValues&&e.options.getFacetedMinMaxValues(e,"__global__"),e.getGlobalFacetedMinMaxValues=()=>{if(e._getGlobalFacetedMinMaxValues)return e._getGlobalFacetedMinMaxValues()}}},{getInitialState:e=>({globalFilter:void 0,...e}),getDefaultOptions:e=>({onGlobalFilterChange:i("globalFilter",e),globalFilterFn:"auto",getColumnCanGlobalFilter:t=>{var r;let n=null==(r=e.getCoreRowModel().flatRows[0])||null==(r=r._getAllCellsByColumnId()[t.id])?void 0:r.getValue();return"string"==typeof n||"number"==typeof n}}),createColumn:(e,t)=>{e.getCanGlobalFilter=()=>{var r,n,i,o;return(null==(r=e.columnDef.enableGlobalFilter)||r)&&(null==(n=t.options.enableGlobalFilter)||n)&&(null==(i=t.options.enableFilters)||i)&&(null==(o=null==t.options.getColumnCanGlobalFilter?void 0:t.options.getColumnCanGlobalFilter(e))||o)&&!!e.accessorFn}},createTable:e=>{e.getGlobalAutoFilterFn=()=>x.includesString,e.getGlobalFilterFn=()=>{var t,r;let{globalFilterFn:n}=e.options;return o(n)?n:"auto"===n?e.getGlobalAutoFilterFn():null!=(t=null==(r=e.options.filterFns)?void 0:r[n])?t:x[n]},e.setGlobalFilter=t=>{null==e.options.onGlobalFilterChange||e.options.onGlobalFilterChange(t)},e.resetGlobalFilter=t=>{e.setGlobalFilter(t?void 0:e.initialState.globalFilter)}}},{getInitialState:e=>({sorting:[],...e}),getDefaultColumnDef:()=>({sortingFn:"auto",sortUndefined:1}),getDefaultOptions:e=>({onSortingChange:i("sorting",e),isMultiSortEvent:e=>e.shiftKey}),createColumn:(e,t)=>{e.getAutoSortingFn=()=>{let r=t.getFilteredRowModel().flatRows.slice(10),n=!1;for(let t of r){let r=null==t?void 0:t.getValue(e.id);if("[object Date]"===Object.prototype.toString.call(r))return B.datetime;if("string"==typeof r&&(n=!0,r.split(N).length>1))return B.alphanumeric}return n?B.text:B.basic},e.getAutoSortDir=()=>{let r=t.getFilteredRowModel().flatRows[0];return"string"==typeof(null==r?void 0:r.getValue(e.id))?"asc":"desc"},e.getSortingFn=()=>{var r,n;if(!e)throw Error();return o(e.columnDef.sortingFn)?e.columnDef.sortingFn:"auto"===e.columnDef.sortingFn?e.getAutoSortingFn():null!=(r=null==(n=t.options.sortingFns)?void 0:n[e.columnDef.sortingFn])?r:B[e.columnDef.sortingFn]},e.toggleSorting=(r,n)=>{let i=e.getNextSortingOrder(),o=null!=r;t.setSorting(a=>{let l,u=null==a?void 0:a.find(t=>t.id===e.id),s=null==a?void 0:a.findIndex(t=>t.id===e.id),c=[],f=o?r:"desc"===i;if("toggle"!=(l=null!=a&&a.length&&e.getCanMultiSort()&&n?u?"toggle":"add":null!=a&&a.length&&s!==a.length-1?"replace":u?"toggle":"replace")||o||i||(l="remove"),"add"===l){var d;(c=[...a,{id:e.id,desc:f}]).splice(0,c.length-(null!=(d=t.options.maxMultiSortColCount)?d:Number.MAX_SAFE_INTEGER))}else c="toggle"===l?a.map(t=>t.id===e.id?{...t,desc:f}:t):"remove"===l?a.filter(t=>t.id!==e.id):[{id:e.id,desc:f}];return c})},e.getFirstSortDir=()=>{var r,n;return(null!=(r=null!=(n=e.columnDef.sortDescFirst)?n:t.options.sortDescFirst)?r:"desc"===e.getAutoSortDir())?"desc":"asc"},e.getNextSortingOrder=r=>{var n,i;let o=e.getFirstSortDir(),a=e.getIsSorted();return a?(a===o||null!=(n=t.options.enableSortingRemoval)&&!n||!!r&&null!=(i=t.options.enableMultiRemove)&&!i)&&("desc"===a?"asc":"desc"):o},e.getCanSort=()=>{var r,n;return(null==(r=e.columnDef.enableSorting)||r)&&(null==(n=t.options.enableSorting)||n)&&!!e.accessorFn},e.getCanMultiSort=()=>{var r,n;return null!=(r=null!=(n=e.columnDef.enableMultiSort)?n:t.options.enableMultiSort)?r:!!e.accessorFn},e.getIsSorted=()=>{var r;let n=null==(r=t.getState().sorting)?void 0:r.find(t=>t.id===e.id);return!!n&&(n.desc?"desc":"asc")},e.getSortIndex=()=>{var r,n;return null!=(r=null==(n=t.getState().sorting)?void 0:n.findIndex(t=>t.id===e.id))?r:-1},e.clearSorting=()=>{t.setSorting(t=>null!=t&&t.length?t.filter(t=>t.id!==e.id):[])},e.getToggleSortingHandler=()=>{let r=e.getCanSort();return n=>{r&&(null==n.persist||n.persist(),null==e.toggleSorting||e.toggleSorting(void 0,!!e.getCanMultiSort()&&(null==t.options.isMultiSortEvent?void 0:t.options.isMultiSortEvent(n))))}}},createTable:e=>{e.setSorting=t=>null==e.options.onSortingChange?void 0:e.options.onSortingChange(t),e.resetSorting=t=>{var r,n;e.setSorting(t?[]:null!=(r=null==(n=e.initialState)?void 0:n.sorting)?r:[])},e.getPreSortedRowModel=()=>e.getGroupedRowModel(),e.getSortedRowModel=()=>(!e._getSortedRowModel&&e.options.getSortedRowModel&&(e._getSortedRowModel=e.options.getSortedRowModel(e)),e.options.manualSorting||!e._getSortedRowModel)?e.getPreSortedRowModel():e._getSortedRowModel()}},{getDefaultColumnDef:()=>({aggregatedCell:e=>{var t,r;return null!=(t=null==(r=e.getValue())||null==r.toString?void 0:r.toString())?t:null},aggregationFn:"auto"}),getInitialState:e=>({grouping:[],...e}),getDefaultOptions:e=>({onGroupingChange:i("grouping",e),groupedColumnMode:"reorder"}),createColumn:(e,t)=>{e.toggleGrouping=()=>{t.setGrouping(t=>null!=t&&t.includes(e.id)?t.filter(t=>t!==e.id):[...null!=t?t:[],e.id])},e.getCanGroup=()=>{var r,n;return(null==(r=e.columnDef.enableGrouping)||r)&&(null==(n=t.options.enableGrouping)||n)&&(!!e.accessorFn||!!e.columnDef.getGroupingValue)},e.getIsGrouped=()=>{var r;return null==(r=t.getState().grouping)?void 0:r.includes(e.id)},e.getGroupedIndex=()=>{var r;return null==(r=t.getState().grouping)?void 0:r.indexOf(e.id)},e.getToggleGroupingHandler=()=>{let t=e.getCanGroup();return()=>{t&&e.toggleGrouping()}},e.getAutoAggregationFn=()=>{let r=t.getCoreRowModel().flatRows[0],n=null==r?void 0:r.getValue(e.id);return"number"==typeof n?E.sum:"[object Date]"===Object.prototype.toString.call(n)?E.extent:void 0},e.getAggregationFn=()=>{var r,n;if(!e)throw Error();return o(e.columnDef.aggregationFn)?e.columnDef.aggregationFn:"auto"===e.columnDef.aggregationFn?e.getAutoAggregationFn():null!=(r=null==(n=t.options.aggregationFns)?void 0:n[e.columnDef.aggregationFn])?r:E[e.columnDef.aggregationFn]}},createTable:e=>{e.setGrouping=t=>null==e.options.onGroupingChange?void 0:e.options.onGroupingChange(t),e.resetGrouping=t=>{var r,n;e.setGrouping(t?[]:null!=(r=null==(n=e.initialState)?void 0:n.grouping)?r:[])},e.getPreGroupedRowModel=()=>e.getFilteredRowModel(),e.getGroupedRowModel=()=>(!e._getGroupedRowModel&&e.options.getGroupedRowModel&&(e._getGroupedRowModel=e.options.getGroupedRowModel(e)),e.options.manualGrouping||!e._getGroupedRowModel)?e.getPreGroupedRowModel():e._getGroupedRowModel()},createRow:(e,t)=>{e.getIsGrouped=()=>!!e.groupingColumnId,e.getGroupingValue=r=>{if(e._groupingValuesCache.hasOwnProperty(r))return e._groupingValuesCache[r];let n=t.getColumn(r);return null!=n&&n.columnDef.getGroupingValue?(e._groupingValuesCache[r]=n.columnDef.getGroupingValue(e.original),e._groupingValuesCache[r]):e.getValue(r)},e._groupingValuesCache={}},createCell:(e,t,r,n)=>{e.getIsGrouped=()=>t.getIsGrouped()&&t.id===r.groupingColumnId,e.getIsPlaceholder=()=>!e.getIsGrouped()&&t.getIsGrouped(),e.getIsAggregated=()=>{var t;return!e.getIsGrouped()&&!e.getIsPlaceholder()&&!!(null!=(t=r.subRows)&&t.length)}}},{getInitialState:e=>({expanded:{},...e}),getDefaultOptions:e=>({onExpandedChange:i("expanded",e),paginateExpandedRows:!0}),createTable:e=>{let t=!1,r=!1;e._autoResetExpanded=()=>{var n,i;if(!t)return void e._queue(()=>{t=!0});if(null!=(n=null!=(i=e.options.autoResetAll)?i:e.options.autoResetExpanded)?n:!e.options.manualExpanding){if(r)return;r=!0,e._queue(()=>{e.resetExpanded(),r=!1})}},e.setExpanded=t=>null==e.options.onExpandedChange?void 0:e.options.onExpandedChange(t),e.toggleAllRowsExpanded=t=>{(null!=t?t:!e.getIsAllRowsExpanded())?e.setExpanded(!0):e.setExpanded({})},e.resetExpanded=t=>{var r,n;e.setExpanded(t?{}:null!=(r=null==(n=e.initialState)?void 0:n.expanded)?r:{})},e.getCanSomeRowsExpand=()=>e.getPrePaginationRowModel().flatRows.some(e=>e.getCanExpand()),e.getToggleAllRowsExpandedHandler=()=>t=>{null==t.persist||t.persist(),e.toggleAllRowsExpanded()},e.getIsSomeRowsExpanded=()=>{let t=e.getState().expanded;return!0===t||Object.values(t).some(Boolean)},e.getIsAllRowsExpanded=()=>{let t=e.getState().expanded;return"boolean"==typeof t?!0===t:!(!Object.keys(t).length||e.getRowModel().flatRows.some(e=>!e.getIsExpanded()))},e.getExpandedDepth=()=>{let t=0;return(!0===e.getState().expanded?Object.keys(e.getRowModel().rowsById):Object.keys(e.getState().expanded)).forEach(e=>{let r=e.split(".");t=Math.max(t,r.length)}),t},e.getPreExpandedRowModel=()=>e.getSortedRowModel(),e.getExpandedRowModel=()=>(!e._getExpandedRowModel&&e.options.getExpandedRowModel&&(e._getExpandedRowModel=e.options.getExpandedRowModel(e)),e.options.manualExpanding||!e._getExpandedRowModel)?e.getPreExpandedRowModel():e._getExpandedRowModel()},createRow:(e,t)=>{e.toggleExpanded=r=>{t.setExpanded(n=>{var i;let o=!0===n||!!(null!=n&&n[e.id]),a={};if(!0===n?Object.keys(t.getRowModel().rowsById).forEach(e=>{a[e]=!0}):a=n,r=null!=(i=r)?i:!o,!o&&r)return{...a,[e.id]:!0};if(o&&!r){let{[e.id]:t,...r}=a;return r}return n})},e.getIsExpanded=()=>{var r;let n=t.getState().expanded;return!!(null!=(r=null==t.options.getIsRowExpanded?void 0:t.options.getIsRowExpanded(e))?r:!0===n||(null==n?void 0:n[e.id]))},e.getCanExpand=()=>{var r,n,i;return null!=(r=null==t.options.getRowCanExpand?void 0:t.options.getRowCanExpand(e))?r:(null==(n=t.options.enableExpanding)||n)&&!!(null!=(i=e.subRows)&&i.length)},e.getIsAllParentsExpanded=()=>{let r=!0,n=e;for(;r&&n.parentId;)r=(n=t.getRow(n.parentId,!0)).getIsExpanded();return r},e.getToggleExpandedHandler=()=>{let t=e.getCanExpand();return()=>{t&&e.toggleExpanded()}}}},{getInitialState:e=>({...e,pagination:{...j(),...null==e?void 0:e.pagination}}),getDefaultOptions:e=>({onPaginationChange:i("pagination",e)}),createTable:e=>{let t=!1,r=!1;e._autoResetPageIndex=()=>{var n,i;if(!t)return void e._queue(()=>{t=!0});if(null!=(n=null!=(i=e.options.autoResetAll)?i:e.options.autoResetPageIndex)?n:!e.options.manualPagination){if(r)return;r=!0,e._queue(()=>{e.resetPageIndex(),r=!1})}},e.setPagination=t=>null==e.options.onPaginationChange?void 0:e.options.onPaginationChange(e=>n(t,e)),e.resetPagination=t=>{var r;e.setPagination(t?j():null!=(r=e.initialState.pagination)?r:j())},e.setPageIndex=t=>{e.setPagination(r=>{let i=n(t,r.pageIndex);return i=Math.max(0,Math.min(i,void 0===e.options.pageCount||-1===e.options.pageCount?Number.MAX_SAFE_INTEGER:e.options.pageCount-1)),{...r,pageIndex:i}})},e.resetPageIndex=t=>{var r,n;e.setPageIndex(t?0:null!=(r=null==(n=e.initialState)||null==(n=n.pagination)?void 0:n.pageIndex)?r:0)},e.resetPageSize=t=>{var r,n;e.setPageSize(t?10:null!=(r=null==(n=e.initialState)||null==(n=n.pagination)?void 0:n.pageSize)?r:10)},e.setPageSize=t=>{e.setPagination(e=>{let r=Math.max(1,n(t,e.pageSize)),i=Math.floor(e.pageSize*e.pageIndex/r);return{...e,pageIndex:i,pageSize:r}})},e.setPageCount=t=>e.setPagination(r=>{var i;let o=n(t,null!=(i=e.options.pageCount)?i:-1);return"number"==typeof o&&(o=Math.max(-1,o)),{...r,pageCount:o}}),e.getPageOptions=a(()=>[e.getPageCount()],e=>{let t=[];return e&&e>0&&(t=[...Array(e)].fill(null).map((e,t)=>t)),t},l(e.options,"debugTable","getPageOptions")),e.getCanPreviousPage=()=>e.getState().pagination.pageIndex>0,e.getCanNextPage=()=>{let{pageIndex:t}=e.getState().pagination,r=e.getPageCount();return -1===r||0!==r&&te.setPageIndex(e=>e-1),e.nextPage=()=>e.setPageIndex(e=>e+1),e.firstPage=()=>e.setPageIndex(0),e.lastPage=()=>e.setPageIndex(e.getPageCount()-1),e.getPrePaginationRowModel=()=>e.getExpandedRowModel(),e.getPaginationRowModel=()=>(!e._getPaginationRowModel&&e.options.getPaginationRowModel&&(e._getPaginationRowModel=e.options.getPaginationRowModel(e)),e.options.manualPagination||!e._getPaginationRowModel)?e.getPrePaginationRowModel():e._getPaginationRowModel(),e.getPageCount=()=>{var t;return null!=(t=e.options.pageCount)?t:Math.ceil(e.getRowCount()/e.getState().pagination.pageSize)},e.getRowCount=()=>{var t;return null!=(t=e.options.rowCount)?t:e.getPrePaginationRowModel().rows.length}}},{getInitialState:e=>({rowPinning:T(),...e}),getDefaultOptions:e=>({onRowPinningChange:i("rowPinning",e)}),createRow:(e,t)=>{e.pin=(r,n,i)=>{let o=n?e.getLeafRows().map(e=>{let{id:t}=e;return t}):[],a=new Set([...i?e.getParentRows().map(e=>{let{id:t}=e;return t}):[],e.id,...o]);t.setRowPinning(e=>{var t,n,i,o,l,u;return"bottom"===r?{top:(null!=(i=null==e?void 0:e.top)?i:[]).filter(e=>!(null!=a&&a.has(e))),bottom:[...(null!=(o=null==e?void 0:e.bottom)?o:[]).filter(e=>!(null!=a&&a.has(e))),...Array.from(a)]}:"top"===r?{top:[...(null!=(l=null==e?void 0:e.top)?l:[]).filter(e=>!(null!=a&&a.has(e))),...Array.from(a)],bottom:(null!=(u=null==e?void 0:e.bottom)?u:[]).filter(e=>!(null!=a&&a.has(e)))}:{top:(null!=(t=null==e?void 0:e.top)?t:[]).filter(e=>!(null!=a&&a.has(e))),bottom:(null!=(n=null==e?void 0:e.bottom)?n:[]).filter(e=>!(null!=a&&a.has(e)))}})},e.getCanPin=()=>{var r;let{enableRowPinning:n,enablePinning:i}=t.options;return"function"==typeof n?n(e):null==(r=null!=n?n:i)||r},e.getIsPinned=()=>{let r=[e.id],{top:n,bottom:i}=t.getState().rowPinning,o=r.some(e=>null==n?void 0:n.includes(e)),a=r.some(e=>null==i?void 0:i.includes(e));return o?"top":!!a&&"bottom"},e.getPinnedIndex=()=>{var r,n;let i=e.getIsPinned();if(!i)return -1;let o=null==(r="top"===i?t.getTopRows():t.getBottomRows())?void 0:r.map(e=>{let{id:t}=e;return t});return null!=(n=null==o?void 0:o.indexOf(e.id))?n:-1}},createTable:e=>{e.setRowPinning=t=>null==e.options.onRowPinningChange?void 0:e.options.onRowPinningChange(t),e.resetRowPinning=t=>{var r,n;return e.setRowPinning(t?T():null!=(r=null==(n=e.initialState)?void 0:n.rowPinning)?r:T())},e.getIsSomeRowsPinned=t=>{var r,n,i;let o=e.getState().rowPinning;return t?!!(null==(r=o[t])?void 0:r.length):!!((null==(n=o.top)?void 0:n.length)||(null==(i=o.bottom)?void 0:i.length))},e._getPinnedRows=(t,r,n)=>{var i;return(null==(i=e.options.keepPinnedRows)||i?(null!=r?r:[]).map(t=>{let r=e.getRow(t,!0);return r.getIsAllParentsExpanded()?r:null}):(null!=r?r:[]).map(e=>t.find(t=>t.id===e))).filter(Boolean).map(e=>({...e,position:n}))},e.getTopRows=a(()=>[e.getRowModel().rows,e.getState().rowPinning.top],(t,r)=>e._getPinnedRows(t,r,"top"),l(e.options,"debugRows","getTopRows")),e.getBottomRows=a(()=>[e.getRowModel().rows,e.getState().rowPinning.bottom],(t,r)=>e._getPinnedRows(t,r,"bottom"),l(e.options,"debugRows","getBottomRows")),e.getCenterRows=a(()=>[e.getRowModel().rows,e.getState().rowPinning.top,e.getState().rowPinning.bottom],(e,t,r)=>{let n=new Set([...null!=t?t:[],...null!=r?r:[]]);return e.filter(e=>!n.has(e.id))},l(e.options,"debugRows","getCenterRows"))}},{getInitialState:e=>({rowSelection:{},...e}),getDefaultOptions:e=>({onRowSelectionChange:i("rowSelection",e),enableRowSelection:!0,enableMultiRowSelection:!0,enableSubRowSelection:!0}),createTable:e=>{e.setRowSelection=t=>null==e.options.onRowSelectionChange?void 0:e.options.onRowSelectionChange(t),e.resetRowSelection=t=>{var r;return e.setRowSelection(t?{}:null!=(r=e.initialState.rowSelection)?r:{})},e.toggleAllRowsSelected=t=>{e.setRowSelection(r=>{t=void 0!==t?t:!e.getIsAllRowsSelected();let n={...r},i=e.getPreGroupedRowModel().flatRows;return t?i.forEach(e=>{e.getCanSelect()&&(n[e.id]=!0)}):i.forEach(e=>{delete n[e.id]}),n})},e.toggleAllPageRowsSelected=t=>e.setRowSelection(r=>{let n=void 0!==t?t:!e.getIsAllPageRowsSelected(),i={...r};return e.getRowModel().rows.forEach(t=>{R(i,t.id,n,!0,e)}),i}),e.getPreSelectedRowModel=()=>e.getCoreRowModel(),e.getSelectedRowModel=a(()=>[e.getState().rowSelection,e.getCoreRowModel()],(t,r)=>Object.keys(t).length?z(e,r):{rows:[],flatRows:[],rowsById:{}},l(e.options,"debugTable","getSelectedRowModel")),e.getFilteredSelectedRowModel=a(()=>[e.getState().rowSelection,e.getFilteredRowModel()],(t,r)=>Object.keys(t).length?z(e,r):{rows:[],flatRows:[],rowsById:{}},l(e.options,"debugTable","getFilteredSelectedRowModel")),e.getGroupedSelectedRowModel=a(()=>[e.getState().rowSelection,e.getSortedRowModel()],(t,r)=>Object.keys(t).length?z(e,r):{rows:[],flatRows:[],rowsById:{}},l(e.options,"debugTable","getGroupedSelectedRowModel")),e.getIsAllRowsSelected=()=>{let t=e.getFilteredRowModel().flatRows,{rowSelection:r}=e.getState(),n=!!(t.length&&Object.keys(r).length);return n&&t.some(e=>e.getCanSelect()&&!r[e.id])&&(n=!1),n},e.getIsAllPageRowsSelected=()=>{let t=e.getPaginationRowModel().flatRows.filter(e=>e.getCanSelect()),{rowSelection:r}=e.getState(),n=!!t.length;return n&&t.some(e=>!r[e.id])&&(n=!1),n},e.getIsSomeRowsSelected=()=>{var t;let r=Object.keys(null!=(t=e.getState().rowSelection)?t:{}).length;return r>0&&r{let t=e.getPaginationRowModel().flatRows;return!e.getIsAllPageRowsSelected()&&t.filter(e=>e.getCanSelect()).some(e=>e.getIsSelected()||e.getIsSomeSelected())},e.getToggleAllRowsSelectedHandler=()=>t=>{e.toggleAllRowsSelected(t.target.checked)},e.getToggleAllPageRowsSelectedHandler=()=>t=>{e.toggleAllPageRowsSelected(t.target.checked)}},createRow:(e,t)=>{e.toggleSelected=(r,n)=>{let i=e.getIsSelected();t.setRowSelection(o=>{var a;if(r=void 0!==r?r:!i,e.getCanSelect()&&i===r)return o;let l={...o};return R(l,e.id,r,null==(a=null==n?void 0:n.selectChildren)||a,t),l})},e.getIsSelected=()=>{let{rowSelection:r}=t.getState();return D(e,r)},e.getIsSomeSelected=()=>{let{rowSelection:r}=t.getState();return"some"===I(e,r)},e.getIsAllSubRowsSelected=()=>{let{rowSelection:r}=t.getState();return"all"===I(e,r)},e.getCanSelect=()=>{var r;return"function"==typeof t.options.enableRowSelection?t.options.enableRowSelection(e):null==(r=t.options.enableRowSelection)||r},e.getCanSelectSubRows=()=>{var r;return"function"==typeof t.options.enableSubRowSelection?t.options.enableSubRowSelection(e):null==(r=t.options.enableSubRowSelection)||r},e.getCanMultiSelect=()=>{var r;return"function"==typeof t.options.enableMultiRowSelection?t.options.enableMultiRowSelection(e):null==(r=t.options.enableMultiRowSelection)||r},e.getToggleSelectedHandler=()=>{let t=e.getCanSelect();return r=>{var n;t&&e.toggleSelected(null==(n=r.target)?void 0:n.checked)}}}},{getDefaultColumnDef:()=>M,getInitialState:e=>({columnSizing:{},columnSizingInfo:k(),...e}),getDefaultOptions:e=>({columnResizeMode:"onEnd",columnResizeDirection:"ltr",onColumnSizingChange:i("columnSizing",e),onColumnSizingInfoChange:i("columnSizingInfo",e)}),createColumn:(e,t)=>{e.getSize=()=>{var r,n,i;let o=t.getState().columnSizing[e.id];return Math.min(Math.max(null!=(r=e.columnDef.minSize)?r:M.minSize,null!=(n=null!=o?o:e.columnDef.size)?n:M.size),null!=(i=e.columnDef.maxSize)?i:M.maxSize)},e.getStart=a(e=>[e,A(t,e),t.getState().columnSizing],(t,r)=>r.slice(0,e.getIndex(t)).reduce((e,t)=>e+t.getSize(),0),l(t.options,"debugColumns","getStart")),e.getAfter=a(e=>[e,A(t,e),t.getState().columnSizing],(t,r)=>r.slice(e.getIndex(t)+1).reduce((e,t)=>e+t.getSize(),0),l(t.options,"debugColumns","getAfter")),e.resetSize=()=>{t.setColumnSizing(t=>{let{[e.id]:r,...n}=t;return n})},e.getCanResize=()=>{var r,n;return(null==(r=e.columnDef.enableResizing)||r)&&(null==(n=t.options.enableColumnResizing)||n)},e.getIsResizing=()=>t.getState().columnSizingInfo.isResizingColumn===e.id},createHeader:(e,t)=>{e.getSize=()=>{let t=0,r=e=>{if(e.subHeaders.length)e.subHeaders.forEach(r);else{var n;t+=null!=(n=e.column.getSize())?n:0}};return r(e),t},e.getStart=()=>{if(e.index>0){let t=e.headerGroup.headers[e.index-1];return t.getStart()+t.getSize()}return 0},e.getResizeHandler=r=>{let n=t.getColumn(e.column.id),i=null==n?void 0:n.getCanResize();return o=>{if(!n||!i||(null==o.persist||o.persist(),P(o)&&o.touches&&o.touches.length>1))return;let a=e.getSize(),l=e?e.getLeafHeaders().map(e=>[e.column.id,e.column.getSize()]):[[n.id,n.getSize()]],u=P(o)?Math.round(o.touches[0].clientX):o.clientX,s={},c=(e,r)=>{"number"==typeof r&&(t.setColumnSizingInfo(e=>{var n,i;let o="rtl"===t.options.columnResizeDirection?-1:1,a=(r-(null!=(n=null==e?void 0:e.startOffset)?n:0))*o,l=Math.max(a/(null!=(i=null==e?void 0:e.startSize)?i:0),-.999999);return e.columnSizingStart.forEach(e=>{let[t,r]=e;s[t]=Math.round(100*Math.max(r+r*l,0))/100}),{...e,deltaOffset:a,deltaPercentage:l}}),("onChange"===t.options.columnResizeMode||"end"===e)&&t.setColumnSizing(e=>({...e,...s})))},f=e=>c("move",e),d=e=>{c("end",e),t.setColumnSizingInfo(e=>({...e,isResizingColumn:!1,startOffset:null,startSize:null,deltaOffset:null,deltaPercentage:null,columnSizingStart:[]}))},p=r||("undefined"!=typeof document?document:null),h={moveHandler:e=>f(e.clientX),upHandler:e=>{null==p||p.removeEventListener("mousemove",h.moveHandler),null==p||p.removeEventListener("mouseup",h.upHandler),d(e.clientX)}},g={moveHandler:e=>(e.cancelable&&(e.preventDefault(),e.stopPropagation()),f(e.touches[0].clientX),!1),upHandler:e=>{var t;null==p||p.removeEventListener("touchmove",g.moveHandler),null==p||p.removeEventListener("touchend",g.upHandler),e.cancelable&&(e.preventDefault(),e.stopPropagation()),d(null==(t=e.touches[0])?void 0:t.clientX)}},v=!!function(){if("boolean"==typeof _)return _;let e=!1;try{let t=()=>{};window.addEventListener("test",t,{get passive(){return e=!0,!1}}),window.removeEventListener("test",t)}catch(t){e=!1}return _=e}()&&{passive:!1};P(o)?(null==p||p.addEventListener("touchmove",g.moveHandler,v),null==p||p.addEventListener("touchend",g.upHandler,v)):(null==p||p.addEventListener("mousemove",h.moveHandler,v),null==p||p.addEventListener("mouseup",h.upHandler,v)),t.setColumnSizingInfo(e=>({...e,startOffset:u,startSize:a,deltaOffset:0,deltaPercentage:0,columnSizingStart:l,isResizingColumn:n.id}))}}},createTable:e=>{e.setColumnSizing=t=>null==e.options.onColumnSizingChange?void 0:e.options.onColumnSizingChange(t),e.setColumnSizingInfo=t=>null==e.options.onColumnSizingInfoChange?void 0:e.options.onColumnSizingInfoChange(t),e.resetColumnSizing=t=>{var r;e.setColumnSizing(t?{}:null!=(r=e.initialState.columnSizing)?r:{})},e.resetHeaderSizeInfo=t=>{var r;e.setColumnSizingInfo(t?k():null!=(r=e.initialState.columnSizingInfo)?r:k())},e.getTotalSize=()=>{var t,r;return null!=(t=null==(r=e.getHeaderGroups()[0])?void 0:r.headers.reduce((e,t)=>e+t.getSize(),0))?t:0},e.getLeftTotalSize=()=>{var t,r;return null!=(t=null==(r=e.getLeftHeaderGroups()[0])?void 0:r.headers.reduce((e,t)=>e+t.getSize(),0))?t:0},e.getCenterTotalSize=()=>{var t,r;return null!=(t=null==(r=e.getCenterHeaderGroups()[0])?void 0:r.headers.reduce((e,t)=>e+t.getSize(),0))?t:0},e.getRightTotalSize=()=>{var t,r;return null!=(t=null==(r=e.getRightHeaderGroups()[0])?void 0:r.headers.reduce((e,t)=>e+t.getSize(),0))?t:0}}}];function U(e){var t,r;let i=[...V,...null!=(t=e._features)?t:[]],o={_features:i},u=o._features.reduce((e,t)=>Object.assign(e,null==t.getDefaultOptions?void 0:t.getDefaultOptions(o)),{}),s={...null!=(r=e.initialState)?r:{}};o._features.forEach(e=>{var t;s=null!=(t=null==e.getInitialState?void 0:e.getInitialState(s))?t:s});let c=[],f=!1,d={_features:i,options:{...u,...e},initialState:s,_queue:e=>{c.push(e),f||(f=!0,Promise.resolve().then(()=>{for(;c.length;)c.shift()();f=!1}).catch(e=>setTimeout(()=>{throw e})))},reset:()=>{o.setState(o.initialState)},setOptions:e=>{var t;t=n(e,o.options),o.options=o.options.mergeOptions?o.options.mergeOptions(u,t):{...u,...t}},getState:()=>o.options.state,setState:e=>{null==o.options.onStateChange||o.options.onStateChange(e)},_getRowId:(e,t,r)=>{var n;return null!=(n=null==o.options.getRowId?void 0:o.options.getRowId(e,t,r))?n:`${r?[r.id,t].join("."):t}`},getCoreRowModel:()=>(o._getCoreRowModel||(o._getCoreRowModel=o.options.getCoreRowModel(o)),o._getCoreRowModel()),getRowModel:()=>o.getPaginationRowModel(),getRow:(e,t)=>{let r=(t?o.getPrePaginationRowModel():o.getRowModel()).rowsById[e];if(!r&&!(r=o.getCoreRowModel().rowsById[e]))throw Error();return r},_getDefaultColumnDef:a(()=>[o.options.defaultColumn],e=>{var t;return e=null!=(t=e)?t:{},{header:e=>{let t=e.header.column.columnDef;return t.accessorKey?t.accessorKey:t.accessorFn?t.id:null},cell:e=>{var t,r;return null!=(t=null==(r=e.renderValue())||null==r.toString?void 0:r.toString())?t:null},...o._features.reduce((e,t)=>Object.assign(e,null==t.getDefaultColumnDef?void 0:t.getDefaultColumnDef()),{}),...e}},l(e,"debugColumns","_getDefaultColumnDef")),_getColumnDefs:()=>o.options.columns,getAllColumns:a(()=>[o._getColumnDefs()],e=>{let t=function(e,r,n){return void 0===n&&(n=0),e.map(e=>{let i=function(e,t,r,n){var i,o;let u,s={...e._getDefaultColumnDef(),...t},c=s.accessorKey,f=null!=(i=null!=(o=s.id)?o:c?"function"==typeof String.prototype.replaceAll?c.replaceAll(".","_"):c.replace(/\./g,"_"):void 0)?i:"string"==typeof s.header?s.header:void 0;if(s.accessorFn?u=s.accessorFn:c&&(u=c.includes(".")?e=>{let t=e;for(let e of c.split(".")){var r;t=null==(r=t)?void 0:r[e]}return t}:e=>e[s.accessorKey]),!f)throw Error();let d={id:`${String(f)}`,accessorFn:u,parent:n,depth:r,columnDef:s,columns:[],getFlatColumns:a(()=>[!0],()=>{var e;return[d,...null==(e=d.columns)?void 0:e.flatMap(e=>e.getFlatColumns())]},l(e.options,"debugColumns","column.getFlatColumns")),getLeafColumns:a(()=>[e._getOrderColumnsFn()],e=>{var t;return null!=(t=d.columns)&&t.length?e(d.columns.flatMap(e=>e.getLeafColumns())):[d]},l(e.options,"debugColumns","column.getLeafColumns"))};for(let t of e._features)null==t.createColumn||t.createColumn(d,e);return d}(o,e,n,r);return i.columns=e.columns?t(e.columns,i,n+1):[],i})};return t(e)},l(e,"debugColumns","getAllColumns")),getAllFlatColumns:a(()=>[o.getAllColumns()],e=>e.flatMap(e=>e.getFlatColumns()),l(e,"debugColumns","getAllFlatColumns")),_getAllFlatColumnsById:a(()=>[o.getAllFlatColumns()],e=>e.reduce((e,t)=>(e[t.id]=t,e),{}),l(e,"debugColumns","getAllFlatColumnsById")),getAllLeafColumns:a(()=>[o.getAllColumns(),o._getOrderColumnsFn()],(e,t)=>t(e.flatMap(e=>e.getLeafColumns())),l(e,"debugColumns","getAllLeafColumns")),getColumn:e=>o._getAllFlatColumnsById()[e]};Object.assign(o,d);for(let e=0;ea(()=>[e.options.data],t=>{let r={rows:[],flatRows:[],rowsById:{}},n=function(t,i,o){void 0===i&&(i=0);let a=[];for(let u=0;ue._autoResetPageIndex()))}function G(e,t,r){return r.options.filterFromLeafRows?function(e,t,r){var n;let i=[],o={},a=null!=(n=r.options.maxLeafRowFilterDepth)?n:100,l=function(e,n){void 0===n&&(n=0);let u=[];for(let c=0;ca(()=>[e.getPreFilteredRowModel(),e.getState().columnFilters,e.getState().globalFilter,e.getFilteredRowModel()],(r,n,i)=>{if(!r.rows.length||!(null!=n&&n.length)&&!i)return r;let o=[...n.map(e=>e.id).filter(e=>e!==t),i?"__global__":void 0].filter(Boolean);return G(r.rows,e=>{for(let t=0;ta(()=>{var r;return[null==(r=e.getColumn(t))?void 0:r.getFacetedRowModel()]},e=>{if(!e)return new Map;let r=new Map;for(let i=0;ia(()=>[e.getPreFilteredRowModel(),e.getState().columnFilters,e.getState().globalFilter],(t,r,n)=>{let i,o;if(!t.rows.length||!(null!=r&&r.length)&&!n){for(let e=0;e{var r;let n=e.getColumn(t.id);if(!n)return;let i=n.getFilterFn();i&&a.push({id:t.id,filterFn:i,resolvedValue:null!=(r=null==i.resolveFilterValue?void 0:i.resolveFilterValue(t.value))?r:t.value})});let u=(null!=r?r:[]).map(e=>e.id),s=e.getGlobalFilterFn(),c=e.getAllLeafColumns().filter(e=>e.getCanGlobalFilter());n&&s&&c.length&&(u.push("__global__"),c.forEach(e=>{var t;l.push({id:e.id,filterFn:s,resolvedValue:null!=(t=null==s.resolveFilterValue?void 0:s.resolveFilterValue(n))?t:n})}));for(let e=0;e{r.columnFiltersMeta[t]=e})}if(l.length){for(let e=0;e{r.columnFiltersMeta[t]=e})){r.columnFilters.__global__=!0;break}}!0!==r.columnFilters.__global__&&(r.columnFilters.__global__=!1)}}return G(t.rows,e=>{for(let t=0;te._autoResetPageIndex()))}function q(e){return e=>a(()=>[e.getState().pagination,e.getPrePaginationRowModel(),e.options.paginateExpandedRows?void 0:e.getState().expanded],(t,r)=>{let n;if(!r.rows.length)return r;let{pageSize:i,pageIndex:o}=t,{rows:a,flatRows:l,rowsById:u}=r,s=i*o;a=a.slice(s,s+i),(n=e.options.paginateExpandedRows?{rows:a,flatRows:l,rowsById:u}:function(e){let t=[],r=e=>{var n;t.push(e),null!=(n=e.subRows)&&n.length&&e.getIsExpanded()&&e.subRows.forEach(r)};return e.rows.forEach(r),{rows:t,flatRows:e.flatRows,rowsById:e.rowsById}}({rows:a,flatRows:l,rowsById:u})).flatRows=[];let c=e=>{n.flatRows.push(e),e.subRows.length&&e.subRows.forEach(c)};return n.rows.forEach(c),n},l(e.options,"debugTable","getPaginationRowModel"))}function Y(){return e=>a(()=>[e.getState().sorting,e.getPreSortedRowModel()],(t,r)=>{if(!r.rows.length||!(null!=t&&t.length))return r;let n=e.getState().sorting,i=[],o=n.filter(t=>{var r;return null==(r=e.getColumn(t.id))?void 0:r.getCanSort()}),a={};o.forEach(t=>{let r=e.getColumn(t.id);r&&(a[t.id]={sortUndefined:r.columnDef.sortUndefined,invertSorting:r.columnDef.invertSorting,sortingFn:r.getSortingFn()})});let l=e=>{let t=e.map(e=>({...e}));return t.sort((e,t)=>{for(let n=0;n{var t;i.push(e),null!=(t=e.subRows)&&t.length&&(e.subRows=l(e.subRows))}),t};return{rows:l(r.rows),flatRows:i,rowsById:r.rowsById}},l(e.options,"debugTable","getSortedRowModel",()=>e._autoResetPageIndex()))}},1147:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isPlainObject=function(e){if(!e||"object"!=typeof e)return!1;let t=Object.getPrototypeOf(e);return(null===t||t===Object.prototype||null===Object.getPrototypeOf(t))&&"[object Object]"===Object.prototype.toString.call(e)}},1206:(e,t,r)=>{"use strict";r.d(t,{FN:()=>n}),r(8266);let n=e=>{let{transform:t}=e;return{...t,x:0}}},1243:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("triangle-alert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]])},1285:(e,t,r)=>{"use strict";r.d(t,{B:()=>u});var n,i=r(2115),o=r(2712),a=(n||(n=r.t(i,2)))[" useId ".trim().toString()]||(()=>void 0),l=0;function u(e){let[t,r]=i.useState(a());return(0,o.N)(()=>{e||r(e=>e??String(l++))},[e]),e||(t?`radix-${t}`:"")}},1414:(e,t,r)=>{"use strict";e.exports=r(2436)},1551:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(668),i=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,o=/^\w*$/;t.isKey=function(e,t){return!Array.isArray(e)&&(!!("number"==typeof e||"boolean"==typeof e||null==e||n.isSymbol(e))||"string"==typeof e&&(o.test(e)||!i.test(e))||null!=t&&Object.hasOwn(t,e))}},1571:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(2465),i=r(2194),o=r(4804),a=r(4517);t.iteratee=function(e){if(null==e)return n.identity;switch(typeof e){case"function":return e;case"object":if(Array.isArray(e)&&2===e.length)return a.matchesProperty(e[0],e[1]);return o.matches(e);case"string":case"symbol":case"number":return i.property(e)}}},1643:(e,t,r)=>{"use strict";r.d(t,{m:()=>n});var n={isSsr:!("undefined"!=typeof window&&window.document&&window.document.createElement&&window.setTimeout)}},1807:(e,t,r)=>{"use strict";r.d(t,{r:()=>o});var n=r(2115),i=(0,n.createContext)(null),o=()=>null!=(0,n.useContext)(i)},1847:(e,t,r)=>{"use strict";r.d(t,{i:()=>u});let n=Math.PI,i=2*n,o=i-1e-6;function a(e){this._+=e[0];for(let t=1,r=e.length;t=0))throw Error(`invalid digits: ${e}`);if(t>15)return a;let r=10**t;return function(e){this._+=e[0];for(let t=1,n=e.length;t1e-6)if(Math.abs(f*u-s*c)>1e-6&&o){let p=r-a,h=i-l,g=u*u+s*s,v=Math.sqrt(g),m=Math.sqrt(d),y=o*Math.tan((n-Math.acos((g+d-(p*p+h*h))/(2*v*m)))/2),b=y/m,w=y/v;Math.abs(b-1)>1e-6&&this._append`L${e+b*c},${t+b*f}`,this._append`A${o},${o},0,0,${+(f*p>c*h)},${this._x1=e+w*u},${this._y1=t+w*s}`}else this._append`L${this._x1=e},${this._y1=t}`}arc(e,t,r,a,l,u){if(e*=1,t*=1,r*=1,u=!!u,r<0)throw Error(`negative radius: ${r}`);let s=r*Math.cos(a),c=r*Math.sin(a),f=e+s,d=t+c,p=1^u,h=u?a-l:l-a;null===this._x1?this._append`M${f},${d}`:(Math.abs(this._x1-f)>1e-6||Math.abs(this._y1-d)>1e-6)&&this._append`L${f},${d}`,r&&(h<0&&(h=h%i+i),h>o?this._append`A${r},${r},0,1,${p},${e-s},${t-c}A${r},${r},0,1,${p},${this._x1=f},${this._y1=d}`:h>1e-6&&this._append`A${r},${r},0,${+(h>=n)},${p},${this._x1=e+r*Math.cos(l)},${this._y1=t+r*Math.sin(l)}`)}rect(e,t,r,n){this._append`M${this._x0=this._x1=+e},${this._y0=this._y1=+t}h${r*=1}v${+n}h${-r}Z`}toString(){return this._}}function u(e){let t=3;return e.digits=function(r){if(!arguments.length)return t;if(null==r)t=null;else{let e=Math.floor(r);if(!(e>=0))throw RangeError(`invalid digits: ${r}`);t=e}return e},()=>new l(t)}l.prototype},1918:(e,t,r)=>{"use strict";r.d(t,{UC:()=>r4,In:()=>r5,q7:()=>r8,VF:()=>r7,p4:()=>r9,ZL:()=>r6,bL:()=>r0,wn:()=>nt,PP:()=>ne,l9:()=>r1,WT:()=>r2,LM:()=>r3});var n,i,o,a=r(2115),l=r(7650);function u(e,[t,r]){return Math.min(r,Math.max(t,e))}var s=r(5185),c=r(7328),f=r(6101),d=r(6081),p=r(4315),h=r(3655),g=r(9033),v=r(5155),m="dismissableLayer.update",y=a.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),b=a.forwardRef((e,t)=>{var r,n;let{disableOutsidePointerEvents:o=!1,onEscapeKeyDown:l,onPointerDownOutside:u,onFocusOutside:c,onInteractOutside:d,onDismiss:p,...b}=e,S=a.useContext(y),[O,E]=a.useState(null),C=null!=(n=null==O?void 0:O.ownerDocument)?n:null==(r=globalThis)?void 0:r.document,[,M]=a.useState({}),k=(0,f.s)(t,e=>E(e)),_=Array.from(S.layers),[P]=[...S.layersWithOutsidePointerEventsDisabled].slice(-1),A=_.indexOf(P),j=O?_.indexOf(O):-1,T=S.layersWithOutsidePointerEventsDisabled.size>0,R=j>=A,z=function(e){var t;let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null==(t=globalThis)?void 0:t.document,n=(0,g.c)(e),i=a.useRef(!1),o=a.useRef(()=>{});return a.useEffect(()=>{let e=e=>{if(e.target&&!i.current){let t=function(){x("dismissableLayer.pointerDownOutside",n,i,{discrete:!0})},i={originalEvent:e};"touch"===e.pointerType?(r.removeEventListener("click",o.current),o.current=t,r.addEventListener("click",o.current,{once:!0})):t()}else r.removeEventListener("click",o.current);i.current=!1},t=window.setTimeout(()=>{r.addEventListener("pointerdown",e)},0);return()=>{window.clearTimeout(t),r.removeEventListener("pointerdown",e),r.removeEventListener("click",o.current)}},[r,n]),{onPointerDownCapture:()=>i.current=!0}}(e=>{let t=e.target,r=[...S.branches].some(e=>e.contains(t));R&&!r&&(null==u||u(e),null==d||d(e),e.defaultPrevented||null==p||p())},C),D=function(e){var t;let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null==(t=globalThis)?void 0:t.document,n=(0,g.c)(e),i=a.useRef(!1);return a.useEffect(()=>{let e=e=>{e.target&&!i.current&&x("dismissableLayer.focusOutside",n,{originalEvent:e},{discrete:!1})};return r.addEventListener("focusin",e),()=>r.removeEventListener("focusin",e)},[r,n]),{onFocusCapture:()=>i.current=!0,onBlurCapture:()=>i.current=!1}}(e=>{let t=e.target;![...S.branches].some(e=>e.contains(t))&&(null==c||c(e),null==d||d(e),e.defaultPrevented||null==p||p())},C);return!function(e,t=globalThis?.document){let r=(0,g.c)(e);a.useEffect(()=>{let e=e=>{"Escape"===e.key&&r(e)};return t.addEventListener("keydown",e,{capture:!0}),()=>t.removeEventListener("keydown",e,{capture:!0})},[r,t])}(e=>{j===S.layers.size-1&&(null==l||l(e),!e.defaultPrevented&&p&&(e.preventDefault(),p()))},C),a.useEffect(()=>{if(O)return o&&(0===S.layersWithOutsidePointerEventsDisabled.size&&(i=C.body.style.pointerEvents,C.body.style.pointerEvents="none"),S.layersWithOutsidePointerEventsDisabled.add(O)),S.layers.add(O),w(),()=>{o&&1===S.layersWithOutsidePointerEventsDisabled.size&&(C.body.style.pointerEvents=i)}},[O,C,o,S]),a.useEffect(()=>()=>{O&&(S.layers.delete(O),S.layersWithOutsidePointerEventsDisabled.delete(O),w())},[O,S]),a.useEffect(()=>{let e=()=>M({});return document.addEventListener(m,e),()=>document.removeEventListener(m,e)},[]),(0,v.jsx)(h.sG.div,{...b,ref:k,style:{pointerEvents:T?R?"auto":"none":void 0,...e.style},onFocusCapture:(0,s.m)(e.onFocusCapture,D.onFocusCapture),onBlurCapture:(0,s.m)(e.onBlurCapture,D.onBlurCapture),onPointerDownCapture:(0,s.m)(e.onPointerDownCapture,z.onPointerDownCapture)})});function w(){let e=new CustomEvent(m);document.dispatchEvent(e)}function x(e,t,r,n){let{discrete:i}=n,o=r.originalEvent.target,a=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:r});t&&o.addEventListener(e,t,{once:!0}),i?(0,h.hO)(o,a):o.dispatchEvent(a)}b.displayName="DismissableLayer",a.forwardRef((e,t)=>{let r=a.useContext(y),n=a.useRef(null),i=(0,f.s)(t,n);return a.useEffect(()=>{let e=n.current;if(e)return r.branches.add(e),()=>{r.branches.delete(e)}},[r.branches]),(0,v.jsx)(h.sG.div,{...e,ref:i})}).displayName="DismissableLayerBranch";var S=0;function O(){let e=document.createElement("span");return e.setAttribute("data-radix-focus-guard",""),e.tabIndex=0,e.style.outline="none",e.style.opacity="0",e.style.position="fixed",e.style.pointerEvents="none",e}var E="focusScope.autoFocusOnMount",C="focusScope.autoFocusOnUnmount",M={bubbles:!1,cancelable:!0},k=a.forwardRef((e,t)=>{let{loop:r=!1,trapped:n=!1,onMountAutoFocus:i,onUnmountAutoFocus:o,...l}=e,[u,s]=a.useState(null),c=(0,g.c)(i),d=(0,g.c)(o),p=a.useRef(null),m=(0,f.s)(t,e=>s(e)),y=a.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;a.useEffect(()=>{if(n){let e=function(e){if(y.paused||!u)return;let t=e.target;u.contains(t)?p.current=t:A(p.current,{select:!0})},t=function(e){if(y.paused||!u)return;let t=e.relatedTarget;null!==t&&(u.contains(t)||A(p.current,{select:!0}))};document.addEventListener("focusin",e),document.addEventListener("focusout",t);let r=new MutationObserver(function(e){if(document.activeElement===document.body)for(let t of e)t.removedNodes.length>0&&A(u)});return u&&r.observe(u,{childList:!0,subtree:!0}),()=>{document.removeEventListener("focusin",e),document.removeEventListener("focusout",t),r.disconnect()}}},[n,u,y.paused]),a.useEffect(()=>{if(u){j.add(y);let e=document.activeElement;if(!u.contains(e)){let t=new CustomEvent(E,M);u.addEventListener(E,c),u.dispatchEvent(t),t.defaultPrevented||(function(e){let{select:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=document.activeElement;for(let n of e)if(A(n,{select:t}),document.activeElement!==r)return}(_(u).filter(e=>"A"!==e.tagName),{select:!0}),document.activeElement===e&&A(u))}return()=>{u.removeEventListener(E,c),setTimeout(()=>{let t=new CustomEvent(C,M);u.addEventListener(C,d),u.dispatchEvent(t),t.defaultPrevented||A(null!=e?e:document.body,{select:!0}),u.removeEventListener(C,d),j.remove(y)},0)}}},[u,c,d,y]);let b=a.useCallback(e=>{if(!r&&!n||y.paused)return;let t="Tab"===e.key&&!e.altKey&&!e.ctrlKey&&!e.metaKey,i=document.activeElement;if(t&&i){let t=e.currentTarget,[n,o]=function(e){let t=_(e);return[P(t,e),P(t.reverse(),e)]}(t);n&&o?e.shiftKey||i!==o?e.shiftKey&&i===n&&(e.preventDefault(),r&&A(o,{select:!0})):(e.preventDefault(),r&&A(n,{select:!0})):i===t&&e.preventDefault()}},[r,n,y.paused]);return(0,v.jsx)(h.sG.div,{tabIndex:-1,...l,ref:m,onKeyDown:b})});function _(e){let t=[],r=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:e=>{let t="INPUT"===e.tagName&&"hidden"===e.type;return e.disabled||e.hidden||t?NodeFilter.FILTER_SKIP:e.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;r.nextNode();)t.push(r.currentNode);return t}function P(e,t){for(let r of e)if(!function(e,t){let{upTo:r}=t;if("hidden"===getComputedStyle(e).visibility)return!0;for(;e&&(void 0===r||e!==r);){if("none"===getComputedStyle(e).display)return!0;e=e.parentElement}return!1}(r,{upTo:t}))return r}function A(e){let{select:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(e&&e.focus){var r;let n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&(r=e)instanceof HTMLInputElement&&"select"in r&&t&&e.select()}}k.displayName="FocusScope";var j=function(){let e=[];return{add(t){let r=e[0];t!==r&&(null==r||r.pause()),(e=T(e,t)).unshift(t)},remove(t){var r;null==(r=(e=T(e,t))[0])||r.resume()}}}();function T(e,t){let r=[...e],n=r.indexOf(t);return -1!==n&&r.splice(n,1),r}var R=r(1285);let z=["top","right","bottom","left"],D=Math.min,I=Math.max,N=Math.round,L=Math.floor,F=e=>({x:e,y:e}),$={left:"right",right:"left",bottom:"top",top:"bottom"},B={start:"end",end:"start"};function V(e,t){return"function"==typeof e?e(t):e}function U(e){return e.split("-")[0]}function H(e){return e.split("-")[1]}function G(e){return"x"===e?"y":"x"}function Z(e){return"y"===e?"height":"width"}let W=new Set(["top","bottom"]);function K(e){return W.has(U(e))?"y":"x"}function q(e){return e.replace(/start|end/g,e=>B[e])}let Y=["left","right"],X=["right","left"],J=["top","bottom"],Q=["bottom","top"];function ee(e){return e.replace(/left|right|bottom|top/g,e=>$[e])}function et(e){return"number"!=typeof e?{top:0,right:0,bottom:0,left:0,...e}:{top:e,right:e,bottom:e,left:e}}function er(e){let{x:t,y:r,width:n,height:i}=e;return{width:n,height:i,top:r,left:t,right:t+n,bottom:r+i,x:t,y:r}}function en(e,t,r){let n,{reference:i,floating:o}=e,a=K(t),l=G(K(t)),u=Z(l),s=U(t),c="y"===a,f=i.x+i.width/2-o.width/2,d=i.y+i.height/2-o.height/2,p=i[u]/2-o[u]/2;switch(s){case"top":n={x:f,y:i.y-o.height};break;case"bottom":n={x:f,y:i.y+i.height};break;case"right":n={x:i.x+i.width,y:d};break;case"left":n={x:i.x-o.width,y:d};break;default:n={x:i.x,y:i.y}}switch(H(t)){case"start":n[l]-=p*(r&&c?-1:1);break;case"end":n[l]+=p*(r&&c?-1:1)}return n}let ei=async(e,t,r)=>{let{placement:n="bottom",strategy:i="absolute",middleware:o=[],platform:a}=r,l=o.filter(Boolean),u=await (null==a.isRTL?void 0:a.isRTL(t)),s=await a.getElementRects({reference:e,floating:t,strategy:i}),{x:c,y:f}=en(s,n,u),d=n,p={},h=0;for(let r=0;re[t]>=0)}let eu=new Set(["left","top"]);async function es(e,t){let{placement:r,platform:n,elements:i}=e,o=await (null==n.isRTL?void 0:n.isRTL(i.floating)),a=U(r),l=H(r),u="y"===K(r),s=eu.has(a)?-1:1,c=o&&u?-1:1,f=V(t,e),{mainAxis:d,crossAxis:p,alignmentAxis:h}="number"==typeof f?{mainAxis:f,crossAxis:0,alignmentAxis:null}:{mainAxis:f.mainAxis||0,crossAxis:f.crossAxis||0,alignmentAxis:f.alignmentAxis};return l&&"number"==typeof h&&(p="end"===l?-1*h:h),u?{x:p*c,y:d*s}:{x:d*s,y:p*c}}function ec(){return"undefined"!=typeof window}function ef(e){return eh(e)?(e.nodeName||"").toLowerCase():"#document"}function ed(e){var t;return(null==e||null==(t=e.ownerDocument)?void 0:t.defaultView)||window}function ep(e){var t;return null==(t=(eh(e)?e.ownerDocument:e.document)||window.document)?void 0:t.documentElement}function eh(e){return!!ec()&&(e instanceof Node||e instanceof ed(e).Node)}function eg(e){return!!ec()&&(e instanceof Element||e instanceof ed(e).Element)}function ev(e){return!!ec()&&(e instanceof HTMLElement||e instanceof ed(e).HTMLElement)}function em(e){return!!ec()&&"undefined"!=typeof ShadowRoot&&(e instanceof ShadowRoot||e instanceof ed(e).ShadowRoot)}let ey=new Set(["inline","contents"]);function eb(e){let{overflow:t,overflowX:r,overflowY:n,display:i}=eA(e);return/auto|scroll|overlay|hidden|clip/.test(t+n+r)&&!ey.has(i)}let ew=new Set(["table","td","th"]),ex=[":popover-open",":modal"];function eS(e){return ex.some(t=>{try{return e.matches(t)}catch(e){return!1}})}let eO=["transform","translate","scale","rotate","perspective"],eE=["transform","translate","scale","rotate","perspective","filter"],eC=["paint","layout","strict","content"];function eM(e){let t=ek(),r=eg(e)?eA(e):e;return eO.some(e=>!!r[e]&&"none"!==r[e])||!!r.containerType&&"normal"!==r.containerType||!t&&!!r.backdropFilter&&"none"!==r.backdropFilter||!t&&!!r.filter&&"none"!==r.filter||eE.some(e=>(r.willChange||"").includes(e))||eC.some(e=>(r.contain||"").includes(e))}function ek(){return"undefined"!=typeof CSS&&!!CSS.supports&&CSS.supports("-webkit-backdrop-filter","none")}let e_=new Set(["html","body","#document"]);function eP(e){return e_.has(ef(e))}function eA(e){return ed(e).getComputedStyle(e)}function ej(e){return eg(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function eT(e){if("html"===ef(e))return e;let t=e.assignedSlot||e.parentNode||em(e)&&e.host||ep(e);return em(t)?t.host:t}function eR(e,t,r){var n;void 0===t&&(t=[]),void 0===r&&(r=!0);let i=function e(t){let r=eT(t);return eP(r)?t.ownerDocument?t.ownerDocument.body:t.body:ev(r)&&eb(r)?r:e(r)}(e),o=i===(null==(n=e.ownerDocument)?void 0:n.body),a=ed(i);if(o){let e=ez(a);return t.concat(a,a.visualViewport||[],eb(i)?i:[],e&&r?eR(e):[])}return t.concat(i,eR(i,[],r))}function ez(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function eD(e){let t=eA(e),r=parseFloat(t.width)||0,n=parseFloat(t.height)||0,i=ev(e),o=i?e.offsetWidth:r,a=i?e.offsetHeight:n,l=N(r)!==o||N(n)!==a;return l&&(r=o,n=a),{width:r,height:n,$:l}}function eI(e){return eg(e)?e:e.contextElement}function eN(e){let t=eI(e);if(!ev(t))return F(1);let r=t.getBoundingClientRect(),{width:n,height:i,$:o}=eD(t),a=(o?N(r.width):r.width)/n,l=(o?N(r.height):r.height)/i;return a&&Number.isFinite(a)||(a=1),l&&Number.isFinite(l)||(l=1),{x:a,y:l}}let eL=F(0);function eF(e){let t=ed(e);return ek()&&t.visualViewport?{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}:eL}function e$(e,t,r,n){var i;void 0===t&&(t=!1),void 0===r&&(r=!1);let o=e.getBoundingClientRect(),a=eI(e),l=F(1);t&&(n?eg(n)&&(l=eN(n)):l=eN(e));let u=(void 0===(i=r)&&(i=!1),n&&(!i||n===ed(a))&&i)?eF(a):F(0),s=(o.left+u.x)/l.x,c=(o.top+u.y)/l.y,f=o.width/l.x,d=o.height/l.y;if(a){let e=ed(a),t=n&&eg(n)?ed(n):n,r=e,i=ez(r);for(;i&&n&&t!==r;){let e=eN(i),t=i.getBoundingClientRect(),n=eA(i),o=t.left+(i.clientLeft+parseFloat(n.paddingLeft))*e.x,a=t.top+(i.clientTop+parseFloat(n.paddingTop))*e.y;s*=e.x,c*=e.y,f*=e.x,d*=e.y,s+=o,c+=a,i=ez(r=ed(i))}}return er({width:f,height:d,x:s,y:c})}function eB(e,t){let r=ej(e).scrollLeft;return t?t.left+r:e$(ep(e)).left+r}function eV(e,t,r){void 0===r&&(r=!1);let n=e.getBoundingClientRect();return{x:n.left+t.scrollLeft-(r?0:eB(e,n)),y:n.top+t.scrollTop}}let eU=new Set(["absolute","fixed"]);function eH(e,t,r){let n;if("viewport"===t)n=function(e,t){let r=ed(e),n=ep(e),i=r.visualViewport,o=n.clientWidth,a=n.clientHeight,l=0,u=0;if(i){o=i.width,a=i.height;let e=ek();(!e||e&&"fixed"===t)&&(l=i.offsetLeft,u=i.offsetTop)}return{width:o,height:a,x:l,y:u}}(e,r);else if("document"===t)n=function(e){let t=ep(e),r=ej(e),n=e.ownerDocument.body,i=I(t.scrollWidth,t.clientWidth,n.scrollWidth,n.clientWidth),o=I(t.scrollHeight,t.clientHeight,n.scrollHeight,n.clientHeight),a=-r.scrollLeft+eB(e),l=-r.scrollTop;return"rtl"===eA(n).direction&&(a+=I(t.clientWidth,n.clientWidth)-i),{width:i,height:o,x:a,y:l}}(ep(e));else if(eg(t))n=function(e,t){let r=e$(e,!0,"fixed"===t),n=r.top+e.clientTop,i=r.left+e.clientLeft,o=ev(e)?eN(e):F(1),a=e.clientWidth*o.x,l=e.clientHeight*o.y;return{width:a,height:l,x:i*o.x,y:n*o.y}}(t,r);else{let r=eF(e);n={x:t.x-r.x,y:t.y-r.y,width:t.width,height:t.height}}return er(n)}function eG(e){return"static"===eA(e).position}function eZ(e,t){if(!ev(e)||"fixed"===eA(e).position)return null;if(t)return t(e);let r=e.offsetParent;return ep(e)===r&&(r=r.ownerDocument.body),r}function eW(e,t){var r;let n=ed(e);if(eS(e))return n;if(!ev(e)){let t=eT(e);for(;t&&!eP(t);){if(eg(t)&&!eG(t))return t;t=eT(t)}return n}let i=eZ(e,t);for(;i&&(r=i,ew.has(ef(r)))&&eG(i);)i=eZ(i,t);return i&&eP(i)&&eG(i)&&!eM(i)?n:i||function(e){let t=eT(e);for(;ev(t)&&!eP(t);){if(eM(t))return t;if(eS(t))break;t=eT(t)}return null}(e)||n}let eK=async function(e){let t=this.getOffsetParent||eW,r=this.getDimensions,n=await r(e.floating);return{reference:function(e,t,r){let n=ev(t),i=ep(t),o="fixed"===r,a=e$(e,!0,o,t),l={scrollLeft:0,scrollTop:0},u=F(0);if(n||!n&&!o)if(("body"!==ef(t)||eb(i))&&(l=ej(t)),n){let e=e$(t,!0,o,t);u.x=e.x+t.clientLeft,u.y=e.y+t.clientTop}else i&&(u.x=eB(i));o&&!n&&i&&(u.x=eB(i));let s=!i||n||o?F(0):eV(i,l);return{x:a.left+l.scrollLeft-u.x-s.x,y:a.top+l.scrollTop-u.y-s.y,width:a.width,height:a.height}}(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:n.width,height:n.height}}},eq={convertOffsetParentRelativeRectToViewportRelativeRect:function(e){let{elements:t,rect:r,offsetParent:n,strategy:i}=e,o="fixed"===i,a=ep(n),l=!!t&&eS(t.floating);if(n===a||l&&o)return r;let u={scrollLeft:0,scrollTop:0},s=F(1),c=F(0),f=ev(n);if((f||!f&&!o)&&(("body"!==ef(n)||eb(a))&&(u=ej(n)),ev(n))){let e=e$(n);s=eN(n),c.x=e.x+n.clientLeft,c.y=e.y+n.clientTop}let d=!a||f||o?F(0):eV(a,u,!0);return{width:r.width*s.x,height:r.height*s.y,x:r.x*s.x-u.scrollLeft*s.x+c.x+d.x,y:r.y*s.y-u.scrollTop*s.y+c.y+d.y}},getDocumentElement:ep,getClippingRect:function(e){let{element:t,boundary:r,rootBoundary:n,strategy:i}=e,o=[..."clippingAncestors"===r?eS(t)?[]:function(e,t){let r=t.get(e);if(r)return r;let n=eR(e,[],!1).filter(e=>eg(e)&&"body"!==ef(e)),i=null,o="fixed"===eA(e).position,a=o?eT(e):e;for(;eg(a)&&!eP(a);){let t=eA(a),r=eM(a);r||"fixed"!==t.position||(i=null),(o?!r&&!i:!r&&"static"===t.position&&!!i&&eU.has(i.position)||eb(a)&&!r&&function e(t,r){let n=eT(t);return!(n===r||!eg(n)||eP(n))&&("fixed"===eA(n).position||e(n,r))}(e,a))?n=n.filter(e=>e!==a):i=t,a=eT(a)}return t.set(e,n),n}(t,this._c):[].concat(r),n],a=o[0],l=o.reduce((e,r)=>{let n=eH(t,r,i);return e.top=I(n.top,e.top),e.right=D(n.right,e.right),e.bottom=D(n.bottom,e.bottom),e.left=I(n.left,e.left),e},eH(t,a,i));return{width:l.right-l.left,height:l.bottom-l.top,x:l.left,y:l.top}},getOffsetParent:eW,getElementRects:eK,getClientRects:function(e){return Array.from(e.getClientRects())},getDimensions:function(e){let{width:t,height:r}=eD(e);return{width:t,height:r}},getScale:eN,isElement:eg,isRTL:function(e){return"rtl"===eA(e).direction}};function eY(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}let eX=e=>({name:"arrow",options:e,async fn(t){let{x:r,y:n,placement:i,rects:o,platform:a,elements:l,middlewareData:u}=t,{element:s,padding:c=0}=V(e,t)||{};if(null==s)return{};let f=et(c),d={x:r,y:n},p=G(K(i)),h=Z(p),g=await a.getDimensions(s),v="y"===p,m=v?"clientHeight":"clientWidth",y=o.reference[h]+o.reference[p]-d[p]-o.floating[h],b=d[p]-o.reference[p],w=await (null==a.getOffsetParent?void 0:a.getOffsetParent(s)),x=w?w[m]:0;x&&await (null==a.isElement?void 0:a.isElement(w))||(x=l.floating[m]||o.floating[h]);let S=x/2-g[h]/2-1,O=D(f[v?"top":"left"],S),E=D(f[v?"bottom":"right"],S),C=x-g[h]-E,M=x/2-g[h]/2+(y/2-b/2),k=I(O,D(M,C)),_=!u.arrow&&null!=H(i)&&M!==k&&o.reference[h]/2-(M{t.current=e}),t}var e5=a.forwardRef((e,t)=>{let{children:r,width:n=10,height:i=5,...o}=e;return(0,v.jsx)(h.sG.svg,{...o,ref:t,width:n,height:i,viewBox:"0 0 30 10",preserveAspectRatio:"none",children:e.asChild?r:(0,v.jsx)("polygon",{points:"0,0 30,0 15,10"})})});e5.displayName="Arrow";var e6=r(2712),e4="Popper",[e3,e8]=(0,d.A)(e4),[e9,e7]=e3(e4),te=e=>{let{__scopePopper:t,children:r}=e,[n,i]=a.useState(null);return(0,v.jsx)(e9,{scope:t,anchor:n,onAnchorChange:i,children:r})};te.displayName=e4;var tt="PopperAnchor",tr=a.forwardRef((e,t)=>{let{__scopePopper:r,virtualRef:n,...i}=e,o=e7(tt,r),l=a.useRef(null),u=(0,f.s)(t,l);return a.useEffect(()=>{o.onAnchorChange((null==n?void 0:n.current)||l.current)}),n?null:(0,v.jsx)(h.sG.div,{...i,ref:u})});tr.displayName=tt;var tn="PopperContent",[ti,to]=e3(tn),ta=a.forwardRef((e,t)=>{var r,n,i,o,u,s,c,d;let{__scopePopper:p,side:m="bottom",sideOffset:y=0,align:b="center",alignOffset:w=0,arrowPadding:x=0,avoidCollisions:S=!0,collisionBoundary:O=[],collisionPadding:E=0,sticky:C="partial",hideWhenDetached:M=!1,updatePositionStrategy:k="optimized",onPlaced:_,...P}=e,A=e7(tn,p),[j,T]=a.useState(null),R=(0,f.s)(t,e=>T(e)),[z,N]=a.useState(null),F=function(e){let[t,r]=a.useState(void 0);return(0,e6.N)(()=>{if(e){r({width:e.offsetWidth,height:e.offsetHeight});let t=new ResizeObserver(t=>{let n,i;if(!Array.isArray(t)||!t.length)return;let o=t[0];if("borderBoxSize"in o){let e=o.borderBoxSize,t=Array.isArray(e)?e[0]:e;n=t.inlineSize,i=t.blockSize}else n=e.offsetWidth,i=e.offsetHeight;r({width:n,height:i})});return t.observe(e,{box:"border-box"}),()=>t.unobserve(e)}r(void 0)},[e]),t}(z),$=null!=(c=null==F?void 0:F.width)?c:0,B=null!=(d=null==F?void 0:F.height)?d:0,W="number"==typeof E?E:{top:0,right:0,bottom:0,left:0,...E},et=Array.isArray(O)?O:[O],er=et.length>0,en={padding:W,boundary:et.filter(tc),altBoundary:er},{refs:ec,floatingStyles:ef,placement:ed,isPositioned:eh,middlewareData:eg}=function(e){void 0===e&&(e={});let{placement:t="bottom",strategy:r="absolute",middleware:n=[],platform:i,elements:{reference:o,floating:u}={},transform:s=!0,whileElementsMounted:c,open:f}=e,[d,p]=a.useState({x:0,y:0,strategy:r,placement:t,middlewareData:{},isPositioned:!1}),[h,g]=a.useState(n);eQ(h,n)||g(n);let[v,m]=a.useState(null),[y,b]=a.useState(null),w=a.useCallback(e=>{e!==E.current&&(E.current=e,m(e))},[]),x=a.useCallback(e=>{e!==C.current&&(C.current=e,b(e))},[]),S=o||v,O=u||y,E=a.useRef(null),C=a.useRef(null),M=a.useRef(d),k=null!=c,_=e2(c),P=e2(i),A=e2(f),j=a.useCallback(()=>{if(!E.current||!C.current)return;let e={placement:t,strategy:r,middleware:h};P.current&&(e.platform=P.current),((e,t,r)=>{let n=new Map,i={platform:eq,...r},o={...i.platform,_c:n};return ei(e,t,{...i,platform:o})})(E.current,C.current,e).then(e=>{let t={...e,isPositioned:!1!==A.current};T.current&&!eQ(M.current,t)&&(M.current=t,l.flushSync(()=>{p(t)}))})},[h,t,r,P,A]);eJ(()=>{!1===f&&M.current.isPositioned&&(M.current.isPositioned=!1,p(e=>({...e,isPositioned:!1})))},[f]);let T=a.useRef(!1);eJ(()=>(T.current=!0,()=>{T.current=!1}),[]),eJ(()=>{if(S&&(E.current=S),O&&(C.current=O),S&&O){if(_.current)return _.current(S,O,j);j()}},[S,O,j,_,k]);let R=a.useMemo(()=>({reference:E,floating:C,setReference:w,setFloating:x}),[w,x]),z=a.useMemo(()=>({reference:S,floating:O}),[S,O]),D=a.useMemo(()=>{let e={position:r,left:0,top:0};if(!z.floating)return e;let t=e1(z.floating,d.x),n=e1(z.floating,d.y);return s?{...e,transform:"translate("+t+"px, "+n+"px)",...e0(z.floating)>=1.5&&{willChange:"transform"}}:{position:r,left:t,top:n}},[r,s,z.floating,d.x,d.y]);return a.useMemo(()=>({...d,update:j,refs:R,elements:z,floatingStyles:D}),[d,j,R,z,D])}({strategy:"fixed",placement:m+("center"!==b?"-"+b:""),whileElementsMounted:function(){for(var e=arguments.length,t=Array(e),r=0;r{o&&e.addEventListener("scroll",r,{passive:!0}),a&&e.addEventListener("resize",r)});let d=c&&u?function(e,t){let r,n=null,i=ep(e);function o(){var e;clearTimeout(r),null==(e=n)||e.disconnect(),n=null}return!function a(l,u){void 0===l&&(l=!1),void 0===u&&(u=1),o();let s=e.getBoundingClientRect(),{left:c,top:f,width:d,height:p}=s;if(l||t(),!d||!p)return;let h=L(f),g=L(i.clientWidth-(c+d)),v={rootMargin:-h+"px "+-g+"px "+-L(i.clientHeight-(f+p))+"px "+-L(c)+"px",threshold:I(0,D(1,u))||1},m=!0;function y(t){let n=t[0].intersectionRatio;if(n!==u){if(!m)return a();n?a(!1,n):r=setTimeout(()=>{a(!1,1e-7)},1e3)}1!==n||eY(s,e.getBoundingClientRect())||a(),m=!1}try{n=new IntersectionObserver(y,{...v,root:i.ownerDocument})}catch(e){n=new IntersectionObserver(y,v)}n.observe(e)}(!0),o}(c,r):null,p=-1,h=null;l&&(h=new ResizeObserver(e=>{let[n]=e;n&&n.target===c&&h&&(h.unobserve(t),cancelAnimationFrame(p),p=requestAnimationFrame(()=>{var e;null==(e=h)||e.observe(t)})),r()}),c&&!s&&h.observe(c),h.observe(t));let g=s?e$(e):null;return s&&function t(){let n=e$(e);g&&!eY(g,n)&&r(),g=n,i=requestAnimationFrame(t)}(),r(),()=>{var e;f.forEach(e=>{o&&e.removeEventListener("scroll",r),a&&e.removeEventListener("resize",r)}),null==d||d(),null==(e=h)||e.disconnect(),h=null,s&&cancelAnimationFrame(i)}}(...t,{animationFrame:"always"===k})},elements:{reference:A.anchor},middleware:[((e,t)=>({...function(e){return void 0===e&&(e=0),{name:"offset",options:e,async fn(t){var r,n;let{x:i,y:o,placement:a,middlewareData:l}=t,u=await es(t,e);return a===(null==(r=l.offset)?void 0:r.placement)&&null!=(n=l.arrow)&&n.alignmentOffset?{}:{x:i+u.x,y:o+u.y,data:{...u,placement:a}}}}}(e),options:[e,t]}))({mainAxis:y+B,alignmentAxis:w}),S&&((e,t)=>({...function(e){return void 0===e&&(e={}),{name:"shift",options:e,async fn(t){let{x:r,y:n,placement:i}=t,{mainAxis:o=!0,crossAxis:a=!1,limiter:l={fn:e=>{let{x:t,y:r}=e;return{x:t,y:r}}},...u}=V(e,t),s={x:r,y:n},c=await eo(t,u),f=K(U(i)),d=G(f),p=s[d],h=s[f];if(o){let e="y"===d?"top":"left",t="y"===d?"bottom":"right",r=p+c[e],n=p-c[t];p=I(r,D(p,n))}if(a){let e="y"===f?"top":"left",t="y"===f?"bottom":"right",r=h+c[e],n=h-c[t];h=I(r,D(h,n))}let g=l.fn({...t,[d]:p,[f]:h});return{...g,data:{x:g.x-r,y:g.y-n,enabled:{[d]:o,[f]:a}}}}}}(e),options:[e,t]}))({mainAxis:!0,crossAxis:!1,limiter:"partial"===C?((e,t)=>({...function(e){return void 0===e&&(e={}),{options:e,fn(t){let{x:r,y:n,placement:i,rects:o,middlewareData:a}=t,{offset:l=0,mainAxis:u=!0,crossAxis:s=!0}=V(e,t),c={x:r,y:n},f=K(i),d=G(f),p=c[d],h=c[f],g=V(l,t),v="number"==typeof g?{mainAxis:g,crossAxis:0}:{mainAxis:0,crossAxis:0,...g};if(u){let e="y"===d?"height":"width",t=o.reference[d]-o.floating[e]+v.mainAxis,r=o.reference[d]+o.reference[e]-v.mainAxis;pr&&(p=r)}if(s){var m,y;let e="y"===d?"width":"height",t=eu.has(U(i)),r=o.reference[f]-o.floating[e]+(t&&(null==(m=a.offset)?void 0:m[f])||0)+(t?0:v.crossAxis),n=o.reference[f]+o.reference[e]+(t?0:(null==(y=a.offset)?void 0:y[f])||0)-(t?v.crossAxis:0);hn&&(h=n)}return{[d]:p,[f]:h}}}}(e),options:[e,t]}))():void 0,...en}),S&&((e,t)=>({...function(e){return void 0===e&&(e={}),{name:"flip",options:e,async fn(t){var r,n,i,o,a;let{placement:l,middlewareData:u,rects:s,initialPlacement:c,platform:f,elements:d}=t,{mainAxis:p=!0,crossAxis:h=!0,fallbackPlacements:g,fallbackStrategy:v="bestFit",fallbackAxisSideDirection:m="none",flipAlignment:y=!0,...b}=V(e,t);if(null!=(r=u.arrow)&&r.alignmentOffset)return{};let w=U(l),x=K(c),S=U(c)===c,O=await (null==f.isRTL?void 0:f.isRTL(d.floating)),E=g||(S||!y?[ee(c)]:function(e){let t=ee(e);return[q(e),t,q(t)]}(c)),C="none"!==m;!g&&C&&E.push(...function(e,t,r,n){let i=H(e),o=function(e,t,r){switch(e){case"top":case"bottom":if(r)return t?X:Y;return t?Y:X;case"left":case"right":return t?J:Q;default:return[]}}(U(e),"start"===r,n);return i&&(o=o.map(e=>e+"-"+i),t&&(o=o.concat(o.map(q)))),o}(c,y,m,O));let M=[c,...E],k=await eo(t,b),_=[],P=(null==(n=u.flip)?void 0:n.overflows)||[];if(p&&_.push(k[w]),h){let e=function(e,t,r){void 0===r&&(r=!1);let n=H(e),i=G(K(e)),o=Z(i),a="x"===i?n===(r?"end":"start")?"right":"left":"start"===n?"bottom":"top";return t.reference[o]>t.floating[o]&&(a=ee(a)),[a,ee(a)]}(l,s,O);_.push(k[e[0]],k[e[1]])}if(P=[...P,{placement:l,overflows:_}],!_.every(e=>e<=0)){let e=((null==(i=u.flip)?void 0:i.index)||0)+1,t=M[e];if(t&&("alignment"!==h||x===K(t)||P.every(e=>e.overflows[0]>0&&K(e.placement)===x)))return{data:{index:e,overflows:P},reset:{placement:t}};let r=null==(o=P.filter(e=>e.overflows[0]<=0).sort((e,t)=>e.overflows[1]-t.overflows[1])[0])?void 0:o.placement;if(!r)switch(v){case"bestFit":{let e=null==(a=P.filter(e=>{if(C){let t=K(e.placement);return t===x||"y"===t}return!0}).map(e=>[e.placement,e.overflows.filter(e=>e>0).reduce((e,t)=>e+t,0)]).sort((e,t)=>e[1]-t[1])[0])?void 0:a[0];e&&(r=e);break}case"initialPlacement":r=c}if(l!==r)return{reset:{placement:r}}}return{}}}}(e),options:[e,t]}))({...en}),((e,t)=>({...function(e){return void 0===e&&(e={}),{name:"size",options:e,async fn(t){var r,n;let i,o,{placement:a,rects:l,platform:u,elements:s}=t,{apply:c=()=>{},...f}=V(e,t),d=await eo(t,f),p=U(a),h=H(a),g="y"===K(a),{width:v,height:m}=l.floating;"top"===p||"bottom"===p?(i=p,o=h===(await (null==u.isRTL?void 0:u.isRTL(s.floating))?"start":"end")?"left":"right"):(o=p,i="end"===h?"top":"bottom");let y=m-d.top-d.bottom,b=v-d.left-d.right,w=D(m-d[i],y),x=D(v-d[o],b),S=!t.middlewareData.shift,O=w,E=x;if(null!=(r=t.middlewareData.shift)&&r.enabled.x&&(E=b),null!=(n=t.middlewareData.shift)&&n.enabled.y&&(O=y),S&&!h){let e=I(d.left,0),t=I(d.right,0),r=I(d.top,0),n=I(d.bottom,0);g?E=v-2*(0!==e||0!==t?e+t:I(d.left,d.right)):O=m-2*(0!==r||0!==n?r+n:I(d.top,d.bottom))}await c({...t,availableWidth:E,availableHeight:O});let C=await u.getDimensions(s.floating);return v!==C.width||m!==C.height?{reset:{rects:!0}}:{}}}}(e),options:[e,t]}))({...en,apply:e=>{let{elements:t,rects:r,availableWidth:n,availableHeight:i}=e,{width:o,height:a}=r.reference,l=t.floating.style;l.setProperty("--radix-popper-available-width","".concat(n,"px")),l.setProperty("--radix-popper-available-height","".concat(i,"px")),l.setProperty("--radix-popper-anchor-width","".concat(o,"px")),l.setProperty("--radix-popper-anchor-height","".concat(a,"px"))}}),z&&((e,t)=>({...(e=>({name:"arrow",options:e,fn(t){let{element:r,padding:n}="function"==typeof e?e(t):e;return r&&({}).hasOwnProperty.call(r,"current")?null!=r.current?eX({element:r.current,padding:n}).fn(t):{}:r?eX({element:r,padding:n}).fn(t):{}}}))(e),options:[e,t]}))({element:z,padding:x}),tf({arrowWidth:$,arrowHeight:B}),M&&((e,t)=>({...function(e){return void 0===e&&(e={}),{name:"hide",options:e,async fn(t){let{rects:r}=t,{strategy:n="referenceHidden",...i}=V(e,t);switch(n){case"referenceHidden":{let e=ea(await eo(t,{...i,elementContext:"reference"}),r.reference);return{data:{referenceHiddenOffsets:e,referenceHidden:el(e)}}}case"escaped":{let e=ea(await eo(t,{...i,altBoundary:!0}),r.floating);return{data:{escapedOffsets:e,escaped:el(e)}}}default:return{}}}}}(e),options:[e,t]}))({strategy:"referenceHidden",...en})]}),[ev,em]=td(ed),ey=(0,g.c)(_);(0,e6.N)(()=>{eh&&(null==ey||ey())},[eh,ey]);let eb=null==(r=eg.arrow)?void 0:r.x,ew=null==(n=eg.arrow)?void 0:n.y,ex=(null==(i=eg.arrow)?void 0:i.centerOffset)!==0,[eS,eO]=a.useState();return(0,e6.N)(()=>{j&&eO(window.getComputedStyle(j).zIndex)},[j]),(0,v.jsx)("div",{ref:ec.setFloating,"data-radix-popper-content-wrapper":"",style:{...ef,transform:eh?ef.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:eS,"--radix-popper-transform-origin":[null==(o=eg.transformOrigin)?void 0:o.x,null==(u=eg.transformOrigin)?void 0:u.y].join(" "),...(null==(s=eg.hide)?void 0:s.referenceHidden)&&{visibility:"hidden",pointerEvents:"none"}},dir:e.dir,children:(0,v.jsx)(ti,{scope:p,placedSide:ev,onArrowChange:N,arrowX:eb,arrowY:ew,shouldHideArrow:ex,children:(0,v.jsx)(h.sG.div,{"data-side":ev,"data-align":em,...P,ref:R,style:{...P.style,animation:eh?void 0:"none"}})})})});ta.displayName=tn;var tl="PopperArrow",tu={top:"bottom",right:"left",bottom:"top",left:"right"},ts=a.forwardRef(function(e,t){let{__scopePopper:r,...n}=e,i=to(tl,r),o=tu[i.placedSide];return(0,v.jsx)("span",{ref:i.onArrowChange,style:{position:"absolute",left:i.arrowX,top:i.arrowY,[o]:0,transformOrigin:{top:"",right:"0 0",bottom:"center 0",left:"100% 0"}[i.placedSide],transform:{top:"translateY(100%)",right:"translateY(50%) rotate(90deg) translateX(-50%)",bottom:"rotate(180deg)",left:"translateY(50%) rotate(-90deg) translateX(50%)"}[i.placedSide],visibility:i.shouldHideArrow?"hidden":void 0},children:(0,v.jsx)(e5,{...n,ref:t,style:{...n.style,display:"block"}})})});function tc(e){return null!==e}ts.displayName=tl;var tf=e=>({name:"transformOrigin",options:e,fn(t){var r,n,i,o,a;let{placement:l,rects:u,middlewareData:s}=t,c=(null==(r=s.arrow)?void 0:r.centerOffset)!==0,f=c?0:e.arrowWidth,d=c?0:e.arrowHeight,[p,h]=td(l),g={start:"0%",center:"50%",end:"100%"}[h],v=(null!=(o=null==(n=s.arrow)?void 0:n.x)?o:0)+f/2,m=(null!=(a=null==(i=s.arrow)?void 0:i.y)?a:0)+d/2,y="",b="";return"bottom"===p?(y=c?g:"".concat(v,"px"),b="".concat(-d,"px")):"top"===p?(y=c?g:"".concat(v,"px"),b="".concat(u.floating.height+d,"px")):"right"===p?(y="".concat(-d,"px"),b=c?g:"".concat(m,"px")):"left"===p&&(y="".concat(u.floating.width+d,"px"),b=c?g:"".concat(m,"px")),{data:{x:y,y:b}}}});function td(e){let[t,r="center"]=e.split("-");return[t,r]}var tp=a.forwardRef((e,t)=>{var r,n;let{container:i,...o}=e,[u,s]=a.useState(!1);(0,e6.N)(()=>s(!0),[]);let c=i||u&&(null==(n=globalThis)||null==(r=n.document)?void 0:r.body);return c?l.createPortal((0,v.jsx)(h.sG.div,{...o,ref:t}),c):null});tp.displayName="Portal";var th=r(9708),tg=r(5845),tv=Object.freeze({position:"absolute",border:0,width:1,height:1,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",wordWrap:"normal"});a.forwardRef((e,t)=>(0,v.jsx)(h.sG.span,{...e,ref:t,style:{...tv,...e.style}})).displayName="VisuallyHidden";var tm=new WeakMap,ty=new WeakMap,tb={},tw=0,tx=function(e){return e&&(e.host||tx(e.parentNode))},tS=function(e,t,r,n){var i=(Array.isArray(e)?e:[e]).map(function(e){if(t.contains(e))return e;var r=tx(e);return r&&t.contains(r)?r:(console.error("aria-hidden",e,"in not contained inside",t,". Doing nothing"),null)}).filter(function(e){return!!e});tb[r]||(tb[r]=new WeakMap);var o=tb[r],a=[],l=new Set,u=new Set(i),s=function(e){!e||l.has(e)||(l.add(e),s(e.parentNode))};i.forEach(s);var c=function(e){!e||u.has(e)||Array.prototype.forEach.call(e.children,function(e){if(l.has(e))c(e);else try{var t=e.getAttribute(n),i=null!==t&&"false"!==t,u=(tm.get(e)||0)+1,s=(o.get(e)||0)+1;tm.set(e,u),o.set(e,s),a.push(e),1===u&&i&&ty.set(e,!0),1===s&&e.setAttribute(r,"true"),i||e.setAttribute(n,"true")}catch(t){console.error("aria-hidden: cannot operate on ",e,t)}})};return c(t),l.clear(),tw++,function(){a.forEach(function(e){var t=tm.get(e)-1,i=o.get(e)-1;tm.set(e,t),o.set(e,i),t||(ty.has(e)||e.removeAttribute(n),ty.delete(e)),i||e.removeAttribute(r)}),--tw||(tm=new WeakMap,tm=new WeakMap,ty=new WeakMap,tb={})}},tO=function(e,t,r){void 0===r&&(r="data-aria-hidden");var n=Array.from(Array.isArray(e)?e:[e]),i=t||("undefined"==typeof document?null:(Array.isArray(e)?e[0]:e).ownerDocument.body);return i?(n.push.apply(n,Array.from(i.querySelectorAll("[aria-live], script"))),tS(n,i,r,"aria-hidden")):function(){return null}},tE=function(){return(tE=Object.assign||function(e){for(var t,r=1,n=arguments.length;rt.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,n=Object.getOwnPropertySymbols(e);it.indexOf(n[i])&&Object.prototype.propertyIsEnumerable.call(e,n[i])&&(r[n[i]]=e[n[i]]);return r}Object.create;Object.create;var tM=("function"==typeof SuppressedError&&SuppressedError,"right-scroll-bar-position"),tk="width-before-scroll-bar";function t_(e,t){return"function"==typeof e?e(t):e&&(e.current=t),e}var tP="undefined"!=typeof window?a.useLayoutEffect:a.useEffect,tA=new WeakMap;function tj(e){return e}var tT=function(e){void 0===e&&(e={});var t,r,n,i=(void 0===t&&(t=tj),r=[],n=!1,{read:function(){if(n)throw Error("Sidecar: could not `read` from an `assigned` medium. `read` could be used only with `useMedium`.");return r.length?r[r.length-1]:null},useMedium:function(e){var i=t(e,n);return r.push(i),function(){r=r.filter(function(e){return e!==i})}},assignSyncMedium:function(e){for(n=!0;r.length;){var t=r;r=[],t.forEach(e)}r={push:function(t){return e(t)},filter:function(){return r}}},assignMedium:function(e){n=!0;var t=[];if(r.length){var i=r;r=[],i.forEach(e),t=r}var o=function(){var r=t;t=[],r.forEach(e)},a=function(){return Promise.resolve().then(o)};a(),r={push:function(e){t.push(e),a()},filter:function(e){return t=t.filter(e),r}}}});return i.options=tE({async:!0,ssr:!1},e),i}(),tR=function(){},tz=a.forwardRef(function(e,t){var r,n,i,o,l=a.useRef(null),u=a.useState({onScrollCapture:tR,onWheelCapture:tR,onTouchMoveCapture:tR}),s=u[0],c=u[1],f=e.forwardProps,d=e.children,p=e.className,h=e.removeScrollBar,g=e.enabled,v=e.shards,m=e.sideCar,y=e.noRelative,b=e.noIsolation,w=e.inert,x=e.allowPinchZoom,S=e.as,O=e.gapMode,E=tC(e,["forwardProps","children","className","removeScrollBar","enabled","shards","sideCar","noRelative","noIsolation","inert","allowPinchZoom","as","gapMode"]),C=(r=[l,t],n=function(e){return r.forEach(function(t){return t_(t,e)})},(i=(0,a.useState)(function(){return{value:null,callback:n,facade:{get current(){return i.value},set current(value){var e=i.value;e!==value&&(i.value=value,i.callback(value,e))}}}})[0]).callback=n,o=i.facade,tP(function(){var e=tA.get(o);if(e){var t=new Set(e),n=new Set(r),i=o.current;t.forEach(function(e){n.has(e)||t_(e,null)}),n.forEach(function(e){t.has(e)||t_(e,i)})}tA.set(o,r)},[r]),o),M=tE(tE({},E),s);return a.createElement(a.Fragment,null,g&&a.createElement(m,{sideCar:tT,removeScrollBar:h,shards:v,noRelative:y,noIsolation:b,inert:w,setCallbacks:c,allowPinchZoom:!!x,lockRef:l,gapMode:O}),f?a.cloneElement(a.Children.only(d),tE(tE({},M),{ref:C})):a.createElement(void 0===S?"div":S,tE({},M,{className:p,ref:C}),d))});tz.defaultProps={enabled:!0,removeScrollBar:!0,inert:!1},tz.classNames={fullWidth:tk,zeroRight:tM};var tD=function(e){var t=e.sideCar,r=tC(e,["sideCar"]);if(!t)throw Error("Sidecar: please provide `sideCar` property to import the right car");var n=t.read();if(!n)throw Error("Sidecar medium not found");return a.createElement(n,tE({},r))};tD.isSideCarExport=!0;var tI=function(){var e=0,t=null;return{add:function(n){if(0==e&&(t=function(){if(!document)return null;var e=document.createElement("style");e.type="text/css";var t=o||r.nc;return t&&e.setAttribute("nonce",t),e}())){var i,a;(i=t).styleSheet?i.styleSheet.cssText=n:i.appendChild(document.createTextNode(n)),a=t,(document.head||document.getElementsByTagName("head")[0]).appendChild(a)}e++},remove:function(){--e||!t||(t.parentNode&&t.parentNode.removeChild(t),t=null)}}},tN=function(){var e=tI();return function(t,r){a.useEffect(function(){return e.add(t),function(){e.remove()}},[t&&r])}},tL=function(){var e=tN();return function(t){return e(t.styles,t.dynamic),null}},tF={left:0,top:0,right:0,gap:0},t$=function(e){return parseInt(e||"",10)||0},tB=function(e){var t=window.getComputedStyle(document.body),r=t["padding"===e?"paddingLeft":"marginLeft"],n=t["padding"===e?"paddingTop":"marginTop"],i=t["padding"===e?"paddingRight":"marginRight"];return[t$(r),t$(n),t$(i)]},tV=function(e){if(void 0===e&&(e="margin"),"undefined"==typeof window)return tF;var t=tB(e),r=document.documentElement.clientWidth,n=window.innerWidth;return{left:t[0],top:t[1],right:t[2],gap:Math.max(0,n-r+t[2]-t[0])}},tU=tL(),tH="data-scroll-locked",tG=function(e,t,r,n){var i=e.left,o=e.top,a=e.right,l=e.gap;return void 0===r&&(r="margin"),"\n .".concat("with-scroll-bars-hidden"," {\n overflow: hidden ").concat(n,";\n padding-right: ").concat(l,"px ").concat(n,";\n }\n body[").concat(tH,"] {\n overflow: hidden ").concat(n,";\n overscroll-behavior: contain;\n ").concat([t&&"position: relative ".concat(n,";"),"margin"===r&&"\n padding-left: ".concat(i,"px;\n padding-top: ").concat(o,"px;\n padding-right: ").concat(a,"px;\n margin-left:0;\n margin-top:0;\n margin-right: ").concat(l,"px ").concat(n,";\n "),"padding"===r&&"padding-right: ".concat(l,"px ").concat(n,";")].filter(Boolean).join(""),"\n }\n \n .").concat(tM," {\n right: ").concat(l,"px ").concat(n,";\n }\n \n .").concat(tk," {\n margin-right: ").concat(l,"px ").concat(n,";\n }\n \n .").concat(tM," .").concat(tM," {\n right: 0 ").concat(n,";\n }\n \n .").concat(tk," .").concat(tk," {\n margin-right: 0 ").concat(n,";\n }\n \n body[").concat(tH,"] {\n ").concat("--removed-body-scroll-bar-size",": ").concat(l,"px;\n }\n")},tZ=function(){var e=parseInt(document.body.getAttribute(tH)||"0",10);return isFinite(e)?e:0},tW=function(){a.useEffect(function(){return document.body.setAttribute(tH,(tZ()+1).toString()),function(){var e=tZ()-1;e<=0?document.body.removeAttribute(tH):document.body.setAttribute(tH,e.toString())}},[])},tK=function(e){var t=e.noRelative,r=e.noImportant,n=e.gapMode,i=void 0===n?"margin":n;tW();var o=a.useMemo(function(){return tV(i)},[i]);return a.createElement(tU,{styles:tG(o,!t,i,r?"":"!important")})},tq=!1;if("undefined"!=typeof window)try{var tY=Object.defineProperty({},"passive",{get:function(){return tq=!0,!0}});window.addEventListener("test",tY,tY),window.removeEventListener("test",tY,tY)}catch(e){tq=!1}var tX=!!tq&&{passive:!1},tJ=function(e,t){if(!(e instanceof Element))return!1;var r=window.getComputedStyle(e);return"hidden"!==r[t]&&(r.overflowY!==r.overflowX||"TEXTAREA"===e.tagName||"visible"!==r[t])},tQ=function(e,t){var r=t.ownerDocument,n=t;do{if("undefined"!=typeof ShadowRoot&&n instanceof ShadowRoot&&(n=n.host),t0(e,n)){var i=t1(e,n);if(i[1]>i[2])return!0}n=n.parentNode}while(n&&n!==r.body);return!1},t0=function(e,t){return"v"===e?tJ(t,"overflowY"):tJ(t,"overflowX")},t1=function(e,t){return"v"===e?[t.scrollTop,t.scrollHeight,t.clientHeight]:[t.scrollLeft,t.scrollWidth,t.clientWidth]},t2=function(e,t,r,n,i){var o,a=(o=window.getComputedStyle(t).direction,"h"===e&&"rtl"===o?-1:1),l=a*n,u=r.target,s=t.contains(u),c=!1,f=l>0,d=0,p=0;do{if(!u)break;var h=t1(e,u),g=h[0],v=h[1]-h[2]-a*g;(g||v)&&t0(e,u)&&(d+=v,p+=g);var m=u.parentNode;u=m&&m.nodeType===Node.DOCUMENT_FRAGMENT_NODE?m.host:m}while(!s&&u!==document.body||s&&(t.contains(u)||t===u));return f&&(i&&1>Math.abs(d)||!i&&l>d)?c=!0:!f&&(i&&1>Math.abs(p)||!i&&-l>p)&&(c=!0),c},t5=function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},t6=function(e){return[e.deltaX,e.deltaY]},t4=function(e){return e&&"current"in e?e.current:e},t3=0,t8=[];let t9=(n=function(e){var t=a.useRef([]),r=a.useRef([0,0]),n=a.useRef(),i=a.useState(t3++)[0],o=a.useState(tL)[0],l=a.useRef(e);a.useEffect(function(){l.current=e},[e]),a.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(i));var t=(function(e,t,r){if(r||2==arguments.length)for(var n,i=0,o=t.length;iMath.abs(s)?"h":"v";if("touches"in e&&"h"===f&&"range"===c.type)return!1;var d=tQ(f,c);if(!d)return!0;if(d?i=f:(i="v"===f?"h":"v",d=tQ(f,c)),!d)return!1;if(!n.current&&"changedTouches"in e&&(u||s)&&(n.current=i),!i)return!0;var p=n.current||i;return t2(p,t,e,"h"===p?u:s,!0)},[]),s=a.useCallback(function(e){if(t8.length&&t8[t8.length-1]===o){var r="deltaY"in e?t6(e):t5(e),n=t.current.filter(function(t){var n;return t.name===e.type&&(t.target===e.target||e.target===t.shadowParent)&&(n=t.delta,n[0]===r[0]&&n[1]===r[1])})[0];if(n&&n.should){e.cancelable&&e.preventDefault();return}if(!n){var i=(l.current.shards||[]).map(t4).filter(Boolean).filter(function(t){return t.contains(e.target)});(i.length>0?u(e,i[0]):!l.current.noIsolation)&&e.cancelable&&e.preventDefault()}}},[]),c=a.useCallback(function(e,r,n,i){var o={name:e,delta:r,target:n,should:i,shadowParent:function(e){for(var t=null;null!==e;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}(n)};t.current.push(o),setTimeout(function(){t.current=t.current.filter(function(e){return e!==o})},1)},[]),f=a.useCallback(function(e){r.current=t5(e),n.current=void 0},[]),d=a.useCallback(function(t){c(t.type,t6(t),t.target,u(t,e.lockRef.current))},[]),p=a.useCallback(function(t){c(t.type,t5(t),t.target,u(t,e.lockRef.current))},[]);a.useEffect(function(){return t8.push(o),e.setCallbacks({onScrollCapture:d,onWheelCapture:d,onTouchMoveCapture:p}),document.addEventListener("wheel",s,tX),document.addEventListener("touchmove",s,tX),document.addEventListener("touchstart",f,tX),function(){t8=t8.filter(function(e){return e!==o}),document.removeEventListener("wheel",s,tX),document.removeEventListener("touchmove",s,tX),document.removeEventListener("touchstart",f,tX)}},[]);var h=e.removeScrollBar,g=e.inert;return a.createElement(a.Fragment,null,g?a.createElement(o,{styles:"\n .block-interactivity-".concat(i," {pointer-events: none;}\n .allow-interactivity-").concat(i," {pointer-events: all;}\n")}):null,h?a.createElement(tK,{noRelative:e.noRelative,gapMode:e.gapMode}):null)},tT.useMedium(n),tD);var t7=a.forwardRef(function(e,t){return a.createElement(tz,tE({},e,{ref:t,sideCar:t9}))});t7.classNames=tz.classNames;var re=[" ","Enter","ArrowUp","ArrowDown"],rt=[" ","Enter"],rr="Select",[rn,ri,ro]=(0,c.N)(rr),[ra,rl]=(0,d.A)(rr,[ro,e8]),ru=e8(),[rs,rc]=ra(rr),[rf,rd]=ra(rr),rp=e=>{let{__scopeSelect:t,children:r,open:n,defaultOpen:i,onOpenChange:o,value:l,defaultValue:u,onValueChange:s,dir:c,name:f,autoComplete:d,disabled:h,required:g,form:m}=e,y=ru(t),[b,w]=a.useState(null),[x,S]=a.useState(null),[O,E]=a.useState(!1),C=(0,p.jH)(c),[M,k]=(0,tg.i)({prop:n,defaultProp:null!=i&&i,onChange:o,caller:rr}),[_,P]=(0,tg.i)({prop:l,defaultProp:u,onChange:s,caller:rr}),A=a.useRef(null),j=!b||m||!!b.closest("form"),[T,z]=a.useState(new Set),D=Array.from(T).map(e=>e.props.value).join(";");return(0,v.jsx)(te,{...y,children:(0,v.jsxs)(rs,{required:g,scope:t,trigger:b,onTriggerChange:w,valueNode:x,onValueNodeChange:S,valueNodeHasChildren:O,onValueNodeHasChildrenChange:E,contentId:(0,R.B)(),value:_,onValueChange:P,open:M,onOpenChange:k,dir:C,triggerPointerDownPosRef:A,disabled:h,children:[(0,v.jsx)(rn.Provider,{scope:t,children:(0,v.jsx)(rf,{scope:e.__scopeSelect,onNativeOptionAdd:a.useCallback(e=>{z(t=>new Set(t).add(e))},[]),onNativeOptionRemove:a.useCallback(e=>{z(t=>{let r=new Set(t);return r.delete(e),r})},[]),children:r})}),j?(0,v.jsxs)(rY,{"aria-hidden":!0,required:g,tabIndex:-1,name:f,autoComplete:d,value:_,onChange:e=>P(e.target.value),disabled:h,form:m,children:[void 0===_?(0,v.jsx)("option",{value:""}):null,Array.from(T)]},D):null]})})};rp.displayName=rr;var rh="SelectTrigger",rg=a.forwardRef((e,t)=>{let{__scopeSelect:r,disabled:n=!1,...i}=e,o=ru(r),l=rc(rh,r),u=l.disabled||n,c=(0,f.s)(t,l.onTriggerChange),d=ri(r),p=a.useRef("touch"),[g,m,y]=rJ(e=>{let t=d().filter(e=>!e.disabled),r=t.find(e=>e.value===l.value),n=rQ(t,e,r);void 0!==n&&l.onValueChange(n.value)}),b=e=>{u||(l.onOpenChange(!0),y()),e&&(l.triggerPointerDownPosRef.current={x:Math.round(e.pageX),y:Math.round(e.pageY)})};return(0,v.jsx)(tr,{asChild:!0,...o,children:(0,v.jsx)(h.sG.button,{type:"button",role:"combobox","aria-controls":l.contentId,"aria-expanded":l.open,"aria-required":l.required,"aria-autocomplete":"none",dir:l.dir,"data-state":l.open?"open":"closed",disabled:u,"data-disabled":u?"":void 0,"data-placeholder":rX(l.value)?"":void 0,...i,ref:c,onClick:(0,s.m)(i.onClick,e=>{e.currentTarget.focus(),"mouse"!==p.current&&b(e)}),onPointerDown:(0,s.m)(i.onPointerDown,e=>{p.current=e.pointerType;let t=e.target;t.hasPointerCapture(e.pointerId)&&t.releasePointerCapture(e.pointerId),0===e.button&&!1===e.ctrlKey&&"mouse"===e.pointerType&&(b(e),e.preventDefault())}),onKeyDown:(0,s.m)(i.onKeyDown,e=>{let t=""!==g.current;e.ctrlKey||e.altKey||e.metaKey||1!==e.key.length||m(e.key),(!t||" "!==e.key)&&re.includes(e.key)&&(b(),e.preventDefault())})})})});rg.displayName=rh;var rv="SelectValue",rm=a.forwardRef((e,t)=>{let{__scopeSelect:r,className:n,style:i,children:o,placeholder:a="",...l}=e,u=rc(rv,r),{onValueNodeHasChildrenChange:s}=u,c=void 0!==o,d=(0,f.s)(t,u.onValueNodeChange);return(0,e6.N)(()=>{s(c)},[s,c]),(0,v.jsx)(h.sG.span,{...l,ref:d,style:{pointerEvents:"none"},children:rX(u.value)?(0,v.jsx)(v.Fragment,{children:a}):o})});rm.displayName=rv;var ry=a.forwardRef((e,t)=>{let{__scopeSelect:r,children:n,...i}=e;return(0,v.jsx)(h.sG.span,{"aria-hidden":!0,...i,ref:t,children:n||"▼"})});ry.displayName="SelectIcon";var rb=e=>(0,v.jsx)(tp,{asChild:!0,...e});rb.displayName="SelectPortal";var rw="SelectContent",rx=a.forwardRef((e,t)=>{let r=rc(rw,e.__scopeSelect),[n,i]=a.useState();return((0,e6.N)(()=>{i(new DocumentFragment)},[]),r.open)?(0,v.jsx)(rC,{...e,ref:t}):n?l.createPortal((0,v.jsx)(rS,{scope:e.__scopeSelect,children:(0,v.jsx)(rn.Slot,{scope:e.__scopeSelect,children:(0,v.jsx)("div",{children:e.children})})}),n):null});rx.displayName=rw;var[rS,rO]=ra(rw),rE=(0,th.TL)("SelectContent.RemoveScroll"),rC=a.forwardRef((e,t)=>{let{__scopeSelect:r,position:n="item-aligned",onCloseAutoFocus:i,onEscapeKeyDown:o,onPointerDownOutside:l,side:u,sideOffset:c,align:d,alignOffset:p,arrowPadding:h,collisionBoundary:g,collisionPadding:m,sticky:y,hideWhenDetached:w,avoidCollisions:x,...E}=e,C=rc(rw,r),[M,_]=a.useState(null),[P,A]=a.useState(null),j=(0,f.s)(t,e=>_(e)),[T,R]=a.useState(null),[z,D]=a.useState(null),I=ri(r),[N,L]=a.useState(!1),F=a.useRef(!1);a.useEffect(()=>{if(M)return tO(M)},[M]),a.useEffect(()=>{var e,t;let r=document.querySelectorAll("[data-radix-focus-guard]");return document.body.insertAdjacentElement("afterbegin",null!=(e=r[0])?e:O()),document.body.insertAdjacentElement("beforeend",null!=(t=r[1])?t:O()),S++,()=>{1===S&&document.querySelectorAll("[data-radix-focus-guard]").forEach(e=>e.remove()),S--}},[]);let $=a.useCallback(e=>{let[t,...r]=I().map(e=>e.ref.current),[n]=r.slice(-1),i=document.activeElement;for(let r of e)if(r===i||(null==r||r.scrollIntoView({block:"nearest"}),r===t&&P&&(P.scrollTop=0),r===n&&P&&(P.scrollTop=P.scrollHeight),null==r||r.focus(),document.activeElement!==i))return},[I,P]),B=a.useCallback(()=>$([T,M]),[$,T,M]);a.useEffect(()=>{N&&B()},[N,B]);let{onOpenChange:V,triggerPointerDownPosRef:U}=C;a.useEffect(()=>{if(M){let e={x:0,y:0},t=t=>{var r,n,i,o;e={x:Math.abs(Math.round(t.pageX)-(null!=(i=null==(r=U.current)?void 0:r.x)?i:0)),y:Math.abs(Math.round(t.pageY)-(null!=(o=null==(n=U.current)?void 0:n.y)?o:0))}},r=r=>{e.x<=10&&e.y<=10?r.preventDefault():M.contains(r.target)||V(!1),document.removeEventListener("pointermove",t),U.current=null};return null!==U.current&&(document.addEventListener("pointermove",t),document.addEventListener("pointerup",r,{capture:!0,once:!0})),()=>{document.removeEventListener("pointermove",t),document.removeEventListener("pointerup",r,{capture:!0})}}},[M,V,U]),a.useEffect(()=>{let e=()=>V(!1);return window.addEventListener("blur",e),window.addEventListener("resize",e),()=>{window.removeEventListener("blur",e),window.removeEventListener("resize",e)}},[V]);let[H,G]=rJ(e=>{let t=I().filter(e=>!e.disabled),r=t.find(e=>e.ref.current===document.activeElement),n=rQ(t,e,r);n&&setTimeout(()=>n.ref.current.focus())}),Z=a.useCallback((e,t,r)=>{let n=!F.current&&!r;(void 0!==C.value&&C.value===t||n)&&(R(e),n&&(F.current=!0))},[C.value]),W=a.useCallback(()=>null==M?void 0:M.focus(),[M]),K=a.useCallback((e,t,r)=>{let n=!F.current&&!r;(void 0!==C.value&&C.value===t||n)&&D(e)},[C.value]),q="popper"===n?rk:rM,Y=q===rk?{side:u,sideOffset:c,align:d,alignOffset:p,arrowPadding:h,collisionBoundary:g,collisionPadding:m,sticky:y,hideWhenDetached:w,avoidCollisions:x}:{};return(0,v.jsx)(rS,{scope:r,content:M,viewport:P,onViewportChange:A,itemRefCallback:Z,selectedItem:T,onItemLeave:W,itemTextRefCallback:K,focusSelectedItem:B,selectedItemText:z,position:n,isPositioned:N,searchRef:H,children:(0,v.jsx)(t7,{as:rE,allowPinchZoom:!0,children:(0,v.jsx)(k,{asChild:!0,trapped:C.open,onMountAutoFocus:e=>{e.preventDefault()},onUnmountAutoFocus:(0,s.m)(i,e=>{var t;null==(t=C.trigger)||t.focus({preventScroll:!0}),e.preventDefault()}),children:(0,v.jsx)(b,{asChild:!0,disableOutsidePointerEvents:!0,onEscapeKeyDown:o,onPointerDownOutside:l,onFocusOutside:e=>e.preventDefault(),onDismiss:()=>C.onOpenChange(!1),children:(0,v.jsx)(q,{role:"listbox",id:C.contentId,"data-state":C.open?"open":"closed",dir:C.dir,onContextMenu:e=>e.preventDefault(),...E,...Y,onPlaced:()=>L(!0),ref:j,style:{display:"flex",flexDirection:"column",outline:"none",...E.style},onKeyDown:(0,s.m)(E.onKeyDown,e=>{let t=e.ctrlKey||e.altKey||e.metaKey;if("Tab"===e.key&&e.preventDefault(),t||1!==e.key.length||G(e.key),["ArrowUp","ArrowDown","Home","End"].includes(e.key)){let t=I().filter(e=>!e.disabled).map(e=>e.ref.current);if(["ArrowUp","End"].includes(e.key)&&(t=t.slice().reverse()),["ArrowUp","ArrowDown"].includes(e.key)){let r=e.target,n=t.indexOf(r);t=t.slice(n+1)}setTimeout(()=>$(t)),e.preventDefault()}})})})})})})});rC.displayName="SelectContentImpl";var rM=a.forwardRef((e,t)=>{let{__scopeSelect:r,onPlaced:n,...i}=e,o=rc(rw,r),l=rO(rw,r),[s,c]=a.useState(null),[d,p]=a.useState(null),g=(0,f.s)(t,e=>p(e)),m=ri(r),y=a.useRef(!1),b=a.useRef(!0),{viewport:w,selectedItem:x,selectedItemText:S,focusSelectedItem:O}=l,E=a.useCallback(()=>{if(o.trigger&&o.valueNode&&s&&d&&w&&x&&S){let e=o.trigger.getBoundingClientRect(),t=d.getBoundingClientRect(),r=o.valueNode.getBoundingClientRect(),i=S.getBoundingClientRect();if("rtl"!==o.dir){let n=i.left-t.left,o=r.left-n,a=e.left-o,l=e.width+a,c=Math.max(l,t.width),f=u(o,[10,Math.max(10,window.innerWidth-10-c)]);s.style.minWidth=l+"px",s.style.left=f+"px"}else{let n=t.right-i.right,o=window.innerWidth-r.right-n,a=window.innerWidth-e.right-o,l=e.width+a,c=Math.max(l,t.width),f=u(o,[10,Math.max(10,window.innerWidth-10-c)]);s.style.minWidth=l+"px",s.style.right=f+"px"}let a=m(),l=window.innerHeight-20,c=w.scrollHeight,f=window.getComputedStyle(d),p=parseInt(f.borderTopWidth,10),h=parseInt(f.paddingTop,10),g=parseInt(f.borderBottomWidth,10),v=p+h+c+parseInt(f.paddingBottom,10)+g,b=Math.min(5*x.offsetHeight,v),O=window.getComputedStyle(w),E=parseInt(O.paddingTop,10),C=parseInt(O.paddingBottom,10),M=e.top+e.height/2-10,k=x.offsetHeight/2,_=p+h+(x.offsetTop+k);if(_<=M){let e=a.length>0&&x===a[a.length-1].ref.current;s.style.bottom="0px";let t=Math.max(l-M,k+(e?C:0)+(d.clientHeight-w.offsetTop-w.offsetHeight)+g);s.style.height=_+t+"px"}else{let e=a.length>0&&x===a[0].ref.current;s.style.top="0px";let t=Math.max(M,p+w.offsetTop+(e?E:0)+k);s.style.height=t+(v-_)+"px",w.scrollTop=_-M+w.offsetTop}s.style.margin="".concat(10,"px 0"),s.style.minHeight=b+"px",s.style.maxHeight=l+"px",null==n||n(),requestAnimationFrame(()=>y.current=!0)}},[m,o.trigger,o.valueNode,s,d,w,x,S,o.dir,n]);(0,e6.N)(()=>E(),[E]);let[C,M]=a.useState();(0,e6.N)(()=>{d&&M(window.getComputedStyle(d).zIndex)},[d]);let k=a.useCallback(e=>{e&&!0===b.current&&(E(),null==O||O(),b.current=!1)},[E,O]);return(0,v.jsx)(r_,{scope:r,contentWrapper:s,shouldExpandOnScrollRef:y,onScrollButtonChange:k,children:(0,v.jsx)("div",{ref:c,style:{display:"flex",flexDirection:"column",position:"fixed",zIndex:C},children:(0,v.jsx)(h.sG.div,{...i,ref:g,style:{boxSizing:"border-box",maxHeight:"100%",...i.style}})})})});rM.displayName="SelectItemAlignedPosition";var rk=a.forwardRef((e,t)=>{let{__scopeSelect:r,align:n="start",collisionPadding:i=10,...o}=e,a=ru(r);return(0,v.jsx)(ta,{...a,...o,ref:t,align:n,collisionPadding:i,style:{boxSizing:"border-box",...o.style,"--radix-select-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-select-content-available-width":"var(--radix-popper-available-width)","--radix-select-content-available-height":"var(--radix-popper-available-height)","--radix-select-trigger-width":"var(--radix-popper-anchor-width)","--radix-select-trigger-height":"var(--radix-popper-anchor-height)"}})});rk.displayName="SelectPopperPosition";var[r_,rP]=ra(rw,{}),rA="SelectViewport",rj=a.forwardRef((e,t)=>{let{__scopeSelect:r,nonce:n,...i}=e,o=rO(rA,r),l=rP(rA,r),u=(0,f.s)(t,o.onViewportChange),c=a.useRef(0);return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)("style",{dangerouslySetInnerHTML:{__html:"[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}"},nonce:n}),(0,v.jsx)(rn.Slot,{scope:r,children:(0,v.jsx)(h.sG.div,{"data-radix-select-viewport":"",role:"presentation",...i,ref:u,style:{position:"relative",flex:1,overflow:"hidden auto",...i.style},onScroll:(0,s.m)(i.onScroll,e=>{let t=e.currentTarget,{contentWrapper:r,shouldExpandOnScrollRef:n}=l;if((null==n?void 0:n.current)&&r){let e=Math.abs(c.current-t.scrollTop);if(e>0){let n=window.innerHeight-20,i=Math.max(parseFloat(r.style.minHeight),parseFloat(r.style.height));if(i0?l:0,r.style.justifyContent="flex-end")}}}c.current=t.scrollTop})})})]})});rj.displayName=rA;var rT="SelectGroup",[rR,rz]=ra(rT);a.forwardRef((e,t)=>{let{__scopeSelect:r,...n}=e,i=(0,R.B)();return(0,v.jsx)(rR,{scope:r,id:i,children:(0,v.jsx)(h.sG.div,{role:"group","aria-labelledby":i,...n,ref:t})})}).displayName=rT;var rD="SelectLabel";a.forwardRef((e,t)=>{let{__scopeSelect:r,...n}=e,i=rz(rD,r);return(0,v.jsx)(h.sG.div,{id:i.id,...n,ref:t})}).displayName=rD;var rI="SelectItem",[rN,rL]=ra(rI),rF=a.forwardRef((e,t)=>{let{__scopeSelect:r,value:n,disabled:i=!1,textValue:o,...l}=e,u=rc(rI,r),c=rO(rI,r),d=u.value===n,[p,g]=a.useState(null!=o?o:""),[m,y]=a.useState(!1),b=(0,f.s)(t,e=>{var t;return null==(t=c.itemRefCallback)?void 0:t.call(c,e,n,i)}),w=(0,R.B)(),x=a.useRef("touch"),S=()=>{i||(u.onValueChange(n),u.onOpenChange(!1))};if(""===n)throw Error("A must have a value prop that is not an empty string. This is because the Select value can be set to an empty string to clear the selection and show the placeholder.");return(0,v.jsx)(rN,{scope:r,value:n,disabled:i,textId:w,isSelected:d,onItemTextChange:a.useCallback(e=>{g(t=>{var r;return t||(null!=(r=null==e?void 0:e.textContent)?r:"").trim()})},[]),children:(0,v.jsx)(rn.ItemSlot,{scope:r,value:n,disabled:i,textValue:p,children:(0,v.jsx)(h.sG.div,{role:"option","aria-labelledby":w,"data-highlighted":m?"":void 0,"aria-selected":d&&m,"data-state":d?"checked":"unchecked","aria-disabled":i||void 0,"data-disabled":i?"":void 0,tabIndex:i?void 0:-1,...l,ref:b,onFocus:(0,s.m)(l.onFocus,()=>y(!0)),onBlur:(0,s.m)(l.onBlur,()=>y(!1)),onClick:(0,s.m)(l.onClick,()=>{"mouse"!==x.current&&S()}),onPointerUp:(0,s.m)(l.onPointerUp,()=>{"mouse"===x.current&&S()}),onPointerDown:(0,s.m)(l.onPointerDown,e=>{x.current=e.pointerType}),onPointerMove:(0,s.m)(l.onPointerMove,e=>{if(x.current=e.pointerType,i){var t;null==(t=c.onItemLeave)||t.call(c)}else"mouse"===x.current&&e.currentTarget.focus({preventScroll:!0})}),onPointerLeave:(0,s.m)(l.onPointerLeave,e=>{if(e.currentTarget===document.activeElement){var t;null==(t=c.onItemLeave)||t.call(c)}}),onKeyDown:(0,s.m)(l.onKeyDown,e=>{var t;((null==(t=c.searchRef)?void 0:t.current)===""||" "!==e.key)&&(rt.includes(e.key)&&S()," "===e.key&&e.preventDefault())})})})})});rF.displayName=rI;var r$="SelectItemText",rB=a.forwardRef((e,t)=>{let{__scopeSelect:r,className:n,style:i,...o}=e,u=rc(r$,r),s=rO(r$,r),c=rL(r$,r),d=rd(r$,r),[p,g]=a.useState(null),m=(0,f.s)(t,e=>g(e),c.onItemTextChange,e=>{var t;return null==(t=s.itemTextRefCallback)?void 0:t.call(s,e,c.value,c.disabled)}),y=null==p?void 0:p.textContent,b=a.useMemo(()=>(0,v.jsx)("option",{value:c.value,disabled:c.disabled,children:y},c.value),[c.disabled,c.value,y]),{onNativeOptionAdd:w,onNativeOptionRemove:x}=d;return(0,e6.N)(()=>(w(b),()=>x(b)),[w,x,b]),(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(h.sG.span,{id:c.textId,...o,ref:m}),c.isSelected&&u.valueNode&&!u.valueNodeHasChildren?l.createPortal(o.children,u.valueNode):null]})});rB.displayName=r$;var rV="SelectItemIndicator",rU=a.forwardRef((e,t)=>{let{__scopeSelect:r,...n}=e;return rL(rV,r).isSelected?(0,v.jsx)(h.sG.span,{"aria-hidden":!0,...n,ref:t}):null});rU.displayName=rV;var rH="SelectScrollUpButton",rG=a.forwardRef((e,t)=>{let r=rO(rH,e.__scopeSelect),n=rP(rH,e.__scopeSelect),[i,o]=a.useState(!1),l=(0,f.s)(t,n.onScrollButtonChange);return(0,e6.N)(()=>{if(r.viewport&&r.isPositioned){let e=function(){o(t.scrollTop>0)},t=r.viewport;return e(),t.addEventListener("scroll",e),()=>t.removeEventListener("scroll",e)}},[r.viewport,r.isPositioned]),i?(0,v.jsx)(rK,{...e,ref:l,onAutoScroll:()=>{let{viewport:e,selectedItem:t}=r;e&&t&&(e.scrollTop=e.scrollTop-t.offsetHeight)}}):null});rG.displayName=rH;var rZ="SelectScrollDownButton",rW=a.forwardRef((e,t)=>{let r=rO(rZ,e.__scopeSelect),n=rP(rZ,e.__scopeSelect),[i,o]=a.useState(!1),l=(0,f.s)(t,n.onScrollButtonChange);return(0,e6.N)(()=>{if(r.viewport&&r.isPositioned){let e=function(){let e=t.scrollHeight-t.clientHeight;o(Math.ceil(t.scrollTop)t.removeEventListener("scroll",e)}},[r.viewport,r.isPositioned]),i?(0,v.jsx)(rK,{...e,ref:l,onAutoScroll:()=>{let{viewport:e,selectedItem:t}=r;e&&t&&(e.scrollTop=e.scrollTop+t.offsetHeight)}}):null});rW.displayName=rZ;var rK=a.forwardRef((e,t)=>{let{__scopeSelect:r,onAutoScroll:n,...i}=e,o=rO("SelectScrollButton",r),l=a.useRef(null),u=ri(r),c=a.useCallback(()=>{null!==l.current&&(window.clearInterval(l.current),l.current=null)},[]);return a.useEffect(()=>()=>c(),[c]),(0,e6.N)(()=>{var e;let t=u().find(e=>e.ref.current===document.activeElement);null==t||null==(e=t.ref.current)||e.scrollIntoView({block:"nearest"})},[u]),(0,v.jsx)(h.sG.div,{"aria-hidden":!0,...i,ref:t,style:{flexShrink:0,...i.style},onPointerDown:(0,s.m)(i.onPointerDown,()=>{null===l.current&&(l.current=window.setInterval(n,50))}),onPointerMove:(0,s.m)(i.onPointerMove,()=>{var e;null==(e=o.onItemLeave)||e.call(o),null===l.current&&(l.current=window.setInterval(n,50))}),onPointerLeave:(0,s.m)(i.onPointerLeave,()=>{c()})})});a.forwardRef((e,t)=>{let{__scopeSelect:r,...n}=e;return(0,v.jsx)(h.sG.div,{"aria-hidden":!0,...n,ref:t})}).displayName="SelectSeparator";var rq="SelectArrow";a.forwardRef((e,t)=>{let{__scopeSelect:r,...n}=e,i=ru(r),o=rc(rq,r),a=rO(rq,r);return o.open&&"popper"===a.position?(0,v.jsx)(ts,{...i,...n,ref:t}):null}).displayName=rq;var rY=a.forwardRef((e,t)=>{let{__scopeSelect:r,value:n,...i}=e,o=a.useRef(null),l=(0,f.s)(t,o),u=function(e){let t=a.useRef({value:e,previous:e});return a.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}(n);return a.useEffect(()=>{let e=o.current;if(!e)return;let t=Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype,"value").set;if(u!==n&&t){let r=new Event("change",{bubbles:!0});t.call(e,n),e.dispatchEvent(r)}},[u,n]),(0,v.jsx)(h.sG.select,{...i,style:{...tv,...i.style},ref:l,defaultValue:n})});function rX(e){return""===e||void 0===e}function rJ(e){let t=(0,g.c)(e),r=a.useRef(""),n=a.useRef(0),i=a.useCallback(e=>{let i=r.current+e;t(i),function e(t){r.current=t,window.clearTimeout(n.current),""!==t&&(n.current=window.setTimeout(()=>e(""),1e3))}(i)},[t]),o=a.useCallback(()=>{r.current="",window.clearTimeout(n.current)},[]);return a.useEffect(()=>()=>window.clearTimeout(n.current),[]),[r,i,o]}function rQ(e,t,r){var n,i;let o=t.length>1&&Array.from(t).every(e=>e===t[0])?t[0]:t,a=r?e.indexOf(r):-1,l=(n=e,i=Math.max(a,0),n.map((e,t)=>n[(i+t)%n.length]));1===o.length&&(l=l.filter(e=>e!==r));let u=l.find(e=>e.textValue.toLowerCase().startsWith(o.toLowerCase()));return u!==r?u:void 0}rY.displayName="SelectBubbleInput";var r0=rp,r1=rg,r2=rm,r5=ry,r6=rb,r4=rx,r3=rj,r8=rF,r9=rB,r7=rU,ne=rG,nt=rW},1928:(e,t,r)=>{"use strict";r.d(t,{i:()=>a});var n=r(4890);function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function o(e){for(var t=1;t{if(null==t)return n.k_;var a=function(e,t,r){return"axis"===t?"click"===r?e.axisInteraction.click:e.axisInteraction.hover:"click"===r?e.itemInteraction.click:e.itemInteraction.hover}(e,t,r);if(null==a)return n.k_;if(a.active)return a;if(e.keyboardInteraction.active)return e.keyboardInteraction;if(e.syncInteraction.active&&null!=e.syncInteraction.index)return e.syncInteraction;var l=!0===e.settings.active;if(null!=a.index){if(l)return o(o({},a),{},{active:!0})}else if(null!=i)return{active:!0,coordinate:void 0,dataKey:void 0,index:i};return o(o({},n.k_),{},{coordinate:a.coordinate})}},1971:(e,t,r)=>{"use strict";r.d(t,{G:()=>f,j:()=>l});var n=r(5643),i=r(2115),o=r(5064),a=e=>e,l=()=>{var e=(0,i.useContext)(o.E);return e?e.store.dispatch:a},u=()=>{},s=()=>u,c=(e,t)=>e===t;function f(e){var t=(0,i.useContext)(o.E);return(0,n.useSyncExternalStoreWithSelector)(t?t.subscription.addNestedSub:s,t?t.store.getState:u,t?t.store.getState:u,t?e:u,c)}},1992:(e,t,r)=>{"use strict";r(4993)},2085:(e,t,r)=>{"use strict";r.d(t,{F:()=>a});var n=r(2596);let i=e=>"boolean"==typeof e?`${e}`:0===e?"0":e,o=n.$,a=(e,t)=>r=>{var n;if((null==t?void 0:t.variants)==null)return o(e,null==r?void 0:r.class,null==r?void 0:r.className);let{variants:a,defaultVariants:l}=t,u=Object.keys(a).map(e=>{let t=null==r?void 0:r[e],n=null==l?void 0:l[e];if(null===t)return null;let o=i(t)||i(n);return a[e][o]}),s=r&&Object.entries(r).reduce((e,t)=>{let[r,n]=t;return void 0===n||(e[r]=n),e},{});return o(e,u,null==t||null==(n=t.compoundVariants)?void 0:n.reduce((e,t)=>{let{class:r,className:n,...i}=t;return Object.entries(i).every(e=>{let[t,r]=e;return Array.isArray(r)?r.includes({...l,...s}[t]):({...l,...s})[t]===r})?[...e,r,n]:e},[]),null==r?void 0:r.class,null==r?void 0:r.className)}},2183:(e,t,r)=>{"use strict";r.d(t,{kz:()=>ir,fb:()=>n6,q:()=>iy,tP:()=>ik,g1:()=>iz,iv:()=>iQ,Nk:()=>n2,pM:()=>ie,Oz:()=>iv,tF:()=>iX,rj:()=>n0,ec:()=>nY,bb:()=>iw,xp:()=>iT,wL:()=>iE,sr:()=>iP,Qn:()=>ij,MK:()=>n9,IO:()=>nJ,P9:()=>is,S5:()=>ia,PU:()=>nL,cd:()=>n$,eo:()=>nW,yi:()=>il,ZB:()=>i1,D5:()=>iV,Hd:()=>nG,Gx:()=>i5,_y:()=>i4,um:()=>nZ,gT:()=>id,Kr:()=>iu,$X:()=>ih,TC:()=>n7,CR:()=>i2,ld:()=>nK,Rl:()=>nF,sf:()=>nB});var n,i,o,a,l,u,s,c={};r.r(c),r.d(c,{scaleBand:()=>x,scaleDiverging:()=>function e(){var t=eG(r3()(ek));return t.copy=function(){return r5(t,e())},g.apply(t,arguments)},scaleDivergingLog:()=>function e(){var t=eQ(r3()).domain([.1,1,10]);return t.copy=function(){return r5(t,e()).base(t.base())},g.apply(t,arguments)},scaleDivergingPow:()=>r8,scaleDivergingSqrt:()=>r9,scaleDivergingSymlog:()=>function e(){var t=e2(r3());return t.copy=function(){return r5(t,e()).constant(t.constant())},g.apply(t,arguments)},scaleIdentity:()=>function e(t){var r;function n(e){return null==e||isNaN(e*=1)?r:e}return n.invert=n,n.domain=n.range=function(e){return arguments.length?(t=Array.from(e,eC),n):t.slice()},n.unknown=function(e){return arguments.length?(r=e,n):r},n.copy=function(){return e(t).unknown(r)},t=arguments.length?Array.from(t,eC):[0,1],eG(n)},scaleImplicit:()=>b,scaleLinear:()=>function e(){var t=eR();return t.copy=function(){return ej(t,e())},h.apply(t,arguments),eG(t)},scaleLog:()=>function e(){let t=eQ(eT()).domain([1,10]);return t.copy=()=>ej(t,e()).base(t.base()),h.apply(t,arguments),t},scaleOrdinal:()=>w,scalePoint:()=>S,scalePow:()=>e8,scaleQuantile:()=>function e(){var t,r=[],n=[],i=[];function o(){var e=0,t=Math.max(1,n.length);for(i=Array(t-1);++e=1)return+r(e[n-1],n-1,e);var n,i=(n-1)*t,o=Math.floor(i),a=+r(e[o],o,e);return a+(r(e[o+1],o+1,e)-a)*(i-o)}}(r,e/t);return a}function a(e){return null==e||isNaN(e*=1)?t:n[I(i,e)]}return a.invertExtent=function(e){var t=n.indexOf(e);return t<0?[NaN,NaN]:[t>0?i[t-1]:r[0],tfunction e(){var t,r=0,n=1,i=1,o=[.5],a=[0,1];function l(e){return null!=e&&e<=e?a[I(o,e,0,i)]:t}function u(){var e=-1;for(o=Array(i);++e=i?[o[i-1],n]:[o[t-1],o[t]]},l.unknown=function(e){return arguments.length&&(t=e),l},l.thresholds=function(){return o.slice()},l.copy=function(){return e().domain([r,n]).range(a).unknown(t)},h.apply(eG(l),arguments)},scaleRadial:()=>function e(){var t,r=eR(),n=[0,1],i=!1;function o(e){var n,o=Math.sign(n=r(e))*Math.sqrt(Math.abs(n));return isNaN(o)?t:i?Math.round(o):o}return o.invert=function(e){return r.invert(e7(e))},o.domain=function(e){return arguments.length?(r.domain(e),o):r.domain()},o.range=function(e){return arguments.length?(r.range((n=Array.from(e,eC)).map(e7)),o):n.slice()},o.rangeRound=function(e){return o.range(e).round(!0)},o.round=function(e){return arguments.length?(i=!!e,o):i},o.clamp=function(e){return arguments.length?(r.clamp(e),o):r.clamp()},o.unknown=function(e){return arguments.length?(t=e,o):t},o.copy=function(){return e(r.domain(),n).round(i).clamp(r.clamp()).unknown(t)},h.apply(o,arguments),eG(o)},scaleSequential:()=>function e(){var t=eG(r2()(ek));return t.copy=function(){return r5(t,e())},g.apply(t,arguments)},scaleSequentialLog:()=>function e(){var t=eQ(r2()).domain([1,10]);return t.copy=function(){return r5(t,e()).base(t.base())},g.apply(t,arguments)},scaleSequentialPow:()=>r6,scaleSequentialQuantile:()=>function e(){var t=[],r=ek;function n(e){if(null!=e&&!isNaN(e*=1))return r((I(t,e,1)-1)/(t.length-1))}return n.domain=function(e){if(!arguments.length)return t.slice();for(let r of(t=[],e))null==r||isNaN(r*=1)||t.push(r);return t.sort(A),n},n.interpolator=function(e){return arguments.length?(r=e,n):r},n.range=function(){return t.map((e,n)=>r(n/(t.length-1)))},n.quantiles=function(e){return Array.from({length:e+1},(r,n)=>(function(e,t,r){if(!(!(n=(e=Float64Array.from(function*(e,t){if(void 0===t)for(let t of e)null!=t&&(t*=1)>=t&&(yield t);else{let r=-1;for(let n of e)null!=(n=t(n,++r,e))&&(n*=1)>=n&&(yield n)}}(e,void 0))).length)||isNaN(t*=1))){if(t<=0||n<2)return tt(e);if(t>=1)return te(e);var n,i=(n-1)*t,o=Math.floor(i),a=te((function e(t,r,n=0,i=1/0,o){if(r=Math.floor(r),n=Math.floor(Math.max(0,n)),i=Math.floor(Math.min(t.length-1,i)),!(n<=r&&r<=i))return t;for(o=void 0===o?tr:function(e=A){if(e===A)return tr;if("function"!=typeof e)throw TypeError("compare is not a function");return(t,r)=>{let n=e(t,r);return n||0===n?n:(0===e(r,r))-(0===e(t,t))}}(o);i>n;){if(i-n>600){let a=i-n+1,l=r-n+1,u=Math.log(a),s=.5*Math.exp(2*u/3),c=.5*Math.sqrt(u*s*(a-s)/a)*(l-a/2<0?-1:1),f=Math.max(n,Math.floor(r-l*s/a+c)),d=Math.min(i,Math.floor(r+(a-l)*s/a+c));e(t,r,f,d,o)}let a=t[r],l=n,u=i;for(tn(t,n,r),o(t[i],a)>0&&tn(t,n,i);lo(t[l],a);)++l;for(;o(t[u],a)>0;)--u}0===o(t[n],a)?tn(t,n,u):tn(t,++u,i),u<=r&&(n=u+1),r<=u&&(i=u-1)}return t})(e,o).subarray(0,o+1));return a+(tt(e.subarray(o+1))-a)*(i-o)}})(t,n/e))},n.copy=function(){return e(r).domain(t)},g.apply(n,arguments)},scaleSequentialSqrt:()=>r4,scaleSequentialSymlog:()=>function e(){var t=e2(r2());return t.copy=function(){return r5(t,e()).constant(t.constant())},g.apply(t,arguments)},scaleSqrt:()=>e9,scaleSymlog:()=>function e(){var t=e2(eT());return t.copy=function(){return ej(t,e()).constant(t.constant())},h.apply(t,arguments)},scaleThreshold:()=>function e(){var t,r=[.5],n=[0,1],i=1;function o(e){return null!=e&&e<=e?n[I(r,e,0,i)]:t}return o.domain=function(e){return arguments.length?(i=Math.min((r=Array.from(e)).length,n.length-1),o):r.slice()},o.range=function(e){return arguments.length?(n=Array.from(e),i=Math.min(r.length,n.length-1),o):n.slice()},o.invertExtent=function(e){var t=n.indexOf(e);return[r[t-1],r[t]]},o.unknown=function(e){return arguments.length?(t=e,o):t},o.copy=function(){return e().domain(r).range(n).unknown(t)},h.apply(o,arguments)},scaleTime:()=>r0,scaleUtc:()=>r1,tickFormat:()=>eH});var f=r(8924),d=r(3949),p=r.n(d);function h(e,t){switch(arguments.length){case 0:break;case 1:this.range(e);break;default:this.range(t).domain(e)}return this}function g(e,t){switch(arguments.length){case 0:break;case 1:"function"==typeof e?this.interpolator(e):this.range(e);break;default:this.domain(e),"function"==typeof t?this.interpolator(t):this.range(t)}return this}class v extends Map{constructor(e,t=y){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:t}}),null!=e)for(let[t,r]of e)this.set(t,r)}get(e){return super.get(m(this,e))}has(e){return super.has(m(this,e))}set(e,t){return super.set(function({_intern:e,_key:t},r){let n=t(r);return e.has(n)?e.get(n):(e.set(n,r),r)}(this,e),t)}delete(e){return super.delete(function({_intern:e,_key:t},r){let n=t(r);return e.has(n)&&(r=e.get(n),e.delete(n)),r}(this,e))}}function m({_intern:e,_key:t},r){let n=t(r);return e.has(n)?e.get(n):r}function y(e){return null!==e&&"object"==typeof e?e.valueOf():e}let b=Symbol("implicit");function w(){var e=new v,t=[],r=[],n=b;function i(i){let o=e.get(i);if(void 0===o){if(n!==b)return n;e.set(i,o=t.push(i)-1)}return r[o%r.length]}return i.domain=function(r){if(!arguments.length)return t.slice();for(let n of(t=[],e=new v,r))e.has(n)||e.set(n,t.push(n)-1);return i},i.range=function(e){return arguments.length?(r=Array.from(e),i):r.slice()},i.unknown=function(e){return arguments.length?(n=e,i):n},i.copy=function(){return w(t,r).unknown(n)},h.apply(i,arguments),i}function x(){var e,t,r=w().unknown(void 0),n=r.domain,i=r.range,o=0,a=1,l=!1,u=0,s=0,c=.5;function f(){var r=n().length,f=a=O?10:u>=E?5:u>=C?2:1;return(l<0?(n=Math.round(e*(o=Math.pow(10,-l)/s)),i=Math.round(t*o),n/ot&&--i,o=-o):(n=Math.round(e/(o=Math.pow(10,l)*s)),i=Math.round(t/o),n*ot&&--i),i0))return[];if(e===t)return[e];let n=t=i))return[];let l=o-i+1,u=Array(l);if(n)if(a<0)for(let e=0;et?1:e>=t?0:NaN}function j(e,t){return null==e||null==t?NaN:te?1:t>=e?0:NaN}function T(e){let t,r,n;function i(e,n,o=0,a=e.length){if(o>>1;0>r(e[t],n)?o=t+1:a=t}while(oA(e(t),r),n=(t,r)=>e(t)-r):(t=e===A||e===j?e:R,r=e,n=e),{left:i,center:function(e,t,r=0,o=e.length){let a=i(e,t,r,o-1);return a>r&&n(e[a-1],t)>-n(e[a],t)?a-1:a},right:function(e,n,i=0,o=e.length){if(i>>1;0>=r(e[t],n)?i=t+1:o=t}while(i>8&15|t>>4&240,t>>4&15|240&t,(15&t)<<4|15&t,1):8===r?et(t>>24&255,t>>16&255,t>>8&255,(255&t)/255):4===r?et(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|240&t,((15&t)<<4|15&t)/255):null):(t=H.exec(e))?new en(t[1],t[2],t[3],1):(t=G.exec(e))?new en(255*t[1]/100,255*t[2]/100,255*t[3]/100,1):(t=Z.exec(e))?et(t[1],t[2],t[3],t[4]):(t=W.exec(e))?et(255*t[1]/100,255*t[2]/100,255*t[3]/100,t[4]):(t=K.exec(e))?es(t[1],t[2]/100,t[3]/100,1):(t=q.exec(e))?es(t[1],t[2]/100,t[3]/100,t[4]):Y.hasOwnProperty(e)?ee(Y[e]):"transparent"===e?new en(NaN,NaN,NaN,0):null}function ee(e){return new en(e>>16&255,e>>8&255,255&e,1)}function et(e,t,r,n){return n<=0&&(e=t=r=NaN),new en(e,t,r,n)}function er(e,t,r,n){var i;return 1==arguments.length?((i=e)instanceof F||(i=Q(i)),i)?new en((i=i.rgb()).r,i.g,i.b,i.opacity):new en:new en(e,t,r,null==n?1:n)}function en(e,t,r,n){this.r=+e,this.g=+t,this.b=+r,this.opacity=+n}function ei(){return`#${eu(this.r)}${eu(this.g)}${eu(this.b)}`}function eo(){let e=ea(this.opacity);return`${1===e?"rgb(":"rgba("}${el(this.r)}, ${el(this.g)}, ${el(this.b)}${1===e?")":`, ${e})`}`}function ea(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function el(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function eu(e){return((e=el(e))<16?"0":"")+e.toString(16)}function es(e,t,r,n){return n<=0?e=t=r=NaN:r<=0||r>=1?e=t=NaN:t<=0&&(e=NaN),new ef(e,t,r,n)}function ec(e){if(e instanceof ef)return new ef(e.h,e.s,e.l,e.opacity);if(e instanceof F||(e=Q(e)),!e)return new ef;if(e instanceof ef)return e;var t=(e=e.rgb()).r/255,r=e.g/255,n=e.b/255,i=Math.min(t,r,n),o=Math.max(t,r,n),a=NaN,l=o-i,u=(o+i)/2;return l?(a=t===o?(r-n)/l+(r0&&u<1?0:a,new ef(a,l,u,e.opacity)}function ef(e,t,r,n){this.h=+e,this.s=+t,this.l=+r,this.opacity=+n}function ed(e){return(e=(e||0)%360)<0?e+360:e}function ep(e){return Math.max(0,Math.min(1,e||0))}function eh(e,t,r){return(e<60?t+(r-t)*e/60:e<180?r:e<240?t+(r-t)*(240-e)/60:t)*255}function eg(e,t,r,n,i){var o=e*e,a=o*e;return((1-3*e+3*o-a)*t+(4-6*o+3*a)*r+(1+3*e+3*o-3*a)*n+a*i)/6}N(F,Q,{copy(e){return Object.assign(new this.constructor,this,e)},displayable(){return this.rgb().displayable()},hex:X,formatHex:X,formatHex8:function(){return this.rgb().formatHex8()},formatHsl:function(){return ec(this).formatHsl()},formatRgb:J,toString:J}),N(en,er,L(F,{brighter(e){return e=null==e?1.4285714285714286:Math.pow(1.4285714285714286,e),new en(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=null==e?.7:Math.pow(.7,e),new en(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new en(el(this.r),el(this.g),el(this.b),ea(this.opacity))},displayable(){return -.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:ei,formatHex:ei,formatHex8:function(){return`#${eu(this.r)}${eu(this.g)}${eu(this.b)}${eu((isNaN(this.opacity)?1:this.opacity)*255)}`},formatRgb:eo,toString:eo})),N(ef,function(e,t,r,n){return 1==arguments.length?ec(e):new ef(e,t,r,null==n?1:n)},L(F,{brighter(e){return e=null==e?1.4285714285714286:Math.pow(1.4285714285714286,e),new ef(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=null==e?.7:Math.pow(.7,e),new ef(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,t=isNaN(e)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*t,i=2*r-n;return new en(eh(e>=240?e-240:e+120,i,n),eh(e,i,n),eh(e<120?e+240:e-120,i,n),this.opacity)},clamp(){return new ef(ed(this.h),ep(this.s),ep(this.l),ea(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){let e=ea(this.opacity);return`${1===e?"hsl(":"hsla("}${ed(this.h)}, ${100*ep(this.s)}%, ${100*ep(this.l)}%${1===e?")":`, ${e})`}`}}));let ev=e=>()=>e;function em(e,t){var r=t-e;return r?function(t){return e+t*r}:ev(isNaN(e)?t:e)}let ey=function e(t){var r,n=1==(r=+t)?em:function(e,t){var n,i,o;return t-e?(n=e,i=t,n=Math.pow(n,o=r),i=Math.pow(i,o)-n,o=1/o,function(e){return Math.pow(n+e*i,o)}):ev(isNaN(e)?t:e)};function i(e,t){var r=n((e=er(e)).r,(t=er(t)).r),i=n(e.g,t.g),o=n(e.b,t.b),a=em(e.opacity,t.opacity);return function(t){return e.r=r(t),e.g=i(t),e.b=o(t),e.opacity=a(t),e+""}}return i.gamma=e,i}(1);function eb(e){return function(t){var r,n,i=t.length,o=Array(i),a=Array(i),l=Array(i);for(r=0;r=1?(r=1,t-1):Math.floor(r*t),i=e[n],o=e[n+1],a=n>0?e[n-1]:2*i-o,l=nl&&(a=t.slice(l,a),s[u]?s[u]+=a:s[++u]=a),(i=i[0])===(o=o[0])?s[u]?s[u]+=o:s[++u]=o:(s[++u]=null,c.push({i:u,x:ew(i,o)})),l=eS.lastIndex;return lt&&(r=e,e=t,t=r),s=function(r){return Math.max(e,Math.min(t,r))}),n=u>2?eA:eP,i=o=null,f}function f(t){return null==t||isNaN(t*=1)?r:(i||(i=n(a.map(e),l,u)))(e(s(t)))}return f.invert=function(r){return s(t((o||(o=n(l,a.map(e),ew)))(r)))},f.domain=function(e){return arguments.length?(a=Array.from(e,eC),c()):a.slice()},f.range=function(e){return arguments.length?(l=Array.from(e),c()):l.slice()},f.rangeRound=function(e){return l=Array.from(e),u=eE,c()},f.clamp=function(e){return arguments.length?(s=!!e||ek,c()):s!==ek},f.interpolate=function(e){return arguments.length?(u=e,c()):u},f.unknown=function(e){return arguments.length?(r=e,f):r},function(r,n){return e=r,t=n,c()}}function eR(){return eT()(ek,ek)}var ez=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function eD(e){var t;if(!(t=ez.exec(e)))throw Error("invalid format: "+e);return new eI({fill:t[1],align:t[2],sign:t[3],symbol:t[4],zero:t[5],width:t[6],comma:t[7],precision:t[8]&&t[8].slice(1),trim:t[9],type:t[10]})}function eI(e){this.fill=void 0===e.fill?" ":e.fill+"",this.align=void 0===e.align?">":e.align+"",this.sign=void 0===e.sign?"-":e.sign+"",this.symbol=void 0===e.symbol?"":e.symbol+"",this.zero=!!e.zero,this.width=void 0===e.width?void 0:+e.width,this.comma=!!e.comma,this.precision=void 0===e.precision?void 0:+e.precision,this.trim=!!e.trim,this.type=void 0===e.type?"":e.type+""}function eN(e,t){if((r=(e=t?e.toExponential(t-1):e.toExponential()).indexOf("e"))<0)return null;var r,n=e.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+e.slice(r+1)]}function eL(e){return(e=eN(Math.abs(e)))?e[1]:NaN}function eF(e,t){var r=eN(e,t);if(!r)return e+"";var n=r[0],i=r[1];return i<0?"0."+Array(-i).join("0")+n:n.length>i+1?n.slice(0,i+1)+"."+n.slice(i+1):n+Array(i-n.length+2).join("0")}eD.prototype=eI.prototype,eI.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};let e$={"%":(e,t)=>(100*e).toFixed(t),b:e=>Math.round(e).toString(2),c:e=>e+"",d:function(e){return Math.abs(e=Math.round(e))>=1e21?e.toLocaleString("en").replace(/,/g,""):e.toString(10)},e:(e,t)=>e.toExponential(t),f:(e,t)=>e.toFixed(t),g:(e,t)=>e.toPrecision(t),o:e=>Math.round(e).toString(8),p:(e,t)=>eF(100*e,t),r:eF,s:function(e,t){var r=eN(e,t);if(!r)return e+"";var i=r[0],o=r[1],a=o-(n=3*Math.max(-8,Math.min(8,Math.floor(o/3))))+1,l=i.length;return a===l?i:a>l?i+Array(a-l+1).join("0"):a>0?i.slice(0,a)+"."+i.slice(a):"0."+Array(1-a).join("0")+eN(e,Math.max(0,t+a-1))[0]},X:e=>Math.round(e).toString(16).toUpperCase(),x:e=>Math.round(e).toString(16)};function eB(e){return e}var eV=Array.prototype.map,eU=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"];function eH(e,t,r,n){var i,l,u=P(e,t,r);switch((n=eD(null==n?",f":n)).type){case"s":var s=Math.max(Math.abs(e),Math.abs(t));return null!=n.precision||isNaN(l=Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(eL(s)/3)))-eL(Math.abs(u))))||(n.precision=l),a(n,s);case"":case"e":case"g":case"p":case"r":null!=n.precision||isNaN(l=Math.max(0,eL(Math.abs(Math.max(Math.abs(e),Math.abs(t)))-(i=Math.abs(i=u)))-eL(i))+1)||(n.precision=l-("e"===n.type));break;case"f":case"%":null!=n.precision||isNaN(l=Math.max(0,-eL(Math.abs(u))))||(n.precision=l-("%"===n.type)*2)}return o(n)}function eG(e){var t=e.domain;return e.ticks=function(e){var r=t();return k(r[0],r[r.length-1],null==e?10:e)},e.tickFormat=function(e,r){var n=t();return eH(n[0],n[n.length-1],null==e?10:e,r)},e.nice=function(r){null==r&&(r=10);var n,i,o=t(),a=0,l=o.length-1,u=o[a],s=o[l],c=10;for(s0;){if((i=_(u,s,r))===n)return o[a]=u,o[l]=s,t(o);if(i>0)u=Math.floor(u/i)*i,s=Math.ceil(s/i)*i;else if(i<0)u=Math.ceil(u*i)/i,s=Math.floor(s*i)/i;else break;n=i}return e},e}function eZ(e,t){e=e.slice();var r,n=0,i=e.length-1,o=e[n],a=e[i];return a-e(-t,r)}function eQ(e){let t,r,n=e(eW,eK),i=n.domain,a=10;function l(){var o,l;return t=(o=a)===Math.E?Math.log:10===o&&Math.log10||2===o&&Math.log2||(o=Math.log(o),e=>Math.log(e)/o),r=10===(l=a)?eX:l===Math.E?Math.exp:e=>Math.pow(l,e),i()[0]<0?(t=eJ(t),r=eJ(r),e(eq,eY)):e(eW,eK),n}return n.base=function(e){return arguments.length?(a=+e,l()):a},n.domain=function(e){return arguments.length?(i(e),l()):i()},n.ticks=e=>{let n,o,l=i(),u=l[0],s=l[l.length-1],c=s0){for(;f<=d;++f)for(n=1;ns)break;h.push(o)}}else for(;f<=d;++f)for(n=a-1;n>=1;--n)if(!((o=f>0?n/r(-f):n*r(f))s)break;h.push(o)}2*h.length{if(null==e&&(e=10),null==i&&(i=10===a?"s":","),"function"!=typeof i&&(a%1||null!=(i=eD(i)).precision||(i.trim=!0),i=o(i)),e===1/0)return i;let l=Math.max(1,a*e/n.ticks().length);return e=>{let n=e/r(Math.round(t(e)));return n*ai(eZ(i(),{floor:e=>r(Math.floor(t(e))),ceil:e=>r(Math.ceil(t(e)))})),n}function e0(e){return function(t){return Math.sign(t)*Math.log1p(Math.abs(t/e))}}function e1(e){return function(t){return Math.sign(t)*Math.expm1(Math.abs(t))*e}}function e2(e){var t=1,r=e(e0(1),e1(t));return r.constant=function(r){return arguments.length?e(e0(t=+r),e1(t)):t},eG(r)}function e5(e){return function(t){return t<0?-Math.pow(-t,e):Math.pow(t,e)}}function e6(e){return e<0?-Math.sqrt(-e):Math.sqrt(e)}function e4(e){return e<0?-e*e:e*e}function e3(e){var t=e(ek,ek),r=1;return t.exponent=function(t){return arguments.length?1==(r=+t)?e(ek,ek):.5===r?e(e6,e4):e(e5(r),e5(1/r)):r},eG(t)}function e8(){var e=e3(eT());return e.copy=function(){return ej(e,e8()).exponent(e.exponent())},h.apply(e,arguments),e}function e9(){return e8.apply(null,arguments).exponent(.5)}function e7(e){return Math.sign(e)*e*e}function te(e,t){let r;if(void 0===t)for(let t of e)null!=t&&(r=t)&&(r=t);else{let n=-1;for(let i of e)null!=(i=t(i,++n,e))&&(r=i)&&(r=i)}return r}function tt(e,t){let r;if(void 0===t)for(let t of e)null!=t&&(r>t||void 0===r&&t>=t)&&(r=t);else{let n=-1;for(let i of e)null!=(i=t(i,++n,e))&&(r>i||void 0===r&&i>=i)&&(r=i)}return r}function tr(e,t){return(null==e||!(e>=e))-(null==t||!(t>=t))||(et))}function tn(e,t,r){let n=e[t];e[t]=e[r],e[r]=n}o=(i=function(e){var t,r,i,o=void 0===e.grouping||void 0===e.thousands?eB:(t=eV.call(e.grouping,Number),r=e.thousands+"",function(e,n){for(var i=e.length,o=[],a=0,l=t[0],u=0;i>0&&l>0&&(u+l+1>n&&(l=Math.max(1,n-u)),o.push(e.substring(i-=l,i+l)),!((u+=l+1)>n));)l=t[a=(a+1)%t.length];return o.reverse().join(r)}),a=void 0===e.currency?"":e.currency[0]+"",l=void 0===e.currency?"":e.currency[1]+"",u=void 0===e.decimal?".":e.decimal+"",s=void 0===e.numerals?eB:(i=eV.call(e.numerals,String),function(e){return e.replace(/[0-9]/g,function(e){return i[+e]})}),c=void 0===e.percent?"%":e.percent+"",f=void 0===e.minus?"−":e.minus+"",d=void 0===e.nan?"NaN":e.nan+"";function p(e){var t=(e=eD(e)).fill,r=e.align,i=e.sign,p=e.symbol,h=e.zero,g=e.width,v=e.comma,m=e.precision,y=e.trim,b=e.type;"n"===b?(v=!0,b="g"):e$[b]||(void 0===m&&(m=12),y=!0,b="g"),(h||"0"===t&&"="===r)&&(h=!0,t="0",r="=");var w="$"===p?a:"#"===p&&/[boxX]/.test(b)?"0"+b.toLowerCase():"",x="$"===p?l:/[%p]/.test(b)?c:"",S=e$[b],O=/[defgprs%]/.test(b);function E(e){var a,l,c,p=w,E=x;if("c"===b)E=S(e)+E,e="";else{var C=(e*=1)<0||1/e<0;if(e=isNaN(e)?d:S(Math.abs(e),m),y&&(e=function(e){e:for(var t,r=e.length,n=1,i=-1;n0&&(i=0)}return i>0?e.slice(0,i)+e.slice(t+1):e}(e)),C&&0==+e&&"+"!==i&&(C=!1),p=(C?"("===i?i:f:"-"===i||"("===i?"":i)+p,E=("s"===b?eU[8+n/3]:"")+E+(C&&"("===i?")":""),O){for(a=-1,l=e.length;++a(c=e.charCodeAt(a))||c>57){E=(46===c?u+e.slice(a+1):e.slice(a))+E,e=e.slice(0,a);break}}}v&&!h&&(e=o(e,1/0));var M=p.length+e.length+E.length,k=M>1)+p+e+E+k.slice(M);break;default:e=k+p+e+E}return s(e)}return m=void 0===m?6:/[gprs]/.test(b)?Math.max(1,Math.min(21,m)):Math.max(0,Math.min(20,m)),E.toString=function(){return e+""},E}return{format:p,formatPrefix:function(e,t){var r=p(((e=eD(e)).type="f",e)),n=3*Math.max(-8,Math.min(8,Math.floor(eL(t)/3))),i=Math.pow(10,-n),o=eU[8+n/3];return function(e){return r(i*e)+o}}}}({thousands:",",grouping:[3],currency:["$",""]})).format,a=i.formatPrefix;let ti=new Date,to=new Date;function ta(e,t,r,n){function i(t){return e(t=0==arguments.length?new Date:new Date(+t)),t}return i.floor=t=>(e(t=new Date(+t)),t),i.ceil=r=>(e(r=new Date(r-1)),t(r,1),e(r),r),i.round=e=>{let t=i(e),r=i.ceil(e);return e-t(t(e=new Date(+e),null==r?1:Math.floor(r)),e),i.range=(r,n,o)=>{let a,l=[];if(r=i.ceil(r),o=null==o?1:Math.floor(o),!(r0))return l;do l.push(a=new Date(+r)),t(r,o),e(r);while(ata(t=>{if(t>=t)for(;e(t),!r(t);)t.setTime(t-1)},(e,n)=>{if(e>=e)if(n<0)for(;++n<=0;)for(;t(e,-1),!r(e););else for(;--n>=0;)for(;t(e,1),!r(e););}),r&&(i.count=(t,n)=>(ti.setTime(+t),to.setTime(+n),e(ti),e(to),Math.floor(r(ti,to))),i.every=e=>isFinite(e=Math.floor(e))&&e>0?e>1?i.filter(n?t=>n(t)%e==0:t=>i.count(0,t)%e==0):i:null),i}let tl=ta(()=>{},(e,t)=>{e.setTime(+e+t)},(e,t)=>t-e);tl.every=e=>isFinite(e=Math.floor(e))&&e>0?e>1?ta(t=>{t.setTime(Math.floor(t/e)*e)},(t,r)=>{t.setTime(+t+r*e)},(t,r)=>(r-t)/e):tl:null,tl.range;let tu=ta(e=>{e.setTime(e-e.getMilliseconds())},(e,t)=>{e.setTime(+e+1e3*t)},(e,t)=>(t-e)/1e3,e=>e.getUTCSeconds());tu.range;let ts=ta(e=>{e.setTime(e-e.getMilliseconds()-1e3*e.getSeconds())},(e,t)=>{e.setTime(+e+6e4*t)},(e,t)=>(t-e)/6e4,e=>e.getMinutes());ts.range;let tc=ta(e=>{e.setUTCSeconds(0,0)},(e,t)=>{e.setTime(+e+6e4*t)},(e,t)=>(t-e)/6e4,e=>e.getUTCMinutes());tc.range;let tf=ta(e=>{e.setTime(e-e.getMilliseconds()-1e3*e.getSeconds()-6e4*e.getMinutes())},(e,t)=>{e.setTime(+e+36e5*t)},(e,t)=>(t-e)/36e5,e=>e.getHours());tf.range;let td=ta(e=>{e.setUTCMinutes(0,0,0)},(e,t)=>{e.setTime(+e+36e5*t)},(e,t)=>(t-e)/36e5,e=>e.getUTCHours());td.range;let tp=ta(e=>e.setHours(0,0,0,0),(e,t)=>e.setDate(e.getDate()+t),(e,t)=>(t-e-(t.getTimezoneOffset()-e.getTimezoneOffset())*6e4)/864e5,e=>e.getDate()-1);tp.range;let th=ta(e=>{e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+t)},(e,t)=>(t-e)/864e5,e=>e.getUTCDate()-1);th.range;let tg=ta(e=>{e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+t)},(e,t)=>(t-e)/864e5,e=>Math.floor(e/864e5));function tv(e){return ta(t=>{t.setDate(t.getDate()-(t.getDay()+7-e)%7),t.setHours(0,0,0,0)},(e,t)=>{e.setDate(e.getDate()+7*t)},(e,t)=>(t-e-(t.getTimezoneOffset()-e.getTimezoneOffset())*6e4)/6048e5)}tg.range;let tm=tv(0),ty=tv(1),tb=tv(2),tw=tv(3),tx=tv(4),tS=tv(5),tO=tv(6);function tE(e){return ta(t=>{t.setUTCDate(t.getUTCDate()-(t.getUTCDay()+7-e)%7),t.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+7*t)},(e,t)=>(t-e)/6048e5)}tm.range,ty.range,tb.range,tw.range,tx.range,tS.range,tO.range;let tC=tE(0),tM=tE(1),tk=tE(2),t_=tE(3),tP=tE(4),tA=tE(5),tj=tE(6);tC.range,tM.range,tk.range,t_.range,tP.range,tA.range,tj.range;let tT=ta(e=>{e.setDate(1),e.setHours(0,0,0,0)},(e,t)=>{e.setMonth(e.getMonth()+t)},(e,t)=>t.getMonth()-e.getMonth()+(t.getFullYear()-e.getFullYear())*12,e=>e.getMonth());tT.range;let tR=ta(e=>{e.setUTCDate(1),e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCMonth(e.getUTCMonth()+t)},(e,t)=>t.getUTCMonth()-e.getUTCMonth()+(t.getUTCFullYear()-e.getUTCFullYear())*12,e=>e.getUTCMonth());tR.range;let tz=ta(e=>{e.setMonth(0,1),e.setHours(0,0,0,0)},(e,t)=>{e.setFullYear(e.getFullYear()+t)},(e,t)=>t.getFullYear()-e.getFullYear(),e=>e.getFullYear());tz.every=e=>isFinite(e=Math.floor(e))&&e>0?ta(t=>{t.setFullYear(Math.floor(t.getFullYear()/e)*e),t.setMonth(0,1),t.setHours(0,0,0,0)},(t,r)=>{t.setFullYear(t.getFullYear()+r*e)}):null,tz.range;let tD=ta(e=>{e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCFullYear(e.getUTCFullYear()+t)},(e,t)=>t.getUTCFullYear()-e.getUTCFullYear(),e=>e.getUTCFullYear());function tI(e,t,r,n,i,o){let a=[[tu,1,1e3],[tu,5,5e3],[tu,15,15e3],[tu,30,3e4],[o,1,6e4],[o,5,3e5],[o,15,9e5],[o,30,18e5],[i,1,36e5],[i,3,108e5],[i,6,216e5],[i,12,432e5],[n,1,864e5],[n,2,1728e5],[r,1,6048e5],[t,1,2592e6],[t,3,7776e6],[e,1,31536e6]];function l(t,r,n){let i=Math.abs(r-t)/n,o=T(([,,e])=>e).right(a,i);if(o===a.length)return e.every(P(t/31536e6,r/31536e6,n));if(0===o)return tl.every(Math.max(P(t,r,n),1));let[l,u]=a[i/a[o-1][2]isFinite(e=Math.floor(e))&&e>0?ta(t=>{t.setUTCFullYear(Math.floor(t.getUTCFullYear()/e)*e),t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,r)=>{t.setUTCFullYear(t.getUTCFullYear()+r*e)}):null,tD.range;let[tN,tL]=tI(tD,tR,tC,tg,td,tc),[tF,t$]=tI(tz,tT,tm,tp,tf,ts);function tB(e){if(0<=e.y&&e.y<100){var t=new Date(-1,e.m,e.d,e.H,e.M,e.S,e.L);return t.setFullYear(e.y),t}return new Date(e.y,e.m,e.d,e.H,e.M,e.S,e.L)}function tV(e){if(0<=e.y&&e.y<100){var t=new Date(Date.UTC(-1,e.m,e.d,e.H,e.M,e.S,e.L));return t.setUTCFullYear(e.y),t}return new Date(Date.UTC(e.y,e.m,e.d,e.H,e.M,e.S,e.L))}function tU(e,t,r){return{y:e,m:t,d:r,H:0,M:0,S:0,L:0}}var tH={"-":"",_:" ",0:"0"},tG=/^\s*\d+/,tZ=/^%/,tW=/[\\^$*+?|[\]().{}]/g;function tK(e,t,r){var n=e<0?"-":"",i=(n?-e:e)+"",o=i.length;return n+(o[e.toLowerCase(),t]))}function tJ(e,t,r){var n=tG.exec(t.slice(r,r+1));return n?(e.w=+n[0],r+n[0].length):-1}function tQ(e,t,r){var n=tG.exec(t.slice(r,r+1));return n?(e.u=+n[0],r+n[0].length):-1}function t0(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.U=+n[0],r+n[0].length):-1}function t1(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.V=+n[0],r+n[0].length):-1}function t2(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.W=+n[0],r+n[0].length):-1}function t5(e,t,r){var n=tG.exec(t.slice(r,r+4));return n?(e.y=+n[0],r+n[0].length):-1}function t6(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function t4(e,t,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(t.slice(r,r+6));return n?(e.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function t3(e,t,r){var n=tG.exec(t.slice(r,r+1));return n?(e.q=3*n[0]-3,r+n[0].length):-1}function t8(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.m=n[0]-1,r+n[0].length):-1}function t9(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.d=+n[0],r+n[0].length):-1}function t7(e,t,r){var n=tG.exec(t.slice(r,r+3));return n?(e.m=0,e.d=+n[0],r+n[0].length):-1}function re(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.H=+n[0],r+n[0].length):-1}function rt(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.M=+n[0],r+n[0].length):-1}function rr(e,t,r){var n=tG.exec(t.slice(r,r+2));return n?(e.S=+n[0],r+n[0].length):-1}function rn(e,t,r){var n=tG.exec(t.slice(r,r+3));return n?(e.L=+n[0],r+n[0].length):-1}function ri(e,t,r){var n=tG.exec(t.slice(r,r+6));return n?(e.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function ro(e,t,r){var n=tZ.exec(t.slice(r,r+1));return n?r+n[0].length:-1}function ra(e,t,r){var n=tG.exec(t.slice(r));return n?(e.Q=+n[0],r+n[0].length):-1}function rl(e,t,r){var n=tG.exec(t.slice(r));return n?(e.s=+n[0],r+n[0].length):-1}function ru(e,t){return tK(e.getDate(),t,2)}function rs(e,t){return tK(e.getHours(),t,2)}function rc(e,t){return tK(e.getHours()%12||12,t,2)}function rf(e,t){return tK(1+tp.count(tz(e),e),t,3)}function rd(e,t){return tK(e.getMilliseconds(),t,3)}function rp(e,t){return rd(e,t)+"000"}function rh(e,t){return tK(e.getMonth()+1,t,2)}function rg(e,t){return tK(e.getMinutes(),t,2)}function rv(e,t){return tK(e.getSeconds(),t,2)}function rm(e){var t=e.getDay();return 0===t?7:t}function ry(e,t){return tK(tm.count(tz(e)-1,e),t,2)}function rb(e){var t=e.getDay();return t>=4||0===t?tx(e):tx.ceil(e)}function rw(e,t){return e=rb(e),tK(tx.count(tz(e),e)+(4===tz(e).getDay()),t,2)}function rx(e){return e.getDay()}function rS(e,t){return tK(ty.count(tz(e)-1,e),t,2)}function rO(e,t){return tK(e.getFullYear()%100,t,2)}function rE(e,t){return tK((e=rb(e)).getFullYear()%100,t,2)}function rC(e,t){return tK(e.getFullYear()%1e4,t,4)}function rM(e,t){var r=e.getDay();return tK((e=r>=4||0===r?tx(e):tx.ceil(e)).getFullYear()%1e4,t,4)}function rk(e){var t=e.getTimezoneOffset();return(t>0?"-":(t*=-1,"+"))+tK(t/60|0,"0",2)+tK(t%60,"0",2)}function r_(e,t){return tK(e.getUTCDate(),t,2)}function rP(e,t){return tK(e.getUTCHours(),t,2)}function rA(e,t){return tK(e.getUTCHours()%12||12,t,2)}function rj(e,t){return tK(1+th.count(tD(e),e),t,3)}function rT(e,t){return tK(e.getUTCMilliseconds(),t,3)}function rR(e,t){return rT(e,t)+"000"}function rz(e,t){return tK(e.getUTCMonth()+1,t,2)}function rD(e,t){return tK(e.getUTCMinutes(),t,2)}function rI(e,t){return tK(e.getUTCSeconds(),t,2)}function rN(e){var t=e.getUTCDay();return 0===t?7:t}function rL(e,t){return tK(tC.count(tD(e)-1,e),t,2)}function rF(e){var t=e.getUTCDay();return t>=4||0===t?tP(e):tP.ceil(e)}function r$(e,t){return e=rF(e),tK(tP.count(tD(e),e)+(4===tD(e).getUTCDay()),t,2)}function rB(e){return e.getUTCDay()}function rV(e,t){return tK(tM.count(tD(e)-1,e),t,2)}function rU(e,t){return tK(e.getUTCFullYear()%100,t,2)}function rH(e,t){return tK((e=rF(e)).getUTCFullYear()%100,t,2)}function rG(e,t){return tK(e.getUTCFullYear()%1e4,t,4)}function rZ(e,t){var r=e.getUTCDay();return tK((e=r>=4||0===r?tP(e):tP.ceil(e)).getUTCFullYear()%1e4,t,4)}function rW(){return"+0000"}function rK(){return"%"}function rq(e){return+e}function rY(e){return Math.floor(e/1e3)}function rX(e){return new Date(e)}function rJ(e){return e instanceof Date?+e:+new Date(+e)}function rQ(e,t,r,n,i,o,a,l,u,s){var c=eR(),f=c.invert,d=c.domain,p=s(".%L"),h=s(":%S"),g=s("%I:%M"),v=s("%I %p"),m=s("%a %d"),y=s("%b %d"),b=s("%B"),w=s("%Y");function x(e){return(u(e)=12)]},q:function(e){return 1+~~(e.getMonth()/3)},Q:rq,s:rY,S:rv,u:rm,U:ry,V:rw,w:rx,W:rS,x:null,X:null,y:rO,Y:rC,Z:rk,"%":rK},w={a:function(e){return a[e.getUTCDay()]},A:function(e){return o[e.getUTCDay()]},b:function(e){return u[e.getUTCMonth()]},B:function(e){return l[e.getUTCMonth()]},c:null,d:r_,e:r_,f:rR,g:rH,G:rZ,H:rP,I:rA,j:rj,L:rT,m:rz,M:rD,p:function(e){return i[+(e.getUTCHours()>=12)]},q:function(e){return 1+~~(e.getUTCMonth()/3)},Q:rq,s:rY,S:rI,u:rN,U:rL,V:r$,w:rB,W:rV,x:null,X:null,y:rU,Y:rG,Z:rW,"%":rK},x={a:function(e,t,r){var n=p.exec(t.slice(r));return n?(e.w=h.get(n[0].toLowerCase()),r+n[0].length):-1},A:function(e,t,r){var n=f.exec(t.slice(r));return n?(e.w=d.get(n[0].toLowerCase()),r+n[0].length):-1},b:function(e,t,r){var n=m.exec(t.slice(r));return n?(e.m=y.get(n[0].toLowerCase()),r+n[0].length):-1},B:function(e,t,r){var n=g.exec(t.slice(r));return n?(e.m=v.get(n[0].toLowerCase()),r+n[0].length):-1},c:function(e,r,n){return E(e,t,r,n)},d:t9,e:t9,f:ri,g:t6,G:t5,H:re,I:re,j:t7,L:rn,m:t8,M:rt,p:function(e,t,r){var n=s.exec(t.slice(r));return n?(e.p=c.get(n[0].toLowerCase()),r+n[0].length):-1},q:t3,Q:ra,s:rl,S:rr,u:tQ,U:t0,V:t1,w:tJ,W:t2,x:function(e,t,n){return E(e,r,t,n)},X:function(e,t,r){return E(e,n,t,r)},y:t6,Y:t5,Z:t4,"%":ro};function S(e,t){return function(r){var n,i,o,a=[],l=-1,u=0,s=e.length;for(r instanceof Date||(r=new Date(+r));++l53)return null;"w"in o||(o.w=1),"Z"in o?(n=(i=(n=tV(tU(o.y,0,1))).getUTCDay())>4||0===i?tM.ceil(n):tM(n),n=th.offset(n,(o.V-1)*7),o.y=n.getUTCFullYear(),o.m=n.getUTCMonth(),o.d=n.getUTCDate()+(o.w+6)%7):(n=(i=(n=tB(tU(o.y,0,1))).getDay())>4||0===i?ty.ceil(n):ty(n),n=tp.offset(n,(o.V-1)*7),o.y=n.getFullYear(),o.m=n.getMonth(),o.d=n.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:+("W"in o)),i="Z"in o?tV(tU(o.y,0,1)).getUTCDay():tB(tU(o.y,0,1)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,tV(o)):tB(o)}}function E(e,t,r,n){for(var i,o,a=0,l=t.length,u=r.length;a=u)return -1;if(37===(i=t.charCodeAt(a++))){if(!(o=x[(i=t.charAt(a++))in tH?t.charAt(a++):i])||(n=o(e,r,n))<0)return -1}else if(i!=r.charCodeAt(n++))return -1}return n}return b.x=S(r,b),b.X=S(n,b),b.c=S(t,b),w.x=S(r,w),w.X=S(n,w),w.c=S(t,w),{format:function(e){var t=S(e+="",b);return t.toString=function(){return e},t},parse:function(e){var t=O(e+="",!1);return t.toString=function(){return e},t},utcFormat:function(e){var t=S(e+="",w);return t.toString=function(){return e},t},utcParse:function(e){var t=O(e+="",!0);return t.toString=function(){return e},t}}}({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]})).format,l.parse,s=l.utcFormat,l.utcParse;var r7=r(7238),ne=r(9827),nt=r(356),nr=r(6377),nn=r(8892);function ni(e){if(Array.isArray(e)&&2===e.length){var[t,r]=e;if((0,nn.H)(t)&&(0,nn.H)(r))return!0}return!1}function no(e,t,r){return r?e:[Math.min(e[0],t[0]),Math.max(e[1],t[1])]}var na=r(8870),nl=r.n(na),nu=e=>e,ns={},nc=e=>function t(){let r;return 0==arguments.length||1==arguments.length&&(r=arguments.length<=0?void 0:arguments[0],r===ns)?t:e(...arguments)},nf=(e,t)=>1===e?t:nc(function(){for(var r=arguments.length,n=Array(r),i=0;ie!==ns).length;return o>=e?t(...n):nf(e-o,nc(function(){for(var e=arguments.length,r=Array(e),i=0;ie===ns?r.shift():e),...r)}))}),nd=e=>nf(e.length,e),np=(e,t)=>{for(var r=[],n=e;nArray.isArray(t)?t.map(e):Object.keys(t).map(e=>t[e]).map(e)),ng=function(){for(var e=arguments.length,t=Array(e),r=0;rt(e),i(...arguments))}},nv=e=>Array.isArray(e)?e.reverse():e.split("").reverse().join(""),nm=e=>{var t=null,r=null;return function(){for(var n=arguments.length,i=Array(n),o=0;o{var n;return e===(null==(n=t)?void 0:n[r])})?r:(t=i,r=e(...i))}};function ny(e){return 0===e?1:Math.floor(new(nl())(e).abs().log(10).toNumber())+1}function nb(e,t,r){for(var n=new(nl())(e),i=0,o=[];n.lt(t)&&i<1e5;)o.push(n.toNumber()),n=n.add(r),i++;return o}nd((e,t,r)=>{var n=+e;return n+r*(t-n)}),nd((e,t,r)=>{var n=t-e;return(r-e)/(n=n||1/0)}),nd((e,t,r)=>{var n=t-e;return Math.max(0,Math.min(1,(r-e)/(n=n||1/0)))});var nw=e=>{var[t,r]=e,[n,i]=[t,r];return t>r&&([n,i]=[r,t]),[n,i]},nx=(e,t,r)=>{if(e.lte(0))return new(nl())(0);var n=ny(e.toNumber()),i=new(nl())(10).pow(n),o=e.div(i),a=1!==n?.05:.1,l=new(nl())(Math.ceil(o.div(a).toNumber())).add(r).mul(a).mul(i);return new(nl())(t?l.toNumber():Math.ceil(l.toNumber()))},nS=function(e,t,r,n){var i,o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0;if(!Number.isFinite((t-e)/(r-1)))return{step:new(nl())(0),tickMin:new(nl())(0),tickMax:new(nl())(0)};var a=nx(new(nl())(t).sub(e).div(r-1),n,o),l=Math.ceil((i=e<=0&&t>=0?new(nl())(0):(i=new(nl())(e).add(t).div(2)).sub(new(nl())(i).mod(a))).sub(e).div(a).toNumber()),u=Math.ceil(new(nl())(t).sub(i).div(a).toNumber()),s=l+u+1;return s>r?nS(e,t,r,n,o+1):(s0?u+(r-s):u,l=t>0?l:l+(r-s)),{step:a,tickMin:i.sub(new(nl())(l).mul(a)),tickMax:i.add(new(nl())(u).mul(a))})},nO=nm(function(e){var[t,r]=e,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2],o=Math.max(n,2),[a,l]=nw([t,r]);if(a===-1/0||l===1/0){var u=l===1/0?[a,...np(0,n-1).map(()=>1/0)]:[...np(0,n-1).map(()=>-1/0),l];return t>r?nv(u):u}if(a===l){var s=new(nl())(1),c=new(nl())(a);if(!c.isint()&&i){var f=Math.abs(a);f<1?(s=new(nl())(10).pow(ny(a)-1),c=new(nl())(Math.floor(c.div(s).toNumber())).mul(s)):f>1&&(c=new(nl())(Math.floor(a)))}else 0===a?c=new(nl())(Math.floor((n-1)/2)):i||(c=new(nl())(Math.floor(a)));var d=Math.floor((n-1)/2);return ng(nh(e=>c.add(new(nl())(e-d).mul(s)).toNumber()),np)(0,n)}var{step:p,tickMin:h,tickMax:g}=nS(a,l,o,i,0),v=nb(h,g.add(new(nl())(.1).mul(p)),p);return t>r?nv(v):v}),nE=nm(function(e,t){var[r,n]=e,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2],[o,a]=nw([r,n]);if(o===-1/0||a===1/0)return[r,n];if(o===a)return[o];var l=Math.max(t,2),u=nx(new(nl())(a).sub(o).div(l-1),i,0),s=[...nb(new(nl())(o),new(nl())(a),u),a];return!1===i&&(s=s.map(e=>Math.round(e))),r>n?nv(s):s}),nC=r(2589),nM=r(6908),nk=r(6124),n_=r(972),nP=r(8478),nA=r(7062),nj=(e,t)=>t,nT=(e,t,r)=>r,nR=r(8190),nz=r(4421);function nD(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function nI(e){for(var t=1;t{var r=e.cartesianAxis.xAxis[t];return null==r?nL:r},n$={allowDataOverflow:!1,allowDecimals:!0,allowDuplicatedCategory:!0,angle:0,dataKey:void 0,domain:nN,hide:!0,id:0,includeHidden:!1,interval:"preserveEnd",minTickGap:5,mirror:!1,name:void 0,orientation:"left",padding:{top:0,bottom:0},reversed:!1,scale:"auto",tick:!0,tickCount:5,tickFormatter:void 0,ticks:void 0,type:"number",unit:void 0,width:nz.tQ},nB=(e,t)=>{var r=e.cartesianAxis.yAxis[t];return null==r?n$:r},nV={domain:[0,"auto"],includeHidden:!1,reversed:!1,allowDataOverflow:!1,allowDuplicatedCategory:!1,dataKey:void 0,id:0,name:"",range:[64,64],scale:"auto",type:"number",unit:""},nU=(e,t)=>{var r=e.cartesianAxis.zAxis[t];return null==r?nV:r},nH=(e,t,r)=>{switch(t){case"xAxis":return nF(e,r);case"yAxis":return nB(e,r);case"zAxis":return nU(e,r);case"angleAxis":return(0,nA.Be)(e,r);case"radiusAxis":return(0,nA.Gl)(e,r);default:throw Error("Unexpected axis type: ".concat(t))}},nG=(e,t,r)=>{switch(t){case"xAxis":return nF(e,r);case"yAxis":return nB(e,r);case"angleAxis":return(0,nA.Be)(e,r);case"radiusAxis":return(0,nA.Gl)(e,r);default:throw Error("Unexpected axis type: ".concat(t))}},nZ=e=>e.graphicalItems.countOfBars>0;function nW(e,t){return r=>{switch(e){case"xAxis":return"xAxisId"in r&&r.xAxisId===t;case"yAxis":return"yAxisId"in r&&r.yAxisId===t;case"zAxis":return"zAxisId"in r&&r.zAxisId===t;case"angleAxis":return"angleAxisId"in r&&r.angleAxisId===t;case"radiusAxis":return"radiusAxisId"in r&&r.radiusAxisId===t;default:return!1}}}var nK=e=>e.graphicalItems.cartesianItems,nq=(0,f.Mz)([nj,nT],nW),nY=(e,t,r)=>e.filter(r).filter(e=>(null==t?void 0:t.includeHidden)===!0||!e.hide),nX=(0,f.Mz)([nK,nH,nq],nY),nJ=e=>e.filter(e=>void 0===e.stackId),nQ=(0,f.Mz)([nX],nJ),n0=e=>e.map(e=>e.data).filter(Boolean).flat(1),n1=(0,f.Mz)([nX],n0),n2=(e,t)=>{var{chartData:r=[],dataStartIndex:n,dataEndIndex:i}=t;return e.length>0?e:r.slice(n,i+1)},n5=(0,f.Mz)([n1,nt.HS],n2),n6=(e,t,r)=>(null==t?void 0:t.dataKey)!=null?e.map(e=>({value:(0,ne.kr)(e,t.dataKey)})):r.length>0?r.map(e=>e.dataKey).flatMap(t=>e.map(e=>({value:(0,ne.kr)(e,t)}))):e.map(e=>({value:e})),n4=(0,f.Mz)([n5,nH,nX],n6);function n3(e,t){switch(e){case"xAxis":return"x"===t.direction;case"yAxis":return"y"===t.direction;default:return!1}}function n8(e){return e.filter(e=>(0,nr.vh)(e)||e instanceof Date).map(Number).filter(e=>!1===(0,nr.M8)(e))}var n9=(e,t,r)=>Object.fromEntries(Object.entries(t.reduce((e,t)=>(null==t.stackId||(null==e[t.stackId]&&(e[t.stackId]=[]),e[t.stackId].push(t)),e),{})).map(t=>{var[n,i]=t,o=i.map(e=>e.dataKey);return[n,{stackedData:(0,ne.yy)(e,o,r),graphicalItems:i}]})),n7=(0,f.Mz)([n5,nX,nP.eC],n9),ie=(e,t,r)=>{var{dataStartIndex:n,dataEndIndex:i}=t;if("zAxis"!==r){var o=(0,ne.Mk)(e,n,i);if(null==o||0!==o[0]||0!==o[1])return o}},it=(0,f.Mz)([n7,nt.LF,nj],ie),ir=(e,t,r,n)=>r.length>0?e.flatMap(e=>r.flatMap(r=>{var i,o,a=null==(i=r.errorBars)?void 0:i.filter(e=>n3(n,e)),l=(0,ne.kr)(e,null!=(o=t.dataKey)?o:r.dataKey);return{value:l,errorDomain:function(e,t,r){return!r||"number"!=typeof t||(0,nr.M8)(t)||!r.length?[]:n8(r.flatMap(r=>{var n,i,o=(0,ne.kr)(e,r.dataKey);if(Array.isArray(o)?[n,i]=o:n=i=o,(0,nn.H)(n)&&(0,nn.H)(i))return[t-n,t+i]}))}(e,l,a)}})).filter(Boolean):(null==t?void 0:t.dataKey)!=null?e.map(e=>({value:(0,ne.kr)(e,t.dataKey),errorDomain:[]})):e.map(e=>({value:e,errorDomain:[]})),ii=(0,f.Mz)(n5,nH,nQ,nj,ir);function io(e){var{value:t}=e;if((0,nr.vh)(t)||t instanceof Date)return t}var ia=e=>{var t;if(null==e||!("domain"in e))return nN;if(null!=e.domain)return e.domain;if(null!=e.ticks){if("number"===e.type){var r=n8(e.ticks);return[Math.min(...r),Math.max(...r)]}if("category"===e.type)return e.ticks.map(String)}return null!=(t=null==e?void 0:e.domain)?t:nN},il=function(){for(var e=arguments.length,t=Array(e),r=0;re.referenceElements.dots,is=(e,t,r)=>e.filter(e=>"extendDomain"===e.ifOverflow).filter(e=>"xAxis"===t?e.xAxisId===r:e.yAxisId===r),ic=(0,f.Mz)([iu,nj,nT],is),id=e=>e.referenceElements.areas,ip=(0,f.Mz)([id,nj,nT],is),ih=e=>e.referenceElements.lines,ig=(0,f.Mz)([ih,nj,nT],is),iv=(e,t)=>{var r=n8(e.map(e=>"xAxis"===t?e.x:e.y));if(0!==r.length)return[Math.min(...r),Math.max(...r)]},im=(0,f.Mz)(ic,nj,iv),iy=(e,t)=>{var r=n8(e.flatMap(e=>["xAxis"===t?e.x1:e.y1,"xAxis"===t?e.x2:e.y2]));if(0!==r.length)return[Math.min(...r),Math.max(...r)]},ib=(0,f.Mz)([ip,nj],iy),iw=(e,t)=>{var r=n8(e.map(e=>"xAxis"===t?e.x:e.y));if(0!==r.length)return[Math.min(...r),Math.max(...r)]},ix=(0,f.Mz)(ig,nj,iw),iS=(0,f.Mz)(im,ix,ib,(e,t,r)=>il(e,r,t)),iO=(0,f.Mz)([nH],ia),iE=(e,t,r,n,i)=>{var o=function(e,t){if(t&&"function"!=typeof e&&Array.isArray(e)&&2===e.length){var r,n,[i,o]=e;if((0,nn.H)(i))r=i;else if("function"==typeof i)return;if((0,nn.H)(o))n=o;else if("function"==typeof o)return;var a=[r,n];if(ni(a))return a}}(t,e.allowDataOverflow);return null!=o?o:function(e,t,r){if(r||null!=t){if("function"==typeof e&&null!=t)try{var n=e(t,r);if(ni(n))return no(n,t,r)}catch(e){}if(Array.isArray(e)&&2===e.length){var i,o,[a,l]=e;if("auto"===a)null!=t&&(i=Math.min(...t));else if((0,nr.Et)(a))i=a;else if("function"==typeof a)try{null!=t&&(i=a(null==t?void 0:t[0]))}catch(e){}else if("string"==typeof a&&ne.IH.test(a)){var u=ne.IH.exec(a);if(null==u||null==t)i=void 0;else{var s=+u[1];i=t[0]-s}}else i=null==t?void 0:t[0];if("auto"===l)null!=t&&(o=Math.max(...t));else if((0,nr.Et)(l))o=l;else if("function"==typeof l)try{null!=t&&(o=l(null==t?void 0:t[1]))}catch(e){}else if("string"==typeof l&&ne.qx.test(l)){var c=ne.qx.exec(l);if(null==c||null==t)o=void 0;else{var f=+c[1];o=t[1]+f}}else o=null==t?void 0:t[1];var d=[i,o];if(ni(d))return null==t?d:no(d,t,r)}}}(t,il(r,i,(e=>{var t=n8(e.flatMap(e=>[e.value,e.errorDomain]).flat(1));if(0!==t.length)return[Math.min(...t),Math.max(...t)]})(n)),e.allowDataOverflow)},iC=(0,f.Mz)([nH,iO,it,ii,iS],iE),iM=[0,1],ik=(e,t,r,n,i,o,a)=>{if(null!=e&&null!=r&&0!==r.length){var{dataKey:l,type:u}=e,s=(0,ne._L)(t,o);return s&&null==l?p()(0,r.length):"category"===u?((e,t,r)=>{var n=e.map(io).filter(e=>null!=e);return r&&(null==t.dataKey||t.allowDuplicatedCategory&&(0,nr.CG)(n))?p()(0,e.length):t.allowDuplicatedCategory?n:Array.from(new Set(n))})(n,e,s):"expand"===i?iM:a}},i_=(0,f.Mz)([nH,r7.fz,n5,n4,nP.eC,nj,iC],ik),iP=(e,t,r,n,i)=>{if(null!=e){var{scale:o,type:a}=e;if("auto"===o)return"radial"===t&&"radiusAxis"===i?"band":"radial"===t&&"angleAxis"===i?"linear":"category"===a&&n&&(n.indexOf("LineChart")>=0||n.indexOf("AreaChart")>=0||n.indexOf("ComposedChart")>=0&&!r)?"point":"category"===a?"band":"linear";if("string"==typeof o){var l="scale".concat((0,nr.Zb)(o));return l in c?l:"point"}}},iA=(0,f.Mz)([nH,r7.fz,nZ,nP.iO,nj],iP);function ij(e,t,r,n){if(null!=r&&null!=n){if("function"==typeof e.scale)return e.scale.copy().domain(r).range(n);var i=function(e){if(null!=e){if(e in c)return c[e]();var t="scale".concat((0,nr.Zb)(e));if(t in c)return c[t]()}}(t);if(null!=i){var o=i.domain(r).range(n);return(0,ne.YB)(o),o}}}var iT=(e,t,r)=>{var n=ia(t);if("auto"===r||"linear"===r){if(null!=t&&t.tickCount&&Array.isArray(n)&&("auto"===n[0]||"auto"===n[1])&&ni(e))return nO(e,t.tickCount,t.allowDecimals);if(null!=t&&t.tickCount&&"number"===t.type&&ni(e))return nE(e,t.tickCount,t.allowDecimals)}},iR=(0,f.Mz)([i_,nG,iA],iT),iz=(e,t,r,n)=>"angleAxis"!==n&&(null==e?void 0:e.type)==="number"&&ni(t)&&Array.isArray(r)&&r.length>0?[Math.min(t[0],r[0]),Math.max(t[1],r[r.length-1])]:t,iD=(0,f.Mz)([nH,i_,iR,nj],iz),iI=(0,f.Mz)(n4,nH,(e,t)=>{if(t&&"number"===t.type){var r=1/0,n=Array.from(n8(e.map(e=>e.value))).sort((e,t)=>e-t);if(n.length<2)return 1/0;var i=n[n.length-1]-n[0];if(0===i)return 1/0;for(var o=0;on,(e,t,r,n,i)=>{if(!(0,nn.H)(e))return 0;var o="vertical"===t?n.height:n.width;if("gap"===i)return e*o/2;if("no-gap"===i){var a=(0,nr.F4)(r,e*o),l=e*o/2;return l-a-(l-a)/o*a}return 0}),iL=(0,f.Mz)(nF,(e,t)=>{var r=nF(e,t);return null==r||"string"!=typeof r.padding?0:iN(e,"xAxis",t,r.padding)},(e,t)=>{if(null==e)return{left:0,right:0};var r,n,{padding:i}=e;return"string"==typeof i?{left:t,right:t}:{left:(null!=(r=i.left)?r:0)+t,right:(null!=(n=i.right)?n:0)+t}}),iF=(0,f.Mz)(nB,(e,t)=>{var r=nB(e,t);return null==r||"string"!=typeof r.padding?0:iN(e,"yAxis",t,r.padding)},(e,t)=>{if(null==e)return{top:0,bottom:0};var r,n,{padding:i}=e;return"string"==typeof i?{top:t,bottom:t}:{top:(null!=(r=i.top)?r:0)+t,bottom:(null!=(n=i.bottom)?n:0)+t}}),i$=(0,f.Mz)([nk.HZ,iL,n_.U,n_.C,(e,t,r)=>r],(e,t,r,n,i)=>{var{padding:o}=n;return i?[o.left,r.width-o.right]:[e.left+t.left,e.left+e.width-t.right]}),iB=(0,f.Mz)([nk.HZ,r7.fz,iF,n_.U,n_.C,(e,t,r)=>r],(e,t,r,n,i,o)=>{var{padding:a}=i;return o?[n.height-a.bottom,a.top]:"horizontal"===t?[e.top+e.height-r.bottom,e.top+r.top]:[e.top+r.top,e.top+e.height-r.bottom]}),iV=(e,t,r,n)=>{var i;switch(t){case"xAxis":return i$(e,r,n);case"yAxis":return iB(e,r,n);case"zAxis":return null==(i=nU(e,r))?void 0:i.range;case"angleAxis":return(0,nA.Cv)(e);case"radiusAxis":return(0,nA.Dc)(e,r);default:return}},iU=(0,f.Mz)([nH,iV],nR.I),iH=(0,f.Mz)([nH,iA,iD,iU],ij);function iG(e,t){return e.idt.id)}(0,f.Mz)(nX,nj,(e,t)=>e.flatMap(e=>{var t;return null!=(t=e.errorBars)?t:[]}).filter(e=>n3(t,e)));var iZ=(e,t)=>t,iW=(e,t,r)=>r,iK=(0,f.Mz)(nM.h,iZ,iW,(e,t,r)=>e.filter(e=>e.orientation===t).filter(e=>e.mirror===r).sort(iG)),iq=(0,f.Mz)(nM.W,iZ,iW,(e,t,r)=>e.filter(e=>e.orientation===t).filter(e=>e.mirror===r).sort(iG)),iY=(e,t)=>({width:e.width,height:t.height});(0,f.Mz)(nk.HZ,nF,iY),(0,f.Mz)(nC.A$,nk.HZ,iK,iZ,iW,(e,t,r,n,i)=>{var o,a={};return r.forEach(r=>{var l=iY(t,r);null==o&&(o=((e,t,r)=>{switch(t){case"top":return e.top;case"bottom":return r-e.bottom;default:return 0}})(t,n,e));var u="top"===n&&!i||"bottom"===n&&i;a[r.id]=o-Number(u)*l.height,o+=(u?-1:1)*l.height}),a}),(0,f.Mz)(nC.Lp,nk.HZ,iq,iZ,iW,(e,t,r,n,i)=>{var o,a={};return r.forEach(r=>{var l=((e,t)=>({width:"number"==typeof t.width?t.width:nz.tQ,height:e.height}))(t,r);null==o&&(o=((e,t,r)=>{switch(t){case"left":return e.left;case"right":return r-e.right;default:return 0}})(t,n,e));var u="left"===n&&!i||"right"===n&&i;a[r.id]=o-Number(u)*l.width,o+=(u?-1:1)*l.width}),a}),(0,f.Mz)(nk.HZ,nB,(e,t)=>({width:"number"==typeof t.width?t.width:nz.tQ,height:e.height}));var iX=(e,t,r,n)=>{if(null!=r){var{allowDuplicatedCategory:i,type:o,dataKey:a}=r,l=(0,ne._L)(e,n),u=t.map(e=>e.value);if(a&&l&&"category"===o&&i&&(0,nr.CG)(u))return u}},iJ=(0,f.Mz)([r7.fz,n4,nH,nj],iX),iQ=(e,t,r,n)=>{if(null!=r&&null!=r.dataKey){var{type:i,scale:o}=r;if((0,ne._L)(e,n)&&("number"===i||"auto"!==o))return t.map(e=>e.value)}},i0=(0,f.Mz)([r7.fz,n4,nG,nj],iQ),i1=(0,f.Mz)([r7.fz,(e,t,r)=>{switch(t){case"xAxis":return nF(e,r);case"yAxis":return nB(e,r);default:throw Error("Unexpected axis type: ".concat(t))}},iA,iH,iJ,i0,iV,iR,nj],(e,t,r,n,i,o,a,l,u)=>{if(null==t)return null;var s=(0,ne._L)(e,u);return{angle:t.angle,interval:t.interval,minTickGap:t.minTickGap,orientation:t.orientation,tick:t.tick,tickCount:t.tickCount,tickFormatter:t.tickFormatter,ticks:t.ticks,type:t.type,unit:t.unit,axisType:u,categoricalDomain:o,duplicateDomain:i,isCategorical:s,niceTicks:l,range:a,realScaleType:r,scale:n}});(0,f.Mz)([r7.fz,nG,iA,iH,iR,iV,iJ,i0,nj],(e,t,r,n,i,o,a,l,u)=>{if(null!=t&&null!=n){var s=(0,ne._L)(e,u),{type:c,ticks:f,tickCount:d}=t,p="scaleBand"===r&&"function"==typeof n.bandwidth?n.bandwidth()/2:2,h="category"===c&&n.bandwidth?n.bandwidth()/p:0;h="angleAxis"===u&&null!=o&&o.length>=2?2*(0,nr.sA)(o[0]-o[1])*h:h;var g=f||i;return g?g.map((e,t)=>({index:t,coordinate:n(a?a.indexOf(e):e)+h,value:e,offset:h})).filter(e=>!(0,nr.M8)(e.coordinate)):s&&l?l.map((e,t)=>({coordinate:n(e)+h,value:e,index:t,offset:h})):n.ticks?n.ticks(d).map(e=>({coordinate:n(e)+h,value:e,offset:h})):n.domain().map((e,t)=>({coordinate:n(e)+h,value:a?a[e]:e,index:t,offset:h}))}});var i2=(0,f.Mz)([r7.fz,nG,iH,iV,iJ,i0,nj],(e,t,r,n,i,o,a)=>{if(null!=t&&null!=r&&null!=n&&n[0]!==n[1]){var l=(0,ne._L)(e,a),{tickCount:u}=t,s=0;return(s="angleAxis"===a&&(null==n?void 0:n.length)>=2?2*(0,nr.sA)(n[0]-n[1])*s:s,l&&o)?o.map((e,t)=>({coordinate:r(e)+s,value:e,index:t,offset:s})):r.ticks?r.ticks(u).map(e=>({coordinate:r(e)+s,value:e,offset:s})):r.domain().map((e,t)=>({coordinate:r(e)+s,value:i?i[e]:e,index:t,offset:s}))}}),i5=(0,f.Mz)(nH,iH,(e,t)=>{if(null!=e&&null!=t)return nI(nI({},e),{},{scale:t})}),i6=(0,f.Mz)([nH,iA,i_,iU],ij);(0,f.Mz)((e,t,r)=>nU(e,r),i6,(e,t)=>{if(null!=e&&null!=t)return nI(nI({},e),{},{scale:t})});var i4=(0,f.Mz)([r7.fz,nM.h,nM.W],(e,t,r)=>{switch(e){case"horizontal":return t.some(e=>e.reversed)?"right-to-left":"left-to-right";case"vertical":return r.some(e=>e.reversed)?"bottom-to-top":"top-to-bottom";case"centric":case"radial":return"left-to-right";default:return}})},2188:(e,t,r)=>{e.exports=r(5252).isEqual},2194:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(921);t.property=function(e){return function(t){return n.get(t,e)}}},2248:(e,t,r)=>{"use strict";r.d(t,{Vi:()=>c,ZF:()=>s,g5:()=>u,iZ:()=>p});var n=r(5710),i=r(4532),o=(0,n.Z0)({name:"graphicalItems",initialState:{countOfBars:0,cartesianItems:[],polarItems:[]},reducers:{addBar(e){e.countOfBars+=1},removeBar(e){e.countOfBars-=1},addCartesianGraphicalItem(e,t){e.cartesianItems.push((0,i.h4)(t.payload))},replaceCartesianGraphicalItem(e,t){var{prev:r,next:n}=t.payload,o=(0,i.ss)(e).cartesianItems.indexOf((0,i.h4)(r));o>-1&&(e.cartesianItems[o]=(0,i.h4)(n))},removeCartesianGraphicalItem(e,t){var r=(0,i.ss)(e).cartesianItems.indexOf((0,i.h4)(t.payload));r>-1&&e.cartesianItems.splice(r,1)},addPolarGraphicalItem(e,t){e.polarItems.push((0,i.h4)(t.payload))},removePolarGraphicalItem(e,t){var r=(0,i.ss)(e).polarItems.indexOf((0,i.h4)(t.payload));r>-1&&e.polarItems.splice(r,1)}}}),{addBar:a,removeBar:l,addCartesianGraphicalItem:u,replaceCartesianGraphicalItem:s,removeCartesianGraphicalItem:c,addPolarGraphicalItem:f,removePolarGraphicalItem:d}=o.actions,p=o.reducer},2278:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevrons-left",[["path",{d:"m11 17-5-5 5-5",key:"13zhaf"}],["path",{d:"m18 17-5-5 5-5",key:"h8a8et"}]])},2319:(e,t,r)=>{"use strict";r.d(t,{s:()=>et});var n=r(2115),i=r(7650),o=r(8060),a=r(2596),l=r(2790);let u=Math.cos,s=Math.sin,c=Math.sqrt,f=Math.PI,d=2*f,p={draw(e,t){let r=c(t/f);e.moveTo(r,0),e.arc(0,0,r,0,d)}},h=c(1/3),g=2*h,v=s(f/10)/s(7*f/10),m=s(d/10)*v,y=-u(d/10)*v,b=c(3),w=c(3)/2,x=1/c(12),S=(x/2+1)*3;var O=r(5654),E=r(1847);c(3),c(3);var C=r(788),M=r(6377),k=["type","size","sizeType"];function _(){return(_=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{type:t="circle",size:r=64,sizeType:i="area"}=e,o=A(A({},function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{var e=j["symbol".concat((0,M.Zb)(t))]||p;return(function(e,t){let r=null,n=(0,E.i)(i);function i(){let i;if(r||(r=i=n()),e.apply(this,arguments).draw(r,+t.apply(this,arguments)),i)return r=null,i+""||null}return e="function"==typeof e?e:(0,O.A)(e||p),t="function"==typeof t?t:(0,O.A)(void 0===t?64:+t),i.type=function(t){return arguments.length?(e="function"==typeof t?t:(0,O.A)(t),i):e},i.size=function(e){return arguments.length?(t="function"==typeof e?e:(0,O.A)(+e),i):t},i.context=function(e){return arguments.length?(r=null==e?null:e,i):r},i})().type(e).size(((e,t,r)=>{if("area"===t)return e;switch(r){case"cross":return 5*e*e/9;case"diamond":return .5*e*e/Math.sqrt(3);case"square":return e*e;case"star":var n=18*T;return 1.25*e*e*(Math.tan(n)-Math.tan(2*n)*Math.tan(n)**2);case"triangle":return Math.sqrt(3)*e*e/4;case"wye":return(21-10*Math.sqrt(3))*e*e/8;default:return Math.PI*e*e/4}})(r,i,t))()})()})):null};R.registerSymbol=(e,t)=>{j["symbol".concat((0,M.Zb)(e))]=t};var z=r(3597);function D(){return(D=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var d=e.formatter||i,p=(0,a.$)({"recharts-legend-item":!0,["legend-item-".concat(r)]:!0,inactive:e.inactive});if("none"===e.type)return null;var h=e.inactive?o:e.color,g=d?d(e.value,e,r):e.value;return n.createElement("li",D({className:p,style:c,key:"legend-item-".concat(r)},(0,z.XC)(this.props,e,r)),n.createElement(l.u,{width:t,height:t,viewBox:s,style:f,"aria-label":"".concat(g," legend icon")},this.renderIcon(e,u)),n.createElement("span",{className:"recharts-legend-item-text",style:{color:h}},g))})}render(){var{payload:e,layout:t,align:r}=this.props;return e&&e.length?n.createElement("ul",{className:"recharts-default-legend",style:{padding:0,margin:0,textAlign:"horizontal"===t?r:"left"}},this.renderItems()):null}}N(L,"displayName","Legend"),N(L,"defaultProps",{align:"center",iconSize:14,inactiveColor:"#ccc",layout:"horizontal",verticalAlign:"middle"});var F=r(2494),$=r(1971),B=r(5803),V=r(7918),U=r(7238),H=r(2634),G=["contextPayload"];function Z(){return(Z=Object.assign?Object.assign.bind():function(e){for(var t=1;t{t((0,H.h1)(e))},[t,e]),null}function Q(e){var t=(0,$.j)();return(0,n.useEffect)(()=>(t((0,H.hx)(e)),()=>{t((0,H.hx)({width:0,height:0}))}),[t,e]),null}function ee(e){var t=(0,$.G)(B.g0),r=(0,o.M)(),a=(0,U.Kp)(),{width:l,height:u,wrapperStyle:s,portal:c}=e,[f,d]=(0,V.V)([t]),p=(0,U.yi)(),h=(0,U.rY)(),g=p-(a.left||0)-(a.right||0),v=et.getWidthOrHeight(e.layout,u,l,g),m=c?s:K(K({position:"absolute",width:(null==v?void 0:v.width)||l||"auto",height:(null==v?void 0:v.height)||u||"auto"},function(e,t,r,n,i,o){var a,l,{layout:u,align:s,verticalAlign:c}=t;return e&&(void 0!==e.left&&null!==e.left||void 0!==e.right&&null!==e.right)||(a="center"===s&&"vertical"===u?{left:((n||0)-o.width)/2}:"right"===s?{right:r&&r.right||0}:{left:r&&r.left||0}),e&&(void 0!==e.top&&null!==e.top||void 0!==e.bottom&&null!==e.bottom)||(l="middle"===c?{top:((i||0)-o.height)/2}:"bottom"===c?{bottom:r&&r.bottom||0}:{top:r&&r.top||0}),K(K({},a),l)}(s,e,a,p,h,f)),s),y=null!=c?c:r;if(null==y)return null;var b=n.createElement("div",{className:"recharts-legend-wrapper",style:m,ref:d},n.createElement(J,{layout:e.layout,align:e.align,verticalAlign:e.verticalAlign,itemSorter:e.itemSorter}),n.createElement(Q,{width:f.width,height:f.height}),n.createElement(X,Z({},e,v,{margin:a,chartWidth:p,chartHeight:h,contextPayload:t})));return(0,i.createPortal)(b,y)}class et extends n.PureComponent{static getWidthOrHeight(e,t,r,n){return"vertical"===e&&(0,M.Et)(t)?{height:t}:"horizontal"===e?{width:r||n}:null}render(){return n.createElement(ee,this.props)}}q(et,"displayName","Legend"),q(et,"defaultProps",{align:"center",iconSize:14,itemSorter:"value",layout:"horizontal",verticalAlign:"bottom"})},2348:(e,t,r)=>{"use strict";r.d(t,{W:()=>u});var n=r(2115),i=r(2596),o=r(788),a=["children","className"];function l(){return(l=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:r,className:u}=e,s=function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevron-left",[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]])},2384:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isObject=function(e){return null!==e&&("object"==typeof e||"function"==typeof e)}},2429:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(4117);t.cloneDeep=function(e){return n.cloneDeepWithImpl(e,void 0,e,new Map,void 0)}},2434:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(7064),i=r(5998),o=r(4373);t.sortBy=function(e,...t){let r=t.length;return r>1&&o.isIterateeCall(e,t[0],t[1])?t=[]:r>2&&o.isIterateeCall(t[0],t[1],t[2])&&(t=[t[0]]),n.orderBy(e,i.flatten(t),["asc"])}},2436:(e,t,r)=>{"use strict";var n=r(2115),i="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},o=n.useState,a=n.useEffect,l=n.useLayoutEffect,u=n.useDebugValue;function s(e){var t=e.getSnapshot;e=e.value;try{var r=t();return!i(e,r)}catch(e){return!0}}var c="undefined"==typeof window||void 0===window.document||void 0===window.document.createElement?function(e,t){return t()}:function(e,t){var r=t(),n=o({inst:{value:r,getSnapshot:t}}),i=n[0].inst,c=n[1];return l(function(){i.value=r,i.getSnapshot=t,s(i)&&c({inst:i})},[e,r,t]),a(function(){return s(i)&&c({inst:i}),e(function(){s(i)&&c({inst:i})})},[e]),u(r),r};t.useSyncExternalStore=void 0!==n.useSyncExternalStore?n.useSyncExternalStore:c},2465:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.identity=function(e){return e}},2493:(e,t,r)=>{"use strict";r.d(t,{q7:()=>P,bL:()=>_});var n=r(2115),i=r(6081),o=r(3655),a=r(9196),l=r(5185),u=r(5845),s=r(5155),c="Toggle",f=n.forwardRef((e,t)=>{let{pressed:r,defaultPressed:n,onPressedChange:i,...a}=e,[f,d]=(0,u.i)({prop:r,onChange:i,defaultProp:null!=n&&n,caller:c});return(0,s.jsx)(o.sG.button,{type:"button","aria-pressed":f,"data-state":f?"on":"off","data-disabled":e.disabled?"":void 0,...a,ref:t,onClick:(0,l.m)(e.onClick,()=>{e.disabled||d(!f)})})});f.displayName=c;var d=r(4315),p="ToggleGroup",[h,g]=(0,i.A)(p,[a.RG]),v=(0,a.RG)(),m=n.forwardRef((e,t)=>{let{type:r,...n}=e;if("single"===r)return(0,s.jsx)(w,{...n,ref:t});if("multiple"===r)return(0,s.jsx)(x,{...n,ref:t});throw Error("Missing prop `type` expected on `".concat(p,"`"))});m.displayName=p;var[y,b]=h(p),w=n.forwardRef((e,t)=>{let{value:r,defaultValue:i,onValueChange:o=()=>{},...a}=e,[l,c]=(0,u.i)({prop:r,defaultProp:null!=i?i:"",onChange:o,caller:p});return(0,s.jsx)(y,{scope:e.__scopeToggleGroup,type:"single",value:n.useMemo(()=>l?[l]:[],[l]),onItemActivate:c,onItemDeactivate:n.useCallback(()=>c(""),[c]),children:(0,s.jsx)(E,{...a,ref:t})})}),x=n.forwardRef((e,t)=>{let{value:r,defaultValue:i,onValueChange:o=()=>{},...a}=e,[l,c]=(0,u.i)({prop:r,defaultProp:null!=i?i:[],onChange:o,caller:p}),f=n.useCallback(e=>c(function(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return[...t,e]}),[c]),d=n.useCallback(e=>c(function(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return t.filter(t=>t!==e)}),[c]);return(0,s.jsx)(y,{scope:e.__scopeToggleGroup,type:"multiple",value:l,onItemActivate:f,onItemDeactivate:d,children:(0,s.jsx)(E,{...a,ref:t})})});m.displayName=p;var[S,O]=h(p),E=n.forwardRef((e,t)=>{let{__scopeToggleGroup:r,disabled:n=!1,rovingFocus:i=!0,orientation:l,dir:u,loop:c=!0,...f}=e,p=v(r),h=(0,d.jH)(u),g={role:"group",dir:h,...f};return(0,s.jsx)(S,{scope:r,rovingFocus:i,disabled:n,children:i?(0,s.jsx)(a.bL,{asChild:!0,...p,orientation:l,dir:h,loop:c,children:(0,s.jsx)(o.sG.div,{...g,ref:t})}):(0,s.jsx)(o.sG.div,{...g,ref:t})})}),C="ToggleGroupItem",M=n.forwardRef((e,t)=>{let r=b(C,e.__scopeToggleGroup),i=O(C,e.__scopeToggleGroup),o=v(e.__scopeToggleGroup),l=r.value.includes(e.value),u=i.disabled||e.disabled,c={...e,pressed:l,disabled:u},f=n.useRef(null);return i.rovingFocus?(0,s.jsx)(a.q7,{asChild:!0,...o,focusable:!u,active:l,ref:f,children:(0,s.jsx)(k,{...c,ref:t})}):(0,s.jsx)(k,{...c,ref:t})});M.displayName=C;var k=n.forwardRef((e,t)=>{let{__scopeToggleGroup:r,value:n,...i}=e,o=b(C,r),a={role:"radio","aria-checked":e.pressed,"aria-pressed":void 0},l="single"===o.type?a:void 0;return(0,s.jsx)(f,{...l,...i,ref:t,onPressedChange:e=>{e?o.onItemActivate(n):o.onItemDeactivate(n)}})}),_=m,P=M},2494:(e,t,r)=>{"use strict";r.d(t,{s:()=>o});var n=r(512),i=r.n(n);function o(e,t,r){return!0===t?i()(e,r):"function"==typeof t?i()(e,t):e}},2520:(e,t,r)=>{"use strict";var n=r(9641).Buffer;Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let i=r(1147),o=r(8221),a=r(5160),l=r(2721),u=r(3616);t.isEqualWith=function(e,t,r){return function e(t,r,s,c,f,d,p){let h=p(t,r,s,c,f,d);if(void 0!==h)return h;if(typeof t==typeof r)switch(typeof t){case"bigint":case"string":case"boolean":case"symbol":case"undefined":case"function":return t===r;case"number":return t===r||Object.is(t,r)}return function t(r,s,c,f){if(Object.is(r,s))return!0;let d=a.getTag(r),p=a.getTag(s);if(d===l.argumentsTag&&(d=l.objectTag),p===l.argumentsTag&&(p=l.objectTag),d!==p)return!1;switch(d){case l.stringTag:return r.toString()===s.toString();case l.numberTag:{let e=r.valueOf(),t=s.valueOf();return u.eq(e,t)}case l.booleanTag:case l.dateTag:case l.symbolTag:return Object.is(r.valueOf(),s.valueOf());case l.regexpTag:return r.source===s.source&&r.flags===s.flags;case l.functionTag:return r===s}let h=(c=c??new Map).get(r),g=c.get(s);if(null!=h&&null!=g)return h===s;c.set(r,s),c.set(s,r);try{switch(d){case l.mapTag:if(r.size!==s.size)return!1;for(let[t,n]of r.entries())if(!s.has(t)||!e(n,s.get(t),t,r,s,c,f))return!1;return!0;case l.setTag:{if(r.size!==s.size)return!1;let t=Array.from(r.values()),n=Array.from(s.values());for(let i=0;ie(o,t,void 0,r,s,c,f));if(-1===a)return!1;n.splice(a,1)}return!0}case l.arrayTag:case l.uint8ArrayTag:case l.uint8ClampedArrayTag:case l.uint16ArrayTag:case l.uint32ArrayTag:case l.bigUint64ArrayTag:case l.int8ArrayTag:case l.int16ArrayTag:case l.int32ArrayTag:case l.bigInt64ArrayTag:case l.float32ArrayTag:case l.float64ArrayTag:if(void 0!==n&&n.isBuffer(r)!==n.isBuffer(s)||r.length!==s.length)return!1;for(let t=0;t{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("trash-2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]])},2589:(e,t,r)=>{"use strict";r.d(t,{A$:()=>i,HK:()=>a,Lp:()=>n,et:()=>o});var n=e=>e.layout.width,i=e=>e.layout.height,o=e=>e.layout.scale,a=e=>e.layout.margin},2596:(e,t,r)=>{"use strict";function n(){for(var e,t,r=0,n="",i=arguments.length;rn})},2634:(e,t,r)=>{"use strict";r.d(t,{CU:()=>c,Lx:()=>u,h1:()=>l,hx:()=>a,u3:()=>s});var n=r(5710),i=r(4532),o=(0,n.Z0)({name:"legend",initialState:{settings:{layout:"horizontal",align:"center",verticalAlign:"middle",itemSorter:"value"},size:{width:0,height:0},payload:[]},reducers:{setLegendSize(e,t){e.size.width=t.payload.width,e.size.height=t.payload.height},setLegendSettings(e,t){e.settings.align=t.payload.align,e.settings.layout=t.payload.layout,e.settings.verticalAlign=t.payload.verticalAlign,e.settings.itemSorter=t.payload.itemSorter},addLegendPayload(e,t){e.payload.push((0,i.h4)(t.payload))},removeLegendPayload(e,t){var r=(0,i.ss)(e).payload.indexOf((0,i.h4)(t.payload));r>-1&&e.payload.splice(r,1)}}}),{setLegendSize:a,setLegendSettings:l,addLegendPayload:u,removeLegendPayload:s}=o.actions,c=o.reducer},2661:e=>{"use strict";var t=Object.prototype.hasOwnProperty,r="~";function n(){}function i(e,t,r){this.fn=e,this.context=t,this.once=r||!1}function o(e,t,n,o,a){if("function"!=typeof n)throw TypeError("The listener must be a function");var l=new i(n,o||e,a),u=r?r+t:t;return e._events[u]?e._events[u].fn?e._events[u]=[e._events[u],l]:e._events[u].push(l):(e._events[u]=l,e._eventsCount++),e}function a(e,t){0==--e._eventsCount?e._events=new n:delete e._events[t]}function l(){this._events=new n,this._eventsCount=0}Object.create&&(n.prototype=Object.create(null),new n().__proto__||(r=!1)),l.prototype.eventNames=function(){var e,n,i=[];if(0===this._eventsCount)return i;for(n in e=this._events)t.call(e,n)&&i.push(r?n.slice(1):n);return Object.getOwnPropertySymbols?i.concat(Object.getOwnPropertySymbols(e)):i},l.prototype.listeners=function(e){var t=r?r+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var i=0,o=n.length,a=Array(o);i{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(668);t.toNumber=function(e){return n.isSymbol(e)?NaN:Number(e)}},2712:(e,t,r)=>{"use strict";r.d(t,{N:()=>i});var n=r(2115),i=globalThis?.document?n.useLayoutEffect:()=>{}},2721:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.argumentsTag="[object Arguments]",t.arrayBufferTag="[object ArrayBuffer]",t.arrayTag="[object Array]",t.bigInt64ArrayTag="[object BigInt64Array]",t.bigUint64ArrayTag="[object BigUint64Array]",t.booleanTag="[object Boolean]",t.dataViewTag="[object DataView]",t.dateTag="[object Date]",t.errorTag="[object Error]",t.float32ArrayTag="[object Float32Array]",t.float64ArrayTag="[object Float64Array]",t.functionTag="[object Function]",t.int16ArrayTag="[object Int16Array]",t.int32ArrayTag="[object Int32Array]",t.int8ArrayTag="[object Int8Array]",t.mapTag="[object Map]",t.numberTag="[object Number]",t.objectTag="[object Object]",t.regexpTag="[object RegExp]",t.setTag="[object Set]",t.stringTag="[object String]",t.symbolTag="[object Symbol]",t.uint16ArrayTag="[object Uint16Array]",t.uint32ArrayTag="[object Uint32Array]",t.uint8ArrayTag="[object Uint8Array]",t.uint8ClampedArrayTag="[object Uint8ClampedArray]"},2744:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8132),i=r(2384),o=r(6633),a=r(3616);function l(e,t,r,n){if(t===e)return!0;switch(typeof t){case"object":return function(e,t,r,n){if(null==t)return!0;if(Array.isArray(t))return u(e,t,r,n);if(t instanceof Map){var i=e,a=t,l=r,c=n;if(0===a.size)return!0;if(!(i instanceof Map))return!1;for(let[e,t]of a.entries())if(!1===l(i.get(e),t,e,i,a,c))return!1;return!0}if(t instanceof Set)return s(e,t,r,n);let f=Object.keys(t);if(null==e)return 0===f.length;if(0===f.length)return!0;if(n&&n.has(t))return n.get(t)===e;n&&n.set(t,e);try{for(let i=0;i0)return l(e,{...t},r,n);return a.eq(e,t);default:if(!i.isObject(e))return a.eq(e,t);if("string"==typeof t)return""===t;return!0}}function u(e,t,r,n){if(0===t.length)return!0;if(!Array.isArray(e))return!1;let i=new Set;for(let o=0;o{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevrons-right",[["path",{d:"m6 17 5-5-5-5",key:"xnjwq"}],["path",{d:"m13 17 5-5-5-5",key:"17xmmf"}]])},2790:(e,t,r)=>{"use strict";r.d(t,{u:()=>u});var n=r(2115),i=r(2596),o=r(788),a=["children","width","height","viewBox","className","style","title","desc"];function l(){return(l=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:r,width:u,height:s,viewBox:c,className:f,style:d,title:p,desc:h}=e,g=function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8673);t.throttle=function(e,t=0,r={}){let{leading:i=!0,trailing:o=!0}=r;return n.debounce(e,t,{leading:i,maxWait:t,trailing:o})}},3052:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevron-right",[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]])},3205:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(4545),i=r(8412),o=r(177),a=r(4072);t.has=function(e,t){let r;if(0===(r=Array.isArray(t)?t:"string"==typeof t&&n.isDeepKey(t)&&e?.[t]==null?a.toPath(t):[t]).length)return!1;let l=e;for(let e=0;e{"use strict";function n(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function i(e,t){var r=function(e){for(var t=1;t(void 0===e[r]&&void 0!==t[r]&&(e[r]=t[r]),e),r)}r.d(t,{e:()=>i})},3406:(e,t,r)=>{"use strict";r.d(t,{d:()=>et});var n=r(2115),i=r(675),o=r(6377),a=r(788),l=r(9827),u=r(6605),s=r(1643);class c{static create(e){return new c(e)}get domain(){return this.scale.domain}get range(){return this.scale.range}get rangeMin(){return this.range()[0]}get rangeMax(){return this.range()[1]}get bandwidth(){return this.scale.bandwidth}apply(e){var{bandAware:t,position:r}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(void 0!==e){if(r)switch(r){case"start":default:return this.scale(e);case"middle":var n=this.bandwidth?this.bandwidth()/2:0;return this.scale(e)+n;case"end":var i=this.bandwidth?this.bandwidth():0;return this.scale(e)+i}if(t){var o=this.bandwidth?this.bandwidth()/2:0;return this.scale(e)+o}return this.scale(e)}}isInRange(e){var t=this.range(),r=t[0],n=t[t.length-1];return r<=n?e>=r&&e<=n:e>=n&&e<=r}constructor(e){this.scale=e}}!function(e,t,r){var n;(t="symbol"==typeof(n=function(e,t){if("object"!=typeof e||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var n=r.call(e,t||"default");if("object"!=typeof n)return n;throw TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(t,"string"))?n:n+"")in e?Object.defineProperty(e,t,{value:1e-4,enumerable:!0,configurable:!0,writable:!0}):e[t]=1e-4}(c,"EPS",1e-4);var f=function(e){var{width:t,height:r}=e,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,i=(n%180+180)%180*Math.PI/180,o=Math.atan(r/t);return Math.abs(i>o&&ie*i)return!1;var o=r();return e*(t-e*o/2-n)>=0&&e*(t+e*o/2-i)<=0}function h(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function g(e){for(var t=1;t{var i,o="function"==typeof m?m(e.value,n):e.value;return"width"===w?(i=(0,u.P)(o,{fontSize:t,letterSpacing:r}),f({width:i.width+x.width,height:i.height+x.height},b)):(0,u.P)(o,{fontSize:t,letterSpacing:r})[w]},O=a.length>=2?(0,o.sA)(a[1].coordinate-a[0].coordinate):1,E=function(e,t,r){var n="width"===r,{x:i,y:o,width:a,height:l}=e;return 1===t?{start:n?i:o,end:n?i+a:o+l}:{start:n?i+a:o+l,end:n?i:o}}(l,O,w);return"equidistantPreserveStart"===v?function(e,t,r,n,i){for(var o,a=(n||[]).slice(),{start:l,end:u}=t,s=0,c=1,f=l;c<=a.length;)if(o=function(){var t,o=null==n?void 0:n[s];if(void 0===o)return{v:d(n,c)};var a=s,h=()=>(void 0===t&&(t=r(o,a)),t),g=o.coordinate,v=0===s||p(e,g,h,f,u);v||(s=0,f=l,c+=1),v&&(f=g+e*(h()/2+i),s+=c)}())return o.v;return[]}(O,E,S,a,c):("preserveStart"===v||"preserveStartEnd"===v?function(e,t,r,n,i,o){var a=(n||[]).slice(),l=a.length,{start:u,end:s}=t;if(o){var c=n[l-1],f=r(c,l-1),d=e*(c.coordinate+e*f/2-s);a[l-1]=c=g(g({},c),{},{tickCoord:d>0?c.coordinate-d*e:c.coordinate}),p(e,c.tickCoord,()=>f,u,s)&&(s=c.tickCoord-e*(f/2+i),a[l-1]=g(g({},c),{},{isShow:!0}))}for(var h=o?l-1:l,v=function(t){var n,o=a[t],l=()=>(void 0===n&&(n=r(o,t)),n);if(0===t){var c=e*(o.coordinate-e*l()/2-u);a[t]=o=g(g({},o),{},{tickCoord:c<0?o.coordinate-c*e:o.coordinate})}else a[t]=o=g(g({},o),{},{tickCoord:o.coordinate});p(e,o.tickCoord,l,u,s)&&(u=o.tickCoord+e*(l()/2+i),a[t]=g(g({},o),{},{isShow:!0}))},m=0;m(void 0===n&&(n=r(s,t)),n);if(t===a-1){var f=e*(s.coordinate+e*c()/2-u);o[t]=s=g(g({},s),{},{tickCoord:f>0?s.coordinate-f*e:s.coordinate})}else o[t]=s=g(g({},s),{},{tickCoord:s.coordinate});p(e,s.tickCoord,c,l,u)&&(u=s.tickCoord-e*(c()/2+i),o[t]=g(g({},s),{},{isShow:!0}))},c=a-1;c>=0;c--)s(c);return o}(O,E,S,a,c)).filter(e=>e.isShow)}var m=r(5672),y=r.n(m),b=r(2596);function w(e,t){for(var r in e)if(({}).hasOwnProperty.call(e,r)&&(!({}).hasOwnProperty.call(t,r)||e[r]!==t[r]))return!1;for(var n in t)if(({}).hasOwnProperty.call(t,n)&&!({}).hasOwnProperty.call(e,n))return!1;return!0}var x=r(2348),S=r(9095),O=r(379),E=r(3597),C=["viewBox"],M=["viewBox"];function k(){return(k=Object.assign?Object.assign.bind():function(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:[],{tickLine:i,stroke:o,tick:l,tickFormatter:u,unit:s}=this.props,c=v(P(P({},this.props),{},{ticks:r}),e,t),f=this.getTickTextAnchor(),d=this.getTickVerticalAnchor(),p=(0,a.J9)(this.props,!1),h=(0,a.J9)(l,!1),g=P(P({},p),{},{fill:"none"},(0,a.J9)(i,!1)),m=c.map((e,t)=>{var{line:r,tick:a}=this.getTickLineCoord(e),v=P(P(P(P({textAnchor:f,verticalAnchor:d},p),{},{stroke:"none",fill:o},h),a),{},{index:t,payload:e,visibleTicksCount:c.length,tickFormatter:u});return n.createElement(x.W,k({className:"recharts-cartesian-axis-tick",key:"tick-".concat(e.value,"-").concat(e.coordinate,"-").concat(e.tickCoord)},(0,E.XC)(this.props,e,t)),i&&n.createElement("line",k({},g,r,{className:(0,b.$)("recharts-cartesian-axis-tick-line",y()(i,"className"))})),l&&T.renderTickItem(l,v,"".concat("function"==typeof u?u(e.value,t):e.value).concat(s||"")))});return m.length>0?n.createElement("g",{className:"recharts-cartesian-axis-ticks"},m):null}render(){var{axisLine:e,width:t,height:r,className:i,hide:o}=this.props;if(o)return null;var{ticks:a}=this.props;return null!=t&&t<=0||null!=r&&r<=0?null:n.createElement(x.W,{className:(0,b.$)("recharts-cartesian-axis",i),ref:e=>{if(e){var t=e.getElementsByClassName("recharts-cartesian-axis-tick-value");this.tickRefs.current=Array.from(t);var r=t[0];if(r){var n=window.getComputedStyle(r).fontSize,i=window.getComputedStyle(r).letterSpacing;(n!==this.state.fontSize||i!==this.state.letterSpacing)&&this.setState({fontSize:window.getComputedStyle(r).fontSize,letterSpacing:window.getComputedStyle(r).letterSpacing})}}}},e&&this.renderAxisLine(),this.renderTicks(this.state.fontSize,this.state.letterSpacing,a),O.J.renderCallByParent(this.props))}constructor(e){super(e),this.tickRefs=n.createRef(),this.tickRefs.current=[],this.state={fontSize:"",letterSpacing:""}}}j(T,"displayName","CartesianAxis"),j(T,"defaultProps",{x:0,y:0,width:0,height:0,viewBox:{x:0,y:0,width:0,height:0},orientation:"bottom",ticks:[],stroke:"#666",tickLine:!0,axisLine:!0,tick:!0,mirror:!1,minTickGap:5,tickSize:6,tickMargin:2,interval:"preserveEnd"});var R=r(7238),z=r(2183),D=r(1971),I=r(1807),N=r(3389),L=["x1","y1","x2","y2","key"],F=["offset"],$=["xAxisId","yAxisId"],B=["xAxisId","yAxisId"];function V(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function U(e){for(var t=1;t{var{fill:t}=e;if(!t||"none"===t)return null;var{fillOpacity:r,x:i,y:o,width:a,height:l,ry:u}=e;return n.createElement("rect",{x:i,y:o,ry:u,width:a,height:l,stroke:"none",fill:t,fillOpacity:r,className:"recharts-cartesian-grid-bg"})};function W(e,t){var r;if(n.isValidElement(e))r=n.cloneElement(e,t);else if("function"==typeof e)r=e(t);else{var{x1:i,y1:o,x2:l,y2:u,key:s}=t,c=G(t,L),f=(0,a.J9)(c,!1),{offset:d}=f,p=G(f,F);r=n.createElement("line",H({},p,{x1:i,y1:o,x2:l,y2:u,fill:"none",key:s}))}return r}function K(e){var{x:t,width:r,horizontal:i=!0,horizontalPoints:o}=e;if(!i||!o||!o.length)return null;var{xAxisId:a,yAxisId:l}=e,u=G(e,$),s=o.map((e,n)=>W(i,U(U({},u),{},{x1:t,y1:e,x2:t+r,y2:e,key:"line-".concat(n),index:n})));return n.createElement("g",{className:"recharts-cartesian-grid-horizontal"},s)}function q(e){var{y:t,height:r,vertical:i=!0,verticalPoints:o}=e;if(!i||!o||!o.length)return null;var{xAxisId:a,yAxisId:l}=e,u=G(e,B),s=o.map((e,n)=>W(i,U(U({},u),{},{x1:e,y1:t,x2:e,y2:t+r,key:"line-".concat(n),index:n})));return n.createElement("g",{className:"recharts-cartesian-grid-vertical"},s)}function Y(e){var{horizontalFill:t,fillOpacity:r,x:i,y:o,width:a,height:l,horizontalPoints:u,horizontal:s=!0}=e;if(!s||!t||!t.length)return null;var c=u.map(e=>Math.round(e+o-o)).sort((e,t)=>e-t);o!==c[0]&&c.unshift(0);var f=c.map((e,u)=>{var s=c[u+1]?c[u+1]-e:o+l-e;if(s<=0)return null;var f=u%t.length;return n.createElement("rect",{key:"react-".concat(u),y:e,x:i,height:s,width:a,stroke:"none",fill:t[f],fillOpacity:r,className:"recharts-cartesian-grid-bg"})});return n.createElement("g",{className:"recharts-cartesian-gridstripes-horizontal"},f)}function X(e){var{vertical:t=!0,verticalFill:r,fillOpacity:i,x:o,y:a,width:l,height:u,verticalPoints:s}=e;if(!t||!r||!r.length)return null;var c=s.map(e=>Math.round(e+o-o)).sort((e,t)=>e-t);o!==c[0]&&c.unshift(0);var f=c.map((e,t)=>{var s=c[t+1]?c[t+1]-e:o+l-e;if(s<=0)return null;var f=t%r.length;return n.createElement("rect",{key:"react-".concat(t),x:e,y:a,width:s,height:u,stroke:"none",fill:r[f],fillOpacity:i,className:"recharts-cartesian-grid-bg"})});return n.createElement("g",{className:"recharts-cartesian-gridstripes-vertical"},f)}var J=(e,t)=>{var{xAxis:r,width:n,height:i,offset:o}=e;return(0,l.PW)(v(U(U(U({},T.defaultProps),r),{},{ticks:(0,l.Rh)(r,!0),viewBox:{x:0,y:0,width:n,height:i}})),o.left,o.left+o.width,t)},Q=(e,t)=>{var{yAxis:r,width:n,height:i,offset:o}=e;return(0,l.PW)(v(U(U(U({},T.defaultProps),r),{},{ticks:(0,l.Rh)(r,!0),viewBox:{x:0,y:0,width:n,height:i}})),o.top,o.top+o.height,t)},ee={horizontal:!0,vertical:!0,horizontalPoints:[],verticalPoints:[],stroke:"#ccc",fill:"none",verticalFill:[],horizontalFill:[],xAxisId:0,yAxisId:0};function et(e){var t=(0,R.yi)(),r=(0,R.rY)(),a=(0,R.W7)(),l=U(U({},(0,N.e)(e,ee)),{},{x:(0,o.Et)(e.x)?e.x:a.left,y:(0,o.Et)(e.y)?e.y:a.top,width:(0,o.Et)(e.width)?e.width:a.width,height:(0,o.Et)(e.height)?e.height:a.height}),{xAxisId:u,yAxisId:s,x:c,y:f,width:d,height:p,syncWithTicks:h,horizontalValues:g,verticalValues:v}=l,m=(0,I.r)(),y=(0,D.G)(e=>(0,z.ZB)(e,"xAxis",u,m)),b=(0,D.G)(e=>(0,z.ZB)(e,"yAxis",s,m));if(!(0,o.Et)(d)||d<=0||!(0,o.Et)(p)||p<=0||!(0,o.Et)(c)||c!==+c||!(0,o.Et)(f)||f!==+f)return null;var w=l.verticalCoordinatesGenerator||J,x=l.horizontalCoordinatesGenerator||Q,{horizontalPoints:S,verticalPoints:O}=l;if((!S||!S.length)&&"function"==typeof x){var E=g&&g.length,C=x({yAxis:b?U(U({},b),{},{ticks:E?g:b.ticks}):void 0,width:t,height:r,offset:a},!!E||h);(0,i.R)(Array.isArray(C),"horizontalCoordinatesGenerator should return Array but instead it returned [".concat(typeof C,"]")),Array.isArray(C)&&(S=C)}if((!O||!O.length)&&"function"==typeof w){var M=v&&v.length,k=w({xAxis:y?U(U({},y),{},{ticks:M?v:y.ticks}):void 0,width:t,height:r,offset:a},!!M||h);(0,i.R)(Array.isArray(k),"verticalCoordinatesGenerator should return Array but instead it returned [".concat(typeof k,"]")),Array.isArray(k)&&(O=k)}return n.createElement("g",{className:"recharts-cartesian-grid"},n.createElement(Z,{fill:l.fill,fillOpacity:l.fillOpacity,x:l.x,y:l.y,width:l.width,height:l.height,ry:l.ry}),n.createElement(Y,H({},l,{horizontalPoints:S})),n.createElement(X,H({},l,{verticalPoints:O})),n.createElement(K,H({},l,{offset:a,horizontalPoints:S,xAxis:y,yAxis:b})),n.createElement(q,H({},l,{offset:a,verticalPoints:O,xAxis:y,yAxis:b})))}et.displayName="CartesianGrid"},3540:(e,t,r)=>{"use strict";r.d(t,{u:()=>f});var n=r(2596),i=r(2115),o=r(400),a=r.n(o),l=r(6377),u=r(675);function s(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function c(e){for(var t=1;t{var{aspect:r,initialDimension:o={width:-1,height:-1},width:s="100%",height:f="100%",minWidth:d=0,minHeight:p,maxHeight:h,children:g,debounce:v=0,id:m,className:y,onResize:b,style:w={}}=e,x=(0,i.useRef)(null),S=(0,i.useRef)();S.current=b,(0,i.useImperativeHandle)(t,()=>x.current);var[O,E]=(0,i.useState)({containerWidth:o.width,containerHeight:o.height}),C=(0,i.useCallback)((e,t)=>{E(r=>{var n=Math.round(e),i=Math.round(t);return r.containerWidth===n&&r.containerHeight===i?r:{containerWidth:n,containerHeight:i}})},[]);(0,i.useEffect)(()=>{var e=e=>{var t,{width:r,height:n}=e[0].contentRect;C(r,n),null==(t=S.current)||t.call(S,r,n)};v>0&&(e=a()(e,v,{trailing:!0,leading:!1}));var t=new ResizeObserver(e),{width:r,height:n}=x.current.getBoundingClientRect();return C(r,n),t.observe(x.current),()=>{t.disconnect()}},[C,v]);var M=(0,i.useMemo)(()=>{var{containerWidth:e,containerHeight:t}=O;if(e<0||t<0)return null;(0,u.R)((0,l._3)(s)||(0,l._3)(f),"The width(%s) and height(%s) are both fixed numbers,\n maybe you don't need to use a ResponsiveContainer.",s,f),(0,u.R)(!r||r>0,"The aspect(%s) must be greater than zero.",r);var n=(0,l._3)(s)?e:s,o=(0,l._3)(f)?t:f;return r&&r>0&&(n?o=n/r:o&&(n=o*r),h&&o>h&&(o=h)),(0,u.R)(n>0||o>0,"The width(%s) and height(%s) of chart should be greater than 0,\n please check the style of container, or the props width(%s) and height(%s),\n or add a minWidth(%s) or minHeight(%s) or use aspect(%s) to control the\n height and width.",n,o,s,f,d,p,r),i.Children.map(g,e=>(0,i.cloneElement)(e,{width:n,height:o,style:c({width:n,height:o},e.props.style)}))},[r,g,f,h,p,d,O,s]);return i.createElement("div",{id:m?"".concat(m):void 0,className:(0,n.$)("recharts-responsive-container",y),style:c(c({},w),{},{width:s,height:f,minWidth:d,minHeight:p,maxHeight:h}),ref:x},i.createElement("div",{style:{width:0,height:0,overflow:"visible"}},M))})},3597:(e,t,r)=>{"use strict";r.d(t,{QQ:()=>i,VU:()=>a,XC:()=>s,_U:()=>u,j2:()=>l});var n=r(2115),i=["aria-activedescendant","aria-atomic","aria-autocomplete","aria-busy","aria-checked","aria-colcount","aria-colindex","aria-colspan","aria-controls","aria-current","aria-describedby","aria-details","aria-disabled","aria-errormessage","aria-expanded","aria-flowto","aria-haspopup","aria-hidden","aria-invalid","aria-keyshortcuts","aria-label","aria-labelledby","aria-level","aria-live","aria-modal","aria-multiline","aria-multiselectable","aria-orientation","aria-owns","aria-placeholder","aria-posinset","aria-pressed","aria-readonly","aria-relevant","aria-required","aria-roledescription","aria-rowcount","aria-rowindex","aria-rowspan","aria-selected","aria-setsize","aria-sort","aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext","className","color","height","id","lang","max","media","method","min","name","style","target","width","role","tabIndex","accentHeight","accumulate","additive","alignmentBaseline","allowReorder","alphabetic","amplitude","arabicForm","ascent","attributeName","attributeType","autoReverse","azimuth","baseFrequency","baselineShift","baseProfile","bbox","begin","bias","by","calcMode","capHeight","clip","clipPath","clipPathUnits","clipRule","colorInterpolation","colorInterpolationFilters","colorProfile","colorRendering","contentScriptType","contentStyleType","cursor","cx","cy","d","decelerate","descent","diffuseConstant","direction","display","divisor","dominantBaseline","dur","dx","dy","edgeMode","elevation","enableBackground","end","exponent","externalResourcesRequired","fill","fillOpacity","fillRule","filter","filterRes","filterUnits","floodColor","floodOpacity","focusable","fontFamily","fontSize","fontSizeAdjust","fontStretch","fontStyle","fontVariant","fontWeight","format","from","fx","fy","g1","g2","glyphName","glyphOrientationHorizontal","glyphOrientationVertical","glyphRef","gradientTransform","gradientUnits","hanging","horizAdvX","horizOriginX","href","ideographic","imageRendering","in2","in","intercept","k1","k2","k3","k4","k","kernelMatrix","kernelUnitLength","kerning","keyPoints","keySplines","keyTimes","lengthAdjust","letterSpacing","lightingColor","limitingConeAngle","local","markerEnd","markerHeight","markerMid","markerStart","markerUnits","markerWidth","mask","maskContentUnits","maskUnits","mathematical","mode","numOctaves","offset","opacity","operator","order","orient","orientation","origin","overflow","overlinePosition","overlineThickness","paintOrder","panose1","pathLength","patternContentUnits","patternTransform","patternUnits","pointerEvents","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","r","radius","refX","refY","renderingIntent","repeatCount","repeatDur","requiredExtensions","requiredFeatures","restart","result","rotate","rx","ry","seed","shapeRendering","slope","spacing","specularConstant","specularExponent","speed","spreadMethod","startOffset","stdDeviation","stemh","stemv","stitchTiles","stopColor","stopOpacity","strikethroughPosition","strikethroughThickness","string","stroke","strokeDasharray","strokeDashoffset","strokeLinecap","strokeLinejoin","strokeMiterlimit","strokeOpacity","strokeWidth","surfaceScale","systemLanguage","tableValues","targetX","targetY","textAnchor","textDecoration","textLength","textRendering","to","transform","u1","u2","underlinePosition","underlineThickness","unicode","unicodeBidi","unicodeRange","unitsPerEm","vAlphabetic","values","vectorEffect","version","vertAdvY","vertOriginX","vertOriginY","vHanging","vIdeographic","viewTarget","visibility","vMathematical","widths","wordSpacing","writingMode","x1","x2","x","xChannelSelector","xHeight","xlinkActuate","xlinkArcrole","xlinkHref","xlinkRole","xlinkShow","xlinkTitle","xlinkType","xmlBase","xmlLang","xmlns","xmlnsXlink","xmlSpace","y1","y2","y","yChannelSelector","z","zoomAndPan","ref","key","angle"],o=["points","pathLength"],a={svg:["viewBox","children"],polygon:o,polyline:o},l=["dangerouslySetInnerHTML","onCopy","onCopyCapture","onCut","onCutCapture","onPaste","onPasteCapture","onCompositionEnd","onCompositionEndCapture","onCompositionStart","onCompositionStartCapture","onCompositionUpdate","onCompositionUpdateCapture","onFocus","onFocusCapture","onBlur","onBlurCapture","onChange","onChangeCapture","onBeforeInput","onBeforeInputCapture","onInput","onInputCapture","onReset","onResetCapture","onSubmit","onSubmitCapture","onInvalid","onInvalidCapture","onLoad","onLoadCapture","onError","onErrorCapture","onKeyDown","onKeyDownCapture","onKeyPress","onKeyPressCapture","onKeyUp","onKeyUpCapture","onAbort","onAbortCapture","onCanPlay","onCanPlayCapture","onCanPlayThrough","onCanPlayThroughCapture","onDurationChange","onDurationChangeCapture","onEmptied","onEmptiedCapture","onEncrypted","onEncryptedCapture","onEnded","onEndedCapture","onLoadedData","onLoadedDataCapture","onLoadedMetadata","onLoadedMetadataCapture","onLoadStart","onLoadStartCapture","onPause","onPauseCapture","onPlay","onPlayCapture","onPlaying","onPlayingCapture","onProgress","onProgressCapture","onRateChange","onRateChangeCapture","onSeeked","onSeekedCapture","onSeeking","onSeekingCapture","onStalled","onStalledCapture","onSuspend","onSuspendCapture","onTimeUpdate","onTimeUpdateCapture","onVolumeChange","onVolumeChangeCapture","onWaiting","onWaitingCapture","onAuxClick","onAuxClickCapture","onClick","onClickCapture","onContextMenu","onContextMenuCapture","onDoubleClick","onDoubleClickCapture","onDrag","onDragCapture","onDragEnd","onDragEndCapture","onDragEnter","onDragEnterCapture","onDragExit","onDragExitCapture","onDragLeave","onDragLeaveCapture","onDragOver","onDragOverCapture","onDragStart","onDragStartCapture","onDrop","onDropCapture","onMouseDown","onMouseDownCapture","onMouseEnter","onMouseLeave","onMouseMove","onMouseMoveCapture","onMouseOut","onMouseOutCapture","onMouseOver","onMouseOverCapture","onMouseUp","onMouseUpCapture","onSelect","onSelectCapture","onTouchCancel","onTouchCancelCapture","onTouchEnd","onTouchEndCapture","onTouchMove","onTouchMoveCapture","onTouchStart","onTouchStartCapture","onPointerDown","onPointerDownCapture","onPointerMove","onPointerMoveCapture","onPointerUp","onPointerUpCapture","onPointerCancel","onPointerCancelCapture","onPointerEnter","onPointerEnterCapture","onPointerLeave","onPointerLeaveCapture","onPointerOver","onPointerOverCapture","onPointerOut","onPointerOutCapture","onGotPointerCapture","onGotPointerCaptureCapture","onLostPointerCapture","onLostPointerCaptureCapture","onScroll","onScrollCapture","onWheel","onWheelCapture","onAnimationStart","onAnimationStartCapture","onAnimationEnd","onAnimationEndCapture","onAnimationIteration","onAnimationIterationCapture","onTransitionEnd","onTransitionEndCapture"],u=(e,t)=>{if(!e||"function"==typeof e||"boolean"==typeof e)return null;var r=e;if((0,n.isValidElement)(e)&&(r=e.props),"object"!=typeof r&&"function"!=typeof r)return null;var i={};return Object.keys(r).forEach(e=>{l.includes(e)&&(i[e]=t||(t=>r[e](r,t)))}),i},s=(e,t,r)=>{if(null===e||"object"!=typeof e&&"function"!=typeof e)return null;var n=null;return Object.keys(e).forEach(i=>{var o=e[i];l.includes(i)&&"function"==typeof o&&(n||(n={}),n[i]=e=>(o(t,r,e),null))}),n}},3616:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.eq=function(e,t){return e===t||Number.isNaN(e)&&Number.isNaN(t)}},3655:(e,t,r)=>{"use strict";r.d(t,{hO:()=>u,sG:()=>l});var n=r(2115),i=r(7650),o=r(9708),a=r(5155),l=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"].reduce((e,t)=>{let r=(0,o.TL)(`Primitive.${t}`),i=n.forwardRef((e,n)=>{let{asChild:i,...o}=e;return"undefined"!=typeof window&&(window[Symbol.for("radix-ui")]=!0),(0,a.jsx)(i?r:t,{...o,ref:n})});return i.displayName=`Primitive.${t}`,{...e,[t]:i}},{});function u(e,t){e&&i.flushSync(()=>e.dispatchEvent(t))}},3676:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.uniqBy=function(e,t){let r=new Map;for(let n=0;n{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("external-link",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]])},3949:(e,t,r)=>{e.exports=r(9901).range},4013:(e,t,r)=>{"use strict";r.d(t,{N:()=>l});var n=r(6377),i=r(9827);function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function a(e){for(var t=1;t{if(null!=t&&null!=u){var{chartData:c,computedData:f,dataStartIndex:d,dataEndIndex:p}=r;return e.reduce((e,r)=>{var h,g,v,m,y,{dataDefinedOnItem:b,settings:w}=r,x=function(e,t,r){return Array.isArray(e)&&e&&t+r!==0?e.slice(t,r+1):e}((h=b,g=c,null!=h?h:g),d,p),S=null!=(v=null==w?void 0:w.dataKey)?v:null==o?void 0:o.dataKey,O=null==w?void 0:w.nameKey;return Array.isArray(m=null!=o&&o.dataKey&&Array.isArray(x)&&!Array.isArray(x[0])&&"axis"===s?(0,n.eP)(x,o.dataKey,l):u(x,t,f,O))?m.forEach(t=>{var r=a(a({},w),{},{name:t.name,unit:t.unit,color:void 0,fill:void 0});e.push((0,i.GF)({tooltipEntrySettings:r,dataKey:t.dataKey,payload:t.payload,value:(0,i.kr)(t.payload,t.dataKey),name:t.name}))}):e.push((0,i.GF)({tooltipEntrySettings:w,dataKey:S,payload:m,value:(0,i.kr)(m,S),name:null!=(y=(0,i.kr)(m,O))?y:null==w?void 0:w.name})),e},[])}}},4072:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.toPath=function(e){let t=[],r=e.length;if(0===r)return t;let n=0,i="",o="",a=!1;for(46===e.charCodeAt(0)&&(t.push(""),n++);n{"use strict";var n=r(9641).Buffer;Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let i=r(8221),o=r(5160),a=r(2721),l=r(6633),u=r(885);function s(e,t,r,i=new Map,f){let d=f?.(e,t,r,i);if(null!=d)return d;if(l.isPrimitive(e))return e;if(i.has(e))return i.get(e);if(Array.isArray(e)){let t=Array(e.length);i.set(e,t);for(let n=0;n{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("clock",[["path",{d:"M12 6v6l4 2",key:"mmk7yg"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]])},4315:(e,t,r)=>{"use strict";r.d(t,{jH:()=>o});var n=r(2115);r(5155);var i=n.createContext(void 0);function o(e){let t=n.useContext(i);return e||t||"ltr"}},4373:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8412),i=r(8179),o=r(2384),a=r(3616);t.isIterateeCall=function(e,t,r){return!!o.isObject(r)&&(!!("number"==typeof t&&i.isArrayLike(r)&&n.isIndex(t))&&t{"use strict";r.d(t,{F0:()=>n,tQ:()=>o,um:()=>i});var n="data-recharts-item-index",i="data-recharts-item-data-key",o=60},4460:(e,t,r)=>{"use strict";r.d(t,{i:()=>k});var n=r(2115),i=r(2188),o=r.n(i),a=(e,t)=>[0,3*e,3*t-6*e,3*e-3*t+1],l=(e,t)=>e.map((e,r)=>e*t**r).reduce((e,t)=>e+t),u=(e,t)=>r=>l(a(e,t),r),s=function(){let e,t;for(var r,n,i,o,s=arguments.length,c=Array(s),f=0;fparseFloat(e)))}else 4===c.length&&([r,i,n,o]=c);var p=u(r,n),h=u(i,o),g=(e=r,t=n,r=>l([...a(e,t).map((e,t)=>e*t).slice(1),0],r)),v=e=>e>1?1:e<0?0:e,m=e=>{for(var t=e>1?1:e,r=t,n=0;n<8;++n){var i=p(r)-t,o=g(r);if(1e-4>Math.abs(i-t)||o<1e-4)break;r=v(r-i/o)}return h(r)};return m.isStepper=!1,m},c=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},{stiff:t=100,damping:r=8,dt:n=17}=e,i=(e,i,o)=>{var a=o+(-(e-i)*t-o*r)*n/1e3,l=o*n/1e3+e;return 1e-4>Math.abs(l-i)&&1e-4>Math.abs(a)?[i,0]:[l,a]};return i.isStepper=!0,i.dt=n,i};function f(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function d(e){for(var t=1;tObject.keys(t).reduce((r,n)=>d(d({},r),{},{[n]:e(n,t[n])}),{});function h(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function g(e){for(var t=1;te+(t-e)*r,m=e=>{var{from:t,to:r}=e;return t!==r},y=(e,t,r)=>{var n=p((t,r)=>{if(m(r)){var[n,i]=e(r.from,r.to,r.velocity);return g(g({},r),{},{from:n,velocity:i})}return r},t);return r<1?p((e,t)=>m(t)?g(g({},t),{},{velocity:v(t.velocity,n[e].velocity,r),from:v(t.from,n[e].from,r)}):t,t):y(e,n,r-1)};class b{setTimeout(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,r=performance.now(),n=null,i=o=>{o-r>=t?e(o):"function"==typeof requestAnimationFrame&&(n=requestAnimationFrame(i))};return n=requestAnimationFrame(i),()=>{cancelAnimationFrame(n)}}}var w=["children","begin","duration","attributeName","easing","isActive","from","to","canBegin","onAnimationEnd","shouldReAnimate","onAnimationReStart","animationManager"];function x(){return(x=Object.assign?Object.assign.bind():function(e){for(var t=1;t{if("string"==typeof e)switch(e){case"ease":case"ease-in-out":case"ease-out":case"ease-in":case"linear":return s(e);case"spring":return c();default:if("cubic-bezier"===e.split("(")[0])return s(e)}return"function"==typeof e?e:null})(I),A=this.changeStyle,j=this.manager.getTimeoutController(),T=[Object.keys(R),Object.keys(z)].reduce((e,t)=>e.filter(e=>t.includes(e))),!0===P.isStepper?(t=R,r=z,n=P,i=T,o=A,a=j,u=i.reduce((e,n)=>g(g({},e),{},{[n]:{from:t[n],velocity:0,to:r[n]}}),{}),f=null,d=e=>{l||(l=e);var i=(e-l)/n.dt;u=y(n,u,i),o(g(g(g({},t),r),p((e,t)=>t.from,u))),l=e,Object.values(u).filter(m).length&&(f=a.setTimeout(d))},()=>(f=a.setTimeout(d),()=>{f()})):(h=R,b=z,w=P,x=D,S=T,O=A,E=j,M=null,k=S.reduce((e,t)=>g(g({},e),{},{[t]:[h[t],b[t]]}),{}),_=e=>{C||(C=e);var t=(e-C)/x,r=p((e,r)=>v(...r,w(t)),k);if(O(g(g(g({},h),b),r)),t<1)M=E.setTimeout(_);else{var n=p((e,t)=>v(...t,w(1)),k);O(g(g(g({},h),b),n))}},()=>(M=E.setTimeout(_),()=>{M()})));this.manager.start([F,N,()=>{this.stopJSAnimation=$()},D,L])}runAnimation(e){let t;var{begin:r,duration:n,attributeName:i,to:o,easing:a,onAnimationStart:l,onAnimationEnd:u,children:s}=e;if(this.unSubscribe=this.manager.subscribe(this.handleStyleChange),"function"==typeof a||"function"==typeof s||"spring"===a)return void this.runJSAnimation(e);var c=i?{[i]:o}:o,f=(t=Object.keys(c),t.map(e=>"".concat(e.replace(/([A-Z])/g,e=>"-".concat(e.toLowerCase()))," ").concat(n,"ms ").concat(a)).join(","));this.manager.start([l,r,O(O({},c),{},{transition:f}),n,u])}render(){var e=this.props,{children:t,begin:r,duration:i,attributeName:o,easing:a,isActive:l,from:u,to:s,canBegin:c,onAnimationEnd:f,shouldReAnimate:d,onAnimationReStart:p,animationManager:h}=e,g=function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{var{style:t={},className:r}=e.props;return(0,n.cloneElement)(e,O(O({},g),{},{style:O(O({},t),m),className:r}))};return 1===v?y(n.Children.only(t)):n.createElement("div",null,n.Children.map(t,e=>y(e)))}constructor(e,t){super(e,t),E(this,"mounted",!1),E(this,"manager",null),E(this,"stopJSAnimation",null),E(this,"unSubscribe",null);var{isActive:r,attributeName:n,from:i,to:o,children:a,duration:l,animationManager:u}=this.props;if(this.manager=u,this.handleStyleChange=this.handleStyleChange.bind(this),this.changeStyle=this.changeStyle.bind(this),!r||l<=0){this.state={style:{}},"function"==typeof a&&(this.state={style:o});return}if(i){if("function"==typeof a){this.state={style:i};return}this.state={style:n?{[n]:i}:i}}else this.state={style:{}}}}E(C,"displayName","Animate"),E(C,"defaultProps",{begin:0,duration:1e3,attributeName:"",easing:"ease",isActive:!0,canBegin:!0,onAnimationEnd:()=>{},onAnimationStart:()=>{}});var M=(0,n.createContext)(null);function k(e){var t,r,i,o,a,l,u,s=(0,n.useContext)(M);return n.createElement(C,x({},e,{animationManager:null!=(l=null!=(u=e.animationManager)?u:s)?l:(t=new b,r=()=>null,i=!1,o=null,a=e=>{if(!i){if(Array.isArray(e)){if(!e.length)return;var[n,...l]=e;if("number"==typeof n){o=t.setTimeout(a.bind(null,l),n);return}a(n),o=t.setTimeout(a.bind(null,l));return}"object"==typeof e&&r(e),"function"==typeof e&&e()}},{stop:()=>{i=!0},start:e=>{i=!1,o&&(o(),o=null),a(e)},subscribe:e=>(r=e,()=>{r=()=>null}),getTimeoutController:()=>t})}))}},4487:(e,t,r)=>{"use strict";r.d(t,{LV:()=>l,M:()=>o,hq:()=>i});var n=(0,r(5710).Z0)({name:"chartData",initialState:{chartData:void 0,computedData:void 0,dataStartIndex:0,dataEndIndex:0},reducers:{setChartData(e,t){if(e.chartData=t.payload,null==t.payload){e.dataStartIndex=0,e.dataEndIndex=0;return}t.payload.length>0&&e.dataEndIndex!==t.payload.length-1&&(e.dataEndIndex=t.payload.length-1)},setComputedData(e,t){e.computedData=t.payload},setDataStartEndIndexes(e,t){var{startIndex:r,endIndex:n}=t.payload;null!=r&&(e.dataStartIndex=r),null!=n&&(e.dataEndIndex=n)}}}),{setChartData:i,setDataStartEndIndexes:o,setComputedData:a}=n.actions,l=n.reducer},4517:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8132),i=r(6200),o=r(7298),a=r(921),l=r(3205);t.matchesProperty=function(e,t){switch(typeof e){case"object":Object.is(e?.valueOf(),-0)&&(e="-0");break;case"number":e=i.toKey(e)}return t=o.cloneDeep(t),function(r){let i=a.get(r,e);return void 0===i?l.has(r,e):void 0===t?void 0===i:n.isMatch(i,t)}}},4532:(e,t,r)=>{"use strict";r.d(t,{Qx:()=>s,a6:()=>c,h4:()=>G,jM:()=>H,ss:()=>V});var n,i=Symbol.for("immer-nothing"),o=Symbol.for("immer-draftable"),a=Symbol.for("immer-state");function l(e,...t){throw Error(`[Immer] minified error nr: ${e}. Full error at: https://bit.ly/3cXEKWf`)}var u=Object.getPrototypeOf;function s(e){return!!e&&!!e[a]}function c(e){return!!e&&(d(e)||Array.isArray(e)||!!e[o]||!!e.constructor?.[o]||m(e)||y(e))}var f=Object.prototype.constructor.toString();function d(e){if(!e||"object"!=typeof e)return!1;let t=u(e);if(null===t)return!0;let r=Object.hasOwnProperty.call(t,"constructor")&&t.constructor;return r===Object||"function"==typeof r&&Function.toString.call(r)===f}function p(e,t){0===h(e)?Reflect.ownKeys(e).forEach(r=>{t(r,e[r],e)}):e.forEach((r,n)=>t(n,r,e))}function h(e){let t=e[a];return t?t.type_:Array.isArray(e)?1:m(e)?2:3*!!y(e)}function g(e,t){return 2===h(e)?e.has(t):Object.prototype.hasOwnProperty.call(e,t)}function v(e,t,r){let n=h(e);2===n?e.set(t,r):3===n?e.add(r):e[t]=r}function m(e){return e instanceof Map}function y(e){return e instanceof Set}function b(e){return e.copy_||e.base_}function w(e,t){if(m(e))return new Map(e);if(y(e))return new Set(e);if(Array.isArray(e))return Array.prototype.slice.call(e);let r=d(e);if(!0!==t&&("class_only"!==t||r)){let t=u(e);return null!==t&&r?{...e}:Object.assign(Object.create(t),e)}{let t=Object.getOwnPropertyDescriptors(e);delete t[a];let r=Reflect.ownKeys(t);for(let n=0;n1&&(e.set=e.add=e.clear=e.delete=S),Object.freeze(e),t&&Object.entries(e).forEach(([e,t])=>x(t,!0))),e}function S(){l(2)}function O(e){return Object.isFrozen(e)}var E={};function C(e){let t=E[e];return t||l(0,e),t}function M(e,t){t&&(C("Patches"),e.patches_=[],e.inversePatches_=[],e.patchListener_=t)}function k(e){_(e),e.drafts_.forEach(A),e.drafts_=null}function _(e){e===n&&(n=e.parent_)}function P(e){return n={drafts_:[],parent_:n,immer_:e,canAutoFreeze_:!0,unfinalizedDrafts_:0}}function A(e){let t=e[a];0===t.type_||1===t.type_?t.revoke_():t.revoked_=!0}function j(e,t){t.unfinalizedDrafts_=t.drafts_.length;let r=t.drafts_[0];return void 0!==e&&e!==r?(r[a].modified_&&(k(t),l(4)),c(e)&&(e=T(t,e),t.parent_||z(t,e)),t.patches_&&C("Patches").generateReplacementPatches_(r[a].base_,e,t.patches_,t.inversePatches_)):e=T(t,r,[]),k(t),t.patches_&&t.patchListener_(t.patches_,t.inversePatches_),e!==i?e:void 0}function T(e,t,r){if(O(t))return t;let n=t[a];if(!n)return p(t,(i,o)=>R(e,n,t,i,o,r)),t;if(n.scope_!==e)return t;if(!n.modified_)return z(e,n.base_,!0),n.base_;if(!n.finalized_){n.finalized_=!0,n.scope_.unfinalizedDrafts_--;let t=n.copy_,i=t,o=!1;3===n.type_&&(i=new Set(t),t.clear(),o=!0),p(i,(i,a)=>R(e,n,t,i,a,r,o)),z(e,t,!1),r&&e.patches_&&C("Patches").generatePatches_(n,r,e.patches_,e.inversePatches_)}return n.copy_}function R(e,t,r,n,i,o,a){if(s(i)){let a=T(e,i,o&&t&&3!==t.type_&&!g(t.assigned_,n)?o.concat(n):void 0);if(v(r,n,a),!s(a))return;e.canAutoFreeze_=!1}else a&&r.add(i);if(c(i)&&!O(i)){if(!e.immer_.autoFreeze_&&e.unfinalizedDrafts_<1)return;T(e,i),(!t||!t.scope_.parent_)&&"symbol"!=typeof n&&Object.prototype.propertyIsEnumerable.call(r,n)&&z(e,i)}}function z(e,t,r=!1){!e.parent_&&e.immer_.autoFreeze_&&e.canAutoFreeze_&&x(t,r)}var D={get(e,t){if(t===a)return e;let r=b(e);if(!g(r,t)){var n=e,i=r,o=t;let a=L(i,o);return a?"value"in a?a.value:a.get?.call(n.draft_):void 0}let l=r[t];return e.finalized_||!c(l)?l:l===N(e.base_,t)?($(e),e.copy_[t]=B(l,e)):l},has:(e,t)=>t in b(e),ownKeys:e=>Reflect.ownKeys(b(e)),set(e,t,r){let n=L(b(e),t);if(n?.set)return n.set.call(e.draft_,r),!0;if(!e.modified_){let n=N(b(e),t),i=n?.[a];if(i&&i.base_===r)return e.copy_[t]=r,e.assigned_[t]=!1,!0;if((r===n?0!==r||1/r==1/n:r!=r&&n!=n)&&(void 0!==r||g(e.base_,t)))return!0;$(e),F(e)}return!!(e.copy_[t]===r&&(void 0!==r||t in e.copy_)||Number.isNaN(r)&&Number.isNaN(e.copy_[t]))||(e.copy_[t]=r,e.assigned_[t]=!0,!0)},deleteProperty:(e,t)=>(void 0!==N(e.base_,t)||t in e.base_?(e.assigned_[t]=!1,$(e),F(e)):delete e.assigned_[t],e.copy_&&delete e.copy_[t],!0),getOwnPropertyDescriptor(e,t){let r=b(e),n=Reflect.getOwnPropertyDescriptor(r,t);return n?{writable:!0,configurable:1!==e.type_||"length"!==t,enumerable:n.enumerable,value:r[t]}:n},defineProperty(){l(11)},getPrototypeOf:e=>u(e.base_),setPrototypeOf(){l(12)}},I={};function N(e,t){let r=e[a];return(r?b(r):e)[t]}function L(e,t){if(!(t in e))return;let r=u(e);for(;r;){let e=Object.getOwnPropertyDescriptor(r,t);if(e)return e;r=u(r)}}function F(e){!e.modified_&&(e.modified_=!0,e.parent_&&F(e.parent_))}function $(e){e.copy_||(e.copy_=w(e.base_,e.scope_.immer_.useStrictShallowCopy_))}function B(e,t){let r=m(e)?C("MapSet").proxyMap_(e,t):y(e)?C("MapSet").proxySet_(e,t):function(e,t){let r=Array.isArray(e),i={type_:+!!r,scope_:t?t.scope_:n,modified_:!1,finalized_:!1,assigned_:{},parent_:t,base_:e,draft_:null,copy_:null,revoke_:null,isManual_:!1},o=i,a=D;r&&(o=[i],a=I);let{revoke:l,proxy:u}=Proxy.revocable(o,a);return i.draft_=u,i.revoke_=l,u}(e,t);return(t?t.scope_:n).drafts_.push(r),r}function V(e){return s(e)||l(10,e),function e(t){let r;if(!c(t)||O(t))return t;let n=t[a];if(n){if(!n.modified_)return n.base_;n.finalized_=!0,r=w(t,n.scope_.immer_.useStrictShallowCopy_)}else r=w(t,!0);return p(r,(t,n)=>{v(r,t,e(n))}),n&&(n.finalized_=!1),r}(e)}p(D,(e,t)=>{I[e]=function(){return arguments[0]=arguments[0][0],t.apply(this,arguments)}}),I.deleteProperty=function(e,t){return I.set.call(this,e,t,void 0)},I.set=function(e,t,r){return D.set.call(this,e[0],t,r,e[0])};var U=new class{constructor(e){this.autoFreeze_=!0,this.useStrictShallowCopy_=!1,this.produce=(e,t,r)=>{let n;if("function"==typeof e&&"function"!=typeof t){let r=t;t=e;let n=this;return function(e=r,...i){return n.produce(e,e=>t.call(this,e,...i))}}if("function"!=typeof t&&l(6),void 0!==r&&"function"!=typeof r&&l(7),c(e)){let i=P(this),o=B(e,void 0),a=!0;try{n=t(o),a=!1}finally{a?k(i):_(i)}return M(i,r),j(n,i)}if(e&&"object"==typeof e)l(1,e);else{if(void 0===(n=t(e))&&(n=e),n===i&&(n=void 0),this.autoFreeze_&&x(n,!0),r){let t=[],i=[];C("Patches").generateReplacementPatches_(e,n,t,i),r(t,i)}return n}},this.produceWithPatches=(e,t)=>{let r,n;return"function"==typeof e?(t,...r)=>this.produceWithPatches(t,t=>e(t,...r)):[this.produce(e,t,(e,t)=>{r=e,n=t}),r,n]},"boolean"==typeof e?.autoFreeze&&this.setAutoFreeze(e.autoFreeze),"boolean"==typeof e?.useStrictShallowCopy&&this.setUseStrictShallowCopy(e.useStrictShallowCopy)}createDraft(e){c(e)||l(8),s(e)&&(e=V(e));let t=P(this),r=B(e,void 0);return r[a].isManual_=!0,_(t),r}finishDraft(e,t){let r=e&&e[a];r&&r.isManual_||l(9);let{scope_:n}=r;return M(n,t),j(void 0,n)}setAutoFreeze(e){this.autoFreeze_=e}setUseStrictShallowCopy(e){this.useStrictShallowCopy_=e}applyPatches(e,t){let r;for(r=t.length-1;r>=0;r--){let n=t[r];if(0===n.path.length&&"replace"===n.op){e=n.value;break}}r>-1&&(t=t.slice(r+1));let n=C("Patches").applyPatches_;return s(e)?n(e,t):this.produce(e,e=>n(e,t))}},H=U.produce;function G(e){return e}U.produceWithPatches.bind(U),U.setAutoFreeze.bind(U),U.setUseStrictShallowCopy.bind(U),U.applyPatches.bind(U),U.createDraft.bind(U),U.finishDraft.bind(U)},4545:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isDeepKey=function(e){switch(typeof e){case"number":case"symbol":return!1;case"string":return e.includes(".")||e.includes("[")||e.includes("]")}}},4664:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(2694);t.toFinite=function(e){return e?(e=n.toNumber(e))===1/0||e===-1/0?(e<0?-1:1)*Number.MAX_VALUE:e==e?e:0:0===e?e:0}},4732:(e,t,r)=>{"use strict";r.d(t,{BZ:()=>R,aX:()=>I,dS:()=>T,dp:()=>P,fW:()=>S,pg:()=>j,r1:()=>M,u9:()=>z,yn:()=>D});var n=r(8924),i=r(241),o=r.n(i),a=r(1971),l=r(9827),u=r(356),s=r(215),c=r(8478),f=r(7238),d=r(6124),p=r(2589),h=r(530),g=r(1928),v=r(841),m=r(4968),y=r(5146),b=r(6670),w=r(5714),x=r(4013),S=()=>(0,a.G)(c.iO),O=(e,t)=>t,E=(e,t,r)=>r,C=(e,t,r,n)=>n,M=(0,n.Mz)(s.R4,e=>o()(e,e=>e.coordinate)),k=(0,n.Mz)([w.J,O,E,C],g.i),_=(0,n.Mz)([k,s.n4],v.P),P=(e,t,r)=>{if(null!=t){var n=(0,w.J)(e);return"axis"===t?"hover"===r?n.axisInteraction.hover.dataKey:n.axisInteraction.click.dataKey:"hover"===r?n.itemInteraction.hover.dataKey:n.itemInteraction.click.dataKey}},A=(0,n.Mz)([w.J,O,E,C],y.q),j=(0,n.Mz)([p.Lp,p.A$,f.fz,d.HZ,s.R4,C,A,b.x],m.o),T=(0,n.Mz)([k,j],(e,t)=>{var r;return null!=(r=e.coordinate)?r:t}),R=(0,n.Mz)(s.R4,_,h.E),z=(0,n.Mz)([A,_,u.LF,s.Dn,R,b.x,O],x.N),D=(0,n.Mz)([k],e=>({isActive:e.active,activeIndex:e.index})),I=(e,t,r,n,i,o,a,u)=>{if(e&&t&&n&&i&&o){var s=(0,l.r4)(e.chartX,e.chartY,t,r,u);if(s){var c=(0,l.SW)(s,t),f=(0,l.gH)(c,a,o,n,i),d=(0,l.bk)(t,o,f,s);return{activeIndex:String(f),activeCoordinate:d}}}}},4804:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(8132),i=r(2429);t.matches=function(e){return e=i.cloneDeep(e),t=>n.isMatch(t,e)}},4861:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("circle-x",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]])},4890:(e,t,r)=>{"use strict";r.d(t,{E1:()=>v,En:()=>y,Ix:()=>l,Nt:()=>h,RD:()=>c,UF:()=>s,XB:()=>u,jF:()=>g,k_:()=>o,o4:()=>m,xS:()=>d});var n=r(5710),i=r(4532),o={active:!1,index:null,dataKey:void 0,coordinate:void 0},a=(0,n.Z0)({name:"tooltip",initialState:{itemInteraction:{click:o,hover:o},axisInteraction:{click:o,hover:o},keyboardInteraction:o,syncInteraction:{active:!1,index:null,dataKey:void 0,label:void 0,coordinate:void 0},tooltipItemPayloads:[],settings:{shared:void 0,trigger:"hover",axisId:0,active:!1,defaultIndex:void 0}},reducers:{addTooltipEntrySettings(e,t){e.tooltipItemPayloads.push((0,i.h4)(t.payload))},removeTooltipEntrySettings(e,t){var r=(0,i.ss)(e).tooltipItemPayloads.indexOf((0,i.h4)(t.payload));r>-1&&e.tooltipItemPayloads.splice(r,1)},setTooltipSettingsState(e,t){e.settings=t.payload},setActiveMouseOverItemIndex(e,t){e.syncInteraction.active=!1,e.keyboardInteraction.active=!1,e.itemInteraction.hover.active=!0,e.itemInteraction.hover.index=t.payload.activeIndex,e.itemInteraction.hover.dataKey=t.payload.activeDataKey,e.itemInteraction.hover.coordinate=t.payload.activeCoordinate},mouseLeaveChart(e){e.itemInteraction.hover.active=!1,e.axisInteraction.hover.active=!1},mouseLeaveItem(e){e.itemInteraction.hover.active=!1},setActiveClickItemIndex(e,t){e.syncInteraction.active=!1,e.itemInteraction.click.active=!0,e.keyboardInteraction.active=!1,e.itemInteraction.click.index=t.payload.activeIndex,e.itemInteraction.click.dataKey=t.payload.activeDataKey,e.itemInteraction.click.coordinate=t.payload.activeCoordinate},setMouseOverAxisIndex(e,t){e.syncInteraction.active=!1,e.axisInteraction.hover.active=!0,e.keyboardInteraction.active=!1,e.axisInteraction.hover.index=t.payload.activeIndex,e.axisInteraction.hover.dataKey=t.payload.activeDataKey,e.axisInteraction.hover.coordinate=t.payload.activeCoordinate},setMouseClickAxisIndex(e,t){e.syncInteraction.active=!1,e.keyboardInteraction.active=!1,e.axisInteraction.click.active=!0,e.axisInteraction.click.index=t.payload.activeIndex,e.axisInteraction.click.dataKey=t.payload.activeDataKey,e.axisInteraction.click.coordinate=t.payload.activeCoordinate},setSyncInteraction(e,t){e.syncInteraction=t.payload},setKeyboardInteraction(e,t){e.keyboardInteraction.active=t.payload.active,e.keyboardInteraction.index=t.payload.activeIndex,e.keyboardInteraction.coordinate=t.payload.activeCoordinate,e.keyboardInteraction.dataKey=t.payload.activeDataKey}}}),{addTooltipEntrySettings:l,removeTooltipEntrySettings:u,setTooltipSettingsState:s,setActiveMouseOverItemIndex:c,mouseLeaveItem:f,mouseLeaveChart:d,setActiveClickItemIndex:p,setMouseOverAxisIndex:h,setMouseClickAxisIndex:g,setSyncInteraction:v,setKeyboardInteraction:m}=a.actions,y=a.reducer},4968:(e,t,r)=>{"use strict";r.d(t,{o:()=>n});var n=(e,t,r,n,i,o,a,l)=>{if(null!=o&&null!=l){var u=a[0],s=null==u?void 0:l(u.positions,o);if(null!=s)return s;var c=null==i?void 0:i[Number(o)];if(c)if("horizontal"===r)return{x:c.coordinate,y:(n.top+t)/2};else return{x:(n.left+e)/2,y:c.coordinate}}}},4986:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.toArray=function(e){return Array.isArray(e)?e:Array.from(e)}},4993:(e,t,r)=>{"use strict";var n=r(2115);"function"==typeof Object.is&&Object.is,n.useSyncExternalStore,n.useRef,n.useEffect,n.useMemo,n.useDebugValue},5064:(e,t,r)=>{"use strict";r.d(t,{E:()=>n});var n=(0,r(2115).createContext)(null)},5115:(e,t,r)=>{"use strict";r.d(t,{$:()=>i,X:()=>o});var n=r(2115),i=(0,n.createContext)(null),o=()=>(0,n.useContext)(i)},5143:(e,t,r)=>{"use strict";r.d(t,{Mp:()=>eR,vL:()=>a,uN:()=>et,cA:()=>eu,IG:()=>ec,fp:()=>A,Sj:()=>D,fF:()=>eN,PM:()=>eI,zM:()=>eF,MS:()=>E,FR:()=>C});var n,i,o,a,l,u,s,c,f,d,p=r(2115),h=r(7650),g=r(8266);let v={display:"none"};function m(e){let{id:t,value:r}=e;return p.createElement("div",{id:t,style:v},r)}function y(e){let{id:t,announcement:r,ariaLiveType:n="assertive"}=e;return p.createElement("div",{id:t,style:{position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"},role:"status","aria-live":n,"aria-atomic":!0},r)}let b=(0,p.createContext)(null),w={draggable:"\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n "},x={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:r}=e;return r?"Draggable item "+t.id+" was moved over droppable area "+r.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:r}=e;return r?"Draggable item "+t.id+" was dropped over droppable area "+r.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function S(e){let{announcements:t=x,container:r,hiddenTextDescribedById:n,screenReaderInstructions:i=w}=e,{announce:o,announcement:a}=function(){let[e,t]=(0,p.useState)("");return{announce:(0,p.useCallback)(e=>{null!=e&&t(e)},[]),announcement:e}}(),l=(0,g.YG)("DndLiveRegion"),[u,s]=(0,p.useState)(!1);(0,p.useEffect)(()=>{s(!0)},[]);var c=(0,p.useMemo)(()=>({onDragStart(e){let{active:r}=e;o(t.onDragStart({active:r}))},onDragMove(e){let{active:r,over:n}=e;t.onDragMove&&o(t.onDragMove({active:r,over:n}))},onDragOver(e){let{active:r,over:n}=e;o(t.onDragOver({active:r,over:n}))},onDragEnd(e){let{active:r,over:n}=e;o(t.onDragEnd({active:r,over:n}))},onDragCancel(e){let{active:r,over:n}=e;o(t.onDragCancel({active:r,over:n}))}}),[o,t]);let f=(0,p.useContext)(b);if((0,p.useEffect)(()=>{if(!f)throw Error("useDndMonitor must be used within a children of ");return f(c)},[c,f]),!u)return null;let d=p.createElement(p.Fragment,null,p.createElement(m,{id:n,value:i.draggable}),p.createElement(y,{id:l,announcement:a}));return r?(0,h.createPortal)(d,r):d}function O(){}function E(e,t){return(0,p.useMemo)(()=>({sensor:e,options:null!=t?t:{}}),[e,t])}function C(){for(var e=arguments.length,t=Array(e),r=0;r[...t].filter(e=>null!=e),[...t])}!function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"}(n||(n={}));let M=Object.freeze({x:0,y:0});function k(e,t){let{data:{value:r}}=e,{data:{value:n}}=t;return r-n}function _(e,t){let{data:{value:r}}=e,{data:{value:n}}=t;return n-r}function P(e,t,r){return void 0===t&&(t=e.left),void 0===r&&(r=e.top),{x:t+.5*e.width,y:r+.5*e.height}}let A=e=>{let{collisionRect:t,droppableRects:r,droppableContainers:n}=e,i=P(t,t.left,t.top),o=[];for(let e of n){let{id:t}=e,n=r.get(t);if(n){let r=function(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}(P(n),i);o.push({id:t,data:{droppableContainer:e,value:r}})}}return o.sort(k)},j=e=>{let{collisionRect:t,droppableRects:r,droppableContainers:n}=e,i=[];for(let e of n){let{id:n}=e,o=r.get(n);if(o){let r=function(e,t){let r=Math.max(t.top,e.top),n=Math.max(t.left,e.left),i=Math.min(t.left+t.width,e.left+e.width),o=Math.min(t.top+t.height,e.top+e.height);if(n0&&i.push({id:n,data:{droppableContainer:e,value:r}})}}return i.sort(_)};function T(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:M}let R=function(e){return function(t){for(var r=arguments.length,n=Array(r>1?r-1:0),i=1;i({...t,top:t.top+e*r.y,bottom:t.bottom+e*r.y,left:t.left+e*r.x,right:t.right+e*r.x}),{...t})}}(1),z={ignoreTransform:!1};function D(e,t){void 0===t&&(t=z);let r=e.getBoundingClientRect();if(t.ignoreTransform){let{transform:t,transformOrigin:n}=(0,g.zk)(e).getComputedStyle(e);t&&(r=function(e,t,r){let n=function(e){if(e.startsWith("matrix3d(")){let t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}if(e.startsWith("matrix(")){let t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}(t);if(!n)return e;let{scaleX:i,scaleY:o,x:a,y:l}=n,u=e.left-a-(1-i)*parseFloat(r),s=e.top-l-(1-o)*parseFloat(r.slice(r.indexOf(" ")+1)),c=i?e.width/i:e.width,f=o?e.height/o:e.height;return{width:c,height:f,top:s,right:u+c,bottom:s+f,left:u}}(r,t,n))}let{top:n,left:i,width:o,height:a,bottom:l,right:u}=r;return{top:n,left:i,width:o,height:a,bottom:l,right:u}}function I(e){return D(e,{ignoreTransform:!0})}function N(e,t){let r=[];return e?function n(i){var o;if(null!=t&&r.length>=t||!i)return r;if((0,g.wz)(i)&&null!=i.scrollingElement&&!r.includes(i.scrollingElement))return r.push(i.scrollingElement),r;if(!(0,g.sb)(i)||(0,g.xZ)(i)||r.includes(i))return r;let a=(0,g.zk)(e).getComputedStyle(i);return(i!==e&&function(e,t){void 0===t&&(t=(0,g.zk)(e).getComputedStyle(e));let r=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(e=>{let n=t[e];return"string"==typeof n&&r.test(n)})}(i,a)&&r.push(i),void 0===(o=a)&&(o=(0,g.zk)(i).getComputedStyle(i)),"fixed"===o.position)?r:n(i.parentNode)}(e):r}function L(e){let[t]=N(e,1);return null!=t?t:null}function F(e){return g.Sw&&e?(0,g.l6)(e)?e:(0,g.Ll)(e)?(0,g.wz)(e)||e===(0,g.TW)(e).scrollingElement?window:(0,g.sb)(e)?e:null:null:null}function $(e){return(0,g.l6)(e)?e.scrollX:e.scrollLeft}function B(e){return(0,g.l6)(e)?e.scrollY:e.scrollTop}function V(e){return{x:$(e),y:B(e)}}function U(e){return!!g.Sw&&!!e&&e===document.scrollingElement}function H(e){let t={x:0,y:0},r=U(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},n={x:e.scrollWidth-r.width,y:e.scrollHeight-r.height},i=e.scrollTop<=t.y,o=e.scrollLeft<=t.x;return{isTop:i,isLeft:o,isBottom:e.scrollTop>=n.y,isRight:e.scrollLeft>=n.x,maxScroll:n,minScroll:t}}!function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"}(i||(i={}));let G={x:.2,y:.2};function Z(e){return e.reduce((e,t)=>(0,g.WQ)(e,V(t)),M)}let W=[["x",["left","right"],function(e){return e.reduce((e,t)=>e+$(t),0)}],["y",["top","bottom"],function(e){return e.reduce((e,t)=>e+B(t),0)}]];class K{constructor(e,t){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;let r=N(t),n=Z(r);for(let[t,i,o]of(this.rect={...e},this.width=e.width,this.height=e.height,W))for(let e of i)Object.defineProperty(this,e,{get:()=>{let i=o(r),a=n[t]-i;return this.rect[e]+a},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class q{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(e=>{var t;return null==(t=this.target)?void 0:t.removeEventListener(...e)})},this.target=e}add(e,t,r){var n;null==(n=this.target)||n.addEventListener(e,t,r),this.listeners.push([e,t,r])}}function Y(e,t){let r=Math.abs(e.x),n=Math.abs(e.y);return"number"==typeof t?Math.sqrt(r**2+n**2)>t:"x"in t&&"y"in t?r>t.x&&n>t.y:"x"in t?r>t.x:"y"in t&&n>t.y}function X(e){e.preventDefault()}function J(e){e.stopPropagation()}!function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"}(o||(o={})),function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"}(a||(a={}));let Q={start:[a.Space,a.Enter],cancel:[a.Esc],end:[a.Space,a.Enter,a.Tab]},ee=(e,t)=>{let{currentCoordinates:r}=t;switch(e.code){case a.Right:return{...r,x:r.x+25};case a.Left:return{...r,x:r.x-25};case a.Down:return{...r,y:r.y+25};case a.Up:return{...r,y:r.y-25}}};class et{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;let{event:{target:t}}=e;this.props=e,this.listeners=new q((0,g.TW)(t)),this.windowListeners=new q((0,g.zk)(t)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(o.Resize,this.handleCancel),this.windowListeners.add(o.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(o.Keydown,this.handleKeyDown))}handleStart(){let{activeNode:e,onStart:t}=this.props,r=e.node.current;r&&function(e,t){if(void 0===t&&(t=D),!e)return;let{top:r,left:n,bottom:i,right:o}=t(e);L(e)&&(i<=0||o<=0||r>=window.innerHeight||n>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}(r),t(M)}handleKeyDown(e){if((0,g.kx)(e)){let{active:t,context:r,options:n}=this.props,{keyboardCodes:i=Q,coordinateGetter:o=ee,scrollBehavior:l="smooth"}=n,{code:u}=e;if(i.end.includes(u))return void this.handleEnd(e);if(i.cancel.includes(u))return void this.handleCancel(e);let{collisionRect:s}=r.current,c=s?{x:s.left,y:s.top}:M;this.referenceCoordinates||(this.referenceCoordinates=c);let f=o(e,{active:t,context:r.current,currentCoordinates:c});if(f){let t=(0,g.Re)(f,c),n={x:0,y:0},{scrollableAncestors:i}=r.current;for(let r of i){let i=e.code,{isTop:o,isRight:u,isLeft:s,isBottom:c,maxScroll:d,minScroll:p}=H(r),h=function(e){if(e===document.scrollingElement){let{innerWidth:e,innerHeight:t}=window;return{top:0,left:0,right:e,bottom:t,width:e,height:t}}let{top:t,left:r,right:n,bottom:i}=e.getBoundingClientRect();return{top:t,left:r,right:n,bottom:i,width:e.clientWidth,height:e.clientHeight}}(r),g={x:Math.min(i===a.Right?h.right-h.width/2:h.right,Math.max(i===a.Right?h.left:h.left+h.width/2,f.x)),y:Math.min(i===a.Down?h.bottom-h.height/2:h.bottom,Math.max(i===a.Down?h.top:h.top+h.height/2,f.y))},v=i===a.Right&&!u||i===a.Left&&!s,m=i===a.Down&&!c||i===a.Up&&!o;if(v&&g.x!==f.x){let e=r.scrollLeft+t.x,o=i===a.Right&&e<=d.x||i===a.Left&&e>=p.x;if(o&&!t.y)return void r.scrollTo({left:e,behavior:l});o?n.x=r.scrollLeft-e:n.x=i===a.Right?r.scrollLeft-d.x:r.scrollLeft-p.x,n.x&&r.scrollBy({left:-n.x,behavior:l});break}if(m&&g.y!==f.y){let e=r.scrollTop+t.y,o=i===a.Down&&e<=d.y||i===a.Up&&e>=p.y;if(o&&!t.x)return void r.scrollTo({top:e,behavior:l});o?n.y=r.scrollTop-e:n.y=i===a.Down?r.scrollTop-d.y:r.scrollTop-p.y,n.y&&r.scrollBy({top:-n.y,behavior:l});break}}this.handleMove(e,(0,g.WQ)((0,g.Re)(f,this.referenceCoordinates),n))}}}handleMove(e,t){let{onMove:r}=this.props;e.preventDefault(),r(t)}handleEnd(e){let{onEnd:t}=this.props;e.preventDefault(),this.detach(),t()}handleCancel(e){let{onCancel:t}=this.props;e.preventDefault(),this.detach(),t()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}function er(e){return!!(e&&"distance"in e)}function en(e){return!!(e&&"delay"in e)}et.activators=[{eventName:"onKeyDown",handler:(e,t,r)=>{let{keyboardCodes:n=Q,onActivation:i}=t,{active:o}=r,{code:a}=e.nativeEvent;if(n.start.includes(a)){let t=o.activatorNode.current;return(!t||e.target===t)&&(e.preventDefault(),null==i||i({event:e.nativeEvent}),!0)}return!1}}];class ei{constructor(e,t,r){var n;void 0===r&&(r=function(e){let{EventTarget:t}=(0,g.zk)(e);return e instanceof t?e:(0,g.TW)(e)}(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=t;let{event:i}=e,{target:o}=i;this.props=e,this.events=t,this.document=(0,g.TW)(o),this.documentListeners=new q(this.document),this.listeners=new q(r),this.windowListeners=new q((0,g.zk)(o)),this.initialCoordinates=null!=(n=(0,g.e_)(i))?n:M,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){let{events:e,props:{options:{activationConstraint:t,bypassActivationConstraint:r}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(o.Resize,this.handleCancel),this.windowListeners.add(o.DragStart,X),this.windowListeners.add(o.VisibilityChange,this.handleCancel),this.windowListeners.add(o.ContextMenu,X),this.documentListeners.add(o.Keydown,this.handleKeydown),t){if(null!=r&&r({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(en(t)){this.timeoutId=setTimeout(this.handleStart,t.delay),this.handlePending(t);return}if(er(t))return void this.handlePending(t)}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),null!==this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,t){let{active:r,onPending:n}=this.props;n(r,e,this.initialCoordinates,t)}handleStart(){let{initialCoordinates:e}=this,{onStart:t}=this.props;e&&(this.activated=!0,this.documentListeners.add(o.Click,J,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(o.SelectionChange,this.removeTextSelection),t(e))}handleMove(e){var t;let{activated:r,initialCoordinates:n,props:i}=this,{onMove:o,options:{activationConstraint:a}}=i;if(!n)return;let l=null!=(t=(0,g.e_)(e))?t:M,u=(0,g.Re)(n,l);if(!r&&a){if(er(a)){if(null!=a.tolerance&&Y(u,a.tolerance))return this.handleCancel();if(Y(u,a.distance))return this.handleStart()}return en(a)&&Y(u,a.tolerance)?this.handleCancel():void this.handlePending(a,u)}e.cancelable&&e.preventDefault(),o(l)}handleEnd(){let{onAbort:e,onEnd:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleCancel(){let{onAbort:e,onCancel:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleKeydown(e){e.code===a.Esc&&this.handleCancel()}removeTextSelection(){var e;null==(e=this.document.getSelection())||e.removeAllRanges()}}let eo={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class ea extends ei{constructor(e){let{event:t}=e;super(e,eo,(0,g.TW)(t.target))}}ea.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t;return!!r.isPrimary&&0===r.button&&(null==n||n({event:r}),!0)}}];let el={move:{name:"mousemove"},end:{name:"mouseup"}};!function(e){e[e.RightClick=2]="RightClick"}(l||(l={}));class eu extends ei{constructor(e){super(e,el,(0,g.TW)(e.event.target))}}eu.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t;return r.button!==l.RightClick&&(null==n||n({event:r}),!0)}}];let es={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class ec extends ei{constructor(e){super(e,es)}static setup(){return window.addEventListener(es.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(es.move.name,e)};function e(){}}}ec.activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t,{touches:i}=r;return!(i.length>1)&&(null==n||n({event:r}),!0)}}],function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"}(u||(u={})),function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"}(s||(s={}));let ef={x:{[i.Backward]:!1,[i.Forward]:!1},y:{[i.Backward]:!1,[i.Forward]:!1}};!function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"}(c||(c={})),(f||(f={})).Optimized="optimized";let ed=new Map;function ep(e,t){return(0,g.KG)(r=>e?r||("function"==typeof t?t(e):e):null,[t,e])}function eh(e){let{callback:t,disabled:r}=e,n=(0,g._q)(t),i=(0,p.useMemo)(()=>{if(r||"undefined"==typeof window||void 0===window.ResizeObserver)return;let{ResizeObserver:e}=window;return new e(n)},[r]);return(0,p.useEffect)(()=>()=>null==i?void 0:i.disconnect(),[i]),i}function eg(e){return new K(D(e),e)}function ev(e,t,r){void 0===t&&(t=eg);let[n,i]=(0,p.useState)(null);function o(){i(n=>{if(!e)return null;if(!1===e.isConnected){var i;return null!=(i=null!=n?n:r)?i:null}let o=t(e);return JSON.stringify(n)===JSON.stringify(o)?n:o})}let a=function(e){let{callback:t,disabled:r}=e,n=(0,g._q)(t),i=(0,p.useMemo)(()=>{if(r||"undefined"==typeof window||void 0===window.MutationObserver)return;let{MutationObserver:e}=window;return new e(n)},[n,r]);return(0,p.useEffect)(()=>()=>null==i?void 0:i.disconnect(),[i]),i}({callback(t){if(e)for(let r of t){let{type:t,target:n}=r;if("childList"===t&&n instanceof HTMLElement&&n.contains(e)){o();break}}}}),l=eh({callback:o});return(0,g.Es)(()=>{o(),e?(null==l||l.observe(e),null==a||a.observe(document.body,{childList:!0,subtree:!0})):(null==l||l.disconnect(),null==a||a.disconnect())},[e]),n}let em=[];function ey(e,t){void 0===t&&(t=[]);let r=(0,p.useRef)(null);return(0,p.useEffect)(()=>{r.current=null},t),(0,p.useEffect)(()=>{let t=e!==M;t&&!r.current&&(r.current=e),!t&&r.current&&(r.current=null)},[e]),r.current?(0,g.Re)(e,r.current):M}function eb(e){return(0,p.useMemo)(()=>e?function(e){let t=e.innerWidth,r=e.innerHeight;return{top:0,left:0,right:t,bottom:r,width:t,height:r}}(e):null,[e])}let ew=[],ex=[{sensor:ea,options:{}},{sensor:et,options:{}}],eS={current:{}},eO={draggable:{measure:I},droppable:{measure:I,strategy:c.WhileDragging,frequency:f.Optimized},dragOverlay:{measure:D}};class eE extends Map{get(e){var t;return null!=e&&null!=(t=super.get(e))?t:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:t}=e;return!t})}getNodeFor(e){var t,r;return null!=(t=null==(r=this.get(e))?void 0:r.node.current)?t:void 0}}let eC={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new eE,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:O},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:eO,measureDroppableContainers:O,windowRect:null,measuringScheduled:!1},eM={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:O,draggableNodes:new Map,over:null,measureDroppableContainers:O},ek=(0,p.createContext)(eM),e_=(0,p.createContext)(eC);function eP(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new eE}}}function eA(e,t){switch(t.type){case n.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case n.DragMove:if(null==e.draggable.active)return e;return{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case n.DragEnd:case n.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case n.RegisterDroppable:{let{element:r}=t,{id:n}=r,i=new eE(e.droppable.containers);return i.set(n,r),{...e,droppable:{...e.droppable,containers:i}}}case n.SetDroppableDisabled:{let{id:r,key:n,disabled:i}=t,o=e.droppable.containers.get(r);if(!o||n!==o.key)return e;let a=new eE(e.droppable.containers);return a.set(r,{...o,disabled:i}),{...e,droppable:{...e.droppable,containers:a}}}case n.UnregisterDroppable:{let{id:r,key:n}=t,i=e.droppable.containers.get(r);if(!i||n!==i.key)return e;let o=new eE(e.droppable.containers);return o.delete(r),{...e,droppable:{...e.droppable,containers:o}}}default:return e}}function ej(e){let{disabled:t}=e,{active:r,activatorEvent:n,draggableNodes:i}=(0,p.useContext)(ek),o=(0,g.ZC)(n),a=(0,g.ZC)(null==r?void 0:r.id);return(0,p.useEffect)(()=>{if(!t&&!n&&o&&null!=a){if(!(0,g.kx)(o)||document.activeElement===o.target)return;let e=i.get(a);if(!e)return;let{activatorNode:t,node:r}=e;(t.current||r.current)&&requestAnimationFrame(()=>{for(let e of[t.current,r.current]){if(!e)continue;let t=(0,g.ag)(e);if(t){t.focus();break}}})}},[n,t,i,a,o]),null}let eT=(0,p.createContext)({...M,scaleX:1,scaleY:1});!function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"}(d||(d={}));let eR=(0,p.memo)(function(e){var t,r,o,a,l,f;let{id:v,accessibility:m,autoScroll:y=!0,children:w,sensors:x=ex,collisionDetection:O=j,measuring:E,modifiers:C,...k}=e,[_,P]=(0,p.useReducer)(eA,void 0,eP),[A,z]=function(){let[e]=(0,p.useState)(()=>new Set),t=(0,p.useCallback)(t=>(e.add(t),()=>e.delete(t)),[e]);return[(0,p.useCallback)(t=>{let{type:r,event:n}=t;e.forEach(e=>{var t;return null==(t=e[r])?void 0:t.call(e,n)})},[e]),t]}(),[I,$]=(0,p.useState)(d.Uninitialized),B=I===d.Initialized,{draggable:{active:W,nodes:q,translate:Y},droppable:{containers:X}}=_,J=null!=W?q.get(W):null,Q=(0,p.useRef)({initial:null,translated:null}),ee=(0,p.useMemo)(()=>{var e;return null!=W?{id:W,data:null!=(e=null==J?void 0:J.data)?e:eS,rect:Q}:null},[W,J]),et=(0,p.useRef)(null),[er,en]=(0,p.useState)(null),[ei,eo]=(0,p.useState)(null),ea=(0,g.YN)(k,Object.values(k)),el=(0,g.YG)("DndDescribedBy",v),eu=(0,p.useMemo)(()=>X.getEnabled(),[X]),es=(0,p.useMemo)(()=>({draggable:{...eO.draggable,...null==E?void 0:E.draggable},droppable:{...eO.droppable,...null==E?void 0:E.droppable},dragOverlay:{...eO.dragOverlay,...null==E?void 0:E.dragOverlay}}),[null==E?void 0:E.draggable,null==E?void 0:E.droppable,null==E?void 0:E.dragOverlay]),{droppableRects:ec,measureDroppableContainers:eg,measuringScheduled:eE}=function(e,t){let{dragging:r,dependencies:n,config:i}=t,[o,a]=(0,p.useState)(null),{frequency:l,measure:u,strategy:s}=i,f=(0,p.useRef)(e),d=function(){switch(s){case c.Always:return!1;case c.BeforeDragging:return r;default:return!r}}(),h=(0,g.YN)(d),v=(0,p.useCallback)(function(e){void 0===e&&(e=[]),h.current||a(t=>null===t?e:t.concat(e.filter(e=>!t.includes(e))))},[h]),m=(0,p.useRef)(null),y=(0,g.KG)(t=>{if(d&&!r)return ed;if(!t||t===ed||f.current!==e||null!=o){let t=new Map;for(let r of e){if(!r)continue;if(o&&o.length>0&&!o.includes(r.id)&&r.rect.current){t.set(r.id,r.rect.current);continue}let e=r.node.current,n=e?new K(u(e),e):null;r.rect.current=n,n&&t.set(r.id,n)}return t}return t},[e,o,r,d,u]);return(0,p.useEffect)(()=>{f.current=e},[e]),(0,p.useEffect)(()=>{d||v()},[r,d]),(0,p.useEffect)(()=>{o&&o.length>0&&a(null)},[JSON.stringify(o)]),(0,p.useEffect)(()=>{d||"number"!=typeof l||null!==m.current||(m.current=setTimeout(()=>{v(),m.current=null},l))},[l,d,v,...n]),{droppableRects:y,measureDroppableContainers:v,measuringScheduled:null!=o}}(eu,{dragging:B,dependencies:[Y.x,Y.y],config:es.droppable}),eC=function(e,t){let r=null!=t?e.get(t):void 0,n=r?r.node.current:null;return(0,g.KG)(e=>{var r;return null==t?null:null!=(r=null!=n?n:e)?r:null},[n,t])}(q,W),eM=(0,p.useMemo)(()=>ei?(0,g.e_)(ei):null,[ei]),eR=function(){let e=(null==er?void 0:er.autoScrollEnabled)===!1,t="object"==typeof y?!1===y.enabled:!1===y,r=B&&!e&&!t;return"object"==typeof y?{...y,enabled:r}:{enabled:r}}(),ez=ep(eC,es.draggable.measure);!function(e){let{activeNode:t,measure:r,initialRect:n,config:i=!0}=e,o=(0,p.useRef)(!1),{x:a,y:l}="boolean"==typeof i?{x:i,y:i}:i;(0,g.Es)(()=>{if(!a&&!l||!t){o.current=!1;return}if(o.current||!n)return;let e=null==t?void 0:t.node.current;if(!e||!1===e.isConnected)return;let i=T(r(e),n);if(a||(i.x=0),l||(i.y=0),o.current=!0,Math.abs(i.x)>0||Math.abs(i.y)>0){let t=L(e);t&&t.scrollBy({top:i.y,left:i.x})}},[t,a,l,n,r])}({activeNode:null!=W?q.get(W):null,config:eR.layoutShiftCompensation,initialRect:ez,measure:es.draggable.measure});let eD=ev(eC,es.draggable.measure,ez),eI=ev(eC?eC.parentElement:null),eN=(0,p.useRef)({activatorEvent:null,active:null,activeNode:eC,collisionRect:null,collisions:null,droppableRects:ec,draggableNodes:q,draggingNode:null,draggingNodeRect:null,droppableContainers:X,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),eL=X.getNodeFor(null==(t=eN.current.over)?void 0:t.id),eF=function(e){let{measure:t}=e,[r,n]=(0,p.useState)(null),i=eh({callback:(0,p.useCallback)(e=>{for(let{target:r}of e)if((0,g.sb)(r)){n(e=>{let n=t(r);return e?{...e,width:n.width,height:n.height}:n});break}},[t])}),o=(0,p.useCallback)(e=>{let r=function(e){if(!e)return null;if(e.children.length>1)return e;let t=e.children[0];return(0,g.sb)(t)?t:e}(e);null==i||i.disconnect(),r&&(null==i||i.observe(r)),n(r?t(r):null)},[t,i]),[a,l]=(0,g.lk)(o);return(0,p.useMemo)(()=>({nodeRef:a,rect:r,setRef:l}),[r,a,l])}({measure:es.dragOverlay.measure}),e$=null!=(r=eF.nodeRef.current)?r:eC,eB=B?null!=(o=eF.rect)?o:eD:null,eV=!!(eF.nodeRef.current&&eF.rect),eU=function(e){let t=ep(e);return T(e,t)}(eV?null:eD),eH=eb(e$?(0,g.zk)(e$):null),eG=function(e){let t=(0,p.useRef)(e),r=(0,g.KG)(r=>e?r&&r!==em&&e&&t.current&&e.parentNode===t.current.parentNode?r:N(e):em,[e]);return(0,p.useEffect)(()=>{t.current=e},[e]),r}(B?null!=eL?eL:eC:null),eZ=function(e,t){void 0===t&&(t=D);let[r]=e,n=eb(r?(0,g.zk)(r):null),[i,o]=(0,p.useState)(ew);function a(){o(()=>e.length?e.map(e=>U(e)?n:new K(t(e),e)):ew)}let l=eh({callback:a});return(0,g.Es)(()=>{null==l||l.disconnect(),a(),e.forEach(e=>null==l?void 0:l.observe(e))},[e]),i}(eG),eW=function(e,t){let{transform:r,...n}=t;return null!=e&&e.length?e.reduce((e,t)=>t({transform:e,...n}),r):r}(C,{transform:{x:Y.x-eU.x,y:Y.y-eU.y,scaleX:1,scaleY:1},activatorEvent:ei,active:ee,activeNodeRect:eD,containerNodeRect:eI,draggingNodeRect:eB,over:eN.current.over,overlayNodeRect:eF.rect,scrollableAncestors:eG,scrollableAncestorRects:eZ,windowRect:eH}),eK=eM?(0,g.WQ)(eM,Y):null,eq=function(e){let[t,r]=(0,p.useState)(null),n=(0,p.useRef)(e),i=(0,p.useCallback)(e=>{let t=F(e.target);t&&r(e=>e?(e.set(t,V(t)),new Map(e)):null)},[]);return(0,p.useEffect)(()=>{let t=n.current;if(e!==t){o(t);let a=e.map(e=>{let t=F(e);return t?(t.addEventListener("scroll",i,{passive:!0}),[t,V(t)]):null}).filter(e=>null!=e);r(a.length?new Map(a):null),n.current=e}return()=>{o(e),o(t)};function o(e){e.forEach(e=>{let t=F(e);null==t||t.removeEventListener("scroll",i)})}},[i,e]),(0,p.useMemo)(()=>e.length?t?Array.from(t.values()).reduce((e,t)=>(0,g.WQ)(e,t),M):Z(e):M,[e,t])}(eG),eY=ey(eq),eX=ey(eq,[eD]),eJ=(0,g.WQ)(eW,eY),eQ=eB?R(eB,eW):null,e0=ee&&eQ?O({active:ee,collisionRect:eQ,droppableRects:ec,droppableContainers:eu,pointerCoordinates:eK}):null,e1=function(e,t){if(!e||0===e.length)return null;let[r]=e;return r.id}(e0,"id"),[e2,e5]=(0,p.useState)(null),e6=(l=eV?eW:(0,g.WQ)(eW,eX),f=null!=(a=null==e2?void 0:e2.rect)?a:null,{...l,scaleX:f&&eD?f.width/eD.width:1,scaleY:f&&eD?f.height/eD.height:1}),e4=(0,p.useRef)(null),e3=(0,p.useCallback)((e,t)=>{let{sensor:r,options:i}=t;if(null==et.current)return;let o=q.get(et.current);if(!o)return;let a=e.nativeEvent,l=new r({active:et.current,activeNode:o,event:a,options:i,context:eN,onAbort(e){if(!q.get(e))return;let{onDragAbort:t}=ea.current,r={id:e};null==t||t(r),A({type:"onDragAbort",event:r})},onPending(e,t,r,n){if(!q.get(e))return;let{onDragPending:i}=ea.current,o={id:e,constraint:t,initialCoordinates:r,offset:n};null==i||i(o),A({type:"onDragPending",event:o})},onStart(e){let t=et.current;if(null==t)return;let r=q.get(t);if(!r)return;let{onDragStart:i}=ea.current,o={activatorEvent:a,active:{id:t,data:r.data,rect:Q}};(0,h.unstable_batchedUpdates)(()=>{null==i||i(o),$(d.Initializing),P({type:n.DragStart,initialCoordinates:e,active:t}),A({type:"onDragStart",event:o}),en(e4.current),eo(a)})},onMove(e){P({type:n.DragMove,coordinates:e})},onEnd:u(n.DragEnd),onCancel:u(n.DragCancel)});function u(e){return async function(){let{active:t,collisions:r,over:i,scrollAdjustedTranslate:o}=eN.current,l=null;if(t&&o){let{cancelDrop:u}=ea.current;l={activatorEvent:a,active:t,collisions:r,delta:o,over:i},e===n.DragEnd&&"function"==typeof u&&await Promise.resolve(u(l))&&(e=n.DragCancel)}et.current=null,(0,h.unstable_batchedUpdates)(()=>{P({type:e}),$(d.Uninitialized),e5(null),en(null),eo(null),e4.current=null;let t=e===n.DragEnd?"onDragEnd":"onDragCancel";if(l){let e=ea.current[t];null==e||e(l),A({type:t,event:l})}})}}e4.current=l},[q]),e8=(0,p.useCallback)((e,t)=>(r,n)=>{let i=r.nativeEvent,o=q.get(n);null!==et.current||!o||i.dndKit||i.defaultPrevented||!0===e(r,t.options,{active:o})&&(i.dndKit={capturedBy:t.sensor},et.current=n,e3(r,t))},[q,e3]),e9=(0,p.useMemo)(()=>x.reduce((e,t)=>{let{sensor:r}=t;return[...e,...r.activators.map(e=>({eventName:e.eventName,handler:e8(e.handler,t)}))]},[]),[x,e8]);(0,p.useEffect)(()=>{if(!g.Sw)return;let e=x.map(e=>{let{sensor:t}=e;return null==t.setup?void 0:t.setup()});return()=>{for(let t of e)null==t||t()}},x.map(e=>{let{sensor:t}=e;return t})),(0,g.Es)(()=>{eD&&I===d.Initializing&&$(d.Initialized)},[eD,I]),(0,p.useEffect)(()=>{let{onDragMove:e}=ea.current,{active:t,activatorEvent:r,collisions:n,over:i}=eN.current;if(!t||!r)return;let o={active:t,activatorEvent:r,collisions:n,delta:{x:eJ.x,y:eJ.y},over:i};(0,h.unstable_batchedUpdates)(()=>{null==e||e(o),A({type:"onDragMove",event:o})})},[eJ.x,eJ.y]),(0,p.useEffect)(()=>{let{active:e,activatorEvent:t,collisions:r,droppableContainers:n,scrollAdjustedTranslate:i}=eN.current;if(!e||null==et.current||!t||!i)return;let{onDragOver:o}=ea.current,a=n.get(e1),l=a&&a.rect.current?{id:a.id,rect:a.rect.current,data:a.data,disabled:a.disabled}:null,u={active:e,activatorEvent:t,collisions:r,delta:{x:i.x,y:i.y},over:l};(0,h.unstable_batchedUpdates)(()=>{e5(l),null==o||o(u),A({type:"onDragOver",event:u})})},[e1]),(0,g.Es)(()=>{eN.current={activatorEvent:ei,active:ee,activeNode:eC,collisionRect:eQ,collisions:e0,droppableRects:ec,draggableNodes:q,draggingNode:e$,draggingNodeRect:eB,droppableContainers:X,over:e2,scrollableAncestors:eG,scrollAdjustedTranslate:eJ},Q.current={initial:eB,translated:eQ}},[ee,eC,e0,eQ,q,e$,eB,ec,X,e2,eG,eJ]),function(e){let{acceleration:t,activator:r=u.Pointer,canScroll:n,draggingRect:o,enabled:a,interval:l=5,order:c=s.TreeOrder,pointerCoordinates:f,scrollableAncestors:d,scrollableAncestorRects:h,delta:v,threshold:m}=e,y=function(e){let{delta:t,disabled:r}=e,n=(0,g.ZC)(t);return(0,g.KG)(e=>{if(r||!n||!e)return ef;let o={x:Math.sign(t.x-n.x),y:Math.sign(t.y-n.y)};return{x:{[i.Backward]:e.x[i.Backward]||-1===o.x,[i.Forward]:e.x[i.Forward]||1===o.x},y:{[i.Backward]:e.y[i.Backward]||-1===o.y,[i.Forward]:e.y[i.Forward]||1===o.y}}},[r,t,n])}({delta:v,disabled:!a}),[b,w]=(0,g.$$)(),x=(0,p.useRef)({x:0,y:0}),S=(0,p.useRef)({x:0,y:0}),O=(0,p.useMemo)(()=>{switch(r){case u.Pointer:return f?{top:f.y,bottom:f.y,left:f.x,right:f.x}:null;case u.DraggableRect:return o}},[r,o,f]),E=(0,p.useRef)(null),C=(0,p.useCallback)(()=>{let e=E.current;if(!e)return;let t=x.current.x*S.current.x,r=x.current.y*S.current.y;e.scrollBy(t,r)},[]),M=(0,p.useMemo)(()=>c===s.TreeOrder?[...d].reverse():d,[c,d]);(0,p.useEffect)(()=>{if(!a||!d.length||!O)return void w();for(let e of M){if((null==n?void 0:n(e))===!1)continue;let r=h[d.indexOf(e)];if(!r)continue;let{direction:o,speed:a}=function(e,t,r,n,o){let{top:a,left:l,right:u,bottom:s}=r;void 0===n&&(n=10),void 0===o&&(o=G);let{isTop:c,isBottom:f,isLeft:d,isRight:p}=H(e),h={x:0,y:0},g={x:0,y:0},v={height:t.height*o.y,width:t.width*o.x};return!c&&a<=t.top+v.height?(h.y=i.Backward,g.y=n*Math.abs((t.top+v.height-a)/v.height)):!f&&s>=t.bottom-v.height&&(h.y=i.Forward,g.y=n*Math.abs((t.bottom-v.height-s)/v.height)),!p&&u>=t.right-v.width?(h.x=i.Forward,g.x=n*Math.abs((t.right-v.width-u)/v.width)):!d&&l<=t.left+v.width&&(h.x=i.Backward,g.x=n*Math.abs((t.left+v.width-l)/v.width)),{direction:h,speed:g}}(e,r,O,t,m);for(let e of["x","y"])y[e][o[e]]||(a[e]=0,o[e]=0);if(a.x>0||a.y>0){w(),E.current=e,b(C,l),x.current=a,S.current=o;return}}x.current={x:0,y:0},S.current={x:0,y:0},w()},[t,C,n,w,a,l,JSON.stringify(O),JSON.stringify(y),b,d,M,h,JSON.stringify(m)])}({...eR,delta:Y,draggingRect:eQ,pointerCoordinates:eK,scrollableAncestors:eG,scrollableAncestorRects:eZ});let e7=(0,p.useMemo)(()=>({active:ee,activeNode:eC,activeNodeRect:eD,activatorEvent:ei,collisions:e0,containerNodeRect:eI,dragOverlay:eF,draggableNodes:q,droppableContainers:X,droppableRects:ec,over:e2,measureDroppableContainers:eg,scrollableAncestors:eG,scrollableAncestorRects:eZ,measuringConfiguration:es,measuringScheduled:eE,windowRect:eH}),[ee,eC,eD,ei,e0,eI,eF,q,X,ec,e2,eg,eG,eZ,es,eE,eH]),te=(0,p.useMemo)(()=>({activatorEvent:ei,activators:e9,active:ee,activeNodeRect:eD,ariaDescribedById:{draggable:el},dispatch:P,draggableNodes:q,over:e2,measureDroppableContainers:eg}),[ei,e9,ee,eD,P,el,q,e2,eg]);return p.createElement(b.Provider,{value:z},p.createElement(ek.Provider,{value:te},p.createElement(e_.Provider,{value:e7},p.createElement(eT.Provider,{value:e6},w)),p.createElement(ej,{disabled:(null==m?void 0:m.restoreFocus)===!1})),p.createElement(S,{...m,hiddenTextDescribedById:el}))}),ez=(0,p.createContext)(null),eD="button";function eI(e){let{id:t,data:r,disabled:n=!1,attributes:i}=e,o=(0,g.YG)("Draggable"),{activators:a,activatorEvent:l,active:u,activeNodeRect:s,ariaDescribedById:c,draggableNodes:f,over:d}=(0,p.useContext)(ek),{role:h=eD,roleDescription:v="draggable",tabIndex:m=0}=null!=i?i:{},y=(null==u?void 0:u.id)===t,b=(0,p.useContext)(y?eT:ez),[w,x]=(0,g.lk)(),[S,O]=(0,g.lk)(),E=(0,p.useMemo)(()=>a.reduce((e,r)=>{let{eventName:n,handler:i}=r;return e[n]=e=>{i(e,t)},e},{}),[a,t]),C=(0,g.YN)(r);return(0,g.Es)(()=>(f.set(t,{id:t,key:o,node:w,activatorNode:S,data:C}),()=>{let e=f.get(t);e&&e.key===o&&f.delete(t)}),[f,t]),{active:u,activatorEvent:l,activeNodeRect:s,attributes:(0,p.useMemo)(()=>({role:h,tabIndex:m,"aria-disabled":n,"aria-pressed":!!y&&h===eD||void 0,"aria-roledescription":v,"aria-describedby":c.draggable}),[n,h,m,y,v,c.draggable]),isDragging:y,listeners:n?void 0:E,node:w,over:d,setNodeRef:x,setActivatorNodeRef:O,transform:b}}function eN(){return(0,p.useContext)(e_)}let eL={timeout:25};function eF(e){let{data:t,disabled:r=!1,id:i,resizeObserverConfig:o}=e,a=(0,g.YG)("Droppable"),{active:l,dispatch:u,over:s,measureDroppableContainers:c}=(0,p.useContext)(ek),f=(0,p.useRef)({disabled:r}),d=(0,p.useRef)(!1),h=(0,p.useRef)(null),v=(0,p.useRef)(null),{disabled:m,updateMeasurementsFor:y,timeout:b}={...eL,...o},w=(0,g.YN)(null!=y?y:i),x=eh({callback:(0,p.useCallback)(()=>{if(!d.current){d.current=!0;return}null!=v.current&&clearTimeout(v.current),v.current=setTimeout(()=>{c(Array.isArray(w.current)?w.current:[w.current]),v.current=null},b)},[b]),disabled:m||!l}),S=(0,p.useCallback)((e,t)=>{x&&(t&&(x.unobserve(t),d.current=!1),e&&x.observe(e))},[x]),[O,E]=(0,g.lk)(S),C=(0,g.YN)(t);return(0,p.useEffect)(()=>{x&&O.current&&(x.disconnect(),d.current=!1,x.observe(O.current))},[O,x]),(0,p.useEffect)(()=>(u({type:n.RegisterDroppable,element:{id:i,key:a,disabled:r,node:O,rect:h,data:C}}),()=>u({type:n.UnregisterDroppable,key:a,id:i})),[i]),(0,p.useEffect)(()=>{r!==f.current.disabled&&(u({type:n.SetDroppableDisabled,id:i,key:a,disabled:r}),f.current.disabled=r)},[i,a,r,u]),{active:l,rect:h,isOver:(null==s?void 0:s.id)===i,node:O,over:s,setNodeRef:E}}},5146:(e,t,r)=>{"use strict";r.d(t,{q:()=>n});var n=(e,t,r,n)=>{var i;return"axis"===t?e.tooltipItemPayloads:0===e.tooltipItemPayloads.length?[]:null==(i="hover"===r?e.itemInteraction.hover.dataKey:e.itemInteraction.click.dataKey)&&null!=n?[e.tooltipItemPayloads[0]]:e.tooltipItemPayloads.filter(e=>{var t;return(null==(t=e.settings)?void 0:t.dataKey)===i})}},5148:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.noop=function(){}},5160:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.getTag=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":Object.prototype.toString.call(e)}},5181:(e,t)=>{"use strict";function r(e){return"symbol"==typeof e?1:null===e?2:void 0===e?3:4*(e!=e)}Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.compareValues=(e,t,n)=>{if(e!==t){let i=r(e),o=r(t);if(i===o&&0===i){if(et)return"desc"===n?-1:1}return"desc"===n?o-i:i-o}return 0}},5185:(e,t,r)=>{"use strict";function n(e,t,{checkForDefaultPrevented:r=!0}={}){return function(n){if(e?.(n),!1===r||!n.defaultPrevented)return t?.(n)}}r.d(t,{m:()=>n})},5196:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]])},5252:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(2520),i=r(5148);t.isEqual=function(e,t){return n.isEqualWith(e,t,i.noop)}},5525:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("shield",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]])},5604:(e,t,r)=>{"use strict";function n(e,t,r){function n(r,n){var i;for(let o in Object.defineProperty(r,"_zod",{value:r._zod??{},enumerable:!1}),(i=r._zod).traits??(i.traits=new Set),r._zod.traits.add(e),t(r,n),a.prototype)o in r||Object.defineProperty(r,o,{value:a.prototype[o].bind(r)});r._zod.constr=a,r._zod.def=n}let i=r?.Parent??Object;class o extends i{}function a(e){var t;let i=r?.Parent?new o:this;for(let r of(n(i,e),(t=i._zod).deferred??(t.deferred=[]),i._zod.deferred))r();return i}return Object.defineProperty(o,"name",{value:e}),Object.defineProperty(a,"init",{value:n}),Object.defineProperty(a,Symbol.hasInstance,{value:t=>!!r?.Parent&&t instanceof r.Parent||t?._zod?.traits?.has(e)}),Object.defineProperty(a,"name",{value:e}),a}r.d(t,{EB:()=>tZ,YO:()=>rd,zM:()=>rl,k5:()=>rw,ai:()=>rn,Ik:()=>rh,g1:()=>ry,Yj:()=>tG}),Object.freeze({status:"aborted"}),Symbol("zod_brand");class i extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}let o={};function a(e){return e&&Object.assign(o,e),o}let l=/^[cC][^\s-]{8,}$/,u=/^[0-9a-z]+$/,s=/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,c=/^[0-9a-vA-V]{20}$/,f=/^[A-Za-z0-9]{27}$/,d=/^[a-zA-Z0-9_-]{21}$/,p=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,h=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,g=e=>e?RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$/,v=/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,m=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,y=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})$/,b=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,w=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,x=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,S=/^[A-Za-z0-9_-]*$/,O=/^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/,E=/^\+(?:[0-9]){6,14}[0-9]$/,C="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))",M=RegExp(`^${C}$`);function k(e){let t="(?:[01]\\d|2[0-3]):[0-5]\\d";return"number"==typeof e.precision?-1===e.precision?`${t}`:0===e.precision?`${t}:[0-5]\\d`:`${t}:[0-5]\\d\\.\\d{${e.precision}}`:`${t}(?::[0-5]\\d(?:\\.\\d+)?)?`}let _=/^\d+$/,P=/^-?\d+(?:\.\d+)?/i,A=/true|false/i,j=/^[^A-Z]*$/,T=/^[^a-z]*$/;function R(e,t){return"bigint"==typeof t?t.toString():t}function z(e){return{get value(){{let t=e();return Object.defineProperty(this,"value",{value:t}),t}}}}function D(e){let t=+!!e.startsWith("^"),r=e.endsWith("$")?e.length-1:e.length;return e.slice(t,r)}function I(e,t,r){Object.defineProperty(e,t,{get(){{let n=r();return e[t]=n,n}},set(r){Object.defineProperty(e,t,{value:r})},configurable:!0})}function N(e,t,r){Object.defineProperty(e,t,{value:r,writable:!0,enumerable:!0,configurable:!0})}function L(e){return JSON.stringify(e)}let F=Error.captureStackTrace?Error.captureStackTrace:(...e)=>{};function $(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}let B=z(()=>{if("undefined"!=typeof navigator&&navigator?.userAgent?.includes("Cloudflare"))return!1;try{return Function(""),!0}catch(e){return!1}});function V(e){if(!1===$(e))return!1;let t=e.constructor;if(void 0===t)return!0;let r=t.prototype;return!1!==$(r)&&!1!==Object.prototype.hasOwnProperty.call(r,"isPrototypeOf")}let U=new Set(["string","number","symbol"]);function H(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function G(e,t,r){let n=new e._zod.constr(t??e._zod.def);return(!t||r?.parent)&&(n._zod.parent=e),n}function Z(e){if(!e)return{};if("string"==typeof e)return{error:()=>e};if(e?.message!==void 0){if(e?.error!==void 0)throw Error("Cannot specify both `message` and `error` params");e.error=e.message}return(delete e.message,"string"==typeof e.error)?{...e,error:()=>e.error}:e}let W={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-0x80000000,0x7fffffff],uint32:[0,0xffffffff],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]};function K(e,t=0){for(let r=t;r(t.path??(t.path=[]),t.path.unshift(e),t))}function Y(e){return"string"==typeof e?e:e?.message}function X(e,t,r){let n={...e,path:e.path??[]};return e.message||(n.message=Y(e.inst?._zod.def?.error?.(e))??Y(t?.error?.(e))??Y(r.customError?.(e))??Y(r.localeError?.(e))??"Invalid input"),delete n.inst,delete n.continue,t?.reportInput||delete n.input,n}function J(e){return Array.isArray(e)?"array":"string"==typeof e?"string":"unknown"}function Q(...e){let[t,r,n]=e;return"string"==typeof t?{message:t,code:"custom",input:r,inst:n}:{...t}}let ee=n("$ZodCheck",(e,t)=>{var r;e._zod??(e._zod={}),e._zod.def=t,(r=e._zod).onattach??(r.onattach=[])}),et={number:"number",bigint:"bigint",object:"date"},er=n("$ZodCheckLessThan",(e,t)=>{ee.init(e,t);let r=et[typeof t.value];e._zod.onattach.push(e=>{let r=e._zod.bag,n=(t.inclusive?r.maximum:r.exclusiveMaximum)??1/0;t.value{(t.inclusive?n.value<=t.value:n.value{ee.init(e,t);let r=et[typeof t.value];e._zod.onattach.push(e=>{let r=e._zod.bag,n=(t.inclusive?r.minimum:r.exclusiveMinimum)??-1/0;t.value>n&&(t.inclusive?r.minimum=t.value:r.exclusiveMinimum=t.value)}),e._zod.check=n=>{(t.inclusive?n.value>=t.value:n.value>t.value)||n.issues.push({origin:r,code:"too_small",minimum:t.value,input:n.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),ei=n("$ZodCheckMultipleOf",(e,t)=>{ee.init(e,t),e._zod.onattach.push(e=>{var r;(r=e._zod.bag).multipleOf??(r.multipleOf=t.value)}),e._zod.check=r=>{if(typeof r.value!=typeof t.value)throw Error("Cannot mix number and bigint in multiple_of check.");("bigint"==typeof r.value?r.value%t.value===BigInt(0):0===function(e,t){let r=(e.toString().split(".")[1]||"").length,n=(t.toString().split(".")[1]||"").length,i=r>n?r:n;return Number.parseInt(e.toFixed(i).replace(".",""))%Number.parseInt(t.toFixed(i).replace(".",""))/10**i}(r.value,t.value))||r.issues.push({origin:typeof r.value,code:"not_multiple_of",divisor:t.value,input:r.value,inst:e,continue:!t.abort})}}),eo=n("$ZodCheckNumberFormat",(e,t)=>{ee.init(e,t),t.format=t.format||"float64";let r=t.format?.includes("int"),n=r?"int":"number",[i,o]=W[t.format];e._zod.onattach.push(e=>{let n=e._zod.bag;n.format=t.format,n.minimum=i,n.maximum=o,r&&(n.pattern=_)}),e._zod.check=a=>{let l=a.value;if(r){if(!Number.isInteger(l))return void a.issues.push({expected:n,format:t.format,code:"invalid_type",input:l,inst:e});if(!Number.isSafeInteger(l))return void(l>0?a.issues.push({input:l,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:n,continue:!t.abort}):a.issues.push({input:l,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:n,continue:!t.abort}))}lo&&a.issues.push({origin:"number",input:l,code:"too_big",maximum:o,inst:e})}}),ea=n("$ZodCheckMaxLength",(e,t)=>{var r;ee.init(e,t),(r=e._zod.def).when??(r.when=e=>{let t=e.value;return null!=t&&void 0!==t.length}),e._zod.onattach.push(e=>{let r=e._zod.bag.maximum??1/0;t.maximum{let n=r.value;if(n.length<=t.maximum)return;let i=J(n);r.issues.push({origin:i,code:"too_big",maximum:t.maximum,inclusive:!0,input:n,inst:e,continue:!t.abort})}}),el=n("$ZodCheckMinLength",(e,t)=>{var r;ee.init(e,t),(r=e._zod.def).when??(r.when=e=>{let t=e.value;return null!=t&&void 0!==t.length}),e._zod.onattach.push(e=>{let r=e._zod.bag.minimum??-1/0;t.minimum>r&&(e._zod.bag.minimum=t.minimum)}),e._zod.check=r=>{let n=r.value;if(n.length>=t.minimum)return;let i=J(n);r.issues.push({origin:i,code:"too_small",minimum:t.minimum,inclusive:!0,input:n,inst:e,continue:!t.abort})}}),eu=n("$ZodCheckLengthEquals",(e,t)=>{var r;ee.init(e,t),(r=e._zod.def).when??(r.when=e=>{let t=e.value;return null!=t&&void 0!==t.length}),e._zod.onattach.push(e=>{let r=e._zod.bag;r.minimum=t.length,r.maximum=t.length,r.length=t.length}),e._zod.check=r=>{let n=r.value,i=n.length;if(i===t.length)return;let o=J(n),a=i>t.length;r.issues.push({origin:o,...a?{code:"too_big",maximum:t.length}:{code:"too_small",minimum:t.length},inclusive:!0,exact:!0,input:r.value,inst:e,continue:!t.abort})}}),es=n("$ZodCheckStringFormat",(e,t)=>{var r,n;ee.init(e,t),e._zod.onattach.push(e=>{let r=e._zod.bag;r.format=t.format,t.pattern&&(r.patterns??(r.patterns=new Set),r.patterns.add(t.pattern))}),t.pattern?(r=e._zod).check??(r.check=r=>{t.pattern.lastIndex=0,t.pattern.test(r.value)||r.issues.push({origin:"string",code:"invalid_format",format:t.format,input:r.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(n=e._zod).check??(n.check=()=>{})}),ec=n("$ZodCheckRegex",(e,t)=>{es.init(e,t),e._zod.check=r=>{t.pattern.lastIndex=0,t.pattern.test(r.value)||r.issues.push({origin:"string",code:"invalid_format",format:"regex",input:r.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),ef=n("$ZodCheckLowerCase",(e,t)=>{t.pattern??(t.pattern=j),es.init(e,t)}),ed=n("$ZodCheckUpperCase",(e,t)=>{t.pattern??(t.pattern=T),es.init(e,t)}),ep=n("$ZodCheckIncludes",(e,t)=>{ee.init(e,t);let r=H(t.includes),n=new RegExp("number"==typeof t.position?`^.{${t.position}}${r}`:r);t.pattern=n,e._zod.onattach.push(e=>{let t=e._zod.bag;t.patterns??(t.patterns=new Set),t.patterns.add(n)}),e._zod.check=r=>{r.value.includes(t.includes,t.position)||r.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:t.includes,input:r.value,inst:e,continue:!t.abort})}}),eh=n("$ZodCheckStartsWith",(e,t)=>{ee.init(e,t);let r=RegExp(`^${H(t.prefix)}.*`);t.pattern??(t.pattern=r),e._zod.onattach.push(e=>{let t=e._zod.bag;t.patterns??(t.patterns=new Set),t.patterns.add(r)}),e._zod.check=r=>{r.value.startsWith(t.prefix)||r.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:t.prefix,input:r.value,inst:e,continue:!t.abort})}}),eg=n("$ZodCheckEndsWith",(e,t)=>{ee.init(e,t);let r=RegExp(`.*${H(t.suffix)}$`);t.pattern??(t.pattern=r),e._zod.onattach.push(e=>{let t=e._zod.bag;t.patterns??(t.patterns=new Set),t.patterns.add(r)}),e._zod.check=r=>{r.value.endsWith(t.suffix)||r.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:t.suffix,input:r.value,inst:e,continue:!t.abort})}}),ev=n("$ZodCheckOverwrite",(e,t)=>{ee.init(e,t),e._zod.check=e=>{e.value=t.tx(e.value)}});class em{constructor(e=[]){this.content=[],this.indent=0,this&&(this.args=e)}indented(e){this.indent+=1,e(this),this.indent-=1}write(e){if("function"==typeof e){e(this,{execution:"sync"}),e(this,{execution:"async"});return}let t=e.split("\n").filter(e=>e),r=Math.min(...t.map(e=>e.length-e.trimStart().length));for(let e of t.map(e=>e.slice(r)).map(e=>" ".repeat(2*this.indent)+e))this.content.push(e)}compile(){return Function(...this?.args,[...(this?.content??[""]).map(e=>` ${e}`)].join("\n"))}}let ey=(e,t)=>{e.name="$ZodError",Object.defineProperty(e,"_zod",{value:e._zod,enumerable:!1}),Object.defineProperty(e,"issues",{value:t,enumerable:!1}),Object.defineProperty(e,"message",{get:()=>JSON.stringify(t,R,2),enumerable:!0}),Object.defineProperty(e,"toString",{value:()=>e.message,enumerable:!1})},eb=n("$ZodError",ey),ew=n("$ZodError",ey,{Parent:Error}),ex=e=>(t,r,n)=>{let o=n?{...n,async:!1}:{async:!1},l=t._zod.run({value:r,issues:[]},o);if(l instanceof Promise)throw new i;return l.issues.length?{success:!1,error:new(e??eb)(l.issues.map(e=>X(e,o,a())))}:{success:!0,data:l.value}},eS=ex(ew),eO=e=>async(t,r,n)=>{let i=n?Object.assign(n,{async:!0}):{async:!0},o=t._zod.run({value:r,issues:[]},i);return o instanceof Promise&&(o=await o),o.issues.length?{success:!1,error:new e(o.issues.map(e=>X(e,i,a())))}:{success:!0,data:o.value}},eE=eO(ew),eC={major:4,minor:0,patch:5},eM=n("$ZodType",(e,t)=>{var r;e??(e={}),e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=eC;let n=[...e._zod.def.checks??[]];for(let t of(e._zod.traits.has("$ZodCheck")&&n.unshift(e),n))for(let r of t._zod.onattach)r(e);if(0===n.length)(r=e._zod).deferred??(r.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,r)=>{let n,o=K(e);for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(o)continue;let t=e.issues.length,l=a._zod.check(e);if(l instanceof Promise&&r?.async===!1)throw new i;if(n||l instanceof Promise)n=(n??Promise.resolve()).then(async()=>{await l,e.issues.length!==t&&(o||(o=K(e,t)))});else{if(e.issues.length===t)continue;o||(o=K(e,t))}}return n?n.then(()=>e):e};e._zod.run=(r,o)=>{let a=e._zod.parse(r,o);if(a instanceof Promise){if(!1===o.async)throw new i;return a.then(e=>t(e,n,o))}return t(a,n,o)}}e["~standard"]={validate:t=>{try{let r=eS(e,t);return r.success?{value:r.data}:{issues:r.error?.issues}}catch(r){return eE(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:"zod",version:1}}),ek=n("$ZodString",(e,t)=>{eM.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??(e=>{let t=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??""}}`:"[\\s\\S]*";return RegExp(`^${t}$`)})(e._zod.bag),e._zod.parse=(r,n)=>{if(t.coerce)try{r.value=String(r.value)}catch(e){}return"string"==typeof r.value||r.issues.push({expected:"string",code:"invalid_type",input:r.value,inst:e}),r}}),e_=n("$ZodStringFormat",(e,t)=>{es.init(e,t),ek.init(e,t)}),eP=n("$ZodGUID",(e,t)=>{t.pattern??(t.pattern=h),e_.init(e,t)}),eA=n("$ZodUUID",(e,t)=>{if(t.version){let e={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[t.version];if(void 0===e)throw Error(`Invalid UUID version: "${t.version}"`);t.pattern??(t.pattern=g(e))}else t.pattern??(t.pattern=g());e_.init(e,t)}),ej=n("$ZodEmail",(e,t)=>{t.pattern??(t.pattern=v),e_.init(e,t)}),eT=n("$ZodURL",(e,t)=>{e_.init(e,t),e._zod.check=r=>{try{let n=r.value,i=new URL(n),o=i.href;t.hostname&&(t.hostname.lastIndex=0,t.hostname.test(i.hostname)||r.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:O.source,input:r.value,inst:e,continue:!t.abort})),t.protocol&&(t.protocol.lastIndex=0,t.protocol.test(i.protocol.endsWith(":")?i.protocol.slice(0,-1):i.protocol)||r.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:t.protocol.source,input:r.value,inst:e,continue:!t.abort})),!n.endsWith("/")&&o.endsWith("/")?r.value=o.slice(0,-1):r.value=o;return}catch(n){r.issues.push({code:"invalid_format",format:"url",input:r.value,inst:e,continue:!t.abort})}}}),eR=n("$ZodEmoji",(e,t)=>{t.pattern??(t.pattern=RegExp("^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$","u")),e_.init(e,t)}),ez=n("$ZodNanoID",(e,t)=>{t.pattern??(t.pattern=d),e_.init(e,t)}),eD=n("$ZodCUID",(e,t)=>{t.pattern??(t.pattern=l),e_.init(e,t)}),eI=n("$ZodCUID2",(e,t)=>{t.pattern??(t.pattern=u),e_.init(e,t)}),eN=n("$ZodULID",(e,t)=>{t.pattern??(t.pattern=s),e_.init(e,t)}),eL=n("$ZodXID",(e,t)=>{t.pattern??(t.pattern=c),e_.init(e,t)}),eF=n("$ZodKSUID",(e,t)=>{t.pattern??(t.pattern=f),e_.init(e,t)}),e$=n("$ZodISODateTime",(e,t)=>{t.pattern??(t.pattern=function(e){let t=k({precision:e.precision}),r=["Z"];e.local&&r.push(""),e.offset&&r.push("([+-]\\d{2}:\\d{2})");let n=`${t}(?:${r.join("|")})`;return RegExp(`^${C}T(?:${n})$`)}(t)),e_.init(e,t)}),eB=n("$ZodISODate",(e,t)=>{t.pattern??(t.pattern=M),e_.init(e,t)}),eV=n("$ZodISOTime",(e,t)=>{t.pattern??(t.pattern=RegExp(`^${k(t)}$`)),e_.init(e,t)}),eU=n("$ZodISODuration",(e,t)=>{t.pattern??(t.pattern=p),e_.init(e,t)}),eH=n("$ZodIPv4",(e,t)=>{t.pattern??(t.pattern=m),e_.init(e,t),e._zod.onattach.push(e=>{e._zod.bag.format="ipv4"})}),eG=n("$ZodIPv6",(e,t)=>{t.pattern??(t.pattern=y),e_.init(e,t),e._zod.onattach.push(e=>{e._zod.bag.format="ipv6"}),e._zod.check=r=>{try{new URL(`http://[${r.value}]`)}catch{r.issues.push({code:"invalid_format",format:"ipv6",input:r.value,inst:e,continue:!t.abort})}}}),eZ=n("$ZodCIDRv4",(e,t)=>{t.pattern??(t.pattern=b),e_.init(e,t)}),eW=n("$ZodCIDRv6",(e,t)=>{t.pattern??(t.pattern=w),e_.init(e,t),e._zod.check=r=>{let[n,i]=r.value.split("/");try{if(!i)throw Error();let e=Number(i);if(`${e}`!==i||e<0||e>128)throw Error();new URL(`http://[${n}]`)}catch{r.issues.push({code:"invalid_format",format:"cidrv6",input:r.value,inst:e,continue:!t.abort})}}});function eK(e){if(""===e)return!0;if(e.length%4!=0)return!1;try{return atob(e),!0}catch{return!1}}let eq=n("$ZodBase64",(e,t)=>{t.pattern??(t.pattern=x),e_.init(e,t),e._zod.onattach.push(e=>{e._zod.bag.contentEncoding="base64"}),e._zod.check=r=>{eK(r.value)||r.issues.push({code:"invalid_format",format:"base64",input:r.value,inst:e,continue:!t.abort})}}),eY=n("$ZodBase64URL",(e,t)=>{t.pattern??(t.pattern=S),e_.init(e,t),e._zod.onattach.push(e=>{e._zod.bag.contentEncoding="base64url"}),e._zod.check=r=>{!function(e){if(!S.test(e))return!1;let t=e.replace(/[-_]/g,e=>"-"===e?"+":"/");return eK(t.padEnd(4*Math.ceil(t.length/4),"="))}(r.value)&&r.issues.push({code:"invalid_format",format:"base64url",input:r.value,inst:e,continue:!t.abort})}}),eX=n("$ZodE164",(e,t)=>{t.pattern??(t.pattern=E),e_.init(e,t)}),eJ=n("$ZodJWT",(e,t)=>{e_.init(e,t),e._zod.check=r=>{!function(e,t=null){try{let r=e.split(".");if(3!==r.length)return!1;let[n]=r;if(!n)return!1;let i=JSON.parse(atob(n));if("typ"in i&&i?.typ!=="JWT"||!i.alg||t&&(!("alg"in i)||i.alg!==t))return!1;return!0}catch{return!1}}(r.value,t.alg)&&r.issues.push({code:"invalid_format",format:"jwt",input:r.value,inst:e,continue:!t.abort})}}),eQ=n("$ZodNumber",(e,t)=>{eM.init(e,t),e._zod.pattern=e._zod.bag.pattern??P,e._zod.parse=(r,n)=>{if(t.coerce)try{r.value=Number(r.value)}catch(e){}let i=r.value;if("number"==typeof i&&!Number.isNaN(i)&&Number.isFinite(i))return r;let o="number"==typeof i?Number.isNaN(i)?"NaN":Number.isFinite(i)?void 0:"Infinity":void 0;return r.issues.push({expected:"number",code:"invalid_type",input:i,inst:e,...o?{received:o}:{}}),r}}),e0=n("$ZodNumber",(e,t)=>{eo.init(e,t),eQ.init(e,t)}),e1=n("$ZodBoolean",(e,t)=>{eM.init(e,t),e._zod.pattern=A,e._zod.parse=(r,n)=>{if(t.coerce)try{r.value=!!r.value}catch(e){}let i=r.value;return"boolean"==typeof i||r.issues.push({expected:"boolean",code:"invalid_type",input:i,inst:e}),r}}),e2=n("$ZodUnknown",(e,t)=>{eM.init(e,t),e._zod.parse=e=>e}),e5=n("$ZodNever",(e,t)=>{eM.init(e,t),e._zod.parse=(t,r)=>(t.issues.push({expected:"never",code:"invalid_type",input:t.value,inst:e}),t)});function e6(e,t,r){e.issues.length&&t.issues.push(...q(r,e.issues)),t.value[r]=e.value}let e4=n("$ZodArray",(e,t)=>{eM.init(e,t),e._zod.parse=(r,n)=>{let i=r.value;if(!Array.isArray(i))return r.issues.push({expected:"array",code:"invalid_type",input:i,inst:e}),r;r.value=Array(i.length);let o=[];for(let e=0;ee6(t,r,e))):e6(l,r,e)}return o.length?Promise.all(o).then(()=>r):r}});function e3(e,t,r){e.issues.length&&t.issues.push(...q(r,e.issues)),t.value[r]=e.value}function e8(e,t,r,n){e.issues.length?void 0===n[r]?r in n?t.value[r]=void 0:t.value[r]=e.value:t.issues.push(...q(r,e.issues)):void 0===e.value?r in n&&(t.value[r]=void 0):t.value[r]=e.value}let e9=n("$ZodObject",(e,t)=>{let r,n;eM.init(e,t);let i=z(()=>{let e=Object.keys(t.shape);for(let r of e)if(!(t.shape[r]instanceof eM))throw Error(`Invalid element at key "${r}": expected a Zod schema`);let r=function(e){return Object.keys(e).filter(t=>"optional"===e[t]._zod.optin&&"optional"===e[t]._zod.optout)}(t.shape);return{shape:t.shape,keys:e,keySet:new Set(e),numKeys:e.length,optionalKeys:new Set(r)}});I(e._zod,"propValues",()=>{let e=t.shape,r={};for(let t in e){let n=e[t]._zod;if(n.values)for(let e of(r[t]??(r[t]=new Set),n.values))r[t].add(e)}return r});let a=!o.jitless,l=a&&B.value,u=t.catchall;e._zod.parse=(o,s)=>{n??(n=i.value);let c=o.value;if(!$(c))return o.issues.push({expected:"object",code:"invalid_type",input:c,inst:e}),o;let f=[];if(a&&l&&s?.async===!1&&!0!==s.jitless)r||(r=(e=>{let t=new em(["shape","payload","ctx"]),r=i.value,n=e=>{let t=L(e);return`shape[${t}]._zod.run({ value: input[${t}], issues: [] }, ctx)`};t.write("const input = payload.value;");let o=Object.create(null),a=0;for(let e of r.keys)o[e]=`key_${a++}`;for(let e of(t.write("const newResult = {}"),r.keys))if(r.optionalKeys.has(e)){let r=o[e];t.write(`const ${r} = ${n(e)};`);let i=L(e);t.write(` - if (${r}.issues.length) { - if (input[${i}] === undefined) { - if (${i} in input) { - newResult[${i}] = undefined; - } - } else { - payload.issues = payload.issues.concat( - ${r}.issues.map((iss) => ({ - ...iss, - path: iss.path ? [${i}, ...iss.path] : [${i}], - })) - ); - } - } else if (${r}.value === undefined) { - if (${i} in input) newResult[${i}] = undefined; - } else { - newResult[${i}] = ${r}.value; - } - `)}else{let r=o[e];t.write(`const ${r} = ${n(e)};`),t.write(` - if (${r}.issues.length) payload.issues = payload.issues.concat(${r}.issues.map(iss => ({ - ...iss, - path: iss.path ? [${L(e)}, ...iss.path] : [${L(e)}] - })));`),t.write(`newResult[${L(e)}] = ${r}.value`)}t.write("payload.value = newResult;"),t.write("return payload;");let l=t.compile();return(t,r)=>l(e,t,r)})(t.shape)),o=r(o,s);else{o.value={};let e=n.shape;for(let t of n.keys){let r=e[t],n=r._zod.run({value:c[t],issues:[]},s),i="optional"===r._zod.optin&&"optional"===r._zod.optout;n instanceof Promise?f.push(n.then(e=>i?e8(e,o,t,c):e3(e,o,t))):i?e8(n,o,t,c):e3(n,o,t)}}if(!u)return f.length?Promise.all(f).then(()=>o):o;let d=[],p=n.keySet,h=u._zod,g=h.def.type;for(let e of Object.keys(c)){if(p.has(e))continue;if("never"===g){d.push(e);continue}let t=h.run({value:c[e],issues:[]},s);t instanceof Promise?f.push(t.then(t=>e3(t,o,e))):e3(t,o,e)}return(d.length&&o.issues.push({code:"unrecognized_keys",keys:d,input:c,inst:e}),f.length)?Promise.all(f).then(()=>o):o}});function e7(e,t,r,n){for(let r of e)if(0===r.issues.length)return t.value=r.value,t;return t.issues.push({code:"invalid_union",input:t.value,inst:r,errors:e.map(e=>e.issues.map(e=>X(e,n,a())))}),t}let te=n("$ZodUnion",(e,t)=>{eM.init(e,t),I(e._zod,"optin",()=>t.options.some(e=>"optional"===e._zod.optin)?"optional":void 0),I(e._zod,"optout",()=>t.options.some(e=>"optional"===e._zod.optout)?"optional":void 0),I(e._zod,"values",()=>{if(t.options.every(e=>e._zod.values))return new Set(t.options.flatMap(e=>Array.from(e._zod.values)))}),I(e._zod,"pattern",()=>{if(t.options.every(e=>e._zod.pattern)){let e=t.options.map(e=>e._zod.pattern);return RegExp(`^(${e.map(e=>D(e.source)).join("|")})$`)}}),e._zod.parse=(r,n)=>{let i=!1,o=[];for(let e of t.options){let t=e._zod.run({value:r.value,issues:[]},n);if(t instanceof Promise)o.push(t),i=!0;else{if(0===t.issues.length)return t;o.push(t)}}return i?Promise.all(o).then(t=>e7(t,r,e,n)):e7(o,r,e,n)}}),tt=n("$ZodIntersection",(e,t)=>{eM.init(e,t),e._zod.parse=(e,r)=>{let n=e.value,i=t.left._zod.run({value:n,issues:[]},r),o=t.right._zod.run({value:n,issues:[]},r);return i instanceof Promise||o instanceof Promise?Promise.all([i,o]).then(([t,r])=>tr(e,t,r)):tr(e,i,o)}});function tr(e,t,r){if(t.issues.length&&e.issues.push(...t.issues),r.issues.length&&e.issues.push(...r.issues),K(e))return e;let n=function e(t,r){if(t===r||t instanceof Date&&r instanceof Date&&+t==+r)return{valid:!0,data:t};if(V(t)&&V(r)){let n=Object.keys(r),i=Object.keys(t).filter(e=>-1!==n.indexOf(e)),o={...t,...r};for(let n of i){let i=e(t[n],r[n]);if(!i.valid)return{valid:!1,mergeErrorPath:[n,...i.mergeErrorPath]};o[n]=i.data}return{valid:!0,data:o}}if(Array.isArray(t)&&Array.isArray(r)){if(t.length!==r.length)return{valid:!1,mergeErrorPath:[]};let n=[];for(let i=0;i{eM.init(e,t),e._zod.parse=(r,n)=>{let i=r.value;if(!V(i))return r.issues.push({expected:"record",code:"invalid_type",input:i,inst:e}),r;let o=[];if(t.keyType._zod.values){let a,l=t.keyType._zod.values;for(let e of(r.value={},l))if("string"==typeof e||"number"==typeof e||"symbol"==typeof e){let a=t.valueType._zod.run({value:i[e],issues:[]},n);a instanceof Promise?o.push(a.then(t=>{t.issues.length&&r.issues.push(...q(e,t.issues)),r.value[e]=t.value})):(a.issues.length&&r.issues.push(...q(e,a.issues)),r.value[e]=a.value)}for(let e in i)l.has(e)||(a=a??[]).push(e);a&&a.length>0&&r.issues.push({code:"unrecognized_keys",input:i,inst:e,keys:a})}else for(let l of(r.value={},Reflect.ownKeys(i))){if("__proto__"===l)continue;let u=t.keyType._zod.run({value:l,issues:[]},n);if(u instanceof Promise)throw Error("Async schemas not supported in object keys currently");if(u.issues.length){r.issues.push({origin:"record",code:"invalid_key",issues:u.issues.map(e=>X(e,n,a())),input:l,path:[l],inst:e}),r.value[u.value]=u.value;continue}let s=t.valueType._zod.run({value:i[l],issues:[]},n);s instanceof Promise?o.push(s.then(e=>{e.issues.length&&r.issues.push(...q(l,e.issues)),r.value[u.value]=e.value})):(s.issues.length&&r.issues.push(...q(l,s.issues)),r.value[u.value]=s.value)}return o.length?Promise.all(o).then(()=>r):r}}),ti=n("$ZodEnum",(e,t)=>{eM.init(e,t);let r=function(e){let t=Object.values(e).filter(e=>"number"==typeof e);return Object.entries(e).filter(([e,r])=>-1===t.indexOf(+e)).map(([e,t])=>t)}(t.entries);e._zod.values=new Set(r),e._zod.pattern=RegExp(`^(${r.filter(e=>U.has(typeof e)).map(e=>"string"==typeof e?H(e):e.toString()).join("|")})$`),e._zod.parse=(t,n)=>{let i=t.value;return e._zod.values.has(i)||t.issues.push({code:"invalid_value",values:r,input:i,inst:e}),t}}),to=n("$ZodTransform",(e,t)=>{eM.init(e,t),e._zod.parse=(e,r)=>{let n=t.transform(e.value,e);if(r.async)return(n instanceof Promise?n:Promise.resolve(n)).then(t=>(e.value=t,e));if(n instanceof Promise)throw new i;return e.value=n,e}}),ta=n("$ZodOptional",(e,t)=>{eM.init(e,t),e._zod.optin="optional",e._zod.optout="optional",I(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),I(e._zod,"pattern",()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${D(e.source)})?$`):void 0}),e._zod.parse=(e,r)=>"optional"===t.innerType._zod.optin?t.innerType._zod.run(e,r):void 0===e.value?e:t.innerType._zod.run(e,r)}),tl=n("$ZodNullable",(e,t)=>{eM.init(e,t),I(e._zod,"optin",()=>t.innerType._zod.optin),I(e._zod,"optout",()=>t.innerType._zod.optout),I(e._zod,"pattern",()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${D(e.source)}|null)$`):void 0}),I(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(e,r)=>null===e.value?e:t.innerType._zod.run(e,r)}),tu=n("$ZodDefault",(e,t)=>{eM.init(e,t),e._zod.optin="optional",I(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(e,r)=>{if(void 0===e.value)return e.value=t.defaultValue,e;let n=t.innerType._zod.run(e,r);return n instanceof Promise?n.then(e=>ts(e,t)):ts(n,t)}});function ts(e,t){return void 0===e.value&&(e.value=t.defaultValue),e}let tc=n("$ZodPrefault",(e,t)=>{eM.init(e,t),e._zod.optin="optional",I(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(e,r)=>(void 0===e.value&&(e.value=t.defaultValue),t.innerType._zod.run(e,r))}),tf=n("$ZodNonOptional",(e,t)=>{eM.init(e,t),I(e._zod,"values",()=>{let e=t.innerType._zod.values;return e?new Set([...e].filter(e=>void 0!==e)):void 0}),e._zod.parse=(r,n)=>{let i=t.innerType._zod.run(r,n);return i instanceof Promise?i.then(t=>td(t,e)):td(i,e)}});function td(e,t){return e.issues.length||void 0!==e.value||e.issues.push({code:"invalid_type",expected:"nonoptional",input:e.value,inst:t}),e}let tp=n("$ZodCatch",(e,t)=>{eM.init(e,t),e._zod.optin="optional",I(e._zod,"optout",()=>t.innerType._zod.optout),I(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(e,r)=>{let n=t.innerType._zod.run(e,r);return n instanceof Promise?n.then(n=>(e.value=n.value,n.issues.length&&(e.value=t.catchValue({...e,error:{issues:n.issues.map(e=>X(e,r,a()))},input:e.value}),e.issues=[]),e)):(e.value=n.value,n.issues.length&&(e.value=t.catchValue({...e,error:{issues:n.issues.map(e=>X(e,r,a()))},input:e.value}),e.issues=[]),e)}}),th=n("$ZodPipe",(e,t)=>{eM.init(e,t),I(e._zod,"values",()=>t.in._zod.values),I(e._zod,"optin",()=>t.in._zod.optin),I(e._zod,"optout",()=>t.out._zod.optout),I(e._zod,"propValues",()=>t.in._zod.propValues),e._zod.parse=(e,r)=>{let n=t.in._zod.run(e,r);return n instanceof Promise?n.then(e=>tg(e,t,r)):tg(n,t,r)}});function tg(e,t,r){return K(e)?e:t.out._zod.run({value:e.value,issues:e.issues},r)}let tv=n("$ZodReadonly",(e,t)=>{eM.init(e,t),I(e._zod,"propValues",()=>t.innerType._zod.propValues),I(e._zod,"values",()=>t.innerType._zod.values),I(e._zod,"optin",()=>t.innerType._zod.optin),I(e._zod,"optout",()=>t.innerType._zod.optout),e._zod.parse=(e,r)=>{let n=t.innerType._zod.run(e,r);return n instanceof Promise?n.then(tm):tm(n)}});function tm(e){return e.value=Object.freeze(e.value),e}let ty=n("$ZodCustom",(e,t)=>{ee.init(e,t),eM.init(e,t),e._zod.parse=(e,t)=>e,e._zod.check=r=>{let n=r.value,i=t.fn(n);if(i instanceof Promise)return i.then(t=>tb(t,r,n,e));tb(i,r,n,e)}});function tb(e,t,r,n){if(!e){let e={code:"custom",input:r,inst:n,path:[...n._zod.def.path??[]],continue:!n._zod.def.abort};n._zod.def.params&&(e.params=n._zod.def.params),t.issues.push(Q(e))}}Symbol("ZodOutput"),Symbol("ZodInput");class tw{constructor(){this._map=new Map,this._idmap=new Map}add(e,...t){let r=t[0];if(this._map.set(e,r),r&&"object"==typeof r&&"id"in r){if(this._idmap.has(r.id))throw Error(`ID ${r.id} already exists in the registry`);this._idmap.set(r.id,e)}return this}clear(){return this._map=new Map,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&"object"==typeof t&&"id"in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let r={...this.get(t)??{}};return delete r.id,{...r,...this._map.get(e)}}return this._map.get(e)}has(e){return this._map.has(e)}}let tx=new tw;function tS(e,t){return new e({type:"string",format:"guid",check:"string_format",abort:!1,...Z(t)})}function tO(e,t){return new er({check:"less_than",...Z(t),value:e,inclusive:!1})}function tE(e,t){return new er({check:"less_than",...Z(t),value:e,inclusive:!0})}function tC(e,t){return new en({check:"greater_than",...Z(t),value:e,inclusive:!1})}function tM(e,t){return new en({check:"greater_than",...Z(t),value:e,inclusive:!0})}function tk(e,t){return new ei({check:"multiple_of",...Z(t),value:e})}function t_(e,t){return new ea({check:"max_length",...Z(t),maximum:e})}function tP(e,t){return new el({check:"min_length",...Z(t),minimum:e})}function tA(e,t){return new eu({check:"length_equals",...Z(t),length:e})}function tj(e){return new ev({check:"overwrite",tx:e})}let tT=n("ZodISODateTime",(e,t)=>{e$.init(e,t),tZ.init(e,t)}),tR=n("ZodISODate",(e,t)=>{eB.init(e,t),tZ.init(e,t)}),tz=n("ZodISOTime",(e,t)=>{eV.init(e,t),tZ.init(e,t)}),tD=n("ZodISODuration",(e,t)=>{eU.init(e,t),tZ.init(e,t)}),tI=(e,t)=>{eb.init(e,t),e.name="ZodError",Object.defineProperties(e,{format:{value:t=>(function(e,t){let r=t||function(e){return e.message},n={_errors:[]},i=e=>{for(let t of e.issues)if("invalid_union"===t.code&&t.errors.length)t.errors.map(e=>i({issues:e}));else if("invalid_key"===t.code)i({issues:t.issues});else if("invalid_element"===t.code)i({issues:t.issues});else if(0===t.path.length)n._errors.push(r(t));else{let e=n,i=0;for(;i(function(e,t=e=>e.message){let r={},n=[];for(let i of e.issues)i.path.length>0?(r[i.path[0]]=r[i.path[0]]||[],r[i.path[0]].push(t(i))):n.push(t(i));return{formErrors:n,fieldErrors:r}})(e,t)},addIssue:{value:t=>e.issues.push(t)},addIssues:{value:t=>e.issues.push(...t)},isEmpty:{get:()=>0===e.issues.length}})};n("ZodError",tI);let tN=n("ZodError",tI,{Parent:Error}),tL=(e,t,r,n)=>{let o=r?Object.assign(r,{async:!1}):{async:!1},l=e._zod.run({value:t,issues:[]},o);if(l instanceof Promise)throw new i;if(l.issues.length){let e=new(n?.Err??tN)(l.issues.map(e=>X(e,o,a())));throw F(e,n?.callee),e}return l.value},tF=async(e,t,r,n)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},o=e._zod.run({value:t,issues:[]},i);if(o instanceof Promise&&(o=await o),o.issues.length){let e=new(n?.Err??tN)(o.issues.map(e=>X(e,i,a())));throw F(e,n?.callee),e}return o.value},t$=ex(tN),tB=eO(tN),tV=n("ZodType",(e,t)=>(eM.init(e,t),e.def=t,Object.defineProperty(e,"_def",{value:t}),e.check=(...r)=>e.clone({...t,checks:[...t.checks??[],...r.map(e=>"function"==typeof e?{_zod:{check:e,def:{check:"custom"},onattach:[]}}:e)]}),e.clone=(t,r)=>G(e,t,r),e.brand=()=>e,e.register=(t,r)=>(t.add(e,r),e),e.parse=(t,r)=>tL(e,t,r,{callee:e.parse}),e.safeParse=(t,r)=>t$(e,t,r),e.parseAsync=async(t,r)=>tF(e,t,r,{callee:e.parseAsync}),e.safeParseAsync=async(t,r)=>tB(e,t,r),e.spa=e.safeParseAsync,e.refine=(t,r)=>e.check(function(e,t={}){return new rR({type:"custom",check:"custom",fn:e,...Z(t)})}(t,r)),e.superRefine=t=>e.check(function(e){let t=function(e){let t=new ee({check:"custom"});return t._zod.check=e,t}(r=>(r.addIssue=e=>{"string"==typeof e?r.issues.push(Q(e,r.value,t._zod.def)):(e.fatal&&(e.continue=!1),e.code??(e.code="custom"),e.input??(e.input=r.value),e.inst??(e.inst=t),e.continue??(e.continue=!t._zod.def.abort),r.issues.push(Q(e)))},e(r.value,r)));return t}(t)),e.overwrite=t=>e.check(tj(t)),e.optional=()=>rO(e),e.nullable=()=>rC(e),e.nullish=()=>rO(rC(e)),e.nonoptional=t=>new r_({type:"nonoptional",innerType:e,...Z(t)}),e.array=()=>rd(e),e.or=t=>new rg({type:"union",options:[e,t],...Z(void 0)}),e.and=t=>new rv({type:"intersection",left:e,right:t}),e.transform=t=>rj(e,new rx({type:"transform",transform:t})),e.default=t=>(function(e,t){return new rM({type:"default",innerType:e,get defaultValue(){return"function"==typeof t?t():t}})})(e,t),e.prefault=t=>(function(e,t){return new rk({type:"prefault",innerType:e,get defaultValue(){return"function"==typeof t?t():t}})})(e,t),e.catch=t=>(function(e,t){return new rP({type:"catch",innerType:e,catchValue:"function"==typeof t?t:()=>t})})(e,t),e.pipe=t=>rj(e,t),e.readonly=()=>new rT({type:"readonly",innerType:e}),e.describe=t=>{let r=e.clone();return tx.add(r,{description:t}),r},Object.defineProperty(e,"description",{get:()=>tx.get(e)?.description,configurable:!0}),e.meta=(...t)=>{if(0===t.length)return tx.get(e);let r=e.clone();return tx.add(r,t[0]),r},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e)),tU=n("_ZodString",(e,t)=>{ek.init(e,t),tV.init(e,t);let r=e._zod.bag;e.format=r.format??null,e.minLength=r.minimum??null,e.maxLength=r.maximum??null,e.regex=(...t)=>e.check(function(e,t){return new ec({check:"string_format",format:"regex",...Z(t),pattern:e})}(...t)),e.includes=(...t)=>e.check(function(e,t){return new ep({check:"string_format",format:"includes",...Z(t),includes:e})}(...t)),e.startsWith=(...t)=>e.check(function(e,t){return new eh({check:"string_format",format:"starts_with",...Z(t),prefix:e})}(...t)),e.endsWith=(...t)=>e.check(function(e,t){return new eg({check:"string_format",format:"ends_with",...Z(t),suffix:e})}(...t)),e.min=(...t)=>e.check(tP(...t)),e.max=(...t)=>e.check(t_(...t)),e.length=(...t)=>e.check(tA(...t)),e.nonempty=(...t)=>e.check(tP(1,...t)),e.lowercase=t=>e.check(new ef({check:"string_format",format:"lowercase",...Z(t)})),e.uppercase=t=>e.check(new ed({check:"string_format",format:"uppercase",...Z(t)})),e.trim=()=>e.check(tj(e=>e.trim())),e.normalize=(...t)=>e.check(function(e){return tj(t=>t.normalize(e))}(...t)),e.toLowerCase=()=>e.check(tj(e=>e.toLowerCase())),e.toUpperCase=()=>e.check(tj(e=>e.toUpperCase()))}),tH=n("ZodString",(e,t)=>{ek.init(e,t),tU.init(e,t),e.email=t=>e.check(new tW({type:"string",format:"email",check:"string_format",abort:!1,...Z(t)})),e.url=t=>e.check(new tY({type:"string",format:"url",check:"string_format",abort:!1,...Z(t)})),e.jwt=t=>e.check(new rt({type:"string",format:"jwt",check:"string_format",abort:!1,...Z(t)})),e.emoji=t=>e.check(new tX({type:"string",format:"emoji",check:"string_format",abort:!1,...Z(t)})),e.guid=t=>e.check(tS(tK,t)),e.uuid=t=>e.check(new tq({type:"string",format:"uuid",check:"string_format",abort:!1,...Z(t)})),e.uuidv4=t=>e.check(new tq({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",...Z(t)})),e.uuidv6=t=>e.check(new tq({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v6",...Z(t)})),e.uuidv7=t=>e.check(new tq({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v7",...Z(t)})),e.nanoid=t=>e.check(new tJ({type:"string",format:"nanoid",check:"string_format",abort:!1,...Z(t)})),e.guid=t=>e.check(tS(tK,t)),e.cuid=t=>e.check(new tQ({type:"string",format:"cuid",check:"string_format",abort:!1,...Z(t)})),e.cuid2=t=>e.check(new t0({type:"string",format:"cuid2",check:"string_format",abort:!1,...Z(t)})),e.ulid=t=>e.check(new t1({type:"string",format:"ulid",check:"string_format",abort:!1,...Z(t)})),e.base64=t=>e.check(new t9({type:"string",format:"base64",check:"string_format",abort:!1,...Z(t)})),e.base64url=t=>e.check(new t7({type:"string",format:"base64url",check:"string_format",abort:!1,...Z(t)})),e.xid=t=>e.check(new t2({type:"string",format:"xid",check:"string_format",abort:!1,...Z(t)})),e.ksuid=t=>e.check(new t5({type:"string",format:"ksuid",check:"string_format",abort:!1,...Z(t)})),e.ipv4=t=>e.check(new t6({type:"string",format:"ipv4",check:"string_format",abort:!1,...Z(t)})),e.ipv6=t=>e.check(new t4({type:"string",format:"ipv6",check:"string_format",abort:!1,...Z(t)})),e.cidrv4=t=>e.check(new t3({type:"string",format:"cidrv4",check:"string_format",abort:!1,...Z(t)})),e.cidrv6=t=>e.check(new t8({type:"string",format:"cidrv6",check:"string_format",abort:!1,...Z(t)})),e.e164=t=>e.check(new re({type:"string",format:"e164",check:"string_format",abort:!1,...Z(t)})),e.datetime=t=>e.check(new tT({type:"string",format:"datetime",check:"string_format",offset:!1,local:!1,precision:null,...Z(t)})),e.date=t=>e.check(new tR({type:"string",format:"date",check:"string_format",...Z(t)})),e.time=t=>e.check(new tz({type:"string",format:"time",check:"string_format",precision:null,...Z(t)})),e.duration=t=>e.check(new tD({type:"string",format:"duration",check:"string_format",...Z(t)}))});function tG(e){return new tH({type:"string",...Z(e)})}let tZ=n("ZodStringFormat",(e,t)=>{e_.init(e,t),tU.init(e,t)}),tW=n("ZodEmail",(e,t)=>{ej.init(e,t),tZ.init(e,t)}),tK=n("ZodGUID",(e,t)=>{eP.init(e,t),tZ.init(e,t)}),tq=n("ZodUUID",(e,t)=>{eA.init(e,t),tZ.init(e,t)}),tY=n("ZodURL",(e,t)=>{eT.init(e,t),tZ.init(e,t)}),tX=n("ZodEmoji",(e,t)=>{eR.init(e,t),tZ.init(e,t)}),tJ=n("ZodNanoID",(e,t)=>{ez.init(e,t),tZ.init(e,t)}),tQ=n("ZodCUID",(e,t)=>{eD.init(e,t),tZ.init(e,t)}),t0=n("ZodCUID2",(e,t)=>{eI.init(e,t),tZ.init(e,t)}),t1=n("ZodULID",(e,t)=>{eN.init(e,t),tZ.init(e,t)}),t2=n("ZodXID",(e,t)=>{eL.init(e,t),tZ.init(e,t)}),t5=n("ZodKSUID",(e,t)=>{eF.init(e,t),tZ.init(e,t)}),t6=n("ZodIPv4",(e,t)=>{eH.init(e,t),tZ.init(e,t)}),t4=n("ZodIPv6",(e,t)=>{eG.init(e,t),tZ.init(e,t)}),t3=n("ZodCIDRv4",(e,t)=>{eZ.init(e,t),tZ.init(e,t)}),t8=n("ZodCIDRv6",(e,t)=>{eW.init(e,t),tZ.init(e,t)}),t9=n("ZodBase64",(e,t)=>{eq.init(e,t),tZ.init(e,t)}),t7=n("ZodBase64URL",(e,t)=>{eY.init(e,t),tZ.init(e,t)}),re=n("ZodE164",(e,t)=>{eX.init(e,t),tZ.init(e,t)}),rt=n("ZodJWT",(e,t)=>{eJ.init(e,t),tZ.init(e,t)}),rr=n("ZodNumber",(e,t)=>{eQ.init(e,t),tV.init(e,t),e.gt=(t,r)=>e.check(tC(t,r)),e.gte=(t,r)=>e.check(tM(t,r)),e.min=(t,r)=>e.check(tM(t,r)),e.lt=(t,r)=>e.check(tO(t,r)),e.lte=(t,r)=>e.check(tE(t,r)),e.max=(t,r)=>e.check(tE(t,r)),e.int=t=>e.check(ro(t)),e.safe=t=>e.check(ro(t)),e.positive=t=>e.check(tC(0,t)),e.nonnegative=t=>e.check(tM(0,t)),e.negative=t=>e.check(tO(0,t)),e.nonpositive=t=>e.check(tE(0,t)),e.multipleOf=(t,r)=>e.check(tk(t,r)),e.step=(t,r)=>e.check(tk(t,r)),e.finite=()=>e;let r=e._zod.bag;e.minValue=Math.max(r.minimum??-1/0,r.exclusiveMinimum??-1/0)??null,e.maxValue=Math.min(r.maximum??1/0,r.exclusiveMaximum??1/0)??null,e.isInt=(r.format??"").includes("int")||Number.isSafeInteger(r.multipleOf??.5),e.isFinite=!0,e.format=r.format??null});function rn(e){return new rr({type:"number",checks:[],...Z(e)})}let ri=n("ZodNumberFormat",(e,t)=>{e0.init(e,t),rr.init(e,t)});function ro(e){return new ri({type:"number",check:"number_format",abort:!1,format:"safeint",...Z(e)})}let ra=n("ZodBoolean",(e,t)=>{e1.init(e,t),tV.init(e,t)});function rl(e){return new ra({type:"boolean",...Z(e)})}let ru=n("ZodUnknown",(e,t)=>{e2.init(e,t),tV.init(e,t)});function rs(){return new ru({type:"unknown"})}let rc=n("ZodNever",(e,t)=>{e5.init(e,t),tV.init(e,t)}),rf=n("ZodArray",(e,t)=>{e4.init(e,t),tV.init(e,t),e.element=t.element,e.min=(t,r)=>e.check(tP(t,r)),e.nonempty=t=>e.check(tP(1,t)),e.max=(t,r)=>e.check(t_(t,r)),e.length=(t,r)=>e.check(tA(t,r)),e.unwrap=()=>e.element});function rd(e,t){return new rf({type:"array",element:e,...Z(t)})}let rp=n("ZodObject",(e,t)=>{e9.init(e,t),tV.init(e,t),I(e,"shape",()=>t.shape),e.keyof=()=>rw(Object.keys(e._zod.def.shape)),e.catchall=t=>e.clone({...e._zod.def,catchall:t}),e.passthrough=()=>e.clone({...e._zod.def,catchall:rs()}),e.loose=()=>e.clone({...e._zod.def,catchall:rs()}),e.strict=()=>e.clone({...e._zod.def,catchall:new rc({type:"never",...Z(void 0)})}),e.strip=()=>e.clone({...e._zod.def,catchall:void 0}),e.extend=t=>(function(e,t){if(!V(t))throw Error("Invalid input to extend: expected a plain object");let r={...e._zod.def,get shape(){let r={...e._zod.def.shape,...t};return N(this,"shape",r),r},checks:[]};return G(e,r)})(e,t),e.merge=t=>(function(e,t){return G(e,{...e._zod.def,get shape(){let r={...e._zod.def.shape,...t._zod.def.shape};return N(this,"shape",r),r},catchall:t._zod.def.catchall,checks:[]})})(e,t),e.pick=t=>(function(e,t){let r={},n=e._zod.def;for(let e in t){if(!(e in n.shape))throw Error(`Unrecognized key: "${e}"`);t[e]&&(r[e]=n.shape[e])}return G(e,{...e._zod.def,shape:r,checks:[]})})(e,t),e.omit=t=>(function(e,t){let r={...e._zod.def.shape},n=e._zod.def;for(let e in t){if(!(e in n.shape))throw Error(`Unrecognized key: "${e}"`);t[e]&&delete r[e]}return G(e,{...e._zod.def,shape:r,checks:[]})})(e,t),e.partial=(...t)=>(function(e,t,r){let n=t._zod.def.shape,i={...n};if(r)for(let t in r){if(!(t in n))throw Error(`Unrecognized key: "${t}"`);r[t]&&(i[t]=e?new e({type:"optional",innerType:n[t]}):n[t])}else for(let t in n)i[t]=e?new e({type:"optional",innerType:n[t]}):n[t];return G(t,{...t._zod.def,shape:i,checks:[]})})(rS,e,t[0]),e.required=(...t)=>(function(e,t,r){let n=t._zod.def.shape,i={...n};if(r)for(let t in r){if(!(t in i))throw Error(`Unrecognized key: "${t}"`);r[t]&&(i[t]=new e({type:"nonoptional",innerType:n[t]}))}else for(let t in n)i[t]=new e({type:"nonoptional",innerType:n[t]});return G(t,{...t._zod.def,shape:i,checks:[]})})(r_,e,t[0])});function rh(e,t){return new rp({type:"object",get shape(){return N(this,"shape",{...e}),this.shape},...Z(t)})}let rg=n("ZodUnion",(e,t)=>{te.init(e,t),tV.init(e,t),e.options=t.options}),rv=n("ZodIntersection",(e,t)=>{tt.init(e,t),tV.init(e,t)}),rm=n("ZodRecord",(e,t)=>{tn.init(e,t),tV.init(e,t),e.keyType=t.keyType,e.valueType=t.valueType});function ry(e,t,r){return new rm({type:"record",keyType:e,valueType:t,...Z(r)})}let rb=n("ZodEnum",(e,t)=>{ti.init(e,t),tV.init(e,t),e.enum=t.entries,e.options=Object.values(t.entries);let r=new Set(Object.keys(t.entries));e.extract=(e,n)=>{let i={};for(let n of e)if(r.has(n))i[n]=t.entries[n];else throw Error(`Key ${n} not found in enum`);return new rb({...t,checks:[],...Z(n),entries:i})},e.exclude=(e,n)=>{let i={...t.entries};for(let t of e)if(r.has(t))delete i[t];else throw Error(`Key ${t} not found in enum`);return new rb({...t,checks:[],...Z(n),entries:i})}});function rw(e,t){return new rb({type:"enum",entries:Array.isArray(e)?Object.fromEntries(e.map(e=>[e,e])):e,...Z(t)})}let rx=n("ZodTransform",(e,t)=>{to.init(e,t),tV.init(e,t),e._zod.parse=(r,n)=>{r.addIssue=n=>{"string"==typeof n?r.issues.push(Q(n,r.value,t)):(n.fatal&&(n.continue=!1),n.code??(n.code="custom"),n.input??(n.input=r.value),n.inst??(n.inst=e),n.continue??(n.continue=!0),r.issues.push(Q(n)))};let i=t.transform(r.value,r);return i instanceof Promise?i.then(e=>(r.value=e,r)):(r.value=i,r)}}),rS=n("ZodOptional",(e,t)=>{ta.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType});function rO(e){return new rS({type:"optional",innerType:e})}let rE=n("ZodNullable",(e,t)=>{tl.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType});function rC(e){return new rE({type:"nullable",innerType:e})}let rM=n("ZodDefault",(e,t)=>{tu.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap}),rk=n("ZodPrefault",(e,t)=>{tc.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType}),r_=n("ZodNonOptional",(e,t)=>{tf.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType}),rP=n("ZodCatch",(e,t)=>{tp.init(e,t),tV.init(e,t),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap}),rA=n("ZodPipe",(e,t)=>{th.init(e,t),tV.init(e,t),e.in=t.in,e.out=t.out});function rj(e,t){return new rA({type:"pipe",in:e,out:t})}let rT=n("ZodReadonly",(e,t)=>{tv.init(e,t),tV.init(e,t)}),rR=n("ZodCustom",(e,t)=>{ty.init(e,t),tV.init(e,t)})},5641:(e,t,r)=>{"use strict";function n(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function i(e){for(var t=1;ta,Kg:()=>o,lY:()=>l,yy:()=>u}),r(2115);var o=Math.PI/180,a=(e,t,r,n)=>({x:e+Math.cos(-o*n)*r,y:t+Math.sin(-o*n)*r}),l=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{top:0,right:0,bottom:0,left:0,width:0,height:0,brushBottom:0};return Math.min(Math.abs(e-(r.left||0)-(r.right||0)),Math.abs(t-(r.top||0)-(r.bottom||0)))/2},u=(e,t)=>{var r,{x:n,y:o}=e,{radius:a,angle:l}=((e,t)=>{var{x:r,y:n}=e,{cx:i,cy:o}=t,a=((e,t)=>{var{x:r,y:n}=e,{x:i,y:o}=t;return Math.sqrt((r-i)**2+(n-o)**2)})({x:r,y:n},{x:i,y:o});if(a<=0)return{radius:a,angle:0};var l=Math.acos((r-i)/a);return n>o&&(l=2*Math.PI-l),{radius:a,angle:180*l/Math.PI,angleInRadian:l}})({x:n,y:o},t),{innerRadius:u,outerRadius:s}=t;if(as||0===a)return null;var{startAngle:c,endAngle:f}=(e=>{var{startAngle:t,endAngle:r}=e,n=Math.min(Math.floor(t/360),Math.floor(r/360));return{startAngle:t-360*n,endAngle:r-360*n}})(t),d=l;if(c<=f){for(;d>f;)d-=360;for(;d=c&&d<=f}else{for(;d>c;)d-=360;for(;d=f&&d<=c}return r?i(i({},t),{},{radius:a,angle:((e,t)=>{var{startAngle:r,endAngle:n}=t;return e+360*Math.min(Math.floor(r/360),Math.floor(n/360))})(d,t)}):null}},5643:(e,t,r)=>{"use strict";e.exports=r(6115)},5654:(e,t,r)=>{"use strict";function n(e){return function(){return e}}r.d(t,{A:()=>n})},5672:(e,t,r)=>{e.exports=r(921).get},5695:(e,t,r)=>{"use strict";var n=r(8999);r.o(n,"useRouter")&&r.d(t,{useRouter:function(){return n.useRouter}}),r.o(n,"useSearchParams")&&r.d(t,{useSearchParams:function(){return n.useSearchParams}})},5710:(e,t,r)=>{"use strict";r.d(t,{U1:()=>p,VP:()=>u,Nc:()=>q,Z0:()=>m});var n=r(52);function i(e){return({dispatch:t,getState:r})=>n=>i=>"function"==typeof i?i(t,r,e):n(i)}var o=i(),a=r(4532),l=(r(9509),"undefined"!=typeof window&&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__:function(){if(0!=arguments.length)return"object"==typeof arguments[0]?n.Zz:n.Zz.apply(null,arguments)});function u(e,t){function r(...n){if(t){let r=t(...n);if(!r)throw Error(Y(0));return{type:e,payload:r.payload,..."meta"in r&&{meta:r.meta},..."error"in r&&{error:r.error}}}return{type:e,payload:n[0]}}return r.toString=()=>`${e}`,r.type=e,r.match=t=>(0,n.ve)(t)&&t.type===e,r}"undefined"!=typeof window&&window.__REDUX_DEVTOOLS_EXTENSION__&&window.__REDUX_DEVTOOLS_EXTENSION__;var s=class e extends Array{constructor(...t){super(...t),Object.setPrototypeOf(this,e.prototype)}static get[Symbol.species](){return e}concat(...e){return super.concat.apply(this,e)}prepend(...t){return 1===t.length&&Array.isArray(t[0])?new e(...t[0].concat(this)):new e(...t.concat(this))}};function c(e){return(0,a.a6)(e)?(0,a.jM)(e,()=>{}):e}function f(e,t,r){return e.has(t)?e.get(t):e.set(t,r(t)).get(t)}var d=e=>t=>{setTimeout(t,e)};function p(e){let t,r,a,u=function(e){let{thunk:t=!0,immutableCheck:r=!0,serializableCheck:n=!0,actionCreatorCheck:a=!0}=e??{},l=new s;return t&&("boolean"==typeof t?l.push(o):l.push(i(t.extraArgument))),l},{reducer:c,middleware:f,devTools:p=!0,duplicateMiddlewareCheck:h=!0,preloadedState:g,enhancers:v}=e||{};if("function"==typeof c)t=c;else if((0,n.Qd)(c))t=(0,n.HY)(c);else throw Error(Y(1));r="function"==typeof f?f(u):u();let m=n.Zz;p&&(m=l({trace:!1,..."object"==typeof p&&p}));let y=(a=(0,n.Tw)(...r),function(e){let{autoBatch:t=!0}=e??{},r=new s(a);return t&&r.push(((e={type:"raf"})=>t=>(...r)=>{let n=t(...r),i=!0,o=!1,a=!1,l=new Set,u="tick"===e.type?queueMicrotask:"raf"===e.type?"undefined"!=typeof window&&window.requestAnimationFrame?window.requestAnimationFrame:d(10):"callback"===e.type?e.queueNotification:d(e.timeout),s=()=>{a=!1,o&&(o=!1,l.forEach(e=>e()))};return Object.assign({},n,{subscribe(e){let t=n.subscribe(()=>i&&e());return l.add(e),()=>{t(),l.delete(e)}},dispatch(e){try{return(o=!(i=!e?.meta?.RTK_autoBatch))&&!a&&(a=!0,u(s)),n.dispatch(e)}finally{i=!0}}})})("object"==typeof t?t:void 0)),r}),b=m(..."function"==typeof v?v(y):y());return(0,n.y$)(t,g,b)}function h(e){let t,r={},n=[],i={addCase(e,t){let n="string"==typeof e?e:e.type;if(!n)throw Error(Y(28));if(n in r)throw Error(Y(29));return r[n]=t,i},addMatcher:(e,t)=>(n.push({matcher:e,reducer:t}),i),addDefaultCase:e=>(t=e,i)};return e(i),[r,n,t]}var g=Symbol.for("rtk-slice-createasyncthunk"),v=(e=>(e.reducer="reducer",e.reducerWithPrepare="reducerWithPrepare",e.asyncThunk="asyncThunk",e))(v||{}),m=function({creators:e}={}){let t=e?.asyncThunk?.[g];return function(e){let r,{name:n,reducerPath:i=n}=e;if(!n)throw Error(Y(11));let o=("function"==typeof e.reducers?e.reducers(function(){function e(e,t){return{_reducerDefinitionType:"asyncThunk",payloadCreator:e,...t}}return e.withTypes=()=>e,{reducer:e=>Object.assign({[e.name]:(...t)=>e(...t)}[e.name],{_reducerDefinitionType:"reducer"}),preparedReducer:(e,t)=>({_reducerDefinitionType:"reducerWithPrepare",prepare:e,reducer:t}),asyncThunk:e}}()):e.reducers)||{},l=Object.keys(o),s={},d={},p={},g=[],v={addCase(e,t){let r="string"==typeof e?e:e.type;if(!r)throw Error(Y(12));if(r in d)throw Error(Y(13));return d[r]=t,v},addMatcher:(e,t)=>(g.push({matcher:e,reducer:t}),v),exposeAction:(e,t)=>(p[e]=t,v),exposeCaseReducer:(e,t)=>(s[e]=t,v)};function m(){let[t={},r=[],n]="function"==typeof e.extraReducers?h(e.extraReducers):[e.extraReducers],i={...t,...d};return function(e,t){let r,[n,i,o]=h(t);if("function"==typeof e)r=()=>c(e());else{let t=c(e);r=()=>t}function l(e=r(),t){let u=[n[t.type],...i.filter(({matcher:e})=>e(t)).map(({reducer:e})=>e)];return 0===u.filter(e=>!!e).length&&(u=[o]),u.reduce((e,r)=>{if(r)if((0,a.Qx)(e)){let n=r(e,t);return void 0===n?e:n}else{if((0,a.a6)(e))return(0,a.jM)(e,e=>r(e,t));let n=r(e,t);if(void 0===n){if(null===e)return e;throw Error("A case reducer on a non-draftable value must not return undefined")}return n}return e},e)}return l.getInitialState=r,l}(e.initialState,e=>{for(let t in i)e.addCase(t,i[t]);for(let t of g)e.addMatcher(t.matcher,t.reducer);for(let t of r)e.addMatcher(t.matcher,t.reducer);n&&e.addDefaultCase(n)})}l.forEach(r=>{let i=o[r],a={reducerName:r,type:`${n}/${r}`,createNotation:"function"==typeof e.reducers};"asyncThunk"===i._reducerDefinitionType?function({type:e,reducerName:t},r,n,i){if(!i)throw Error(Y(18));let{payloadCreator:o,fulfilled:a,pending:l,rejected:u,settled:s,options:c}=r,f=i(e,o,c);n.exposeAction(t,f),a&&n.addCase(f.fulfilled,a),l&&n.addCase(f.pending,l),u&&n.addCase(f.rejected,u),s&&n.addMatcher(f.settled,s),n.exposeCaseReducer(t,{fulfilled:a||y,pending:l||y,rejected:u||y,settled:s||y})}(a,i,v,t):function({type:e,reducerName:t,createNotation:r},n,i){let o,a;if("reducer"in n){if(r&&"reducerWithPrepare"!==n._reducerDefinitionType)throw Error(Y(17));o=n.reducer,a=n.prepare}else o=n;i.addCase(e,o).exposeCaseReducer(t,o).exposeAction(t,a?u(e,a):u(e))}(a,i,v)});let b=e=>e,w=new Map,x=new WeakMap;function S(e,t){return r||(r=m()),r(e,t)}function O(){return r||(r=m()),r.getInitialState()}function E(t,r=!1){function n(e){let i=e[t];return void 0===i&&r&&(i=f(x,n,O)),i}function i(t=b){let n=f(w,r,()=>new WeakMap);return f(n,t,()=>{let n={};for(let[i,o]of Object.entries(e.selectors??{}))n[i]=function(e,t,r,n){function i(o,...a){let l=t(o);return void 0===l&&n&&(l=r()),e(l,...a)}return i.unwrapped=e,i}(o,t,()=>f(x,t,O),r);return n})}return{reducerPath:t,getSelectors:i,get selectors(){return i(n)},selectSlice:n}}let C={name:n,reducer:S,actions:p,caseReducers:s,getInitialState:O,...E(i),injectInto(e,{reducerPath:t,...r}={}){let n=t??i;return e.inject({reducerPath:n,reducer:S},r),{...C,...E(n,!0)}}};return C}}();function y(){}var b="listener",w="completed",x="cancelled",S=`task-${x}`,O=`task-${w}`,E=`${b}-${x}`,C=`${b}-${w}`,M=class{constructor(e){this.code=e,this.message=`task ${x} (reason: ${e})`}name="TaskAbortError";message},k=(e,t)=>{if("function"!=typeof e)throw TypeError(Y(32))},_=()=>{},P=(e,t=_)=>(e.catch(t),e),A=(e,t)=>(e.addEventListener("abort",t,{once:!0}),()=>e.removeEventListener("abort",t)),j=(e,t)=>{let r=e.signal;r.aborted||("reason"in r||Object.defineProperty(r,"reason",{enumerable:!0,value:t,configurable:!0,writable:!0}),e.abort(t))},T=e=>{if(e.aborted){let{reason:t}=e;throw new M(t)}};function R(e,t){let r=_;return new Promise((n,i)=>{let o=()=>i(new M(e.reason));if(e.aborted)return void o();r=A(e,o),t.finally(()=>r()).then(n,i)}).finally(()=>{r=_})}var z=async(e,t)=>{try{await Promise.resolve();let t=await e();return{status:"ok",value:t}}catch(e){return{status:e instanceof M?"cancelled":"rejected",error:e}}finally{t?.()}},D=e=>t=>P(R(e,t).then(t=>(T(e),t))),I=e=>{let t=D(e);return e=>t(new Promise(t=>setTimeout(t,e)))},{assign:N}=Object,L={},F="listenerMiddleware",$=e=>{let{type:t,actionCreator:r,matcher:n,predicate:i,effect:o}=e;if(t)i=u(t).match;else if(r)t=r.type,i=r.match;else if(n)i=n;else if(i);else throw Error(Y(21));return k(o,"options.listener"),{predicate:i,type:t,effect:o}},B=N(e=>{let{type:t,predicate:r,effect:n}=$(e);return{id:((e=21)=>{let t="",r=e;for(;r--;)t+="ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW"[64*Math.random()|0];return t})(),effect:n,type:t,predicate:r,pending:new Set,unsubscribe:()=>{throw Error(Y(22))}}},{withTypes:()=>B}),V=(e,t)=>{let{type:r,effect:n,predicate:i}=$(t);return Array.from(e.values()).find(e=>("string"==typeof r?e.type===r:e.predicate===i)&&e.effect===n)},U=e=>{e.pending.forEach(e=>{j(e,E)})},H=(e,t,r)=>{try{e(t,r)}catch(e){setTimeout(()=>{throw e},0)}},G=N(u(`${F}/add`),{withTypes:()=>G}),Z=u(`${F}/removeAll`),W=N(u(`${F}/remove`),{withTypes:()=>W}),K=(...e)=>{console.error(`${F}/error`,...e)},q=(e={})=>{let t=new Map,{extra:r,onError:i=K}=e;k(i,"onError");let o=e=>(e=>(e.unsubscribe=()=>t.delete(e.id),t.set(e.id,e),t=>{e.unsubscribe(),t?.cancelActive&&U(e)}))(V(t,e)??B(e));N(o,{withTypes:()=>o});let a=e=>{let r=V(t,e);return r&&(r.unsubscribe(),e.cancelActive&&U(r)),!!r};N(a,{withTypes:()=>a});let l=async(e,n,a,l)=>{let u=new AbortController,s=((e,t)=>{let r=async(r,n)=>{T(t);let i=()=>{},o=[new Promise((t,n)=>{let o=e({predicate:r,effect:(e,r)=>{r.unsubscribe(),t([e,r.getState(),r.getOriginalState()])}});i=()=>{o(),n()}})];null!=n&&o.push(new Promise(e=>setTimeout(e,n,null)));try{let e=await R(t,Promise.race(o));return T(t),e}finally{i()}};return(e,t)=>P(r(e,t))})(o,u.signal),c=[];try{e.pending.add(u),await Promise.resolve(e.effect(n,N({},a,{getOriginalState:l,condition:(e,t)=>s(e,t).then(Boolean),take:s,delay:I(u.signal),pause:D(u.signal),extra:r,signal:u.signal,fork:((e,t)=>(r,n)=>{k(r,"taskExecutor");let i=new AbortController;A(e,()=>j(i,e.reason));let o=z(async()=>{T(e),T(i.signal);let t=await r({pause:D(i.signal),delay:I(i.signal),signal:i.signal});return T(i.signal),t},()=>j(i,O));return n?.autoJoin&&t.push(o.catch(_)),{result:D(e)(o),cancel(){j(i,S)}}})(u.signal,c),unsubscribe:e.unsubscribe,subscribe:()=>{t.set(e.id,e)},cancelActiveListeners:()=>{e.pending.forEach((e,t,r)=>{e!==u&&(j(e,E),r.delete(e))})},cancel:()=>{j(u,E),e.pending.delete(u)},throwIfCancelled:()=>{T(u.signal)}})))}catch(e){e instanceof M||H(i,e,{raisedBy:"effect"})}finally{await Promise.all(c),j(u,C),e.pending.delete(u)}},u=(e=>()=>{e.forEach(U),e.clear()})(t);return{middleware:e=>r=>s=>{let c;if(!(0,n.ve)(s))return r(s);if(G.match(s))return o(s.payload);if(Z.match(s))return void u();if(W.match(s))return a(s.payload);let f=e.getState(),d=()=>{if(f===L)throw Error(Y(23));return f};try{if(c=r(s),t.size>0){let r=e.getState();for(let n of Array.from(t.values())){let t=!1;try{t=n.predicate(s,r,f)}catch(e){t=!1,H(i,e,{raisedBy:"predicate"})}t&&l(n,s,e,d)}}}finally{f=L}return c},startListening:o,stopListening:a,clearListeners:u}};function Y(e){return`Minified Redux Toolkit error #${e}; visit https://redux-toolkit.js.org/Errors?code=${e} for the full message or use the non-minified dev environment for full errors. `}Symbol.for("rtk-state-proxy-original")},5714:(e,t,r)=>{"use strict";r.d(t,{J:()=>n});var n=e=>e.tooltip},5803:(e,t,r)=>{"use strict";r.d(t,{dc:()=>l,ff:()=>a,g0:()=>u});var n=r(8924),i=r(241),o=r.n(i),a=e=>e.legend.settings,l=e=>e.legend.size,u=(0,n.Mz)([e=>e.legend.payload,a],(e,t)=>{var{itemSorter:r}=t,n=e.flat(1);return r?o()(n,r):n})},5845:(e,t,r)=>{"use strict";r.d(t,{i:()=>l});var n,i=r(2115),o=r(2712),a=(n||(n=r.t(i,2)))[" useInsertionEffect ".trim().toString()]||o.N;function l({prop:e,defaultProp:t,onChange:r=()=>{},caller:n}){let[o,l,u]=function({defaultProp:e,onChange:t}){let[r,n]=i.useState(e),o=i.useRef(r),l=i.useRef(t);return a(()=>{l.current=t},[t]),i.useEffect(()=>{o.current!==r&&(l.current?.(r),o.current=r)},[r,o]),[r,n,l]}({defaultProp:t,onChange:r}),s=void 0!==e,c=s?e:o;{let t=i.useRef(void 0!==e);i.useEffect(()=>{let e=t.current;if(e!==s){let t=s?"controlled":"uncontrolled";console.warn(`${n} is changing from ${e?"controlled":"uncontrolled"} to ${t}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`)}t.current=s},[s,n])}return[c,i.useCallback(t=>{if(s){let r="function"==typeof t?t(e):t;r!==e&&u.current?.(r)}else l(t)},[s,e,l,u])]}Symbol("RADIX:SYNC_STATE")},5998:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.flatten=function(e,t=1){let r=[],n=Math.floor(t),i=(e,t)=>{for(let o=0;o{"use strict";r.d(t,{A:()=>o});var n=r(2115),i=r(5155);function o(e,t=[]){let r=[],a=()=>{let t=r.map(e=>n.createContext(e));return function(r){let i=r?.[e]||t;return n.useMemo(()=>({[`__scope${e}`]:{...r,[e]:i}}),[r,i])}};return a.scopeName=e,[function(t,o){let a=n.createContext(o),l=r.length;r=[...r,o];let u=t=>{let{scope:r,children:o,...u}=t,s=r?.[e]?.[l]||a,c=n.useMemo(()=>u,Object.values(u));return(0,i.jsx)(s.Provider,{value:c,children:o})};return u.displayName=t+"Provider",[u,function(r,i){let u=i?.[e]?.[l]||a,s=n.useContext(u);if(s)return s;if(void 0!==o)return o;throw Error(`\`${r}\` must be used within \`${t}\``)}]},function(...e){let t=e[0];if(1===e.length)return t;let r=()=>{let r=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let i=r.reduce((t,{useScope:r,scopeName:n})=>{let i=r(e)[`__scope${n}`];return{...t,...i}},{});return n.useMemo(()=>({[`__scope${t.scopeName}`]:i}),[i])}};return r.scopeName=t.scopeName,r}(a,...t)]}},6101:(e,t,r)=>{"use strict";r.d(t,{s:()=>a,t:()=>o});var n=r(2115);function i(e,t){if("function"==typeof e)return e(t);null!=e&&(e.current=t)}function o(...e){return t=>{let r=!1,n=e.map(e=>{let n=i(e,t);return r||"function"!=typeof n||(r=!0),n});if(r)return()=>{for(let t=0;t{"use strict";var n=r(2115),i=r(1414),o="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},a=i.useSyncExternalStore,l=n.useRef,u=n.useEffect,s=n.useMemo,c=n.useDebugValue;t.useSyncExternalStoreWithSelector=function(e,t,r,n,i){var f=l(null);if(null===f.current){var d={hasValue:!1,value:null};f.current=d}else d=f.current;var p=a(e,(f=s(function(){function e(e){if(!u){if(u=!0,a=e,e=n(e),void 0!==i&&d.hasValue){var t=d.value;if(i(t,e))return l=t}return l=e}if(t=l,o(a,e))return t;var r=n(e);return void 0!==i&&i(t,r)?(a=e,t):(a=e,l=r)}var a,l,u=!1,s=void 0===r?null:r;return[function(){return e(t())},null===s?void 0:function(){return e(s())}]},[t,r,n,i]))[0],f[1]);return u(function(){d.hasValue=!0,d.value=p},[p]),c(p),p}},6124:(e,t,r)=>{"use strict";r.d(t,{Ds:()=>h,HZ:()=>p});var n=r(8924),i=r(5672),o=r.n(i),a=r(5803),l=r(9827),u=r(2589),s=r(6908),c=r(4421);function f(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function d(e){for(var t=1;te.brush.height,s.h,s.W,a.ff,a.dc],(e,t,r,n,i,a,u,s)=>{var f=a.reduce((e,t)=>{var{orientation:r}=t;if(!t.mirror&&!t.hide){var n="number"==typeof t.width?t.width:c.tQ;return d(d({},e),{},{[r]:e[r]+n})}return e},{left:r.left||0,right:r.right||0}),p=i.reduce((e,t)=>{var{orientation:r}=t;return t.mirror||t.hide?e:d(d({},e),{},{[r]:o()(e,"".concat(r))+t.height})},{top:r.top||0,bottom:r.bottom||0}),h=d(d({},p),f),g=h.bottom;h.bottom+=n;var v=e-(h=(0,l.s0)(h,u,s)).left-h.right,m=t-h.top-h.bottom;return d(d({brushBottom:g},h),{},{width:Math.max(v,0),height:Math.max(m,0)})}),h=(0,n.Mz)(p,e=>({x:e.left,y:e.top,width:e.width,height:e.height}));(0,n.Mz)(u.Lp,u.A$,(e,t)=>({x:0,y:0,width:e,height:t}))},6200:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.toKey=function(e){return"string"==typeof e||"symbol"==typeof e?e:Object.is(e?.valueOf?.(),-0)?"-0":String(e)}},6268:(e,t,r)=>{"use strict";r.d(t,{Kv:()=>o,N4:()=>a});var n=r(2115),i=r(1032);function o(e,t){var r,i,o;return e?"function"==typeof(i=r=e)&&(()=>{let e=Object.getPrototypeOf(i);return e.prototype&&e.prototype.isReactComponent})()||"function"==typeof r||"object"==typeof(o=r)&&"symbol"==typeof o.$$typeof&&["react.memo","react.forward_ref"].includes(o.$$typeof.description)?n.createElement(e,t):e:null}function a(e){let t={state:{},onStateChange:()=>{},renderFallbackValue:null,...e},[r]=n.useState(()=>({current:(0,i.ZR)(t)})),[o,a]=n.useState(()=>r.current.initialState);return r.current.setOptions(t=>({...t,...e,state:{...o,...e.state},onStateChange:t=>{a(t),null==e.onStateChange||e.onStateChange(t)}})),r.current}},6377:(e,t,r)=>{"use strict";r.d(t,{CG:()=>p,Et:()=>u,F4:()=>d,GW:()=>h,M8:()=>a,NF:()=>f,Zb:()=>m,_3:()=>l,eP:()=>g,sA:()=>o,uy:()=>v,vh:()=>s});var n=r(5672),i=r.n(n),o=e=>0===e?0:e>0?1:-1,a=e=>"number"==typeof e&&e!=+e,l=e=>"string"==typeof e&&e.indexOf("%")===e.length-1,u=e=>("number"==typeof e||e instanceof Number)&&!a(e),s=e=>u(e)||"string"==typeof e,c=0,f=e=>{var t=++c;return"".concat(e||"").concat(t)},d=function(e,t){var r,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(!u(e)&&"string"!=typeof e)return n;if(l(e)){if(null==t)return n;var o=e.indexOf("%");r=t*parseFloat(e.slice(0,o))/100}else r=+e;return a(r)&&(r=n),i&&null!=t&&r>t&&(r=t),r},p=e=>{if(!Array.isArray(e))return!1;for(var t=e.length,r={},n=0;ne&&("function"==typeof t?t(e):i()(e,t))===r)}var v=e=>null==e,m=e=>v(e)?e:"".concat(e.charAt(0).toUpperCase()).concat(e.slice(1))},6474:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevron-down",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]])},6523:(e,t,r)=>{"use strict";r.d(t,{$g:()=>a,Hw:()=>o,Td:()=>u,au:()=>l,xH:()=>i});var n=r(1971),i=e=>e.options.defaultTooltipEventType,o=e=>e.options.validateTooltipEventTypes;function a(e,t,r){if(null==e)return t;var n=e?"axis":"item";return null==r?t:r.includes(n)?n:t}function l(e,t){return a(t,i(e),o(e))}function u(e){return(0,n.G)(t=>l(t,e))}},6605:(e,t,r)=>{"use strict";r.d(t,{P:()=>s});var n=r(1643);function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function o(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};if(null==e||n.m.isSsr)return{width:0,height:0};var i=(Object.keys(t=o({},r)).forEach(e=>{t[e]||delete t[e]}),t),s=JSON.stringify({text:e,copyStyle:i});if(a.widthCache[s])return a.widthCache[s];try{var c=document.getElementById(u);c||((c=document.createElement("span")).setAttribute("id",u),c.setAttribute("aria-hidden","true"),document.body.appendChild(c));var f=o(o({},l),i);Object.assign(c.style,f),c.textContent="".concat(e);var d=c.getBoundingClientRect(),p={width:d.width,height:d.height};return a.widthCache[s]=p,++a.cacheCount>2e3&&(a.cacheCount=0,a.widthCache={}),p}catch(e){return{width:0,height:0}}}},6633:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isPrimitive=function(e){return null==e||"object"!=typeof e&&"function"!=typeof e}},6641:(e,t,r)=>{"use strict";r.d(t,{dl:()=>u,lJ:()=>l,uN:()=>o});var n=r(5710),i=r(6377);function o(e,t){if(t){var r=Number.parseInt(t,10);if(!(0,i.M8)(r))return null==e?void 0:e[r]}}var a=(0,n.Z0)({name:"options",initialState:{chartName:"",tooltipPayloadSearcher:void 0,eventEmitter:void 0,defaultTooltipEventType:"axis"},reducers:{createEventEmitter:e=>{null==e.eventEmitter&&(e.eventEmitter=Symbol("rechartsEventEmitter"))}}}),l=a.reducer,{createEventEmitter:u}=a.actions},6670:(e,t,r)=>{"use strict";r.d(t,{x:()=>n});var n=e=>e.options.tooltipPayloadSearcher},6752:(e,t,r)=>{"use strict";r.d(t,{$:()=>i});var n=r(1971),i=()=>(0,n.G)(e=>e.rootProps.accessibilityLayer)},6785:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("target",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"6",key:"1vlfrh"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}]])},6850:(e,t,r)=>{"use strict";r.d(t,{l3:()=>m,m7:()=>y});var n=r(2115),i=r(1971),o=r(8478),a=new(r(2661)),l="recharts.syncEvent.tooltip",u="recharts.syncEvent.brush",s=r(6641),c=r(4890),f=r(4732),d=r(215);function p(e){return e.tooltip.syncInteraction}var h=r(7238),g=r(4487),v=()=>{};function m(){var e,t,r,f,p,m,y,b,w,x,S,O=(0,i.j)();(0,n.useEffect)(()=>{O((0,s.dl)())},[O]),e=(0,i.G)(o.lZ),t=(0,i.G)(o.pH),r=(0,i.j)(),f=(0,i.G)(o.hX),p=(0,i.G)(d.R4),m=(0,h.WX)(),y=(0,h.sk)(),b=(0,i.G)(e=>e.rootProps.className),(0,n.useEffect)(()=>{if(null==e)return v;var n=(n,i,o)=>{if(t!==o&&e===n){if("index"===f)return void r(i);if(null!=p){if("function"==typeof f){var a,l=f(p,{activeTooltipIndex:null==i.payload.index?void 0:Number(i.payload.index),isTooltipActive:i.payload.active,activeIndex:null==i.payload.index?void 0:Number(i.payload.index),activeLabel:i.payload.label,activeDataKey:i.payload.dataKey,activeCoordinate:i.payload.coordinate});a=p[l]}else"value"===f&&(a=p.find(e=>String(e.value)===i.payload.label));var{coordinate:u}=i.payload;if(null==a||!1===i.payload.active||null==u||null==y)return void r((0,c.E1)({active:!1,coordinate:void 0,dataKey:void 0,index:null,label:void 0}));var{x:s,y:d}=u,h=Math.min(s,y.x+y.width),g=Math.min(d,y.y+y.height),v={x:"horizontal"===m?a.coordinate:h,y:"horizontal"===m?g:a.coordinate};r((0,c.E1)({active:i.payload.active,coordinate:v,dataKey:i.payload.dataKey,index:String(a.index),label:i.payload.label}))}}};return a.on(l,n),()=>{a.off(l,n)}},[b,r,t,e,f,p,m,y]),w=(0,i.G)(o.lZ),x=(0,i.G)(o.pH),S=(0,i.j)(),(0,n.useEffect)(()=>{if(null==w)return v;var e=(e,t,r)=>{x!==r&&w===e&&S((0,g.M)(t))};return a.on(u,e),()=>{a.off(u,e)}},[S,x,w])}function y(e,t,r,u,s,d){var h=(0,i.G)(r=>(0,f.dp)(r,e,t)),g=(0,i.G)(o.pH),v=(0,i.G)(o.lZ),m=(0,i.G)(o.hX),y=(0,i.G)(p),b=null==y?void 0:y.active;(0,n.useEffect)(()=>{if(!b&&null!=v&&null!=g){var e=(0,c.E1)({active:d,coordinate:r,dataKey:h,index:s,label:"number"==typeof u?String(u):u});a.emit(l,v,e,g)}},[b,r,h,s,u,g,v,m,d])}},6908:(e,t,r)=>{"use strict";r.d(t,{W:()=>o,h:()=>i});var n=r(8924),i=(0,n.Mz)(e=>e.cartesianAxis.xAxis,e=>Object.values(e)),o=(0,n.Mz)(e=>e.cartesianAxis.yAxis,e=>Object.values(e))},7040:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isUnsafeProperty=function(e){return"__proto__"===e}},7062:(e,t,r)=>{"use strict";r.d(t,{Be:()=>v,Cv:()=>S,D0:()=>E,Gl:()=>m,Dc:()=>O});var n=r(8924),i=r(2589),o=r(6124),a=r(5641),l=r(6377),u={allowDuplicatedCategory:!0,angleAxisId:0,reversed:!1,scale:"auto",tick:!0,type:"category"},s={allowDataOverflow:!1,allowDuplicatedCategory:!0,radiusAxisId:0,scale:"auto",tick:!0,tickCount:5,type:"number"},c=r(8190),f=r(7238),d={allowDataOverflow:!1,allowDecimals:!1,allowDuplicatedCategory:!1,dataKey:void 0,domain:void 0,id:u.angleAxisId,includeHidden:!1,name:void 0,reversed:u.reversed,scale:u.scale,tick:u.tick,tickCount:void 0,ticks:void 0,type:u.type,unit:void 0},p={allowDataOverflow:s.allowDataOverflow,allowDecimals:!1,allowDuplicatedCategory:s.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:s.radiusAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:s.scale,tick:s.tick,tickCount:s.tickCount,ticks:void 0,type:s.type,unit:void 0},h={allowDataOverflow:!1,allowDecimals:!1,allowDuplicatedCategory:u.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:u.angleAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:u.scale,tick:u.tick,tickCount:void 0,ticks:void 0,type:"number",unit:void 0},g={allowDataOverflow:s.allowDataOverflow,allowDecimals:!1,allowDuplicatedCategory:s.allowDuplicatedCategory,dataKey:void 0,domain:void 0,id:s.radiusAxisId,includeHidden:!1,name:void 0,reversed:!1,scale:s.scale,tick:s.tick,tickCount:s.tickCount,ticks:void 0,type:"category",unit:void 0},v=(e,t)=>null!=e.polarAxis.angleAxis[t]?e.polarAxis.angleAxis[t]:"radial"===e.layout.layoutType?h:d,m=(e,t)=>null!=e.polarAxis.radiusAxis[t]?e.polarAxis.radiusAxis[t]:"radial"===e.layout.layoutType?g:p,y=e=>e.polarOptions,b=(0,n.Mz)([i.Lp,i.A$,o.HZ],a.lY),w=(0,n.Mz)([y,b],(e,t)=>{if(null!=e)return(0,l.F4)(e.innerRadius,t,0)}),x=(0,n.Mz)([y,b],(e,t)=>{if(null!=e)return(0,l.F4)(e.outerRadius,t,.8*t)}),S=(0,n.Mz)([y],e=>{if(null==e)return[0,0];var{startAngle:t,endAngle:r}=e;return[t,r]});(0,n.Mz)([v,S],c.I);var O=(0,n.Mz)([b,w,x],(e,t,r)=>{if(null!=e&&null!=t&&null!=r)return[t,r]});(0,n.Mz)([m,O],c.I);var E=(0,n.Mz)([f.fz,y,w,x,i.Lp,i.A$],(e,t,r,n,i,o)=>{if(("centric"===e||"radial"===e)&&null!=t&&null!=r&&null!=n){var{cx:a,cy:u,startAngle:s,endAngle:c}=t;return{cx:(0,l.F4)(a,i,i/2),cy:(0,l.F4)(u,o,o/2),innerRadius:r,outerRadius:n,startAngle:s,endAngle:c,clockWise:!1}}})},7064:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(5181),i=r(1551),o=r(4072);t.orderBy=function(e,t,r,a){if(null==e)return[];r=a?void 0:r,Array.isArray(e)||(e=Object.values(e)),Array.isArray(t)||(t=null==t?[null]:[t]),0===t.length&&(t=[null]),Array.isArray(r)||(r=null==r?[]:[r]),r=r.map(e=>String(e));let l=(e,t)=>{let r=e;for(let e=0;e(Array.isArray(e)&&1===e.length&&(e=e[0]),null==e||"function"==typeof e||Array.isArray(e)||i.isKey(e))?e:{key:e,path:o.toPath(e)});return e.map(e=>({original:e,criteria:u.map(t=>{var r,n;return r=t,null==(n=e)||null==r?n:"object"==typeof r&&"key"in r?Object.hasOwn(n,r.key)?n[r.key]:l(n,r.path):"function"==typeof r?r(n):Array.isArray(r)?l(n,r):"object"==typeof n?n[r]:n})})).slice().sort((e,t)=>{for(let i=0;ie.original)}},7156:(e,t,r)=>{"use strict";r.d(t,{Gk:()=>ek,Vf:()=>eM});var n=r(2115),i=r(2596),o=r(688),a=r(3597),l=r(788);function u(){return(u=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{cx:t,cy:r,r:o,className:s}=e,c=(0,i.$)("recharts-dot",s);return t===+t&&r===+r&&o===+o?n.createElement("circle",u({},(0,l.J9)(e,!1),(0,a._U)(e),{className:c,cx:t,cy:r,r:o})):null},c=r(2348),f=r(8080),d=r.n(f),p=r(379),h=r(9827),g=r(6377),v=["valueAccessor"],m=["data","dataKey","clockWise","id","textBreakAll"];function y(){return(y=Object.assign?Object.assign.bind():function(e){for(var t=1;tArray.isArray(e.value)?d()(e.value):e.value;function O(e){var{valueAccessor:t=S}=e,r=x(e,v),{data:i,dataKey:o,clockWise:a,id:u,textBreakAll:s}=r,f=x(r,m);return i&&i.length?n.createElement(c.W,{className:"recharts-label-list"},i.map((e,r)=>{var i=(0,g.uy)(o)?t(e,r):(0,h.kr)(e&&e.payload,o),c=(0,g.uy)(u)?{}:{id:"".concat(u,"-").concat(r)};return n.createElement(p.J,y({},(0,l.J9)(e,!0),f,c,{parentViewBox:e.parentViewBox,value:i,textBreakAll:s,viewBox:p.J.parseViewBox((0,g.uy)(a)?e:w(w({},e),{},{clockWise:a})),key:"label-".concat(r),index:r}))})):null}O.displayName="LabelList",O.renderCallByParent=function(e,t){var r,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(!e||!e.children&&i&&!e.label)return null;var{children:o}=e,a=(0,l.aS)(o,O).map((e,r)=>(0,n.cloneElement)(e,{data:t,key:"labelList-".concat(r)}));return i?[(r=e.label,r?!0===r?n.createElement(O,{key:"labelList-implicit",data:t}):n.isValidElement(r)||(0,p.Z)(r)?n.createElement(O,{key:"labelList-implicit",data:t,content:r}):"object"==typeof r?n.createElement(O,y({data:t},r,{key:"labelList-implicit"})):null:null),...a]:a};var E=r(1643),C=r(1971),M=r(215),k=r(8234);function _(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function P(e){for(var t=1;tf.includes(e.payload));return(0,g.uy)(d)?null:(e=>{var t,{point:r,childIndex:i,mainColor:o,activeDot:u,dataKey:f}=e;if(!1===u||null==r.x||null==r.y)return null;var d=P(P({index:i,dataKey:f,cx:r.x,cy:r.y,r:4,fill:null!=o?o:"none",strokeWidth:2,stroke:"#fff",payload:r.payload,value:r.value},(0,l.J9)(u,!1)),(0,a._U)(u));return t=(0,n.isValidElement)(u)?(0,n.cloneElement)(u,d):"function"==typeof u?u(d):n.createElement(s,d),n.createElement(c.W,{className:"recharts-active-dot"},t)})({point:d,childIndex:Number(u),mainColor:r,dataKey:o,activeDot:i})}var j=r(4890),T=r(1807);function R(e){var{fn:t,args:r}=e,i=(0,C.j)(),o=(0,T.r)();return(0,n.useEffect)(()=>{if(!o){var e=t(r);return i((0,j.Ix)(e)),()=>{i((0,j.XB)(e))}}},[t,r,i,o]),null}var z=r(2248);function D(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function I(e){for(var t=1;t{var n=I(I({},e),{},{stackId:(0,h.$8)(e.stackId)});null===r.current?t((0,z.g5)(n)):r.current!==n&&t((0,z.ZF)({prev:r.current,next:n})),r.current=n},[t,e]),(0,n.useEffect)(()=>()=>{r.current&&(t((0,z.Vi)(r.current)),r.current=null)},[t]),null}var L=()=>{},F=(0,n.createContext)({addErrorBar:L,removeErrorBar:L}),$=e=>{var{children:t,xAxisId:r,yAxisId:i,zAxisId:o,dataKey:a,data:l,stackId:u,hide:s,type:c,barSize:f}=e,[d,p]=n.useState([]),h=(0,n.useCallback)(e=>{p(t=>[...t,e])},[p]),g=(0,n.useCallback)(e=>{p(t=>t.filter(t=>t!==e))},[p]),v=(0,T.r)();return n.createElement(F.Provider,{value:{addErrorBar:h,removeErrorBar:g}},n.createElement(N,{type:c,data:l,xAxisId:r,yAxisId:i,zAxisId:o,dataKey:a,errorBars:d,stackId:u,hide:s,barSize:f,isPanorama:v}),t)},B=r(2183);function V(e,t){var r,n,i=(0,C.G)(t=>(0,B.Rl)(t,e)),o=(0,C.G)(e=>(0,B.sf)(e,t)),a=null!=(r=null==i?void 0:i.allowDataOverflow)?r:B.PU.allowDataOverflow,l=null!=(n=null==o?void 0:o.allowDataOverflow)?n:B.cd.allowDataOverflow;return{needClip:a||l,needClipX:a,needClipY:l}}function U(e){var{xAxisId:t,yAxisId:r,clipPathId:i}=e,o=(0,k.oM)(),{needClipX:a,needClipY:l,needClip:u}=V(t,r);if(!u)return null;var{x:s,y:c,width:f,height:d}=o;return n.createElement("clipPath",{id:"clipPath-".concat(i)},n.createElement("rect",{x:a?s:s-f/2,y:l?c:c-d/2,width:a?f:2*f,height:l?d:2*d}))}var H=r(8924),G=r(7238),Z=r(356),W=(e,t,r,n)=>(0,B.Gx)(e,"xAxis",t,n),K=(e,t,r,n)=>(0,B.CR)(e,"xAxis",t,n),q=(e,t,r,n)=>(0,B.Gx)(e,"yAxis",r,n),Y=(e,t,r,n)=>(0,B.CR)(e,"yAxis",r,n),X=(0,H.Mz)([G.fz,W,q,K,Y],(e,t,r,n,i)=>(0,h._L)(e,"xAxis")?(0,h.Hj)(t,n,!1):(0,h.Hj)(r,i,!1)),J=(0,H.Mz)([B.ld,(e,t,r,n,i)=>i],(e,t)=>{if(e.some(e=>"area"===e.type&&t.dataKey===e.dataKey&&(0,h.$8)(t.stackId)===e.stackId&&t.data===e.data))return t}),Q=(0,H.Mz)([G.fz,W,q,K,Y,(e,t,r,n,i)=>{var o,a,l=(0,G.fz)(e);if(null!=(a=(0,h._L)(l,"xAxis")?(0,B.TC)(e,"yAxis",r,n):(0,B.TC)(e,"xAxis",t,n))){var{dataKey:u,stackId:s}=i;if(null!=s){var c=null==(o=a[s])?void 0:o.stackedData;return null==c?void 0:c.find(e=>e.key===u)}}},Z.HS,X,J],(e,t,r,n,i,o,a,l,u)=>{var s,{chartData:c,dataStartIndex:f,dataEndIndex:d}=a;if(null!=u&&("horizontal"===e||"vertical"===e)&&null!=t&&null!=r&&null!=n&&null!=i&&0!==n.length&&0!==i.length&&null!=l){var{data:p}=u;if(null!=(s=p&&p.length>0?p:null==c?void 0:c.slice(f,d+1)))return eM({layout:e,xAxis:t,yAxis:r,xAxisTicks:n,yAxisTicks:i,dataStartIndex:f,areaSettings:u,stackedData:o,displayedData:s,chartBaseValue:void 0,bandSize:l})}}),ee=r(4732),et=r(2634),er=()=>{};function en(e){var{legendPayload:t}=e,r=(0,C.j)(),i=(0,T.r)();return(0,n.useEffect)(()=>i?er:(r((0,et.Lx)(t)),()=>{r((0,et.u3)(t))}),[r,i,t]),null}var ei=r(3389),eo=r(8892),ea=r(4460),el=["layout","type","stroke","connectNulls","isRange"],eu=["activeDot","animationBegin","animationDuration","animationEasing","connectNulls","dot","fill","fillOpacity","hide","isAnimationActive","legendType","stroke","xAxisId","yAxisId"];function es(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{var o,a=ef(ef(ef({key:"dot-".concat(t),r:3},p),h),{},{index:t,cx:e.x,cy:e.y,dataKey:f,value:e.value,payload:e.payload,points:r});if(n.isValidElement(u))o=n.cloneElement(u,a);else if("function"==typeof u)o=u(a);else{var l=(0,i.$)("recharts-area-dot","boolean"!=typeof u?u.className:"");o=n.createElement(s,ep({},a,{className:l}))}return o}),v={clipPath:a?"url(#clipPath-".concat(d?"":"dots-").concat(t,")"):void 0};return n.createElement(c.W,ep({className:"recharts-area-dots"},v),g)}function em(e){var{points:t,baseLine:r,needClip:i,clipPathId:a,props:u,showLabels:s}=e,{layout:f,type:d,stroke:p,connectNulls:h,isRange:g}=u,v=es(u,el);return n.createElement(n.Fragment,null,(null==t?void 0:t.length)>1&&n.createElement(c.W,{clipPath:i?"url(#clipPath-".concat(a,")"):void 0},n.createElement(o.I,ep({},(0,l.J9)(v,!0),{points:t,connectNulls:h,type:d,baseLine:r,layout:f,stroke:"none",className:"recharts-area-area"})),"none"!==p&&n.createElement(o.I,ep({},(0,l.J9)(u,!1),{className:"recharts-area-curve",layout:f,type:d,connectNulls:h,fill:"none",points:t})),"none"!==p&&g&&n.createElement(o.I,ep({},(0,l.J9)(u,!1),{className:"recharts-area-curve",layout:f,type:d,connectNulls:h,fill:"none",points:r}))),n.createElement(ev,{points:t,props:u,clipPathId:a}),s&&O.renderCallByParent(u,t))}function ey(e){var{alpha:t,baseLine:r,points:i,strokeWidth:o}=e,a=i[0].y,l=i[i.length-1].y;if(!(0,eo.H)(a)||!(0,eo.H)(l))return null;var u=t*Math.abs(a-l),s=Math.max(...i.map(e=>e.x||0));return((0,g.Et)(r)?s=Math.max(r,s):r&&Array.isArray(r)&&r.length&&(s=Math.max(...r.map(e=>e.x||0),s)),(0,g.Et)(s))?n.createElement("rect",{x:0,y:ae.y||0));return((0,g.Et)(r)?s=Math.max(r,s):r&&Array.isArray(r)&&r.length&&(s=Math.max(...r.map(e=>e.y||0),s)),(0,g.Et)(s))?n.createElement("rect",{x:a1&&void 0!==arguments[1]?arguments[1]:"animation-",r=(0,n.useRef)((0,g.NF)(t)),i=(0,n.useRef)(e);return i.current!==e&&(r.current=(0,g.NF)(t),i.current=e),r.current}(i,"recharts-area-"),[y,b]=(0,n.useState)(!0),w=(0,n.useCallback)(()=>{"function"==typeof v&&v(),b(!1)},[v]),x=(0,n.useCallback)(()=>{"function"==typeof h&&h(),b(!0)},[h]),S=o.current,O=a.current;return n.createElement(ea.i,{begin:f,duration:d,isActive:s,easing:p,from:{t:0},to:{t:1},onAnimationEnd:w,onAnimationStart:x,key:m},e=>{var{t:s}=e;if(S){var f,d=S.length/l.length,p=1===s?l:l.map((e,t)=>{var r=Math.floor(t*d);if(S[r]){var n=S[r];return ef(ef({},e),{},{x:(0,g.GW)(n.x,e.x,s),y:(0,g.GW)(n.y,e.y,s)})}return e});return f=(0,g.Et)(u)?(0,g.GW)(O,u,s):(0,g.uy)(u)||(0,g.M8)(u)?(0,g.GW)(O,0,s):u.map((e,t)=>{var r=Math.floor(t*d);if(Array.isArray(O)&&O[r]){var n=O[r];return ef(ef({},e),{},{x:(0,g.GW)(n.x,e.x,s),y:(0,g.GW)(n.y,e.y,s)})}return e}),s>0&&(o.current=p,a.current=f),n.createElement(em,{points:p,baseLine:f,needClip:t,clipPathId:r,props:i,showLabels:!y})}return s>0&&(o.current=l,a.current=u),n.createElement(c.W,null,n.createElement("defs",null,n.createElement("clipPath",{id:"animationClipPath-".concat(r)},n.createElement(ew,{alpha:s,points:l,baseLine:u,layout:i.layout,strokeWidth:i.strokeWidth}))),n.createElement(c.W,{clipPath:"url(#animationClipPath-".concat(r,")")},n.createElement(em,{points:l,baseLine:u,needClip:t,clipPathId:r,props:i,showLabels:!0})))})}function eS(e){var{needClip:t,clipPathId:r,props:i}=e,{points:o,baseLine:a,isAnimationActive:l}=i,u=(0,n.useRef)(null),s=(0,n.useRef)(),c=u.current,f=s.current;return l&&o&&o.length&&(c!==o||f!==a)?n.createElement(ex,{needClip:t,clipPathId:r,props:i,previousPointsRef:u,previousBaselineRef:s}):n.createElement(em,{points:o,baseLine:a,needClip:t,clipPathId:r,props:i,showLabels:!0})}class eO extends n.PureComponent{render(){var e,{hide:t,dot:r,points:o,className:a,top:u,left:s,needClip:f,xAxisId:d,yAxisId:p,width:h,height:v,id:m,baseLine:y}=this.props;if(t)return null;var b=(0,i.$)("recharts-area",a),w=(0,g.uy)(m)?this.id:m,{r:x=3,strokeWidth:S=2}=null!=(e=(0,l.J9)(r,!1))?e:{r:3,strokeWidth:2},O=(0,l.y$)(r),E=2*x+S;return n.createElement(n.Fragment,null,n.createElement(c.W,{className:b},f&&n.createElement("defs",null,n.createElement(U,{clipPathId:w,xAxisId:d,yAxisId:p}),!O&&n.createElement("clipPath",{id:"clipPath-dots-".concat(w)},n.createElement("rect",{x:s-E/2,y:u-E/2,width:h+E,height:v+E}))),n.createElement(eS,{needClip:f,clipPathId:w,props:this.props})),n.createElement(A,{points:o,mainColor:eh(this.props.stroke,this.props.fill),itemDataKey:this.props.dataKey,activeDot:this.props.activeDot}),this.props.isRange&&Array.isArray(y)&&n.createElement(A,{points:y,mainColor:eh(this.props.stroke,this.props.fill),itemDataKey:this.props.dataKey,activeDot:this.props.activeDot}))}constructor(){super(...arguments),ed(this,"id",(0,g.NF)("recharts-area-"))}}var eE={activeDot:!0,animationBegin:0,animationDuration:1500,animationEasing:"ease",connectNulls:!1,dot:!1,fill:"#3182bd",fillOpacity:.6,hide:!1,isAnimationActive:!E.m.isSsr,legendType:"line",stroke:"#3182bd",xAxisId:0,yAxisId:0};function eC(e){var t,r=(0,ei.e)(e,eE),{activeDot:i,animationBegin:o,animationDuration:a,animationEasing:l,connectNulls:u,dot:s,fill:c,fillOpacity:f,hide:d,isAnimationActive:p,legendType:h,stroke:g,xAxisId:v,yAxisId:m}=r,y=es(r,eu),b=(0,G.WX)(),w=(0,ee.fW)(),{needClip:x}=V(v,m),S=(0,T.r)(),O=(0,n.useMemo)(()=>({baseValue:e.baseValue,stackId:e.stackId,connectNulls:u,data:e.data,dataKey:e.dataKey}),[e.baseValue,e.stackId,u,e.data,e.dataKey]),{points:E,isRange:M,baseLine:_}=null!=(t=(0,C.G)(e=>Q(e,v,m,S,O)))?t:{},{height:P,width:A,x:j,y:R}=(0,k.oM)();return"horizontal"!==b&&"vertical"!==b||"AreaChart"!==w&&"ComposedChart"!==w?null:n.createElement(eO,ep({},y,{activeDot:i,animationBegin:o,animationDuration:a,animationEasing:l,baseLine:_,connectNulls:u,dot:s,fill:c,fillOpacity:f,height:P,hide:d,layout:b,isAnimationActive:p,isRange:M,legendType:h,needClip:x,points:E,stroke:g,width:A,left:j,top:R,xAxisId:v,yAxisId:m}))}function eM(e){var t,{areaSettings:{connectNulls:r,baseValue:n,dataKey:i},stackedData:o,layout:a,chartBaseValue:l,xAxis:u,yAxis:s,displayedData:c,dataStartIndex:f,xAxisTicks:d,yAxisTicks:p,bandSize:v}=e,m=o&&o.length,y=((e,t,r,n,i)=>{var o=null!=r?r:t;if((0,g.Et)(o))return o;var a="horizontal"===e?i:n,l=a.scale.domain();if("number"===a.type){var u=Math.max(l[0],l[1]),s=Math.min(l[0],l[1]);return"dataMin"===o?s:"dataMax"===o||u<0?u:Math.max(Math.min(l[0],l[1]),0)}return"dataMin"===o?l[0]:"dataMax"===o?l[1]:l[0]})(a,l,n,u,s),b="horizontal"===a,w=!1,x=c.map((e,t)=>{m?n=o[f+t]:Array.isArray(n=(0,h.kr)(e,i))?w=!0:n=[y,n];var n,a=null==n[1]||m&&!r&&null==(0,h.kr)(e,i);return b?{x:(0,h.nb)({axis:u,ticks:d,bandSize:v,entry:e,index:t}),y:a?null:s.scale(n[1]),value:n,payload:e}:{x:a?null:u.scale(n[1]),y:(0,h.nb)({axis:s,ticks:p,bandSize:v,entry:e,index:t}),value:n,payload:e}});return t=m||w?x.map(e=>{var t=Array.isArray(e.value)?e.value[0]:null;return b?{x:e.x,y:null!=t&&null!=e.y?s.scale(t):null}:{x:null!=t?u.scale(t):null,y:e.y}}):b?s.scale(y):u.scale(y),{points:x,baseLine:t,isRange:w}}class ek extends n.PureComponent{render(){return n.createElement($,{type:"area",data:this.props.data,dataKey:this.props.dataKey,xAxisId:this.props.xAxisId,yAxisId:this.props.yAxisId,zAxisId:0,stackId:this.props.stackId,hide:this.props.hide,barSize:void 0},n.createElement(en,{legendPayload:(e=>{var{dataKey:t,name:r,stroke:n,fill:i,legendType:o,hide:a}=e;return[{inactive:a,dataKey:t,type:o,color:eh(n,i),value:(0,h.uM)(r,t),payload:e}]})(this.props)}),n.createElement(R,{fn:eg,args:this.props}),n.createElement(eC,this.props))}}ed(ek,"displayName","Area"),ed(ek,"defaultProps",eE)},7238:(e,t,r)=>{"use strict";r.d(t,{Kp:()=>h,W7:()=>c,WX:()=>v,fz:()=>g,rY:()=>d,sk:()=>u,yi:()=>f}),r(2115);var n=r(1971),i=r(6124),o=r(2589),a=r(1807),l=r(972),u=()=>{var e,t=(0,a.r)(),r=(0,n.G)(i.Ds),o=(0,n.G)(l.U),u=null==(e=(0,n.G)(l.C))?void 0:e.padding;return t&&o&&u?{width:o.width-u.left-u.right,height:o.height-u.top-u.bottom,x:u.left,y:u.top}:r},s={top:0,bottom:0,left:0,right:0,width:0,height:0,brushBottom:0},c=()=>{var e;return null!=(e=(0,n.G)(i.HZ))?e:s},f=()=>(0,n.G)(o.Lp),d=()=>(0,n.G)(o.A$),p={top:0,right:0,bottom:0,left:0},h=()=>{var e;return null!=(e=(0,n.G)(e=>e.layout.margin))?e:p},g=e=>e.layout.layoutType,v=()=>(0,n.G)(g)},7298:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(9738);t.cloneDeep=function(e){return n.cloneDeepWith(e)}},7328:(e,t,r)=>{"use strict";function n(e,t,r){if(!t.has(e))throw TypeError("attempted to "+r+" private field on non-instance");return t.get(e)}function i(e,t){var r=n(e,t,"get");return r.get?r.get.call(e):r.value}function o(e,t,r){var i=n(e,t,"set");if(i.set)i.set.call(e,r);else{if(!i.writable)throw TypeError("attempted to set read only private field");i.value=r}return r}r.d(t,{N:()=>d});var a,l=r(2115),u=r(6081),s=r(6101),c=r(9708),f=r(5155);function d(e){let t=e+"CollectionProvider",[r,n]=(0,u.A)(t),[i,o]=r(t,{collectionRef:{current:null},itemMap:new Map}),a=e=>{let{scope:t,children:r}=e,n=l.useRef(null),o=l.useRef(new Map).current;return(0,f.jsx)(i,{scope:t,itemMap:o,collectionRef:n,children:r})};a.displayName=t;let d=e+"CollectionSlot",p=(0,c.TL)(d),h=l.forwardRef((e,t)=>{let{scope:r,children:n}=e,i=o(d,r),a=(0,s.s)(t,i.collectionRef);return(0,f.jsx)(p,{ref:a,children:n})});h.displayName=d;let g=e+"CollectionItemSlot",v="data-radix-collection-item",m=(0,c.TL)(g),y=l.forwardRef((e,t)=>{let{scope:r,children:n,...i}=e,a=l.useRef(null),u=(0,s.s)(t,a),c=o(g,r);return l.useEffect(()=>(c.itemMap.set(a,{ref:a,...i}),()=>void c.itemMap.delete(a))),(0,f.jsx)(m,{...{[v]:""},ref:u,children:n})});return y.displayName=g,[{Provider:a,Slot:h,ItemSlot:y},function(t){let r=o(e+"CollectionConsumer",t);return l.useCallback(()=>{let e=r.collectionRef.current;if(!e)return[];let t=Array.from(e.querySelectorAll("[".concat(v,"]")));return Array.from(r.itemMap.values()).sort((e,r)=>t.indexOf(e.ref.current)-t.indexOf(r.ref.current))},[r.collectionRef,r.itemMap])},n]}var p=new WeakMap;function h(e,t){if("at"in Array.prototype)return Array.prototype.at.call(e,t);let r=function(e,t){let r=e.length,n=g(t),i=n>=0?n:r+n;return i<0||i>=r?-1:i}(e,t);return -1===r?void 0:e[r]}function g(e){return e!=e||0===e?0:Math.trunc(e)}a=new WeakMap,class e extends Map{set(e,t){return p.get(this)&&(this.has(e)?i(this,a)[i(this,a).indexOf(e)]=e:i(this,a).push(e)),super.set(e,t),this}insert(e,t,r){let n,o=this.has(t),l=i(this,a).length,u=g(e),s=u>=0?u:l+u,c=s<0||s>=l?-1:s;if(c===this.size||o&&c===this.size-1||-1===c)return this.set(t,r),this;let f=this.size+ +!o;u<0&&s++;let d=[...i(this,a)],p=!1;for(let e=s;e=this.size&&(n=this.size-1),this.at(n)}keyFrom(e,t){let r=this.indexOf(e);if(-1===r)return;let n=r+t;return n<0&&(n=0),n>=this.size&&(n=this.size-1),this.keyAt(n)}find(e,t){let r=0;for(let n of this){if(Reflect.apply(e,t,[n,r,this]))return n;r++}}findIndex(e,t){let r=0;for(let n of this){if(Reflect.apply(e,t,[n,r,this]))return r;r++}return -1}filter(t,r){let n=[],i=0;for(let e of this)Reflect.apply(t,r,[e,i,this])&&n.push(e),i++;return new e(n)}map(t,r){let n=[],i=0;for(let e of this)n.push([e[0],Reflect.apply(t,r,[e,i,this])]),i++;return new e(n)}reduce(){for(var e=arguments.length,t=Array(e),r=0;r=0;e--){let r=this.at(e);o=e===this.size-1&&1===t.length?r:Reflect.apply(n,this,[o,r,e,this])}return o}toSorted(t){return new e([...this.entries()].sort(t))}toReversed(){let t=new e;for(let e=this.size-1;e>=0;e--){let r=this.keyAt(e),n=this.get(r);t.set(r,n)}return t}toSpliced(){for(var t=arguments.length,r=Array(t),n=0;n0&&(i=r-1);for(let e=t;e<=i;e++){let t=this.keyAt(e),r=this.get(t);n.set(t,r)}return n}every(e,t){let r=0;for(let n of this){if(!Reflect.apply(e,t,[n,r,this]))return!1;r++}return!0}some(e,t){let r=0;for(let n of this){if(Reflect.apply(e,t,[n,r,this]))return!0;r++}return!1}constructor(e){super(e),function(e,t,r){if(t.has(e))throw TypeError("Cannot initialize the same private elements twice on an object");t.set(e,r)}(this,a,{writable:!0,value:void 0}),o(this,a,[...super.keys()]),p.set(this,!0)}}},7547:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(3676),i=r(2465),o=r(656),a=r(1571);t.uniqBy=function(e,t=i.identity){return o.isArrayLikeObject(e)?n.uniqBy(Array.from(e),a.iteratee(t)):[]}},7580:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]])},7863:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("chevron-up",[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]])},7918:(e,t,r)=>{"use strict";r.d(t,{V:()=>i});var n=r(2115);function i(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],[t,r]=(0,n.useState)({height:0,left:0,top:0,width:0}),i=(0,n.useCallback)(e=>{if(null!=e){var n=e.getBoundingClientRect(),i={height:n.height,left:n.left,top:n.top,width:n.width};(Math.abs(i.height-t.height)>1||Math.abs(i.left-t.left)>1||Math.abs(i.top-t.top)>1||Math.abs(i.width-t.width)>1)&&r({height:i.height,left:i.left,top:i.top,width:i.width})}},[t.width,t.height,t.top,t.left,...e]);return[t,i]}},8060:(e,t,r)=>{"use strict";r.d(t,{M:()=>o,t:()=>i});var n=r(2115),i=(0,n.createContext)(null),o=()=>(0,n.useContext)(i)},8080:(e,t,r)=>{e.exports=r(8359).last},8124:(e,t,r)=>{"use strict";r.d(t,{m:()=>ef});var n=r(2115),i=r(7650),o=r(241),a=r.n(o),l=r(2596),u=r(6377);function s(){return(s=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{separator:t=" : ",contentStyle:r={},itemStyle:i={},labelStyle:o={},payload:c,formatter:p,itemSorter:h,wrapperClassName:g,labelClassName:v,label:m,labelFormatter:y,accessibilityLayer:b=!1}=e,w=f({margin:0,padding:10,backgroundColor:"#fff",border:"1px solid #ccc",whiteSpace:"nowrap"},r),x=f({margin:0},o),S=!(0,u.uy)(m),O=S?m:"",E=(0,l.$)("recharts-default-tooltip",g),C=(0,l.$)("recharts-tooltip-label",v);return S&&y&&null!=c&&(O=y(m,c)),n.createElement("div",s({className:E,style:w},b?{role:"status","aria-live":"assertive"}:{}),n.createElement("p",{className:C,style:x},n.isValidElement(O)?O:"".concat(O)),(()=>{if(c&&c.length){var e=(h?a()(c,h):c).map((e,r)=>{if("none"===e.type)return null;var o=e.formatter||p||d,{value:a,name:l}=e,s=a,h=l;if(o){var g=o(a,l,e,r,c);if(Array.isArray(g))[s,h]=g;else{if(null==g)return null;s=g}}var v=f({display:"block",paddingTop:4,paddingBottom:4,color:e.color||"#000"},i);return n.createElement("li",{className:"recharts-tooltip-item",key:"tooltip-item-".concat(r),style:v},(0,u.vh)(h)?n.createElement("span",{className:"recharts-tooltip-item-name"},h):null,(0,u.vh)(h)?n.createElement("span",{className:"recharts-tooltip-item-separator"},t):null,n.createElement("span",{className:"recharts-tooltip-item-value"},s),n.createElement("span",{className:"recharts-tooltip-item-unit"},e.unit||""))});return n.createElement("ul",{className:"recharts-tooltip-item-list",style:{padding:0,margin:0}},e)}return null})())},h="recharts-tooltip-wrapper",g={visibility:"hidden"};function v(e){var{allowEscapeViewBox:t,coordinate:r,key:n,offsetTopLeft:i,position:o,reverseDirection:a,tooltipDimension:l,viewBox:s,viewBoxDimension:c}=e;if(o&&(0,u.Et)(o[n]))return o[n];var f=r[n]-l-(i>0?i:0),d=r[n]+i;if(t[n])return a[n]?f:d;var p=s[n];return null==p?0:a[n]?fp+c?Math.max(f,p):Math.max(d,p)}function m(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function y(e){for(var t=1;t0&&f.width>0&&o?function(e){var{translateX:t,translateY:r,useTranslate3d:n}=e;return{transform:n?"translate3d(".concat(t,"px, ").concat(r,"px, 0)"):"translate(".concat(t,"px, ").concat(r,"px)")}}({translateX:r=v({allowEscapeViewBox:i,coordinate:o,key:"x",offsetTopLeft:a,position:s,reverseDirection:c,tooltipDimension:f.width,viewBox:p,viewBoxDimension:p.width}),translateY:n=v({allowEscapeViewBox:i,coordinate:o,key:"y",offsetTopLeft:a,position:s,reverseDirection:c,tooltipDimension:f.height,viewBox:p,viewBoxDimension:p.height}),useTranslate3d:d}):g,cssClasses:function(e){var{coordinate:t,translateX:r,translateY:n}=e;return(0,l.$)(h,{["".concat(h,"-right")]:(0,u.Et)(r)&&t&&(0,u.Et)(t.x)&&r>=t.x,["".concat(h,"-left")]:(0,u.Et)(r)&&t&&(0,u.Et)(t.x)&&r=t.y,["".concat(h,"-top")]:(0,u.Et)(n)&&t&&(0,u.Et)(t.y)&&n{if("Escape"===e.key){var t,r,n,i;this.setState({dismissed:!0,dismissedAtCoordinate:{x:null!=(t=null==(r=this.props.coordinate)?void 0:r.x)?t:0,y:null!=(n=null==(i=this.props.coordinate)?void 0:i.y)?n:0}})}})}}var x=r(1643),S=r(2494),O=r(7238),E=r(6752),C=r(7918),M=r(688),k=r(788),_=["x","y","top","left","width","height","className"];function P(){return(P=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{x:t=0,y:r=0,top:i=0,left:o=0,width:a=0,height:s=0,className:c}=e,f=function(e){for(var t=1;t{var o,a=Math.min(Math.abs(r)/2,Math.abs(n)/2),l=n>=0?1:-1,u=r>=0?1:-1,s=+(n>=0&&r>=0||n<0&&r<0);if(a>0&&i instanceof Array){for(var c=[0,0,0,0],f=0;f<4;f++)c[f]=i[f]>a?a:i[f];o="M".concat(e,",").concat(t+l*c[0]),c[0]>0&&(o+="A ".concat(c[0],",").concat(c[0],",0,0,").concat(s,",").concat(e+u*c[0],",").concat(t)),o+="L ".concat(e+r-u*c[1],",").concat(t),c[1]>0&&(o+="A ".concat(c[1],",").concat(c[1],",0,0,").concat(s,",\n ").concat(e+r,",").concat(t+l*c[1])),o+="L ".concat(e+r,",").concat(t+n-l*c[2]),c[2]>0&&(o+="A ".concat(c[2],",").concat(c[2],",0,0,").concat(s,",\n ").concat(e+r-u*c[2],",").concat(t+n)),o+="L ".concat(e+u*c[3],",").concat(t+n),c[3]>0&&(o+="A ".concat(c[3],",").concat(c[3],",0,0,").concat(s,",\n ").concat(e,",").concat(t+n-l*c[3])),o+="Z"}else if(a>0&&i===+i&&i>0){var d=Math.min(a,i);o="M ".concat(e,",").concat(t+l*d,"\n A ").concat(d,",").concat(d,",0,0,").concat(s,",").concat(e+u*d,",").concat(t,"\n L ").concat(e+r-u*d,",").concat(t,"\n A ").concat(d,",").concat(d,",0,0,").concat(s,",").concat(e+r,",").concat(t+l*d,"\n L ").concat(e+r,",").concat(t+n-l*d,"\n A ").concat(d,",").concat(d,",0,0,").concat(s,",").concat(e+r-u*d,",").concat(t+n,"\n L ").concat(e+u*d,",").concat(t+n,"\n A ").concat(d,",").concat(d,",0,0,").concat(s,",").concat(e,",").concat(t+n-l*d," Z")}else o="M ".concat(e,",").concat(t," h ").concat(r," v ").concat(n," h ").concat(-r," Z");return o},I={x:0,y:0,width:0,height:0,radius:0,isAnimationActive:!1,isUpdateAnimationActive:!1,animationBegin:0,animationDuration:1500,animationEasing:"ease"},N=e=>{var t=(0,T.e)(e,I),r=(0,n.useRef)(null),[i,o]=(0,n.useState)(-1);(0,n.useEffect)(()=>{if(r.current&&r.current.getTotalLength)try{var e=r.current.getTotalLength();e&&o(e)}catch(e){}},[]);var{x:a,y:u,width:s,height:c,radius:f,className:d}=t,{animationEasing:p,animationDuration:h,animationBegin:g,isAnimationActive:v,isUpdateAnimationActive:m}=t;if(a!==+a||u!==+u||s!==+s||c!==+c||0===s||0===c)return null;var y=(0,l.$)("recharts-rectangle",d);return m?n.createElement(R.i,{canBegin:i>0,from:{width:s,height:c,x:a,y:u},to:{width:s,height:c,x:a,y:u},duration:h,animationEasing:p,isActive:m},e=>{var{width:o,height:a,x:l,y:u}=e;return n.createElement(R.i,{canBegin:i>0,from:"0px ".concat(-1===i?1:i,"px"),to:"".concat(i,"px 0px"),attributeName:"strokeDasharray",begin:g,duration:h,isActive:v,easing:p},n.createElement("path",z({},(0,k.J9)(t,!0),{className:y,d:D(l,u,o,a,f),ref:r})))}):n.createElement("path",z({},(0,k.J9)(t,!0),{className:y,d:D(a,u,s,c,f)}))},L=r(5641);function F(e){var{cx:t,cy:r,radius:n,startAngle:i,endAngle:o}=e;return{points:[(0,L.IZ)(t,r,n,i),(0,L.IZ)(t,r,n,o)],cx:t,cy:r,radius:n,startAngle:i,endAngle:o}}function $(){return($=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{cx:t,cy:r,radius:n,angle:i,sign:o,isExternal:a,cornerRadius:l,cornerIsExternal:u}=e,s=l*(a?1:-1)+n,c=Math.asin(l/s)/L.Kg,f=u?i:i+o*c,d=(0,L.IZ)(t,r,s,f);return{center:d,circleTangency:(0,L.IZ)(t,r,n,f),lineTangency:(0,L.IZ)(t,r,s*Math.cos(c*L.Kg),u?i-o*c:i),theta:c}},V=e=>{var{cx:t,cy:r,innerRadius:n,outerRadius:i,startAngle:o,endAngle:a}=e,l=((e,t)=>(0,u.sA)(t-e)*Math.min(Math.abs(t-e),359.999))(o,a),s=o+l,c=(0,L.IZ)(t,r,i,o),f=(0,L.IZ)(t,r,i,s),d="M ".concat(c.x,",").concat(c.y,"\n A ").concat(i,",").concat(i,",0,\n ").concat(+(Math.abs(l)>180),",").concat(+(o>s),",\n ").concat(f.x,",").concat(f.y,"\n ");if(n>0){var p=(0,L.IZ)(t,r,n,o),h=(0,L.IZ)(t,r,n,s);d+="L ".concat(h.x,",").concat(h.y,"\n A ").concat(n,",").concat(n,",0,\n ").concat(+(Math.abs(l)>180),",").concat(+(o<=s),",\n ").concat(p.x,",").concat(p.y," Z")}else d+="L ".concat(t,",").concat(r," Z");return d},U={cx:0,cy:0,innerRadius:0,outerRadius:0,startAngle:0,endAngle:0,cornerRadius:0,forceCornerRadius:!1,cornerIsExternal:!1},H=e=>{var t,r=(0,T.e)(e,U),{cx:i,cy:o,innerRadius:a,outerRadius:s,cornerRadius:c,forceCornerRadius:f,cornerIsExternal:d,startAngle:p,endAngle:h,className:g}=r;if(s0&&360>Math.abs(p-h)?(e=>{var{cx:t,cy:r,innerRadius:n,outerRadius:i,cornerRadius:o,forceCornerRadius:a,cornerIsExternal:l,startAngle:s,endAngle:c}=e,f=(0,u.sA)(c-s),{circleTangency:d,lineTangency:p,theta:h}=B({cx:t,cy:r,radius:i,angle:s,sign:f,cornerRadius:o,cornerIsExternal:l}),{circleTangency:g,lineTangency:v,theta:m}=B({cx:t,cy:r,radius:i,angle:c,sign:-f,cornerRadius:o,cornerIsExternal:l}),y=l?Math.abs(s-c):Math.abs(s-c)-h-m;if(y<0)return a?"M ".concat(p.x,",").concat(p.y,"\n a").concat(o,",").concat(o,",0,0,1,").concat(2*o,",0\n a").concat(o,",").concat(o,",0,0,1,").concat(-(2*o),",0\n "):V({cx:t,cy:r,innerRadius:n,outerRadius:i,startAngle:s,endAngle:c});var b="M ".concat(p.x,",").concat(p.y,"\n A").concat(o,",").concat(o,",0,0,").concat(+(f<0),",").concat(d.x,",").concat(d.y,"\n A").concat(i,",").concat(i,",0,").concat(+(y>180),",").concat(+(f<0),",").concat(g.x,",").concat(g.y,"\n A").concat(o,",").concat(o,",0,0,").concat(+(f<0),",").concat(v.x,",").concat(v.y,"\n ");if(n>0){var{circleTangency:w,lineTangency:x,theta:S}=B({cx:t,cy:r,radius:n,angle:s,sign:f,isExternal:!0,cornerRadius:o,cornerIsExternal:l}),{circleTangency:O,lineTangency:E,theta:C}=B({cx:t,cy:r,radius:n,angle:c,sign:-f,isExternal:!0,cornerRadius:o,cornerIsExternal:l}),M=l?Math.abs(s-c):Math.abs(s-c)-S-C;if(M<0&&0===o)return"".concat(b,"L").concat(t,",").concat(r,"Z");b+="L".concat(E.x,",").concat(E.y,"\n A").concat(o,",").concat(o,",0,0,").concat(+(f<0),",").concat(O.x,",").concat(O.y,"\n A").concat(n,",").concat(n,",0,").concat(+(M>180),",").concat(+(f>0),",").concat(w.x,",").concat(w.y,"\n A").concat(o,",").concat(o,",0,0,").concat(+(f<0),",").concat(x.x,",").concat(x.y,"Z")}else b+="L".concat(t,",").concat(r,"Z");return b})({cx:i,cy:o,innerRadius:a,outerRadius:s,cornerRadius:Math.min(y,m/2),forceCornerRadius:f,cornerIsExternal:d,startAngle:p,endAngle:h}):V({cx:i,cy:o,innerRadius:a,outerRadius:s,startAngle:p,endAngle:h}),n.createElement("path",$({},(0,k.J9)(r,!0),{className:v,d:t}))},G=r(1971),Z=r(9827),W=r(215);function K(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function q(e){for(var t=1;t{A((0,en.UF)({shared:x,trigger:M,axisId:P,active:o,defaultIndex:j}))},[A,x,M,P,o,j]);var R=(0,O.sk)(),z=(0,E.$)(),D=(0,eo.Td)(x),{activeIndex:I,isActive:N}=(0,G.G)(e=>(0,Y.yn)(e,D,M,j)),L=(0,G.G)(e=>(0,Y.u9)(e,D,M,j)),F=(0,G.G)(e=>(0,Y.BZ)(e,D,M,j)),$=(0,G.G)(e=>(0,Y.dS)(e,D,M,j)),B=(0,er.X)(),V=null!=o?o:N,[U,H]=(0,C.V)([L,V]),Z="axis"===D?F:void 0;(0,ei.m7)(D,M,$,Z,I,V);var W=null!=_?_:B;if(null==W)return null;var K=null!=L?L:es;V||(K=es),c&&K.length&&(K=(0,S.s)(L.filter(e=>null!=e.value&&(!0!==e.hide||r.includeHidden)),h,eu));var q=K.length>0,X=n.createElement(w,{allowEscapeViewBox:a,animationDuration:l,animationEasing:u,isAnimationActive:f,active:V,coordinate:$,hasPayload:q,offset:d,position:g,reverseDirection:v,useTranslate3d:m,viewBox:R,wrapperStyle:y,lastBoundingBox:U,innerRef:H,hasPortalFromProps:!!_},(t=el(el({},r),{},{payload:K,label:Z,active:V,coordinate:$,accessibilityLayer:z}),n.isValidElement(s)?n.cloneElement(s,t):"function"==typeof s?n.createElement(s,t):n.createElement(p,t)));return n.createElement(n.Fragment,null,(0,i.createPortal)(X,W),V&&n.createElement(et,{cursor:b,tooltipEventType:D,coordinate:$,payload:L,index:I}))}},8132:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(2744);t.isMatch=function(e,t){return n.isMatchWith(e,t,()=>void 0)}},8179:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(9452);t.isArrayLike=function(e){return null!=e&&"function"!=typeof e&&n.isLength(e.length)}},8190:(e,t,r)=>{"use strict";r.d(t,{I:()=>n});var n=(e,t)=>{if(e&&t)return null!=e&&e.reversed?[t[1],t[0]]:t}},8221:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.getSymbols=function(e){return Object.getOwnPropertySymbols(e).filter(t=>Object.prototype.propertyIsEnumerable.call(e,t))}},8234:(e,t,r)=>{"use strict";r.d(t,{EI:()=>f,oM:()=>c});var n=r(1971),i=r(215),o=r(8924),a=r(6124),l=(0,o.Mz)([a.HZ],e=>{if(e)return{top:e.top,bottom:e.bottom,left:e.left,right:e.right}}),u=r(2589),s=(0,o.Mz)([l,u.Lp,u.A$],(e,t,r)=>{if(e&&null!=t&&null!=r)return{x:e.left,y:e.top,width:Math.max(0,t-e.left-e.right),height:Math.max(0,r-e.top-e.bottom)}}),c=()=>(0,n.G)(s),f=()=>(0,n.G)(i.JG)},8247:(e,t,r)=>{"use strict";r.d(t,{Q:()=>tf});var n=r(2115),i=r(6641);r(1992);var o={notify(){},get:()=>[]},a="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,l="undefined"!=typeof navigator&&"ReactNative"===navigator.product,u=a||l?n.useLayoutEffect:n.useEffect;Object.getOwnPropertyNames,Object.getOwnPropertySymbols,Object.getOwnPropertyDescriptor,Object.getPrototypeOf,Object.prototype;var s=Symbol.for("react-redux-context"),c="undefined"!=typeof globalThis?globalThis:{},f=function(){if(!n.createContext)return{};let e=c[s]??=new Map,t=e.get(n.createContext);return t||(t=n.createContext(null),e.set(n.createContext,t)),t}(),d=function(e){let{children:t,context:r,serverState:i,store:a}=e,l=n.useMemo(()=>{let e=function(e,t){let r,n=o,i=0,a=!1;function l(){c.onStateChange&&c.onStateChange()}function u(){if(i++,!r){let t,i;r=e.subscribe(l),t=null,i=null,n={clear(){t=null,i=null},notify(){let e=t;for(;e;)e.callback(),e=e.next},get(){let e=[],r=t;for(;r;)e.push(r),r=r.next;return e},subscribe(e){let r=!0,n=i={callback:e,next:null,prev:i};return n.prev?n.prev.next=n:t=n,function(){r&&null!==t&&(r=!1,n.next?n.next.prev=n.prev:i=n.prev,n.prev?n.prev.next=n.next:t=n.next)}}}}}function s(){i--,r&&0===i&&(r(),r=void 0,n.clear(),n=o)}let c={addNestedSub:function(e){u();let t=n.subscribe(e),r=!1;return()=>{r||(r=!0,t(),s())}},notifyNestedSubs:function(){n.notify()},handleChangeWrapper:l,isSubscribed:function(){return a},trySubscribe:function(){a||(a=!0,u())},tryUnsubscribe:function(){a&&(a=!1,s())},getListeners:()=>n};return c}(a);return{store:a,subscription:e,getServerState:i?()=>i:void 0}},[a,i]),s=n.useMemo(()=>a.getState(),[a]);return u(()=>{let{subscription:e}=l;return e.onStateChange=e.notifyNestedSubs,e.trySubscribe(),s!==a.getState()&&e.notifyNestedSubs(),()=>{e.tryUnsubscribe(),e.onStateChange=void 0}},[l,s]),n.createElement((r||f).Provider,{value:l},t)},p=r(52),h=r(5710),g=r(4890),v=r(4487),m=(0,h.Z0)({name:"chartLayout",initialState:{layoutType:"horizontal",width:0,height:0,margin:{top:5,right:5,bottom:5,left:5},scale:1},reducers:{setLayout(e,t){e.layoutType=t.payload},setChartSize(e,t){e.width=t.payload.width,e.height=t.payload.height},setMargin(e,t){e.margin.top=t.payload.top,e.margin.right=t.payload.right,e.margin.bottom=t.payload.bottom,e.margin.left=t.payload.left},setScale(e,t){e.scale=t.payload}}}),{setMargin:y,setLayout:b,setChartSize:w,setScale:x}=m.actions,S=m.reducer,O=r(8924),E=r(7238),C=r(215),M=r(6124),k=r(4732),_=r(7062),P=(0,O.Mz)([(e,t)=>t,E.fz,_.D0,C.Re,C.gL,C.R4,k.r1,M.HZ],k.aX),A=r(6523),j=e=>{var t=e.currentTarget.getBoundingClientRect(),r=t.width/e.currentTarget.offsetWidth,n=t.height/e.currentTarget.offsetHeight;return{chartX:Math.round((e.clientX-t.left)/r),chartY:Math.round((e.clientY-t.top)/n)}},T=(0,h.VP)("mouseClick"),R=(0,h.Nc)();R.startListening({actionCreator:T,effect:(e,t)=>{var r=e.payload,n=P(t.getState(),j(r));(null==n?void 0:n.activeIndex)!=null&&t.dispatch((0,g.jF)({activeIndex:n.activeIndex,activeDataKey:void 0,activeCoordinate:n.activeCoordinate}))}});var z=(0,h.VP)("mouseMove"),D=(0,h.Nc)();function I(e,t){return t instanceof HTMLElement?"HTMLElement <".concat(t.tagName,' class="').concat(t.className,'">'):t===window?"global.window":t}D.startListening({actionCreator:z,effect:(e,t)=>{var r=e.payload,n=t.getState(),i=(0,A.au)(n,n.tooltip.settings.shared),o=P(n,j(r));"axis"===i&&((null==o?void 0:o.activeIndex)!=null?t.dispatch((0,g.Nt)({activeIndex:o.activeIndex,activeDataKey:void 0,activeCoordinate:o.activeCoordinate})):t.dispatch((0,g.xS)()))}});var N=r(4532);function L(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function F(e){for(var t=1;t{e.dots.push(t.payload)},removeDot:(e,t)=>{var r=(0,N.ss)(e).dots.findIndex(e=>e===t.payload);-1!==r&&e.dots.splice(r,1)},addArea:(e,t)=>{e.areas.push(t.payload)},removeArea:(e,t)=>{var r=(0,N.ss)(e).areas.findIndex(e=>e===t.payload);-1!==r&&e.areas.splice(r,1)},addLine:(e,t)=>{e.lines.push(t.payload)},removeLine:(e,t)=>{var r=(0,N.ss)(e).lines.findIndex(e=>e===t.payload);-1!==r&&e.lines.splice(r,1)}}}),{addDot:X,removeDot:J,addArea:Q,removeArea:ee,addLine:et,removeLine:er}=Y.actions,en=Y.reducer,ei={x:0,y:0,width:0,height:0,padding:{top:0,right:0,bottom:0,left:0}},eo=(0,h.Z0)({name:"brush",initialState:ei,reducers:{setBrushSettings:(e,t)=>null==t.payload?ei:t.payload}}),{setBrushSettings:ea}=eo.actions,el=eo.reducer,eu=r(2634),es={accessibilityLayer:!0,barCategoryGap:"10%",barGap:4,barSize:void 0,className:void 0,maxBarSize:void 0,stackOffset:"none",syncId:void 0,syncMethod:"index"},ec=(0,h.Z0)({name:"rootProps",initialState:es,reducers:{updateOptions:(e,t)=>{var r;e.accessibilityLayer=t.payload.accessibilityLayer,e.barCategoryGap=t.payload.barCategoryGap,e.barGap=null!=(r=t.payload.barGap)?r:es.barGap,e.barSize=t.payload.barSize,e.maxBarSize=t.payload.maxBarSize,e.stackOffset=t.payload.stackOffset,e.syncId=t.payload.syncId,e.syncMethod=t.payload.syncMethod,e.className=t.payload.className}}}),ef=ec.reducer,{updateOptions:ed}=ec.actions,ep=(0,h.Z0)({name:"polarAxis",initialState:{radiusAxis:{},angleAxis:{}},reducers:{addRadiusAxis(e,t){e.radiusAxis[t.payload.id]=(0,N.h4)(t.payload)},removeRadiusAxis(e,t){delete e.radiusAxis[t.payload.id]},addAngleAxis(e,t){e.angleAxis[t.payload.id]=(0,N.h4)(t.payload)},removeAngleAxis(e,t){delete e.angleAxis[t.payload.id]}}}),{addRadiusAxis:eh,removeRadiusAxis:eg,addAngleAxis:ev,removeAngleAxis:em}=ep.actions,ey=ep.reducer,eb=(0,h.Z0)({name:"polarOptions",initialState:null,reducers:{updatePolarOptions:(e,t)=>t.payload}}),{updatePolarOptions:ew}=eb.actions,ex=eb.reducer,eS=r(2183),eO=r(841),eE=(0,h.VP)("keyDown"),eC=(0,h.VP)("focus"),eM=(0,h.Nc)();eM.startListening({actionCreator:eE,effect:(e,t)=>{var r=t.getState();if(!1!==r.rootProps.accessibilityLayer){var{keyboardInteraction:n}=r.tooltip,i=e.payload;if("ArrowRight"===i||"ArrowLeft"===i||"Enter"===i){var o=Number((0,eO.P)(n,(0,C.n4)(r))),a=(0,C.R4)(r);if("Enter"===i){var l=(0,k.pg)(r,"axis","hover",String(n.index));t.dispatch((0,g.o4)({active:!n.active,activeIndex:n.index,activeDataKey:n.dataKey,activeCoordinate:l}));return}var u=o+("ArrowRight"===i?1:-1)*("left-to-right"===(0,eS._y)(r)?1:-1);if(null!=a&&!(u>=a.length)&&!(u<0)){var s=(0,k.pg)(r,"axis","hover",String(u));t.dispatch((0,g.o4)({active:!0,activeIndex:u.toString(),activeDataKey:void 0,activeCoordinate:s}))}}}}}),eM.startListening({actionCreator:eC,effect:(e,t)=>{var r=t.getState();if(!1!==r.rootProps.accessibilityLayer){var{keyboardInteraction:n}=r.tooltip;if(!n.active&&null==n.index){var i=(0,k.pg)(r,"axis","hover",String("0"));t.dispatch((0,g.o4)({activeDataKey:void 0,active:!0,activeIndex:"0",activeCoordinate:i}))}}}});var ek=(0,h.VP)("externalEvent"),e_=(0,h.Nc)();e_.startListening({actionCreator:ek,effect:(e,t)=>{if(null!=e.payload.handler){var r=t.getState(),n={activeCoordinate:(0,C.eE)(r),activeDataKey:(0,C.Xb)(r),activeIndex:(0,C.A2)(r),activeLabel:(0,C.BZ)(r),activeTooltipIndex:(0,C.A2)(r),isTooltipActive:(0,C.yn)(r)};e.payload.handler(n,e.payload.reactEvent)}}});var eP=r(4421),eA=r(6670),ej=r(5714),eT=(0,O.Mz)([ej.J],e=>e.tooltipItemPayloads),eR=(0,O.Mz)([eT,eA.x,(e,t,r)=>t,(e,t,r)=>r],(e,t,r,n)=>{var i=e.find(e=>e.settings.dataKey===n);if(null!=i){var{positions:o}=i;if(null!=o)return t(o,r)}}),ez=(0,h.VP)("touchMove"),eD=(0,h.Nc)();eD.startListening({actionCreator:ez,effect:(e,t)=>{var r=e.payload,n=t.getState(),i=(0,A.au)(n,n.tooltip.settings.shared);if("axis"===i){var o=P(n,j({clientX:r.touches[0].clientX,clientY:r.touches[0].clientY,currentTarget:r.currentTarget}));(null==o?void 0:o.activeIndex)!=null&&t.dispatch((0,g.Nt)({activeIndex:o.activeIndex,activeDataKey:void 0,activeCoordinate:o.activeCoordinate}))}else if("item"===i){var a,l=r.touches[0],u=document.elementFromPoint(l.clientX,l.clientY);if(!u||!u.getAttribute)return;var s=u.getAttribute(eP.F0),c=null!=(a=u.getAttribute(eP.um))?a:void 0,f=eR(t.getState(),s,c);t.dispatch((0,g.RD)({activeDataKey:c,activeIndex:s,activeCoordinate:f}))}}});var eI=(0,p.HY)({brush:el,cartesianAxis:K,chartData:v.LV,graphicalItems:q.iZ,layout:S,legend:eu.CU,options:i.lJ,polarAxis:ey,polarOptions:ex,referenceElements:en,rootProps:ef,tooltip:g.En}),eN=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"Chart";return(0,h.U1)({reducer:eI,preloadedState:e,middleware:e=>e({serializableCheck:!1}).concat([R.middleware,D.middleware,eM.middleware,e_.middleware,eD.middleware]),devTools:{serialize:{replacer:I},name:"recharts-".concat(t)}})},eL=r(1807),eF=r(5064);function e$(e){var{preloadedState:t,children:r,reduxStoreName:i}=e,o=(0,eL.r)(),a=(0,n.useRef)(null);if(o)return r;null==a.current&&(a.current=eN(t,i));var l=eF.E;return n.createElement(d,{context:l,store:a.current},r)}var eB=r(1971),eV=e=>{var{chartData:t}=e,r=(0,eB.j)(),i=(0,eL.r)();return(0,n.useEffect)(()=>i?()=>{}:(r((0,v.hq)(t)),()=>{r((0,v.hq)(void 0))}),[t,r,i]),null};function eU(e){var{layout:t,width:r,height:i,margin:o}=e,a=(0,eB.j)(),l=(0,eL.r)();return(0,n.useEffect)(()=>{l||(a(b(t)),a(w({width:r,height:i})),a(y(o)))},[a,l,t,r,i,o]),null}function eH(e){var t=(0,eB.j)();return(0,n.useEffect)(()=>{t(ed(e))},[t,e]),null}var eG=r(788),eZ=r(6752),eW=r(2790),eK=r(972),eq=r(8892),eY=["children"];function eX(){return(eX=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var r,i,o=(0,E.yi)(),a=(0,E.rY)(),l=(0,eZ.$)();if(!(0,eq.F)(o)||!(0,eq.F)(a))return null;var{children:u,otherAttributes:s,title:c,desc:f}=e;return r="number"==typeof s.tabIndex?s.tabIndex:l?0:void 0,i="string"==typeof s.role?s.role:l?"application":void 0,n.createElement(eW.u,eX({},s,{title:c,desc:f,role:i,tabIndex:r,width:o,height:a,style:eJ,ref:t}),u)}),e0=e=>{var{children:t}=e,r=(0,eB.G)(eK.U);if(!r)return null;var{width:i,height:o,y:a,x:l}=r;return n.createElement(eW.u,{width:i,height:o,x:l,y:a},t)},e1=(0,n.forwardRef)((e,t)=>{var{children:r}=e,i=function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n{var{children:r,className:i,height:o,onClick:a,onContextMenu:l,onDoubleClick:u,onMouseDown:s,onMouseEnter:c,onMouseLeave:f,onMouseMove:d,onMouseUp:p,onTouchEnd:h,onTouchMove:v,onTouchStart:m,style:y,width:b}=e,w=(0,eB.j)(),[S,O]=(0,n.useState)(null),[E,C]=(0,n.useState)(null);(0,e5.l3)();var M=function(){var e=(0,eB.j)(),[t,r]=(0,n.useState)(null),i=(0,eB.G)(e6.et);return(0,n.useEffect)(()=>{if(null!=t){var r=t.getBoundingClientRect().width/t.offsetWidth;(0,eq.H)(r)&&r!==i&&e(x(r))}},[t,e,i]),r}(),k=(0,n.useCallback)(e=>{M(e),"function"==typeof t&&t(e),O(e),C(e)},[M,t,O,C]),_=(0,n.useCallback)(e=>{w(T(e)),w(ek({handler:a,reactEvent:e}))},[w,a]),P=(0,n.useCallback)(e=>{w(z(e)),w(ek({handler:c,reactEvent:e}))},[w,c]),A=(0,n.useCallback)(e=>{w((0,g.xS)()),w(ek({handler:f,reactEvent:e}))},[w,f]),j=(0,n.useCallback)(e=>{w(z(e)),w(ek({handler:d,reactEvent:e}))},[w,d]),R=(0,n.useCallback)(()=>{w(eC())},[w]),D=(0,n.useCallback)(e=>{w(eE(e.key))},[w]),I=(0,n.useCallback)(e=>{w(ek({handler:l,reactEvent:e}))},[w,l]),N=(0,n.useCallback)(e=>{w(ek({handler:u,reactEvent:e}))},[w,u]),L=(0,n.useCallback)(e=>{w(ek({handler:s,reactEvent:e}))},[w,s]),F=(0,n.useCallback)(e=>{w(ek({handler:p,reactEvent:e}))},[w,p]),$=(0,n.useCallback)(e=>{w(ek({handler:m,reactEvent:e}))},[w,m]),B=(0,n.useCallback)(e=>{w(ez(e)),w(ek({handler:v,reactEvent:e}))},[w,v]),V=(0,n.useCallback)(e=>{w(ek({handler:h,reactEvent:e}))},[w,h]);return n.createElement(e4.$.Provider,{value:S},n.createElement(e3.t.Provider,{value:E},n.createElement("div",{className:(0,e2.$)("recharts-wrapper",i),style:function(e){for(var t=1;t{var{children:t}=e,[r]=(0,n.useState)("".concat((0,e7.NF)("recharts"),"-clip")),i=(0,te.oM)();if(null==i)return null;var{x:o,y:a,width:l,height:u}=i;return n.createElement(tt.Provider,{value:r},n.createElement("defs",null,n.createElement("clipPath",{id:r},n.createElement("rect",{x:o,y:a,height:u,width:l}))),t)},tn=["children","className","width","height","style","compact","title","desc"],ti=(0,n.forwardRef)((e,t)=>{var{children:r,className:i,width:o,height:a,style:l,compact:u,title:s,desc:c}=e,f=function(e,t){if(null==e)return{};var r,n,i=function(e,t){if(null==e)return{};var r={};for(var n in e)if(({}).hasOwnProperty.call(e,n)){if(-1!==t.indexOf(n))continue;r[n]=e[n]}return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;nn.createElement(ts,{chartName:"AreaChart",defaultTooltipEventType:"axis",validateTooltipEventTypes:tc,tooltipPayloadSearcher:i.uN,categoricalChartProps:e,ref:t}))},8266:(e,t,r)=>{"use strict";r.d(t,{$$:()=>g,Es:()=>p,KG:()=>m,Ks:()=>k,Ll:()=>l,Re:()=>E,Sw:()=>o,TW:()=>d,WQ:()=>O,YG:()=>x,YN:()=>v,ZC:()=>b,_q:()=>h,ag:()=>P,e_:()=>M,jn:()=>i,kx:()=>C,l6:()=>a,lk:()=>y,sb:()=>c,wz:()=>s,xZ:()=>f,zk:()=>u});var n=r(2115);function i(){for(var e=arguments.length,t=Array(e),r=0;re=>{t.forEach(t=>t(e))},t)}let o="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement;function a(e){let t=Object.prototype.toString.call(e);return"[object Window]"===t||"[object global]"===t}function l(e){return"nodeType"in e}function u(e){var t,r;return e?a(e)?e:l(e)&&null!=(t=null==(r=e.ownerDocument)?void 0:r.defaultView)?t:window:window}function s(e){let{Document:t}=u(e);return e instanceof t}function c(e){return!a(e)&&e instanceof u(e).HTMLElement}function f(e){return e instanceof u(e).SVGElement}function d(e){return e?a(e)?e.document:l(e)?s(e)?e:c(e)||f(e)?e.ownerDocument:document:document:document}let p=o?n.useLayoutEffect:n.useEffect;function h(e){let t=(0,n.useRef)(e);return p(()=>{t.current=e}),(0,n.useCallback)(function(){for(var e=arguments.length,r=Array(e),n=0;n{e.current=setInterval(t,r)},[]),(0,n.useCallback)(()=>{null!==e.current&&(clearInterval(e.current),e.current=null)},[])]}function v(e,t){void 0===t&&(t=[e]);let r=(0,n.useRef)(e);return p(()=>{r.current!==e&&(r.current=e)},t),r}function m(e,t){let r=(0,n.useRef)();return(0,n.useMemo)(()=>{let t=e(r.current);return r.current=t,t},[...t])}function y(e){let t=h(e),r=(0,n.useRef)(null),i=(0,n.useCallback)(e=>{e!==r.current&&(null==t||t(e,r.current)),r.current=e},[]);return[r,i]}function b(e){let t=(0,n.useRef)();return(0,n.useEffect)(()=>{t.current=e},[e]),t.current}let w={};function x(e,t){return(0,n.useMemo)(()=>{if(t)return t;let r=null==w[e]?0:w[e]+1;return w[e]=r,e+"-"+r},[e,t])}function S(e){return function(t){for(var r=arguments.length,n=Array(r>1?r-1:0),i=1;i{for(let[n,i]of Object.entries(r)){let r=t[n];null!=r&&(t[n]=r+e*i)}return t},{...t})}}let O=S(1),E=S(-1);function C(e){if(!e)return!1;let{KeyboardEvent:t}=u(e.target);return t&&e instanceof t}function M(e){if(function(e){if(!e)return!1;let{TouchEvent:t}=u(e.target);return t&&e instanceof t}(e)){if(e.touches&&e.touches.length){let{clientX:t,clientY:r}=e.touches[0];return{x:t,y:r}}else if(e.changedTouches&&e.changedTouches.length){let{clientX:t,clientY:r}=e.changedTouches[0];return{x:t,y:r}}}return"clientX"in e&&"clientY"in e?{x:e.clientX,y:e.clientY}:null}let k=Object.freeze({Translate:{toString(e){if(!e)return;let{x:t,y:r}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(r?Math.round(r):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;let{scaleX:t,scaleY:r}=e;return"scaleX("+t+") scaleY("+r+")"}},Transform:{toString(e){if(e)return[k.Translate.toString(e),k.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:r,easing:n}=e;return t+" "+r+"ms "+n}}}),_="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function P(e){return e.matches(_)?e:e.querySelector(_)}},8359:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(220),i=r(4986),o=r(8179);t.last=function(e){if(o.isArrayLike(e))return n.last(i.toArray(e))}},8412:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let r=/^(?:0|[1-9]\d*)$/;t.isIndex=function(e,t=Number.MAX_SAFE_INTEGER){switch(typeof e){case"number":return Number.isInteger(e)&&e>=0&&e{"use strict";r.d(t,{eC:()=>i,gY:()=>n,hX:()=>l,iO:()=>o,lZ:()=>a,pH:()=>u});var n=e=>e.rootProps.barCategoryGap,i=e=>e.rootProps.stackOffset,o=e=>e.options.chartName,a=e=>e.rootProps.syncId,l=e=>e.rootProps.syncMethod,u=e=>e.options.eventEmitter},8673:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.debounce=function(e,t=0,r={}){let n;"object"!=typeof r&&(r={});let i=null,o=null,a=null,l=0,u=null,{leading:s=!1,trailing:c=!0,maxWait:f}=r,d="maxWait"in r,p=d?Math.max(Number(f)||0,t):0,h=t=>(null!==i&&(n=e.apply(o,i)),i=o=null,l=t,n),g=e=>(u=null,c&&null!==i)?h(e):n,v=e=>{if(null===a)return!0;let r=e-a,n=d&&e-l>=p;return r>=t||r<0||n},m=()=>{let e=Date.now();if(v(e))return g(e);u=setTimeout(m,(e=>{let r=t-(null===a?0:e-a),n=p-(e-l);return d?Math.min(r,n):r})(e))},y=function(...e){let r=Date.now(),c=v(r);if(i=e,o=this,a=r,c){if(null===u)return(l=r,u=setTimeout(m,t),s&&null!==i)?h(r):n;if(d)return clearTimeout(u),u=setTimeout(m,t),h(r)}return null===u&&(u=setTimeout(m,t)),n};return y.cancel=()=>{null!==u&&clearTimeout(u),l=0,a=i=o=u=null},y.flush=()=>null===u?n:g(Date.now()),y}},8870:function(e,t,r){var n;!function(i){"use strict";var o,a={precision:20,rounding:4,toExpNeg:-7,toExpPos:21,LN10:"2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286"},l=!0,u="[DecimalError] ",s=u+"Invalid argument: ",c=u+"Exponent out of range: ",f=Math.floor,d=Math.pow,p=/^(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,h=f(1286742750677284.5),g={};function v(e,t){var r,n,i,o,a,u,s,c,f=e.constructor,d=f.precision;if(!e.s||!t.s)return t.s||(t=new f(e)),l?M(t,d):t;if(s=e.d,c=t.d,a=e.e,i=t.e,s=s.slice(),o=a-i){for(o<0?(n=s,o=-o,u=c.length):(n=c,i=a,u=s.length),o>(u=(a=Math.ceil(d/7))>u?a+1:u+1)&&(o=u,n.length=1),n.reverse();o--;)n.push(0);n.reverse()}for((u=s.length)-(o=c.length)<0&&(o=u,n=c,c=s,s=n),r=0;o;)r=(s[--o]=s[o]+c[o]+r)/1e7|0,s[o]%=1e7;for(r&&(s.unshift(r),++i),u=s.length;0==s[--u];)s.pop();return t.d=s,t.e=i,l?M(t,d):t}function m(e,t,r){if(e!==~~e||er)throw Error(s+e)}function y(e){var t,r,n,i=e.length-1,o="",a=e[0];if(i>0){for(o+=a,t=1;te.e^this.s<0?1:-1;for(t=0,r=(n=this.d.length)<(i=e.d.length)?n:i;te.d[t]^this.s<0?1:-1;return n===i?0:n>i^this.s<0?1:-1},g.decimalPlaces=g.dp=function(){var e=this.d.length-1,t=(e-this.e)*7;if(e=this.d[e])for(;e%10==0;e/=10)t--;return t<0?0:t},g.dividedBy=g.div=function(e){return b(this,new this.constructor(e))},g.dividedToIntegerBy=g.idiv=function(e){var t=this.constructor;return M(b(this,new t(e),0,1),t.precision)},g.equals=g.eq=function(e){return!this.cmp(e)},g.exponent=function(){return x(this)},g.greaterThan=g.gt=function(e){return this.cmp(e)>0},g.greaterThanOrEqualTo=g.gte=function(e){return this.cmp(e)>=0},g.isInteger=g.isint=function(){return this.e>this.d.length-2},g.isNegative=g.isneg=function(){return this.s<0},g.isPositive=g.ispos=function(){return this.s>0},g.isZero=function(){return 0===this.s},g.lessThan=g.lt=function(e){return 0>this.cmp(e)},g.lessThanOrEqualTo=g.lte=function(e){return 1>this.cmp(e)},g.logarithm=g.log=function(e){var t,r=this.constructor,n=r.precision,i=n+5;if(void 0===e)e=new r(10);else if((e=new r(e)).s<1||e.eq(o))throw Error(u+"NaN");if(this.s<1)throw Error(u+(this.s?"NaN":"-Infinity"));return this.eq(o)?new r(0):(l=!1,t=b(E(this,i),E(e,i),i),l=!0,M(t,n))},g.minus=g.sub=function(e){return e=new this.constructor(e),this.s==e.s?k(this,e):v(this,(e.s=-e.s,e))},g.modulo=g.mod=function(e){var t,r=this.constructor,n=r.precision;if(!(e=new r(e)).s)throw Error(u+"NaN");return this.s?(l=!1,t=b(this,e,0,1).times(e),l=!0,this.minus(t)):M(new r(this),n)},g.naturalExponential=g.exp=function(){return w(this)},g.naturalLogarithm=g.ln=function(){return E(this)},g.negated=g.neg=function(){var e=new this.constructor(this);return e.s=-e.s||0,e},g.plus=g.add=function(e){return e=new this.constructor(e),this.s==e.s?v(this,e):k(this,(e.s=-e.s,e))},g.precision=g.sd=function(e){var t,r,n;if(void 0!==e&&!!e!==e&&1!==e&&0!==e)throw Error(s+e);if(t=x(this)+1,r=7*(n=this.d.length-1)+1,n=this.d[n]){for(;n%10==0;n/=10)r--;for(n=this.d[0];n>=10;n/=10)r++}return e&&t>r?t:r},g.squareRoot=g.sqrt=function(){var e,t,r,n,i,o,a,s=this.constructor;if(this.s<1){if(!this.s)return new s(0);throw Error(u+"NaN")}for(e=x(this),l=!1,0==(i=Math.sqrt(+this))||i==1/0?(((t=y(this.d)).length+e)%2==0&&(t+="0"),i=Math.sqrt(t),e=f((e+1)/2)-(e<0||e%2),n=new s(t=i==1/0?"5e"+e:(t=i.toExponential()).slice(0,t.indexOf("e")+1)+e)):n=new s(i.toString()),i=a=(r=s.precision)+3;;)if(n=(o=n).plus(b(this,o,a+2)).times(.5),y(o.d).slice(0,a)===(t=y(n.d)).slice(0,a)){if(t=t.slice(a-3,a+1),i==a&&"4999"==t){if(M(o,r+1,0),o.times(o).eq(this)){n=o;break}}else if("9999"!=t)break;a+=4}return l=!0,M(n,r)},g.times=g.mul=function(e){var t,r,n,i,o,a,u,s,c,f=this.constructor,d=this.d,p=(e=new f(e)).d;if(!this.s||!e.s)return new f(0);for(e.s*=this.s,r=this.e+e.e,(s=d.length)<(c=p.length)&&(o=d,d=p,p=o,a=s,s=c,c=a),o=[],n=a=s+c;n--;)o.push(0);for(n=c;--n>=0;){for(t=0,i=s+n;i>n;)u=o[i]+p[n]*d[i-n-1]+t,o[i--]=u%1e7|0,t=u/1e7|0;o[i]=(o[i]+t)%1e7|0}for(;!o[--a];)o.pop();return t?++r:o.shift(),e.d=o,e.e=r,l?M(e,f.precision):e},g.toDecimalPlaces=g.todp=function(e,t){var r=this,n=r.constructor;return(r=new n(r),void 0===e)?r:(m(e,0,1e9),void 0===t?t=n.rounding:m(t,0,8),M(r,e+x(r)+1,t))},g.toExponential=function(e,t){var r,n=this,i=n.constructor;return void 0===e?r=_(n,!0):(m(e,0,1e9),void 0===t?t=i.rounding:m(t,0,8),r=_(n=M(new i(n),e+1,t),!0,e+1)),r},g.toFixed=function(e,t){var r,n,i=this.constructor;return void 0===e?_(this):(m(e,0,1e9),void 0===t?t=i.rounding:m(t,0,8),r=_((n=M(new i(this),e+x(this)+1,t)).abs(),!1,e+x(n)+1),this.isneg()&&!this.isZero()?"-"+r:r)},g.toInteger=g.toint=function(){var e=this.constructor;return M(new e(this),x(this)+1,e.rounding)},g.toNumber=function(){return+this},g.toPower=g.pow=function(e){var t,r,n,i,a,s,c=this,d=c.constructor,p=+(e=new d(e));if(!e.s)return new d(o);if(!(c=new d(c)).s){if(e.s<1)throw Error(u+"Infinity");return c}if(c.eq(o))return c;if(n=d.precision,e.eq(o))return M(c,n);if(s=(t=e.e)>=(r=e.d.length-1),a=c.s,s){if((r=p<0?-p:p)<=0x1fffffffffffff){for(i=new d(o),t=Math.ceil(n/7+4),l=!1;r%2&&P((i=i.times(c)).d,t),0!==(r=f(r/2));)P((c=c.times(c)).d,t);return l=!0,e.s<0?new d(o).div(i):M(i,n)}}else if(a<0)throw Error(u+"NaN");return a=a<0&&1&e.d[Math.max(t,r)]?-1:1,c.s=1,l=!1,i=e.times(E(c,n+12)),l=!0,(i=w(i)).s=a,i},g.toPrecision=function(e,t){var r,n,i=this,o=i.constructor;return void 0===e?(r=x(i),n=_(i,r<=o.toExpNeg||r>=o.toExpPos)):(m(e,1,1e9),void 0===t?t=o.rounding:m(t,0,8),r=x(i=M(new o(i),e,t)),n=_(i,e<=r||r<=o.toExpNeg,e)),n},g.toSignificantDigits=g.tosd=function(e,t){var r=this.constructor;return void 0===e?(e=r.precision,t=r.rounding):(m(e,1,1e9),void 0===t?t=r.rounding:m(t,0,8)),M(new r(this),e,t)},g.toString=g.valueOf=g.val=g.toJSON=function(){var e=x(this),t=this.constructor;return _(this,e<=t.toExpNeg||e>=t.toExpPos)};var b=function(){function e(e,t){var r,n=0,i=e.length;for(e=e.slice();i--;)r=e[i]*t+n,e[i]=r%1e7|0,n=r/1e7|0;return n&&e.unshift(n),e}function t(e,t,r,n){var i,o;if(r!=n)o=r>n?1:-1;else for(i=o=0;it[i]?1:-1;break}return o}function r(e,t,r){for(var n=0;r--;)e[r]-=n,n=+(e[r]1;)e.shift()}return function(n,i,o,a){var l,s,c,f,d,p,h,g,v,m,y,b,w,S,O,E,C,k,_=n.constructor,P=n.s==i.s?1:-1,A=n.d,j=i.d;if(!n.s)return new _(n);if(!i.s)throw Error(u+"Division by zero");for(c=0,s=n.e-i.e,C=j.length,O=A.length,g=(h=new _(P)).d=[];j[c]==(A[c]||0);)++c;if(j[c]>(A[c]||0)&&--s,(b=null==o?o=_.precision:a?o+(x(n)-x(i))+1:o)<0)return new _(0);if(b=b/7+2|0,c=0,1==C)for(f=0,j=j[0],b++;(c1&&(j=e(j,f),A=e(A,f),C=j.length,O=A.length),S=C,m=(v=A.slice(0,C)).length;m=1e7/2&&++E;do f=0,(l=t(j,v,C,m))<0?(y=v[0],C!=m&&(y=1e7*y+(v[1]||0)),(f=y/E|0)>1?(f>=1e7&&(f=1e7-1),p=(d=e(j,f)).length,m=v.length,1==(l=t(d,v,p,m))&&(f--,r(d,C16)throw Error(c+x(e));if(!e.s)return new p(o);for(null==t?(l=!1,u=h):u=t,a=new p(.03125);e.abs().gte(.1);)e=e.times(a),f+=5;for(u+=Math.log(d(2,f))/Math.LN10*2+5|0,r=n=i=new p(o),p.precision=u;;){if(n=M(n.times(e),u),r=r.times(++s),y((a=i.plus(b(n,r,u))).d).slice(0,u)===y(i.d).slice(0,u)){for(;f--;)i=M(i.times(i),u);return p.precision=h,null==t?(l=!0,M(i,h)):i}i=a}}function x(e){for(var t=7*e.e,r=e.d[0];r>=10;r/=10)t++;return t}function S(e,t,r){if(t>e.LN10.sd())throw l=!0,r&&(e.precision=r),Error(u+"LN10 precision limit exceeded");return M(new e(e.LN10),t)}function O(e){for(var t="";e--;)t+="0";return t}function E(e,t){var r,n,i,a,s,c,f,d,p,h=1,g=e,v=g.d,m=g.constructor,w=m.precision;if(g.s<1)throw Error(u+(g.s?"NaN":"-Infinity"));if(g.eq(o))return new m(0);if(null==t?(l=!1,d=w):d=t,g.eq(10))return null==t&&(l=!0),S(m,d);if(m.precision=d+=10,n=(r=y(v)).charAt(0),!(15e14>Math.abs(a=x(g))))return f=S(m,d+2,w).times(a+""),g=E(new m(n+"."+r.slice(1)),d-10).plus(f),m.precision=w,null==t?(l=!0,M(g,w)):g;for(;n<7&&1!=n||1==n&&r.charAt(1)>3;)n=(r=y((g=g.times(e)).d)).charAt(0),h++;for(a=x(g),n>1?(g=new m("0."+r),a++):g=new m(n+"."+r.slice(1)),c=s=g=b(g.minus(o),g.plus(o),d),p=M(g.times(g),d),i=3;;){if(s=M(s.times(p),d),y((f=c.plus(b(s,new m(i),d))).d).slice(0,d)===y(c.d).slice(0,d))return c=c.times(2),0!==a&&(c=c.plus(S(m,d+2,w).times(a+""))),c=b(c,new m(h),d),m.precision=w,null==t?(l=!0,M(c,w)):c;c=f,i+=2}}function C(e,t){var r,n,i;for((r=t.indexOf("."))>-1&&(t=t.replace(".","")),(n=t.search(/e/i))>0?(r<0&&(r=n),r+=+t.slice(n+1),t=t.substring(0,n)):r<0&&(r=t.length),n=0;48===t.charCodeAt(n);)++n;for(i=t.length;48===t.charCodeAt(i-1);)--i;if(t=t.slice(n,i)){if(i-=n,e.e=f((r=r-n-1)/7),e.d=[],n=(r+1)%7,r<0&&(n+=7),nh||e.e<-h))throw Error(c+r)}else e.s=0,e.e=0,e.d=[0];return e}function M(e,t,r){var n,i,o,a,u,s,p,g,v=e.d;for(a=1,o=v[0];o>=10;o/=10)a++;if((n=t-a)<0)n+=7,i=t,p=v[g=0];else{if((g=Math.ceil((n+1)/7))>=(o=v.length))return e;for(a=1,p=o=v[g];o>=10;o/=10)a++;n%=7,i=n-7+a}if(void 0!==r&&(u=p/(o=d(10,a-i-1))%10|0,s=t<0||void 0!==v[g+1]||p%o,s=r<4?(u||s)&&(0==r||r==(e.s<0?3:2)):u>5||5==u&&(4==r||s||6==r&&(n>0?i>0?p/d(10,a-i):0:v[g-1])%10&1||r==(e.s<0?8:7))),t<1||!v[0])return s?(o=x(e),v.length=1,t=t-o-1,v[0]=d(10,(7-t%7)%7),e.e=f(-t/7)||0):(v.length=1,v[0]=e.e=e.s=0),e;if(0==n?(v.length=g,o=1,g--):(v.length=g+1,o=d(10,7-n),v[g]=i>0?(p/d(10,a-i)%d(10,i)|0)*o:0),s)for(;;)if(0==g){1e7==(v[0]+=o)&&(v[0]=1,++e.e);break}else{if(v[g]+=o,1e7!=v[g])break;v[g--]=0,o=1}for(n=v.length;0===v[--n];)v.pop();if(l&&(e.e>h||e.e<-h))throw Error(c+x(e));return e}function k(e,t){var r,n,i,o,a,u,s,c,f,d,p=e.constructor,h=p.precision;if(!e.s||!t.s)return t.s?t.s=-t.s:t=new p(e),l?M(t,h):t;if(s=e.d,d=t.d,n=t.e,c=e.e,s=s.slice(),a=c-n){for((f=a<0)?(r=s,a=-a,u=d.length):(r=d,n=c,u=s.length),a>(i=Math.max(Math.ceil(h/7),u)+2)&&(a=i,r.length=1),r.reverse(),i=a;i--;)r.push(0);r.reverse()}else{for((f=(i=s.length)<(u=d.length))&&(u=i),i=0;i0;--i)s[u++]=0;for(i=d.length;i>a;){if(s[--i]0?o=o.charAt(0)+"."+o.slice(1)+O(n):a>1&&(o=o.charAt(0)+"."+o.slice(1)),o=o+(i<0?"e":"e+")+i):i<0?(o="0."+O(-i-1)+o,r&&(n=r-a)>0&&(o+=O(n))):i>=a?(o+=O(i+1-a),r&&(n=r-i-1)>0&&(o=o+"."+O(n))):((n=i+1)0&&(i+1===a&&(o+="."),o+=O(n))),e.s<0?"-"+o:o}function P(e,t){if(e.length>t)return e.length=t,!0}function A(e){if(!e||"object"!=typeof e)throw Error(u+"Object expected");var t,r,n,i=["precision",1,1e9,"rounding",0,8,"toExpNeg",-1/0,0,"toExpPos",0,1/0];for(t=0;t=i[t+1]&&n<=i[t+2])this[r]=n;else throw Error(s+r+": "+n);if(void 0!==(n=e[r="LN10"]))if(n==Math.LN10)this[r]=new this(n);else throw Error(s+r+": "+n);return this}(a=function e(t){var r,n,i;function o(e){if(!(this instanceof o))return new o(e);if(this.constructor=o,e instanceof o){this.s=e.s,this.e=e.e,this.d=(e=e.d)?e.slice():e;return}if("number"==typeof e){if(0*e!=0)throw Error(s+e);if(e>0)this.s=1;else if(e<0)e=-e,this.s=-1;else{this.s=0,this.e=0,this.d=[0];return}if(e===~~e&&e<1e7){this.e=0,this.d=[e];return}return C(this,e.toString())}if("string"!=typeof e)throw Error(s+e);if(45===e.charCodeAt(0)?(e=e.slice(1),this.s=-1):this.s=1,p.test(e))C(this,e);else throw Error(s+e)}if(o.prototype=g,o.ROUND_UP=0,o.ROUND_DOWN=1,o.ROUND_CEIL=2,o.ROUND_FLOOR=3,o.ROUND_HALF_UP=4,o.ROUND_HALF_DOWN=5,o.ROUND_HALF_EVEN=6,o.ROUND_HALF_CEIL=7,o.ROUND_HALF_FLOOR=8,o.clone=e,o.config=o.set=A,void 0===t&&(t={}),t)for(r=0,i=["precision","rounding","toExpNeg","toExpPos","LN10"];r{"use strict";function n(e){return Number.isFinite(e)}function i(e){return"number"==typeof e&&e>0&&Number.isFinite(e)}r.d(t,{F:()=>i,H:()=>n})},8924:(e,t,r)=>{"use strict";r.d(t,{Mz:()=>w});var n=e=>Array.isArray(e)?e:[e],i=0,o=class{revision=i;_value;_lastValue;_isEqual=a;constructor(e,t=a){this._value=this._lastValue=e,this._isEqual=t}get value(){return this._value}set value(e){this.value!==e&&(this._value=e,this.revision=++i)}};function a(e,t){return e===t}function l(e){return e instanceof o||console.warn("Not a valid cell! ",e),e.value}var u=(e,t)=>!1;function s(){return function(e,t=a){return new o(null,t)}(0,u)}var c=e=>{let t=e.collectionTag;null===t&&(t=e.collectionTag=s()),l(t)};Symbol();var f=0,d=Object.getPrototypeOf({}),p=class{constructor(e){this.value=e,this.value=e,this.tag.value=e}proxy=new Proxy(this,h);tag=s();tags={};children={};collectionTag=null;id=f++},h={get:(e,t)=>(function(){let{value:r}=e,n=Reflect.get(r,t);if("symbol"==typeof t||t in d)return n;if("object"==typeof n&&null!==n){var i;let r=e.children[t];return void 0===r&&(r=e.children[t]=Array.isArray(i=n)?new g(i):new p(i)),r.tag&&l(r.tag),r.proxy}{let r=e.tags[t];return void 0===r&&((r=e.tags[t]=s()).value=n),l(r),n}})(),ownKeys:e=>(c(e),Reflect.ownKeys(e.value)),getOwnPropertyDescriptor:(e,t)=>Reflect.getOwnPropertyDescriptor(e.value,t),has:(e,t)=>Reflect.has(e.value,t)},g=class{constructor(e){this.value=e,this.value=e,this.tag.value=e}proxy=new Proxy([this],v);tag=s();tags={};children={};collectionTag=null;id=f++},v={get:([e],t)=>("length"===t&&c(e),h.get(e,t)),ownKeys:([e])=>h.ownKeys(e),getOwnPropertyDescriptor:([e],t)=>h.getOwnPropertyDescriptor(e,t),has:([e],t)=>h.has(e,t)},m="undefined"!=typeof WeakRef?WeakRef:class{constructor(e){this.value=e}deref(){return this.value}};function y(){return{s:0,v:void 0,o:null,p:null}}function b(e,t={}){let r,n=y(),{resultEqualityCheck:i}=t,o=0;function a(){let t,a=n,{length:l}=arguments;for(let e=0;e{n=y(),a.resetResultsCount()},a.resultsCount=()=>o,a.resetResultsCount=()=>{o=0},a}var w=function(e,...t){let r="function"==typeof e?{memoize:e,memoizeOptions:t}:e,i=(...e)=>{let t,i=0,o=0,a={},l=e.pop();"object"==typeof l&&(a=l,l=e.pop()),function(e,t=`expected a function, instead received ${typeof e}`){if("function"!=typeof e)throw TypeError(t)}(l,`createSelector expects an output function after the inputs, but received: [${typeof l}]`);let{memoize:u,memoizeOptions:s=[],argsMemoize:c=b,argsMemoizeOptions:f=[],devModeChecks:d={}}={...r,...a},p=n(s),h=n(f),g=function(e){let t=Array.isArray(e[0])?e[0]:e;return!function(e,t="expected all items to be functions, instead received the following types: "){if(!e.every(e=>"function"==typeof e)){let r=e.map(e=>"function"==typeof e?`function ${e.name||"unnamed"}()`:typeof e).join(", ");throw TypeError(`${t}[${r}]`)}}(t,"createSelector expects all input-selectors to be functions, but received the following types: "),t}(e),v=u(function(){return i++,l.apply(null,arguments)},...p);return Object.assign(c(function(){o++;let e=function(e,t){let r=[],{length:n}=e;for(let i=0;io,resetDependencyRecomputations:()=>{o=0},lastResult:()=>t,recomputations:()=>i,resetRecomputations:()=>{i=0},memoize:u,argsMemoize:c})};return Object.assign(i,{withTypes:()=>i}),i}(b),x=Object.assign((e,t=w)=>{!function(e,t=`expected an object, instead received ${typeof e}`){if("object"!=typeof e)throw TypeError(t)}(e,`createStructuredSelector expects first argument to be an object where each property is a selector, instead received a ${typeof e}`);let r=Object.keys(e);return t(r.map(t=>e[t]),(...e)=>e.reduce((e,t,n)=>(e[r[n]]=t,e),{}))},{withTypes:()=>x})},9033:(e,t,r)=>{"use strict";r.d(t,{c:()=>i});var n=r(2115);function i(e){let t=n.useRef(e);return n.useEffect(()=>{t.current=e}),n.useMemo(()=>(...e)=>t.current?.(...e),[])}},9074:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});let n=(0,r(9946).A)("calendar",[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}]])},9095:(e,t,r)=>{"use strict";r.d(t,{E:()=>k});var n=r(2115),i=r(2596),o=r(6377),a=r(1643),l=r(788),u=r(6605),s=/(-?\d+(?:\.\d+)?[a-zA-Z%]*)([*/])(-?\d+(?:\.\d+)?[a-zA-Z%]*)/,c=/(-?\d+(?:\.\d+)?[a-zA-Z%]*)([+-])(-?\d+(?:\.\d+)?[a-zA-Z%]*)/,f=/^px|cm|vh|vw|em|rem|%|mm|in|pt|pc|ex|ch|vmin|vmax|Q$/,d=/(-?\d+(?:\.\d+)?)([a-zA-Z%]+)?/,p={cm:96/2.54,mm:96/25.4,pt:96/72,pc:16,in:96,Q:96/101.6,px:1},h=Object.keys(p);class g{static parse(e){var t,[,r,n]=null!=(t=d.exec(e))?t:[];return new g(parseFloat(r),null!=n?n:"")}add(e){return this.unit!==e.unit?new g(NaN,""):new g(this.num+e.num,this.unit)}subtract(e){return this.unit!==e.unit?new g(NaN,""):new g(this.num-e.num,this.unit)}multiply(e){return""!==this.unit&&""!==e.unit&&this.unit!==e.unit?new g(NaN,""):new g(this.num*e.num,this.unit||e.unit)}divide(e){return""!==this.unit&&""!==e.unit&&this.unit!==e.unit?new g(NaN,""):new g(this.num/e.num,this.unit||e.unit)}toString(){return"".concat(this.num).concat(this.unit)}isNaN(){return(0,o.M8)(this.num)}constructor(e,t){this.num=e,this.unit=t,this.num=e,this.unit=t,(0,o.M8)(e)&&(this.unit=""),""===t||f.test(t)||(this.num=NaN,this.unit=""),h.includes(t)&&(this.num=e*p[t],this.unit="px")}}function v(e){if(e.includes("NaN"))return"NaN";for(var t=e;t.includes("*")||t.includes("/");){var r,[,n,i,o]=null!=(r=s.exec(t))?r:[],a=g.parse(null!=n?n:""),l=g.parse(null!=o?o:""),u="*"===i?a.multiply(l):a.divide(l);if(u.isNaN())return"NaN";t=t.replace(s,u.toString())}for(;t.includes("+")||/.-\d+(?:\.\d+)?/.test(t);){var f,[,d,p,h]=null!=(f=c.exec(t))?f:[],v=g.parse(null!=d?d:""),m=g.parse(null!=h?h:""),y="+"===p?v.add(m):v.subtract(m);if(y.isNaN())return"NaN";t=t.replace(c,y.toString())}return t}var m=/\(([^()]*)\)/;function y(e){var t=function(e){try{var t;return t=e.replace(/\s+/g,""),t=function(e){for(var t,r=e;null!=(t=m.exec(r));){var[,n]=t;r=r.replace(m,v(n))}return r}(t),t=v(t)}catch(e){return"NaN"}}(e.slice(5,-1));return"NaN"===t?"":t}var b=["x","y","lineHeight","capHeight","scaleToFit","textAnchor","verticalAnchor","fill"],w=["dx","dy","angle","className","breakAll"];function x(){return(x=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:t,breakAll:r,style:n}=e;try{var i=[];(0,o.uy)(t)||(i=r?t.toString().split(""):t.toString().split(O));var a=i.map(e=>({word:e,width:(0,u.P)(e,n).width})),l=r?0:(0,u.P)("\xa0",n).width;return{wordsWithComputedWidth:a,spaceWidth:l}}catch(e){return null}},C=e=>[{words:(0,o.uy)(e)?[]:e.toString().split(O)}],M="#808080",k=(0,n.forwardRef)((e,t)=>{var r,{x:u=0,y:s=0,lineHeight:c="1em",capHeight:f="0.71em",scaleToFit:d=!1,textAnchor:p="start",verticalAnchor:h="end",fill:g=M}=e,v=S(e,b),m=(0,n.useMemo)(()=>(e=>{var{width:t,scaleToFit:r,children:n,style:i,breakAll:l,maxLines:u}=e;if((t||r)&&!a.m.isSsr){var s=E({breakAll:l,children:n,style:i});if(!s)return C(n);var{wordsWithComputedWidth:c,spaceWidth:f}=s;return((e,t,r,n,i)=>{var a,{maxLines:l,children:u,style:s,breakAll:c}=e,f=(0,o.Et)(l),d=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.reduce((e,t)=>{var{word:o,width:a}=t,l=e[e.length-1];return l&&(null==n||i||l.width+a+re.reduce((e,t)=>e.width>t.width?e:t);if(!f||i||!(p.length>l||h(p).width>Number(n)))return p;for(var g=e=>{var t=d(E({breakAll:c,style:s,children:u.slice(0,e)+"…"}).wordsWithComputedWidth);return[t.length>l||h(t).width>Number(n),t]},v=0,m=u.length-1,y=0;v<=m&&y<=u.length-1;){var b=Math.floor((v+m)/2),[w,x]=g(b-1),[S]=g(b);if(w||S||(v=b+1),w&&S&&(m=b-1),!w&&S){a=x;break}y++}return a||p})({breakAll:l,children:n,maxLines:u,style:i},c,f,t,r)}return C(n)})({breakAll:v.breakAll,children:v.children,maxLines:v.maxLines,scaleToFit:d,style:v.style,width:v.width}),[v.breakAll,v.children,v.maxLines,d,v.style,v.width]),{dx:O,dy:k,angle:_,className:P,breakAll:A}=v,j=S(v,w);if(!(0,o.vh)(u)||!(0,o.vh)(s))return null;var T=u+((0,o.Et)(O)?O:0),R=s+((0,o.Et)(k)?k:0);switch(h){case"start":r=y("calc(".concat(f,")"));break;case"middle":r=y("calc(".concat((m.length-1)/2," * -").concat(c," + (").concat(f," / 2))"));break;default:r=y("calc(".concat(m.length-1," * -").concat(c,")"))}var z=[];if(d){var D=m[0].width,{width:I}=v;z.push("scale(".concat((0,o.Et)(I)?I/D:1,")"))}return _&&z.push("rotate(".concat(_,", ").concat(T,", ").concat(R,")")),z.length&&(j.transform=z.join(" ")),n.createElement("text",x({},(0,l.J9)(j,!0),{ref:t,x:T,y:R,className:(0,i.$)("recharts-text",P),textAnchor:p,fill:g.includes("url")?M:g}),m.map((e,t)=>{var i=e.words.join(A?"":" ");return n.createElement("tspan",{x:T,dy:0===t?r:c,key:"".concat(i,"-").concat(t)},i)}))});k.displayName="Text"},9196:(e,t,r)=>{"use strict";r.d(t,{RG:()=>x,bL:()=>A,q7:()=>j});var n=r(2115),i=r(5185),o=r(7328),a=r(6101),l=r(6081),u=r(1285),s=r(3655),c=r(9033),f=r(5845),d=r(4315),p=r(5155),h="rovingFocusGroup.onEntryFocus",g={bubbles:!1,cancelable:!0},v="RovingFocusGroup",[m,y,b]=(0,o.N)(v),[w,x]=(0,l.A)(v,[b]),[S,O]=w(v),E=n.forwardRef((e,t)=>(0,p.jsx)(m.Provider,{scope:e.__scopeRovingFocusGroup,children:(0,p.jsx)(m.Slot,{scope:e.__scopeRovingFocusGroup,children:(0,p.jsx)(C,{...e,ref:t})})}));E.displayName=v;var C=n.forwardRef((e,t)=>{let{__scopeRovingFocusGroup:r,orientation:o,loop:l=!1,dir:u,currentTabStopId:m,defaultCurrentTabStopId:b,onCurrentTabStopIdChange:w,onEntryFocus:x,preventScrollOnEntryFocus:O=!1,...E}=e,C=n.useRef(null),M=(0,a.s)(t,C),k=(0,d.jH)(u),[_,A]=(0,f.i)({prop:m,defaultProp:null!=b?b:null,onChange:w,caller:v}),[j,T]=n.useState(!1),R=(0,c.c)(x),z=y(r),D=n.useRef(!1),[I,N]=n.useState(0);return n.useEffect(()=>{let e=C.current;if(e)return e.addEventListener(h,R),()=>e.removeEventListener(h,R)},[R]),(0,p.jsx)(S,{scope:r,orientation:o,dir:k,loop:l,currentTabStopId:_,onItemFocus:n.useCallback(e=>A(e),[A]),onItemShiftTab:n.useCallback(()=>T(!0),[]),onFocusableItemAdd:n.useCallback(()=>N(e=>e+1),[]),onFocusableItemRemove:n.useCallback(()=>N(e=>e-1),[]),children:(0,p.jsx)(s.sG.div,{tabIndex:j||0===I?-1:0,"data-orientation":o,...E,ref:M,style:{outline:"none",...e.style},onMouseDown:(0,i.m)(e.onMouseDown,()=>{D.current=!0}),onFocus:(0,i.m)(e.onFocus,e=>{let t=!D.current;if(e.target===e.currentTarget&&t&&!j){let t=new CustomEvent(h,g);if(e.currentTarget.dispatchEvent(t),!t.defaultPrevented){let e=z().filter(e=>e.focusable);P([e.find(e=>e.active),e.find(e=>e.id===_),...e].filter(Boolean).map(e=>e.ref.current),O)}}D.current=!1}),onBlur:(0,i.m)(e.onBlur,()=>T(!1))})})}),M="RovingFocusGroupItem",k=n.forwardRef((e,t)=>{let{__scopeRovingFocusGroup:r,focusable:o=!0,active:a=!1,tabStopId:l,children:c,...f}=e,d=(0,u.B)(),h=l||d,g=O(M,r),v=g.currentTabStopId===h,b=y(r),{onFocusableItemAdd:w,onFocusableItemRemove:x,currentTabStopId:S}=g;return n.useEffect(()=>{if(o)return w(),()=>x()},[o,w,x]),(0,p.jsx)(m.ItemSlot,{scope:r,id:h,focusable:o,active:a,children:(0,p.jsx)(s.sG.span,{tabIndex:v?0:-1,"data-orientation":g.orientation,...f,ref:t,onMouseDown:(0,i.m)(e.onMouseDown,e=>{o?g.onItemFocus(h):e.preventDefault()}),onFocus:(0,i.m)(e.onFocus,()=>g.onItemFocus(h)),onKeyDown:(0,i.m)(e.onKeyDown,e=>{if("Tab"===e.key&&e.shiftKey)return void g.onItemShiftTab();if(e.target!==e.currentTarget)return;let t=function(e,t,r){var n;let i=(n=e.key,"rtl"!==r?n:"ArrowLeft"===n?"ArrowRight":"ArrowRight"===n?"ArrowLeft":n);if(!("vertical"===t&&["ArrowLeft","ArrowRight"].includes(i))&&!("horizontal"===t&&["ArrowUp","ArrowDown"].includes(i)))return _[i]}(e,g.orientation,g.dir);if(void 0!==t){if(e.metaKey||e.ctrlKey||e.altKey||e.shiftKey)return;e.preventDefault();let r=b().filter(e=>e.focusable).map(e=>e.ref.current);if("last"===t)r.reverse();else if("prev"===t||"next"===t){"prev"===t&&r.reverse();let n=r.indexOf(e.currentTarget);r=g.loop?function(e,t){return e.map((r,n)=>e[(t+n)%e.length])}(r,n+1):r.slice(n+1)}setTimeout(()=>P(r))}}),children:"function"==typeof c?c({isCurrentTabStop:v,hasTabStop:null!=S}):c})})});k.displayName=M;var _={ArrowLeft:"prev",ArrowUp:"prev",ArrowRight:"next",ArrowDown:"next",PageUp:"first",Home:"first",PageDown:"last",End:"last"};function P(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=document.activeElement;for(let n of e)if(n===r||(n.focus({preventScroll:t}),document.activeElement!==r))return}var A=E,j=k},9279:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isObjectLike=function(e){return"object"==typeof e&&null!==e}},9452:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isLength=function(e){return Number.isSafeInteger(e)&&e>=0}},9641:e=>{!function(){var t={675:function(e,t){"use strict";t.byteLength=function(e){var t=u(e),r=t[0],n=t[1];return(r+n)*3/4-n},t.toByteArray=function(e){var t,r,o=u(e),a=o[0],l=o[1],s=new i((a+l)*3/4-l),c=0,f=l>0?a-4:a;for(r=0;r>16&255,s[c++]=t>>8&255,s[c++]=255&t;return 2===l&&(t=n[e.charCodeAt(r)]<<2|n[e.charCodeAt(r+1)]>>4,s[c++]=255&t),1===l&&(t=n[e.charCodeAt(r)]<<10|n[e.charCodeAt(r+1)]<<4|n[e.charCodeAt(r+2)]>>2,s[c++]=t>>8&255,s[c++]=255&t),s},t.fromByteArray=function(e){for(var t,n=e.length,i=n%3,o=[],a=0,l=n-i;a>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return o.join("")}(e,a,a+16383>l?l:a+16383));return 1===i?o.push(r[(t=e[n-1])>>2]+r[t<<4&63]+"=="):2===i&&o.push(r[(t=(e[n-2]<<8)+e[n-1])>>10]+r[t>>4&63]+r[t<<2&63]+"="),o.join("")};for(var r=[],n=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,l=o.length;a0)throw Error("Invalid string. Length must be a multiple of 4");var r=e.indexOf("=");-1===r&&(r=t);var n=r===t?0:4-r%4;return[r,n]}n[45]=62,n[95]=63},72:function(e,t,r){"use strict";var n=r(675),i=r(783),o="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;function a(e){if(e>0x7fffffff)throw RangeError('The value "'+e+'" is invalid for option "size"');var t=new Uint8Array(e);return Object.setPrototypeOf(t,l.prototype),t}function l(e,t,r){if("number"==typeof e){if("string"==typeof t)throw TypeError('The "string" argument must be of type string. Received type number');return c(e)}return u(e,t,r)}function u(e,t,r){if("string"==typeof e){var n=e,i=t;if(("string"!=typeof i||""===i)&&(i="utf8"),!l.isEncoding(i))throw TypeError("Unknown encoding: "+i);var o=0|p(n,i),u=a(o),s=u.write(n,i);return s!==o&&(u=u.slice(0,s)),u}if(ArrayBuffer.isView(e))return f(e);if(null==e)throw TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(P(e,ArrayBuffer)||e&&P(e.buffer,ArrayBuffer)||"undefined"!=typeof SharedArrayBuffer&&(P(e,SharedArrayBuffer)||e&&P(e.buffer,SharedArrayBuffer)))return function(e,t,r){var n;if(t<0||e.byteLength=0x7fffffff)throw RangeError("Attempt to allocate Buffer larger than maximum size: 0x7fffffff bytes");return 0|e}function p(e,t){if(l.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||P(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);var r=e.length,n=arguments.length>2&&!0===arguments[2];if(!n&&0===r)return 0;for(var i=!1;;)switch(t){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":return C(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return k(e).length;default:if(i)return n?-1:C(e).length;t=(""+t).toLowerCase(),i=!0}}function h(e,t,r){var i,o,a,l=!1;if((void 0===t||t<0)&&(t=0),t>this.length||((void 0===r||r>this.length)&&(r=this.length),r<=0||(r>>>=0)<=(t>>>=0)))return"";for(e||(e="utf8");;)switch(e){case"hex":return function(e,t,r){var n=e.length;(!t||t<0)&&(t=0),(!r||r<0||r>n)&&(r=n);for(var i="",o=t;o0x7fffffff?r=0x7fffffff:r<-0x80000000&&(r=-0x80000000),(o=r*=1)!=o&&(r=i?0:e.length-1),r<0&&(r=e.length+r),r>=e.length)if(i)return -1;else r=e.length-1;else if(r<0)if(!i)return -1;else r=0;if("string"==typeof t&&(t=l.from(t,n)),l.isBuffer(t))return 0===t.length?-1:m(e,t,r,n,i);if("number"==typeof t){if(t&=255,"function"==typeof Uint8Array.prototype.indexOf)if(i)return Uint8Array.prototype.indexOf.call(e,t,r);else return Uint8Array.prototype.lastIndexOf.call(e,t,r);return m(e,[t],r,n,i)}throw TypeError("val must be string, number or Buffer")}function m(e,t,r,n,i){var o,a=1,l=e.length,u=t.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(e.length<2||t.length<2)return -1;a=2,l/=2,u/=2,r/=2}function s(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(i){var c=-1;for(o=r;ol&&(r=l-u),o=r;o>=0;o--){for(var f=!0,d=0;dr&&(e+=" ... "),""},o&&(l.prototype[o]=l.prototype.inspect),l.prototype.compare=function(e,t,r,n,i){if(P(e,Uint8Array)&&(e=l.from(e,e.offset,e.byteLength)),!l.isBuffer(e))throw TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===n&&(n=0),void 0===i&&(i=this.length),t<0||r>e.length||n<0||i>this.length)throw RangeError("out of range index");if(n>=i&&t>=r)return 0;if(n>=i)return -1;if(t>=r)return 1;if(t>>>=0,r>>>=0,n>>>=0,i>>>=0,this===e)return 0;for(var o=i-n,a=r-t,u=Math.min(o,a),s=this.slice(n,i),c=e.slice(t,r),f=0;f239?4:s>223?3:s>191?2:1;if(i+f<=r)switch(f){case 1:s<128&&(c=s);break;case 2:(192&(o=e[i+1]))==128&&(u=(31&s)<<6|63&o)>127&&(c=u);break;case 3:o=e[i+1],a=e[i+2],(192&o)==128&&(192&a)==128&&(u=(15&s)<<12|(63&o)<<6|63&a)>2047&&(u<55296||u>57343)&&(c=u);break;case 4:o=e[i+1],a=e[i+2],l=e[i+3],(192&o)==128&&(192&a)==128&&(192&l)==128&&(u=(15&s)<<18|(63&o)<<12|(63&a)<<6|63&l)>65535&&u<1114112&&(c=u)}null===c?(c=65533,f=1):c>65535&&(c-=65536,n.push(c>>>10&1023|55296),c=56320|1023&c),n.push(c),i+=f}var d=n,p=d.length;if(p<=4096)return String.fromCharCode.apply(String,d);for(var h="",g=0;gr)throw RangeError("Trying to access beyond buffer length")}function w(e,t,r,n,i,o){if(!l.isBuffer(e))throw TypeError('"buffer" argument must be a Buffer instance');if(t>i||te.length)throw RangeError("Index out of range")}function x(e,t,r,n,i,o){if(r+n>e.length||r<0)throw RangeError("Index out of range")}function S(e,t,r,n,o){return t*=1,r>>>=0,o||x(e,t,r,4,34028234663852886e22,-34028234663852886e22),i.write(e,t,r,n,23,4),r+4}function O(e,t,r,n,o){return t*=1,r>>>=0,o||x(e,t,r,8,17976931348623157e292,-17976931348623157e292),i.write(e,t,r,n,52,8),r+8}l.prototype.write=function(e,t,r,n){if(void 0===t)n="utf8",r=this.length,t=0;else if(void 0===r&&"string"==typeof t)n=t,r=this.length,t=0;else if(isFinite(t))t>>>=0,isFinite(r)?(r>>>=0,void 0===n&&(n="utf8")):(n=r,r=void 0);else throw Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");var i,o,a,l,u,s,c,f,d=this.length-t;if((void 0===r||r>d)&&(r=d),e.length>0&&(r<0||t<0)||t>this.length)throw RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var p=!1;;)switch(n){case"hex":return function(e,t,r,n){r=Number(r)||0;var i=e.length-r;n?(n=Number(n))>i&&(n=i):n=i;var o=t.length;n>o/2&&(n=o/2);for(var a=0;a>8,i.push(r%256),i.push(n);return i}(e,this.length-c),this,c,f);default:if(p)throw TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),p=!0}},l.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}},l.prototype.slice=function(e,t){var r=this.length;e=~~e,t=void 0===t?r:~~t,e<0?(e+=r)<0&&(e=0):e>r&&(e=r),t<0?(t+=r)<0&&(t=0):t>r&&(t=r),t>>=0,t>>>=0,r||b(e,t,this.length);for(var n=this[e],i=1,o=0;++o>>=0,t>>>=0,r||b(e,t,this.length);for(var n=this[e+--t],i=1;t>0&&(i*=256);)n+=this[e+--t]*i;return n},l.prototype.readUInt8=function(e,t){return e>>>=0,t||b(e,1,this.length),this[e]},l.prototype.readUInt16LE=function(e,t){return e>>>=0,t||b(e,2,this.length),this[e]|this[e+1]<<8},l.prototype.readUInt16BE=function(e,t){return e>>>=0,t||b(e,2,this.length),this[e]<<8|this[e+1]},l.prototype.readUInt32LE=function(e,t){return e>>>=0,t||b(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+0x1000000*this[e+3]},l.prototype.readUInt32BE=function(e,t){return e>>>=0,t||b(e,4,this.length),0x1000000*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},l.prototype.readIntLE=function(e,t,r){e>>>=0,t>>>=0,r||b(e,t,this.length);for(var n=this[e],i=1,o=0;++o=(i*=128)&&(n-=Math.pow(2,8*t)),n},l.prototype.readIntBE=function(e,t,r){e>>>=0,t>>>=0,r||b(e,t,this.length);for(var n=t,i=1,o=this[e+--n];n>0&&(i*=256);)o+=this[e+--n]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*t)),o},l.prototype.readInt8=function(e,t){return(e>>>=0,t||b(e,1,this.length),128&this[e])?-((255-this[e]+1)*1):this[e]},l.prototype.readInt16LE=function(e,t){e>>>=0,t||b(e,2,this.length);var r=this[e]|this[e+1]<<8;return 32768&r?0xffff0000|r:r},l.prototype.readInt16BE=function(e,t){e>>>=0,t||b(e,2,this.length);var r=this[e+1]|this[e]<<8;return 32768&r?0xffff0000|r:r},l.prototype.readInt32LE=function(e,t){return e>>>=0,t||b(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},l.prototype.readInt32BE=function(e,t){return e>>>=0,t||b(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},l.prototype.readFloatLE=function(e,t){return e>>>=0,t||b(e,4,this.length),i.read(this,e,!0,23,4)},l.prototype.readFloatBE=function(e,t){return e>>>=0,t||b(e,4,this.length),i.read(this,e,!1,23,4)},l.prototype.readDoubleLE=function(e,t){return e>>>=0,t||b(e,8,this.length),i.read(this,e,!0,52,8)},l.prototype.readDoubleBE=function(e,t){return e>>>=0,t||b(e,8,this.length),i.read(this,e,!1,52,8)},l.prototype.writeUIntLE=function(e,t,r,n){if(e*=1,t>>>=0,r>>>=0,!n){var i=Math.pow(2,8*r)-1;w(this,e,t,r,i,0)}var o=1,a=0;for(this[t]=255&e;++a>>=0,r>>>=0,!n){var i=Math.pow(2,8*r)-1;w(this,e,t,r,i,0)}var o=r-1,a=1;for(this[t+o]=255&e;--o>=0&&(a*=256);)this[t+o]=e/a&255;return t+r},l.prototype.writeUInt8=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,1,255,0),this[t]=255&e,t+1},l.prototype.writeUInt16LE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeUInt16BE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeUInt32LE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,4,0xffffffff,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},l.prototype.writeUInt32BE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,4,0xffffffff,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeIntLE=function(e,t,r,n){if(e*=1,t>>>=0,!n){var i=Math.pow(2,8*r-1);w(this,e,t,r,i-1,-i)}var o=0,a=1,l=0;for(this[t]=255&e;++o>>=0,!n){var i=Math.pow(2,8*r-1);w(this,e,t,r,i-1,-i)}var o=r-1,a=1,l=0;for(this[t+o]=255&e;--o>=0&&(a*=256);)e<0&&0===l&&0!==this[t+o+1]&&(l=1),this[t+o]=(e/a|0)-l&255;return t+r},l.prototype.writeInt8=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},l.prototype.writeInt16LE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeInt16BE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeInt32LE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,4,0x7fffffff,-0x80000000),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},l.prototype.writeInt32BE=function(e,t,r){return e*=1,t>>>=0,r||w(this,e,t,4,0x7fffffff,-0x80000000),e<0&&(e=0xffffffff+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeFloatLE=function(e,t,r){return S(this,e,t,!0,r)},l.prototype.writeFloatBE=function(e,t,r){return S(this,e,t,!1,r)},l.prototype.writeDoubleLE=function(e,t,r){return O(this,e,t,!0,r)},l.prototype.writeDoubleBE=function(e,t,r){return O(this,e,t,!1,r)},l.prototype.copy=function(e,t,r,n){if(!l.isBuffer(e))throw TypeError("argument should be a Buffer");if(r||(r=0),n||0===n||(n=this.length),t>=e.length&&(t=e.length),t||(t=0),n>0&&n=this.length)throw RangeError("Index out of range");if(n<0)throw RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),e.length-t=0;--o)e[o+t]=this[o+r];else Uint8Array.prototype.set.call(e,this.subarray(r,n),t);return i},l.prototype.fill=function(e,t,r,n){if("string"==typeof e){if("string"==typeof t?(n=t,t=0,r=this.length):"string"==typeof r&&(n=r,r=this.length),void 0!==n&&"string"!=typeof n)throw TypeError("encoding must be a string");if("string"==typeof n&&!l.isEncoding(n))throw TypeError("Unknown encoding: "+n);if(1===e.length){var i,o=e.charCodeAt(0);("utf8"===n&&o<128||"latin1"===n)&&(e=o)}}else"number"==typeof e?e&=255:"boolean"==typeof e&&(e=Number(e));if(t<0||this.length>>=0,r=void 0===r?this.length:r>>>0,e||(e=0),"number"==typeof e)for(i=t;i55295&&r<57344){if(!i){if(r>56319||a+1===n){(t-=3)>-1&&o.push(239,191,189);continue}i=r;continue}if(r<56320){(t-=3)>-1&&o.push(239,191,189),i=r;continue}r=(i-55296<<10|r-56320)+65536}else i&&(t-=3)>-1&&o.push(239,191,189);if(i=null,r<128){if((t-=1)<0)break;o.push(r)}else if(r<2048){if((t-=2)<0)break;o.push(r>>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;o.push(r>>12|224,r>>6&63|128,63&r|128)}else if(r<1114112){if((t-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}else throw Error("Invalid code point")}return o}function M(e){for(var t=[],r=0;r=t.length)&&!(i>=e.length);++i)t[i+r]=e[i];return i}function P(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}var A=function(){for(var e="0123456789abcdef",t=Array(256),r=0;r<16;++r)for(var n=16*r,i=0;i<16;++i)t[n+i]=e[r]+e[i];return t}()},783:function(e,t){t.read=function(e,t,r,n,i){var o,a,l=8*i-n-1,u=(1<>1,c=-7,f=r?i-1:0,d=r?-1:1,p=e[t+f];for(f+=d,o=p&(1<<-c)-1,p>>=-c,c+=l;c>0;o=256*o+e[t+f],f+=d,c-=8);for(a=o&(1<<-c)-1,o>>=-c,c+=n;c>0;a=256*a+e[t+f],f+=d,c-=8);if(0===o)o=1-s;else{if(o===u)return a?NaN:1/0*(p?-1:1);a+=Math.pow(2,n),o-=s}return(p?-1:1)*a*Math.pow(2,o-n)},t.write=function(e,t,r,n,i,o){var a,l,u,s=8*o-i-1,c=(1<>1,d=5960464477539062e-23*(23===i),p=n?0:o-1,h=n?1:-1,g=+(t<0||0===t&&1/t<0);for(isNaN(t=Math.abs(t))||t===1/0?(l=+!!isNaN(t),a=c):(a=Math.floor(Math.log(t)/Math.LN2),t*(u=Math.pow(2,-a))<1&&(a--,u*=2),a+f>=1?t+=d/u:t+=d*Math.pow(2,1-f),t*u>=2&&(a++,u/=2),a+f>=c?(l=0,a=c):a+f>=1?(l=(t*u-1)*Math.pow(2,i),a+=f):(l=t*Math.pow(2,f-1)*Math.pow(2,i),a=0));i>=8;e[r+p]=255&l,p+=h,l/=256,i-=8);for(a=a<0;e[r+p]=255&a,p+=h,a/=256,s-=8);e[r+p-h]|=128*g}}},r={};function n(e){var i=r[e];if(void 0!==i)return i.exports;var o=r[e]={exports:{}},a=!0;try{t[e](o,o.exports,n),a=!1}finally{a&&delete r[e]}return o.exports}n.ab="//",e.exports=n(72)}()},9688:(e,t,r)=>{"use strict";r.d(t,{QP:()=>ee});let n=(e,t)=>{if(0===e.length)return t.classGroupId;let r=e[0],i=t.nextPart.get(r),o=i?n(e.slice(1),i):void 0;if(o)return o;if(0===t.validators.length)return;let a=e.join("-");return t.validators.find(({validator:e})=>e(a))?.classGroupId},i=/^\[(.+)\]$/,o=(e,t,r,n)=>{e.forEach(e=>{if("string"==typeof e){(""===e?t:a(t,e)).classGroupId=r;return}if("function"==typeof e)return l(e)?void o(e(n),t,r,n):void t.validators.push({validator:e,classGroupId:r});Object.entries(e).forEach(([e,i])=>{o(i,a(t,e),r,n)})})},a=(e,t)=>{let r=e;return t.split("-").forEach(e=>{r.nextPart.has(e)||r.nextPart.set(e,{nextPart:new Map,validators:[]}),r=r.nextPart.get(e)}),r},l=e=>e.isThemeGetter,u=/\s+/;function s(){let e,t,r=0,n="";for(;r{let t;if("string"==typeof e)return e;let r="";for(let n=0;n{let t=t=>t[e]||[];return t.isThemeGetter=!0,t},d=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,p=/^\((?:(\w[\w-]*):)?(.+)\)$/i,h=/^\d+\/\d+$/,g=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,v=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,m=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,y=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,b=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,w=e=>h.test(e),x=e=>!!e&&!Number.isNaN(Number(e)),S=e=>!!e&&Number.isInteger(Number(e)),O=e=>e.endsWith("%")&&x(e.slice(0,-1)),E=e=>g.test(e),C=()=>!0,M=e=>v.test(e)&&!m.test(e),k=()=>!1,_=e=>y.test(e),P=e=>b.test(e),A=e=>!T(e)&&!L(e),j=e=>G(e,q,k),T=e=>d.test(e),R=e=>G(e,Y,M),z=e=>G(e,X,x),D=e=>G(e,W,k),I=e=>G(e,K,P),N=e=>G(e,Q,_),L=e=>p.test(e),F=e=>Z(e,Y),$=e=>Z(e,J),B=e=>Z(e,W),V=e=>Z(e,q),U=e=>Z(e,K),H=e=>Z(e,Q,!0),G=(e,t,r)=>{let n=d.exec(e);return!!n&&(n[1]?t(n[1]):r(n[2]))},Z=(e,t,r=!1)=>{let n=p.exec(e);return!!n&&(n[1]?t(n[1]):r)},W=e=>"position"===e||"percentage"===e,K=e=>"image"===e||"url"===e,q=e=>"length"===e||"size"===e||"bg-size"===e,Y=e=>"length"===e,X=e=>"number"===e,J=e=>"family-name"===e,Q=e=>"shadow"===e;Symbol.toStringTag;let ee=function(e,...t){let r,a,l,c=function(u){let s;return a=(r={cache:(e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let t=0,r=new Map,n=new Map,i=(i,o)=>{r.set(i,o),++t>e&&(t=0,n=r,r=new Map)};return{get(e){let t=r.get(e);return void 0!==t?t:void 0!==(t=n.get(e))?(i(e,t),t):void 0},set(e,t){r.has(e)?r.set(e,t):i(e,t)}}})((s=t.reduce((e,t)=>t(e),e())).cacheSize),parseClassName:(e=>{let{prefix:t,experimentalParseClassName:r}=e,n=e=>{let t,r,n=[],i=0,o=0,a=0;for(let r=0;ra?t-a:void 0}};if(t){let e=t+":",r=n;n=t=>t.startsWith(e)?r(t.substring(e.length)):{isExternal:!0,modifiers:[],hasImportantModifier:!1,baseClassName:t,maybePostfixModifierPosition:void 0}}if(r){let e=n;n=t=>r({className:t,parseClassName:e})}return n})(s),sortModifiers:(e=>{let t=Object.fromEntries(e.orderSensitiveModifiers.map(e=>[e,!0]));return e=>{if(e.length<=1)return e;let r=[],n=[];return e.forEach(e=>{"["===e[0]||t[e]?(r.push(...n.sort(),e),n=[]):n.push(e)}),r.push(...n.sort()),r}})(s),...(e=>{let t=(e=>{let{theme:t,classGroups:r}=e,n={nextPart:new Map,validators:[]};for(let e in r)o(r[e],n,e,t);return n})(e),{conflictingClassGroups:r,conflictingClassGroupModifiers:a}=e;return{getClassGroupId:e=>{let r=e.split("-");return""===r[0]&&1!==r.length&&r.shift(),n(r,t)||(e=>{if(i.test(e)){let t=i.exec(e)[1],r=t?.substring(0,t.indexOf(":"));if(r)return"arbitrary.."+r}})(e)},getConflictingClassGroupIds:(e,t)=>{let n=r[e]||[];return t&&a[e]?[...n,...a[e]]:n}}})(s)}).cache.get,l=r.cache.set,c=f,f(u)};function f(e){let t=a(e);if(t)return t;let n=((e,t)=>{let{parseClassName:r,getClassGroupId:n,getConflictingClassGroupIds:i,sortModifiers:o}=t,a=[],l=e.trim().split(u),s="";for(let e=l.length-1;e>=0;e-=1){let t=l[e],{isExternal:u,modifiers:c,hasImportantModifier:f,baseClassName:d,maybePostfixModifierPosition:p}=r(t);if(u){s=t+(s.length>0?" "+s:s);continue}let h=!!p,g=n(h?d.substring(0,p):d);if(!g){if(!h||!(g=n(d))){s=t+(s.length>0?" "+s:s);continue}h=!1}let v=o(c).join(":"),m=f?v+"!":v,y=m+g;if(a.includes(y))continue;a.push(y);let b=i(g,h);for(let e=0;e0?" "+s:s)}return s})(e,r);return l(e,n),n}return function(){return c(s.apply(null,arguments))}}(()=>{let e=f("color"),t=f("font"),r=f("text"),n=f("font-weight"),i=f("tracking"),o=f("leading"),a=f("breakpoint"),l=f("container"),u=f("spacing"),s=f("radius"),c=f("shadow"),d=f("inset-shadow"),p=f("text-shadow"),h=f("drop-shadow"),g=f("blur"),v=f("perspective"),m=f("aspect"),y=f("ease"),b=f("animate"),M=()=>["auto","avoid","all","avoid-page","page","left","right","column"],k=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom"],_=()=>[...k(),L,T],P=()=>["auto","hidden","clip","visible","scroll"],G=()=>["auto","contain","none"],Z=()=>[L,T,u],W=()=>[w,"full","auto",...Z()],K=()=>[S,"none","subgrid",L,T],q=()=>["auto",{span:["full",S,L,T]},S,L,T],Y=()=>[S,"auto",L,T],X=()=>["auto","min","max","fr",L,T],J=()=>["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"],Q=()=>["start","end","center","stretch","center-safe","end-safe"],ee=()=>["auto",...Z()],et=()=>[w,"auto","full","dvw","dvh","lvw","lvh","svw","svh","min","max","fit",...Z()],er=()=>[e,L,T],en=()=>[...k(),B,D,{position:[L,T]}],ei=()=>["no-repeat",{repeat:["","x","y","space","round"]}],eo=()=>["auto","cover","contain",V,j,{size:[L,T]}],ea=()=>[O,F,R],el=()=>["","none","full",s,L,T],eu=()=>["",x,F,R],es=()=>["solid","dashed","dotted","double"],ec=()=>["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],ef=()=>[x,O,B,D],ed=()=>["","none",g,L,T],ep=()=>["none",x,L,T],eh=()=>["none",x,L,T],eg=()=>[x,L,T],ev=()=>[w,"full",...Z()];return{cacheSize:500,theme:{animate:["spin","ping","pulse","bounce"],aspect:["video"],blur:[E],breakpoint:[E],color:[C],container:[E],"drop-shadow":[E],ease:["in","out","in-out"],font:[A],"font-weight":["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"],"inset-shadow":[E],leading:["none","tight","snug","normal","relaxed","loose"],perspective:["dramatic","near","normal","midrange","distant","none"],radius:[E],shadow:[E],spacing:["px",x],text:[E],"text-shadow":[E],tracking:["tighter","tight","normal","wide","wider","widest"]},classGroups:{aspect:[{aspect:["auto","square",w,T,L,m]}],container:["container"],columns:[{columns:[x,T,L,l]}],"break-after":[{"break-after":M()}],"break-before":[{"break-before":M()}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"]}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{box:["border","content"]}],display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"],sr:["sr-only","not-sr-only"],float:[{float:["right","left","none","start","end"]}],clear:[{clear:["left","right","both","none","start","end"]}],isolation:["isolate","isolation-auto"],"object-fit":[{object:["contain","cover","fill","none","scale-down"]}],"object-position":[{object:_()}],overflow:[{overflow:P()}],"overflow-x":[{"overflow-x":P()}],"overflow-y":[{"overflow-y":P()}],overscroll:[{overscroll:G()}],"overscroll-x":[{"overscroll-x":G()}],"overscroll-y":[{"overscroll-y":G()}],position:["static","fixed","absolute","relative","sticky"],inset:[{inset:W()}],"inset-x":[{"inset-x":W()}],"inset-y":[{"inset-y":W()}],start:[{start:W()}],end:[{end:W()}],top:[{top:W()}],right:[{right:W()}],bottom:[{bottom:W()}],left:[{left:W()}],visibility:["visible","invisible","collapse"],z:[{z:[S,"auto",L,T]}],basis:[{basis:[w,"full","auto",l,...Z()]}],"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}],"flex-wrap":[{flex:["nowrap","wrap","wrap-reverse"]}],flex:[{flex:[x,w,"auto","initial","none",T]}],grow:[{grow:["",x,L,T]}],shrink:[{shrink:["",x,L,T]}],order:[{order:[S,"first","last","none",L,T]}],"grid-cols":[{"grid-cols":K()}],"col-start-end":[{col:q()}],"col-start":[{"col-start":Y()}],"col-end":[{"col-end":Y()}],"grid-rows":[{"grid-rows":K()}],"row-start-end":[{row:q()}],"row-start":[{"row-start":Y()}],"row-end":[{"row-end":Y()}],"grid-flow":[{"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{"auto-cols":X()}],"auto-rows":[{"auto-rows":X()}],gap:[{gap:Z()}],"gap-x":[{"gap-x":Z()}],"gap-y":[{"gap-y":Z()}],"justify-content":[{justify:[...J(),"normal"]}],"justify-items":[{"justify-items":[...Q(),"normal"]}],"justify-self":[{"justify-self":["auto",...Q()]}],"align-content":[{content:["normal",...J()]}],"align-items":[{items:[...Q(),{baseline:["","last"]}]}],"align-self":[{self:["auto",...Q(),{baseline:["","last"]}]}],"place-content":[{"place-content":J()}],"place-items":[{"place-items":[...Q(),"baseline"]}],"place-self":[{"place-self":["auto",...Q()]}],p:[{p:Z()}],px:[{px:Z()}],py:[{py:Z()}],ps:[{ps:Z()}],pe:[{pe:Z()}],pt:[{pt:Z()}],pr:[{pr:Z()}],pb:[{pb:Z()}],pl:[{pl:Z()}],m:[{m:ee()}],mx:[{mx:ee()}],my:[{my:ee()}],ms:[{ms:ee()}],me:[{me:ee()}],mt:[{mt:ee()}],mr:[{mr:ee()}],mb:[{mb:ee()}],ml:[{ml:ee()}],"space-x":[{"space-x":Z()}],"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":Z()}],"space-y-reverse":["space-y-reverse"],size:[{size:et()}],w:[{w:[l,"screen",...et()]}],"min-w":[{"min-w":[l,"screen","none",...et()]}],"max-w":[{"max-w":[l,"screen","none","prose",{screen:[a]},...et()]}],h:[{h:["screen","lh",...et()]}],"min-h":[{"min-h":["screen","lh","none",...et()]}],"max-h":[{"max-h":["screen","lh",...et()]}],"font-size":[{text:["base",r,F,R]}],"font-smoothing":["antialiased","subpixel-antialiased"],"font-style":["italic","not-italic"],"font-weight":[{font:[n,L,z]}],"font-stretch":[{"font-stretch":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded",O,T]}],"font-family":[{font:[$,T,t]}],"fvn-normal":["normal-nums"],"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"],"fvn-figure":["lining-nums","oldstyle-nums"],"fvn-spacing":["proportional-nums","tabular-nums"],"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{tracking:[i,L,T]}],"line-clamp":[{"line-clamp":[x,"none",L,z]}],leading:[{leading:[o,...Z()]}],"list-image":[{"list-image":["none",L,T]}],"list-style-position":[{list:["inside","outside"]}],"list-style-type":[{list:["disc","decimal","none",L,T]}],"text-alignment":[{text:["left","center","right","justify","start","end"]}],"placeholder-color":[{placeholder:er()}],"text-color":[{text:er()}],"text-decoration":["underline","overline","line-through","no-underline"],"text-decoration-style":[{decoration:[...es(),"wavy"]}],"text-decoration-thickness":[{decoration:[x,"from-font","auto",L,R]}],"text-decoration-color":[{decoration:er()}],"underline-offset":[{"underline-offset":[x,"auto",L,T]}],"text-transform":["uppercase","lowercase","capitalize","normal-case"],"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:Z()}],"vertical-align":[{align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",L,T]}],whitespace:[{whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}],break:[{break:["normal","words","all","keep"]}],wrap:[{wrap:["break-word","anywhere","normal"]}],hyphens:[{hyphens:["none","manual","auto"]}],content:[{content:["none",L,T]}],"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{"bg-clip":["border","padding","content","text"]}],"bg-origin":[{"bg-origin":["border","padding","content"]}],"bg-position":[{bg:en()}],"bg-repeat":[{bg:ei()}],"bg-size":[{bg:eo()}],"bg-image":[{bg:["none",{linear:[{to:["t","tr","r","br","b","bl","l","tl"]},S,L,T],radial:["",L,T],conic:[S,L,T]},U,I]}],"bg-color":[{bg:er()}],"gradient-from-pos":[{from:ea()}],"gradient-via-pos":[{via:ea()}],"gradient-to-pos":[{to:ea()}],"gradient-from":[{from:er()}],"gradient-via":[{via:er()}],"gradient-to":[{to:er()}],rounded:[{rounded:el()}],"rounded-s":[{"rounded-s":el()}],"rounded-e":[{"rounded-e":el()}],"rounded-t":[{"rounded-t":el()}],"rounded-r":[{"rounded-r":el()}],"rounded-b":[{"rounded-b":el()}],"rounded-l":[{"rounded-l":el()}],"rounded-ss":[{"rounded-ss":el()}],"rounded-se":[{"rounded-se":el()}],"rounded-ee":[{"rounded-ee":el()}],"rounded-es":[{"rounded-es":el()}],"rounded-tl":[{"rounded-tl":el()}],"rounded-tr":[{"rounded-tr":el()}],"rounded-br":[{"rounded-br":el()}],"rounded-bl":[{"rounded-bl":el()}],"border-w":[{border:eu()}],"border-w-x":[{"border-x":eu()}],"border-w-y":[{"border-y":eu()}],"border-w-s":[{"border-s":eu()}],"border-w-e":[{"border-e":eu()}],"border-w-t":[{"border-t":eu()}],"border-w-r":[{"border-r":eu()}],"border-w-b":[{"border-b":eu()}],"border-w-l":[{"border-l":eu()}],"divide-x":[{"divide-x":eu()}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{"divide-y":eu()}],"divide-y-reverse":["divide-y-reverse"],"border-style":[{border:[...es(),"hidden","none"]}],"divide-style":[{divide:[...es(),"hidden","none"]}],"border-color":[{border:er()}],"border-color-x":[{"border-x":er()}],"border-color-y":[{"border-y":er()}],"border-color-s":[{"border-s":er()}],"border-color-e":[{"border-e":er()}],"border-color-t":[{"border-t":er()}],"border-color-r":[{"border-r":er()}],"border-color-b":[{"border-b":er()}],"border-color-l":[{"border-l":er()}],"divide-color":[{divide:er()}],"outline-style":[{outline:[...es(),"none","hidden"]}],"outline-offset":[{"outline-offset":[x,L,T]}],"outline-w":[{outline:["",x,F,R]}],"outline-color":[{outline:er()}],shadow:[{shadow:["","none",c,H,N]}],"shadow-color":[{shadow:er()}],"inset-shadow":[{"inset-shadow":["none",d,H,N]}],"inset-shadow-color":[{"inset-shadow":er()}],"ring-w":[{ring:eu()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:er()}],"ring-offset-w":[{"ring-offset":[x,R]}],"ring-offset-color":[{"ring-offset":er()}],"inset-ring-w":[{"inset-ring":eu()}],"inset-ring-color":[{"inset-ring":er()}],"text-shadow":[{"text-shadow":["none",p,H,N]}],"text-shadow-color":[{"text-shadow":er()}],opacity:[{opacity:[x,L,T]}],"mix-blend":[{"mix-blend":[...ec(),"plus-darker","plus-lighter"]}],"bg-blend":[{"bg-blend":ec()}],"mask-clip":[{"mask-clip":["border","padding","content","fill","stroke","view"]},"mask-no-clip"],"mask-composite":[{mask:["add","subtract","intersect","exclude"]}],"mask-image-linear-pos":[{"mask-linear":[x]}],"mask-image-linear-from-pos":[{"mask-linear-from":ef()}],"mask-image-linear-to-pos":[{"mask-linear-to":ef()}],"mask-image-linear-from-color":[{"mask-linear-from":er()}],"mask-image-linear-to-color":[{"mask-linear-to":er()}],"mask-image-t-from-pos":[{"mask-t-from":ef()}],"mask-image-t-to-pos":[{"mask-t-to":ef()}],"mask-image-t-from-color":[{"mask-t-from":er()}],"mask-image-t-to-color":[{"mask-t-to":er()}],"mask-image-r-from-pos":[{"mask-r-from":ef()}],"mask-image-r-to-pos":[{"mask-r-to":ef()}],"mask-image-r-from-color":[{"mask-r-from":er()}],"mask-image-r-to-color":[{"mask-r-to":er()}],"mask-image-b-from-pos":[{"mask-b-from":ef()}],"mask-image-b-to-pos":[{"mask-b-to":ef()}],"mask-image-b-from-color":[{"mask-b-from":er()}],"mask-image-b-to-color":[{"mask-b-to":er()}],"mask-image-l-from-pos":[{"mask-l-from":ef()}],"mask-image-l-to-pos":[{"mask-l-to":ef()}],"mask-image-l-from-color":[{"mask-l-from":er()}],"mask-image-l-to-color":[{"mask-l-to":er()}],"mask-image-x-from-pos":[{"mask-x-from":ef()}],"mask-image-x-to-pos":[{"mask-x-to":ef()}],"mask-image-x-from-color":[{"mask-x-from":er()}],"mask-image-x-to-color":[{"mask-x-to":er()}],"mask-image-y-from-pos":[{"mask-y-from":ef()}],"mask-image-y-to-pos":[{"mask-y-to":ef()}],"mask-image-y-from-color":[{"mask-y-from":er()}],"mask-image-y-to-color":[{"mask-y-to":er()}],"mask-image-radial":[{"mask-radial":[L,T]}],"mask-image-radial-from-pos":[{"mask-radial-from":ef()}],"mask-image-radial-to-pos":[{"mask-radial-to":ef()}],"mask-image-radial-from-color":[{"mask-radial-from":er()}],"mask-image-radial-to-color":[{"mask-radial-to":er()}],"mask-image-radial-shape":[{"mask-radial":["circle","ellipse"]}],"mask-image-radial-size":[{"mask-radial":[{closest:["side","corner"],farthest:["side","corner"]}]}],"mask-image-radial-pos":[{"mask-radial-at":k()}],"mask-image-conic-pos":[{"mask-conic":[x]}],"mask-image-conic-from-pos":[{"mask-conic-from":ef()}],"mask-image-conic-to-pos":[{"mask-conic-to":ef()}],"mask-image-conic-from-color":[{"mask-conic-from":er()}],"mask-image-conic-to-color":[{"mask-conic-to":er()}],"mask-mode":[{mask:["alpha","luminance","match"]}],"mask-origin":[{"mask-origin":["border","padding","content","fill","stroke","view"]}],"mask-position":[{mask:en()}],"mask-repeat":[{mask:ei()}],"mask-size":[{mask:eo()}],"mask-type":[{"mask-type":["alpha","luminance"]}],"mask-image":[{mask:["none",L,T]}],filter:[{filter:["","none",L,T]}],blur:[{blur:ed()}],brightness:[{brightness:[x,L,T]}],contrast:[{contrast:[x,L,T]}],"drop-shadow":[{"drop-shadow":["","none",h,H,N]}],"drop-shadow-color":[{"drop-shadow":er()}],grayscale:[{grayscale:["",x,L,T]}],"hue-rotate":[{"hue-rotate":[x,L,T]}],invert:[{invert:["",x,L,T]}],saturate:[{saturate:[x,L,T]}],sepia:[{sepia:["",x,L,T]}],"backdrop-filter":[{"backdrop-filter":["","none",L,T]}],"backdrop-blur":[{"backdrop-blur":ed()}],"backdrop-brightness":[{"backdrop-brightness":[x,L,T]}],"backdrop-contrast":[{"backdrop-contrast":[x,L,T]}],"backdrop-grayscale":[{"backdrop-grayscale":["",x,L,T]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[x,L,T]}],"backdrop-invert":[{"backdrop-invert":["",x,L,T]}],"backdrop-opacity":[{"backdrop-opacity":[x,L,T]}],"backdrop-saturate":[{"backdrop-saturate":[x,L,T]}],"backdrop-sepia":[{"backdrop-sepia":["",x,L,T]}],"border-collapse":[{border:["collapse","separate"]}],"border-spacing":[{"border-spacing":Z()}],"border-spacing-x":[{"border-spacing-x":Z()}],"border-spacing-y":[{"border-spacing-y":Z()}],"table-layout":[{table:["auto","fixed"]}],caption:[{caption:["top","bottom"]}],transition:[{transition:["","all","colors","opacity","shadow","transform","none",L,T]}],"transition-behavior":[{transition:["normal","discrete"]}],duration:[{duration:[x,"initial",L,T]}],ease:[{ease:["linear","initial",y,L,T]}],delay:[{delay:[x,L,T]}],animate:[{animate:["none",b,L,T]}],backface:[{backface:["hidden","visible"]}],perspective:[{perspective:[v,L,T]}],"perspective-origin":[{"perspective-origin":_()}],rotate:[{rotate:ep()}],"rotate-x":[{"rotate-x":ep()}],"rotate-y":[{"rotate-y":ep()}],"rotate-z":[{"rotate-z":ep()}],scale:[{scale:eh()}],"scale-x":[{"scale-x":eh()}],"scale-y":[{"scale-y":eh()}],"scale-z":[{"scale-z":eh()}],"scale-3d":["scale-3d"],skew:[{skew:eg()}],"skew-x":[{"skew-x":eg()}],"skew-y":[{"skew-y":eg()}],transform:[{transform:[L,T,"","none","gpu","cpu"]}],"transform-origin":[{origin:_()}],"transform-style":[{transform:["3d","flat"]}],translate:[{translate:ev()}],"translate-x":[{"translate-x":ev()}],"translate-y":[{"translate-y":ev()}],"translate-z":[{"translate-z":ev()}],"translate-none":["translate-none"],accent:[{accent:er()}],appearance:[{appearance:["none","auto"]}],"caret-color":[{caret:er()}],"color-scheme":[{scheme:["normal","dark","light","light-dark","only-dark","only-light"]}],cursor:[{cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",L,T]}],"field-sizing":[{"field-sizing":["fixed","content"]}],"pointer-events":[{"pointer-events":["auto","none"]}],resize:[{resize:["none","","y","x"]}],"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":Z()}],"scroll-mx":[{"scroll-mx":Z()}],"scroll-my":[{"scroll-my":Z()}],"scroll-ms":[{"scroll-ms":Z()}],"scroll-me":[{"scroll-me":Z()}],"scroll-mt":[{"scroll-mt":Z()}],"scroll-mr":[{"scroll-mr":Z()}],"scroll-mb":[{"scroll-mb":Z()}],"scroll-ml":[{"scroll-ml":Z()}],"scroll-p":[{"scroll-p":Z()}],"scroll-px":[{"scroll-px":Z()}],"scroll-py":[{"scroll-py":Z()}],"scroll-ps":[{"scroll-ps":Z()}],"scroll-pe":[{"scroll-pe":Z()}],"scroll-pt":[{"scroll-pt":Z()}],"scroll-pr":[{"scroll-pr":Z()}],"scroll-pb":[{"scroll-pb":Z()}],"scroll-pl":[{"scroll-pl":Z()}],"snap-align":[{snap:["start","end","center","align-none"]}],"snap-stop":[{snap:["normal","always"]}],"snap-type":[{snap:["none","x","y","both"]}],"snap-strictness":[{snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}],"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{select:["none","text","all","auto"]}],"will-change":[{"will-change":["auto","scroll","contents","transform",L,T]}],fill:[{fill:["none",...er()]}],"stroke-w":[{stroke:[x,F,R,z]}],stroke:[{stroke:["none",...er()]}],"forced-color-adjust":[{"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"],inset:["inset-x","inset-y","start","end","top","right","bottom","left"],"inset-x":["right","left"],"inset-y":["top","bottom"],flex:["basis","grow","shrink"],gap:["gap-x","gap-y"],p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"],m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"],size:["w","h"],"font-size":["leading"],"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"],"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"],"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"],"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"],rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"],"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"],"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"],"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"],"border-spacing":["border-spacing-x","border-spacing-y"],"border-w":["border-w-x","border-w-y","border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"],"border-w-x":["border-w-r","border-w-l"],"border-w-y":["border-w-t","border-w-b"],"border-color":["border-color-x","border-color-y","border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"],"border-color-x":["border-color-r","border-color-l"],"border-color-y":["border-color-t","border-color-b"],translate:["translate-x","translate-y","translate-none"],"translate-none":["translate","translate-x","translate-y","translate-z"],"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"],"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"],"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"],"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"],touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"],"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]},orderSensitiveModifiers:["*","**","after","backdrop","before","details-content","file","first-letter","first-line","marker","placeholder","selection"]}})},9708:(e,t,r)=>{"use strict";r.d(t,{DX:()=>l,TL:()=>a});var n=r(2115),i=r(6101),o=r(5155);function a(e){let t=function(e){let t=n.forwardRef((e,t)=>{let{children:r,...o}=e;if(n.isValidElement(r)){var a;let e,l,u=(a=r,(l=(e=Object.getOwnPropertyDescriptor(a.props,"ref")?.get)&&"isReactWarning"in e&&e.isReactWarning)?a.ref:(l=(e=Object.getOwnPropertyDescriptor(a,"ref")?.get)&&"isReactWarning"in e&&e.isReactWarning)?a.props.ref:a.props.ref||a.ref),s=function(e,t){let r={...t};for(let n in t){let i=e[n],o=t[n];/^on[A-Z]/.test(n)?i&&o?r[n]=(...e)=>{let t=o(...e);return i(...e),t}:i&&(r[n]=i):"style"===n?r[n]={...i,...o}:"className"===n&&(r[n]=[i,o].filter(Boolean).join(" "))}return{...e,...r}}(o,r.props);return r.type!==n.Fragment&&(s.ref=t?(0,i.t)(t,u):u),n.cloneElement(r,s)}return n.Children.count(r)>1?n.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}(e),r=n.forwardRef((e,r)=>{let{children:i,...a}=e,l=n.Children.toArray(i),u=l.find(s);if(u){let e=u.props.children,i=l.map(t=>t!==u?t:n.Children.count(e)>1?n.Children.only(null):n.isValidElement(e)?e.props.children:null);return(0,o.jsx)(t,{...a,ref:r,children:n.isValidElement(e)?n.cloneElement(e,void 0,i):null})}return(0,o.jsx)(t,{...a,ref:r,children:i})});return r.displayName=`${e}.Slot`,r}var l=a("Slot"),u=Symbol("radix.slottable");function s(e){return n.isValidElement(e)&&"function"==typeof e.type&&"__radixId"in e.type&&e.type.__radixId===u}},9738:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(4117),i=r(2721);t.cloneDeepWith=function(e,t){return n.cloneDeepWith(e,(r,o,a,l)=>{let u=t?.(r,o,a,l);if(null!=u)return u;if("object"==typeof e)switch(Object.prototype.toString.call(e)){case i.numberTag:case i.stringTag:case i.booleanTag:{let t=new e.constructor(e?.valueOf());return n.copyProperties(t,e),t}case i.argumentsTag:{let t={};return n.copyProperties(t,e),t.length=e.length,t[Symbol.iterator]=e[Symbol.iterator],t}default:return}})}},9819:(e,t,r)=>{"use strict";function n(e){return"object"==typeof e&&"length"in e?e:Array.from(e)}r.d(t,{A:()=>n}),Array.prototype.slice},9827:(e,t,r)=>{"use strict";r.d(t,{qx:()=>A,IH:()=>P,s0:()=>b,gH:()=>y,SW:()=>I,YB:()=>O,bk:()=>D,Hj:()=>j,nb:()=>k,PW:()=>x,Mk:()=>_,$8:()=>M,yy:()=>C,Rh:()=>S,GF:()=>T,uM:()=>R,kr:()=>m,r4:()=>z,_L:()=>w});var n=r(241),i=r.n(n),o=r(5672),a=r.n(o);function l(e,t){if((i=e.length)>1)for(var r,n,i,o=1,a=e[t[0]],l=a.length;o=0;)r[t]=t;return r}function f(e,t){return e[t]}function d(e){let t=[];return t.key=e,t}var p=r(6377),h=r(5641);function g(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function v(e){for(var t=1;t{var o,a=-1,l=null!=(o=null==t?void 0:t.length)?o:0;if(l<=1||null==e)return 0;if("angleAxis"===n&&null!=i&&1e-6>=Math.abs(Math.abs(i[1]-i[0])-360))for(var u=0;u0?r[u-1].coordinate:r[l-1].coordinate,c=r[u].coordinate,f=u>=l-1?r[0].coordinate:r[u+1].coordinate,d=void 0;if((0,p.sA)(c-s)!==(0,p.sA)(f-c)){var h=[];if((0,p.sA)(f-c)===(0,p.sA)(i[1]-i[0])){d=f;var g=c+i[1]-i[0];h[0]=Math.min(g,(g+s)/2),h[1]=Math.max(g,(g+s)/2)}else{d=s;var v=f+i[1]-i[0];h[0]=Math.min(c,(v+c)/2),h[1]=Math.max(c,(v+c)/2)}var m=[Math.min(c,(d+c)/2),Math.max(c,(d+c)/2)];if(e>m[0]&&e<=m[1]||e>=h[0]&&e<=h[1]){({index:a}=r[u]);break}}else{var y=Math.min(s,f),b=Math.max(s,f);if(e>(y+c)/2&&e<=(b+c)/2){({index:a}=r[u]);break}}}else if(t){for(var w=0;w0&&w(t[w].coordinate+t[w-1].coordinate)/2&&e<=(t[w].coordinate+t[w+1].coordinate)/2||w===l-1&&e>(t[w].coordinate+t[w-1].coordinate)/2){({index:a}=t[w]);break}}return a},b=(e,t,r)=>{if(t&&r){var{width:n,height:i}=r,{align:o,verticalAlign:a,layout:l}=t;if(("vertical"===l||"horizontal"===l&&"middle"===a)&&"center"!==o&&(0,p.Et)(e[o]))return v(v({},e),{},{[o]:e[o]+(n||0)});if(("horizontal"===l||"vertical"===l&&"center"===o)&&"middle"!==a&&(0,p.Et)(e[a]))return v(v({},e),{},{[a]:e[a]+(i||0)})}return e},w=(e,t)=>"horizontal"===e&&"xAxis"===t||"vertical"===e&&"yAxis"===t||"centric"===e&&"angleAxis"===t||"radial"===e&&"radiusAxis"===t,x=(e,t,r,n)=>{if(n)return e.map(e=>e.coordinate);var i,o,a=e.map(e=>(e.coordinate===t&&(i=!0),e.coordinate===r&&(o=!0),e.coordinate));return i||a.push(t),o||a.push(r),a},S=(e,t,r)=>{if(!e)return null;var{duplicateDomain:n,type:i,range:o,scale:a,realScaleType:l,isCategorical:u,categoricalDomain:s,tickCount:c,ticks:f,niceTicks:d,axisType:h}=e;if(!a)return null;var g="scaleBand"===l&&a.bandwidth?a.bandwidth()/2:2,v=(t||r)&&"category"===i&&a.bandwidth?a.bandwidth()/g:0;return(v="angleAxis"===h&&o&&o.length>=2?2*(0,p.sA)(o[0]-o[1])*v:v,t&&(f||d))?(f||d||[]).map((e,t)=>({coordinate:a(n?n.indexOf(e):e)+v,value:e,offset:v,index:t})).filter(e=>!(0,p.M8)(e.coordinate)):u&&s?s.map((e,t)=>({coordinate:a(e)+v,value:e,index:t,offset:v})):a.ticks&&!r&&null!=c?a.ticks(c).map((e,t)=>({coordinate:a(e)+v,value:e,offset:v,index:t})):a.domain().map((e,t)=>({coordinate:a(e)+v,value:n?n[e]:e,index:t,offset:v}))},O=e=>{var t=e.domain();if(t&&!(t.length<=2)){var r=t.length,n=e.range(),i=Math.min(n[0],n[1])-1e-4,o=Math.max(n[0],n[1])+1e-4,a=e(t[0]),l=e(t[r-1]);(ao||lo)&&e.domain([t[0],t[r-1]])}},E={sign:e=>{var t=e.length;if(!(t<=0))for(var r=0,n=e[0].length;r=0?(e[a][r][0]=i,e[a][r][1]=i+l,i=e[a][r][1]):(e[a][r][0]=o,e[a][r][1]=o+l,o=e[a][r][1])}},expand:function(e,t){if((n=e.length)>0){for(var r,n,i,o=0,a=e[0].length;o0){for(var r,n=0,i=e[t[0]],o=i.length;n0&&(n=(r=e[t[0]]).length)>0){for(var r,n,i,o=0,a=1;a{var t=e.length;if(!(t<=0))for(var r=0,n=e[0].length;r=0?(e[o][r][0]=i,e[o][r][1]=i+a,i=e[o][r][1]):(e[o][r][0]=0,e[o][r][1]=0)}}},C=(e,t,r)=>{var n=E[r];return(function(){var e=(0,s.A)([]),t=c,r=l,n=f;function i(i){var o,a,l=Array.from(e.apply(this,arguments),d),s=l.length,c=-1;for(let e of i)for(o=0,++c;o+m(e,t,0)).order(c).offset(n)(e)};function M(e){return null==e?void 0:String(e)}function k(e){var{axis:t,ticks:r,bandSize:n,entry:i,index:o,dataKey:a}=e;if("category"===t.type){if(!t.allowDuplicatedCategory&&t.dataKey&&!(0,p.uy)(i[t.dataKey])){var l=(0,p.eP)(r,"value",i[t.dataKey]);if(l)return l.coordinate+n/2}return r[o]?r[o].coordinate+n/2:null}var u=m(i,(0,p.uy)(a)?t.dataKey:a);return(0,p.uy)(u)?null:t.scale(u)}var _=(e,t,r)=>{if(null!=e)return(e=>[e[0]===1/0?0:e[0],e[1]===-1/0?0:e[1]])(Object.keys(e).reduce((n,i)=>{var{stackedData:o}=e[i],a=o.reduce((e,n)=>{var i=(e=>{var t=e.flat(2).filter(p.Et);return[Math.min(...t),Math.max(...t)]})(n.slice(t,r+1));return[Math.min(e[0],i[0]),Math.max(e[1],i[1])]},[1/0,-1/0]);return[Math.min(a[0],n[0]),Math.max(a[1],n[1])]},[1/0,-1/0]))},P=/^dataMin[\s]*-[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,A=/^dataMax[\s]*\+[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,j=(e,t,r)=>{if(e&&e.scale&&e.scale.bandwidth){var n=e.scale.bandwidth();if(!r||n>0)return n}if(e&&t&&t.length>=2){for(var o=i()(t,e=>e.coordinate),a=1/0,l=1,u=o.length;l=i.left&&e<=i.left+i.width&&t>=i.top&&t<=i.top+i.height?{x:e,y:t}:null:n?(0,h.yy)({x:e,y:t},n):null}var D=(e,t,r,n)=>{var i=t.find(e=>e&&e.index===r);if(i){if("horizontal"===e)return{x:i.coordinate,y:n.y};if("vertical"===e)return{x:n.x,y:i.coordinate};if("centric"===e){var o=i.coordinate,{radius:a}=n;return v(v(v({},n),(0,h.IZ)(n.cx,n.cy,a,o)),{},{angle:o,radius:a})}var l=i.coordinate,{angle:u}=n;return v(v(v({},n),(0,h.IZ)(n.cx,n.cy,l,u)),{},{angle:u,radius:l})}return{x:0,y:0}},I=(e,t)=>"horizontal"===t?e.x:"vertical"===t?e.y:"centric"===t?e.angle:e.radius},9901:(e,t,r)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});let n=r(4373),i=r(4664);t.range=function(e,t,r){r&&"number"!=typeof r&&n.isIterateeCall(e,t,r)&&(t=r=void 0),e=i.toFinite(e),void 0===t?(t=e,e=0):t=i.toFinite(t),r=void 0===r?e{"use strict";r.d(t,{A:()=>u});var n=r(2115);let i=e=>{let t=e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,r)=>r?r.toUpperCase():t.toLowerCase());return t.charAt(0).toUpperCase()+t.slice(1)},o=function(){for(var e=arguments.length,t=Array(e),r=0;r!!e&&""!==e.trim()&&r.indexOf(e)===t).join(" ").trim()};var a={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};let l=(0,n.forwardRef)((e,t)=>{let{color:r="currentColor",size:i=24,strokeWidth:l=2,absoluteStrokeWidth:u,className:s="",children:c,iconNode:f,...d}=e;return(0,n.createElement)("svg",{ref:t,...a,width:i,height:i,stroke:r,strokeWidth:u?24*Number(l)/Number(i):l,className:o("lucide",s),...!c&&!(e=>{for(let t in e)if(t.startsWith("aria-")||"role"===t||"title"===t)return!0})(d)&&{"aria-hidden":"true"},...d},[...f.map(e=>{let[t,r]=e;return(0,n.createElement)(t,r)}),...Array.isArray(c)?c:[c]])}),u=(e,t)=>{let r=(0,n.forwardRef)((r,a)=>{let{className:u,...s}=r;return(0,n.createElement)(l,{ref:a,iconNode:t,className:o("lucide-".concat(i(e).replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()),"lucide-".concat(e),u),...s})});return r.displayName=i(e),r}}}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/964-69097a61540f27b4.js b/keploy/pkg/service/load/out/_next/static/chunks/964-69097a61540f27b4.js deleted file mode 100644 index 58ae106..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/964-69097a61540f27b4.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[964],{89:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"useRouterBFCache",{enumerable:!0,get:function(){return u}});let n=r(2115);function u(e,t){let[r,u]=(0,n.useState)(()=>({tree:e,stateKey:t,next:null}));if(r.tree===e)return r;let l={tree:e,stateKey:t,next:null},o=1,a=r,i=l;for(;null!==a&&o<1;){if(a.stateKey===t){i.next=a.next;break}{o++;let e={tree:a.tree,stateKey:a.stateKey,next:null};i.next=e,i=e}a=a.next}return u(l),l}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},214:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizePathTrailingSlash",{enumerable:!0,get:function(){return l}});let n=r(6361),u=r(427),l=e=>{if(!e.startsWith("/"))return e;let{pathname:t,query:r,hash:l}=(0,u.parsePath)(e);return/\.[^/]+\/?$/.test(t)?""+(0,n.removeTrailingSlash)(t)+r+l:t.endsWith("/")?""+t+r+l:t+"/"+r+l};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},427:(e,t)=>{"use strict";function r(e){let t=e.indexOf("#"),r=e.indexOf("?"),n=r>-1&&(t<0||r-1?{pathname:e.substring(0,n?r:t),query:n?e.substring(r,t>-1?t:void 0):"",hash:t>-1?e.slice(t):""}:{pathname:e,query:"",hash:""}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"parsePath",{enumerable:!0,get:function(){return r}})},589:(e,t)=>{"use strict";function r(e){return e.split("/").map(e=>encodeURIComponent(e)).join("/")}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"encodeURIPath",{enumerable:!0,get:function(){return r}})},666:e=>{!function(){var t={229:function(e){var t,r,n,u=e.exports={};function l(){throw Error("setTimeout has not been defined")}function o(){throw Error("clearTimeout has not been defined")}try{t="function"==typeof setTimeout?setTimeout:l}catch(e){t=l}try{r="function"==typeof clearTimeout?clearTimeout:o}catch(e){r=o}function a(e){if(t===setTimeout)return setTimeout(e,0);if((t===l||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(r){try{return t.call(null,e,0)}catch(r){return t.call(this,e,0)}}}var i=[],c=!1,s=-1;function f(){c&&n&&(c=!1,n.length?i=n.concat(i):s=-1,i.length&&d())}function d(){if(!c){var e=a(f);c=!0;for(var t=i.length;t;){for(n=i,i=[];++s1)for(var r=1;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{RedirectBoundary:function(){return f},RedirectErrorBoundary:function(){return s}});let n=r(6966),u=r(5155),l=n._(r(2115)),o=r(8999),a=r(6825),i=r(2210);function c(e){let{redirect:t,reset:r,redirectType:n}=e,u=(0,o.useRouter)();return(0,l.useEffect)(()=>{l.default.startTransition(()=>{n===i.RedirectType.push?u.push(t,{}):u.replace(t,{}),r()})},[t,n,r,u]),null}class s extends l.default.Component{static getDerivedStateFromError(e){if((0,i.isRedirectError)(e))return{redirect:(0,a.getURLFromRedirectError)(e),redirectType:(0,a.getRedirectTypeFromError)(e)};throw e}render(){let{redirect:e,redirectType:t}=this.state;return null!==e&&null!==t?(0,u.jsx)(c,{redirect:e,redirectType:t,reset:()=>this.setState({redirect:null})}):this.props.children}constructor(e){super(e),this.state={redirect:null,redirectType:null}}}function f(e){let{children:t}=e,r=(0,o.useRouter)();return(0,u.jsx)(s,{router:r,children:t})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},708:(e,t)=>{"use strict";function r(e){return Array.isArray(e)?e[1]:e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getSegmentValue",{enumerable:!0,get:function(){return r}}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},774:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{HTML_LIMITED_BOT_UA_RE:function(){return n.HTML_LIMITED_BOT_UA_RE},HTML_LIMITED_BOT_UA_RE_STRING:function(){return l},getBotType:function(){return i},isBot:function(){return a}});let n=r(5072),u=/google/i,l=n.HTML_LIMITED_BOT_UA_RE.source;function o(e){return n.HTML_LIMITED_BOT_UA_RE.test(e)}function a(e){return u.test(e)||o(e)}function i(e){return u.test(e)?"dom":o(e)?"html":void 0}},878:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"applyFlightData",{enumerable:!0,get:function(){return l}});let n=r(4758),u=r(3118);function l(e,t,r,l,o){let{tree:a,seedData:i,head:c,isRootRender:s}=l;if(null===i)return!1;if(s){let u=i[1];r.loading=i[3],r.rsc=u,r.prefetchRsc=null,(0,n.fillLazyItemsTillLeafWithHead)(e,r,t,a,i,c,o)}else r.rsc=t.rsc,r.prefetchRsc=t.prefetchRsc,r.parallelRoutes=new Map(t.parallelRoutes),r.loading=t.loading,(0,u.fillCacheWithNewSubTreeData)(e,r,t,l,o);return!0}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},886:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{PathParamsContext:function(){return o},PathnameContext:function(){return l},SearchParamsContext:function(){return u}});let n=r(2115),u=(0,n.createContext)(null),l=(0,n.createContext)(null),o=(0,n.createContext)(null)},894:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ClientPageRoot",{enumerable:!0,get:function(){return u}});let n=r(5155);function u(e){let{Component:t,searchParams:u,params:l,promises:o}=e;{let{createRenderSearchParamsFromClient:e}=r(7205),o=e(u),{createRenderParamsFromClient:a}=r(3558),i=a(l);return(0,n.jsx)(t,{params:i,searchParams:o})}}r(9837),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1027:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{dispatchAppRouterAction:function(){return o},useActionQueue:function(){return a}});let n=r(6966)._(r(2115)),u=r(5122),l=null;function o(e){if(null===l)throw Object.defineProperty(Error("Internal Next.js error: Router action dispatched before initialization."),"__NEXT_ERROR_CODE",{value:"E668",enumerable:!1,configurable:!0});l(e)}function a(e){let[t,r]=n.default.useState(e.state);return l=t=>e.dispatch(t,r),(0,u.isThenable)(t)?(0,n.use)(t):t}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1127:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"matchSegment",{enumerable:!0,get:function(){return r}});let r=(e,t)=>"string"==typeof e?"string"==typeof t&&e===t:"string"!=typeof t&&e[0]===t[0]&&e[1]===t[1];("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1139:(e,t)=>{"use strict";function r(e,t){return void 0===t&&(t=!0),e.pathname+e.search+(t?e.hash:"")}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createHrefFromUrl",{enumerable:!0,get:function(){return r}}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1295:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return a}});let n=r(6966),u=r(5155),l=n._(r(2115)),o=r(5227);function a(){let e=(0,l.useContext)(o.TemplateContext);return(0,u.jsx)(u.Fragment,{children:e})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1315:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"assignLocation",{enumerable:!0,get:function(){return u}});let n=r(5929);function u(e,t){if(e.startsWith(".")){let r=t.origin+t.pathname;return new URL((r.endsWith("/")?r:r+"/")+e)}return new URL((0,n.addBasePath)(e),t.href)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1408:(e,t,r)=>{"use strict";e.exports=r(9393)},1426:(e,t,r)=>{"use strict";var n=r(9509),u=Symbol.for("react.transitional.element"),l=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),c=Symbol.for("react.consumer"),s=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),d=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),h=Symbol.for("react.lazy"),_=Symbol.iterator,y={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},b=Object.assign,v={};function g(e,t,r){this.props=e,this.context=t,this.refs=v,this.updater=r||y}function m(){}function R(e,t,r){this.props=e,this.context=t,this.refs=v,this.updater=r||y}g.prototype.isReactComponent={},g.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},g.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},m.prototype=g.prototype;var E=R.prototype=new m;E.constructor=R,b(E,g.prototype),E.isPureReactComponent=!0;var O=Array.isArray;function P(){}var j={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function S(e,t,r,n,l,o){return{$$typeof:u,type:e,key:t,ref:void 0!==(r=o.ref)?r:null,props:o}}function M(e){return"object"==typeof e&&null!==e&&e.$$typeof===u}var w=/\/+/g;function x(e,t){var r,n;return"object"==typeof e&&null!==e&&null!=e.key?(r=""+e.key,n={"=":"=0",":":"=2"},"$"+r.replace(/[=:]/g,function(e){return n[e]})):t.toString(36)}function C(e,t,r){if(null==e)return e;var n=[],o=0;return!function e(t,r,n,o,a){var i,c,s,f=typeof t;("undefined"===f||"boolean"===f)&&(t=null);var d=!1;if(null===t)d=!0;else switch(f){case"bigint":case"string":case"number":d=!0;break;case"object":switch(t.$$typeof){case u:case l:d=!0;break;case h:return e((d=t._init)(t._payload),r,n,o,a)}}if(d)return a=a(t),d=""===o?"."+x(t,0):o,O(a)?(n="",null!=d&&(n=d.replace(w,"$&/")+"/"),e(a,r,n,"",function(e){return e})):null!=a&&(M(a)&&(i=a,c=n+(null==a.key||t&&t.key===a.key?"":(""+a.key).replace(w,"$&/")+"/")+d,a=S(i.type,c,void 0,void 0,void 0,i.props)),r.push(a)),1;d=0;var p=""===o?".":o+":";if(O(t))for(var y=0;y{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{DYNAMIC_STALETIME_MS:function(){return d},STATIC_STALETIME_MS:function(){return p},createSeededPrefetchCacheEntry:function(){return c},getOrCreatePrefetchCacheEntry:function(){return i},prunePrefetchCache:function(){return f}});let n=r(8586),u=r(9818),l=r(9154);function o(e,t,r){let n=e.pathname;return(t&&(n+=e.search),r)?""+r+"%"+n:n}function a(e,t,r){return o(e,t===u.PrefetchKind.FULL,r)}function i(e){let{url:t,nextUrl:r,tree:n,prefetchCache:l,kind:a,allowAliasing:i=!0}=e,c=function(e,t,r,n,l){for(let a of(void 0===t&&(t=u.PrefetchKind.TEMPORARY),[r,null])){let r=o(e,!0,a),i=o(e,!1,a),c=e.search?r:i,s=n.get(c);if(s&&l){if(s.url.pathname===e.pathname&&s.url.search!==e.search)return{...s,aliased:!0};return s}let f=n.get(i);if(l&&e.search&&t!==u.PrefetchKind.FULL&&f&&!f.key.includes("%"))return{...f,aliased:!0}}if(t!==u.PrefetchKind.FULL&&l){for(let t of n.values())if(t.url.pathname===e.pathname&&!t.key.includes("%"))return{...t,aliased:!0}}}(t,a,r,l,i);return c?(c.status=h(c),c.kind!==u.PrefetchKind.FULL&&a===u.PrefetchKind.FULL&&c.data.then(e=>{if(!(Array.isArray(e.flightData)&&e.flightData.some(e=>e.isRootRender&&null!==e.seedData)))return s({tree:n,url:t,nextUrl:r,prefetchCache:l,kind:null!=a?a:u.PrefetchKind.TEMPORARY})}),a&&c.kind===u.PrefetchKind.TEMPORARY&&(c.kind=a),c):s({tree:n,url:t,nextUrl:r,prefetchCache:l,kind:a||u.PrefetchKind.TEMPORARY})}function c(e){let{nextUrl:t,tree:r,prefetchCache:n,url:l,data:o,kind:i}=e,c=o.couldBeIntercepted?a(l,i,t):a(l,i),s={treeAtTimeOfPrefetch:r,data:Promise.resolve(o),kind:i,prefetchTime:Date.now(),lastUsedTime:Date.now(),staleTime:o.staleTime,key:c,status:u.PrefetchCacheEntryStatus.fresh,url:l};return n.set(c,s),s}function s(e){let{url:t,kind:r,tree:o,nextUrl:i,prefetchCache:c}=e,s=a(t,r),f=l.prefetchQueue.enqueue(()=>(0,n.fetchServerResponse)(t,{flightRouterState:o,nextUrl:i,prefetchKind:r}).then(e=>{let r;if(e.couldBeIntercepted&&(r=function(e){let{url:t,nextUrl:r,prefetchCache:n,existingCacheKey:u}=e,l=n.get(u);if(!l)return;let o=a(t,l.kind,r);return n.set(o,{...l,key:o}),n.delete(u),o}({url:t,existingCacheKey:s,nextUrl:i,prefetchCache:c})),e.prerendered){let t=c.get(null!=r?r:s);t&&(t.kind=u.PrefetchKind.FULL,-1!==e.staleTime&&(t.staleTime=e.staleTime))}return e})),d={treeAtTimeOfPrefetch:o,data:f,kind:r,prefetchTime:Date.now(),lastUsedTime:null,staleTime:-1,key:s,status:u.PrefetchCacheEntryStatus.fresh,url:t};return c.set(s,d),d}function f(e){for(let[t,r]of e)h(r)===u.PrefetchCacheEntryStatus.expired&&e.delete(t)}let d=1e3*Number("0"),p=1e3*Number("300");function h(e){let{kind:t,prefetchTime:r,lastUsedTime:n,staleTime:l}=e;return -1!==l?Date.now(){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"reportGlobalError",{enumerable:!0,get:function(){return r}});let r="function"==typeof reportError?reportError:e=>{globalThis.console.error(e)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1747:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"pathHasPrefix",{enumerable:!0,get:function(){return u}});let n=r(427);function u(e,t){if("string"!=typeof e)return!1;let{pathname:r}=(0,n.parsePath)(e);return r===t||r.startsWith(t+"/")}},1799:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HandleISRError",{enumerable:!0,get:function(){return n}});let r=void 0;function n(e){let{error:t}=e;if(r){let e=r.getStore();if((null==e?void 0:e.isRevalidate)||(null==e?void 0:e.isStaticGeneration))throw console.error(t),t}return null}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1818:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"findSourceMapURL",{enumerable:!0,get:function(){return r}});let r=void 0;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1822:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"unresolvedThenable",{enumerable:!0,get:function(){return r}});let r={then:()=>{}};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2004:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"invalidateCacheByRouterState",{enumerable:!0,get:function(){return u}});let n=r(5637);function u(e,t,r){for(let u in r[1]){let l=r[1][u][0],o=(0,n.createRouterCacheKey)(l),a=t.parallelRoutes.get(u);if(a){let t=new Map(a);t.delete(o),e.parallelRoutes.set(u,t)}}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2115:(e,t,r)=>{"use strict";e.exports=r(1426)},2210:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{REDIRECT_ERROR_CODE:function(){return u},RedirectType:function(){return l},isRedirectError:function(){return o}});let n=r(4420),u="NEXT_REDIRECT";var l=function(e){return e.push="push",e.replace="replace",e}({});function o(e){if("object"!=typeof e||null===e||!("digest"in e)||"string"!=typeof e.digest)return!1;let t=e.digest.split(";"),[r,l]=t,o=t.slice(2,-2).join(";"),a=Number(t.at(-2));return r===u&&("replace"===l||"push"===l)&&"string"==typeof o&&!isNaN(a)&&a in n.RedirectStatusCode}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2223:(e,t)=>{"use strict";function r(e,t){var r=e.length;for(e.push(t);0>>1,u=e[n];if(0>>1;nl(i,r))cl(s,i)?(e[n]=s,e[c]=r,n=c):(e[n]=i,e[a]=r,n=a);else if(cl(s,r))e[n]=s,e[c]=r,n=c;else break}}return t}function l(e,t){var r=e.sortIndex-t.sortIndex;return 0!==r?r:e.id-t.id}if(t.unstable_now=void 0,"object"==typeof performance&&"function"==typeof performance.now){var o,a=performance;t.unstable_now=function(){return a.now()}}else{var i=Date,c=i.now();t.unstable_now=function(){return i.now()-c}}var s=[],f=[],d=1,p=null,h=3,_=!1,y=!1,b=!1,v=!1,g="function"==typeof setTimeout?setTimeout:null,m="function"==typeof clearTimeout?clearTimeout:null,R="undefined"!=typeof setImmediate?setImmediate:null;function E(e){for(var t=n(f);null!==t;){if(null===t.callback)u(f);else if(t.startTime<=e)u(f),t.sortIndex=t.expirationTime,r(s,t);else break;t=n(f)}}function O(e){if(b=!1,E(e),!y)if(null!==n(s))y=!0,P||(P=!0,o());else{var t=n(f);null!==t&&A(O,t.startTime-e)}}var P=!1,j=-1,T=5,S=-1;function M(){return!!v||!(t.unstable_now()-Se&&M());){var a=p.callback;if("function"==typeof a){p.callback=null,h=p.priorityLevel;var i=a(p.expirationTime<=e);if(e=t.unstable_now(),"function"==typeof i){p.callback=i,E(e),r=!0;break t}p===n(s)&&u(s),E(e)}else u(s);p=n(s)}if(null!==p)r=!0;else{var c=n(f);null!==c&&A(O,c.startTime-e),r=!1}}break e}finally{p=null,h=l,_=!1}}}finally{r?o():P=!1}}}if("function"==typeof R)o=function(){R(w)};else if("undefined"!=typeof MessageChannel){var x=new MessageChannel,C=x.port2;x.port1.onmessage=w,o=function(){C.postMessage(null)}}else o=function(){g(w,0)};function A(e,r){j=g(function(){e(t.unstable_now())},r)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_forceFrameRate=function(e){0>e||125a?(e.sortIndex=l,r(f,e),null===n(s)&&e===n(f)&&(b?(m(j),j=-1):b=!0,A(O,l-a))):(e.sortIndex=i,r(s,e),y||_||(y=!0,P||(P=!0,o()))),e},t.unstable_shouldYield=M,t.unstable_wrapCallback=function(e){var t=h;return function(){var r=h;h=t;try{return e.apply(this,arguments)}finally{h=r}}}},2312:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"PromiseQueue",{enumerable:!0,get:function(){return c}});let n=r(5952),u=r(6420);var l=u._("_maxConcurrency"),o=u._("_runningCount"),a=u._("_queue"),i=u._("_processNext");class c{enqueue(e){let t,r,u=new Promise((e,n)=>{t=e,r=n}),l=async()=>{try{n._(this,o)[o]++;let r=await e();t(r)}catch(e){r(e)}finally{n._(this,o)[o]--,n._(this,i)[i]()}};return n._(this,a)[a].push({promiseFn:u,task:l}),n._(this,i)[i](),u}bump(e){let t=n._(this,a)[a].findIndex(t=>t.promiseFn===e);if(t>-1){let e=n._(this,a)[a].splice(t,1)[0];n._(this,a)[a].unshift(e),n._(this,i)[i](!0)}}constructor(e=5){Object.defineProperty(this,i,{value:s}),Object.defineProperty(this,l,{writable:!0,value:void 0}),Object.defineProperty(this,o,{writable:!0,value:void 0}),Object.defineProperty(this,a,{writable:!0,value:void 0}),n._(this,l)[l]=e,n._(this,o)[o]=0,n._(this,a)[a]=[]}}function s(e){if(void 0===e&&(e=!1),(n._(this,o)[o]0){var t;null==(t=n._(this,a)[a].shift())||t.task()}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2561:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getFlightDataPartsFromPath:function(){return u},getNextFlightSegmentPath:function(){return l},normalizeFlightData:function(){return o},prepareFlightRouterStateForRequest:function(){return a}});let n=r(8291);function u(e){var t;let[r,n,u,l]=e.slice(-4),o=e.slice(0,-4);return{pathToSegment:o.slice(0,-1),segmentPath:o,segment:null!=(t=o[o.length-1])?t:"",tree:r,seedData:n,head:u,isHeadPartial:l,isRootRender:4===e.length}}function l(e){return e.slice(2)}function o(e){return"string"==typeof e?e:e.map(u)}function a(e,t){return t?encodeURIComponent(JSON.stringify(e)):encodeURIComponent(JSON.stringify(function e(t){var r,u;let[l,o,a,i,c,s]=t,f="string"==typeof(r=l)&&r.startsWith(n.PAGE_SEGMENT_KEY+"?")?n.PAGE_SEGMENT_KEY:r,d={};for(let[t,r]of Object.entries(o))d[t]=e(r);let p=[f,d,null,(u=i)&&"refresh"!==u?i:null];return void 0!==c&&(p[4]=c),void 0!==s&&(p[5]=s),p}(e)))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2669:(e,t,r)=>{"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=r(9248)},2691:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"findHeadInCache",{enumerable:!0,get:function(){return l}});let n=r(8291),u=r(5637);function l(e,t){return function e(t,r,l){if(0===Object.keys(r).length)return[t,l];let o=Object.keys(r).filter(e=>"children"!==e);for(let a of("children"in r&&o.unshift("children"),o)){let[o,i]=r[a];if(o===n.DEFAULT_SEGMENT_KEY)continue;let c=t.parallelRoutes.get(a);if(!c)continue;let s=(0,u.createRouterCacheKey)(o),f=c.get(s);if(!f)continue;let d=e(f,i,l+"/"+s);if(d)return d}return null}(e,t,"")}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2816:(e,t)=>{"use strict";function r(e){let t=parseInt(e.slice(0,2),16),r=t>>1&63,n=Array(6);for(let e=0;e<6;e++){let t=r>>5-e&1;n[e]=1===t}return{type:1==(t>>7&1)?"use-cache":"server-action",usedArgs:n,hasRestArgs:1==(1&t)}}function n(e,t){let r=Array(e.length);for(let n=0;n=6&&t.hasRestArgs)&&(r[n]=e[n]);return r}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{extractInfoFromServerReferenceId:function(){return r},omitUnusedArgs:function(){return n}})},2830:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HeadManagerContext",{enumerable:!0,get:function(){return n}});let n=r(8229)._(r(2115)).default.createContext({})},2858:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isNextRouterError",{enumerable:!0,get:function(){return l}});let n=r(6494),u=r(2210);function l(e){return(0,u.isRedirectError)(e)||(0,n.isHTTPAccessFallbackError)(e)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3118:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{fillCacheWithNewSubTreeData:function(){return i},fillCacheWithNewSubTreeDataButOnlyLoading:function(){return c}});let n=r(2004),u=r(4758),l=r(5637),o=r(8291);function a(e,t,r,a,i,c){let{segmentPath:s,seedData:f,tree:d,head:p}=a,h=t,_=r;for(let t=0;t{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"warnOnce",{enumerable:!0,get:function(){return r}});let r=e=>{}},3269:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ACTION_HEADER:function(){return n},FLIGHT_HEADERS:function(){return f},NEXT_ACTION_NOT_FOUND_HEADER:function(){return v},NEXT_DID_POSTPONE_HEADER:function(){return h},NEXT_HMR_REFRESH_HASH_COOKIE:function(){return i},NEXT_HMR_REFRESH_HEADER:function(){return a},NEXT_IS_PRERENDER_HEADER:function(){return b},NEXT_REWRITTEN_PATH_HEADER:function(){return _},NEXT_REWRITTEN_QUERY_HEADER:function(){return y},NEXT_ROUTER_PREFETCH_HEADER:function(){return l},NEXT_ROUTER_SEGMENT_PREFETCH_HEADER:function(){return o},NEXT_ROUTER_STALE_TIME_HEADER:function(){return p},NEXT_ROUTER_STATE_TREE_HEADER:function(){return u},NEXT_RSC_UNION_QUERY:function(){return d},NEXT_URL:function(){return c},RSC_CONTENT_TYPE_HEADER:function(){return s},RSC_HEADER:function(){return r}});let r="RSC",n="Next-Action",u="Next-Router-State-Tree",l="Next-Router-Prefetch",o="Next-Router-Segment-Prefetch",a="Next-HMR-Refresh",i="__next_hmr_refresh_hash__",c="Next-Url",s="text/x-component",f=[r,u,l,a,o],d="_rsc",p="x-nextjs-stale-time",h="x-nextjs-postponed",_="x-nextjs-rewritten-path",y="x-nextjs-rewritten-query",b="x-nextjs-prerender",v="x-nextjs-action-not-found";("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3507:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"handleMutable",{enumerable:!0,get:function(){return l}});let n=r(8946);function u(e){return void 0!==e}function l(e,t){var r,l;let o=null==(r=t.shouldScroll)||r,a=e.nextUrl;if(u(t.patchedTree)){let r=(0,n.computeChangedPath)(e.tree,t.patchedTree);r?a=r:a||(a=e.canonicalUrl)}return{canonicalUrl:u(t.canonicalUrl)?t.canonicalUrl===e.canonicalUrl?e.canonicalUrl:t.canonicalUrl:e.canonicalUrl,pushRef:{pendingPush:u(t.pendingPush)?t.pendingPush:e.pushRef.pendingPush,mpaNavigation:u(t.mpaNavigation)?t.mpaNavigation:e.pushRef.mpaNavigation,preserveCustomHistoryState:u(t.preserveCustomHistoryState)?t.preserveCustomHistoryState:e.pushRef.preserveCustomHistoryState},focusAndScrollRef:{apply:!!o&&(!!u(null==t?void 0:t.scrollableSegments)||e.focusAndScrollRef.apply),onlyHashChange:t.onlyHashChange||!1,hashFragment:o?t.hashFragment&&""!==t.hashFragment?decodeURIComponent(t.hashFragment.slice(1)):e.focusAndScrollRef.hashFragment:null,segmentPaths:o?null!=(l=null==t?void 0:t.scrollableSegments)?l:e.focusAndScrollRef.segmentPaths:[]},cache:t.cache?t.cache:e.cache,prefetchCache:t.prefetchCache?t.prefetchCache:e.prefetchCache,tree:u(t.patchedTree)?t.patchedTree:e.tree,nextUrl:a}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3558:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createRenderParamsFromClient",{enumerable:!0,get:function(){return n}});let n=r(7829).createRenderParamsFromClient;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3567:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createInitialRouterState",{enumerable:!0,get:function(){return s}});let n=r(1139),u=r(4758),l=r(8946),o=r(1518),a=r(9818),i=r(4908),c=r(2561);function s(e){var t,r;let{navigatedAt:s,initialFlightData:f,initialCanonicalUrlParts:d,initialParallelRoutes:p,location:h,couldBeIntercepted:_,postponed:y,prerendered:b}=e,v=d.join("/"),g=(0,c.getFlightDataPartsFromPath)(f[0]),{tree:m,seedData:R,head:E}=g,O={lazyData:null,rsc:null==R?void 0:R[1],prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:p,loading:null!=(t=null==R?void 0:R[3])?t:null,navigatedAt:s},P=h?(0,n.createHrefFromUrl)(h):v;(0,i.addRefreshMarkerToActiveParallelSegments)(m,P);let j=new Map;(null===p||0===p.size)&&(0,u.fillLazyItemsTillLeafWithHead)(s,O,void 0,m,R,E,void 0);let T={tree:m,cache:O,prefetchCache:j,pushRef:{pendingPush:!1,mpaNavigation:!1,preserveCustomHistoryState:!0},focusAndScrollRef:{apply:!1,onlyHashChange:!1,hashFragment:null,segmentPaths:[]},canonicalUrl:P,nextUrl:null!=(r=(0,l.extractPathFromFlightRouterState)(m)||(null==h?void 0:h.pathname))?r:null};if(h){let e=new URL(""+h.pathname+h.search,h.origin);(0,o.createSeededPrefetchCacheEntry)({url:e,data:{flightData:[g],canonicalUrl:void 0,couldBeIntercepted:!!_,prerendered:b,postponed:y,staleTime:b&&1?o.STATIC_STALETIME_MS:-1},tree:T.tree,prefetchCache:T.prefetchCache,nextUrl:T.nextUrl,kind:b?a.PrefetchKind.FULL:a.PrefetchKind.AUTO})}return T}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3612:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hmrRefreshReducer",{enumerable:!0,get:function(){return n}}),r(8586),r(1139),r(7442),r(9234),r(3894),r(3507),r(878),r(6158),r(6375),r(4108);let n=function(e,t){return e};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3668:(e,t)=>{"use strict";function r(){return""}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getDeploymentIdQueryOrEmptyString",{enumerable:!0,get:function(){return r}})},3678:(e,t,r)=>{"use strict";function n(){throw Object.defineProperty(Error("`forbidden()` is experimental and only allowed to be enabled when `experimental.authInterrupts` is enabled."),"__NEXT_ERROR_CODE",{value:"E488",enumerable:!1,configurable:!0})}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"forbidden",{enumerable:!0,get:function(){return n}}),r(6494).HTTP_ERROR_FALLBACK_ERROR_CODE,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3806:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"callServer",{enumerable:!0,get:function(){return o}});let n=r(2115),u=r(9818),l=r(1027);async function o(e,t){return new Promise((r,o)=>{(0,n.startTransition)(()=>{(0,l.dispatchAppRouterAction)({type:u.ACTION_SERVER_ACTION,actionId:e,actionArgs:t,resolve:r,reject:o})})})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3838:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(6446),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3894:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{handleExternalUrl:function(){return g},navigateReducer:function(){return function e(t,r){let{url:R,isExternalUrl:E,navigateType:O,shouldScroll:P,allowAliasing:j}=r,T={},{hash:S}=R,M=(0,u.createHrefFromUrl)(R),w="push"===O;if((0,y.prunePrefetchCache)(t.prefetchCache),T.preserveCustomHistoryState=!1,T.pendingPush=w,E)return g(t,T,R.toString(),w);if(document.getElementById("__next-page-redirect"))return g(t,T,M,w);let x=(0,y.getOrCreatePrefetchCacheEntry)({url:R,nextUrl:t.nextUrl,tree:t.tree,prefetchCache:t.prefetchCache,allowAliasing:j}),{treeAtTimeOfPrefetch:C,data:A}=x;return d.prefetchQueue.bump(A),A.then(d=>{let{flightData:y,canonicalUrl:E,postponed:O}=d,j=Date.now(),A=!1;if(x.lastUsedTime||(x.lastUsedTime=j,A=!0),x.aliased){let n=new URL(R.href);E&&(n.pathname=E.pathname);let u=(0,v.handleAliasedPrefetchEntry)(j,t,y,n,T);return!1===u?e(t,{...r,allowAliasing:!1}):u}if("string"==typeof y)return g(t,T,y,w);let N=E?(0,u.createHrefFromUrl)(E):M;if(S&&t.canonicalUrl.split("#",1)[0]===N.split("#",1)[0])return T.onlyHashChange=!0,T.canonicalUrl=N,T.shouldScroll=P,T.hashFragment=S,T.scrollableSegments=[],(0,s.handleMutable)(t,T);let D=t.tree,U=t.cache,I=[];for(let e of y){let{pathToSegment:r,seedData:u,head:s,isHeadPartial:d,isRootRender:y}=e,v=e.tree,E=["",...r],P=(0,o.applyRouterStatePatchToTree)(E,D,v,M);if(null===P&&(P=(0,o.applyRouterStatePatchToTree)(E,C,v,M)),null!==P){if(u&&y&&O){let e=(0,_.startPPRNavigation)(j,U,D,v,u,s,d,!1,I);if(null!==e){if(null===e.route)return g(t,T,M,w);P=e.route;let r=e.node;null!==r&&(T.cache=r);let u=e.dynamicRequestTree;if(null!==u){let r=(0,n.fetchServerResponse)(new URL(N,R.origin),{flightRouterState:u,nextUrl:t.nextUrl});(0,_.listenForDynamicRequest)(e,r)}}else P=v}else{if((0,i.isNavigatingToNewRootLayout)(D,P))return g(t,T,M,w);let n=(0,p.createEmptyCacheNode)(),u=!1;for(let t of(x.status!==c.PrefetchCacheEntryStatus.stale||A?u=(0,f.applyFlightData)(j,U,n,e,x):(u=function(e,t,r,n){let u=!1;for(let l of(e.rsc=t.rsc,e.prefetchRsc=t.prefetchRsc,e.loading=t.loading,e.parallelRoutes=new Map(t.parallelRoutes),m(n).map(e=>[...r,...e])))(0,b.clearCacheNodeDataForSegmentPath)(e,t,l),u=!0;return u}(n,U,r,v),x.lastUsedTime=j),(0,a.shouldHardNavigate)(E,D)?(n.rsc=U.rsc,n.prefetchRsc=U.prefetchRsc,(0,l.invalidateCacheBelowFlightSegmentPath)(n,U,r),T.cache=n):u&&(T.cache=n,U=n),m(v))){let e=[...r,...t];e[e.length-1]!==h.DEFAULT_SEGMENT_KEY&&I.push(e)}}D=P}}return T.patchedTree=D,T.canonicalUrl=N,T.scrollableSegments=I,T.hashFragment=S,T.shouldScroll=P,(0,s.handleMutable)(t,T)},()=>t)}}});let n=r(8586),u=r(1139),l=r(4466),o=r(7442),a=r(5567),i=r(9234),c=r(9818),s=r(3507),f=r(878),d=r(9154),p=r(6158),h=r(8291),_=r(4150),y=r(1518),b=r(9880),v=r(5563);function g(e,t,r,n){return t.mpaNavigation=!0,t.canonicalUrl=r,t.pendingPush=n,t.scrollableSegments=void 0,(0,s.handleMutable)(e,t)}function m(e){let t=[],[r,n]=e;if(0===Object.keys(n).length)return[[r]];for(let[e,u]of Object.entries(n))for(let n of m(u))""===r?t.push([e,...n]):t.push([r,e,...n]);return t}r(6005),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3942:(e,t)=>{"use strict";function r(e){let t=5381;for(let r=0;r>>0}function n(e){return r(e).toString(36).slice(0,5)}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{djb2Hash:function(){return r},hexHash:function(){return n}})},4074:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addPathPrefix",{enumerable:!0,get:function(){return u}});let n=r(427);function u(e,t){if(!e.startsWith("/")||!t)return e;let{pathname:r,query:u,hash:l}=(0,n.parsePath)(e);return""+t+r+u+l}},4108:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hasInterceptionRouteInCurrentTree",{enumerable:!0,get:function(){return function e(t){let[r,u]=t;if(Array.isArray(r)&&("di"===r[2]||"ci"===r[2])||"string"==typeof r&&(0,n.isInterceptionRouteAppPath)(r))return!0;if(u){for(let t in u)if(e(u[t]))return!0}return!1}}});let n=r(7755);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4150:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{abortTask:function(){return h},listenForDynamicRequest:function(){return p},startPPRNavigation:function(){return c},updateCacheNodeOnPopstateRestoration:function(){return function e(t,r){let n=r[1],u=t.parallelRoutes,o=new Map(u);for(let t in n){let r=n[t],a=r[0],i=(0,l.createRouterCacheKey)(a),c=u.get(t);if(void 0!==c){let n=c.get(i);if(void 0!==n){let u=e(n,r),l=new Map(c);l.set(i,u),o.set(t,l)}}}let a=t.rsc,i=b(a)&&"pending"===a.status;return{lazyData:null,rsc:a,head:t.head,prefetchHead:i?t.prefetchHead:[null,null],prefetchRsc:i?t.prefetchRsc:null,loading:t.loading,parallelRoutes:o,navigatedAt:t.navigatedAt}}}});let n=r(8291),u=r(1127),l=r(5637),o=r(9234),a=r(1518),i={route:null,node:null,dynamicRequestTree:null,children:null};function c(e,t,r,o,a,c,d,p,h){return function e(t,r,o,a,c,d,p,h,_,y,b){let v=o[1],g=a[1],m=null!==d?d[2]:null;c||!0===a[4]&&(c=!0);let R=r.parallelRoutes,E=new Map(R),O={},P=null,j=!1,T={};for(let r in g){let o,a=g[r],f=v[r],d=R.get(r),S=null!==m?m[r]:null,M=a[0],w=y.concat([r,M]),x=(0,l.createRouterCacheKey)(M),C=void 0!==f?f[0]:void 0,A=void 0!==d?d.get(x):void 0;if(null!==(o=M===n.DEFAULT_SEGMENT_KEY?void 0!==f?{route:f,node:null,dynamicRequestTree:null,children:null}:s(t,f,a,A,c,void 0!==S?S:null,p,h,w,b):_&&0===Object.keys(a[1]).length?s(t,f,a,A,c,void 0!==S?S:null,p,h,w,b):void 0!==f&&void 0!==C&&(0,u.matchSegment)(M,C)&&void 0!==A&&void 0!==f?e(t,A,f,a,c,S,p,h,_,w,b):s(t,f,a,A,c,void 0!==S?S:null,p,h,w,b))){if(null===o.route)return i;null===P&&(P=new Map),P.set(r,o);let e=o.node;if(null!==e){let t=new Map(d);t.set(x,e),E.set(r,t)}let t=o.route;O[r]=t;let n=o.dynamicRequestTree;null!==n?(j=!0,T[r]=n):T[r]=t}else O[r]=a,T[r]=a}if(null===P)return null;let S={lazyData:null,rsc:r.rsc,prefetchRsc:r.prefetchRsc,head:r.head,prefetchHead:r.prefetchHead,loading:r.loading,parallelRoutes:E,navigatedAt:t};return{route:f(a,O),node:S,dynamicRequestTree:j?f(a,T):null,children:P}}(e,t,r,o,!1,a,c,d,p,[],h)}function s(e,t,r,n,u,c,s,p,h,_){return!u&&(void 0===t||(0,o.isNavigatingToNewRootLayout)(t,r))?i:function e(t,r,n,u,o,i,c,s){let p,h,_,y,b=r[1],v=0===Object.keys(b).length;if(void 0!==n&&n.navigatedAt+a.DYNAMIC_STALETIME_MS>t)p=n.rsc,h=n.loading,_=n.head,y=n.navigatedAt;else if(null===u)return d(t,r,null,o,i,c,s);else if(p=u[1],h=u[3],_=v?o:null,y=t,u[4]||i&&v)return d(t,r,u,o,i,c,s);let g=null!==u?u[2]:null,m=new Map,R=void 0!==n?n.parallelRoutes:null,E=new Map(R),O={},P=!1;if(v)s.push(c);else for(let r in b){let n=b[r],u=null!==g?g[r]:null,a=null!==R?R.get(r):void 0,f=n[0],d=c.concat([r,f]),p=(0,l.createRouterCacheKey)(f),h=e(t,n,void 0!==a?a.get(p):void 0,u,o,i,d,s);m.set(r,h);let _=h.dynamicRequestTree;null!==_?(P=!0,O[r]=_):O[r]=n;let y=h.node;if(null!==y){let e=new Map;e.set(p,y),E.set(r,e)}}return{route:r,node:{lazyData:null,rsc:p,prefetchRsc:null,head:_,prefetchHead:null,loading:h,parallelRoutes:E,navigatedAt:y},dynamicRequestTree:P?f(r,O):null,children:m}}(e,r,n,c,s,p,h,_)}function f(e,t){let r=[e[0],t];return 2 in e&&(r[2]=e[2]),3 in e&&(r[3]=e[3]),4 in e&&(r[4]=e[4]),r}function d(e,t,r,n,u,o,a){let i=f(t,t[1]);return i[3]="refetch",{route:t,node:function e(t,r,n,u,o,a,i){let c=r[1],s=null!==n?n[2]:null,f=new Map;for(let r in c){let n=c[r],d=null!==s?s[r]:null,p=n[0],h=a.concat([r,p]),_=(0,l.createRouterCacheKey)(p),y=e(t,n,void 0===d?null:d,u,o,h,i),b=new Map;b.set(_,y),f.set(r,b)}let d=0===f.size;d&&i.push(a);let p=null!==n?n[1]:null,h=null!==n?n[3]:null;return{lazyData:null,parallelRoutes:f,prefetchRsc:void 0!==p?p:null,prefetchHead:d?u:[null,null],loading:void 0!==h?h:null,rsc:v(),head:d?v():null,navigatedAt:t}}(e,t,r,n,u,o,a),dynamicRequestTree:i,children:null}}function p(e,t){t.then(t=>{let{flightData:r}=t;if("string"!=typeof r){for(let t of r){let{segmentPath:r,tree:n,seedData:o,head:a}=t;o&&function(e,t,r,n,o){let a=e;for(let e=0;e{h(e,t)})}function h(e,t){let r=e.node;if(null===r)return;let n=e.children;if(null===n)_(e.route,r,t);else for(let e of n.values())h(e,t);e.dynamicRequestTree=null}function _(e,t,r){let n=e[1],u=t.parallelRoutes;for(let e in n){let t=n[e],o=u.get(e);if(void 0===o)continue;let a=t[0],i=(0,l.createRouterCacheKey)(a),c=o.get(i);void 0!==c&&_(t,c,r)}let o=t.rsc;b(o)&&(null===r?o.resolve(null):o.reject(r));let a=t.head;b(a)&&a.resolve(null)}let y=Symbol();function b(e){return e&&e.tag===y}function v(){let e,t,r=new Promise((r,n)=>{e=r,t=n});return r.status="pending",r.resolve=t=>{"pending"===r.status&&(r.status="fulfilled",r.value=t,e(t))},r.reject=e=>{"pending"===r.status&&(r.status="rejected",r.reason=e,t(e))},r.tag=y,r}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4340:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{GracefulDegradeBoundary:function(){return l},default:function(){return o}});let n=r(5155),u=r(2115);class l extends u.Component{static getDerivedStateFromError(e){return{hasError:!0}}componentDidMount(){let e=this.htmlRef.current;this.state.hasError&&e&&Object.entries(this.htmlAttributes).forEach(t=>{let[r,n]=t;e.setAttribute(r,n)})}render(){let{hasError:e}=this.state;return(this.rootHtml||(this.rootHtml=document.documentElement.innerHTML,this.htmlAttributes=function(e){let t={};for(let r=0;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"RedirectStatusCode",{enumerable:!0,get:function(){return r}});var r=function(e){return e[e.SeeOther=303]="SeeOther",e[e.TemporaryRedirect=307]="TemporaryRedirect",e[e.PermanentRedirect=308]="PermanentRedirect",e}({});("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4466:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"invalidateCacheBelowFlightSegmentPath",{enumerable:!0,get:function(){return function e(t,r,l){let o=l.length<=2,[a,i]=l,c=(0,n.createRouterCacheKey)(i),s=r.parallelRoutes.get(a);if(!s)return;let f=t.parallelRoutes.get(a);if(f&&f!==s||(f=new Map(s),t.parallelRoutes.set(a,f)),o)return void f.delete(c);let d=s.get(c),p=f.get(c);p&&d&&(p===d&&(p={lazyData:p.lazyData,rsc:p.rsc,prefetchRsc:p.prefetchRsc,head:p.head,prefetchHead:p.prefetchHead,parallelRoutes:new Map(p.parallelRoutes)},f.set(c,p)),e(p,d,(0,u.getNextFlightSegmentPath)(l)))}}});let n=r(5637),u=r(2561);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4486:(e,t,r)=>{"use strict";let n,u;Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hydrate",{enumerable:!0,get:function(){return U}});let l=r(8229),o=r(6966),a=r(5155);r(3838);let i=l._(r(2669)),c=o._(r(2115)),s=r(7197),f=r(2830),d=r(6698),p=r(9155),h=r(3806),_=r(1818),y=r(6634),b=l._(r(6158)),v=r(3567);r(5227);let g=r(5624),m=r(774),R=s.createFromReadableStream,E=document,O=new TextEncoder,P=!1,j=!1,T=null;function S(e){if(0===e[0])n=[];else if(1===e[0]){if(!n)throw Object.defineProperty(Error("Unexpected server data: missing bootstrap script."),"__NEXT_ERROR_CODE",{value:"E18",enumerable:!1,configurable:!0});u?u.enqueue(O.encode(e[1])):n.push(e[1])}else if(2===e[0])T=e[1];else if(3===e[0]){if(!n)throw Object.defineProperty(Error("Unexpected server data: missing bootstrap script."),"__NEXT_ERROR_CODE",{value:"E18",enumerable:!1,configurable:!0});let r=atob(e[1]),l=new Uint8Array(r.length);for(var t=0;t{e.enqueue("string"==typeof t?O.encode(t):t)}),P&&!j)&&(null===e.desiredSize||e.desiredSize<0?e.error(Object.defineProperty(Error("The connection to the page was unexpectedly closed, possibly due to the stop button being clicked, loss of Wi-Fi, or an unstable internet connection."),"__NEXT_ERROR_CODE",{value:"E117",enumerable:!1,configurable:!0})):e.close(),j=!0,n=void 0),u=e}}),{callServer:h.callServer,findSourceMapURL:_.findSourceMapURL});function C(e){let{pendingActionQueue:t}=e,r=(0,c.use)(x),n=(0,c.use)(t);return(0,a.jsx)(b.default,{gracefullyDegrade:(0,m.isBot)(window.navigator.userAgent),actionQueue:n,globalErrorState:r.G,assetPrefix:r.p})}let A=c.default.StrictMode;function N(e){let{children:t}=e;return t}let D={onDefaultTransitionIndicator:function(){return()=>{}},onRecoverableError:d.onRecoverableError,onCaughtError:p.onCaughtError,onUncaughtError:p.onUncaughtError};function U(e){let t=new Promise((t,r)=>{x.then(r=>{(0,g.setAppBuildId)(r.b);let n=Date.now();t((0,y.createMutableActionQueue)((0,v.createInitialRouterState)({navigatedAt:n,initialFlightData:r.f,initialCanonicalUrlParts:r.c,initialParallelRoutes:new Map,location:window.location,couldBeIntercepted:r.i,postponed:r.s,prerendered:r.S}),e))},e=>r(e))}),r=(0,a.jsx)(A,{children:(0,a.jsx)(f.HeadManagerContext.Provider,{value:{appDir:!0},children:(0,a.jsx)(N,{children:(0,a.jsx)(C,{pendingActionQueue:t})})})});"__next_error__"===document.documentElement.id?i.default.createRoot(E,D).render(r):c.default.startTransition(()=>{i.default.hydrateRoot(E,r,{...D,formState:T})})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4758:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"fillLazyItemsTillLeafWithHead",{enumerable:!0,get:function(){return function e(t,r,l,o,a,i,c){if(0===Object.keys(o[1]).length){r.head=i;return}for(let s in o[1]){let f,d=o[1][s],p=d[0],h=(0,n.createRouterCacheKey)(p),_=null!==a&&void 0!==a[2][s]?a[2][s]:null;if(l){let n=l.parallelRoutes.get(s);if(n){let l,o=(null==c?void 0:c.kind)==="auto"&&c.status===u.PrefetchCacheEntryStatus.reusable,a=new Map(n),f=a.get(h);l=null!==_?{lazyData:null,rsc:_[1],prefetchRsc:null,head:null,prefetchHead:null,loading:_[3],parallelRoutes:new Map(null==f?void 0:f.parallelRoutes),navigatedAt:t}:o&&f?{lazyData:f.lazyData,rsc:f.rsc,prefetchRsc:f.prefetchRsc,head:f.head,prefetchHead:f.prefetchHead,parallelRoutes:new Map(f.parallelRoutes),loading:f.loading}:{lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map(null==f?void 0:f.parallelRoutes),loading:null,navigatedAt:t},a.set(h,l),e(t,l,f,d,_||null,i,c),r.parallelRoutes.set(s,a);continue}}if(null!==_){let e=_[1],r=_[3];f={lazyData:null,rsc:e,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:r,navigatedAt:t}}else f={lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:t};let y=r.parallelRoutes.get(s);y?y.set(h,f):r.parallelRoutes.set(s,new Map([[h,f]])),e(t,f,void 0,d,_,i,c)}}}});let n=r(5637),u=r(9818);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4819:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"restoreReducer",{enumerable:!0,get:function(){return l}});let n=r(1139),u=r(8946);function l(e,t){var r;let{url:l,tree:o}=t,a=(0,n.createHrefFromUrl)(l),i=o||e.tree,c=e.cache;return{canonicalUrl:a,pushRef:{pendingPush:!1,mpaNavigation:!1,preserveCustomHistoryState:!0},focusAndScrollRef:e.focusAndScrollRef,cache:c,prefetchCache:e.prefetchCache,tree:i,nextUrl:null!=(r=(0,u.extractPathFromFlightRouterState)(i))?r:l.pathname}}r(4150),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4882:(e,t,r)=>{"use strict";function n(e){return e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeBasePath",{enumerable:!0,get:function(){return n}}),r(7102),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4908:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{addRefreshMarkerToActiveParallelSegments:function(){return function e(t,r){let[n,u,,o]=t;for(let a in n.includes(l.PAGE_SEGMENT_KEY)&&"refresh"!==o&&(t[2]=r,t[3]="refresh"),u)e(u[a],r)}},refreshInactiveParallelSegments:function(){return o}});let n=r(878),u=r(8586),l=r(8291);async function o(e){let t=new Set;await a({...e,rootTree:e.updatedTree,fetchedSegments:t})}async function a(e){let{navigatedAt:t,state:r,updatedTree:l,updatedCache:o,includeNextUrl:i,fetchedSegments:c,rootTree:s=l,canonicalUrl:f}=e,[,d,p,h]=l,_=[];if(p&&p!==f&&"refresh"===h&&!c.has(p)){c.add(p);let e=(0,u.fetchServerResponse)(new URL(p,location.origin),{flightRouterState:[s[0],s[1],s[2],"refetch"],nextUrl:i?r.nextUrl:null}).then(e=>{let{flightData:r}=e;if("string"!=typeof r)for(let e of r)(0,n.applyFlightData)(t,o,o,e)});_.push(e)}for(let e in d){let n=a({navigatedAt:t,state:r,updatedTree:d[e],updatedCache:o,includeNextUrl:i,fetchedSegments:c,rootTree:s,canonicalUrl:f});_.push(n)}await Promise.all(_)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4911:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"AsyncMetadataOutlet",{enumerable:!0,get:function(){return o}});let n=r(5155),u=r(2115);function l(e){let{promise:t}=e,{error:r,digest:n}=(0,u.use)(t);if(r)throw n&&(r.digest=n),r;return null}function o(e){let{promise:t}=e;return(0,n.jsx)(u.Suspense,{fallback:null,children:(0,n.jsx)(l,{promise:t})})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4930:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{IDLE_LINK_STATUS:function(){return c},PENDING_LINK_STATUS:function(){return i},mountFormInstance:function(){return v},mountLinkInstance:function(){return b},onLinkVisibilityChanged:function(){return m},onNavigationIntent:function(){return R},pingVisibleLinks:function(){return O},setLinkForCurrentNavigation:function(){return s},unmountLinkForCurrentNavigation:function(){return f},unmountPrefetchableInstance:function(){return g}}),r(6634);let n=r(6158),u=r(9818),l=r(6005),o=r(2115),a=null,i={pending:!0},c={pending:!1};function s(e){(0,o.startTransition)(()=>{null==a||a.setOptimisticLinkStatus(c),null==e||e.setOptimisticLinkStatus(i),a=e})}function f(e){a===e&&(a=null)}let d="function"==typeof WeakMap?new WeakMap:new Map,p=new Set,h="function"==typeof IntersectionObserver?new IntersectionObserver(function(e){for(let t of e){let e=t.intersectionRatio>0;m(t.target,e)}},{rootMargin:"200px"}):null;function _(e,t){void 0!==d.get(e)&&g(e),d.set(e,t),null!==h&&h.observe(e)}function y(e){try{return(0,n.createPrefetchURL)(e)}catch(t){return("function"==typeof reportError?reportError:console.error)("Cannot prefetch '"+e+"' because it cannot be converted to a URL."),null}}function b(e,t,r,n,u,l){if(u){let u=y(t);if(null!==u){let t={router:r,kind:n,isVisible:!1,prefetchTask:null,prefetchHref:u.href,setOptimisticLinkStatus:l};return _(e,t),t}}return{router:r,kind:n,isVisible:!1,prefetchTask:null,prefetchHref:null,setOptimisticLinkStatus:l}}function v(e,t,r,n){let u=y(t);null!==u&&_(e,{router:r,kind:n,isVisible:!1,prefetchTask:null,prefetchHref:u.href,setOptimisticLinkStatus:null})}function g(e){let t=d.get(e);if(void 0!==t){d.delete(e),p.delete(t);let r=t.prefetchTask;null!==r&&(0,l.cancelPrefetchTask)(r)}null!==h&&h.unobserve(e)}function m(e,t){let r=d.get(e);void 0!==r&&(r.isVisible=t,t?p.add(r):p.delete(r),E(r,l.PrefetchPriority.Default))}function R(e,t){let r=d.get(e);void 0!==r&&void 0!==r&&E(r,l.PrefetchPriority.Intent)}function E(e,t){var r;let n=e.prefetchTask;if(!e.isVisible){null!==n&&(0,l.cancelPrefetchTask)(n);return}r=e,(async()=>r.router.prefetch(r.prefetchHref,{kind:r.kind}))().catch(e=>{})}function O(e,t){for(let r of p){let n=r.prefetchTask;if(null!==n&&!(0,l.isPrefetchTaskDirty)(n,e,t))continue;null!==n&&(0,l.cancelPrefetchTask)(n);let o=(0,l.createCacheKey)(r.prefetchHref,e);r.prefetchTask=(0,l.schedulePrefetchTask)(o,t,r.kind===u.PrefetchKind.FULL,l.PrefetchPriority.Default,null)}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4970:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ClientSegmentRoot",{enumerable:!0,get:function(){return u}});let n=r(5155);function u(e){let{Component:t,slots:u,params:l,promise:o}=e;{let{createRenderParamsFromClient:e}=r(3558),o=e(l);return(0,n.jsx)(t,{...u,params:o})}}r(9837),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5072:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTML_LIMITED_BOT_UA_RE",{enumerable:!0,get:function(){return r}});let r=/Mediapartners-Google|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti/i},5122:(e,t)=>{"use strict";function r(e){return null!==e&&"object"==typeof e&&"then"in e&&"function"==typeof e.then}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isThenable",{enumerable:!0,get:function(){return r}})},5155:(e,t,r)=>{"use strict";e.exports=r(6897)},5209:(e,t)=>{"use strict";function r(e){return Object.prototype.toString.call(e)}function n(e){if("[object Object]"!==r(e))return!1;let t=Object.getPrototypeOf(e);return null===t||t.hasOwnProperty("isPrototypeOf")}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getObjectClassLabel:function(){return r},isPlainObject:function(){return n}})},5227:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{AppRouterContext:function(){return u},GlobalLayoutRouterContext:function(){return o},LayoutRouterContext:function(){return l},MissingSlotContext:function(){return i},TemplateContext:function(){return a}});let n=r(8229)._(r(2115)),u=n.default.createContext(null),l=n.default.createContext(null),o=n.default.createContext(null),a=n.default.createContext(null),i=n.default.createContext(new Set)},5262:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{BailoutToCSRError:function(){return n},isBailoutToCSRError:function(){return u}});let r="BAILOUT_TO_CLIENT_SIDE_RENDERING";class n extends Error{constructor(e){super("Bail out to client-side rendering: "+e),this.reason=e,this.digest=r}}function u(e){return"object"==typeof e&&null!==e&&"digest"in e&&e.digest===r}},5415:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(5449);let n=r(6188),u=r(1408);(0,n.appBootstrap)(()=>{let{hydrate:e}=r(4486);r(6158),r(7555),e(u)}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5449:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(3668);let n=r(589);{let e=r.u;r.u=function(){for(var t=arguments.length,r=Array(t),u=0;u{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"refreshReducer",{enumerable:!0,get:function(){return h}});let n=r(8586),u=r(1139),l=r(7442),o=r(9234),a=r(3894),i=r(3507),c=r(4758),s=r(6158),f=r(6375),d=r(4108),p=r(4908);function h(e,t){let{origin:r}=t,h={},_=e.canonicalUrl,y=e.tree;h.preserveCustomHistoryState=!1;let b=(0,s.createEmptyCacheNode)(),v=(0,d.hasInterceptionRouteInCurrentTree)(e.tree);b.lazyData=(0,n.fetchServerResponse)(new URL(_,r),{flightRouterState:[y[0],y[1],y[2],"refetch"],nextUrl:v?e.nextUrl:null});let g=Date.now();return b.lazyData.then(async r=>{let{flightData:n,canonicalUrl:s}=r;if("string"==typeof n)return(0,a.handleExternalUrl)(e,h,n,e.pushRef.pendingPush);for(let r of(b.lazyData=null,n)){let{tree:n,seedData:i,head:d,isRootRender:m}=r;if(!m)return console.log("REFRESH FAILED"),e;let R=(0,l.applyRouterStatePatchToTree)([""],y,n,e.canonicalUrl);if(null===R)return(0,f.handleSegmentMismatch)(e,t,n);if((0,o.isNavigatingToNewRootLayout)(y,R))return(0,a.handleExternalUrl)(e,h,_,e.pushRef.pendingPush);let E=s?(0,u.createHrefFromUrl)(s):void 0;if(s&&(h.canonicalUrl=E),null!==i){let e=i[1],t=i[3];b.rsc=e,b.prefetchRsc=null,b.loading=t,(0,c.fillLazyItemsTillLeafWithHead)(g,b,void 0,n,i,d,void 0),h.prefetchCache=new Map}await (0,p.refreshInactiveParallelSegments)({navigatedAt:g,state:e,updatedTree:R,updatedCache:b,includeNextUrl:v,canonicalUrl:h.canonicalUrl||e.canonicalUrl}),h.cache=b,h.patchedTree=R,y=R}return(0,i.handleMutable)(e,h)},()=>e)}r(6005),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5563:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{addSearchParamsToPageSegments:function(){return f},handleAliasedPrefetchEntry:function(){return s}});let n=r(8291),u=r(6158),l=r(7442),o=r(1139),a=r(5637),i=r(3118),c=r(3507);function s(e,t,r,s,d){let p,h=t.tree,_=t.cache,y=(0,o.createHrefFromUrl)(s);if("string"==typeof r)return!1;for(let t of r){if(!function e(t){if(!t)return!1;let r=t[2];if(t[3])return!0;for(let t in r)if(e(r[t]))return!0;return!1}(t.seedData))continue;let r=t.tree;r=f(r,Object.fromEntries(s.searchParams));let{seedData:o,isRootRender:c,pathToSegment:d}=t,b=["",...d];r=f(r,Object.fromEntries(s.searchParams));let v=(0,l.applyRouterStatePatchToTree)(b,h,r,y),g=(0,u.createEmptyCacheNode)();if(c&&o){let t=o[1];g.loading=o[3],g.rsc=t,function e(t,r,u,l,o){if(0!==Object.keys(l[1]).length)for(let i in l[1]){let c,s=l[1][i],f=s[0],d=(0,a.createRouterCacheKey)(f),p=null!==o&&void 0!==o[2][i]?o[2][i]:null;if(null!==p){let e=p[1],r=p[3];c={lazyData:null,rsc:f.includes(n.PAGE_SEGMENT_KEY)?null:e,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:r,navigatedAt:t}}else c={lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:-1};let h=r.parallelRoutes.get(i);h?h.set(d,c):r.parallelRoutes.set(i,new Map([[d,c]])),e(t,c,u,s,p)}}(e,g,_,r,o)}else g.rsc=_.rsc,g.prefetchRsc=_.prefetchRsc,g.loading=_.loading,g.parallelRoutes=new Map(_.parallelRoutes),(0,i.fillCacheWithNewSubTreeDataButOnlyLoading)(e,g,_,t);v&&(h=v,_=g,p=!0)}return!!p&&(d.patchedTree=h,d.cache=_,d.canonicalUrl=y,d.hashFragment=s.hash,(0,c.handleMutable)(t,d))}function f(e,t){let[r,u,...l]=e;if(r.includes(n.PAGE_SEGMENT_KEY))return[(0,n.addSearchParamsIfPageSegment)(r,t),u,...l];let o={};for(let[e,r]of Object.entries(u))o[e]=f(r,t);return[r,o,...l]}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5567:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"shouldHardNavigate",{enumerable:!0,get:function(){return function e(t,r){let[l,o]=r,[a,i]=t;return(0,u.matchSegment)(a,l)?!(t.length<=2)&&e((0,n.getNextFlightSegmentPath)(t),o[i]):!!Array.isArray(a)}}});let n=r(2561),u=r(1127);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5618:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ReadonlyURLSearchParams:function(){return s},RedirectType:function(){return u.RedirectType},forbidden:function(){return o.forbidden},notFound:function(){return l.notFound},permanentRedirect:function(){return n.permanentRedirect},redirect:function(){return n.redirect},unauthorized:function(){return a.unauthorized},unstable_rethrow:function(){return i.unstable_rethrow}});let n=r(6825),u=r(2210),l=r(8527),o=r(3678),a=r(9187),i=r(7599);class c extends Error{constructor(){super("Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams")}}class s extends URLSearchParams{append(){throw new c}delete(){throw new c}set(){throw new c}sort(){throw new c}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5624:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getAppBuildId:function(){return u},setAppBuildId:function(){return n}});let r="";function n(e){r=e}function u(){return r}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5637:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createRouterCacheKey",{enumerable:!0,get:function(){return u}});let n=r(8291);function u(e,t){return(void 0===t&&(t=!1),Array.isArray(e))?e[0]+"|"+e[1]+"|"+e[2]:t&&e.startsWith(n.PAGE_SEGMENT_KEY)?n.PAGE_SEGMENT_KEY:e}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5807:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return u},getProperError:function(){return l}});let n=r(5209);function u(e){return"object"==typeof e&&null!==e&&"name"in e&&"message"in e}function l(e){return u(e)?e:Object.defineProperty(Error((0,n.isPlainObject)(e)?function(e){let t=new WeakSet;return JSON.stringify(e,(e,r)=>{if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r})}(e):e+""),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0})}},5929:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addBasePath",{enumerable:!0,get:function(){return l}});let n=r(4074),u=r(214);function l(e,t){return(0,u.normalizePathTrailingSlash)((0,n.addPathPrefix)(e,""))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5952:(e,t,r)=>{"use strict";function n(e,t){if(!Object.prototype.hasOwnProperty.call(e,t))throw TypeError("attempted to use private field on non-instance");return e}r.r(t),r.d(t,{_:()=>n})},6005:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{NavigationResultTag:function(){return d},PrefetchPriority:function(){return p},cancelPrefetchTask:function(){return i},createCacheKey:function(){return f},getCurrentCacheVersion:function(){return o},isPrefetchTaskDirty:function(){return s},navigate:function(){return u},prefetch:function(){return n},reschedulePrefetchTask:function(){return c},revalidateEntireCache:function(){return l},schedulePrefetchTask:function(){return a}});let r=()=>{throw Object.defineProperty(Error("Segment Cache experiment is not enabled. This is a bug in Next.js."),"__NEXT_ERROR_CODE",{value:"E654",enumerable:!1,configurable:!0})},n=r,u=r,l=r,o=r,a=r,i=r,c=r,s=r,f=r;var d=function(e){return e[e.MPA=0]="MPA",e[e.Success=1]="Success",e[e.NoOp=2]="NoOp",e[e.Async=3]="Async",e}({}),p=function(e){return e[e.Intent=2]="Intent",e[e.Default=1]="Default",e[e.Background=0]="Background",e}({});("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6158:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{createEmptyCacheNode:function(){return A},createPrefetchURL:function(){return x},default:function(){return I},isExternalURL:function(){return w}});let n=r(8229),u=r(6966),l=r(5155),o=u._(r(2115)),a=r(5227),i=r(9818),c=r(1139),s=r(886),f=r(1027),d=r(6614),p=n._(r(8393)),h=r(774),_=r(5929),y=r(7760),b=r(686),v=r(2691),g=r(1822),m=r(4882),R=r(7102),E=r(8946),O=r(8836),P=r(6634),j=r(6825),T=r(2210);r(4930);let S=n._(r(4340)),M={};function w(e){return e.origin!==window.location.origin}function x(e){let t;if((0,h.isBot)(window.navigator.userAgent))return null;try{t=new URL((0,_.addBasePath)(e),window.location.href)}catch(t){throw Object.defineProperty(Error("Cannot prefetch '"+e+"' because it cannot be converted to a URL."),"__NEXT_ERROR_CODE",{value:"E234",enumerable:!1,configurable:!0})}return w(t)?null:t}function C(e){let{appRouterState:t}=e;return(0,o.useInsertionEffect)(()=>{let{tree:e,pushRef:r,canonicalUrl:n}=t,u={...r.preserveCustomHistoryState?window.history.state:{},__NA:!0,__PRIVATE_NEXTJS_INTERNALS_TREE:e};r.pendingPush&&(0,c.createHrefFromUrl)(new URL(window.location.href))!==n?(r.pendingPush=!1,window.history.pushState(u,"",n)):window.history.replaceState(u,"",n)},[t]),(0,o.useEffect)(()=>{},[t.nextUrl,t.tree]),null}function A(){return{lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:-1}}function N(e){null==e&&(e={});let t=window.history.state,r=null==t?void 0:t.__NA;r&&(e.__NA=r);let n=null==t?void 0:t.__PRIVATE_NEXTJS_INTERNALS_TREE;return n&&(e.__PRIVATE_NEXTJS_INTERNALS_TREE=n),e}function D(e){let{headCacheNode:t}=e,r=null!==t?t.head:null,n=null!==t?t.prefetchHead:null,u=null!==n?n:r;return(0,o.useDeferredValue)(r,u)}function U(e){let t,{actionQueue:r,assetPrefix:n,globalError:u,gracefullyDegrade:c}=e,p=(0,f.useActionQueue)(r),{canonicalUrl:h}=p,{searchParams:_,pathname:O}=(0,o.useMemo)(()=>{let e=new URL(h,window.location.href);return{searchParams:e.searchParams,pathname:(0,R.hasBasePath)(e.pathname)?(0,m.removeBasePath)(e.pathname):e.pathname}},[h]);(0,o.useEffect)(()=>{function e(e){var t;e.persisted&&(null==(t=window.history.state)?void 0:t.__PRIVATE_NEXTJS_INTERNALS_TREE)&&(M.pendingMpaPath=void 0,(0,f.dispatchAppRouterAction)({type:i.ACTION_RESTORE,url:new URL(window.location.href),tree:window.history.state.__PRIVATE_NEXTJS_INTERNALS_TREE}))}return window.addEventListener("pageshow",e),()=>{window.removeEventListener("pageshow",e)}},[]),(0,o.useEffect)(()=>{function e(e){let t="reason"in e?e.reason:e.error;if((0,T.isRedirectError)(t)){e.preventDefault();let r=(0,j.getURLFromRedirectError)(t);(0,j.getRedirectTypeFromError)(t)===T.RedirectType.push?P.publicAppRouterInstance.push(r,{}):P.publicAppRouterInstance.replace(r,{})}}return window.addEventListener("error",e),window.addEventListener("unhandledrejection",e),()=>{window.removeEventListener("error",e),window.removeEventListener("unhandledrejection",e)}},[]);let{pushRef:w}=p;if(w.mpaNavigation){if(M.pendingMpaPath!==h){let e=window.location;w.pendingPush?e.assign(h):e.replace(h),M.pendingMpaPath=h}throw g.unresolvedThenable}(0,o.useEffect)(()=>{let e=window.history.pushState.bind(window.history),t=window.history.replaceState.bind(window.history),r=e=>{var t;let r=window.location.href,n=null==(t=window.history.state)?void 0:t.__PRIVATE_NEXTJS_INTERNALS_TREE;(0,o.startTransition)(()=>{(0,f.dispatchAppRouterAction)({type:i.ACTION_RESTORE,url:new URL(null!=e?e:r,r),tree:n})})};window.history.pushState=function(t,n,u){return(null==t?void 0:t.__NA)||(null==t?void 0:t._N)||(t=N(t),u&&r(u)),e(t,n,u)},window.history.replaceState=function(e,n,u){return(null==e?void 0:e.__NA)||(null==e?void 0:e._N)||(e=N(e),u&&r(u)),t(e,n,u)};let n=e=>{if(e.state){if(!e.state.__NA)return void window.location.reload();(0,o.startTransition)(()=>{(0,P.dispatchTraverseAction)(window.location.href,e.state.__PRIVATE_NEXTJS_INTERNALS_TREE)})}};return window.addEventListener("popstate",n),()=>{window.history.pushState=e,window.history.replaceState=t,window.removeEventListener("popstate",n)}},[]);let{cache:x,tree:A,nextUrl:U,focusAndScrollRef:I}=p,L=(0,o.useMemo)(()=>(0,v.findHeadInCache)(x,A[1]),[x,A]),k=(0,o.useMemo)(()=>(0,E.getSelectedParams)(A),[A]),F=(0,o.useMemo)(()=>({parentTree:A,parentCacheNode:x,parentSegmentPath:null,url:h}),[A,x,h]),B=(0,o.useMemo)(()=>({tree:A,focusAndScrollRef:I,nextUrl:U}),[A,I,U]);if(null!==L){let[e,r]=L;t=(0,l.jsx)(D,{headCacheNode:e},r)}else t=null;let K=(0,l.jsxs)(b.RedirectBoundary,{children:[t,x.rsc,(0,l.jsx)(y.AppRouterAnnouncer,{tree:A})]});return K=c?(0,l.jsx)(S.default,{children:K}):(0,l.jsx)(d.ErrorBoundary,{errorComponent:u[0],errorStyles:u[1],children:K}),(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(C,{appRouterState:p}),(0,l.jsx)(H,{}),(0,l.jsx)(s.PathParamsContext.Provider,{value:k,children:(0,l.jsx)(s.PathnameContext.Provider,{value:O,children:(0,l.jsx)(s.SearchParamsContext.Provider,{value:_,children:(0,l.jsx)(a.GlobalLayoutRouterContext.Provider,{value:B,children:(0,l.jsx)(a.AppRouterContext.Provider,{value:P.publicAppRouterInstance,children:(0,l.jsx)(a.LayoutRouterContext.Provider,{value:F,children:K})})})})})})]})}function I(e){let{actionQueue:t,globalErrorState:r,assetPrefix:n,gracefullyDegrade:u}=e;(0,O.useNavFailureHandler)();let o=(0,l.jsx)(U,{actionQueue:t,assetPrefix:n,globalError:r,gracefullyDegrade:u});return u?o:(0,l.jsx)(d.ErrorBoundary,{errorComponent:p.default,children:o})}let L=new Set,k=new Set;function H(){let[,e]=o.default.useState(0),t=L.size;return(0,o.useEffect)(()=>{let r=()=>e(e=>e+1);return k.add(r),t!==L.size&&r(),()=>{k.delete(r)}},[t,e]),[...L].map((e,t)=>(0,l.jsx)("link",{rel:"stylesheet",href:""+e,precedence:"next"},t))}globalThis._N_E_STYLE_LOAD=function(e){let t=L.size;return L.add(e),L.size!==t&&k.forEach(e=>e()),Promise.resolve()},("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6188:(e,t)=>{"use strict";function r(e){var t,r;t=self.__next_s,r=()=>{e()},t&&t.length?t.reduce((e,t)=>{let[r,n]=t;return e.then(()=>new Promise((e,t)=>{let u=document.createElement("script");if(n)for(let e in n)"children"!==e&&u.setAttribute(e,n[e]);r?(u.src=r,u.onload=()=>e(),u.onerror=t):n&&(u.innerHTML=n.children,setTimeout(e)),document.head.appendChild(u)}))},Promise.resolve()).catch(e=>{console.error(e)}).then(()=>{r()}):r()}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"appBootstrap",{enumerable:!0,get:function(){return r}}),window.next={version:"15.4.2",appDir:!0},("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6206:(e,t,r)=>{"use strict";e.exports=r(2223)},6361:(e,t)=>{"use strict";function r(e){return e.replace(/\/$/,"")||"/"}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeTrailingSlash",{enumerable:!0,get:function(){return r}})},6375:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"handleSegmentMismatch",{enumerable:!0,get:function(){return u}});let n=r(3894);function u(e,t,r){return(0,n.handleExternalUrl)(e,{},e.canonicalUrl,!0)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6420:(e,t,r)=>{"use strict";r.r(t),r.d(t,{_:()=>u});var n=0;function u(e){return"__private_"+n+++"_"+e}},6446:()=>{"trimStart"in String.prototype||(String.prototype.trimStart=String.prototype.trimLeft),"trimEnd"in String.prototype||(String.prototype.trimEnd=String.prototype.trimRight),"description"in Symbol.prototype||Object.defineProperty(Symbol.prototype,"description",{configurable:!0,get:function(){var e=/\((.*)\)/.exec(this.toString());return e?e[1]:void 0}}),Array.prototype.flat||(Array.prototype.flat=function(e,t){return t=this.concat.apply([],this),e>1&&t.some(Array.isArray)?t.flat(e-1):t},Array.prototype.flatMap=function(e,t){return this.map(e,t).flat()}),Promise.prototype.finally||(Promise.prototype.finally=function(e){if("function"!=typeof e)return this.then(e,e);var t=this.constructor||Promise;return this.then(function(r){return t.resolve(e()).then(function(){return r})},function(r){return t.resolve(e()).then(function(){throw r})})}),Object.fromEntries||(Object.fromEntries=function(e){return Array.from(e).reduce(function(e,t){return e[t[0]]=t[1],e},{})}),Array.prototype.at||(Array.prototype.at=function(e){var t=Math.trunc(e)||0;if(t<0&&(t+=this.length),!(t<0||t>=this.length))return this[t]}),Object.hasOwn||(Object.hasOwn=function(e,t){if(null==e)throw TypeError("Cannot convert undefined or null to object");return Object.prototype.hasOwnProperty.call(Object(e),t)}),"canParse"in URL||(URL.canParse=function(e,t){try{return new URL(e,t),!0}catch(e){return!1}})},6494:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{HTTPAccessErrorStatus:function(){return r},HTTP_ERROR_FALLBACK_ERROR_CODE:function(){return u},getAccessFallbackErrorTypeByStatus:function(){return a},getAccessFallbackHTTPStatus:function(){return o},isHTTPAccessFallbackError:function(){return l}});let r={NOT_FOUND:404,FORBIDDEN:403,UNAUTHORIZED:401},n=new Set(Object.values(r)),u="NEXT_HTTP_ERROR_FALLBACK";function l(e){if("object"!=typeof e||null===e||!("digest"in e)||"string"!=typeof e.digest)return!1;let[t,r]=e.digest.split(";");return t===u&&n.has(Number(r))}function o(e){return Number(e.digest.split(";")[1])}function a(e){switch(e){case 401:return"unauthorized";case 403:return"forbidden";case 404:return"not-found";default:return}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6539:(e,t,r)=>{"use strict";function n(e,t){if(void 0===t&&(t={}),t.onlyHashChange)return void e();let r=document.documentElement;r.dataset.scrollBehavior;let n=r.style.scrollBehavior;r.style.scrollBehavior="auto",t.dontForceLayout||r.getClientRects(),e(),r.style.scrollBehavior=n}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"disableSmoothScrollDuringRouteTransition",{enumerable:!0,get:function(){return n}}),r(3230)},6614:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ErrorBoundary:function(){return s},ErrorBoundaryHandler:function(){return c}});let n=r(8229),u=r(5155),l=n._(r(2115)),o=r(9921),a=r(2858);r(8836);let i=r(1799);class c extends l.default.Component{static getDerivedStateFromError(e){if((0,a.isNextRouterError)(e))throw e;return{error:e}}static getDerivedStateFromProps(e,t){let{error:r}=t;return e.pathname!==t.previousPathname&&t.error?{error:null,previousPathname:e.pathname}:{error:t.error,previousPathname:e.pathname}}render(){return this.state.error?(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(i.HandleISRError,{error:this.state.error}),this.props.errorStyles,this.props.errorScripts,(0,u.jsx)(this.props.errorComponent,{error:this.state.error,reset:this.reset})]}):this.props.children}constructor(e){super(e),this.reset=()=>{this.setState({error:null})},this.state={error:null,previousPathname:this.props.pathname}}}function s(e){let{errorComponent:t,errorStyles:r,errorScripts:n,children:l}=e,a=(0,o.useUntrackedPathname)();return t?(0,u.jsx)(c,{pathname:a,errorComponent:t,errorStyles:r,errorScripts:n,children:l}):(0,u.jsx)(u.Fragment,{children:l})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6634:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{createMutableActionQueue:function(){return _},dispatchNavigateAction:function(){return v},dispatchTraverseAction:function(){return g},getCurrentAppRouterState:function(){return y},publicAppRouterInstance:function(){return m}});let n=r(9818),u=r(9726),l=r(2115),o=r(5122);r(6005);let a=r(1027),i=r(5929),c=r(6158),s=r(9154),f=r(4930);function d(e,t){null!==e.pending&&(e.pending=e.pending.next,null!==e.pending?p({actionQueue:e,action:e.pending,setState:t}):e.needsRefresh&&(e.needsRefresh=!1,e.dispatch({type:n.ACTION_REFRESH,origin:window.location.origin},t)))}async function p(e){let{actionQueue:t,action:r,setState:n}=e,u=t.state;t.pending=r;let l=r.payload,a=t.action(u,l);function i(e){r.discarded||(t.state=e,d(t,n),r.resolve(e))}(0,o.isThenable)(a)?a.then(i,e=>{d(t,n),r.reject(e)}):i(a)}let h=null;function _(e,t){let r={state:e,dispatch:(e,t)=>(function(e,t,r){let u={resolve:r,reject:()=>{}};if(t.type!==n.ACTION_RESTORE){let e=new Promise((e,t)=>{u={resolve:e,reject:t}});(0,l.startTransition)(()=>{r(e)})}let o={payload:t,next:null,resolve:u.resolve,reject:u.reject};null===e.pending?(e.last=o,p({actionQueue:e,action:o,setState:r})):t.type===n.ACTION_NAVIGATE||t.type===n.ACTION_RESTORE?(e.pending.discarded=!0,o.next=e.pending.next,e.pending.payload.type===n.ACTION_SERVER_ACTION&&(e.needsRefresh=!0),p({actionQueue:e,action:o,setState:r})):(null!==e.last&&(e.last.next=o),e.last=o)})(r,e,t),action:async(e,t)=>(0,u.reducer)(e,t),pending:null,last:null,onRouterTransitionStart:null!==t&&"function"==typeof t.onRouterTransitionStart?t.onRouterTransitionStart:null};if(null!==h)throw Object.defineProperty(Error("Internal Next.js Error: createMutableActionQueue was called more than once"),"__NEXT_ERROR_CODE",{value:"E624",enumerable:!1,configurable:!0});return h=r,r}function y(){return null!==h?h.state:null}function b(){return null!==h?h.onRouterTransitionStart:null}function v(e,t,r,u){let l=new URL((0,i.addBasePath)(e),location.href);(0,f.setLinkForCurrentNavigation)(u);let o=b();null!==o&&o(e,t),(0,a.dispatchAppRouterAction)({type:n.ACTION_NAVIGATE,url:l,isExternalUrl:(0,c.isExternalURL)(l),locationSearch:location.search,shouldScroll:r,navigateType:t,allowAliasing:!0})}function g(e,t){let r=b();null!==r&&r(e,"traverse"),(0,a.dispatchAppRouterAction)({type:n.ACTION_RESTORE,url:new URL(e),tree:t})}let m={back:()=>window.history.back(),forward:()=>window.history.forward(),prefetch:(e,t)=>{let r=function(){if(null===h)throw Object.defineProperty(Error("Internal Next.js error: Router action dispatched before initialization."),"__NEXT_ERROR_CODE",{value:"E668",enumerable:!1,configurable:!0});return h}(),u=(0,c.createPrefetchURL)(e);if(null!==u){var l;(0,s.prefetchReducer)(r.state,{type:n.ACTION_PREFETCH,url:u,kind:null!=(l=null==t?void 0:t.kind)?l:n.PrefetchKind.FULL})}},replace:(e,t)=>{(0,l.startTransition)(()=>{var r;v(e,"replace",null==(r=null==t?void 0:t.scroll)||r,null)})},push:(e,t)=>{(0,l.startTransition)(()=>{var r;v(e,"push",null==(r=null==t?void 0:t.scroll)||r,null)})},refresh:()=>{(0,l.startTransition)(()=>{(0,a.dispatchAppRouterAction)({type:n.ACTION_REFRESH,origin:window.location.origin})})},hmrRefresh:()=>{throw Object.defineProperty(Error("hmrRefresh can only be used in development mode. Please use refresh instead."),"__NEXT_ERROR_CODE",{value:"E485",enumerable:!1,configurable:!0})}};window.next&&(window.next.router=m),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6698:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{isRecoverableError:function(){return i},onRecoverableError:function(){return c}});let n=r(8229),u=r(5262),l=n._(r(5807)),o=r(1646),a=new WeakSet;function i(e){return a.has(e)}let c=(e,t)=>{let r=(0,l.default)(e)&&"cause"in e?e.cause:e;(0,u.isBailoutToCSRError)(r)||(0,o.reportGlobalError)(r)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6825:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getRedirectError:function(){return o},getRedirectStatusCodeFromError:function(){return f},getRedirectTypeFromError:function(){return s},getURLFromRedirectError:function(){return c},permanentRedirect:function(){return i},redirect:function(){return a}});let n=r(4420),u=r(2210),l=void 0;function o(e,t,r){void 0===r&&(r=n.RedirectStatusCode.TemporaryRedirect);let l=Object.defineProperty(Error(u.REDIRECT_ERROR_CODE),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});return l.digest=u.REDIRECT_ERROR_CODE+";"+t+";"+e+";"+r+";",l}function a(e,t){var r;throw null!=t||(t=(null==l||null==(r=l.getStore())?void 0:r.isAction)?u.RedirectType.push:u.RedirectType.replace),o(e,t,n.RedirectStatusCode.TemporaryRedirect)}function i(e,t){throw void 0===t&&(t=u.RedirectType.replace),o(e,t,n.RedirectStatusCode.PermanentRedirect)}function c(e){return(0,u.isRedirectError)(e)?e.digest.split(";").slice(2,-2).join(";"):null}function s(e){if(!(0,u.isRedirectError)(e))throw Object.defineProperty(Error("Not a redirect error"),"__NEXT_ERROR_CODE",{value:"E260",enumerable:!1,configurable:!0});return e.digest.split(";",2)[1]}function f(e){if(!(0,u.isRedirectError)(e))throw Object.defineProperty(Error("Not a redirect error"),"__NEXT_ERROR_CODE",{value:"E260",enumerable:!1,configurable:!0});return Number(e.digest.split(";").at(-2))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6897:(e,t)=>{"use strict";var r=Symbol.for("react.transitional.element");function n(e,t,n){var u=null;if(void 0!==n&&(u=""+n),void 0!==t.key&&(u=""+t.key),"key"in t)for(var l in n={},t)"key"!==l&&(n[l]=t[l]);else n=t;return{$$typeof:r,type:e,key:u,ref:void 0!==(t=n.ref)?t:null,props:n}}t.Fragment=Symbol.for("react.fragment"),t.jsx=n,t.jsxs=n},6966:(e,t,r)=>{"use strict";function n(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(n=function(e){return e?r:t})(e)}function u(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=n(t);if(r&&r.has(e))return r.get(e);var u={__proto__:null},l=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if("default"!==o&&Object.prototype.hasOwnProperty.call(e,o)){var a=l?Object.getOwnPropertyDescriptor(e,o):null;a&&(a.get||a.set)?Object.defineProperty(u,o,a):u[o]=e[o]}return u.default=e,r&&r.set(e,u),u}r.r(t),r.d(t,{_:()=>u})},6975:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTTPAccessFallbackBoundary",{enumerable:!0,get:function(){return s}});let n=r(6966),u=r(5155),l=n._(r(2115)),o=r(9921),a=r(6494);r(3230);let i=r(5227);class c extends l.default.Component{componentDidCatch(){}static getDerivedStateFromError(e){if((0,a.isHTTPAccessFallbackError)(e))return{triggeredStatus:(0,a.getAccessFallbackHTTPStatus)(e)};throw e}static getDerivedStateFromProps(e,t){return e.pathname!==t.previousPathname&&t.triggeredStatus?{triggeredStatus:void 0,previousPathname:e.pathname}:{triggeredStatus:t.triggeredStatus,previousPathname:e.pathname}}render(){let{notFound:e,forbidden:t,unauthorized:r,children:n}=this.props,{triggeredStatus:l}=this.state,o={[a.HTTPAccessErrorStatus.NOT_FOUND]:e,[a.HTTPAccessErrorStatus.FORBIDDEN]:t,[a.HTTPAccessErrorStatus.UNAUTHORIZED]:r};if(l){let i=l===a.HTTPAccessErrorStatus.NOT_FOUND&&e,c=l===a.HTTPAccessErrorStatus.FORBIDDEN&&t,s=l===a.HTTPAccessErrorStatus.UNAUTHORIZED&&r;return i||c||s?(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)("meta",{name:"robots",content:"noindex"}),!1,o[l]]}):n}return n}constructor(e){super(e),this.state={triggeredStatus:void 0,previousPathname:e.pathname}}}function s(e){let{notFound:t,forbidden:r,unauthorized:n,children:a}=e,s=(0,o.useUntrackedPathname)(),f=(0,l.useContext)(i.MissingSlotContext);return t||r||n?(0,u.jsx)(c,{pathname:s,notFound:t,forbidden:r,unauthorized:n,missingSlots:f,children:a}):(0,u.jsx)(u.Fragment,{children:a})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7102:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hasBasePath",{enumerable:!0,get:function(){return u}});let n=r(1747);function u(e){return(0,n.pathHasPrefix)(e,"")}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7197:(e,t,r)=>{"use strict";e.exports=r(9062)},7205:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createRenderSearchParamsFromClient",{enumerable:!0,get:function(){return n}});let n=r(8324).createRenderSearchParamsFromClient;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7276:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{normalizeAppPath:function(){return l},normalizeRscURL:function(){return o}});let n=r(9133),u=r(8291);function l(e){return(0,n.ensureLeadingSlash)(e.split("/").reduce((e,t,r,n)=>!t||(0,u.isGroupSegment)(t)||"@"===t[0]||("page"===t||"route"===t)&&r===n.length-1?e:e+"/"+t,""))}function o(e){return e.replace(/\.rsc($|\?)/,"$1")}},7442:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"applyRouterStatePatchToTree",{enumerable:!0,get:function(){return function e(t,r,n,i){let c,[s,f,d,p,h]=r;if(1===t.length){let e=a(r,n);return(0,o.addRefreshMarkerToActiveParallelSegments)(e,i),e}let[_,y]=t;if(!(0,l.matchSegment)(_,s))return null;if(2===t.length)c=a(f[y],n);else if(null===(c=e((0,u.getNextFlightSegmentPath)(t),f[y],n,i)))return null;let b=[t[0],{...f,[y]:c},d,p];return h&&(b[4]=!0),(0,o.addRefreshMarkerToActiveParallelSegments)(b,i),b}}});let n=r(8291),u=r(2561),l=r(1127),o=r(4908);function a(e,t){let[r,u]=e,[o,i]=t;if(o===n.DEFAULT_SEGMENT_KEY&&r!==n.DEFAULT_SEGMENT_KEY)return e;if((0,l.matchSegment)(r,o)){let t={};for(let e in u)void 0!==i[e]?t[e]=a(u[e],i[e]):t[e]=u[e];for(let e in i)t[e]||(t[e]=i[e]);let n=[r,t];return e[2]&&(n[2]=e[2]),e[3]&&(n[3]=e[3]),e[4]&&(n[4]=e[4]),n}return t}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7541:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{describeHasCheckingStringProperty:function(){return u},describeStringPropertyAccess:function(){return n},wellKnownProperties:function(){return l}});let r=/^[A-Za-z_$][A-Za-z0-9_$]*$/;function n(e,t){return r.test(t)?"`"+e+"."+t+"`":"`"+e+"["+JSON.stringify(t)+"]`"}function u(e,t){let r=JSON.stringify(t);return"`Reflect.has("+e+", "+r+")`, `"+r+" in "+e+"`, or similar"}let l=new Set(["hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toString","valueOf","toLocaleString","then","catch","finally","status","displayName","_debugInfo","toJSON","$$typeof","__esModule"])},7555:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return w}});let n=r(8229),u=r(6966),l=r(5155),o=r(9818),a=u._(r(2115)),i=n._(r(7650)),c=r(5227),s=r(8586),f=r(1822),d=r(6614),p=r(1127),h=r(6539),_=r(686),y=r(6975),b=r(5637),v=r(4108),g=r(1027),m=r(89);r(7276);let R=i.default.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,E=["bottom","height","left","right","top","width","x","y"];function O(e,t){let r=e.getBoundingClientRect();return r.top>=0&&r.top<=t}class P extends a.default.Component{componentDidMount(){this.handlePotentialScroll()}componentDidUpdate(){this.props.focusAndScrollRef.apply&&this.handlePotentialScroll()}render(){return this.props.children}constructor(...e){super(...e),this.handlePotentialScroll=()=>{let{focusAndScrollRef:e,segmentPath:t}=this.props;if(e.apply){if(0!==e.segmentPaths.length&&!e.segmentPaths.some(e=>t.every((t,r)=>(0,p.matchSegment)(t,e[r]))))return;let r=null,n=e.hashFragment;if(n&&(r=function(e){var t;return"top"===e?document.body:null!=(t=document.getElementById(e))?t:document.getElementsByName(e)[0]}(n)),r||(r=(0,R.findDOMNode)(this)),!(r instanceof Element))return;for(;!(r instanceof HTMLElement)||function(e){if(["sticky","fixed"].includes(getComputedStyle(e).position))return!0;let t=e.getBoundingClientRect();return E.every(e=>0===t[e])}(r);){if(null===r.nextElementSibling)return;r=r.nextElementSibling}e.apply=!1,e.hashFragment=null,e.segmentPaths=[],(0,h.disableSmoothScrollDuringRouteTransition)(()=>{if(n)return void r.scrollIntoView();let e=document.documentElement,t=e.clientHeight;!O(r,t)&&(e.scrollTop=0,O(r,t)||r.scrollIntoView())},{dontForceLayout:!0,onlyHashChange:e.onlyHashChange}),e.onlyHashChange=!1,r.focus()}}}}function j(e){let{segmentPath:t,children:r}=e,n=(0,a.useContext)(c.GlobalLayoutRouterContext);if(!n)throw Object.defineProperty(Error("invariant global layout router not mounted"),"__NEXT_ERROR_CODE",{value:"E473",enumerable:!1,configurable:!0});return(0,l.jsx)(P,{segmentPath:t,focusAndScrollRef:n.focusAndScrollRef,children:r})}function T(e){let{tree:t,segmentPath:r,cacheNode:n,url:u}=e,i=(0,a.useContext)(c.GlobalLayoutRouterContext);if(!i)throw Object.defineProperty(Error("invariant global layout router not mounted"),"__NEXT_ERROR_CODE",{value:"E473",enumerable:!1,configurable:!0});let{tree:d}=i,h=null!==n.prefetchRsc?n.prefetchRsc:n.rsc,_=(0,a.useDeferredValue)(n.rsc,h),y="object"==typeof _&&null!==_&&"function"==typeof _.then?(0,a.use)(_):_;if(!y){let e=n.lazyData;if(null===e){let t=function e(t,r){if(t){let[n,u]=t,l=2===t.length;if((0,p.matchSegment)(r[0],n)&&r[1].hasOwnProperty(u)){if(l){let t=e(void 0,r[1][u]);return[r[0],{...r[1],[u]:[t[0],t[1],t[2],"refetch"]}]}return[r[0],{...r[1],[u]:e(t.slice(2),r[1][u])}]}}return r}(["",...r],d),l=(0,v.hasInterceptionRouteInCurrentTree)(d),c=Date.now();n.lazyData=e=(0,s.fetchServerResponse)(new URL(u,location.origin),{flightRouterState:t,nextUrl:l?i.nextUrl:null}).then(e=>((0,a.startTransition)(()=>{(0,g.dispatchAppRouterAction)({type:o.ACTION_SERVER_PATCH,previousTree:d,serverResponse:e,navigatedAt:c})}),e)),(0,a.use)(e)}(0,a.use)(f.unresolvedThenable)}return(0,l.jsx)(c.LayoutRouterContext.Provider,{value:{parentTree:t,parentCacheNode:n,parentSegmentPath:r,url:u},children:y})}function S(e){let t,{loading:r,children:n}=e;if(t="object"==typeof r&&null!==r&&"function"==typeof r.then?(0,a.use)(r):r){let e=t[0],r=t[1],u=t[2];return(0,l.jsx)(a.Suspense,{fallback:(0,l.jsxs)(l.Fragment,{children:[r,u,e]}),children:n})}return(0,l.jsx)(l.Fragment,{children:n})}function M(e){let{children:t}=e;return(0,l.jsx)(l.Fragment,{children:t})}function w(e){let{parallelRouterKey:t,error:r,errorStyles:n,errorScripts:u,templateStyles:o,templateScripts:i,template:s,notFound:f,forbidden:p,unauthorized:h,gracefullyDegrade:v,segmentViewBoundaries:g}=e,R=(0,a.useContext)(c.LayoutRouterContext);if(!R)throw Object.defineProperty(Error("invariant expected layout router to be mounted"),"__NEXT_ERROR_CODE",{value:"E56",enumerable:!1,configurable:!0});let{parentTree:E,parentCacheNode:O,parentSegmentPath:P,url:w}=R,x=O.parallelRoutes,C=x.get(t);C||(C=new Map,x.set(t,C));let A=E[0],N=null===P?[t]:P.concat([A,t]),D=E[1][t],U=D[0],I=(0,b.createRouterCacheKey)(U,!0),L=(0,m.useRouterBFCache)(D,I),k=[];do{let e=L.tree,t=L.stateKey,a=e[0],g=(0,b.createRouterCacheKey)(a),m=C.get(g);if(void 0===m){let e={lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:-1};m=e,C.set(g,e)}let R=v?M:d.ErrorBoundary,E=O.loading,P=(0,l.jsxs)(c.TemplateContext.Provider,{value:(0,l.jsxs)(j,{segmentPath:N,children:[(0,l.jsx)(R,{errorComponent:r,errorStyles:n,errorScripts:u,children:(0,l.jsx)(S,{loading:E,children:(0,l.jsx)(y.HTTPAccessFallbackBoundary,{notFound:f,forbidden:p,unauthorized:h,children:(0,l.jsxs)(_.RedirectBoundary,{children:[(0,l.jsx)(T,{url:w,tree:e,cacheNode:m,segmentPath:N}),null]})})})}),null]}),children:[o,i,s]},t);k.push(P),L=L.next}while(null!==L);return k}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7568:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ServerInsertedHTMLContext:function(){return u},useServerInsertedHTML:function(){return l}});let n=r(6966)._(r(2115)),u=n.default.createContext(null);function l(e){let t=(0,n.useContext)(u);t&&t(e)}},7599:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"unstable_rethrow",{enumerable:!0,get:function(){return n}});let n=r(7865).unstable_rethrow;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7650:(e,t,r)=>{"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=r(8730)},7755:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{INTERCEPTION_ROUTE_MARKERS:function(){return u},extractInterceptionRouteInformation:function(){return o},isInterceptionRouteAppPath:function(){return l}});let n=r(7276),u=["(..)(..)","(.)","(..)","(...)"];function l(e){return void 0!==e.split("/").find(e=>u.find(t=>e.startsWith(t)))}function o(e){let t,r,l;for(let n of e.split("/"))if(r=u.find(e=>n.startsWith(e))){[t,l]=e.split(r,2);break}if(!t||!r||!l)throw Object.defineProperty(Error("Invalid interception route: "+e+". Must be in the format //(..|...|..)(..)/"),"__NEXT_ERROR_CODE",{value:"E269",enumerable:!1,configurable:!0});switch(t=(0,n.normalizeAppPath)(t),r){case"(.)":l="/"===t?"/"+l:t+"/"+l;break;case"(..)":if("/"===t)throw Object.defineProperty(Error("Invalid interception route: "+e+". Cannot use (..) marker at the root level, use (.) instead."),"__NEXT_ERROR_CODE",{value:"E207",enumerable:!1,configurable:!0});l=t.split("/").slice(0,-1).concat(l).join("/");break;case"(...)":l="/"+l;break;case"(..)(..)":let o=t.split("/");if(o.length<=2)throw Object.defineProperty(Error("Invalid interception route: "+e+". Cannot use (..)(..) marker at the root level or one level up."),"__NEXT_ERROR_CODE",{value:"E486",enumerable:!1,configurable:!0});l=o.slice(0,-2).concat(l).join("/");break;default:throw Object.defineProperty(Error("Invariant: unexpected marker"),"__NEXT_ERROR_CODE",{value:"E112",enumerable:!1,configurable:!0})}return{interceptingRoute:t,interceptedRoute:l}}},7760:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"AppRouterAnnouncer",{enumerable:!0,get:function(){return o}});let n=r(2115),u=r(7650),l="next-route-announcer";function o(e){let{tree:t}=e,[r,o]=(0,n.useState)(null);(0,n.useEffect)(()=>(o(function(){var e;let t=document.getElementsByName(l)[0];if(null==t||null==(e=t.shadowRoot)?void 0:e.childNodes[0])return t.shadowRoot.childNodes[0];{let e=document.createElement(l);e.style.cssText="position:absolute";let t=document.createElement("div");return t.ariaLive="assertive",t.id="__next-route-announcer__",t.role="alert",t.style.cssText="position:absolute;border:0;height:1px;margin:-1px;padding:0;width:1px;clip:rect(0 0 0 0);overflow:hidden;white-space:nowrap;word-wrap:normal",e.attachShadow({mode:"open"}).appendChild(t),document.body.appendChild(e),t}}()),()=>{let e=document.getElementsByTagName(l)[0];(null==e?void 0:e.isConnected)&&document.body.removeChild(e)}),[]);let[a,i]=(0,n.useState)(""),c=(0,n.useRef)(void 0);return(0,n.useEffect)(()=>{let e="";if(document.title)e=document.title;else{let t=document.querySelector("h1");t&&(e=t.innerText||t.textContent||"")}void 0!==c.current&&c.current!==e&&i(e),c.current=e},[t]),r?(0,u.createPortal)(a,r):null}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7801:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"serverPatchReducer",{enumerable:!0,get:function(){return s}});let n=r(1139),u=r(7442),l=r(9234),o=r(3894),a=r(878),i=r(3507),c=r(6158);function s(e,t){let{serverResponse:{flightData:r,canonicalUrl:s},navigatedAt:f}=t,d={};if(d.preserveCustomHistoryState=!1,"string"==typeof r)return(0,o.handleExternalUrl)(e,d,r,e.pushRef.pendingPush);let p=e.tree,h=e.cache;for(let t of r){let{segmentPath:r,tree:i}=t,_=(0,u.applyRouterStatePatchToTree)(["",...r],p,i,e.canonicalUrl);if(null===_)return e;if((0,l.isNavigatingToNewRootLayout)(p,_))return(0,o.handleExternalUrl)(e,d,e.canonicalUrl,e.pushRef.pendingPush);let y=s?(0,n.createHrefFromUrl)(s):void 0;y&&(d.canonicalUrl=y);let b=(0,c.createEmptyCacheNode)();(0,a.applyFlightData)(f,h,b,t),d.patchedTree=_,d.cache=b,h=b,p=_}return(0,i.handleMutable)(e,d)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7829:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createRenderParamsFromClient",{enumerable:!0,get:function(){return l}});let n=r(7541),u=new WeakMap;function l(e){let t=u.get(e);if(t)return t;let r=Promise.resolve(e);return u.set(e,r),Object.keys(e).forEach(t=>{n.wellKnownProperties.has(t)||(r[t]=e[t])}),r}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7865:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"unstable_rethrow",{enumerable:!0,get:function(){return function e(t){if((0,u.isNextRouterError)(t)||(0,n.isBailoutToCSRError)(t))throw t;t instanceof Error&&"cause"in t&&e(t.cause)}}});let n=r(5262),u=r(2858);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8175:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"IconMark",{enumerable:!0,get:function(){return n}}),r(5155);let n=()=>null},8229:(e,t,r)=>{"use strict";function n(e){return e&&e.__esModule?e:{default:e}}r.r(t),r.d(t,{_:()=>n})},8287:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{METADATA_BOUNDARY_NAME:function(){return r},OUTLET_BOUNDARY_NAME:function(){return u},VIEWPORT_BOUNDARY_NAME:function(){return n}});let r="__next_metadata_boundary__",n="__next_viewport_boundary__",u="__next_outlet_boundary__"},8291:(e,t)=>{"use strict";function r(e){return"("===e[0]&&e.endsWith(")")}function n(e){return e.startsWith("@")&&"@children"!==e}function u(e,t){if(e.includes(l)){let e=JSON.stringify(t);return"{}"!==e?l+"?"+e:l}return e}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{DEFAULT_SEGMENT_KEY:function(){return o},PAGE_SEGMENT_KEY:function(){return l},addSearchParamsIfPageSegment:function(){return u},isGroupSegment:function(){return r},isParallelRouteSegment:function(){return n}});let l="__PAGE__",o="__DEFAULT__"},8324:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createRenderSearchParamsFromClient",{enumerable:!0,get:function(){return l}});let n=r(7541),u=new WeakMap;function l(e){let t=u.get(e);if(t)return t;let r=Promise.resolve(e);return u.set(e,r),Object.keys(e).forEach(t=>{n.wellKnownProperties.has(t)||(r[t]=e[t])}),r}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8393:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return o}});let n=r(5155),u=r(1799),l={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},text:{fontSize:"14px",fontWeight:400,lineHeight:"28px",margin:"0 8px"}},o=function(e){let{error:t}=e,r=null==t?void 0:t.digest;return(0,n.jsxs)("html",{id:"__next_error__",children:[(0,n.jsx)("head",{}),(0,n.jsxs)("body",{children:[(0,n.jsx)(u.HandleISRError,{error:t}),(0,n.jsx)("div",{style:l.error,children:(0,n.jsxs)("div",{children:[(0,n.jsxs)("h2",{style:l.text,children:["Application error: a ",r?"server":"client","-side exception has occurred while loading ",window.location.hostname," (see the"," ",r?"server logs":"browser console"," for more information)."]}),r?(0,n.jsx)("p",{style:l.text,children:"Digest: "+r}):null]})})]})]})};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8527:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"notFound",{enumerable:!0,get:function(){return u}});let n=""+r(6494).HTTP_ERROR_FALLBACK_ERROR_CODE+";404";function u(){let e=Object.defineProperty(Error(n),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});throw e.digest=n,e}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8586:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{createFetch:function(){return y},createFromNextReadableStream:function(){return b},fetchServerResponse:function(){return _},urlToUrlWithoutFlightMarker:function(){return d}});let n=r(7197),u=r(3269),l=r(3806),o=r(1818),a=r(9818),i=r(2561),c=r(5624),s=r(8969),f=n.createFromReadableStream;function d(e){let t=new URL(e,location.origin);if(t.searchParams.delete(u.NEXT_RSC_UNION_QUERY),t.pathname.endsWith(".txt")){let{pathname:e}=t,r=e.endsWith("/index.txt")?10:4;t.pathname=e.slice(0,-r)}return t}function p(e){return{flightData:d(e).toString(),canonicalUrl:void 0,couldBeIntercepted:!1,prerendered:!1,postponed:!1,staleTime:-1}}let h=new AbortController;async function _(e,t){let{flightRouterState:r,nextUrl:n,prefetchKind:l}=t,o={[u.RSC_HEADER]:"1",[u.NEXT_ROUTER_STATE_TREE_HEADER]:(0,i.prepareFlightRouterStateForRequest)(r,t.isHmrRefresh)};l===a.PrefetchKind.AUTO&&(o[u.NEXT_ROUTER_PREFETCH_HEADER]="1"),n&&(o[u.NEXT_URL]=n);try{var s;let t=l?l===a.PrefetchKind.TEMPORARY?"high":"low":"auto";(e=new URL(e)).pathname.endsWith("/")?e.pathname+="index.txt":e.pathname+=".txt";let r=await y(e,o,t,h.signal),n=d(r.url),f=r.redirected?n:void 0,_=r.headers.get("content-type")||"",v=!!(null==(s=r.headers.get("vary"))?void 0:s.includes(u.NEXT_URL)),g=!!r.headers.get(u.NEXT_DID_POSTPONE_HEADER),m=r.headers.get(u.NEXT_ROUTER_STALE_TIME_HEADER),R=null!==m?1e3*parseInt(m,10):-1,E=_.startsWith(u.RSC_CONTENT_TYPE_HEADER);if(E||(E=_.startsWith("text/plain")),!E||!r.ok||!r.body)return e.hash&&(n.hash=e.hash),p(n.toString());let O=g?function(e){let t=e.getReader();return new ReadableStream({async pull(e){for(;;){let{done:r,value:n}=await t.read();if(!r){e.enqueue(n);continue}return}}})}(r.body):r.body,P=await b(O);if((0,c.getAppBuildId)()!==P.b)return p(r.url);return{flightData:(0,i.normalizeFlightData)(P.f),canonicalUrl:f,couldBeIntercepted:v,prerendered:P.S,postponed:g,staleTime:R}}catch(t){return h.signal.aborted||console.error("Failed to fetch RSC payload for "+e+". Falling back to browser navigation.",t),{flightData:e.toString(),canonicalUrl:void 0,couldBeIntercepted:!1,prerendered:!1,postponed:!1,staleTime:-1}}}async function y(e,t,r,n){let l=new URL(e);(0,s.setCacheBustingSearchParam)(l,t);let o=await fetch(l,{credentials:"same-origin",headers:t,priority:r||void 0,signal:n}),a=o.redirected,i=new URL(o.url,l);return i.searchParams.delete(u.NEXT_RSC_UNION_QUERY),{url:i.href,redirected:a,ok:o.ok,headers:o.headers,body:o.body,status:o.status}}function b(e){return f(e,{callServer:l.callServer,findSourceMapURL:o.findSourceMapURL})}window.addEventListener("pagehide",()=>{h.abort()}),window.addEventListener("pageshow",()=>{h=new AbortController}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8709:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"serverActionReducer",{enumerable:!0,get:function(){return w}});let n=r(3806),u=r(1818),l=r(3269),o=r(7197),a=r(9818),i=r(1315),c=r(1139),s=r(3894),f=r(7442),d=r(9234),p=r(3507),h=r(4758),_=r(6158),y=r(4108),b=r(6375),v=r(4908),g=r(2561),m=r(6825),R=r(2210),E=r(1518),O=r(4882),P=r(7102),j=r(2816);r(6005);let T=o.createFromFetch;async function S(e,t,r){let a,c,s,f,{actionId:d,actionArgs:p}=r,h=(0,o.createTemporaryReferenceSet)(),_=(0,j.extractInfoFromServerReferenceId)(d),y="use-cache"===_.type?(0,j.omitUnusedArgs)(p,_):p,b=await (0,o.encodeReply)(y,{temporaryReferences:h}),v=await fetch(e.canonicalUrl,{method:"POST",headers:{Accept:l.RSC_CONTENT_TYPE_HEADER,[l.ACTION_HEADER]:d,[l.NEXT_ROUTER_STATE_TREE_HEADER]:(0,g.prepareFlightRouterStateForRequest)(e.tree),...{},...t?{[l.NEXT_URL]:t}:{}},body:b});if("1"===v.headers.get(l.NEXT_ACTION_NOT_FOUND_HEADER))throw Object.defineProperty(Error('Server Action "'+d+'" was not found on the server. \nRead more: https://nextjs.org/docs/messages/failed-to-find-server-action'),"__NEXT_ERROR_CODE",{value:"E715",enumerable:!1,configurable:!0});let m=v.headers.get("x-action-redirect"),[E,O]=(null==m?void 0:m.split(";"))||[];switch(O){case"push":a=R.RedirectType.push;break;case"replace":a=R.RedirectType.replace;break;default:a=void 0}let P=!!v.headers.get(l.NEXT_IS_PRERENDER_HEADER);try{let e=JSON.parse(v.headers.get("x-action-revalidated")||"[[],0,0]");c={paths:e[0]||[],tag:!!e[1],cookie:e[2]}}catch(e){c=M}let S=E?(0,i.assignLocation)(E,new URL(e.canonicalUrl,window.location.href)):void 0,w=v.headers.get("content-type"),x=!!(w&&w.startsWith(l.RSC_CONTENT_TYPE_HEADER));if(!x&&!S)throw Object.defineProperty(Error(v.status>=400&&"text/plain"===w?await v.text():"An unexpected response was received from the server."),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});if(x){let e=await T(Promise.resolve(v),{callServer:n.callServer,findSourceMapURL:u.findSourceMapURL,temporaryReferences:h});s=S?void 0:e.a,f=(0,g.normalizeFlightData)(e.f)}else s=void 0,f=void 0;return{actionResult:s,actionFlightData:f,redirectLocation:S,redirectType:a,revalidatedParts:c,isPrerender:P}}let M={paths:[],tag:!1,cookie:!1};function w(e,t){let{resolve:r,reject:n}=t,u={},l=e.tree;u.preserveCustomHistoryState=!1;let o=e.nextUrl&&(0,y.hasInterceptionRouteInCurrentTree)(e.tree)?e.nextUrl:null,i=Date.now();return S(e,o,t).then(async y=>{let g,{actionResult:j,actionFlightData:T,redirectLocation:S,redirectType:M,isPrerender:w,revalidatedParts:x}=y;if(S&&(M===R.RedirectType.replace?(e.pushRef.pendingPush=!1,u.pendingPush=!1):(e.pushRef.pendingPush=!0,u.pendingPush=!0),u.canonicalUrl=g=(0,c.createHrefFromUrl)(S,!1)),!T)return(r(j),S)?(0,s.handleExternalUrl)(e,u,S.href,e.pushRef.pendingPush):e;if("string"==typeof T)return r(j),(0,s.handleExternalUrl)(e,u,T,e.pushRef.pendingPush);let C=x.paths.length>0||x.tag||x.cookie;for(let n of T){let{tree:a,seedData:c,head:p,isRootRender:y}=n;if(!y)return console.log("SERVER ACTION APPLY FAILED"),r(j),e;let m=(0,f.applyRouterStatePatchToTree)([""],l,a,g||e.canonicalUrl);if(null===m)return r(j),(0,b.handleSegmentMismatch)(e,t,a);if((0,d.isNavigatingToNewRootLayout)(l,m))return r(j),(0,s.handleExternalUrl)(e,u,g||e.canonicalUrl,e.pushRef.pendingPush);if(null!==c){let t=c[1],r=(0,_.createEmptyCacheNode)();r.rsc=t,r.prefetchRsc=null,r.loading=c[3],(0,h.fillLazyItemsTillLeafWithHead)(i,r,void 0,a,c,p,void 0),u.cache=r,u.prefetchCache=new Map,C&&await (0,v.refreshInactiveParallelSegments)({navigatedAt:i,state:e,updatedTree:m,updatedCache:r,includeNextUrl:!!o,canonicalUrl:u.canonicalUrl||e.canonicalUrl})}u.patchedTree=m,l=m}return S&&g?(C||((0,E.createSeededPrefetchCacheEntry)({url:S,data:{flightData:T,canonicalUrl:void 0,couldBeIntercepted:!1,prerendered:!1,postponed:!1,staleTime:-1},tree:e.tree,prefetchCache:e.prefetchCache,nextUrl:e.nextUrl,kind:w?a.PrefetchKind.FULL:a.PrefetchKind.AUTO}),u.prefetchCache=e.prefetchCache),n((0,m.getRedirectError)((0,P.hasBasePath)(g)?(0,O.removeBasePath)(g):g,M||R.RedirectType.push))):r(j),(0,p.handleMutable)(e,u)},t=>(n(t),e))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8726:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"computeCacheBustingSearchParam",{enumerable:!0,get:function(){return u}});let n=r(3942);function u(e,t,r,u){return void 0===e&&void 0===t&&void 0===r&&void 0===u?"":(0,n.hexHash)([e||"0",t||"0",r||"0",u||"0"].join(","))}},8730:(e,t,r)=>{"use strict";var n=r(2115);function u(e){var t="https://react.dev/errors/"+e;if(1{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{handleHardNavError:function(){return u},useNavFailureHandler:function(){return l}}),r(2115);let n=r(1139);function u(e){return!!e&&!!window.next.__pendingUrl&&(0,n.createHrefFromUrl)(new URL(window.location.href))!==(0,n.createHrefFromUrl)(window.next.__pendingUrl)&&(console.error("Error occurred during navigation, falling back to hard navigation",e),window.location.href=window.next.__pendingUrl.toString(),!0)}function l(){}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8946:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{computeChangedPath:function(){return c},extractPathFromFlightRouterState:function(){return i},getSelectedParams:function(){return function e(t,r){for(let n of(void 0===r&&(r={}),Object.values(t[1]))){let t=n[0],l=Array.isArray(t),o=l?t[1]:t;!o||o.startsWith(u.PAGE_SEGMENT_KEY)||(l&&("c"===t[2]||"oc"===t[2])?r[t[0]]=t[1].split("/"):l&&(r[t[0]]=t[1]),r=e(n,r))}return r}}});let n=r(7755),u=r(8291),l=r(1127),o=e=>"string"==typeof e?"children"===e?"":e:e[1];function a(e){return e.reduce((e,t)=>{let r;return""===(t="/"===(r=t)[0]?r.slice(1):r)||(0,u.isGroupSegment)(t)?e:e+"/"+t},"")||"/"}function i(e){var t;let r=Array.isArray(e[0])?e[0][1]:e[0];if(r===u.DEFAULT_SEGMENT_KEY||n.INTERCEPTION_ROUTE_MARKERS.some(e=>r.startsWith(e)))return;if(r.startsWith(u.PAGE_SEGMENT_KEY))return"";let l=[o(r)],c=null!=(t=e[1])?t:{},s=c.children?i(c.children):void 0;if(void 0!==s)l.push(s);else for(let[e,t]of Object.entries(c)){if("children"===e)continue;let r=i(t);void 0!==r&&l.push(r)}return a(l)}function c(e,t){let r=function e(t,r){let[u,a]=t,[c,s]=r,f=o(u),d=o(c);if(n.INTERCEPTION_ROUTE_MARKERS.some(e=>f.startsWith(e)||d.startsWith(e)))return"";if(!(0,l.matchSegment)(u,c)){var p;return null!=(p=i(r))?p:""}for(let t in a)if(s[t]){let r=e(a[t],s[t]);if(null!==r)return o(c)+"/"+r}return null}(e,t);return null==r||"/"===r?r:a(r.split("/"))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8969:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{setCacheBustingSearchParam:function(){return l},setCacheBustingSearchParamWithHash:function(){return o}});let n=r(8726),u=r(3269),l=(e,t)=>{o(e,(0,n.computeCacheBustingSearchParam)(t[u.NEXT_ROUTER_PREFETCH_HEADER],t[u.NEXT_ROUTER_SEGMENT_PREFETCH_HEADER],t[u.NEXT_ROUTER_STATE_TREE_HEADER],t[u.NEXT_URL]))},o=(e,t)=>{let r=e.search,n=(r.startsWith("?")?r.slice(1):r).split("&").filter(e=>e&&!e.startsWith(""+u.NEXT_RSC_UNION_QUERY+"="));t.length>0?n.push(u.NEXT_RSC_UNION_QUERY+"="+t):n.push(""+u.NEXT_RSC_UNION_QUERY),e.search=n.length?"?"+n.join("&"):""};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8999:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ReadonlyURLSearchParams:function(){return i.ReadonlyURLSearchParams},RedirectType:function(){return i.RedirectType},ServerInsertedHTMLContext:function(){return c.ServerInsertedHTMLContext},forbidden:function(){return i.forbidden},notFound:function(){return i.notFound},permanentRedirect:function(){return i.permanentRedirect},redirect:function(){return i.redirect},unauthorized:function(){return i.unauthorized},unstable_rethrow:function(){return i.unstable_rethrow},useParams:function(){return h},usePathname:function(){return d},useRouter:function(){return p},useSearchParams:function(){return f},useSelectedLayoutSegment:function(){return y},useSelectedLayoutSegments:function(){return _},useServerInsertedHTML:function(){return c.useServerInsertedHTML}});let n=r(2115),u=r(5227),l=r(886),o=r(708),a=r(8291),i=r(5618),c=r(7568),s=void 0;function f(){let e=(0,n.useContext)(l.SearchParamsContext);return(0,n.useMemo)(()=>e?new i.ReadonlyURLSearchParams(e):null,[e])}function d(){return null==s||s("usePathname()"),(0,n.useContext)(l.PathnameContext)}function p(){let e=(0,n.useContext)(u.AppRouterContext);if(null===e)throw Object.defineProperty(Error("invariant expected app router to be mounted"),"__NEXT_ERROR_CODE",{value:"E238",enumerable:!1,configurable:!0});return e}function h(){return null==s||s("useParams()"),(0,n.useContext)(l.PathParamsContext)}function _(e){void 0===e&&(e="children"),null==s||s("useSelectedLayoutSegments()");let t=(0,n.useContext)(u.LayoutRouterContext);return t?function e(t,r,n,u){let l;if(void 0===n&&(n=!0),void 0===u&&(u=[]),n)l=t[1][r];else{var i;let e=t[1];l=null!=(i=e.children)?i:Object.values(e)[0]}if(!l)return u;let c=l[0],s=(0,o.getSegmentValue)(c);return!s||s.startsWith(a.PAGE_SEGMENT_KEY)?u:(u.push(s),e(l,r,!1,u))}(t.parentTree,e):null}function y(e){void 0===e&&(e="children"),null==s||s("useSelectedLayoutSegment()");let t=_(e);if(!t||0===t.length)return null;let r="children"===e?t[0]:t[t.length-1];return r===a.DEFAULT_SEGMENT_KEY?null:r}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9062:(e,t,r)=>{"use strict";var n=r(7650),u={stream:!0},l=new Map;function o(e){var t=r(e);return"function"!=typeof t.then||"fulfilled"===t.status?null:(t.then(function(e){t.status="fulfilled",t.value=e},function(e){t.status="rejected",t.reason=e}),t)}function a(){}function i(e){for(var t=e[1],n=[],u=0;uf||35===f||114===f||120===f?(p=f,f=3,s++):(p=0,f=3);continue;case 2:44===(b=c[s++])?f=4:h=h<<4|(96c.length&&(b=-1)}var v=c.byteOffset+s;if(-1{"use strict";function r(e){return e.startsWith("/")?e:"/"+e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ensureLeadingSlash",{enumerable:!0,get:function(){return r}})},9154:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{prefetchQueue:function(){return l},prefetchReducer:function(){return o}});let n=r(2312),u=r(1518),l=new n.PromiseQueue(5),o=function(e,t){(0,u.prunePrefetchCache)(e.prefetchCache);let{url:r}=t;return(0,u.getOrCreatePrefetchCacheEntry)({url:r,nextUrl:e.nextUrl,prefetchCache:e.prefetchCache,kind:t.kind,tree:e.tree,allowAliasing:!0}),e};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9155:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{onCaughtError:function(){return s},onUncaughtError:function(){return f}});let n=r(8229),u=r(2858),l=r(5262),o=r(1646),a=r(6614),i=n._(r(8393)),c={decorateDevError:e=>e,handleClientError:()=>{},originConsoleError:console.error.bind(console)};function s(e,t){var r;let n,o=null==(r=t.errorBoundary)?void 0:r.constructor;if(n=n||o===a.ErrorBoundaryHandler&&t.errorBoundary.props.errorComponent===i.default)return f(e,t);(0,l.isBailoutToCSRError)(e)||(0,u.isNextRouterError)(e)||c.originConsoleError(e)}function f(e,t){(0,l.isBailoutToCSRError)(e)||(0,u.isNextRouterError)(e)||(0,o.reportGlobalError)(e)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9187:(e,t,r)=>{"use strict";function n(){throw Object.defineProperty(Error("`unauthorized()` is experimental and only allowed to be used when `experimental.authInterrupts` is enabled."),"__NEXT_ERROR_CODE",{value:"E411",enumerable:!1,configurable:!0})}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"unauthorized",{enumerable:!0,get:function(){return n}}),r(6494).HTTP_ERROR_FALLBACK_ERROR_CODE,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9234:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isNavigatingToNewRootLayout",{enumerable:!0,get:function(){return function e(t,r){let n=t[0],u=r[0];if(Array.isArray(n)&&Array.isArray(u)){if(n[0]!==u[0]||n[2]!==u[2])return!0}else if(n!==u)return!0;if(t[4])return!r[4];if(r[4])return!0;let l=Object.values(t[1])[0],o=Object.values(r[1])[0];return!l||!o||e(l,o)}}}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9509:(e,t,r)=>{"use strict";var n,u;e.exports=(null==(n=r.g.process)?void 0:n.env)&&"object"==typeof(null==(u=r.g.process)?void 0:u.env)?r.g.process:r(666)},9665:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{MetadataBoundary:function(){return l},OutletBoundary:function(){return a},ViewportBoundary:function(){return o}});let n=r(8287),u={[n.METADATA_BOUNDARY_NAME]:function(e){let{children:t}=e;return t},[n.VIEWPORT_BOUNDARY_NAME]:function(e){let{children:t}=e;return t},[n.OUTLET_BOUNDARY_NAME]:function(e){let{children:t}=e;return t}},l=u[n.METADATA_BOUNDARY_NAME.slice(0)],o=u[n.VIEWPORT_BOUNDARY_NAME.slice(0)],a=u[n.OUTLET_BOUNDARY_NAME.slice(0)];("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9726:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"reducer",{enumerable:!0,get:function(){return f}});let n=r(9818),u=r(3894),l=r(7801),o=r(4819),a=r(5542),i=r(9154),c=r(3612),s=r(8709),f=function(e,t){switch(t.type){case n.ACTION_NAVIGATE:return(0,u.navigateReducer)(e,t);case n.ACTION_SERVER_PATCH:return(0,l.serverPatchReducer)(e,t);case n.ACTION_RESTORE:return(0,o.restoreReducer)(e,t);case n.ACTION_REFRESH:return(0,a.refreshReducer)(e,t);case n.ACTION_HMR_REFRESH:return(0,c.hmrRefreshReducer)(e,t);case n.ACTION_PREFETCH:return(0,i.prefetchReducer)(e,t);case n.ACTION_SERVER_ACTION:return(0,s.serverActionReducer)(e,t);default:throw Object.defineProperty(Error("Unknown action"),"__NEXT_ERROR_CODE",{value:"E295",enumerable:!1,configurable:!0})}};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9818:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ACTION_HMR_REFRESH:function(){return a},ACTION_NAVIGATE:function(){return n},ACTION_PREFETCH:function(){return o},ACTION_REFRESH:function(){return r},ACTION_RESTORE:function(){return u},ACTION_SERVER_ACTION:function(){return i},ACTION_SERVER_PATCH:function(){return l},PrefetchCacheEntryStatus:function(){return s},PrefetchKind:function(){return c}});let r="refresh",n="navigate",u="restore",l="server-patch",o="prefetch",a="hmr-refresh",i="server-action";var c=function(e){return e.AUTO="auto",e.FULL="full",e.TEMPORARY="temporary",e}({}),s=function(e){return e.fresh="fresh",e.reusable="reusable",e.expired="expired",e.stale="stale",e}({});("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9837:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"InvariantError",{enumerable:!0,get:function(){return r}});class r extends Error{constructor(e,t){super("Invariant: "+(e.endsWith(".")?e:e+".")+" This is a bug in Next.js.",t),this.name="InvariantError"}}},9880:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"clearCacheNodeDataForSegmentPath",{enumerable:!0,get:function(){return function e(t,r,l){let o=l.length<=2,[a,i]=l,c=(0,u.createRouterCacheKey)(i),s=r.parallelRoutes.get(a),f=t.parallelRoutes.get(a);f&&f!==s||(f=new Map(s),t.parallelRoutes.set(a,f));let d=null==s?void 0:s.get(c),p=f.get(c);if(o){p&&p.lazyData&&p!==d||f.set(c,{lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:-1});return}if(!p||!d){p||f.set(c,{lazyData:null,rsc:null,prefetchRsc:null,head:null,prefetchHead:null,parallelRoutes:new Map,loading:null,navigatedAt:-1});return}return p===d&&(p={lazyData:p.lazyData,rsc:p.rsc,prefetchRsc:p.prefetchRsc,head:p.head,prefetchHead:p.prefetchHead,parallelRoutes:new Map(p.parallelRoutes),loading:p.loading},f.set(c,p)),e(p,d,(0,n.getNextFlightSegmentPath)(l))}}});let n=r(2561),u=r(5637);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9921:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"useUntrackedPathname",{enumerable:!0,get:function(){return l}});let n=r(2115),u=r(886);function l(){return(0,n.useContext)(u.PathnameContext)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/app/_not-found/page-7c3ecdf160dc3360.js b/keploy/pkg/service/load/out/_next/static/chunks/app/_not-found/page-7c3ecdf160dc3360.js deleted file mode 100644 index 132ec28..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/app/_not-found/page-7c3ecdf160dc3360.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[492],{1137:(e,t,l)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found/page",function(){return l(3303)}])},3303:(e,t,l)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return o}});let r=l(5155),n=l(6395),o=function(){return(0,r.jsx)("html",{children:(0,r.jsx)("body",{children:(0,r.jsx)(n.HTTPAccessErrorFallback,{status:404,message:"This page could not be found."})})})};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4502:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"styles",{enumerable:!0,get:function(){return l}});let l={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{display:"inline-block"},h1:{display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},h2:{fontSize:14,fontWeight:400,lineHeight:"49px",margin:0}};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6395:(e,t,l)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTTPAccessErrorFallback",{enumerable:!0,get:function(){return o}});let r=l(5155),n=l(4502);function o(e){let{status:t,message:l}=e;return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)("title",{children:t+": "+l}),(0,r.jsx)("div",{style:n.styles.error,children:(0,r.jsxs)("div",{children:[(0,r.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,r.jsx)("h1",{className:"next-error-h1",style:n.styles.h1,children:t}),(0,r.jsx)("div",{style:n.styles.desc,children:(0,r.jsx)("h2",{style:n.styles.h2,children:l})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{e.O(0,[441,964,358],()=>e(e.s=1137)),_N_E=e.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/app/layout-bc503d5738af696e.js b/keploy/pkg/service/load/out/_next/static/chunks/app/layout-bc503d5738af696e.js deleted file mode 100644 index 0c818e6..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/app/layout-bc503d5738af696e.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[177],{347:()=>{},1012:(e,r,t)=>{Promise.resolve().then(t.t.bind(t,8346,23)),Promise.resolve().then(t.t.bind(t,347,23)),Promise.resolve().then(t.bind(t,3313)),Promise.resolve().then(t.bind(t,2597))},2597:(e,r,t)=>{"use strict";t.d(r,{default:()=>i});var o=t(2115);let a=async()=>{if("serviceWorker"in navigator)try{let e=await navigator.serviceWorker.register("/sw.js",{scope:"/"});console.log("Service Worker registered successfully:",e),e.addEventListener("updatefound",()=>{let r=e.installing;r&&r.addEventListener("statechange",()=>{"installed"===r.state&&navigator.serviceWorker.controller&&confirm("A new version of the app is available. Would you like to refresh?")&&(r.postMessage({type:"SKIP_WAITING"}),window.location.reload())})}),navigator.serviceWorker.addEventListener("controllerchange",()=>{window.location.reload()})}catch(e){console.error("Service Worker registration failed:",e)}},n=async()=>{if("serviceWorker"in navigator)try{let e=await navigator.serviceWorker.getRegistration();e&&(await e.unregister(),sessionStorage.setItem("sw-unregistered",Date.now().toString()),console.log("Service Worker unregistered"),await s())}catch(e){console.error("Service Worker unregistration failed:",e)}},s=async()=>{if("caches"in window)try{let e=await caches.keys();await Promise.all(e.map(e=>(console.log("Clearing cache:",e),caches.delete(e)))),console.log("All caches cleared")}catch(e){console.error("Failed to clear caches:",e)}};function i(){return(0,o.useEffect)(()=>{if("serviceWorker"in navigator){let e=sessionStorage.getItem("sw-unregistered");e&&Date.now()-parseInt(e)>1e4&&sessionStorage.removeItem("sw-unregistered");let r=new URLSearchParams(window.location.search);if("true"===r.get("disable-sw")||"true"===localStorage.getItem("disable-sw")){console.log("\uD83D\uDEAB Service Worker disabled via parameter/localStorage"),n();return}{let e=r.get("dashboard");setTimeout(()=>{"true"!==localStorage.getItem("disable-sw")&&(console.log("✅ Registering Service Worker for offline support"),a())},!(e&&(()=>{try{let r=localStorage.getItem("klt-dashboards");if(r)return JSON.parse(r).some(r=>r.id===e)}catch(e){console.error("Failed to check stored dashboards:",e)}return!1})())&&e?2e3:100)}}},[]),null}},3313:(e,r,t)=>{"use strict";t.d(r,{default:()=>n});var o=t(5155),a=t(2115);function n(){let[e,r]=(0,a.useState)(!0);return((0,a.useEffect)(()=>{let e=()=>{r(navigator.onLine)};return r(navigator.onLine),window.addEventListener("online",e),window.addEventListener("offline",e),()=>{window.removeEventListener("online",e),window.removeEventListener("offline",e)}},[]),e)?null:(0,o.jsx)("div",{className:"fixed top-0 left-0 right-0 bg-yellow-500 text-black text-center py-2 px-4 z-50",children:(0,o.jsx)("span",{className:"text-sm font-medium",children:"\uD83D\uDCE1 You're currently offline. The app is running from cache."})})}},8346:e=>{e.exports={style:{fontFamily:"'Inter', 'Inter Fallback'",fontStyle:"normal"},className:"__className_e8ce0c"}}},e=>{e.O(0,[838,441,964,358],()=>e(e.s=1012)),_N_E=e.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/app/page-a3cd1a73fe99bbf3.js b/keploy/pkg/service/load/out/_next/static/chunks/app/page-a3cd1a73fe99bbf3.js deleted file mode 100644 index 1ed22c0..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/app/page-a3cd1a73fe99bbf3.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[974],{2769:(e,t,a)=>{Promise.resolve().then(a.bind(a,7704))},7704:(e,t,a)=>{"use strict";a.r(t),a.d(t,{default:()=>td});var s=a(5155),r=a(2115),n=a(5695),l=a(9708),i=a(2085),o=a(2596),d=a(9688);function c(){for(var e=arguments.length,t=Array(e),a=0;asvg]:px-3",sm:"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9"}},defaultVariants:{variant:"default",size:"default"}});function m(e){let{className:t,variant:a,size:r,asChild:n=!1,...i}=e,o=n?l.DX:"button";return(0,s.jsx)(o,{"data-slot":"button",className:c(u({variant:a,size:r,className:t})),...i})}let h=r.createContext(null);function x(){let e=r.useContext(h);if(!e)throw Error("useSidebar must be used within a SidebarProvider");return e}function g(e){let{children:t}=e,[a,n]=r.useState(!0);return(0,s.jsx)(h.Provider,{value:{isOpen:a,toggle:()=>n(!a)},children:(0,s.jsx)("div",{className:"flex h-screen",children:t})})}function p(e){let{children:t,className:a}=e,{isOpen:r}=x();return(0,s.jsx)("div",{"data-sidebar":!0,className:c("bg-sidebar border-r border-sidebar-border transition-all duration-300",r?"w-64":"w-16",a),children:t})}function f(e){let{children:t,className:a}=e;return(0,s.jsx)("div",{className:c("p-4 border-b border-sidebar-border",a),children:t})}function v(e){let{children:t,className:a}=e;return(0,s.jsx)("div",{className:c("flex-1 overflow-y-auto p-4",a),children:t})}function b(e){let{className:t}=e,{toggle:a}=x();return(0,s.jsx)(m,{onClick:a,variant:"ghost",size:"sm",className:c("p-1 cursor-pointer",t),children:(0,s.jsx)("svg",{className:"h-4 w-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 6h16M4 12h16M4 18h16"})})})}function j(e){let{children:t,className:a}=e;return(0,s.jsx)("main",{className:c("flex-1 overflow-auto",a),children:t})}var y=a(5604);let N=y.Ik({step_name:y.Yj(),step_count:y.ai().int().nonnegative(),step_failure:y.ai().int().nonnegative(),step_response_time:y.YO(y.ai().nonnegative()),step_bytes_in:y.ai().int().nonnegative(),step_bytes_out:y.ai().int().nonnegative()}),w=y.Ik({vu_id:y.ai().int().min(0),ts_exec_count:y.ai().int().nonnegative(),ts_exec_failure:y.ai().int().nonnegative(),ts_exec_time:y.YO(y.ai().nonnegative()),steps:y.YO(N)}),_=y.Ik({Target:y.ai().int().positive(),Duration:y.Yj()}),S=y.Ik({Metric:y.Yj(),Condition:y.Yj(),Severity:y.Yj(),Value:y.ai()}),k=y.Ik({Profile:y.Yj(),VUs:y.ai().int().positive(),Duration:y.Yj(),RPS:y.ai().int().positive(),Stages:y.YO(_).optional(),Thresholds:y.YO(S).optional()}),A=y.Ik({check_id:y.Yj(),check_name:y.Yj(),status:y.k5(["passed","failed","warning"]),severity:y.Yj(),description:y.Yj(),details:y.Yj().optional(),recommendation:y.Yj().optional(),step_name:y.Yj(),step_method:y.Yj(),step_url:y.Yj(),status_code:y.ai().int().optional(),target:y.k5(["request","response"])}),C=y.Ik({step_name:y.Yj(),step_method:y.Yj(),step_url:y.Yj(),results:y.YO(A),passed:y.ai().int().nonnegative(),failed:y.ai().int().nonnegative(),warnings:y.ai().int().nonnegative()}),I=y.Ik({test_suite:y.Yj(),timestamp:y.Yj(),total_checks:y.ai().int().nonnegative(),passed:y.ai().int().nonnegative(),failed:y.ai().int().nonnegative(),warnings:y.ai().int().nonnegative(),steps:y.YO(C),summary:y.g1(y.Yj(),y.ai().int().nonnegative())}),P=y.Ik({id:y.Yj().min(1),url:y.Yj().url(),title:y.Yj().min(1),created_at:y.Yj(),description:y.Yj(),load_options:k,security_report:I.optional(),end_at:y.Yj().optional()}),T=y.Ik({timestamp:y.ai().int().positive(),value:y.ai().nonnegative()}),E=y.Ik({timestamp:y.ai().int().positive(),avg_latency:y.ai().nonnegative()}),z=y.Ik({overall:y.YO(E),perStep:y.g1(y.Yj(),y.YO(T)),perVU:y.g1(y.Yj(),y.YO(T))});y.YO(P);let O=y.YO(w);y.Ik({dashboardData:y.g1(y.Yj(),O),chartHistories:y.g1(y.Yj(),z),dashboardStopTimes:y.g1(y.Yj(),y.Yj()),dashboardCloseTimes:y.g1(y.Yj(),y.Yj()).optional(),dashboardOpenedStatus:y.g1(y.Yj(),y.zM()).optional()});var D=a(464),Y=a(574),M=a(5525);function V(e){let{selectedDashboard:t,currentView:a="dashboard",onViewChange:r}=e,{isOpen:n}=x(),l=e=>{null==r||r(e)},i=null==t?void 0:t.security_report;return(0,s.jsxs)(p,{children:[(0,s.jsx)(f,{children:(0,s.jsx)("div",{className:"flex items-center gap-2 w-full",children:(0,s.jsxs)("div",{className:"p-2 text-orange-900 h-full w-full flex items-center ".concat(n?"ml-4":"justify-center"),children:[(0,s.jsx)(D.A,{className:"h-4 w-4"}),n&&(0,s.jsx)("h2",{className:"font-semibold text-lg ml-2",children:"KLT Dashboard"})]})})}),(0,s.jsx)(v,{children:(0,s.jsxs)("div",{className:"space-y-2",children:[(0,s.jsxs)(m,{onClick:()=>l("dashboard"),variant:"dashboard"===a?"default":"ghost",className:"w-full ".concat(n?"justify-start":"justify-center"," gap-2 ").concat("dashboard"===a?"bg-orange-100 text-orange-900 hover:bg-orange-200":"text-orange-900 hover:bg-orange-100"),children:[(0,s.jsx)(Y.A,{className:"h-4 w-4"}),n&&"Dashboard"]}),(0,s.jsxs)(m,{onClick:()=>l("security"),disabled:!i,variant:"security"===a?"default":"ghost",className:"w-full ".concat(n?"justify-start":"justify-center"," gap-2 ").concat("security"===a?"bg-orange-100 text-orange-900 hover:bg-orange-200":i?"text-orange-900 hover:bg-orange-100":"text-orange-400 cursor-not-allowed"),children:[(0,s.jsx)(M.A,{className:"h-4 w-4"}),n&&(0,s.jsxs)("span",{children:["Security Report",!i&&(0,s.jsx)("span",{className:"text-xs ml-1",children:"(N/A)"})]})]}),n&&i&&(null==t?void 0:t.security_report)&&(0,s.jsxs)("div",{className:"mt-4 p-3 bg-orange-50 rounded-md",children:[(0,s.jsx)("div",{className:"text-xs font-medium text-orange-900 mb-2",children:"Security Summary"}),(0,s.jsxs)("div",{className:"space-y-1 text-xs text-orange-700",children:[(0,s.jsxs)("div",{className:"flex justify-between",children:[(0,s.jsx)("span",{children:"Total Checks:"}),(0,s.jsx)("span",{className:"font-medium",children:t.security_report.total_checks})]}),(0,s.jsxs)("div",{className:"flex justify-between",children:[(0,s.jsx)("span",{children:"Passed:"}),(0,s.jsx)("span",{className:"font-medium text-green-600",children:t.security_report.passed})]}),(0,s.jsxs)("div",{className:"flex justify-between",children:[(0,s.jsx)("span",{children:"Failed:"}),(0,s.jsx)("span",{className:"font-medium text-red-600",children:t.security_report.failed})]}),(0,s.jsxs)("div",{className:"flex justify-between",children:[(0,s.jsx)("span",{children:"Warnings:"}),(0,s.jsx)("span",{className:"font-medium text-yellow-600",children:t.security_report.warnings})]})]})]})]})})]})}let R=(0,r.createContext)(void 0);function U(e){let{children:t}=e,[a,n]=(0,r.useState)(null);return(0,s.jsx)(R.Provider,{value:{selectedDashboard:a,setSelectedDashboard:n},children:t})}function L(){let e=(0,r.useContext)(R);if(void 0===e)throw Error("useDashboard must be used within a DashboardProvider");return e}function K(e){let{lttoken:t,currentVUCount:a=0,stopped:n=!1,stopTime:l}=e,i=function(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],a=arguments.length>2?arguments[2]:void 0,[s,n]=(0,r.useState)("0s");return(0,r.useEffect)(()=>{if(!e)return;if(t&&a)return void n(a);if(t)return void n("0s");let s=()=>{let t=new Date(e).getTime(),a=Math.max(0,Math.floor((Date.now()-t)/1e3)),s=Math.floor(a/3600),r=Math.floor(a%3600/60),l=a%60;s>0?n("".concat(s,"h ").concat(r,"m ").concat(l,"s")):r>0?n("".concat(r,"m ").concat(l,"s")):n("".concat(l,"s"))};s();let r=setInterval(s,1e3);return()=>clearInterval(r)},[e,t,a]),s}(null==t?void 0:t.created_at,n,l);if(!t)return(0,s.jsx)("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6",children:(0,s.jsx)(F,{children:(0,s.jsxs)(J,{className:"text-center",children:[(0,s.jsx)("div",{className:"text-2xl font-bold text-gray-400",children:"--"}),(0,s.jsx)("div",{className:"text-sm text-gray-500",children:"Select a dashboard"})]})})});let o=[{label:"Profile",value:t.load_options.Profile,color:"text-gray-600"},{label:"Active VUs",value:"".concat(a," / ").concat(t.load_options.VUs),color:"text-gray-600"},{label:"Target Duration",value:t.load_options.Duration,color:"text-gray-600"},{label:n?"Final Runtime":"Elapsed Time",value:n?l||"Stopped":i,color:n?"text-red-600":"text-blue-600"},{label:"Target RPS",value:t.load_options.RPS.toLocaleString(),color:"text-gray-600"},{label:"Status",value:n?"Completed":"Running",color:n?"text-red-600":"text-green-600"}];return(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsx)("div",{className:"bg-orange-50 border border-orange-200 rounded-lg p-4",children:(0,s.jsxs)("div",{className:"flex justify-between items-start",children:[(0,s.jsxs)("div",{className:"flex items-center gap-3",children:[(0,s.jsx)(Y.A,{className:"h-10 w-10 text-orange-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-xl font-semibold text-orange-900",children:t.title}),(0,s.jsx)("p",{className:"text-orange-700 text-sm mt-1",children:t.description}),(0,s.jsxs)("p",{className:"text-orange-600 text-xs mt-2",children:["ID: ",t.id]})]})]}),(0,s.jsx)("span",{className:"px-3 py-1 rounded-full text-xs font-medium ".concat(n?"bg-red-100 text-red-800":"bg-green-100 text-green-800"),children:n?"Completed":"Running"})]})}),(0,s.jsx)("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-4",children:o.map(e=>(0,s.jsx)(F,{children:(0,s.jsxs)(J,{className:"text-center",children:[(0,s.jsx)("div",{className:"text-2xl font-bold ".concat(e.color),children:e.value}),(0,s.jsx)("div",{className:"text-sm text-orange-600",children:e.label})]})},e.label))})]})}function F(e){let{children:t,className:a=""}=e;return(0,s.jsx)("div",{className:"bg-orange-50 border border-orange-200 rounded-lg shadow-sm ".concat(a),children:t})}function J(e){let{children:t,className:a=""}=e;return(0,s.jsx)("div",{className:"p-4 ".concat(a),children:t})}var G=a(6268),H=a(1032);function q(e){let{data:t,columns:a,enableRowSelection:s=!0,defaultPageIndex:n,defaultPageSize:l,getRowId:i}=e,[o,d]=r.useState({}),[c,u]=r.useState({}),[m,h]=r.useState([]),[x,g]=r.useState([]),[p,f]=r.useState({pageIndex:null!=n?n:0,pageSize:null!=l?l:10});return(0,G.N4)({data:t,columns:a,state:{sorting:x,columnVisibility:c,rowSelection:o,columnFilters:m,pagination:p},enableRowSelection:s,getRowId:null!=i?i:e=>e.id.toString(),onRowSelectionChange:d,onSortingChange:g,onColumnFiltersChange:h,onColumnVisibilityChange:u,onPaginationChange:f,getCoreRowModel:(0,H.HT)(),getFilteredRowModel:(0,H.hM)(),getPaginationRowModel:(0,H.kW)(),getSortedRowModel:(0,H.h5)(),getFacetedRowModel:(0,H.kQ)(),getFacetedUniqueValues:(0,H.oS)()})}var B=a(17);function W(e){let{className:t,...a}=e;return(0,s.jsx)(B.bL,{"data-slot":"tabs",className:c("flex flex-col gap-2",t),...a})}function Q(e){let{className:t,...a}=e;return(0,s.jsx)(B.B8,{"data-slot":"tabs-list",className:c("bg-orange-200/60 text-orange-700 inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",t),...a})}function X(e){let{className:t,...a}=e;return(0,s.jsx)(B.l9,{"data-slot":"tabs-trigger",className:c("data-[state=active]:bg-orange-50 data-[state=active]:text-orange-900 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring data-[state=active]:border-orange-300 data-[state=active]:bg-orange-100 text-orange-800 hover:bg-orange-100/50 hover:text-orange-900 inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",t),...a})}var Z=a(5143),$=a(1206),ee=a(402);function et(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"table-container",className:"relative w-full overflow-x-auto",children:(0,s.jsx)("table",{"data-slot":"table",className:c("w-full caption-bottom text-sm",t),...a})})}function ea(e){let{className:t,...a}=e;return(0,s.jsx)("thead",{"data-slot":"table-header",className:c("[&_tr]:border-b",t),...a})}function es(e){let{className:t,...a}=e;return(0,s.jsx)("tbody",{"data-slot":"table-body",className:c("[&_tr:last-child]:border-0",t),...a})}function er(e){let{className:t,...a}=e;return(0,s.jsx)("tr",{"data-slot":"table-row",className:c("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",t),...a})}function en(e){let{className:t,...a}=e;return(0,s.jsx)("th",{"data-slot":"table-head",className:c("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",t),...a})}function el(e){let{className:t,...a}=e;return(0,s.jsx)("td",{"data-slot":"table-cell",className:c("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",t),...a})}var ei=a(8266);function eo(e){let{row:t}=e,{transform:a,transition:r,setNodeRef:n,isDragging:l}=(0,ee.gl)({id:t.original.id});return(0,s.jsx)(er,{"data-state":t.getIsSelected()&&"selected","data-dragging":l,ref:n,className:"relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80",style:{transform:ei.Ks.Transform.toString(a),transition:r},children:t.getVisibleCells().map(e=>(0,s.jsx)(el,{children:(0,G.Kv)(e.column.columnDef.cell,e.getContext())},e.id))})}function ed(e){let{table:t,columns:a,dndEnabled:n=!1,onReorder:l}=e,i=t.getRowModel().rows.map(e=>Number(e.id)),o=r.useId(),d=(0,Z.FR)((0,Z.MS)(Z.cA,{}),(0,Z.MS)(Z.IG,{}),(0,Z.MS)(Z.uN,{})),c=(0,s.jsxs)(et,{children:[(0,s.jsx)(ea,{className:"bg-orange-100 sticky top-0 z-10",children:t.getHeaderGroups().map(e=>(0,s.jsx)(er,{children:e.headers.map(e=>(0,s.jsx)(en,{colSpan:e.colSpan,children:e.isPlaceholder?null:(0,G.Kv)(e.column.columnDef.header,e.getContext())},e.id))},e.id))}),(0,s.jsx)(es,{className:"**:data-[slot=table-cell]:first:w-8",children:function(e){let{table:t,columns:a,dndEnabled:r,dataIds:n}=e;return t.getRowModel().rows.length?r?(0,s.jsx)(ee.gB,{items:n,strategy:ee._G,children:t.getRowModel().rows.map(e=>(0,s.jsx)(eo,{row:e},e.id))}):t.getRowModel().rows.map(e=>(0,s.jsx)(er,{"data-state":e.getIsSelected()&&"selected",children:e.getVisibleCells().map(e=>(0,s.jsx)(el,{children:(0,G.Kv)(e.column.columnDef.cell,e.getContext())},e.id))},e.id)):(0,s.jsx)(er,{children:(0,s.jsx)(el,{colSpan:a.length,className:"h-24 text-center",children:"No results."})})}({table:t,columns:a,dndEnabled:n,dataIds:i})})]});return n?(0,s.jsx)(Z.Mp,{collisionDetection:Z.fp,modifiers:[$.FN],onDragEnd:function(e){let{active:a,over:s}=e;if(a&&s&&a.id!==s.id&&l){let e=i.indexOf(a.id),r=i.indexOf(s.id);l((0,ee.be)(t.options.data,e,r))}},sensors:d,id:o,children:c}):c}var ec=a(2278),eu=a(2355),em=a(3052),eh=a(2767),ex=a(968);function eg(e){let{className:t,...a}=e;return(0,s.jsx)(ex.b,{"data-slot":"label",className:c("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",t),...a})}var ep=a(1918),ef=a(6474),ev=a(5196),eb=a(7863);function ej(e){let{...t}=e;return(0,s.jsx)(ep.bL,{"data-slot":"select",...t})}function ey(e){let{...t}=e;return(0,s.jsx)(ep.WT,{"data-slot":"select-value",...t})}function eN(e){let{className:t,size:a="default",children:r,...n}=e;return(0,s.jsxs)(ep.l9,{"data-slot":"select-trigger","data-size":a,className:c("border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",t),...n,children:[r,(0,s.jsx)(ep.In,{asChild:!0,children:(0,s.jsx)(ef.A,{className:"size-4 opacity-50"})})]})}function ew(e){let{className:t,children:a,position:r="popper",...n}=e;return(0,s.jsx)(ep.ZL,{children:(0,s.jsxs)(ep.UC,{"data-slot":"select-content",className:c("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md","popper"===r&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",t),position:r,...n,children:[(0,s.jsx)(eS,{}),(0,s.jsx)(ep.LM,{className:c("p-1","popper"===r&&"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"),children:a}),(0,s.jsx)(ek,{})]})})}function e_(e){let{className:t,children:a,...r}=e;return(0,s.jsxs)(ep.q7,{"data-slot":"select-item",className:c("focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",t),...r,children:[(0,s.jsx)("span",{className:"absolute right-2 flex size-3.5 items-center justify-center",children:(0,s.jsx)(ep.VF,{children:(0,s.jsx)(ev.A,{className:"size-4"})})}),(0,s.jsx)(ep.p4,{children:a})]})}function eS(e){let{className:t,...a}=e;return(0,s.jsx)(ep.PP,{"data-slot":"select-scroll-up-button",className:c("flex cursor-default items-center justify-center py-1",t),...a,children:(0,s.jsx)(eb.A,{className:"size-4"})})}function ek(e){let{className:t,...a}=e;return(0,s.jsx)(ep.wn,{"data-slot":"select-scroll-down-button",className:c("flex cursor-default items-center justify-center py-1",t),...a,children:(0,s.jsx)(ef.A,{className:"size-4"})})}function eA(e){let{table:t}=e;return(0,s.jsx)("div",{className:"flex items-center justify-between px-4",children:(0,s.jsxs)("div",{className:"flex w-full items-center gap-8 lg:w-fit",children:[(0,s.jsxs)("div",{className:"hidden items-center gap-2 lg:flex",children:[(0,s.jsx)(eg,{htmlFor:"rows-per-page",className:"text-sm font-medium",children:"Rows per page"}),(0,s.jsxs)(ej,{value:"".concat(t.getState().pagination.pageSize),onValueChange:e=>t.setPageSize(Number(e)),children:[(0,s.jsx)(eN,{className:"h-8 w-[70px] bg-orange-50 border border-orange-200 hover:bg-orange-100",id:"rows-per-page",children:(0,s.jsx)(ey,{placeholder:t.getState().pagination.pageSize})}),(0,s.jsx)(ew,{side:"top",className:"bg-orange-50 border border-orange-200 shadow-lg",children:[10,20,30,40,50].map(e=>(0,s.jsx)(e_,{value:"".concat(e),className:"hover:bg-orange-100",children:e},e))})]})]}),(0,s.jsxs)("div",{className:"flex w-fit items-center justify-center text-sm font-medium",children:["Page ",t.getState().pagination.pageIndex+1," of ",t.getPageCount()]}),(0,s.jsxs)("div",{className:"ml-auto flex items-center gap-2 lg:ml-0",children:[(0,s.jsxs)(m,{variant:"outline",className:"hidden h-8 w-8 p-0 lg:flex",onClick:()=>t.setPageIndex(0),disabled:!t.getCanPreviousPage(),children:[(0,s.jsx)("span",{className:"sr-only",children:"Go to first page"}),(0,s.jsx)(ec.A,{})]}),(0,s.jsxs)(m,{variant:"outline",className:"size-8",size:"icon",onClick:()=>t.previousPage(),disabled:!t.getCanPreviousPage(),children:[(0,s.jsx)("span",{className:"sr-only",children:"Go to previous page"}),(0,s.jsx)(eu.A,{})]}),(0,s.jsxs)(m,{variant:"outline",className:"size-8",size:"icon",onClick:()=>t.nextPage(),disabled:!t.getCanNextPage(),children:[(0,s.jsx)("span",{className:"sr-only",children:"Go to next page"}),(0,s.jsx)(em.A,{})]}),(0,s.jsxs)(m,{variant:"outline",className:"hidden size-8 lg:flex",size:"icon",onClick:()=>t.setPageIndex(t.getPageCount()-1),disabled:!t.getCanNextPage(),children:[(0,s.jsx)("span",{className:"sr-only",children:"Go to last page"}),(0,s.jsx)(eh.A,{})]})]})]})})}function eC(e){if(!Array.isArray(e)||0===e.length)return"-";let t=[...e].sort((e,t)=>e-t),a=Math.floor(.95*(t.length-1));return(t[a]/1e6).toFixed(2)}function eI(e){if(!Array.isArray(e)||0===e.length)return"-";let t=[...e].sort((e,t)=>e-t),a=Math.floor(.9*(t.length-1));return(t[a]/1e6).toFixed(2)}function eP(e){if(!Array.isArray(e)||0===e.length)return"-";let t=[...e].sort((e,t)=>e-t),a=Math.floor(.99*(t.length-1));return(t[a]/1e6).toFixed(2)}function eT(e){return"number"!=typeof e||isNaN(e)?"-":(e/1048576).toFixed(2)}function eE(e){let{data:t,isCompleted:a=!1}=e,[n,l]=r.useState(()=>null!=t?t:[]),[i,o]=r.useState("virtual-users"),[d,c]=r.useState(()=>null!=t?t:[]),[u,m]=r.useState(()=>(null!=t?t:[]).flatMap(e=>Array.isArray(e.steps)?e.steps.map(t=>({...t,vu_id:e.vu_id})):[]));r.useEffect(()=>{console.log("DataTable updating with data:",(null==t?void 0:t.length)||0,"records"),l(null!=t?t:[]),c(null!=t?t:[]),m((null!=t?t:[]).flatMap(e=>Array.isArray(e.steps)?e.steps.map(t=>({...t,vu_id:e.vu_id})):[]))},[t]),r.useEffect(()=>{"virtual-users"===i?c(n):"steps"===i&&m(n.flatMap(e=>Array.isArray(e.steps)?e.steps.map(t=>({...t,vu_id:e.vu_id})):[]))},[i,n]);let h=r.useMemo(()=>{let e={},t={};return n.forEach(a=>{Array.isArray(a.steps)&&a.steps.forEach(a=>{let s=a.step_name;if(s&&(e[s]||(e[s]={step_name:s,step_count:0,step_failure:0,step_response_time:[],step_bytes_in:0,step_bytes_out:0,step_response_time_averages:[]},t[s]=[]),e[s].step_count+="number"==typeof a.step_count?a.step_count:0,e[s].step_failure+="number"==typeof a.step_failure?a.step_failure:0,e[s].step_bytes_in+="number"==typeof a.step_bytes_in?a.step_bytes_in:0,e[s].step_bytes_out+="number"==typeof a.step_bytes_out?a.step_bytes_out:0,Array.isArray(a.step_response_time)&&a.step_response_time.length>0)){e[s].step_response_time.push(...a.step_response_time);let r=a.step_response_time.reduce((e,t)=>e+t,0)/a.step_response_time.length;t[s].push(r)}})}),Object.keys(e).forEach(a=>{e[a].step_response_time_averages=t[a]}),Object.values(e)},[n]);r.useEffect(()=>{"virtual-users"===i?c(n):"steps"===i&&m(h)},[i,h]);let x=[{accessorKey:"vu_id",header:"ID"},{accessorKey:"ts_exec_count",header:"TSExecCount"},{accessorKey:"ts_exec_failure",header:"TSExecFailure"},{accessorKey:"steps",header:"StepExecCount",cell:e=>{let{row:t}=e;return Array.isArray(t.original.steps)?t.original.steps.reduce((e,t)=>e+("number"==typeof t.step_count?t.step_count:0),0):"-"}},{accessorKey:"ts_exec_time",header:"AvgTSExecTime(ms)",cell:e=>{let{row:t}=e;var a=t.original.ts_exec_time;return Array.isArray(a)&&0!==a.length?(a.reduce((e,t)=>e+t,0)/a.length/1e6).toFixed(2):"-"}},{accessorKey:"ts_exec_time_p90",header:"P90TSExecTime(ms)",cell:e=>{let{row:t}=e;return eI(t.original.ts_exec_time)}},{accessorKey:"ts_exec_time_p95",header:"P95TSExecTime(ms)",cell:e=>{let{row:t}=e;return eC(t.original.ts_exec_time)}},{accessorKey:"ts_exec_time_p99",header:"P99TSExecTime(ms)",cell:e=>{let{row:t}=e;return eP(t.original.ts_exec_time)}}],g=[{accessorKey:"step_name",header:"Step Name"},{accessorKey:"step_count",header:"Count(All VUs)",cell:e=>{var t;let{row:a}=e;return null!=(t=a.original.step_count)?t:"-"}},{accessorKey:"step_failure",header:"Failure(All VUs)",cell:e=>{var t;let{row:a}=e;return null!=(t=a.original.step_failure)?t:"-"}},{accessorKey:"step_bytes_in",header:"BytesIn(MB)",cell:e=>{let{row:t}=e;return eT(t.original.step_bytes_in)}},{accessorKey:"step_bytes_out",header:"BytesOut (MB)",cell:e=>{let{row:t}=e;return eT(t.original.step_bytes_out)}},{accessorKey:"step_response_time",header:"AvgResponseTime(ms)",cell:e=>{let{row:t}=e,a=t.original.step_response_time_averages;return Array.isArray(a)&&0!==a.length?(a.reduce((e,t)=>e+t,0)/a.length/1e6).toFixed(2):"-"}},{accessorKey:"p90",header:"P90(ms)",cell:e=>{let{row:t}=e;return eI(t.original.step_response_time)}},{accessorKey:"p95",header:"P95(ms)",cell:e=>{let{row:t}=e;return eC(t.original.step_response_time)}},{accessorKey:"p99",header:"P99(ms)",cell:e=>{let{row:t}=e;return eP(t.original.step_response_time)}}],p=q({data:d,columns:x,getRowId:(e,t)=>null!=e.vu_id?e.vu_id.toString():"row-".concat(t)}),f=q({data:u,columns:g,getRowId:(e,t)=>null!=e.vu_id&&null!=e.step_name?"".concat(e.vu_id,"-").concat(e.step_name):e.step_name?e.step_name+"-"+t:"row-"+t});return t&&0!==t.length?(0,s.jsxs)("div",{className:"w-full bg-orange-50 border border-orange-200 rounded-lg overflow-hidden",children:[(0,s.jsx)("div",{className:"p-4 border-b border-orange-200 bg-orange-100",children:(0,s.jsxs)("div",{className:"flex items-center justify-between",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("h3",{className:"text-lg font-semibold text-orange-900",children:"Load Test Data"}),(0,s.jsx)("p",{className:"text-sm text-orange-700",children:"virtual-users"===i?"".concat(d.length," Virtual Users"):"".concat(h.length," Unique Steps")})]}),(0,s.jsx)(W,{value:i,onValueChange:e=>o(e),className:"w-fit",children:(0,s.jsxs)(Q,{children:[(0,s.jsx)(X,{value:"virtual-users",children:"Virtual Users"}),(0,s.jsx)(X,{value:"steps",children:"Steps"})]})})]})}),(0,s.jsxs)("div",{className:"relative flex flex-col",children:[(0,s.jsx)("div",{className:"overflow-hidden px-2",children:"virtual-users"===i?(0,s.jsx)(ed,{dndEnabled:!0,table:p,columns:x,onReorder:c}):(0,s.jsx)(ed,{dndEnabled:!0,table:f,columns:g,onReorder:m})}),(0,s.jsx)("div",{className:"p-4 border-t border-orange-200 bg-orange-100",children:"virtual-users"===i?(0,s.jsx)(eA,{table:p}):(0,s.jsx)(eA,{table:f})})]})]}):(0,s.jsx)("div",{className:"w-full bg-orange-50 border border-orange-200 rounded-lg p-8 text-center text-orange-600",children:"No VU data available"})}var ez=a(8247),eO=a(3406),eD=a(7156);function eY(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"card",className:c("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",t),...a})}function eM(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"card-header",className:c("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",t),...a})}function eV(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"card-title",className:c("leading-none font-semibold",t),...a})}function eR(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"card-description",className:c("text-muted-foreground text-sm",t),...a})}function eU(e){let{className:t,...a}=e;return(0,s.jsx)("div",{"data-slot":"card-content",className:c("px-6",t),...a})}var eL=a(3540),eK=a(8124),eF=a(2319);let eJ={light:"",dark:".dark"},eG=r.createContext(null);function eH(e){let{id:t,className:a,children:n,config:l,...i}=e,o=r.useId(),d="chart-".concat(t||o.replace(/:/g,""));return(0,s.jsx)(eG.Provider,{value:{config:l},children:(0,s.jsxs)("div",{"data-slot":"chart","data-chart":d,className:c("[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",a),...i,children:[(0,s.jsx)(eq,{id:d,config:l}),(0,s.jsx)(eL.u,{children:n})]})})}let eq=e=>{let{id:t,config:a}=e,r=Object.entries(a).filter(e=>{let[,t]=e;return t.theme||t.color});return r.length?(0,s.jsx)("style",{dangerouslySetInnerHTML:{__html:Object.entries(eJ).map(e=>{let[a,s]=e;return"\n".concat(s," [data-chart=").concat(t,"] {\n").concat(r.map(e=>{var t;let[s,r]=e,n=(null==(t=r.theme)?void 0:t[a])||r.color;return n?" --color-".concat(s,": ").concat(n,";"):null}).join("\n"),"\n}\n")}).join("\n")}}):null},eB=eK.m;function eW(e){let{active:t,payload:a,className:n,indicator:l="dot",hideLabel:i=!1,hideIndicator:o=!1,label:d,labelFormatter:u,labelClassName:m,formatter:h,color:x,nameKey:g,labelKey:p}=e,{config:f}=function(){let e=r.useContext(eG);if(!e)throw Error("useChart must be used within a ");return e}(),v=r.useMemo(()=>{var e;if(i||!(null==a?void 0:a.length))return null;let[t]=a,r="".concat(p||(null==t?void 0:t.dataKey)||(null==t?void 0:t.name)||"value"),n=eQ(f,t,r),l=p||"string"!=typeof d?null==n?void 0:n.label:(null==(e=f[d])?void 0:e.label)||d;return u?(0,s.jsx)("div",{className:c("font-medium",m),children:u(l,a)}):l?(0,s.jsx)("div",{className:c("font-medium",m),children:l}):null},[d,u,a,i,m,f,p]);if(!t||!(null==a?void 0:a.length))return null;let b=1===a.length&&"dot"!==l;return(0,s.jsxs)("div",{className:c("border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",n),children:[b?null:v,(0,s.jsx)("div",{className:"grid gap-1.5",children:a.map((e,t)=>{let a="".concat(g||e.name||e.dataKey||"value"),r=eQ(f,e,a),n=x||(null==r?void 0:r.color)||e.color||e.stroke||e.fill||"hsl(var(--chart-".concat(t%5+1,"))");return(0,s.jsx)("div",{className:c("[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5","dot"===l&&"items-center"),children:h&&(null==e?void 0:e.value)!==void 0&&e.name?h(e.value,e.name,e,t,e.payload):(0,s.jsxs)(s.Fragment,{children:[(null==r?void 0:r.icon)?(0,s.jsx)(r.icon,{}):!o&&(0,s.jsx)("div",{className:c("shrink-0 rounded-[2px]",{"h-2.5 w-2.5":"dot"===l,"w-1":"line"===l,"w-0 border-[1.5px] border-dashed bg-transparent":"dashed"===l,"my-0.5":b&&"dashed"===l}),style:{backgroundColor:"dashed"!==l?n:"transparent",borderColor:n,..."dashed"===l&&{borderStyle:"dashed"}}}),(0,s.jsxs)("div",{className:c("flex flex-1 justify-between leading-none",b?"items-end":"items-center"),children:[(0,s.jsxs)("div",{className:"grid gap-1.5",children:[b?v:null,(0,s.jsx)("span",{className:"text-muted-foreground",children:(null==r?void 0:r.label)||e.name})]}),e.value&&(0,s.jsx)("span",{className:"text-foreground font-mono font-medium tabular-nums",children:"number"==typeof e.value?e.value.toLocaleString():e.value})]})]})},e.dataKey||t)})})]})}function eQ(e,t,a){if("object"!=typeof t||null===t)return;let s="payload"in t&&"object"==typeof t.payload&&null!==t.payload?t.payload:void 0,r=a;return a in t&&"string"==typeof t[a]?r=t[a]:s&&a in s&&"string"==typeof s[a]&&(r=s[a]),r in e?e[r]:e[a]}eF.s;var eX=a(2493);let eZ=(0,i.F)("inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",{variants:{variant:{default:"bg-transparent",outline:"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"},size:{default:"h-9 px-2 min-w-9",sm:"h-8 px-1.5 min-w-8",lg:"h-10 px-2.5 min-w-10"}},defaultVariants:{variant:"default",size:"default"}}),e$=r.createContext({size:"default",variant:"default"});function e0(e){let{className:t,variant:a,size:r,children:n,...l}=e;return(0,s.jsx)(eX.bL,{"data-slot":"toggle-group","data-variant":a,"data-size":r,className:c("group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",t),...l,children:(0,s.jsx)(e$.Provider,{value:{variant:a,size:r},children:n})})}function e1(e){let{className:t,children:a,variant:n,size:l,...i}=e,o=r.useContext(e$);return(0,s.jsx)(eX.q7,{"data-slot":"toggle-group-item","data-variant":o.variant||n,"data-size":o.size||l,className:c(eZ({variant:o.variant||n,size:o.size||l}),"min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",t),...i,children:a})}function e2(e){return Array.isArray(e)&&0!==e.length?e.reduce((e,t)=>e+t,0)/e.length:0}function e4(e){let{vuData:t=[],chartHistory:a,setChartHistory:n,isCompleted:l=!1}=e,[i,o]=r.useState("overall"),[d,c]=r.useState(!1),u=r.useRef("");r.useEffect(()=>{if(!Array.isArray(t)||0===t.length)return;if(l&&a.overall.length>0)return void console.log("Dashboard is completed and chart history exists, skipping chart updates");let e=JSON.stringify(t.map(e=>{var t;return{id:e.vu_id,stepCount:(null==(t=e.steps)?void 0:t.length)||0,timestamp:Date.now()}}));if(e===u.current)return;u.current=e;let s=Date.now(),r=[];t.forEach(e=>{var t;null==(t=e.steps)||t.forEach(e=>{Array.isArray(e.step_response_time)&&r.push(...e.step_response_time)})});let i=e2(r)/1e6,o=Array.from(new Set(t.flatMap(e=>{var t;return(null==(t=e.steps)?void 0:t.map(e=>e.step_name))||[]}))).filter(Boolean);n(e=>{let a={...e.perStep};o.forEach(e=>{let r=[];t.forEach(t=>{var a;null==(a=t.steps)||a.forEach(t=>{t.step_name===e&&Array.isArray(t.step_response_time)&&r.push(...t.step_response_time)})});let n=e2(r)/1e6;a[e]||(a[e]=[]),a[e]=[...a[e],{timestamp:s,value:n}]});let r=Array.from(new Set(t.map(e=>e.vu_id.toString()))),n={...e.perVU};return r.forEach(e=>{let a=[];t.forEach(t=>{if(t.vu_id.toString()===e){var s;null==(s=t.steps)||s.forEach(e=>{Array.isArray(e.step_response_time)&&a.push(...e.step_response_time)})}});let r=e2(a)/1e6;n[e]||(n[e]=[]),n[e]=[...n[e],{timestamp:s,value:r}]}),{overall:[...e.overall,{timestamp:s,avg_latency:i}],perStep:a,perVU:n}})},[t,l]);let h=r.useMemo(()=>{if("overall"===i)return a.overall.map(e=>({timestamp:e.timestamp,avg_latency:e.avg_latency}));if("perStep"===i){let e=Object.keys(a.perStep);return Array.from(new Set(e.flatMap(e=>(a.perStep[e]||[]).map(e=>e.timestamp)))).sort((e,t)=>e-t).map(t=>{let s={timestamp:t};return e.forEach(e=>{let r=(a.perStep[e]||[]).find(e=>e.timestamp===t);r&&(s[e]=r.value)}),s})}if("perVU"===i){let e=Object.keys(a.perVU);return Array.from(new Set(e.flatMap(e=>(a.perVU[e]||[]).map(e=>e.timestamp)))).sort((e,t)=>e-t).map(t=>{let s={timestamp:t};return e.forEach(e=>{let r=(a.perVU[e]||[]).find(e=>e.timestamp===t);r&&(s[e]=r.value)}),s})}return[]},[a,i]),x=r.useMemo(()=>{if("overall"===i)return{avg_latency:{label:"Avg Latency (ms)",color:"var(--chart-1)"}};if("perStep"===i){let e=Object.keys(a.perStep),t=["var(--chart-1)","var(--chart-2)","var(--chart-3)","var(--chart-4)","var(--chart-5)"];return Object.fromEntries(e.map((e,a)=>[e,{label:e,color:t[a%t.length]}]))}if("perVU"===i){let e=Object.keys(a.perVU),t=["var(--chart-1)","var(--chart-2)","var(--chart-3)","var(--chart-4)","var(--chart-5)"];return Object.fromEntries(e.map((e,a)=>[e,{label:"VU ".concat(e),color:t[a%t.length]}]))}return{}},[i,a]),g=r.useMemo(()=>"perStep"===i?Object.entries(a.perStep).map(e=>{let[t,a]=e;return{key:t,label:t,data:a.map(e=>({timestamp:e.timestamp,value:e.value}))}}):"perVU"===i?Object.entries(a.perVU).map(e=>{let[t,a]=e;return{key:t,label:"VU ".concat(t),data:a.map(e=>({timestamp:e.timestamp,value:e.value}))}}):[],[i,a]),p=d?g:g.slice(0,4);return t&&0!==t.length?(0,s.jsxs)(eY,{className:"@container/card",children:[(0,s.jsx)(eM,{children:(0,s.jsxs)("div",{className:"flex items-center justify-between",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(eV,{children:"Average Latency"}),(0,s.jsxs)(eR,{children:[(0,s.jsxs)("span",{className:"hidden @[540px]/card:block",children:["Latency by"," ","overall"===i?"All Steps":"perStep"===i?"Step":"VU"]}),(0,s.jsx)("span",{className:"@[540px]/card:hidden",children:"Latency"})]})]}),(0,s.jsxs)(e0,{type:"single",value:i,onValueChange:e=>e&&o(e),variant:"outline",className:"gap-2",children:[(0,s.jsx)(e1,{value:"overall",className:"text-xs transition-all ".concat("overall"===i?"bg-orange-100 text-orange-900 border-orange-300 shadow-sm":"hover:bg-oragne-50"),children:"Overall"}),(0,s.jsx)(e1,{value:"perStep",className:"text-xs transition-all ".concat("perStep"===i?"bg-orange-100 text-orange-900 border-orange-300 shadow-sm":"hover:bg-orange-50"),children:"Per Step"}),(0,s.jsx)(e1,{value:"perVU",className:"text-xs transition-all ".concat("perVU"===i?"bg-orange-100 text-orange-900 border-orange-300 shadow-sm":"hover:bg-orange-50"),children:"Per VU"})]})]})}),(0,s.jsx)(eU,{className:"px-2 pt-4 sm:px-6 sm:pt-6",children:(0,s.jsx)(eH,{config:x,className:"aspect-auto h-[250px] w-full",children:(0,s.jsxs)(ez.Q,{data:h,children:[(0,s.jsx)(eO.d,{vertical:!1}),(0,s.jsx)(eB,{cursor:!1,content:(0,s.jsx)(eW,{indicator:"line",hideLabel:!0})}),Object.keys(x).map(e=>(0,s.jsx)(eD.Gk,{dataKey:e,type:"natural",fill:x[e].color,fillOpacity:.3,stroke:x[e].color,strokeWidth:2,name:x[e].label||e},e))]})})}),("perStep"===i||"perVU"===i)&&g.length>0&&(0,s.jsxs)("div",{className:"px-4",children:[g.length>4&&(0,s.jsxs)("div",{className:"mb-4 flex justify-between items-center",children:[(0,s.jsxs)("h3",{className:"text-sm font-medium text-gray-700",children:["Individual ","perStep"===i?"Step":"VU"," Charts",(0,s.jsxs)("span",{className:"text-gray-500 font-normal ml-1",children:["(",d?g.length:Math.min(4,g.length)," ","of ",g.length,")"]})]}),(0,s.jsx)(m,{variant:"outline",size:"sm",onClick:()=>c(!d),className:"text-xs cursor-pointer",children:d?"Show Less (4 of ".concat(g.length,")"):"Show All ".concat("perStep"===i?"Steps":"VUs"," (").concat(g.length,")")})]}),g.length<=4&&(0,s.jsx)("div",{className:"mb-4",children:(0,s.jsxs)("h3",{className:"text-sm font-medium text-gray-700",children:["Individual ","perStep"===i?"Step":"VU"," Charts",(0,s.jsxs)("span",{className:"text-gray-500 font-normal ml-1",children:["(",g.length,")"]})]})}),(0,s.jsx)("div",{className:"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",children:p.map((e,t)=>{let{key:a,label:r,data:n}=e,l=["var(--chart-1)","var(--chart-2)","var(--chart-3)","var(--chart-4)","var(--chart-5)"],i=l[t%l.length];return(0,s.jsxs)(eY,{className:"h-[180px] overflow-hidden",children:[(0,s.jsx)(eM,{children:(0,s.jsx)(eV,{className:"font-medium truncate leading-tight -m-2",title:r,children:r})}),(0,s.jsx)(eU,{className:"px-3",children:(0,s.jsx)("div",{className:"h-[130px] w-full",children:(0,s.jsx)(eH,{config:{value:{label:r,color:i}},className:"h-full w-full",children:(0,s.jsxs)(ez.Q,{data:n,margin:{top:20,right:2,left:2,bottom:20},children:[(0,s.jsx)(eB,{cursor:!1,content:(0,s.jsx)(eW,{indicator:"dot"})}),(0,s.jsx)(eD.Gk,{type:"natural",dataKey:"value",fill:i,fillOpacity:.4,stroke:i,strokeWidth:1.5,name:x[a].label||a})]})})})})]},a)})}),g.length>4&&(0,s.jsx)("div",{className:"mt-4 flex justify-center",children:(0,s.jsx)(m,{variant:"outline",size:"sm",onClick:()=>c(!d),className:"text-xs cursor-pointer",children:d?"Show Less (4 of ".concat(g.length,")"):"Show All ".concat("perStep"===i?"Steps":"VUs"," (").concat(g.length,")")})})]})]}):(0,s.jsxs)(eY,{className:"w-full",children:[(0,s.jsxs)(eM,{children:[(0,s.jsx)(eV,{children:"Average Latency"}),(0,s.jsx)(eR,{children:"No data available"})]}),(0,s.jsx)(eU,{className:"h-[250px] flex items-center justify-center text-gray-500",children:"No data is available"})]})}let e5=(0,i.F)("inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",secondary:"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",destructive:"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",outline:"text-foreground"}},defaultVariants:{variant:"default"}});function e6(e){let{className:t,variant:a,...r}=e;return(0,s.jsx)("div",{className:c(e5({variant:a}),t),...r})}var e3=a(646),e9=a(4861),e8=a(1243),e7=a(6785),te=a(4186);function tt(e){let{securityReport:t}=e,[a,n]=(0,r.useState)(new Set),l=e=>{switch(e.toLowerCase()){case"critical":return"bg-red-600 text-white";case"high":return"bg-red-500 text-white";case"medium":return"bg-yellow-500 text-white";case"low":return"bg-blue-500 text-white";case"info":return"bg-gray-500 text-white";default:return"bg-gray-400 text-white"}};return(0,s.jsxs)("div",{className:"space-y-6",children:[(0,s.jsxs)("div",{className:"flex items-center gap-3",children:[(0,s.jsx)(M.A,{className:"w-10 h-10 text-orange-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-2xl font-bold text-orange-900",children:"Security Report"}),(0,s.jsxs)("p",{className:"text-orange-700",children:["Test Suite: ",t.test_suite]})]})]}),(0,s.jsxs)("div",{className:"grid grid-cols-2 md:grid-cols-4 gap-4",children:[(0,s.jsx)(eY,{className:"p-4 border-orange-200",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e7.A,{className:"w-5 h-5 text-orange-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-2xl font-bold text-orange-900",children:t.total_checks}),(0,s.jsx)("div",{className:"text-sm text-orange-600",children:"Total Checks"})]})]})}),(0,s.jsx)(eY,{className:"p-4 border-green-200",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e3.A,{className:"w-5 h-5 text-green-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-2xl font-bold text-green-700",children:t.passed}),(0,s.jsx)("div",{className:"text-sm text-green-600",children:"Passed"})]})]})}),(0,s.jsx)(eY,{className:"p-4 border-red-200",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e9.A,{className:"w-5 h-5 text-red-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-2xl font-bold text-red-700",children:t.failed}),(0,s.jsx)("div",{className:"text-sm text-red-600",children:"Failed"})]})]})}),(0,s.jsx)(eY,{className:"p-4 border-yellow-200",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e8.A,{className:"w-5 h-5 text-yellow-600"}),(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-2xl font-bold text-yellow-700",children:t.warnings}),(0,s.jsx)("div",{className:"text-sm text-yellow-600",children:"Warnings"})]})]})})]}),(0,s.jsx)(eY,{className:"p-4 border-orange-200",children:(0,s.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-6",children:[(0,s.jsxs)("div",{children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 mb-2",children:[(0,s.jsx)(te.A,{className:"w-4 h-4 text-orange-600"}),(0,s.jsx)("span",{className:"text-sm font-medium text-orange-900",children:"Report Generated"})]}),(0,s.jsx)("p",{className:"text-orange-700",children:new Date(t.timestamp).toLocaleString()})]}),Object.keys(t.summary).length>0&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h4",{className:"text-sm font-medium text-orange-900 mb-3",children:"Severity Summary"}),(0,s.jsx)("div",{className:"flex flex-wrap gap-2",children:Object.entries(t.summary).map(e=>{let[t,a]=e;return(0,s.jsxs)(e6,{className:l(t),children:[t,": ",a]},t)})})]})]})}),(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsx)("h3",{className:"text-lg font-semibold text-orange-900",children:"Security Check Results by Step"}),t.steps.map((e,t)=>{let r="".concat(e.step_method,"-").concat(e.step_name),i=a.has(r);return(0,s.jsxs)(eY,{className:"border-l-4 ".concat(e.failed>0?"border-l-red-500 border-red-200":e.warnings>0?"border-l-yellow-500 border-yellow-200":"border-l-green-500 border-green-200"),children:[(0,s.jsx)("div",{className:"p-4 cursor-pointer",onClick:()=>{n(e=>{let t=new Set(e);return t.has(r)?t.delete(r):t.add(r),t})},children:(0,s.jsxs)("div",{className:"flex items-center justify-between",children:[(0,s.jsxs)("div",{className:"flex items-center gap-3",children:[i?(0,s.jsx)(ef.A,{className:"w-5 h-5 text-orange-600"}):(0,s.jsx)(em.A,{className:"w-5 h-5 text-orange-600"}),(0,s.jsxs)("div",{children:[(0,s.jsxs)("h4",{className:"font-semibold text-orange-900",children:[e.step_method," - ",e.step_name]}),(0,s.jsx)("p",{className:"text-sm text-orange-700 break-all",children:e.step_url})]})]}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsxs)(e6,{className:"bg-green-100 text-green-800 border-green-200",children:["Passed: ",e.passed]}),(0,s.jsxs)(e6,{className:"bg-red-100 text-red-800 border-red-200",children:["Failed: ",e.failed]}),(0,s.jsxs)(e6,{className:"bg-yellow-100 text-yellow-800 border-yellow-200",children:["Warnings: ",e.warnings]})]})]})}),i&&(0,s.jsx)("div",{className:"border-t border-orange-200 p-4 space-y-4",children:e.results.map((e,t)=>(0,s.jsxs)("div",{className:"bg-orange-50 p-4 rounded-md space-y-3",children:[(0,s.jsxs)("div",{className:"flex items-start justify-between",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(e=>{switch(e){case"passed":return(0,s.jsx)(e3.A,{className:"w-4 h-4 text-green-600"});case"failed":return(0,s.jsx)(e9.A,{className:"w-4 h-4 text-red-600"});case"warning":return(0,s.jsx)(e8.A,{className:"w-4 h-4 text-yellow-600"});default:return(0,s.jsx)(M.A,{className:"w-4 h-4 text-gray-600"})}})(e.status),(0,s.jsx)("h5",{className:"font-semibold text-orange-900",children:e.check_name})]}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsx)(e6,{className:(e=>{switch(e){case"passed":return"bg-green-100 text-green-800 border-green-200";case"failed":return"bg-red-100 text-red-800 border-red-200";case"warning":return"bg-yellow-100 text-yellow-800 border-yellow-200";default:return"bg-gray-100 text-gray-800 border-gray-200"}})(e.status),children:e.status}),(0,s.jsx)(e6,{className:l(e.severity),children:e.severity})]})]}),(0,s.jsx)("p",{className:"text-orange-700",children:e.description}),(0,s.jsx)("div",{className:"bg-white p-3 rounded-md",children:(0,s.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-2 text-sm",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"font-medium text-orange-900",children:"Check ID:"})," ",(0,s.jsx)("span",{className:"text-orange-700",children:e.check_id})]}),e.status_code&&(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"font-medium text-orange-900",children:"Status Code:"})," ",(0,s.jsx)("span",{className:"text-orange-700",children:e.status_code})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"font-medium text-orange-900",children:"Target:"})," ",(0,s.jsx)("span",{className:"text-orange-700 capitalize",children:e.target})]})]})}),e.details&&(0,s.jsxs)("div",{className:"bg-gray-50 p-3 rounded-md",children:[(0,s.jsx)("h6",{className:"font-medium text-orange-900 mb-1",children:"Details"}),(0,s.jsx)("p",{className:"text-sm text-orange-700",children:e.details})]}),e.recommendation&&(0,s.jsxs)("div",{className:"bg-blue-50 p-3 rounded-md",children:[(0,s.jsx)("h6",{className:"font-medium text-orange-900 mb-1",children:"Recommendation"}),(0,s.jsx)("p",{className:"text-sm text-orange-700",children:e.recommendation})]})]},t))})]},t)})]})]})}var ta=a(3786),ts=a(2525),tr=a(9074),tn=a(7580);function tl(){let e=(0,n.useRouter)(),[t,a]=(0,r.useState)([]);return(0,r.useEffect)(()=>{let e=localStorage.getItem("klt-dashboards");if(e)try{let t=JSON.parse(e);a(t)}catch(e){console.error("Failed to parse saved dashboards:",e)}},[]),(0,s.jsx)("div",{className:"min-h-screen p-6",children:(0,s.jsxs)("div",{className:"max-w-7xl mx-auto",children:[(0,s.jsxs)("div",{className:"mb-8",children:[(0,s.jsx)("h1",{className:"text-4xl font-bold text-orange-900 mb-2",children:"KLT Dashboard"}),(0,s.jsx)("p",{className:"text-orange-700 text-lg",children:"Manage and monitor your load testing dashboards"})]}),(0,s.jsx)("div",{className:"mb-6",children:(0,s.jsxs)("div",{className:"text-sm text-orange-600",children:[t.length," dashboard",1!==t.length?"s":""," available"]})}),0===t.length?(0,s.jsxs)(eY,{className:"p-12 text-center border-orange-200",children:[(0,s.jsx)("div",{className:"text-orange-400 mb-4",children:(0,s.jsx)(Y.A,{className:"w-16 h-16 mx-auto mb-4"})}),(0,s.jsx)("h3",{className:"text-xl font-semibold text-orange-800 mb-2",children:"No dashboards available"}),(0,s.jsx)("p",{className:"text-orange-600 mb-6",children:"Dashboards will appear here automatically when load tests are running"})]}):(0,s.jsx)("div",{className:"grid gap-6 md:grid-cols-2 lg:grid-cols-3",children:t.map(t=>(0,s.jsxs)(eY,{className:"p-6 hover:shadow-lg transition-shadow border-orange-200 hover:border-orange-300",children:[(0,s.jsxs)("div",{className:"flex justify-between items-start mb-4",children:[(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsx)("h3",{className:"text-xl font-semibold text-orange-900 truncate",children:t.title}),(0,s.jsx)("p",{className:"text-sm text-orange-500 mt-1",children:t.id})]}),(0,s.jsxs)("div",{className:"flex gap-2 ml-2",children:[(0,s.jsx)(m,{variant:"ghost",size:"sm",onClick:()=>(e=>{let t=new URL(window.location.origin);t.searchParams.set("dashboard",e),window.open(t.toString(),"_blank")})(t.id),className:"p-2 text-orange-600 hover:text-orange-700 hover:bg-orange-50",children:(0,s.jsx)(ta.A,{className:"w-4 h-4"})}),(0,s.jsx)(m,{variant:"ghost",size:"sm",onClick:()=>(e=>{a(t=>{let a=t.filter(t=>t.id!==e);return localStorage.setItem("klt-dashboards",JSON.stringify(a)),a});try{localStorage.removeItem("dashboard-storage-dashboard-".concat(e))}catch(e){console.error("Failed to clean up dashboard data:",e)}})(t.id),className:"p-2 text-red-600 hover:text-red-700 hover:bg-red-50",children:(0,s.jsx)(ts.A,{className:"w-4 h-4"})})]})]}),(0,s.jsx)("p",{className:"text-orange-700 text-sm mb-4 line-clamp-2",children:t.description}),(0,s.jsxs)("div",{className:"space-y-2 mb-4 text-sm text-orange-600",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(tr.A,{className:"w-4 h-4"}),(0,s.jsx)("span",{children:new Date(t.created_at).toLocaleString()})]}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(tn.A,{className:"w-4 h-4"}),(0,s.jsxs)("span",{children:[t.load_options.VUs," VUs"]})]}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(te.A,{className:"w-4 h-4"}),(0,s.jsx)("span",{children:t.load_options.Duration})]}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e7.A,{className:"w-4 h-4"}),(0,s.jsxs)("span",{children:[t.load_options.RPS," RPS"]})]})]}),(0,s.jsx)("div",{className:"space-y-2",children:(0,s.jsx)(m,{onClick:()=>{var a;return a=t.id,void e.push("/?dashboard=".concat(a))},className:"w-full bg-orange-600 hover:bg-orange-700 text-white",children:"Open Dashboard"})})]},t.id))})]})})}function ti(){let{selectedDashboard:e,setSelectedDashboard:t}=L(),[a,n]=(0,r.useState)("dashboard"),[l,i]=(0,r.useState)({}),[o,d]=(0,r.useState)({}),[c,u]=(0,r.useState)({}),[m,h]=(0,r.useState)({}),[x,p]=(0,r.useState)({}),[f,v]=(0,r.useState)(!1),y=(null==e?void 0:e.id)||"",N=l[y]||[],_=o[y]||{overall:[],perStep:{},perVU:{}},S=(0,r.useRef)(l),k=(0,r.useRef)(o),A=(0,r.useRef)(c),C=(0,r.useRef)(m),I=(0,r.useRef)(x);(0,r.useEffect)(()=>{S.current=l,k.current=o,A.current=c,C.current=m,I.current=x},[l,o,c,m,x]);let P=(e,t)=>"".concat(e,"-dashboard-").concat(t),T=e=>{let t=new Date(e).getTime();var a=Math.max(0,Math.floor((Date.now()-t)/1e3));let s=Math.floor(a/3600),r=Math.floor(a%3600/60),n=a%60;return s>0?"".concat(s,"h ").concat(r,"m ").concat(n,"s"):r>0?"".concat(r,"m ").concat(n,"s"):"".concat(n,"s")},E=(e,t)=>{u(a=>({...a,[e]:t}))};(0,r.useEffect)(()=>{(()=>{if(!(null==e?void 0:e.id))return v(!1);try{let t=P("dashboard-storage",e.id),a=localStorage.getItem(t);if(a){let e=JSON.parse(a);i(t=>({...t,...e.dashboardData})),d(t=>({...t,...e.chartHistories})),u(t=>({...t,...e.dashboardStopTimes})),h(t=>({...t,...e.dashboardCloseTimes})),p(t=>({...t,...e.dashboardOpenedStatus}))}}catch(e){}finally{v(!0)}})()},[null==e?void 0:e.id]),(0,r.useEffect)(()=>{if(!(null==e?void 0:e.id)||!f)return;let t=e.id;x[t]||(p(e=>({...e,[t]:!0})),z(e.url).then(e=>{if(e)h(e=>{let a={...e};return delete a[t],a});else{let e=m[t];e?E(t,e):E(t,"0s"),h(e=>{let a={...e};return delete a[t],a})}}))},[null==e?void 0:e.id,x,null==e?void 0:e.url,f]);let z=async function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;for(let a=0;a<=t;a++)try{if(0===a){let e=sessionStorage.getItem("sw-unregistered");e&&Date.now()-parseInt(e)<3e3&&await new Promise(e=>setTimeout(e,300))}let t=await fetch(e,{cache:"no-store",signal:AbortSignal.timeout(2e3+500*a)});if(t.ok)return!0;if(t.status>=400&&t.status<500)break}catch(e){if(a===t)return!1;await new Promise(e=>setTimeout(e,500*(a+1)))}return!1};(0,r.useEffect)(()=>{if(!(null==e?void 0:e.id)||!(null==e?void 0:e.created_at)||!(null==e?void 0:e.url)||!f)return;let t=e.id;if(!c[t]){if(m[t]&&x[t])(async()=>{if(!await z(e.url)){let e=m[t];E(t,e),h(e=>{let a={...e};return delete a[t],a})}})();else if(x[t]){let a=setInterval(async()=>{if(!await z(e.url)&&!c[t]){let a=(null==e?void 0:e.id)===t,s=m[t];E(t,a?T(e.created_at):s||T(e.created_at)),s&&!a&&h(e=>{let a={...e};return delete a[t],a})}},1e4);return()=>clearInterval(a)}}},[e,c,m,x,f]);let O=(0,r.useRef)(null);(0,r.useEffect)(()=>{let t=O.current;if((null==t?void 0:t.id)&&t.id!==(null==e?void 0:e.id)&&f){let e=t.id;if(!A.current[e])try{let a=T(t.created_at);h(t=>({...t,[e]:a}));let s=P("dashboard-storage",e),r=localStorage.getItem(s);if(r){let t=JSON.parse(r);t.dashboardCloseTimes={...t.dashboardCloseTimes,[e]:a},localStorage.setItem(s,JSON.stringify(t))}}catch(e){}}O.current=e},[null==e?void 0:e.id,f]),(0,r.useEffect)(()=>{if(!f)return;let t=Object.entries(m);if(0===t.length)return;let a=async()=>{for(let[a,s]of t){if(c[a]||(null==e?void 0:e.id)===a||!x[a])continue;let t=JSON.parse(localStorage.getItem("klt-dashboards")||"[]").find(e=>e.id===a);(null==t?void 0:t.url)&&(await z(t.url)||(E(a,s),h(e=>{let t={...e};return delete t[a],t})))}};a();let s=setInterval(a,15e3);return()=>clearInterval(s)},[m,c,null==e?void 0:e.id,f]),(0,r.useEffect)(()=>{if(null==e?void 0:e.id)try{let t=P("dashboard-storage",e.id);localStorage.setItem(t,JSON.stringify({dashboardData:l,chartHistories:o,dashboardStopTimes:c,dashboardCloseTimes:m,dashboardOpenedStatus:x}))}catch(e){}},[l,o,c,m,x,null==e?void 0:e.id]),(0,r.useEffect)(()=>{if(!(null==e?void 0:e.id)||!(null==e?void 0:e.created_at))return;let t=()=>{if(!A.current[e.id])try{let t=T(e.created_at);h(a=>({...a,[e.id]:t}));let a=P("dashboard-storage",e.id),s=localStorage.getItem(a);if(s){let r=JSON.parse(s);r.dashboardCloseTimes={...r.dashboardCloseTimes,[e.id]:t},r.dashboardOpenedStatus={...r.dashboardOpenedStatus,[e.id]:!0},localStorage.setItem(a,JSON.stringify(r))}}catch(e){}};return window.addEventListener("beforeunload",t),()=>window.removeEventListener("beforeunload",t)},[null==e?void 0:e.id,null==e?void 0:e.created_at]);let D=async function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;if(!e)return[];for(let a=0;a<=t;a++)try{if(0===a){let e=sessionStorage.getItem("sw-unregistered");e&&Date.now()-parseInt(e)<3e3&&await new Promise(e=>setTimeout(e,200))}let t=await fetch(e,{cache:"no-store",signal:AbortSignal.timeout(3e3+500*a)});if(!t.ok)throw Error("HTTP ".concat(t.status,": ").concat(t.statusText));let s=await t.json();if(!Array.isArray(s))return[];let r=[],n=[];return s.forEach((e,t)=>{if(!e||"number"!=typeof e.vu_id)return void n.push({index:t,item:e,reason:"Missing or invalid vu_id"});let a=w.safeParse(e);a.success?r.push(a.data):n.push({index:t,item:{vu_id:e.vu_id},reason:"Schema validation failed",errors:a.error.issues})}),r}catch(e){if(a===t)return[];await new Promise(e=>setTimeout(e,500*(a+1)))}return[]};(0,r.useEffect)(()=>{var t;let a;if(!(null==e?void 0:e.url)||!f)return;let s=!!c[y],r=!!A.current[y],n=!!(null==(t=l[y])?void 0:t.length)&&(s||r);if(s||r||n)return;let o=!0,d=0,u=async()=>{var t;let s=A.current[y]||c[y];if(s||(null==(t=S.current[y])?void 0:t.length)>0&&s)return void clearInterval(a);try{let t=await D(e.url);if(A.current[y]||c[y])return void clearInterval(a);o&&(Array.isArray(t)&&t.length>0?(i(e=>({...e,[y]:t})),d=0):d++)}catch(e){d++}if(d>=3&&a&&(clearInterval(a),!A.current[y])){let t=I.current[y],a=C.current[y],s=(null==e?void 0:e.id)===y;E(y,s&&(null==e?void 0:e.created_at)?T(e.created_at):a&&t?a:t&&(null==e?void 0:e.created_at)?T(e.created_at):"0s"),a&&t&&!s&&h(e=>{let t={...e};return delete t[y],t})}};return u(),a=setInterval(u,1e3),()=>{o=!1,a&&clearInterval(a)}},[e,y,c,f]);let Y=N.length,M=!!c[y];return(0,s.jsxs)(g,{children:[(0,s.jsx)(V,{selectedDashboard:e,onSelectDashboard:t,onDashboardDeleted:a=>{i(e=>{let t={...e};return delete t[a],t}),d(e=>{let t={...e};return delete t[a],t}),u(e=>{let t={...e};return delete t[a],t}),h(e=>{let t={...e};return delete t[a],t}),p(e=>{let t={...e};return delete t[a],t}),(null==e?void 0:e.id)===a&&t(null)},currentView:a,onViewChange:n}),(0,s.jsxs)(j,{children:[(0,s.jsxs)("header",{className:"flex h-12 items-center gap-2 border-b px-4",children:[(0,s.jsx)(b,{className:"text-orange-900"}),(0,s.jsxs)("h1",{className:"text-lg font-semibold text-orange-900",children:["KLT Dashboard","security"===a&&" - Security Report"]}),e&&(0,s.jsxs)("span",{className:"text-xs text-orange-500 ml-auto",children:["Dashboard: ",e.id]})]}),(0,s.jsx)("div",{className:"p-6",children:"dashboard"===a?(0,s.jsxs)("div",{className:"@container/main flex flex-col gap-4 md:gap-6",children:[(0,s.jsx)(K,{lttoken:null!=e?e:void 0,stopped:M,stopTime:c[y],currentVUCount:Y}),(0,s.jsx)(e4,{vuData:N,chartHistory:_,setChartHistory:e=>{d(t=>({...t,[y]:e(t[y]||{overall:[],perStep:{},perVU:{}})}))},isCompleted:M}),(0,s.jsx)(eE,{data:N,isCompleted:M})]}):"security"===a&&(null==e?void 0:e.security_report)?(0,s.jsx)(tt,{securityReport:e.security_report}):(0,s.jsx)("div",{className:"flex items-center justify-center h-64",children:(0,s.jsx)("div",{className:"text-center text-orange-600",children:(0,s.jsx)("p",{children:"No security report available for this dashboard."})})})})]})]})}function to(){let{selectedDashboard:e,setSelectedDashboard:t}=L(),a=(0,n.useSearchParams)(),[l,i]=(0,r.useState)(!1),[o,d]=(0,r.useState)(null),c=()=>{try{let e=localStorage.getItem("klt-dashboards");if(e)return JSON.parse(e)}catch(e){}return[]},u=e=>{try{let t=c(),a=t.findIndex(t=>t.id===e.id);a>=0?t[a]=e:t.unshift(e),localStorage.setItem("klt-dashboards",JSON.stringify(t))}catch(e){}},m=e=>{let a=c().find(t=>t.id===e);return!!a&&(t(a),!0)};(0,r.useEffect)(()=>{if(l){let t=setTimeout(()=>{i(!1),e||o||d("Request timed out. Using cached data if available.")},1e4);return()=>clearTimeout(t)}},[l,e,o]);let h=async function(e){let a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;i(!0),d(null);try{for(let s=0;s<=a;s++)try{if(0===s){let e=sessionStorage.getItem("sw-unregistered");e&&Date.now()-parseInt(e)<3e3&&await new Promise(e=>setTimeout(e,500))}let a=localStorage.getItem("disable-sw");localStorage.setItem("disable-sw","true");let r=await fetch("http://localhost:2345/dashboards",{cache:"no-store",signal:AbortSignal.timeout(3e3+1e3*s)});if(!r.ok)throw Error("HTTP error! status: ".concat(r.status));let n=await r.json();if(n&&n.id&&(u(n),n.id===e&&t(n)),null===a?localStorage.removeItem("disable-sw"):localStorage.setItem("disable-sw",a),n.id===e)return;throw Error("Dashboard ID mismatch: expected ".concat(e,", got ").concat(n.id))}catch(t){let e=localStorage.getItem("disable-sw");if("true"===e&&localStorage.removeItem("disable-sw"),s===a)throw t;await new Promise(e=>setTimeout(e,500*(s+1)))}}catch(t){d(t instanceof Error?t.message:"Failed to fetch dashboard"),m(e)?d(null):d("Dashboard token unavailable. The server may have closed after serving the initial request.")}finally{i(!1)}};return((0,r.useEffect)(()=>{let s=null==a?void 0:a.get("dashboard");if(s){if((null==e?void 0:e.id)===s)return;m(s)?setTimeout(()=>{h(s).catch(()=>{})},100):h(s)}else t(null)},[null==a?void 0:a.get("dashboard")]),null==a?void 0:a.get("dashboard"))?l?(0,s.jsx)("div",{className:"flex h-screen items-center justify-center",children:(0,s.jsx)("div",{className:"text-orange-900",children:"Loading dashboard..."})}):o&&!e?(0,s.jsx)("div",{className:"flex h-screen items-center justify-center",children:(0,s.jsxs)("div",{className:"text-center max-w-md",children:[(0,s.jsx)("div",{className:"text-red-600 mb-2",children:"Failed to load dashboard"}),(0,s.jsx)("div",{className:"text-sm text-gray-600 mb-4",children:o}),o.includes("Dashboard token unavailable")&&(0,s.jsxs)("div",{className:"text-xs text-orange-600 bg-orange-50 p-3 rounded",children:[(0,s.jsx)("strong",{children:"Note:"})," The dashboard server only serves one request per session. If you've unregistered the service worker, the token may no longer be available. Try restarting the load test to generate a new token."]})]})}):(0,s.jsx)(ti,{}):(0,s.jsx)(tl,{})}function td(){return(0,s.jsx)(U,{children:(0,s.jsx)(r.Suspense,{fallback:(0,s.jsx)("div",{className:"flex h-screen items-center justify-center",children:(0,s.jsx)("div",{className:"text-orange-900",children:"Loading dashboard..."})}),children:(0,s.jsx)(to,{})})})}}},e=>{e.O(0,[547,441,964,358],()=>e(e.s=2769)),_N_E=e.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/framework-7c95b8e5103c9e90.js b/keploy/pkg/service/load/out/_next/static/chunks/framework-7c95b8e5103c9e90.js deleted file mode 100644 index 458bbbf..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/framework-7c95b8e5103c9e90.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[593],{2167:(e,t,n)=>{var r=n(5364),l=Symbol.for("react.transitional.element"),a=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),i=Symbol.for("react.strict_mode"),u=Symbol.for("react.profiler"),s=Symbol.for("react.consumer"),c=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),d=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),h=Symbol.iterator,g={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,v={};function b(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}function k(){}function w(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}b.prototype.isReactComponent={},b.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},b.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},k.prototype=b.prototype;var S=w.prototype=new k;S.constructor=w,y(S,b.prototype),S.isPureReactComponent=!0;var x=Array.isArray,E={H:null,A:null,T:null,S:null,V:null},C=Object.prototype.hasOwnProperty;function _(e,t,n,r,a,o){return{$$typeof:l,type:e,key:t,ref:void 0!==(n=o.ref)?n:null,props:o}}function P(e){return"object"==typeof e&&null!==e&&e.$$typeof===l}var z=/\/+/g;function N(e,t){var n,r;return"object"==typeof e&&null!==e&&null!=e.key?(n=""+e.key,r={"=":"=0",":":"=2"},"$"+n.replace(/[=:]/g,function(e){return r[e]})):t.toString(36)}function T(){}function L(e,t,n){if(null==e)return e;var r=[],o=0;return!function e(t,n,r,o,i){var u,s,c,f=typeof t;("undefined"===f||"boolean"===f)&&(t=null);var d=!1;if(null===t)d=!0;else switch(f){case"bigint":case"string":case"number":d=!0;break;case"object":switch(t.$$typeof){case l:case a:d=!0;break;case m:return e((d=t._init)(t._payload),n,r,o,i)}}if(d)return i=i(t),d=""===o?"."+N(t,0):o,x(i)?(r="",null!=d&&(r=d.replace(z,"$&/")+"/"),e(i,n,r,"",function(e){return e})):null!=i&&(P(i)&&(u=i,s=r+(null==i.key||t&&t.key===i.key?"":(""+i.key).replace(z,"$&/")+"/")+d,i=_(u.type,s,void 0,void 0,void 0,u.props)),n.push(i)),1;d=0;var p=""===o?".":o+":";if(x(t))for(var g=0;g{e.exports=n(5919)},4232:(e,t,n)=>{e.exports=n(2167)},4279:(e,t,n)=>{var r,l=n(5364),a=n(2786),o=n(4232),i=n(8477);function u(e){var t="https://react.dev/errors/"+e;if(1I||(e.current=M[I],M[I]=null,I--)}function H(e,t){M[++I]=e.current,e.current=t}var $=U(null),V=U(null),B=U(null),Q=U(null);function W(e,t){switch(H(B,t),H(V,e),H($,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?si(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)e=su(t=si(t),e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}j($),H($,e)}function q(){j($),j(V),j(B)}function K(e){null!==e.memoizedState&&H(Q,e);var t=$.current,n=su(t,e.type);t!==n&&(H(V,e),H($,n))}function Y(e){V.current===e&&(j($),j(V)),Q.current===e&&(j(Q),sX._currentValue=F)}var G=Object.prototype.hasOwnProperty,X=a.unstable_scheduleCallback,Z=a.unstable_cancelCallback,J=a.unstable_shouldYield,ee=a.unstable_requestPaint,et=a.unstable_now,en=a.unstable_getCurrentPriorityLevel,er=a.unstable_ImmediatePriority,el=a.unstable_UserBlockingPriority,ea=a.unstable_NormalPriority,eo=a.unstable_LowPriority,ei=a.unstable_IdlePriority,eu=a.log,es=a.unstable_setDisableYieldValue,ec=null,ef=null;function ed(e){if("function"==typeof eu&&es(e),ef&&"function"==typeof ef.setStrictMode)try{ef.setStrictMode(ec,e)}catch(e){}}var ep=Math.clz32?Math.clz32:function(e){return 0==(e>>>=0)?32:31-(em(e)/eh|0)|0},em=Math.log,eh=Math.LN2,eg=256,ey=4194304;function ev(e){var t=42&e;if(0!==t)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return 4194048&e;case 4194304:case 8388608:case 0x1000000:case 0x2000000:return 0x3c00000&e;case 0x4000000:return 0x4000000;case 0x8000000:return 0x8000000;case 0x10000000:return 0x10000000;case 0x20000000:return 0x20000000;case 0x40000000:return 0;default:return e}}function eb(e,t,n){var r=e.pendingLanes;if(0===r)return 0;var l=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var i=0x7ffffff&r;return 0!==i?0!=(r=i&~a)?l=ev(r):0!=(o&=i)?l=ev(o):n||0!=(n=i&~e)&&(l=ev(n)):0!=(i=r&~a)?l=ev(i):0!==o?l=ev(o):n||0!=(n=r&~e)&&(l=ev(n)),0===l?0:0!==t&&t!==l&&0==(t&a)&&((a=l&-l)>=(n=t&-t)||32===a&&0!=(4194048&n))?t:l}function ek(e,t){return 0==(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)}function ew(){var e=eg;return 0==(4194048&(eg<<=1))&&(eg=256),e}function eS(){var e=ey;return 0==(0x3c00000&(ey<<=1))&&(ey=4194304),e}function ex(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function eE(e,t){e.pendingLanes|=t,0x10000000!==t&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function eC(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-ep(t);e.entangledLanes|=t,e.entanglements[r]=0x40000000|e.entanglements[r]|4194090&n}function e_(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-ep(n),l=1<)":-1l||u[r]!==s[l]){var c="\n"+u[r].replace(" at new "," at ");return e.displayName&&c.includes("")&&(c=c.replace("",e.displayName)),c}while(1<=r&&0<=l);break}}}finally{e2=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:"")?e1(n):""}function e4(e){try{var t="";do t+=function(e){switch(e.tag){case 26:case 27:case 5:return e1(e.type);case 16:return e1("Lazy");case 13:return e1("Suspense");case 19:return e1("SuspenseList");case 0:case 15:return e3(e.type,!1);case 11:return e3(e.type.render,!1);case 1:return e3(e.type,!0);case 31:return e1("Activity");default:return""}}(e),e=e.return;while(e);return t}catch(e){return"\nError generating stack: "+e.message+"\n"+e.stack}}function e8(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":case"object":return e;default:return""}}function e6(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===t||"radio"===t)}function e5(e){e._valueTracker||(e._valueTracker=function(e){var t=e6(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&void 0!==n&&"function"==typeof n.get&&"function"==typeof n.set){var l=n.get,a=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(e){r=""+e,a.call(this,e)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(e){r=""+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e))}function e9(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=e6(e)?e.checked?"true":"false":e.value),(e=r)!==n&&(t.setValue(e),!0)}function e7(e){if(void 0===(e=e||("undefined"!=typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}var te=/[\n"\\]/g;function tt(e){return e.replace(te,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function tn(e,t,n,r,l,a,o,i){e.name="",null!=o&&"function"!=typeof o&&"symbol"!=typeof o&&"boolean"!=typeof o?e.type=o:e.removeAttribute("type"),null!=t?"number"===o?(0===t&&""===e.value||e.value!=t)&&(e.value=""+e8(t)):e.value!==""+e8(t)&&(e.value=""+e8(t)):"submit"!==o&&"reset"!==o||e.removeAttribute("value"),null!=t?tl(e,o,e8(t)):null!=n?tl(e,o,e8(n)):null!=r&&e.removeAttribute("value"),null==l&&null!=a&&(e.defaultChecked=!!a),null!=l&&(e.checked=l&&"function"!=typeof l&&"symbol"!=typeof l),null!=i&&"function"!=typeof i&&"symbol"!=typeof i&&"boolean"!=typeof i?e.name=""+e8(i):e.removeAttribute("name")}function tr(e,t,n,r,l,a,o,i){if(null!=a&&"function"!=typeof a&&"symbol"!=typeof a&&"boolean"!=typeof a&&(e.type=a),null!=t||null!=n){if(("submit"===a||"reset"===a)&&null==t)return;n=null!=n?""+e8(n):"",t=null!=t?""+e8(t):n,i||t===e.value||(e.value=t),e.defaultValue=t}r="function"!=typeof(r=null!=r?r:l)&&"symbol"!=typeof r&&!!r,e.checked=i?e.checked:!!r,e.defaultChecked=!!r,null!=o&&"function"!=typeof o&&"symbol"!=typeof o&&"boolean"!=typeof o&&(e.name=o)}function tl(e,t,n){"number"===t&&e7(e.ownerDocument)===e||e.defaultValue===""+n||(e.defaultValue=""+n)}function ta(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l=ne),nr=!1;function nl(e,t){switch(e){case"keyup":return -1!==t9.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function na(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var no=!1,ni={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function nu(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!ni[e.type]:"textarea"===t}function ns(e,t,n,r){tv?tb?tb.push(r):tb=[r]:tv=r,0<(t=u3(t,"onChange")).length&&(n=new tH("onChange","change",null,n,r),e.push({event:n,listeners:t}))}var nc=null,nf=null;function nd(e){uY(e,0)}function np(e){if(e9(e$(e)))return e}function nm(e,t){if("change"===e)return t}var nh=!1;if(tE){if(tE){var ng="oninput"in document;if(!ng){var ny=document.createElement("div");ny.setAttribute("oninput","return;"),ng="function"==typeof ny.oninput}r=ng}else r=!1;nh=r&&(!document.documentMode||9=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=n_(r)}}function nz(e){e=null!=e&&null!=e.ownerDocument&&null!=e.ownerDocument.defaultView?e.ownerDocument.defaultView:window;for(var t=e7(e.document);t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(e){n=!1}if(n)e=t.contentWindow;else break;t=e7(e.document)}return t}function nN(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var nT=tE&&"documentMode"in document&&11>=document.documentMode,nL=null,nO=null,nR=null,nD=!1;function nA(e,t,n){var r=n.window===n?n.document:9===n.nodeType?n:n.ownerDocument;nD||null==nL||nL!==e7(r)||(r="selectionStart"in(r=nL)&&nN(r)?{start:r.selectionStart,end:r.selectionEnd}:{anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},nR&&nC(nR,r)||(nR=r,0<(r=u3(nO,"onSelect")).length&&(t=new tH("onSelect","select",null,t,n),e.push({event:t,listeners:r}),t.target=nL)))}function nF(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var nM={animationend:nF("Animation","AnimationEnd"),animationiteration:nF("Animation","AnimationIteration"),animationstart:nF("Animation","AnimationStart"),transitionrun:nF("Transition","TransitionRun"),transitionstart:nF("Transition","TransitionStart"),transitioncancel:nF("Transition","TransitionCancel"),transitionend:nF("Transition","TransitionEnd")},nI={},nU={};function nj(e){if(nI[e])return nI[e];if(!nM[e])return e;var t,n=nM[e];for(t in n)if(n.hasOwnProperty(t)&&t in nU)return nI[e]=n[t];return e}tE&&(nU=document.createElement("div").style,"AnimationEvent"in window||(delete nM.animationend.animation,delete nM.animationiteration.animation,delete nM.animationstart.animation),"TransitionEvent"in window||delete nM.transitionend.transition);var nH=nj("animationend"),n$=nj("animationiteration"),nV=nj("animationstart"),nB=nj("transitionrun"),nQ=nj("transitionstart"),nW=nj("transitioncancel"),nq=nj("transitionend"),nK=new Map,nY="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function nG(e,t){nK.set(e,t),eq(t,[e])}nY.push("scrollEnd");var nX=new WeakMap;function nZ(e,t){if("object"==typeof e&&null!==e){var n=nX.get(e);return void 0!==n?n:(t={value:e,source:t,stack:e4(t)},nX.set(e,t),t)}return{value:e,source:t,stack:e4(t)}}var nJ=[],n0=0,n1=0;function n2(){for(var e=n0,t=n1=n0=0;t>=o,l-=o,rh=1<<32-ep(t)+l|n<a?a:8;var o=D.T,i={};D.T=i,aj(e,!1,t,n);try{var u=l(),s=D.S;if(null!==s&&s(i,u),null!==u&&"object"==typeof u&&"function"==typeof u.then){var c,f,d=(c=[],f={status:"pending",value:null,reason:null,then:function(e){c.push(e)}},u.then(function(){f.status="fulfilled",f.value=r;for(var e=0;eh?(g=f,f=null):g=f.sibling;var y=p(l,f,i[h],u);if(null===y){null===f&&(f=g);break}e&&f&&null===y.alternate&&t(l,f),o=a(y,o,h),null===c?s=y:c.sibling=y,c=y,f=g}if(h===i.length)return n(l,f),rx&&ry(l,h),s;if(null===f){for(;hg?(y=h,h=null):y=h.sibling;var b=p(l,h,v.value,s);if(null===b){null===h&&(h=y);break}e&&h&&null===b.alternate&&t(l,h),o=a(b,o,g),null===f?c=b:f.sibling=b,f=b,h=y}if(v.done)return n(l,h),rx&&ry(l,g),c;if(null===h){for(;!v.done;g++,v=i.next())null!==(v=d(l,v.value,s))&&(o=a(v,o,g),null===f?c=v:f.sibling=v,f=v);return rx&&ry(l,g),c}for(h=r(h);!v.done;g++,v=i.next())null!==(v=m(h,l,g,v.value,s))&&(e&&null!==v.alternate&&h.delete(null===v.key?g:v.key),o=a(v,o,g),null===f?c=v:f.sibling=v,f=v);return e&&h.forEach(function(e){return t(l,e)}),rx&&ry(l,g),c}(s,c,f=b.call(f),v)}if("function"==typeof f.then)return i(s,c,aG(f),v);if(f.$$typeof===S)return i(s,c,rQ(s,f),v);aZ(s,f)}return"string"==typeof f&&""!==f||"number"==typeof f||"bigint"==typeof f?(f=""+f,null!==c&&6===c.tag?(n(s,c.sibling),(v=l(c,f)).return=s):(n(s,c),(v=ro(f,s.mode,v)).return=s),o(s=v)):n(s,c)}(i,s,c,f);return aK=null,v}catch(e){if(e===r7||e===lt)throw e;var b=re(29,e,null,i.mode);return b.lanes=f,b.return=i,b}finally{}}}var a1=a0(!0),a2=a0(!1),a3=U(null),a4=null;function a8(e){var t=e.alternate;H(a7,1&a7.current),H(a3,e),null===a4&&(null===t||null!==lw.current?a4=e:null!==t.memoizedState&&(a4=e))}function a6(e){if(22===e.tag){if(H(a7,a7.current),H(a3,e),null===a4){var t=e.alternate;null!==t&&null!==t.memoizedState&&(a4=e)}}else a5(e)}function a5(){H(a7,a7.current),H(a3,a3.current)}function a9(e){j(a3),a4===e&&(a4=null),j(a7)}var a7=U(0);function oe(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||"$?"===n.data||sb(n)))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(0!=(128&t.flags))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function ot(e,t,n,r){n=null==(n=n(r,t=e.memoizedState))?t:p({},t,n),e.memoizedState=n,0===e.lanes&&(e.updateQueue.baseState=n)}var on={enqueueSetState:function(e,t,n){e=e._reactInternals;var r=i6(),l=ld(r);l.payload=t,null!=n&&(l.callback=n),null!==(t=lp(e,l,r))&&(i9(t,e,r),lm(t,e,r))},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var r=i6(),l=ld(r);l.tag=1,l.payload=t,null!=n&&(l.callback=n),null!==(t=lp(e,l,r))&&(i9(t,e,r),lm(t,e,r))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=i6(),r=ld(n);r.tag=2,null!=t&&(r.callback=t),null!==(t=lp(e,r,n))&&(i9(t,e,n),lm(t,e,n))}};function or(e,t,n,r,l,a,o){return"function"==typeof(e=e.stateNode).shouldComponentUpdate?e.shouldComponentUpdate(r,a,o):!t.prototype||!t.prototype.isPureReactComponent||!nC(n,r)||!nC(l,a)}function ol(e,t,n,r){e=t.state,"function"==typeof t.componentWillReceiveProps&&t.componentWillReceiveProps(n,r),"function"==typeof t.UNSAFE_componentWillReceiveProps&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&on.enqueueReplaceState(t,t.state,null)}function oa(e,t){var n=t;if("ref"in t)for(var r in n={},t)"ref"!==r&&(n[r]=t[r]);if(e=e.defaultProps)for(var l in n===t&&(n=p({},n)),e)void 0===n[l]&&(n[l]=e[l]);return n}var oo="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof l&&"function"==typeof l.emit)return void l.emit("uncaughtException",e);console.error(e)};function oi(e){oo(e)}function ou(e){console.error(e)}function os(e){oo(e)}function oc(e,t){try{(0,e.onUncaughtError)(t.value,{componentStack:t.stack})}catch(e){setTimeout(function(){throw e})}}function of(e,t,n){try{(0,e.onCaughtError)(n.value,{componentStack:n.stack,errorBoundary:1===t.tag?t.stateNode:null})}catch(e){setTimeout(function(){throw e})}}function od(e,t,n){return(n=ld(n)).tag=3,n.payload={element:null},n.callback=function(){oc(e,t)},n}function op(e){return(e=ld(e)).tag=3,e}function om(e,t,n,r){var l=n.type.getDerivedStateFromError;if("function"==typeof l){var a=r.value;e.payload=function(){return l(a)},e.callback=function(){of(t,n,r)}}var o=n.stateNode;null!==o&&"function"==typeof o.componentDidCatch&&(e.callback=function(){of(t,n,r),"function"!=typeof l&&(null===iG?iG=new Set([this]):iG.add(this));var e=r.stack;this.componentDidCatch(r.value,{componentStack:null!==e?e:""})})}var oh=Error(u(461)),og=!1;function oy(e,t,n,r){t.child=null===e?a2(t,null,n,r):a1(t,e.child,n,r)}function ov(e,t,n,r,l){n=n.render;var a=t.ref;if("ref"in r){var o={};for(var i in r)"ref"!==i&&(o[i]=r[i])}else o=r;return(rV(t),r=lU(e,t,n,o,a,l),i=lV(),null===e||og)?(rx&&i&&rb(t),t.flags|=1,oy(e,t,r,l),t.child):(lB(e,t,l),oI(e,t,l))}function ob(e,t,n,r,l){if(null===e){var a=n.type;return"function"!=typeof a||rt(a)||void 0!==a.defaultProps||null!==n.compare?((e=rl(n.type,null,r,t,t.mode,l)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=a,ok(e,t,a,r,l))}if(a=e.child,!oU(e,l)){var o=a.memoizedProps;if((n=null!==(n=n.compare)?n:nC)(o,r)&&e.ref===t.ref)return oI(e,t,l)}return t.flags|=1,(e=rn(a,r)).ref=t.ref,e.return=t,t.child=e}function ok(e,t,n,r,l){if(null!==e){var a=e.memoizedProps;if(nC(a,r)&&e.ref===t.ref)if(og=!1,t.pendingProps=r=a,!oU(e,l))return t.lanes=e.lanes,oI(e,t,l);else 0!=(131072&e.flags)&&(og=!0)}return oE(e,t,n,r,l)}function ow(e,t,n){var r=t.pendingProps,l=r.children,a=null!==e?e.memoizedState:null;if("hidden"===r.mode){if(0!=(128&t.flags)){if(r=null!==a?a.baseLanes|n:n,null!==e){for(a=0,l=t.child=e.child;null!==l;)a=a|l.lanes|l.childLanes,l=l.sibling;t.childLanes=a&~r}else t.childLanes=0,t.child=null;return oS(e,t,r,n)}if(0==(0x20000000&n))return t.lanes=t.childLanes=0x20000000,oS(e,t,null!==a?a.baseLanes|n:n,n);t.memoizedState={baseLanes:0,cachePool:null},null!==e&&r5(t,null!==a?a.cachePool:null),null!==a?lx(t,a):lE(),a6(t)}else null!==a?(r5(t,a.cachePool),lx(t,a),a5(t),t.memoizedState=null):(null!==e&&r5(t,null),lE(),a5(t));return oy(e,t,l,n),t.child}function oS(e,t,n,r){var l=r6();return t.memoizedState={baseLanes:n,cachePool:l=null===l?null:{parent:rG._currentValue,pool:l}},null!==e&&r5(t,null),lE(),a6(t),null!==e&&rH(e,t,r,!0),null}function ox(e,t){var n=t.ref;if(null===n)null!==e&&null!==e.ref&&(t.flags|=4194816);else{if("function"!=typeof n&&"object"!=typeof n)throw Error(u(284));(null===e||e.ref!==n)&&(t.flags|=4194816)}}function oE(e,t,n,r,l){return(rV(t),n=lU(e,t,n,r,void 0,l),r=lV(),null===e||og)?(rx&&r&&rb(t),t.flags|=1,oy(e,t,n,l),t.child):(lB(e,t,l),oI(e,t,l))}function oC(e,t,n,r,l,a){return(rV(t),t.updateQueue=null,n=lH(t,r,n,l),lj(e),r=lV(),null===e||og)?(rx&&r&&rb(t),t.flags|=1,oy(e,t,n,a),t.child):(lB(e,t,a),oI(e,t,a))}function o_(e,t,n,r,l){if(rV(t),null===t.stateNode){var a=n9,o=n.contextType;"object"==typeof o&&null!==o&&(a=rB(o)),t.memoizedState=null!==(a=new n(r,a)).state&&void 0!==a.state?a.state:null,a.updater=on,t.stateNode=a,a._reactInternals=t,(a=t.stateNode).props=r,a.state=t.memoizedState,a.refs={},lc(t),o=n.contextType,a.context="object"==typeof o&&null!==o?rB(o):n9,a.state=t.memoizedState,"function"==typeof(o=n.getDerivedStateFromProps)&&(ot(t,n,o,r),a.state=t.memoizedState),"function"==typeof n.getDerivedStateFromProps||"function"==typeof a.getSnapshotBeforeUpdate||"function"!=typeof a.UNSAFE_componentWillMount&&"function"!=typeof a.componentWillMount||(o=a.state,"function"==typeof a.componentWillMount&&a.componentWillMount(),"function"==typeof a.UNSAFE_componentWillMount&&a.UNSAFE_componentWillMount(),o!==a.state&&on.enqueueReplaceState(a,a.state,null),lv(t,r,a,l),ly(),a.state=t.memoizedState),"function"==typeof a.componentDidMount&&(t.flags|=4194308),r=!0}else if(null===e){a=t.stateNode;var i=t.memoizedProps,u=oa(n,i);a.props=u;var s=a.context,c=n.contextType;o=n9,"object"==typeof c&&null!==c&&(o=rB(c));var f=n.getDerivedStateFromProps;c="function"==typeof f||"function"==typeof a.getSnapshotBeforeUpdate,i=t.pendingProps!==i,c||"function"!=typeof a.UNSAFE_componentWillReceiveProps&&"function"!=typeof a.componentWillReceiveProps||(i||s!==o)&&ol(t,a,r,o),ls=!1;var d=t.memoizedState;a.state=d,lv(t,r,a,l),ly(),s=t.memoizedState,i||d!==s||ls?("function"==typeof f&&(ot(t,n,f,r),s=t.memoizedState),(u=ls||or(t,n,u,r,d,s,o))?(c||"function"!=typeof a.UNSAFE_componentWillMount&&"function"!=typeof a.componentWillMount||("function"==typeof a.componentWillMount&&a.componentWillMount(),"function"==typeof a.UNSAFE_componentWillMount&&a.UNSAFE_componentWillMount()),"function"==typeof a.componentDidMount&&(t.flags|=4194308)):("function"==typeof a.componentDidMount&&(t.flags|=4194308),t.memoizedProps=r,t.memoizedState=s),a.props=r,a.state=s,a.context=o,r=u):("function"==typeof a.componentDidMount&&(t.flags|=4194308),r=!1)}else{a=t.stateNode,lf(e,t),c=oa(n,o=t.memoizedProps),a.props=c,f=t.pendingProps,d=a.context,s=n.contextType,u=n9,"object"==typeof s&&null!==s&&(u=rB(s)),(s="function"==typeof(i=n.getDerivedStateFromProps)||"function"==typeof a.getSnapshotBeforeUpdate)||"function"!=typeof a.UNSAFE_componentWillReceiveProps&&"function"!=typeof a.componentWillReceiveProps||(o!==f||d!==u)&&ol(t,a,r,u),ls=!1,d=t.memoizedState,a.state=d,lv(t,r,a,l),ly();var p=t.memoizedState;o!==f||d!==p||ls||null!==e&&null!==e.dependencies&&r$(e.dependencies)?("function"==typeof i&&(ot(t,n,i,r),p=t.memoizedState),(c=ls||or(t,n,c,r,d,p,u)||null!==e&&null!==e.dependencies&&r$(e.dependencies))?(s||"function"!=typeof a.UNSAFE_componentWillUpdate&&"function"!=typeof a.componentWillUpdate||("function"==typeof a.componentWillUpdate&&a.componentWillUpdate(r,p,u),"function"==typeof a.UNSAFE_componentWillUpdate&&a.UNSAFE_componentWillUpdate(r,p,u)),"function"==typeof a.componentDidUpdate&&(t.flags|=4),"function"==typeof a.getSnapshotBeforeUpdate&&(t.flags|=1024)):("function"!=typeof a.componentDidUpdate||o===e.memoizedProps&&d===e.memoizedState||(t.flags|=4),"function"!=typeof a.getSnapshotBeforeUpdate||o===e.memoizedProps&&d===e.memoizedState||(t.flags|=1024),t.memoizedProps=r,t.memoizedState=p),a.props=r,a.state=p,a.context=u,r=c):("function"!=typeof a.componentDidUpdate||o===e.memoizedProps&&d===e.memoizedState||(t.flags|=4),"function"!=typeof a.getSnapshotBeforeUpdate||o===e.memoizedProps&&d===e.memoizedState||(t.flags|=1024),r=!1)}return a=r,ox(e,t),r=0!=(128&t.flags),a||r?(a=t.stateNode,n=r&&"function"!=typeof n.getDerivedStateFromError?null:a.render(),t.flags|=1,null!==e&&r?(t.child=a1(t,e.child,null,l),t.child=a1(t,null,n,l)):oy(e,t,n,l),t.memoizedState=a.state,e=t.child):e=oI(e,t,l),e}function oP(e,t,n,r){return rL(),t.flags|=256,oy(e,t,n,r),t.child}var oz={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function oN(e){return{baseLanes:e,cachePool:r9()}}function oT(e,t,n){return e=null!==e?e.childLanes&~n:0,t&&(e|=i$),e}function oL(e,t,n){var r,l=t.pendingProps,a=!1,o=0!=(128&t.flags);if((r=o)||(r=(null===e||null!==e.memoizedState)&&0!=(2&a7.current)),r&&(a=!0,t.flags&=-129),r=0!=(32&t.flags),t.flags&=-33,null===e){if(rx){if(a?a8(t):a5(t),rx){var i,s=rS;if(i=s){n:{for(i=s,s=rC;8!==i.nodeType;)if(!s||null===(i=sk(i.nextSibling))){s=null;break n}s=i}null!==s?(t.memoizedState={dehydrated:s,treeContext:null!==rm?{id:rh,overflow:rg}:null,retryLane:0x20000000,hydrationErrors:null},(i=re(18,null,null,0)).stateNode=s,i.return=t,t.child=i,rw=t,rS=null,i=!0):i=!1}i||rP(t)}if(null!==(s=t.memoizedState)&&null!==(s=s.dehydrated))return sb(s)?t.lanes=32:t.lanes=0x20000000,null;a9(t)}return(s=l.children,l=l.fallback,a)?(a5(t),s=oR({mode:"hidden",children:s},a=t.mode),l=ra(l,a,n,null),s.return=t,l.return=t,s.sibling=l,t.child=s,(a=t.child).memoizedState=oN(n),a.childLanes=oT(e,r,n),t.memoizedState=oz,l):(a8(t),oO(t,s))}if(null!==(i=e.memoizedState)&&null!==(s=i.dehydrated)){if(o)256&t.flags?(a8(t),t.flags&=-257,t=oD(e,t,n)):null!==t.memoizedState?(a5(t),t.child=e.child,t.flags|=128,t=null):(a5(t),a=l.fallback,s=t.mode,l=oR({mode:"visible",children:l.children},s),a=ra(a,s,n,null),a.flags|=2,l.return=t,a.return=t,l.sibling=a,t.child=l,a1(t,e.child,null,n),(l=t.child).memoizedState=oN(n),l.childLanes=oT(e,r,n),t.memoizedState=oz,t=a);else if(a8(t),sb(s)){if(r=s.nextSibling&&s.nextSibling.dataset)var c=r.dgst;r=c,(l=Error(u(419))).stack="",l.digest=r,rR({value:l,source:null,stack:null}),t=oD(e,t,n)}else if(og||rH(e,t,n,!1),r=0!=(n&e.childLanes),og||r){if(null!==(r=iN)&&0!==(l=0!=((l=0!=(42&(l=n&-n))?1:eP(l))&(r.suspendedLanes|n))?0:l)&&l!==i.retryLane)throw i.retryLane=l,n8(e,l),i9(r,e,l),oh;"$?"===s.data||uu(),t=oD(e,t,n)}else"$?"===s.data?(t.flags|=192,t.child=e.child,t=null):(e=i.treeContext,rS=sk(s.nextSibling),rw=t,rx=!0,rE=null,rC=!1,null!==e&&(rd[rp++]=rh,rd[rp++]=rg,rd[rp++]=rm,rh=e.id,rg=e.overflow,rm=t),t=oO(t,l.children),t.flags|=4096);return t}return a?(a5(t),a=l.fallback,s=t.mode,c=(i=e.child).sibling,(l=rn(i,{mode:"hidden",children:l.children})).subtreeFlags=0x3e00000&i.subtreeFlags,null!==c?a=rn(c,a):(a=ra(a,s,n,null),a.flags|=2),a.return=t,l.return=t,l.sibling=a,t.child=l,l=a,a=t.child,null===(s=e.child.memoizedState)?s=oN(n):(null!==(i=s.cachePool)?(c=rG._currentValue,i=i.parent!==c?{parent:c,pool:c}:i):i=r9(),s={baseLanes:s.baseLanes|n,cachePool:i}),a.memoizedState=s,a.childLanes=oT(e,r,n),t.memoizedState=oz,l):(a8(t),e=(n=e.child).sibling,(n=rn(n,{mode:"visible",children:l.children})).return=t,n.sibling=null,null!==e&&(null===(r=t.deletions)?(t.deletions=[e],t.flags|=16):r.push(e)),t.child=n,t.memoizedState=null,n)}function oO(e,t){return(t=oR({mode:"visible",children:t},e.mode)).return=e,e.child=t}function oR(e,t){return(e=re(22,e,null,t)).lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function oD(e,t,n){return a1(t,e.child,null,n),e=oO(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function oA(e,t,n){e.lanes|=t;var r=e.alternate;null!==r&&(r.lanes|=t),rU(e.return,t,n)}function oF(e,t,n,r,l){var a=e.memoizedState;null===a?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:l}:(a.isBackwards=t,a.rendering=null,a.renderingStartTime=0,a.last=r,a.tail=n,a.tailMode=l)}function oM(e,t,n){var r=t.pendingProps,l=r.revealOrder,a=r.tail;if(oy(e,t,r.children,n),0!=(2&(r=a7.current)))r=1&r|2,t.flags|=128;else{if(null!==e&&0!=(128&e.flags))e:for(e=t.child;null!==e;){if(13===e.tag)null!==e.memoizedState&&oA(e,n,t);else if(19===e.tag)oA(e,n,t);else if(null!==e.child){e.child.return=e,e=e.child;continue}if(e===t)break;for(;null===e.sibling;){if(null===e.return||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}switch(H(a7,r),l){case"forwards":for(l=null,n=t.child;null!==n;)null!==(e=n.alternate)&&null===oe(e)&&(l=n),n=n.sibling;null===(n=l)?(l=t.child,t.child=null):(l=n.sibling,n.sibling=null),oF(t,!1,l,n,a);break;case"backwards":for(n=null,l=t.child,t.child=null;null!==l;){if(null!==(e=l.alternate)&&null===oe(e)){t.child=l;break}e=l.sibling,l.sibling=n,n=l,l=e}oF(t,!0,n,null,a);break;case"together":oF(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function oI(e,t,n){if(null!==e&&(t.dependencies=e.dependencies),iU|=t.lanes,0==(n&t.childLanes)){if(null===e)return null;else if(rH(e,t,n,!1),0==(n&t.childLanes))return null}if(null!==e&&t.child!==e.child)throw Error(u(153));if(null!==t.child){for(n=rn(e=t.child,e.pendingProps),t.child=n,n.return=t;null!==e.sibling;)e=e.sibling,(n=n.sibling=rn(e,e.pendingProps)).return=t;n.sibling=null}return t.child}function oU(e,t){return 0!=(e.lanes&t)||!!(null!==(e=e.dependencies)&&r$(e))}function oj(e,t,n){if(null!==e)if(e.memoizedProps!==t.pendingProps)og=!0;else{if(!oU(e,n)&&0==(128&t.flags))return og=!1,function(e,t,n){switch(t.tag){case 3:W(t,t.stateNode.containerInfo),rM(t,rG,e.memoizedState.cache),rL();break;case 27:case 5:K(t);break;case 4:W(t,t.stateNode.containerInfo);break;case 10:rM(t,t.type,t.memoizedProps.value);break;case 13:var r=t.memoizedState;if(null!==r){if(null!==r.dehydrated)return a8(t),t.flags|=128,null;if(0!=(n&t.child.childLanes))return oL(e,t,n);return a8(t),null!==(e=oI(e,t,n))?e.sibling:null}a8(t);break;case 19:var l=0!=(128&e.flags);if((r=0!=(n&t.childLanes))||(rH(e,t,n,!1),r=0!=(n&t.childLanes)),l){if(r)return oM(e,t,n);t.flags|=128}if(null!==(l=t.memoizedState)&&(l.rendering=null,l.tail=null,l.lastEffect=null),H(a7,a7.current),!r)return null;break;case 22:case 23:return t.lanes=0,ow(e,t,n);case 24:rM(t,rG,e.memoizedState.cache)}return oI(e,t,n)}(e,t,n);og=0!=(131072&e.flags)}else og=!1,rx&&0!=(1048576&t.flags)&&rv(t,rf,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var r=t.elementType,l=r._init;if(r=l(r._payload),t.type=r,"function"==typeof r)rt(r)?(e=oa(r,e),t.tag=1,t=o_(null,t,r,e,n)):(t.tag=0,t=oE(null,t,r,e,n));else{if(null!=r){if((l=r.$$typeof)===x){t.tag=11,t=ov(null,t,r,e,n);break e}else if(l===_){t.tag=14,t=ob(null,t,r,e,n);break e}}throw Error(u(306,t=function e(t){if(null==t)return null;if("function"==typeof t)return t.$$typeof===O?null:t.displayName||t.name||null;if("string"==typeof t)return t;switch(t){case y:return"Fragment";case b:return"Profiler";case v:return"StrictMode";case E:return"Suspense";case C:return"SuspenseList";case z:return"Activity"}if("object"==typeof t)switch(t.$$typeof){case g:return"Portal";case S:return(t.displayName||"Context")+".Provider";case w:return(t._context.displayName||"Context")+".Consumer";case x:var n=t.render;return(t=t.displayName)||(t=""!==(t=n.displayName||n.name||"")?"ForwardRef("+t+")":"ForwardRef"),t;case _:return null!==(n=t.displayName||null)?n:e(t.type)||"Memo";case P:n=t._payload,t=t._init;try{return e(t(n))}catch(e){}}return null}(r)||r,""))}}return t;case 0:return oE(e,t,t.type,t.pendingProps,n);case 1:return l=oa(r=t.type,t.pendingProps),o_(e,t,r,l,n);case 3:e:{if(W(t,t.stateNode.containerInfo),null===e)throw Error(u(387));r=t.pendingProps;var a=t.memoizedState;l=a.element,lf(e,t),lv(t,r,null,n);var o=t.memoizedState;if(rM(t,rG,r=o.cache),r!==a.cache&&rj(t,[rG],n,!0),ly(),r=o.element,a.isDehydrated)if(a={element:r,isDehydrated:!1,cache:o.cache},t.updateQueue.baseState=a,t.memoizedState=a,256&t.flags){t=oP(e,t,r,n);break e}else if(r!==l){rR(l=nZ(Error(u(424)),t)),t=oP(e,t,r,n);break e}else for(rS=sk((e=9===(e=t.stateNode.containerInfo).nodeType?e.body:"HTML"===e.nodeName?e.ownerDocument.body:e).firstChild),rw=t,rx=!0,rE=null,rC=!0,n=a2(t,null,r,n),t.child=n;n;)n.flags=-3&n.flags|4096,n=n.sibling;else{if(rL(),r===l){t=oI(e,t,n);break e}oy(e,t,r,n)}t=t.child}return t;case 26:return ox(e,t),null===e?(n=sL(t.type,null,t.pendingProps,null))?t.memoizedState=n:rx||(n=t.type,e=t.pendingProps,(r=so(B.current).createElement(n))[eL]=t,r[eO]=e,sr(r,n,e),eB(r),t.stateNode=r):t.memoizedState=sL(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return K(t),null===e&&rx&&(r=t.stateNode=sx(t.type,t.pendingProps,B.current),rw=t,rC=!0,l=rS,sg(t.type)?(sw=l,rS=sk(r.firstChild)):rS=l),oy(e,t,t.pendingProps.children,n),ox(e,t),null===e&&(t.flags|=4194304),t.child;case 5:return null===e&&rx&&((l=r=rS)&&(null!==(r=function(e,t,n,r){for(;1===e.nodeType;){if(e.nodeName.toLowerCase()!==t.toLowerCase()){if(!r&&("INPUT"!==e.nodeName||"hidden"!==e.type))break}else if(r){if(!e[eI])switch(t){case"meta":if(!e.hasAttribute("itemprop"))break;return e;case"link":if("stylesheet"===(l=e.getAttribute("rel"))&&e.hasAttribute("data-precedence")||l!==n.rel||e.getAttribute("href")!==(null==n.href||""===n.href?null:n.href)||e.getAttribute("crossorigin")!==(null==n.crossOrigin?null:n.crossOrigin)||e.getAttribute("title")!==(null==n.title?null:n.title))break;return e;case"style":if(e.hasAttribute("data-precedence"))break;return e;case"script":if(((l=e.getAttribute("src"))!==(null==n.src?null:n.src)||e.getAttribute("type")!==(null==n.type?null:n.type)||e.getAttribute("crossorigin")!==(null==n.crossOrigin?null:n.crossOrigin))&&l&&e.hasAttribute("async")&&!e.hasAttribute("itemprop"))break;return e;default:return e}}else{if("input"!==t||"hidden"!==e.type)return e;var l=null==n.name?null:""+n.name;if("hidden"===n.type&&e.getAttribute("name")===l)return e}if(null===(e=sk(e.nextSibling)))break}return null}(r,t.type,t.pendingProps,rC))?(t.stateNode=r,rw=t,rS=sk(r.firstChild),rC=!1,l=!0):l=!1),l||rP(t)),K(t),l=t.type,a=t.pendingProps,o=null!==e?e.memoizedProps:null,r=a.children,ss(l,a)?r=null:null!==o&&ss(l,o)&&(t.flags|=32),null!==t.memoizedState&&(sX._currentValue=l=lU(e,t,l$,null,null,n)),ox(e,t),oy(e,t,r,n),t.child;case 6:return null===e&&rx&&((e=n=rS)&&(null!==(n=function(e,t,n){if(""===t)return null;for(;3!==e.nodeType;)if((1!==e.nodeType||"INPUT"!==e.nodeName||"hidden"!==e.type)&&!n||null===(e=sk(e.nextSibling)))return null;return e}(n,t.pendingProps,rC))?(t.stateNode=n,rw=t,rS=null,e=!0):e=!1),e||rP(t)),null;case 13:return oL(e,t,n);case 4:return W(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=a1(t,null,r,n):oy(e,t,r,n),t.child;case 11:return ov(e,t,t.type,t.pendingProps,n);case 7:return oy(e,t,t.pendingProps,n),t.child;case 8:case 12:return oy(e,t,t.pendingProps.children,n),t.child;case 10:return r=t.pendingProps,rM(t,t.type,r.value),oy(e,t,r.children,n),t.child;case 9:return l=t.type._context,r=t.pendingProps.children,rV(t),r=r(l=rB(l)),t.flags|=1,oy(e,t,r,n),t.child;case 14:return ob(e,t,t.type,t.pendingProps,n);case 15:return ok(e,t,t.type,t.pendingProps,n);case 19:return oM(e,t,n);case 31:return r=t.pendingProps,n=t.mode,r={mode:r.mode,children:r.children},null===e?(n=oR(r,n)).ref=t.ref:(n=rn(e.child,r)).ref=t.ref,t.child=n,n.return=t,t=n;case 22:return ow(e,t,n);case 24:return rV(t),r=rB(rG),null===e?(null===(l=r6())&&(l=iN,a=rX(),l.pooledCache=a,a.refCount++,null!==a&&(l.pooledCacheLanes|=n),l=a),t.memoizedState={parent:r,cache:l},lc(t),rM(t,rG,l)):(0!=(e.lanes&n)&&(lf(e,t),lv(t,null,null,n),ly()),l=e.memoizedState,a=t.memoizedState,l.parent!==r?(l={parent:r,cache:r},t.memoizedState=l,0===t.lanes&&(t.memoizedState=t.updateQueue.baseState=l),rM(t,rG,r)):(rM(t,rG,r=a.cache),r!==l.cache&&rj(t,[rG],n,!0))),oy(e,t,t.pendingProps.children,n),t.child;case 29:throw t.pendingProps}throw Error(u(156,t.tag))}function oH(e){e.flags|=4}function o$(e,t){if("stylesheet"!==t.type||0!=(4&t.state.loading))e.flags&=-0x1000001;else if(e.flags|=0x1000000,!sB(t)){if(null!==(t=a3.current)&&((4194048&iL)===iL?null!==a4:(0x3c00000&iL)!==iL&&0==(0x20000000&iL)||t!==a4))throw lo=ln,le;e.flags|=8192}}function oV(e,t){null!==t&&(e.flags|=4),16384&e.flags&&(t=22!==e.tag?eS():0x20000000,e.lanes|=t,iV|=t)}function oB(e,t){if(!rx)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;null!==t;)null!==t.alternate&&(n=t),t=t.sibling;null===n?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;null!==n;)null!==n.alternate&&(r=n),n=n.sibling;null===r?t||null===e.tail?e.tail=null:e.tail.sibling=null:r.sibling=null}}function oQ(e){var t=null!==e.alternate&&e.alternate.child===e.child,n=0,r=0;if(t)for(var l=e.child;null!==l;)n|=l.lanes|l.childLanes,r|=0x3e00000&l.subtreeFlags,r|=0x3e00000&l.flags,l.return=e,l=l.sibling;else for(l=e.child;null!==l;)n|=l.lanes|l.childLanes,r|=l.subtreeFlags,r|=l.flags,l.return=e,l=l.sibling;return e.subtreeFlags|=r,e.childLanes=n,t}function oW(e,t){switch(rk(t),t.tag){case 3:rI(rG),q();break;case 26:case 27:case 5:Y(t);break;case 4:q();break;case 13:a9(t);break;case 19:j(a7);break;case 10:rI(t.type);break;case 22:case 23:a9(t),lC(),null!==e&&j(r8);break;case 24:rI(rG)}}function oq(e,t){try{var n=t.updateQueue,r=null!==n?n.lastEffect:null;if(null!==r){var l=r.next;n=l;do{if((n.tag&e)===e){r=void 0;var a=n.create;n.inst.destroy=r=a()}n=n.next}while(n!==l)}}catch(e){ux(t,t.return,e)}}function oK(e,t,n){try{var r=t.updateQueue,l=null!==r?r.lastEffect:null;if(null!==l){var a=l.next;r=a;do{if((r.tag&e)===e){var o=r.inst,i=o.destroy;if(void 0!==i){o.destroy=void 0,l=t;try{i()}catch(e){ux(l,n,e)}}}r=r.next}while(r!==a)}}catch(e){ux(t,t.return,e)}}function oY(e){var t=e.updateQueue;if(null!==t){var n=e.stateNode;try{lk(t,n)}catch(t){ux(e,e.return,t)}}}function oG(e,t,n){n.props=oa(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){ux(e,t,n)}}function oX(e,t){try{var n=e.ref;if(null!==n){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;default:r=e.stateNode}"function"==typeof n?e.refCleanup=n(r):n.current=r}}catch(n){ux(e,t,n)}}function oZ(e,t){var n=e.ref,r=e.refCleanup;if(null!==n)if("function"==typeof r)try{r()}catch(n){ux(e,t,n)}finally{e.refCleanup=null,null!=(e=e.alternate)&&(e.refCleanup=null)}else if("function"==typeof n)try{n(null)}catch(n){ux(e,t,n)}else n.current=null}function oJ(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{switch(t){case"button":case"input":case"select":case"textarea":n.autoFocus&&r.focus();break;case"img":n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){ux(e,e.return,t)}}function o0(e,t,n){try{var r=e.stateNode;(function(e,t,n,r){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"input":var l=null,a=null,o=null,i=null,s=null,c=null,f=null;for(m in n){var d=n[m];if(n.hasOwnProperty(m)&&null!=d)switch(m){case"checked":case"value":break;case"defaultValue":s=d;default:r.hasOwnProperty(m)||st(e,t,m,null,r,d)}}for(var p in r){var m=r[p];if(d=n[p],r.hasOwnProperty(p)&&(null!=m||null!=d))switch(p){case"type":a=m;break;case"name":l=m;break;case"checked":c=m;break;case"defaultChecked":f=m;break;case"value":o=m;break;case"defaultValue":i=m;break;case"children":case"dangerouslySetInnerHTML":if(null!=m)throw Error(u(137,t));break;default:m!==d&&st(e,t,p,m,r,d)}}tn(e,o,i,s,c,f,a,l);return;case"select":for(a in m=o=i=p=null,n)if(s=n[a],n.hasOwnProperty(a)&&null!=s)switch(a){case"value":break;case"multiple":m=s;default:r.hasOwnProperty(a)||st(e,t,a,null,r,s)}for(l in r)if(a=r[l],s=n[l],r.hasOwnProperty(l)&&(null!=a||null!=s))switch(l){case"value":p=a;break;case"defaultValue":i=a;break;case"multiple":o=a;default:a!==s&&st(e,t,l,a,r,s)}t=i,n=o,r=m,null!=p?ta(e,!!n,p,!1):!!r!=!!n&&(null!=t?ta(e,!!n,t,!0):ta(e,!!n,n?[]:"",!1));return;case"textarea":for(i in m=p=null,n)if(l=n[i],n.hasOwnProperty(i)&&null!=l&&!r.hasOwnProperty(i))switch(i){case"value":case"children":break;default:st(e,t,i,null,r,l)}for(o in r)if(l=r[o],a=n[o],r.hasOwnProperty(o)&&(null!=l||null!=a))switch(o){case"value":p=l;break;case"defaultValue":m=l;break;case"children":break;case"dangerouslySetInnerHTML":if(null!=l)throw Error(u(91));break;default:l!==a&&st(e,t,o,l,r,a)}to(e,p,m);return;case"option":for(var h in n)p=n[h],n.hasOwnProperty(h)&&null!=p&&!r.hasOwnProperty(h)&&("selected"===h?e.selected=!1:st(e,t,h,null,r,p));for(s in r)p=r[s],m=n[s],r.hasOwnProperty(s)&&p!==m&&(null!=p||null!=m)&&("selected"===s?e.selected=p&&"function"!=typeof p&&"symbol"!=typeof p:st(e,t,s,p,r,m));return;case"img":case"link":case"area":case"base":case"br":case"col":case"embed":case"hr":case"keygen":case"meta":case"param":case"source":case"track":case"wbr":case"menuitem":for(var g in n)p=n[g],n.hasOwnProperty(g)&&null!=p&&!r.hasOwnProperty(g)&&st(e,t,g,null,r,p);for(c in r)if(p=r[c],m=n[c],r.hasOwnProperty(c)&&p!==m&&(null!=p||null!=m))switch(c){case"children":case"dangerouslySetInnerHTML":if(null!=p)throw Error(u(137,t));break;default:st(e,t,c,p,r,m)}return;default:if(td(t)){for(var y in n)p=n[y],n.hasOwnProperty(y)&&void 0!==p&&!r.hasOwnProperty(y)&&sn(e,t,y,void 0,r,p);for(f in r)p=r[f],m=n[f],r.hasOwnProperty(f)&&p!==m&&(void 0!==p||void 0!==m)&&sn(e,t,f,p,r,m);return}}for(var v in n)p=n[v],n.hasOwnProperty(v)&&null!=p&&!r.hasOwnProperty(v)&&st(e,t,v,null,r,p);for(d in r)p=r[d],m=n[d],r.hasOwnProperty(d)&&p!==m&&(null!=p||null!=m)&&st(e,t,d,p,r,m)})(r,e.type,n,t),r[eO]=t}catch(t){ux(e,e.return,t)}}function o1(e){return 5===e.tag||3===e.tag||26===e.tag||27===e.tag&&sg(e.type)||4===e.tag}function o2(e){e:for(;;){for(;null===e.sibling;){if(null===e.return||o1(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;5!==e.tag&&6!==e.tag&&18!==e.tag;){if(27===e.tag&&sg(e.type)||2&e.flags||null===e.child||4===e.tag)continue e;e.child.return=e,e=e.child}if(!(2&e.flags))return e.stateNode}}function o3(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(4!==r&&(27===r&&sg(e.type)&&(n=e.stateNode),null!==(e=e.child)))for(o3(e,t,n),e=e.sibling;null!==e;)o3(e,t,n),e=e.sibling}function o4(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,l=t.attributes;l.length;)t.removeAttributeNode(l[0]);sr(t,r,n),t[eL]=e,t[eO]=n}catch(t){ux(e,e.return,t)}}var o8=!1,o6=!1,o5=!1,o9="function"==typeof WeakSet?WeakSet:Set,o7=null;function ie(e,t,n){var r=n.flags;switch(n.tag){case 0:case 11:case 15:ip(e,n),4&r&&oq(5,n);break;case 1:if(ip(e,n),4&r)if(e=n.stateNode,null===t)try{e.componentDidMount()}catch(e){ux(n,n.return,e)}else{var l=oa(n.type,t.memoizedProps);t=t.memoizedState;try{e.componentDidUpdate(l,t,e.__reactInternalSnapshotBeforeUpdate)}catch(e){ux(n,n.return,e)}}64&r&&oY(n),512&r&&oX(n,n.return);break;case 3:if(ip(e,n),64&r&&null!==(e=n.updateQueue)){if(t=null,null!==n.child)switch(n.child.tag){case 27:case 5:case 1:t=n.child.stateNode}try{lk(e,t)}catch(e){ux(n,n.return,e)}}break;case 27:null===t&&4&r&&o4(n);case 26:case 5:ip(e,n),null===t&&4&r&&oJ(n),512&r&&oX(n,n.return);break;case 12:default:ip(e,n);break;case 13:ip(e,n),4&r&&io(e,n),64&r&&null!==(e=n.memoizedState)&&null!==(e=e.dehydrated)&&function(e,t){var n=e.ownerDocument;if("$?"!==e.data||"complete"===n.readyState)t();else{var r=function(){t(),n.removeEventListener("DOMContentLoaded",r)};n.addEventListener("DOMContentLoaded",r),e._reactRetry=r}}(e,n=uP.bind(null,n));break;case 22:if(!(r=null!==n.memoizedState||o8)){t=null!==t&&null!==t.memoizedState||o6,l=o8;var a=o6;o8=r,(o6=t)&&!a?function e(t,n,r){for(r=r&&0!=(8772&n.subtreeFlags),n=n.child;null!==n;){var l=n.alternate,a=t,o=n,i=o.flags;switch(o.tag){case 0:case 11:case 15:e(a,o,r),oq(4,o);break;case 1:if(e(a,o,r),"function"==typeof(a=(l=o).stateNode).componentDidMount)try{a.componentDidMount()}catch(e){ux(l,l.return,e)}if(null!==(a=(l=o).updateQueue)){var u=l.stateNode;try{var s=a.shared.hiddenCallbacks;if(null!==s)for(a.shared.hiddenCallbacks=null,a=0;a title"))),sr(a,r,n),a[eL]=e,eB(a),r=a;break e;case"link":var o=s$("link","href",l).get(r+(n.href||""));if(o){for(var i=0;i<\/script>",e=e.removeChild(e.firstChild);break;case"select":e="string"==typeof r.is?l.createElement("select",{is:r.is}):l.createElement("select"),r.multiple?e.multiple=!0:r.size&&(e.size=r.size);break;default:e="string"==typeof r.is?l.createElement(n,{is:r.is}):l.createElement(n)}}e[eL]=t,e[eO]=r;e:for(l=t.child;null!==l;){if(5===l.tag||6===l.tag)e.appendChild(l.stateNode);else if(4!==l.tag&&27!==l.tag&&null!==l.child){l.child.return=l,l=l.child;continue}if(l===t)break;for(;null===l.sibling;){if(null===l.return||l.return===t)break e;l=l.return}l.sibling.return=l.return,l=l.sibling}switch(t.stateNode=e,sr(e,n,r),n){case"button":case"input":case"select":case"textarea":e=!!r.autoFocus;break;case"img":e=!0;break;default:e=!1}e&&oH(t)}}return oQ(t),t.flags&=-0x1000001,null;case 6:if(e&&null!=t.stateNode)e.memoizedProps!==r&&oH(t);else{if("string"!=typeof r&&null===t.stateNode)throw Error(u(166));if(e=B.current,rT(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,null!==(l=rw))switch(l.tag){case 27:case 5:r=l.memoizedProps}e[eL]=t,(e=!!(e.nodeValue===n||null!==r&&!0===r.suppressHydrationWarning||u7(e.nodeValue,n)))||rP(t)}else(e=so(e).createTextNode(r))[eL]=t,t.stateNode=e}return oQ(t),null;case 13:if(r=t.memoizedState,null===e||null!==e.memoizedState&&null!==e.memoizedState.dehydrated){if(l=rT(t),null!==r&&null!==r.dehydrated){if(null===e){if(!l)throw Error(u(318));if(!(l=null!==(l=t.memoizedState)?l.dehydrated:null))throw Error(u(317));l[eL]=t}else rL(),0==(128&t.flags)&&(t.memoizedState=null),t.flags|=4;oQ(t),l=!1}else l=rO(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=l),l=!0;if(!l){if(256&t.flags)return a9(t),t;return a9(t),null}}if(a9(t),0!=(128&t.flags))return t.lanes=n,t;if(n=null!==r,e=null!==e&&null!==e.memoizedState,n){r=t.child,l=null,null!==r.alternate&&null!==r.alternate.memoizedState&&null!==r.alternate.memoizedState.cachePool&&(l=r.alternate.memoizedState.cachePool.pool);var a=null;null!==r.memoizedState&&null!==r.memoizedState.cachePool&&(a=r.memoizedState.cachePool.pool),a!==l&&(r.flags|=2048)}return n!==e&&n&&(t.child.flags|=8192),oV(t,t.updateQueue),oQ(t),null;case 4:return q(),null===e&&uJ(t.stateNode.containerInfo),oQ(t),null;case 10:return rI(t.type),oQ(t),null;case 19:if(j(a7),null===(l=t.memoizedState))return oQ(t),null;if(r=0!=(128&t.flags),null===(a=l.rendering))if(r)oB(l,!1);else{if(0!==iI||null!==e&&0!=(128&e.flags))for(e=t.child;null!==e;){if(null!==(a=oe(e))){for(t.flags|=128,oB(l,!1),e=a.updateQueue,t.updateQueue=e,oV(t,e),t.subtreeFlags=0,e=n,n=t.child;null!==n;)rr(n,e),n=n.sibling;return H(a7,1&a7.current|2),t.child}e=e.sibling}null!==l.tail&&et()>iK&&(t.flags|=128,r=!0,oB(l,!1),t.lanes=4194304)}else{if(!r)if(null!==(e=oe(a))){if(t.flags|=128,r=!0,e=e.updateQueue,t.updateQueue=e,oV(t,e),oB(l,!0),null===l.tail&&"hidden"===l.tailMode&&!a.alternate&&!rx)return oQ(t),null}else 2*et()-l.renderingStartTime>iK&&0x20000000!==n&&(t.flags|=128,r=!0,oB(l,!1),t.lanes=4194304);l.isBackwards?(a.sibling=t.child,t.child=a):(null!==(e=l.last)?e.sibling=a:t.child=a,l.last=a)}if(null!==l.tail)return t=l.tail,l.rendering=t,l.tail=t.sibling,l.renderingStartTime=et(),t.sibling=null,e=a7.current,H(a7,r?1&e|2:1&e),t;return oQ(t),null;case 22:case 23:return a9(t),lC(),r=null!==t.memoizedState,null!==e?null!==e.memoizedState!==r&&(t.flags|=8192):r&&(t.flags|=8192),r?0!=(0x20000000&n)&&0==(128&t.flags)&&(oQ(t),6&t.subtreeFlags&&(t.flags|=8192)):oQ(t),null!==(n=t.updateQueue)&&oV(t,n.retryQueue),n=null,null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(n=e.memoizedState.cachePool.pool),r=null,null!==t.memoizedState&&null!==t.memoizedState.cachePool&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),null!==e&&j(r8),null;case 24:return n=null,null!==e&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),rI(rG),oQ(t),null;case 25:case 30:return null}throw Error(u(156,t.tag))}(t.alternate,t,iM);if(null!==n){iT=n;return}if(null!==(t=t.sibling)){iT=t;return}iT=t=e}while(null!==t);0===iI&&(iI=5)}function um(e,t){do{var n=function(e,t){switch(rk(t),t.tag){case 1:return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 3:return rI(rG),q(),0!=(65536&(e=t.flags))&&0==(128&e)?(t.flags=-65537&e|128,t):null;case 26:case 27:case 5:return Y(t),null;case 13:if(a9(t),null!==(e=t.memoizedState)&&null!==e.dehydrated){if(null===t.alternate)throw Error(u(340));rL()}return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 19:return j(a7),null;case 4:return q(),null;case 10:return rI(t.type),null;case 22:case 23:return a9(t),lC(),null!==e&&j(r8),65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 24:return rI(rG),null;default:return null}}(e.alternate,e);if(null!==n){n.flags&=32767,iT=n;return}if(null!==(n=e.return)&&(n.flags|=32768,n.subtreeFlags=0,n.deletions=null),!t&&null!==(e=e.sibling)){iT=e;return}iT=e=n}while(null!==e);iI=6,iT=null}function uh(e,t,n,r,l,a,o,i,s){e.cancelPendingCommit=null;do uk();while(0!==iX);if(0!=(6&iz))throw Error(u(327));if(null!==t){if(t===e.current)throw Error(u(177));if(!function(e,t,n,r,l,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var i=e.entanglements,u=e.expirationTimes,s=e.hiddenUpdates;for(n=o&~n;0g&&(o=g,g=h,h=o);var y=nP(i,h),v=nP(i,g);if(y&&v&&(1!==p.rangeCount||p.anchorNode!==y.node||p.anchorOffset!==y.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var b=f.createRange();b.setStart(y.node,y.offset),p.removeAllRanges(),h>g?(p.addRange(b),p.extend(v.node,v.offset)):(b.setEnd(v.node,v.offset),p.addRange(b))}}}}for(f=[],p=i;p=p.parentNode;)1===p.nodeType&&f.push({element:p,left:p.scrollLeft,top:p.scrollTop});for("function"==typeof i.focus&&i.focus(),i=0;in?32:n,D.T=null,n=i2,i2=null;var a=iZ,o=i0;if(iX=0,iJ=iZ=null,i0=0,0!=(6&iz))throw Error(u(331));var i=iz;if(iz|=4,iE(a.current),iy(a,a.current,o,n),iz=i,uF(0,!1),ef&&"function"==typeof ef.onPostCommitFiberRoot)try{ef.onPostCommitFiberRoot(ec,a)}catch(e){}return!0}finally{A.p=l,D.T=r,ub(e,t)}}function uS(e,t,n){t=nZ(n,t),t=od(e.stateNode,t,2),null!==(e=lp(e,t,2))&&(eE(e,2),uA(e))}function ux(e,t,n){if(3===e.tag)uS(e,e,n);else for(;null!==t;){if(3===t.tag){uS(t,e,n);break}if(1===t.tag){var r=t.stateNode;if("function"==typeof t.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===iG||!iG.has(r))){e=nZ(n,e),null!==(r=lp(t,n=op(2),2))&&(om(n,r,t,e),eE(r,2),uA(r));break}}t=t.return}}function uE(e,t,n){var r=e.pingCache;if(null===r){r=e.pingCache=new iP;var l=new Set;r.set(t,l)}else void 0===(l=r.get(t))&&(l=new Set,r.set(t,l));l.has(n)||(iF=!0,l.add(n),e=uC.bind(null,e,t,n),t.then(e,e))}function uC(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,iN===e&&(iL&n)===n&&(4===iI||3===iI&&(0x3c00000&iL)===iL&&300>et()-iq?0==(2&iz)&&ul(e,0):iH|=n,iV===iL&&(iV=0)),uA(e)}function u_(e,t){0===t&&(t=eS()),null!==(e=n8(e,t))&&(eE(e,t),uA(e))}function uP(e){var t=e.memoizedState,n=0;null!==t&&(n=t.retryLane),u_(e,n)}function uz(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;null!==l&&(n=l.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(u(314))}null!==r&&r.delete(t),u_(e,n)}var uN=null,uT=null,uL=!1,uO=!1,uR=!1,uD=0;function uA(e){e!==uT&&null===e.next&&(null===uT?uN=uT=e:uT=uT.next=e),uO=!0,uL||(uL=!0,sm(function(){0!=(6&iz)?X(er,uM):uI()}))}function uF(e,t){if(!uR&&uO){uR=!0;do for(var n=!1,r=uN;null!==r;){if(!t)if(0!==e){var l=r.pendingLanes;if(0===l)var a=0;else{var o=r.suspendedLanes,i=r.pingedLanes;a=0xc000095&(a=(1<<31-ep(42|e)+1)-1&(l&~(o&~i)))?0xc000095&a|1:a?2|a:0}0!==a&&(n=!0,uH(r,a))}else a=iL,0==(3&(a=eb(r,r===iN?a:0,null!==r.cancelPendingCommit||-1!==r.timeoutHandle)))||ek(r,a)||(n=!0,uH(r,a));r=r.next}while(n);uR=!1}}function uM(){uI()}function uI(){uO=uL=!1;var e,t=0;0!==uD&&(((e=window.event)&&"popstate"===e.type?e===sc||(sc=e,0):(sc=null,1))||(t=uD),uD=0);for(var n=et(),r=null,l=uN;null!==l;){var a=l.next,o=uU(l,n);0===o?(l.next=null,null===r?uN=a:r.next=a,null===a&&(uT=r)):(r=l,(0!==t||0!=(3&o))&&(uO=!0)),l=a}uF(t,!1)}function uU(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,l=e.expirationTimes,a=-0x3c00001&e.pendingLanes;0r){n=r;var o=e.ownerDocument;if(1&n&&sE(o.documentElement),2&n&&sE(o.body),4&n)for(sE(n=o.head),o=n.firstChild;o;){var i=o.nextSibling,u=o.nodeName;o[eI]||"SCRIPT"===u||"STYLE"===u||"LINK"===u&&"stylesheet"===o.rel.toLowerCase()||n.removeChild(o),o=i}}if(0===l){e.removeChild(a),cb(t);return}l--}else"$"===n||"$?"===n||"$!"===n?l++:r=n.charCodeAt(0)-48;else r=0;n=a}while(n);cb(t)}function sv(e){var t=e.firstChild;for(t&&10===t.nodeType&&(t=t.nextSibling);t;){var n=t;switch(t=t.nextSibling,n.nodeName){case"HTML":case"HEAD":case"BODY":sv(n),eU(n);continue;case"SCRIPT":case"STYLE":continue;case"LINK":if("stylesheet"===n.rel.toLowerCase())continue}e.removeChild(n)}}function sb(e){return"$!"===e.data||"$?"===e.data&&"complete"===e.ownerDocument.readyState}function sk(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break;if(8===t){if("$"===(t=e.data)||"$!"===t||"$?"===t||"F!"===t||"F"===t)break;if("/$"===t)return null}}return e}var sw=null;function sS(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("$"===n||"$!"===n||"$?"===n){if(0===t)return e;t--}else"/$"===n&&t++}e=e.previousSibling}return null}function sx(e,t,n){switch(t=so(n),e){case"html":if(!(e=t.documentElement))throw Error(u(452));return e;case"head":if(!(e=t.head))throw Error(u(453));return e;case"body":if(!(e=t.body))throw Error(u(454));return e;default:throw Error(u(451))}}function sE(e){for(var t=e.attributes;t.length;)e.removeAttributeNode(t[0]);eU(e)}var sC=new Map,s_=new Set;function sP(e){return"function"==typeof e.getRootNode?e.getRootNode():9===e.nodeType?e:e.ownerDocument}var sz=A.d;A.d={f:function(){var e=sz.f(),t=un();return e||t},r:function(e){var t=eH(e);null!==t&&5===t.tag&&"form"===t.type?aO(t):sz.r(e)},D:function(e){sz.D(e),sT("dns-prefetch",e,null)},C:function(e,t){sz.C(e,t),sT("preconnect",e,t)},L:function(e,t,n){if(sz.L(e,t,n),sN&&e&&t){var r='link[rel="preload"][as="'+tt(t)+'"]';"image"===t&&n&&n.imageSrcSet?(r+='[imagesrcset="'+tt(n.imageSrcSet)+'"]',"string"==typeof n.imageSizes&&(r+='[imagesizes="'+tt(n.imageSizes)+'"]')):r+='[href="'+tt(e)+'"]';var l=r;switch(t){case"style":l=sO(e);break;case"script":l=sA(e)}sC.has(l)||(e=p({rel:"preload",href:"image"===t&&n&&n.imageSrcSet?void 0:e,as:t},n),sC.set(l,e),null!==sN.querySelector(r)||"style"===t&&sN.querySelector(sR(l))||"script"===t&&sN.querySelector(sF(l))||(sr(t=sN.createElement("link"),"link",e),eB(t),sN.head.appendChild(t)))}},m:function(e,t){if(sz.m(e,t),sN&&e){var n=t&&"string"==typeof t.as?t.as:"script",r='link[rel="modulepreload"][as="'+tt(n)+'"][href="'+tt(e)+'"]',l=r;switch(n){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":l=sA(e)}if(!sC.has(l)&&(e=p({rel:"modulepreload",href:e},t),sC.set(l,e),null===sN.querySelector(r))){switch(n){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(sN.querySelector(sF(l)))return}sr(n=sN.createElement("link"),"link",e),eB(n),sN.head.appendChild(n)}}},X:function(e,t){if(sz.X(e,t),sN&&e){var n=eV(sN).hoistableScripts,r=sA(e),l=n.get(r);l||((l=sN.querySelector(sF(r)))||(e=p({src:e,async:!0},t),(t=sC.get(r))&&sj(e,t),eB(l=sN.createElement("script")),sr(l,"link",e),sN.head.appendChild(l)),l={type:"script",instance:l,count:1,state:null},n.set(r,l))}},S:function(e,t,n){if(sz.S(e,t,n),sN&&e){var r=eV(sN).hoistableStyles,l=sO(e);t=t||"default";var a=r.get(l);if(!a){var o={loading:0,preload:null};if(a=sN.querySelector(sR(l)))o.loading=5;else{e=p({rel:"stylesheet",href:e,"data-precedence":t},n),(n=sC.get(l))&&sU(e,n);var i=a=sN.createElement("link");eB(i),sr(i,"link",e),i._p=new Promise(function(e,t){i.onload=e,i.onerror=t}),i.addEventListener("load",function(){o.loading|=1}),i.addEventListener("error",function(){o.loading|=2}),o.loading|=4,sI(a,t,sN)}a={type:"stylesheet",instance:a,count:1,state:o},r.set(l,a)}}},M:function(e,t){if(sz.M(e,t),sN&&e){var n=eV(sN).hoistableScripts,r=sA(e),l=n.get(r);l||((l=sN.querySelector(sF(r)))||(e=p({src:e,async:!0,type:"module"},t),(t=sC.get(r))&&sj(e,t),eB(l=sN.createElement("script")),sr(l,"link",e),sN.head.appendChild(l)),l={type:"script",instance:l,count:1,state:null},n.set(r,l))}}};var sN="undefined"==typeof document?null:document;function sT(e,t,n){if(sN&&"string"==typeof t&&t){var r=tt(t);r='link[rel="'+e+'"][href="'+r+'"]',"string"==typeof n&&(r+='[crossorigin="'+n+'"]'),s_.has(r)||(s_.add(r),e={rel:e,crossOrigin:n,href:t},null===sN.querySelector(r)&&(sr(t=sN.createElement("link"),"link",e),eB(t),sN.head.appendChild(t)))}}function sL(e,t,n,r){var l=(l=B.current)?sP(l):null;if(!l)throw Error(u(446));switch(e){case"meta":case"title":return null;case"style":return"string"==typeof n.precedence&&"string"==typeof n.href?(t=sO(n.href),(r=(n=eV(l).hoistableStyles).get(t))||(r={type:"style",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};case"link":if("stylesheet"===n.rel&&"string"==typeof n.href&&"string"==typeof n.precedence){e=sO(n.href);var a,o,i,s,c=eV(l).hoistableStyles,f=c.get(e);if(f||(l=l.ownerDocument||l,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},c.set(e,f),(c=l.querySelector(sR(e)))&&!c._p&&(f.instance=c,f.state.loading=5),sC.has(e)||(n={rel:"preload",as:"style",href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},sC.set(e,n),c||(a=l,o=e,i=n,s=f.state,a.querySelector('link[rel="preload"][as="style"]['+o+"]")?s.loading=1:(s.preload=o=a.createElement("link"),o.addEventListener("load",function(){return s.loading|=1}),o.addEventListener("error",function(){return s.loading|=2}),sr(o,"link",i),eB(o),a.head.appendChild(o))))),t&&null===r)throw Error(u(528,""));return f}if(t&&null!==r)throw Error(u(529,""));return null;case"script":return t=n.async,"string"==typeof(n=n.src)&&t&&"function"!=typeof t&&"symbol"!=typeof t?(t=sA(n),(r=(n=eV(l).hoistableScripts).get(t))||(r={type:"script",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};default:throw Error(u(444,e))}}function sO(e){return'href="'+tt(e)+'"'}function sR(e){return'link[rel="stylesheet"]['+e+"]"}function sD(e){return p({},e,{"data-precedence":e.precedence,precedence:null})}function sA(e){return'[src="'+tt(e)+'"]'}function sF(e){return"script[async]"+e}function sM(e,t,n){if(t.count++,null===t.instance)switch(t.type){case"style":var r=e.querySelector('style[data-href~="'+tt(n.href)+'"]');if(r)return t.instance=r,eB(r),r;var l=p({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return eB(r=(e.ownerDocument||e).createElement("style")),sr(r,"style",l),sI(r,n.precedence,e),t.instance=r;case"stylesheet":l=sO(n.href);var a=e.querySelector(sR(l));if(a)return t.state.loading|=4,t.instance=a,eB(a),a;r=sD(n),(l=sC.get(l))&&sU(r,l),eB(a=(e.ownerDocument||e).createElement("link"));var o=a;return o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),sr(a,"link",r),t.state.loading|=4,sI(a,n.precedence,e),t.instance=a;case"script":if(a=sA(n.src),l=e.querySelector(sF(a)))return t.instance=l,eB(l),l;return r=n,(l=sC.get(a))&&sj(r=p({},n),l),eB(l=(e=e.ownerDocument||e).createElement("script")),sr(l,"link",r),e.head.appendChild(l),t.instance=l;case"void":return null;default:throw Error(u(443,t.type))}return"stylesheet"===t.type&&0==(4&t.state.loading)&&(r=t.instance,t.state.loading|=4,sI(r,n.precedence,e)),t.instance}function sI(e,t,n){for(var r=n.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),l=r.length?r[r.length-1]:null,a=l,o=0;o title"):null)}function sB(e){return"stylesheet"!==e.type||0!=(3&e.state.loading)}var sQ=null;function sW(){}function sq(){if(this.count--,0===this.count){if(this.stylesheets)sY(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var sK=null;function sY(e,t){e.stylesheets=null,null!==e.unsuspend&&(e.count++,sK=new Map,t.forEach(sG,e),sK=null,sq.call(e))}function sG(e,t){if(!(4&t.state.loading)){var n=sK.get(e);if(n)var r=n.get(null);else{n=new Map,sK.set(e,n);for(var l=e.querySelectorAll("link[data-precedence],style[data-precedence]"),a=0;a{var r=n(4232);function l(e){var t="https://react.dev/errors/"+e;if(1{function n(e,t){var n=e.length;for(e.push(t);0>>1,l=e[r];if(0>>1;ra(u,n))sa(c,u)?(e[r]=c,e[s]=n,r=s):(e[r]=u,e[i]=n,r=i);else if(sa(c,n))e[r]=c,e[s]=n,r=s;else break}}return t}function a(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}if(t.unstable_now=void 0,"object"==typeof performance&&"function"==typeof performance.now){var o,i=performance;t.unstable_now=function(){return i.now()}}else{var u=Date,s=u.now();t.unstable_now=function(){return u.now()-s}}var c=[],f=[],d=1,p=null,m=3,h=!1,g=!1,y=!1,v=!1,b="function"==typeof setTimeout?setTimeout:null,k="function"==typeof clearTimeout?clearTimeout:null,w="undefined"!=typeof setImmediate?setImmediate:null;function S(e){for(var t=r(f);null!==t;){if(null===t.callback)l(f);else if(t.startTime<=e)l(f),t.sortIndex=t.expirationTime,n(c,t);else break;t=r(f)}}function x(e){if(y=!1,S(e),!g)if(null!==r(c))g=!0,E||(E=!0,o());else{var t=r(f);null!==t&&O(x,t.startTime-e)}}var E=!1,C=-1,_=5,P=-1;function z(){return!!v||!(t.unstable_now()-P<_)}function N(){if(v=!1,E){var e=t.unstable_now();P=e;var n=!0;try{e:{g=!1,y&&(y=!1,k(C),C=-1),h=!0;var a=m;try{t:{for(S(e),p=r(c);null!==p&&!(p.expirationTime>e&&z());){var i=p.callback;if("function"==typeof i){p.callback=null,m=p.priorityLevel;var u=i(p.expirationTime<=e);if(e=t.unstable_now(),"function"==typeof u){p.callback=u,S(e),n=!0;break t}p===r(c)&&l(c),S(e)}else l(c);p=r(c)}if(null!==p)n=!0;else{var s=r(f);null!==s&&O(x,s.startTime-e),n=!1}}break e}finally{p=null,m=a,h=!1}}}finally{n?o():E=!1}}}if("function"==typeof w)o=function(){w(N)};else if("undefined"!=typeof MessageChannel){var T=new MessageChannel,L=T.port2;T.port1.onmessage=N,o=function(){L.postMessage(null)}}else o=function(){b(N,0)};function O(e,n){C=b(function(){e(t.unstable_now())},n)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_forceFrameRate=function(e){0>e||125i?(e.sortIndex=a,n(f,e),null===r(c)&&e===r(f)&&(y?(k(C),C=-1):y=!0,O(x,a-i))):(e.sortIndex=u,n(c,e),g||h||(g=!0,E||(E=!0,o()))),e},t.unstable_shouldYield=z,t.unstable_wrapCallback=function(e){var t=m;return function(){var n=m;m=t;try{return e.apply(this,arguments)}finally{m=n}}}},7876:(e,t,n)=>{e.exports=n(8228)},8228:(e,t)=>{var n=Symbol.for("react.transitional.element");function r(e,t,r){var l=null;if(void 0!==r&&(l=""+r),void 0!==t.key&&(l=""+t.key),"key"in t)for(var a in r={},t)"key"!==a&&(r[a]=t[a]);else r=t;return{$$typeof:n,type:e,key:l,ref:void 0!==(t=r.ref)?t:null,props:r}}t.Fragment=Symbol.for("react.fragment"),t.jsx=r,t.jsxs=r},8477:(e,t,n)=>{!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=n(4655)},8944:(e,t,n)=>{!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=n(4279)}}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/main-a5da5fd7e32dc553.js b/keploy/pkg/service/load/out/_next/static/chunks/main-a5da5fd7e32dc553.js deleted file mode 100644 index 5d27872..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/main-a5da5fd7e32dc553.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[792],{303:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"AmpStateContext",{enumerable:!0,get:function(){return n}});let n=r(4252)._(r(4232)).default.createContext({})},472:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return l}});let n=r(4252),o=r(7876),a=n._(r(4232)),i=r(2746);async function u(e){let{Component:t,ctx:r}=e;return{pageProps:await (0,i.loadGetInitialProps)(t,r)}}class l extends a.default.Component{render(){let{Component:e,pageProps:t}=this.props;return(0,o.jsx)(e,{...t})}}l.origGetInitialProps=u,l.getInitialProps=u,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},536:(e,t)=>{"use strict";function r(e){return e.split("/").map(e=>encodeURIComponent(e)).join("/")}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"encodeURIPath",{enumerable:!0,get:function(){return r}})},541:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"parseRelativeUrl",{enumerable:!0,get:function(){return a}});let n=r(2746),o=r(8040);function a(e,t,r){void 0===r&&(r=!0);let a=new URL((0,n.getLocationOrigin)()),i=t?new URL(t,a):e.startsWith(".")?new URL(window.location.href):a,{pathname:u,searchParams:l,search:s,hash:c,href:f,origin:d}=new URL(e,i);if(d!==a.origin)throw Object.defineProperty(Error("invariant: invalid relative URL, router received "+e),"__NEXT_ERROR_CODE",{value:"E159",enumerable:!1,configurable:!0});return{pathname:u,query:r?(0,o.searchParamsToUrlQuery)(l):void 0,search:s,hash:c,href:f.slice(d.length),slashes:void 0}}},938:(e,t)=>{"use strict";function r(e){return e.startsWith("/")?e:"/"+e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ensureLeadingSlash",{enumerable:!0,get:function(){return r}})},990:(e,t)=>{"use strict";function r(e,t){let r={};return Object.keys(e).forEach(n=>{t.includes(n)||(r[n]=e[n])}),r}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"omit",{enumerable:!0,get:function(){return r}})},1017:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"RedirectStatusCode",{enumerable:!0,get:function(){return r}});var r=function(e){return e[e.SeeOther=303]="SeeOther",e[e.TemporaryRedirect=307]="TemporaryRedirect",e[e.PermanentRedirect=308]="PermanentRedirect",e}({});("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1025:(e,t,r)=>{"use strict";function n(e){return e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeBasePath",{enumerable:!0,get:function(){return n}}),r(6023),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1291:()=>{"trimStart"in String.prototype||(String.prototype.trimStart=String.prototype.trimLeft),"trimEnd"in String.prototype||(String.prototype.trimEnd=String.prototype.trimRight),"description"in Symbol.prototype||Object.defineProperty(Symbol.prototype,"description",{configurable:!0,get:function(){var e=/\((.*)\)/.exec(this.toString());return e?e[1]:void 0}}),Array.prototype.flat||(Array.prototype.flat=function(e,t){return t=this.concat.apply([],this),e>1&&t.some(Array.isArray)?t.flat(e-1):t},Array.prototype.flatMap=function(e,t){return this.map(e,t).flat()}),Promise.prototype.finally||(Promise.prototype.finally=function(e){if("function"!=typeof e)return this.then(e,e);var t=this.constructor||Promise;return this.then(function(r){return t.resolve(e()).then(function(){return r})},function(r){return t.resolve(e()).then(function(){throw r})})}),Object.fromEntries||(Object.fromEntries=function(e){return Array.from(e).reduce(function(e,t){return e[t[0]]=t[1],e},{})}),Array.prototype.at||(Array.prototype.at=function(e){var t=Math.trunc(e)||0;if(t<0&&(t+=this.length),!(t<0||t>=this.length))return this[t]}),Object.hasOwn||(Object.hasOwn=function(e,t){if(null==e)throw TypeError("Cannot convert undefined or null to object");return Object.prototype.hasOwnProperty.call(Object(e),t)}),"canParse"in URL||(URL.canParse=function(e,t){try{return new URL(e,t),!0}catch(e){return!1}})},1318:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{RouteAnnouncer:function(){return l},default:function(){return s}});let n=r(4252),o=r(7876),a=n._(r(4232)),i=r(4294),u={border:0,clip:"rect(0 0 0 0)",height:"1px",margin:"-1px",overflow:"hidden",padding:0,position:"absolute",top:0,width:"1px",whiteSpace:"nowrap",wordWrap:"normal"},l=()=>{let{asPath:e}=(0,i.useRouter)(),[t,r]=a.default.useState(""),n=a.default.useRef(e);return a.default.useEffect(()=>{if(n.current!==e)if(n.current=e,document.title)r(document.title);else{var t;let n=document.querySelector("h1");r((null!=(t=null==n?void 0:n.innerText)?t:null==n?void 0:n.textContent)||e)}},[e]),(0,o.jsx)("p",{"aria-live":"assertive",id:"__next-route-announcer__",role:"alert",style:u,children:t})},s=l;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1533:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isLocalURL",{enumerable:!0,get:function(){return a}});let n=r(2746),o=r(6023);function a(e){if(!(0,n.isAbsoluteUrl)(e))return!0;try{let t=(0,n.getLocationOrigin)(),r=new URL(e,t);return r.origin===t&&(0,o.hasBasePath)(r.pathname)}catch(e){return!1}}},1827:(e,t)=>{"use strict";function r(e,t){return void 0===t&&(t=""),("/"===e?"/index":/^\/index(\/|$)/.test(e)?"/index"+e:e)+t}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return r}})},1862:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizeLocalePath",{enumerable:!0,get:function(){return n}});let r=new WeakMap;function n(e,t){let n;if(!t)return{pathname:e};let o=r.get(t);o||(o=t.map(e=>e.toLowerCase()),r.set(t,o));let a=e.split("/",2);if(!a[1])return{pathname:e};let i=a[1].toLowerCase(),u=o.indexOf(i);return u<0?{pathname:e}:(n=t[u],{pathname:e=e.slice(n.length+1)||"/",detectedLocale:n})}},1921:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"resolveHref",{enumerable:!0,get:function(){return f}});let n=r(8040),o=r(8480),a=r(990),i=r(2746),u=r(8205),l=r(1533),s=r(3069),c=r(8069);function f(e,t,r){let f,d="string"==typeof t?t:(0,o.formatWithValidation)(t),p=d.match(/^[a-z][a-z0-9+.-]*:\/\//i),h=p?d.slice(p[0].length):d;if((h.split("?",1)[0]||"").match(/(\/\/|\\)/)){console.error("Invalid href '"+d+"' passed to next/router in page: '"+e.pathname+"'. Repeated forward-slashes (//) or backslashes \\ are not valid in the href.");let t=(0,i.normalizeRepeatedSlashes)(h);d=(p?p[0]:"")+t}if(!(0,l.isLocalURL)(d))return r?[d]:d;try{f=new URL(d.startsWith("#")?e.asPath:e.pathname,"http://n")}catch(e){f=new URL("/","http://n")}try{let e=new URL(d,f);e.pathname=(0,u.normalizePathTrailingSlash)(e.pathname);let t="";if((0,s.isDynamicRoute)(e.pathname)&&e.searchParams&&r){let r=(0,n.searchParamsToUrlQuery)(e.searchParams),{result:i,params:u}=(0,c.interpolateAs)(e.pathname,e.pathname,r);i&&(t=(0,o.formatWithValidation)({pathname:i,hash:e.hash,query:(0,a.omit)(r,u)}))}let i=e.origin===f.origin?e.href.slice(e.origin.length):e.href;return r?[i,t||i]:i}catch(e){return r?[d]:d}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1924:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"escapeStringRegexp",{enumerable:!0,get:function(){return o}});let r=/[|\\{}()[\]^$+*?.-]/,n=/[|\\{}()[\]^$+*?.-]/g;function o(e){return r.test(e)?e.replace(n,"\\$&"):e}},2092:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addBasePath",{enumerable:!0,get:function(){return a}});let n=r(2889),o=r(8205);function a(e,t){return(0,o.normalizePathTrailingSlash)((0,n.addPathPrefix)(e,""))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2326:(e,t)=>{"use strict";function r(e){return"/api"===e||!!(null==e?void 0:e.startsWith("/api/"))}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isAPIRoute",{enumerable:!0,get:function(){return r}})},2455:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTML_LIMITED_BOT_UA_RE",{enumerable:!0,get:function(){return r}});let r=/Mediapartners-Google|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti/i},2591:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{REDIRECT_ERROR_CODE:function(){return o},RedirectType:function(){return a},isRedirectError:function(){return i}});let n=r(1017),o="NEXT_REDIRECT";var a=function(e){return e.push="push",e.replace="replace",e}({});function i(e){if("object"!=typeof e||null===e||!("digest"in e)||"string"!=typeof e.digest)return!1;let t=e.digest.split(";"),[r,a]=t,i=t.slice(2,-2).join(";"),u=Number(t.at(-2));return r===o&&("replace"===a||"push"===a)&&"string"==typeof i&&!isNaN(u)&&u in n.RedirectStatusCode}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2616:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"detectDomainLocale",{enumerable:!0,get:function(){return r}});let r=function(){for(var e=arguments.length,t=Array(e),r=0;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{DecodeError:function(){return h},MiddlewareNotFoundError:function(){return b},MissingStaticPage:function(){return g},NormalizeError:function(){return _},PageNotFoundError:function(){return m},SP:function(){return d},ST:function(){return p},WEB_VITALS:function(){return r},execOnce:function(){return n},getDisplayName:function(){return l},getLocationOrigin:function(){return i},getURL:function(){return u},isAbsoluteUrl:function(){return a},isResSent:function(){return s},loadGetInitialProps:function(){return f},normalizeRepeatedSlashes:function(){return c},stringifyError:function(){return y}});let r=["CLS","FCP","FID","INP","LCP","TTFB"];function n(e){let t,r=!1;return function(){for(var n=arguments.length,o=Array(n),a=0;ao.test(e);function i(){let{protocol:e,hostname:t,port:r}=window.location;return e+"//"+t+(r?":"+r:"")}function u(){let{href:e}=window.location,t=i();return e.substring(t.length)}function l(e){return"string"==typeof e?e:e.displayName||e.name||"Unknown"}function s(e){return e.finished||e.headersSent}function c(e){let t=e.split("?");return t[0].replace(/\\/g,"/").replace(/\/\/+/g,"/")+(t[1]?"?"+t.slice(1).join("?"):"")}async function f(e,t){let r=t.res||t.ctx&&t.ctx.res;if(!e.getInitialProps)return t.ctx&&t.Component?{pageProps:await f(t.Component,t.ctx)}:{};let n=await e.getInitialProps(t);if(r&&s(r))return n;if(!n)throw Object.defineProperty(Error('"'+l(e)+'.getInitialProps()" should resolve to an object. But found "'+n+'" instead.'),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});return n}let d="undefined"!=typeof performance,p=d&&["mark","measure","getEntriesByName"].every(e=>"function"==typeof performance[e]);class h extends Error{}class _ extends Error{}class m extends Error{constructor(e){super(),this.code="ENOENT",this.name="PageNotFoundError",this.message="Cannot find module for page: "+e}}class g extends Error{constructor(e,t){super(),this.message="Failed to load static file for page: "+e+" "+t}}class b extends Error{constructor(){super(),this.code="ENOENT",this.message="Cannot find the middleware module"}}function y(e){return JSON.stringify({message:e.message,stack:e.stack})}},2792:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return d}});let n=r(4252),o=r(2092),a=r(8069),i=n._(r(1827)),u=r(4591),l=r(9163),s=r(541),c=r(4902),f=r(7176);r(3802);class d{getPageList(){return(0,f.getClientBuildManifest)().then(e=>e.sortedPages)}getMiddleware(){return window.__MIDDLEWARE_MATCHERS=[],window.__MIDDLEWARE_MATCHERS}getDataHref(e){let{asPath:t,href:r,locale:n}=e,{pathname:f,query:d,search:p}=(0,s.parseRelativeUrl)(r),{pathname:h}=(0,s.parseRelativeUrl)(t),_=(0,c.removeTrailingSlash)(f);if("/"!==_[0])throw Object.defineProperty(Error('Route name should start with a "/", got "'+_+'"'),"__NEXT_ERROR_CODE",{value:"E303",enumerable:!1,configurable:!0});var m=e.skipInterpolation?h:(0,l.isDynamicRoute)(_)?(0,a.interpolateAs)(f,h,d).result:_;let g=(0,i.default)((0,c.removeTrailingSlash)((0,u.addLocale)(m,n)),".json");return(0,o.addBasePath)("/_next/data/"+this.buildId+g+p,!0)}_isSsg(e){return this.promisedSsgManifest.then(t=>t.has(e))}loadPage(e){return this.routeLoader.loadRoute(e).then(e=>{if("component"in e)return{page:e.component,mod:e.exports,styleSheets:e.styles.map(e=>({href:e.href,text:e.content}))};throw e.error})}prefetch(e){return this.routeLoader.prefetch(e)}constructor(e,t){this.routeLoader=(0,f.createRouteLoader)(t),this.buildId=e,this.assetPrefix=t,this.promisedSsgManifest=new Promise(e=>{window.__SSG_MANIFEST?e(window.__SSG_MANIFEST):window.__SSG_MANIFEST_CB=()=>{e(window.__SSG_MANIFEST)}})}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2850:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{AppRouterContext:function(){return o},GlobalLayoutRouterContext:function(){return i},LayoutRouterContext:function(){return a},MissingSlotContext:function(){return l},TemplateContext:function(){return u}});let n=r(4252)._(r(4232)),o=n.default.createContext(null),a=n.default.createContext(null),i=n.default.createContext(null),u=n.default.createContext(null),l=n.default.createContext(new Set)},2889:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addPathPrefix",{enumerable:!0,get:function(){return o}});let n=r(3670);function o(e,t){if(!e.startsWith("/")||!t)return e;let{pathname:r,query:o,hash:a}=(0,n.parsePath)(e);return""+t+r+o+a}},2917:(e,t)=>{"use strict";let r;Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return n},setConfig:function(){return o}});let n=()=>r;function o(e){r=e}},2959:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{normalizeAppPath:function(){return a},normalizeRscURL:function(){return i}});let n=r(938),o=r(8714);function a(e){return(0,n.ensureLeadingSlash)(e.split("/").reduce((e,t,r,n)=>!t||(0,o.isGroupSegment)(t)||"@"===t[0]||("page"===t||"route"===t)&&r===n.length-1?e:e+"/"+t,""))}function i(e){return e.replace(/\.rsc($|\?)/,"$1")}},3069:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getSortedRouteObjects:function(){return n.getSortedRouteObjects},getSortedRoutes:function(){return n.getSortedRoutes},isDynamicRoute:function(){return o.isDynamicRoute}});let n=r(3703),o=r(9163)},3090:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"Portal",{enumerable:!0,get:function(){return a}});let n=r(4232),o=r(8477),a=e=>{let{children:t,type:r}=e,[a,i]=(0,n.useState)(null);return(0,n.useEffect)(()=>{let e=document.createElement(r);return document.body.appendChild(e),i(e),()=>{document.body.removeChild(e)}},[r]),a?(0,o.createPortal)(t,a):null};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3123:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{BailoutToCSRError:function(){return n},isBailoutToCSRError:function(){return o}});let r="BAILOUT_TO_CLIENT_SIDE_RENDERING";class n extends Error{constructor(e){super("Bail out to client-side rendering: "+e),this.reason=e,this.digest=r}}function o(e){return"object"==typeof e&&null!==e&&"digest"in e&&e.digest===r}},3407:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getNextPathnameInfo",{enumerable:!0,get:function(){return i}});let n=r(1862),o=r(6292),a=r(3716);function i(e,t){var r,i;let{basePath:u,i18n:l,trailingSlash:s}=null!=(r=t.nextConfig)?r:{},c={pathname:e,trailingSlash:"/"!==e?e.endsWith("/"):s};u&&(0,a.pathHasPrefix)(c.pathname,u)&&(c.pathname=(0,o.removePathPrefix)(c.pathname,u),c.basePath=u);let f=c.pathname;if(c.pathname.startsWith("/_next/data/")&&c.pathname.endsWith(".json")){let e=c.pathname.replace(/^\/_next\/data\//,"").replace(/\.json$/,"").split("/");c.buildId=e[0],f="index"!==e[1]?"/"+e.slice(1).join("/"):"/",!0===t.parseData&&(c.pathname=f)}if(l){let e=t.i18nProvider?t.i18nProvider.analyze(c.pathname):(0,n.normalizeLocalePath)(c.pathname,l.locales);c.locale=e.detectedLocale,c.pathname=null!=(i=e.pathname)?i:c.pathname,!e.detectedLocale&&c.buildId&&(e=t.i18nProvider?t.i18nProvider.analyze(f):(0,n.normalizeLocalePath)(f,l.locales)).detectedLocale&&(c.locale=e.detectedLocale)}return c}},3670:(e,t)=>{"use strict";function r(e){let t=e.indexOf("#"),r=e.indexOf("?"),n=r>-1&&(t<0||r-1?{pathname:e.substring(0,n?r:t),query:n?e.substring(r,t>-1?t:void 0):"",hash:t>-1?e.slice(t):""}:{pathname:e,query:"",hash:""}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"parsePath",{enumerable:!0,get:function(){return r}})},3703:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getSortedRouteObjects:function(){return o},getSortedRoutes:function(){return n}});class r{insert(e){this._insert(e.split("/").filter(Boolean),[],!1)}smoosh(){return this._smoosh()}_smoosh(e){void 0===e&&(e="/");let t=[...this.children.keys()].sort();null!==this.slugName&&t.splice(t.indexOf("[]"),1),null!==this.restSlugName&&t.splice(t.indexOf("[...]"),1),null!==this.optionalRestSlugName&&t.splice(t.indexOf("[[...]]"),1);let r=t.map(t=>this.children.get(t)._smoosh(""+e+t+"/")).reduce((e,t)=>[...e,...t],[]);if(null!==this.slugName&&r.push(...this.children.get("[]")._smoosh(e+"["+this.slugName+"]/")),!this.placeholder){let t="/"===e?"/":e.slice(0,-1);if(null!=this.optionalRestSlugName)throw Object.defineProperty(Error('You cannot define a route with the same specificity as a optional catch-all route ("'+t+'" and "'+t+"[[..."+this.optionalRestSlugName+']]").'),"__NEXT_ERROR_CODE",{value:"E458",enumerable:!1,configurable:!0});r.unshift(t)}return null!==this.restSlugName&&r.push(...this.children.get("[...]")._smoosh(e+"[..."+this.restSlugName+"]/")),null!==this.optionalRestSlugName&&r.push(...this.children.get("[[...]]")._smoosh(e+"[[..."+this.optionalRestSlugName+"]]/")),r}_insert(e,t,n){if(0===e.length){this.placeholder=!1;return}if(n)throw Object.defineProperty(Error("Catch-all must be the last part of the URL."),"__NEXT_ERROR_CODE",{value:"E392",enumerable:!1,configurable:!0});let o=e[0];if(o.startsWith("[")&&o.endsWith("]")){let r=o.slice(1,-1),i=!1;if(r.startsWith("[")&&r.endsWith("]")&&(r=r.slice(1,-1),i=!0),r.startsWith("…"))throw Object.defineProperty(Error("Detected a three-dot character ('…') at ('"+r+"'). Did you mean ('...')?"),"__NEXT_ERROR_CODE",{value:"E147",enumerable:!1,configurable:!0});if(r.startsWith("...")&&(r=r.substring(3),n=!0),r.startsWith("[")||r.endsWith("]"))throw Object.defineProperty(Error("Segment names may not start or end with extra brackets ('"+r+"')."),"__NEXT_ERROR_CODE",{value:"E421",enumerable:!1,configurable:!0});if(r.startsWith("."))throw Object.defineProperty(Error("Segment names may not start with erroneous periods ('"+r+"')."),"__NEXT_ERROR_CODE",{value:"E288",enumerable:!1,configurable:!0});function a(e,r){if(null!==e&&e!==r)throw Object.defineProperty(Error("You cannot use different slug names for the same dynamic path ('"+e+"' !== '"+r+"')."),"__NEXT_ERROR_CODE",{value:"E337",enumerable:!1,configurable:!0});t.forEach(e=>{if(e===r)throw Object.defineProperty(Error('You cannot have the same slug name "'+r+'" repeat within a single dynamic path'),"__NEXT_ERROR_CODE",{value:"E247",enumerable:!1,configurable:!0});if(e.replace(/\W/g,"")===o.replace(/\W/g,""))throw Object.defineProperty(Error('You cannot have the slug names "'+e+'" and "'+r+'" differ only by non-word symbols within a single dynamic path'),"__NEXT_ERROR_CODE",{value:"E499",enumerable:!1,configurable:!0})}),t.push(r)}if(n)if(i){if(null!=this.restSlugName)throw Object.defineProperty(Error('You cannot use both an required and optional catch-all route at the same level ("[...'+this.restSlugName+']" and "'+e[0]+'" ).'),"__NEXT_ERROR_CODE",{value:"E299",enumerable:!1,configurable:!0});a(this.optionalRestSlugName,r),this.optionalRestSlugName=r,o="[[...]]"}else{if(null!=this.optionalRestSlugName)throw Object.defineProperty(Error('You cannot use both an optional and required catch-all route at the same level ("[[...'+this.optionalRestSlugName+']]" and "'+e[0]+'").'),"__NEXT_ERROR_CODE",{value:"E300",enumerable:!1,configurable:!0});a(this.restSlugName,r),this.restSlugName=r,o="[...]"}else{if(i)throw Object.defineProperty(Error('Optional route parameters are not yet supported ("'+e[0]+'").'),"__NEXT_ERROR_CODE",{value:"E435",enumerable:!1,configurable:!0});a(this.slugName,r),this.slugName=r,o="[]"}}this.children.has(o)||this.children.set(o,new r),this.children.get(o)._insert(e.slice(1),t,n)}constructor(){this.placeholder=!0,this.children=new Map,this.slugName=null,this.restSlugName=null,this.optionalRestSlugName=null}}function n(e){let t=new r;return e.forEach(e=>t.insert(e)),t.smoosh()}function o(e,t){let r={},o=[];for(let n=0;ne[r[t]])}},3716:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"pathHasPrefix",{enumerable:!0,get:function(){return o}});let n=r(3670);function o(e,t){if("string"!=typeof e)return!1;let{pathname:r}=(0,n.parsePath)(e);return r===t||r.startsWith(t+"/")}},3718:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(8757),self.__next_set_public_path__=e=>{r.p=e},("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3776:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}});let n=r(4232),o=n.useLayoutEffect,a=n.useEffect;function i(e){let{headManager:t,reduceComponentsToState:r}=e;function i(){if(t&&t.mountedInstances){let o=n.Children.toArray(Array.from(t.mountedInstances).filter(Boolean));t.updateHead(r(o,e))}}return o(()=>{var r;return null==t||null==(r=t.mountedInstances)||r.add(e.children),()=>{var r;null==t||null==(r=t.mountedInstances)||r.delete(e.children)}}),o(()=>(t&&(t._pendingUpdate=i),()=>{t&&(t._pendingUpdate=i)})),a(()=>(t&&t._pendingUpdate&&(t._pendingUpdate(),t._pendingUpdate=null),()=>{t&&t._pendingUpdate&&(t._pendingUpdate(),t._pendingUpdate=null)})),null}},3802:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{APP_BUILD_MANIFEST:function(){return y},APP_CLIENT_INTERNALS:function(){return Q},APP_PATHS_MANIFEST:function(){return m},APP_PATH_ROUTES_MANIFEST:function(){return g},BARREL_OPTIMIZATION_PREFIX:function(){return X},BLOCKED_PAGES:function(){return U},BUILD_ID_FILE:function(){return D},BUILD_MANIFEST:function(){return b},CLIENT_PUBLIC_FILES_PATH:function(){return F},CLIENT_REFERENCE_MANIFEST:function(){return W},CLIENT_STATIC_FILES_PATH:function(){return k},CLIENT_STATIC_FILES_RUNTIME_AMP:function(){return Z},CLIENT_STATIC_FILES_RUNTIME_MAIN:function(){return K},CLIENT_STATIC_FILES_RUNTIME_MAIN_APP:function(){return $},CLIENT_STATIC_FILES_RUNTIME_POLYFILLS:function(){return et},CLIENT_STATIC_FILES_RUNTIME_POLYFILLS_SYMBOL:function(){return er},CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH:function(){return J},CLIENT_STATIC_FILES_RUNTIME_WEBPACK:function(){return ee},COMPILER_INDEXES:function(){return a},COMPILER_NAMES:function(){return o},CONFIG_FILES:function(){return L},DEFAULT_RUNTIME_WEBPACK:function(){return en},DEFAULT_SANS_SERIF_FONT:function(){return el},DEFAULT_SERIF_FONT:function(){return eu},DEV_CLIENT_MIDDLEWARE_MANIFEST:function(){return x},DEV_CLIENT_PAGES_MANIFEST:function(){return w},DYNAMIC_CSS_MANIFEST:function(){return Y},EDGE_RUNTIME_WEBPACK:function(){return eo},EDGE_UNSUPPORTED_NODE_APIS:function(){return ep},EXPORT_DETAIL:function(){return O},EXPORT_MARKER:function(){return R},FUNCTIONS_CONFIG_MANIFEST:function(){return E},IMAGES_MANIFEST:function(){return j},INTERCEPTION_ROUTE_REWRITE_MANIFEST:function(){return z},MIDDLEWARE_BUILD_MANIFEST:function(){return q},MIDDLEWARE_MANIFEST:function(){return C},MIDDLEWARE_REACT_LOADABLE_MANIFEST:function(){return V},MODERN_BROWSERSLIST_TARGET:function(){return n.default},NEXT_BUILTIN_DOCUMENT:function(){return H},NEXT_FONT_MANIFEST:function(){return v},PAGES_MANIFEST:function(){return h},PHASE_DEVELOPMENT_SERVER:function(){return f},PHASE_EXPORT:function(){return l},PHASE_INFO:function(){return p},PHASE_PRODUCTION_BUILD:function(){return s},PHASE_PRODUCTION_SERVER:function(){return c},PHASE_TEST:function(){return d},PRERENDER_MANIFEST:function(){return S},REACT_LOADABLE_MANIFEST:function(){return N},ROUTES_MANIFEST:function(){return T},RSC_MODULE_TYPES:function(){return ed},SERVER_DIRECTORY:function(){return M},SERVER_FILES_MANIFEST:function(){return A},SERVER_PROPS_ID:function(){return ei},SERVER_REFERENCE_MANIFEST:function(){return G},STATIC_PROPS_ID:function(){return ea},STATIC_STATUS_PAGES:function(){return es},STRING_LITERAL_DROP_BUNDLE:function(){return B},SUBRESOURCE_INTEGRITY_MANIFEST:function(){return P},SYSTEM_ENTRYPOINTS:function(){return eh},TRACE_OUTPUT_VERSION:function(){return ec},TURBOPACK_CLIENT_MIDDLEWARE_MANIFEST:function(){return I},TURBO_TRACE_DEFAULT_MEMORY_LIMIT:function(){return ef},UNDERSCORE_NOT_FOUND_ROUTE:function(){return i},UNDERSCORE_NOT_FOUND_ROUTE_ENTRY:function(){return u},WEBPACK_STATS:function(){return _}});let n=r(4252)._(r(6582)),o={client:"client",server:"server",edgeServer:"edge-server"},a={[o.client]:0,[o.server]:1,[o.edgeServer]:2},i="/_not-found",u=""+i+"/page",l="phase-export",s="phase-production-build",c="phase-production-server",f="phase-development-server",d="phase-test",p="phase-info",h="pages-manifest.json",_="webpack-stats.json",m="app-paths-manifest.json",g="app-path-routes-manifest.json",b="build-manifest.json",y="app-build-manifest.json",E="functions-config-manifest.json",P="subresource-integrity-manifest",v="next-font-manifest",R="export-marker.json",O="export-detail.json",S="prerender-manifest.json",T="routes-manifest.json",j="images-manifest.json",A="required-server-files.json",w="_devPagesManifest.json",C="middleware-manifest.json",I="_clientMiddlewareManifest.json",x="_devMiddlewareManifest.json",N="react-loadable-manifest.json",M="server",L=["next.config.js","next.config.mjs","next.config.ts"],D="BUILD_ID",U=["/_document","/_app","/_error"],F="public",k="static",B="__NEXT_DROP_CLIENT_FILE__",H="__NEXT_BUILTIN_DOCUMENT__",X="__barrel_optimize__",W="client-reference-manifest",G="server-reference-manifest",q="middleware-build-manifest",V="middleware-react-loadable-manifest",z="interception-route-rewrite-manifest",Y="dynamic-css-manifest",K="main",$=""+K+"-app",Q="app-pages-internals",J="react-refresh",Z="amp",ee="webpack",et="polyfills",er=Symbol(et),en="webpack-runtime",eo="edge-runtime-webpack",ea="__N_SSG",ei="__N_SSP",eu={name:"Times New Roman",xAvgCharWidth:821,azAvgWidth:854.3953488372093,unitsPerEm:2048},el={name:"Arial",xAvgCharWidth:904,azAvgWidth:934.5116279069767,unitsPerEm:2048},es=["/500"],ec=1,ef=6e3,ed={client:"client",server:"server"},ep=["clearImmediate","setImmediate","BroadcastChannel","ByteLengthQueuingStrategy","CompressionStream","CountQueuingStrategy","DecompressionStream","DomException","MessageChannel","MessageEvent","MessagePort","ReadableByteStreamController","ReadableStreamBYOBRequest","ReadableStreamDefaultController","TransformStreamDefaultController","WritableStreamDefaultController"],eh=new Set([K,J,Z,$]);("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3836:(e,t,r)=>{"use strict";function n(e,t){return e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeLocale",{enumerable:!0,get:function(){return n}}),r(3670),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3980:(e,t,r)=>{"use strict";function n(e,t){if(void 0===t&&(t={}),t.onlyHashChange)return void e();let r=document.documentElement;r.dataset.scrollBehavior;let n=r.style.scrollBehavior;r.style.scrollBehavior="auto",t.dontForceLayout||r.getClientRects(),e(),r.style.scrollBehavior=n}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"disableSmoothScrollDuringRouteTransition",{enumerable:!0,get:function(){return n}}),r(6079)},3996:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return g},handleClientScriptLoad:function(){return h},initScriptLoader:function(){return _}});let n=r(4252),o=r(8365),a=r(7876),i=n._(r(8477)),u=o._(r(4232)),l=r(8831),s=r(9611),c=r(6959),f=new Map,d=new Set,p=e=>{let{src:t,id:r,onLoad:n=()=>{},onReady:o=null,dangerouslySetInnerHTML:a,children:u="",strategy:l="afterInteractive",onError:c,stylesheets:p}=e,h=r||t;if(h&&d.has(h))return;if(f.has(t)){d.add(h),f.get(t).then(n,c);return}let _=()=>{o&&o(),d.add(h)},m=document.createElement("script"),g=new Promise((e,t)=>{m.addEventListener("load",function(t){e(),n&&n.call(this,t),_()}),m.addEventListener("error",function(e){t(e)})}).catch(function(e){c&&c(e)});a?(m.innerHTML=a.__html||"",_()):u?(m.textContent="string"==typeof u?u:Array.isArray(u)?u.join(""):"",_()):t&&(m.src=t,f.set(t,g)),(0,s.setAttributesFromProps)(m,e),"worker"===l&&m.setAttribute("type","text/partytown"),m.setAttribute("data-nscript",l),p&&(e=>{if(i.default.preinit)return e.forEach(e=>{i.default.preinit(e,{as:"style"})});{let t=document.head;e.forEach(e=>{let r=document.createElement("link");r.type="text/css",r.rel="stylesheet",r.href=e,t.appendChild(r)})}})(p),document.body.appendChild(m)};function h(e){let{strategy:t="afterInteractive"}=e;"lazyOnload"===t?window.addEventListener("load",()=>{(0,c.requestIdleCallback)(()=>p(e))}):p(e)}function _(e){e.forEach(h),[...document.querySelectorAll('[data-nscript="beforeInteractive"]'),...document.querySelectorAll('[data-nscript="beforePageRender"]')].forEach(e=>{let t=e.id||e.getAttribute("src");d.add(t)})}function m(e){let{id:t,src:r="",onLoad:n=()=>{},onReady:o=null,strategy:s="afterInteractive",onError:f,stylesheets:h,..._}=e,{updateScripts:m,scripts:g,getIsSsr:b,appDir:y,nonce:E}=(0,u.useContext)(l.HeadManagerContext);E=_.nonce||E;let P=(0,u.useRef)(!1);(0,u.useEffect)(()=>{let e=t||r;P.current||(o&&e&&d.has(e)&&o(),P.current=!0)},[o,t,r]);let v=(0,u.useRef)(!1);if((0,u.useEffect)(()=>{if(!v.current){if("afterInteractive"===s)p(e);else"lazyOnload"===s&&("complete"===document.readyState?(0,c.requestIdleCallback)(()=>p(e)):window.addEventListener("load",()=>{(0,c.requestIdleCallback)(()=>p(e))}));v.current=!0}},[e,s]),("beforeInteractive"===s||"worker"===s)&&(m?(g[s]=(g[s]||[]).concat([{id:t,src:r,onLoad:n,onReady:o,onError:f,..._,nonce:E}]),m(g)):b&&b()?d.add(t||r):b&&!b()&&p({...e,nonce:E})),y){if(h&&h.forEach(e=>{i.default.preinit(e,{as:"style"})}),"beforeInteractive"===s)if(!r)return _.dangerouslySetInnerHTML&&(_.children=_.dangerouslySetInnerHTML.__html,delete _.dangerouslySetInnerHTML),(0,a.jsx)("script",{nonce:E,dangerouslySetInnerHTML:{__html:"(self.__next_s=self.__next_s||[]).push("+JSON.stringify([0,{..._,id:t}])+")"}});else return i.default.preload(r,_.integrity?{as:"script",integrity:_.integrity,nonce:E,crossOrigin:_.crossOrigin}:{as:"script",nonce:E,crossOrigin:_.crossOrigin}),(0,a.jsx)("script",{nonce:E,dangerouslySetInnerHTML:{__html:"(self.__next_s=self.__next_s||[]).push("+JSON.stringify([r,{..._,id:t}])+")"}});"afterInteractive"===s&&r&&i.default.preload(r,_.integrity?{as:"script",integrity:_.integrity,nonce:E,crossOrigin:_.crossOrigin}:{as:"script",nonce:E,crossOrigin:_.crossOrigin})}return null}Object.defineProperty(m,"__nextScript",{value:!0});let g=m;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4069:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"BloomFilter",{enumerable:!0,get:function(){return r}});class r{static from(e,t){void 0===t&&(t=1e-4);let n=new r(e.length,t);for(let t of e)n.add(t);return n}export(){return{numItems:this.numItems,errorRate:this.errorRate,numBits:this.numBits,numHashes:this.numHashes,bitArray:this.bitArray}}import(e){this.numItems=e.numItems,this.errorRate=e.errorRate,this.numBits=e.numBits,this.numHashes=e.numHashes,this.bitArray=e.bitArray}add(e){this.getHashValues(e).forEach(e=>{this.bitArray[e]=1})}contains(e){return this.getHashValues(e).every(e=>this.bitArray[e])}getHashValues(e){let t=[];for(let r=1;r<=this.numHashes;r++){let n=function(e){let t=0;for(let r=0;r>>13,t=Math.imul(t,0x5bd1e995);return t>>>0}(""+e+r)%this.numBits;t.push(n)}return t}constructor(e,t=1e-4){this.numItems=e,this.errorRate=t,this.numBits=Math.ceil(-(e*Math.log(t))/(Math.log(2)*Math.log(2))),this.numHashes=Math.ceil(this.numBits/e*Math.log(2)),this.bitArray=Array(this.numBits).fill(0)}}},4181:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{HTTPAccessErrorStatus:function(){return r},HTTP_ERROR_FALLBACK_ERROR_CODE:function(){return o},getAccessFallbackErrorTypeByStatus:function(){return u},getAccessFallbackHTTPStatus:function(){return i},isHTTPAccessFallbackError:function(){return a}});let r={NOT_FOUND:404,FORBIDDEN:403,UNAUTHORIZED:401},n=new Set(Object.values(r)),o="NEXT_HTTP_ERROR_FALLBACK";function a(e){if("object"!=typeof e||null===e||!("digest"in e)||"string"!=typeof e.digest)return!1;let[t,r]=e.digest.split(";");return t===o&&n.has(Number(r))}function i(e){return Number(e.digest.split(";")[1])}function u(e){switch(e){case 401:return"unauthorized";case 403:return"forbidden";case 404:return"not-found";default:return}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4252:(e,t,r)=>{"use strict";function n(e){return e&&e.__esModule?e:{default:e}}r.r(t),r.d(t,{_:()=>n})},4294:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{Router:function(){return a.default},createRouter:function(){return _},default:function(){return p},makePublicRouterInstance:function(){return m},useRouter:function(){return h},withRouter:function(){return l.default}});let n=r(4252),o=n._(r(4232)),a=n._(r(8276)),i=r(9948),u=n._(r(6240)),l=n._(r(8147)),s={router:null,readyCallbacks:[],ready(e){if(this.router)return e();this.readyCallbacks.push(e)}},c=["pathname","route","query","asPath","components","isFallback","basePath","locale","locales","defaultLocale","isReady","isPreview","isLocaleDomain","domainLocales"],f=["push","replace","reload","back","prefetch","beforePopState"];function d(){if(!s.router)throw Object.defineProperty(Error('No router instance found.\nYou should only use "next/router" on the client side of your app.\n'),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});return s.router}Object.defineProperty(s,"events",{get:()=>a.default.events}),c.forEach(e=>{Object.defineProperty(s,e,{get:()=>d()[e]})}),f.forEach(e=>{s[e]=function(){for(var t=arguments.length,r=Array(t),n=0;n{s.ready(()=>{a.default.events.on(e,function(){for(var t=arguments.length,r=Array(t),n=0;ne()),s.readyCallbacks=[],s.router}function m(e){let t={};for(let r of c){if("object"==typeof e[r]){t[r]=Object.assign(Array.isArray(e[r])?[]:{},e[r]);continue}t[r]=e[r]}return t.events=a.default.events,f.forEach(r=>{t[r]=function(){for(var t=arguments.length,n=Array(t),o=0;o{"use strict";function r(e,t){let r=Object.keys(e);if(r.length!==Object.keys(t).length)return!1;for(let n=r.length;n--;){let o=r[n];if("query"===o){let r=Object.keys(e.query);if(r.length!==Object.keys(t.query).length)return!1;for(let n=r.length;n--;){let o=r[n];if(!t.query.hasOwnProperty(o)||e.query[o]!==t.query[o])return!1}}else if(!t.hasOwnProperty(o)||e[o]!==t[o])return!1}return!0}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"compareRouterStates",{enumerable:!0,get:function(){return r}})},4547:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return a},isEqualNode:function(){return o}});let n=r(9611);function o(e,t){if(e instanceof HTMLElement&&t instanceof HTMLElement){let r=t.getAttribute("nonce");if(r&&!e.getAttribute("nonce")){let n=t.cloneNode(!0);return n.setAttribute("nonce",""),n.nonce=r,r===e.nonce&&e.isEqualNode(n)}}return e.isEqualNode(t)}function a(){return{mountedInstances:new Set,updateHead:e=>{let t={};e.forEach(e=>{if("link"===e.type&&e.props["data-optimized-fonts"])if(document.querySelector('style[data-href="'+e.props["data-href"]+'"]'))return;else e.props.href=e.props["data-href"],e.props["data-href"]=void 0;let r=t[e.type]||[];r.push(e),t[e.type]=r});let r=t.title?t.title[0]:null,a="";if(r){let{children:e}=r.props;a="string"==typeof e?e:Array.isArray(e)?e.join(""):""}a!==document.title&&(document.title=a),["meta","base","link","style","script"].forEach(e=>{((e,t)=>{let r=document.querySelector("head");if(!r)return;let a=new Set(r.querySelectorAll(""+e+"[data-next-head]"));if("meta"===e){let e=r.querySelector("meta[charset]");null!==e&&a.add(e)}let i=[];for(let e=0;e{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"reportGlobalError",{enumerable:!0,get:function(){return r}});let r="function"==typeof reportError?reportError:e=>{globalThis.console.error(e)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4591:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addLocale",{enumerable:!0,get:function(){return n}}),r(8205);let n=function(e){for(var t=arguments.length,r=Array(t>1?t-1:0),n=1;n{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}});let n=r(4252)._(r(9871));class o{end(e){if("ended"===this.state.state)throw Object.defineProperty(Error("Span has already ended"),"__NEXT_ERROR_CODE",{value:"E17",enumerable:!1,configurable:!0});this.state={state:"ended",endTime:null!=e?e:Date.now()},this.onSpanEnd(this)}constructor(e,t,r){var n,o;this.name=e,this.attributes=null!=(n=t.attributes)?n:{},this.startTime=null!=(o=t.startTime)?o:Date.now(),this.onSpanEnd=r,this.state={state:"inprogress"}}}class a{startSpan(e,t){return new o(e,t,this.handleSpanEnd)}onSpanEnd(e){return this._emitter.on("spanend",e),()=>{this._emitter.off("spanend",e)}}constructor(){this._emitter=(0,n.default)(),this.handleSpanEnd=e=>{this._emitter.emit("spanend",e)}}}let i=new a;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4902:(e,t)=>{"use strict";function r(e){return e.replace(/\/$/,"")||"/"}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeTrailingSlash",{enumerable:!0,get:function(){return r}})},4980:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"formatNextPathnameInfo",{enumerable:!0,get:function(){return u}});let n=r(4902),o=r(2889),a=r(7952),i=r(6711);function u(e){let t=(0,i.addLocale)(e.pathname,e.locale,e.buildId?void 0:e.defaultLocale,e.ignorePrefix);return(e.buildId||!e.trailingSlash)&&(t=(0,n.removeTrailingSlash)(t)),e.buildId&&(t=(0,a.addPathSuffix)((0,o.addPathPrefix)(t,"/_next/data/"+e.buildId),"/"===e.pathname?"index.json":".json")),t=(0,o.addPathPrefix)(t,e.basePath),!e.buildId&&e.trailingSlash?t.endsWith("/")?t:(0,a.addPathSuffix)(t,"/"):(0,n.removeTrailingSlash)(t)}},5195:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"denormalizePagePath",{enumerable:!0,get:function(){return a}});let n=r(3069),o=r(5419);function a(e){let t=(0,o.normalizePathSep)(e);return t.startsWith("/index/")&&!(0,n.isDynamicRoute)(t)?t.slice(6):"/index"!==t?t:"/"}},5214:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getNamedMiddlewareRegex:function(){return _},getNamedRouteRegex:function(){return h},getRouteRegex:function(){return f},parseParameter:function(){return l}});let n=r(9308),o=r(7188),a=r(1924),i=r(4902),u=/^([^[]*)\[((?:\[[^\]]*\])|[^\]]+)\](.*)$/;function l(e){let t=e.match(u);return t?s(t[2]):s(e)}function s(e){let t=e.startsWith("[")&&e.endsWith("]");t&&(e=e.slice(1,-1));let r=e.startsWith("...");return r&&(e=e.slice(3)),{key:e,repeat:r,optional:t}}function c(e,t,r){let n={},l=1,c=[];for(let f of(0,i.removeTrailingSlash)(e).slice(1).split("/")){let e=o.INTERCEPTION_ROUTE_MARKERS.find(e=>f.startsWith(e)),i=f.match(u);if(e&&i&&i[2]){let{key:t,optional:r,repeat:o}=s(i[2]);n[t]={pos:l++,repeat:o,optional:r},c.push("/"+(0,a.escapeStringRegexp)(e)+"([^/]+?)")}else if(i&&i[2]){let{key:e,repeat:t,optional:o}=s(i[2]);n[e]={pos:l++,repeat:t,optional:o},r&&i[1]&&c.push("/"+(0,a.escapeStringRegexp)(i[1]));let u=t?o?"(?:/(.+?))?":"/(.+?)":"/([^/]+?)";r&&i[1]&&(u=u.substring(1)),c.push(u)}else c.push("/"+(0,a.escapeStringRegexp)(f));t&&i&&i[3]&&c.push((0,a.escapeStringRegexp)(i[3]))}return{parameterizedRoute:c.join(""),groups:n}}function f(e,t){let{includeSuffix:r=!1,includePrefix:n=!1,excludeOptionalTrailingSlash:o=!1}=void 0===t?{}:t,{parameterizedRoute:a,groups:i}=c(e,r,n),u=a;return o||(u+="(?:/)?"),{re:RegExp("^"+u+"$"),groups:i}}function d(e){let t,{interceptionMarker:r,getSafeRouteKey:n,segment:o,routeKeys:i,keyPrefix:u,backreferenceDuplicateKeys:l}=e,{key:c,optional:f,repeat:d}=s(o),p=c.replace(/\W/g,"");u&&(p=""+u+p);let h=!1;(0===p.length||p.length>30)&&(h=!0),isNaN(parseInt(p.slice(0,1)))||(h=!0),h&&(p=n());let _=p in i;u?i[p]=""+u+c:i[p]=c;let m=r?(0,a.escapeStringRegexp)(r):"";return t=_&&l?"\\k<"+p+">":d?"(?<"+p+">.+?)":"(?<"+p+">[^/]+?)",f?"(?:/"+m+t+")?":"/"+m+t}function p(e,t,r,l,s){let c,f=(c=0,()=>{let e="",t=++c;for(;t>0;)e+=String.fromCharCode(97+(t-1)%26),t=Math.floor((t-1)/26);return e}),p={},h=[];for(let c of(0,i.removeTrailingSlash)(e).slice(1).split("/")){let e=o.INTERCEPTION_ROUTE_MARKERS.some(e=>c.startsWith(e)),i=c.match(u);if(e&&i&&i[2])h.push(d({getSafeRouteKey:f,interceptionMarker:i[1],segment:i[2],routeKeys:p,keyPrefix:t?n.NEXT_INTERCEPTION_MARKER_PREFIX:void 0,backreferenceDuplicateKeys:s}));else if(i&&i[2]){l&&i[1]&&h.push("/"+(0,a.escapeStringRegexp)(i[1]));let e=d({getSafeRouteKey:f,segment:i[2],routeKeys:p,keyPrefix:t?n.NEXT_QUERY_PARAM_PREFIX:void 0,backreferenceDuplicateKeys:s});l&&i[1]&&(e=e.substring(1)),h.push(e)}else h.push("/"+(0,a.escapeStringRegexp)(c));r&&i&&i[3]&&h.push((0,a.escapeStringRegexp)(i[3]))}return{namedParameterizedRoute:h.join(""),routeKeys:p}}function h(e,t){var r,n,o;let a=p(e,t.prefixRouteKeys,null!=(r=t.includeSuffix)&&r,null!=(n=t.includePrefix)&&n,null!=(o=t.backreferenceDuplicateKeys)&&o),i=a.namedParameterizedRoute;return t.excludeOptionalTrailingSlash||(i+="(?:/)?"),{...f(e,t),namedRegex:"^"+i+"$",routeKeys:a.routeKeys}}function _(e,t){let{parameterizedRoute:r}=c(e,!1,!1),{catchAll:n=!0}=t;if("/"===r)return{namedRegex:"^/"+(n?".*":"")+"$"};let{namedParameterizedRoute:o}=p(e,!1,!1,!1,!1);return{namedRegex:"^"+o+(n?"(?:(/.*)?)":"")+"$"}}},5364:(e,t,r)=>{"use strict";var n,o;e.exports=(null==(n=r.g.process)?void 0:n.env)&&"object"==typeof(null==(o=r.g.process)?void 0:o.env)?r.g.process:r(5861)},5419:(e,t)=>{"use strict";function r(e){return e.replace(/\\/g,"/")}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizePathSep",{enumerable:!0,get:function(){return r}})},5519:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getRouteMatcher",{enumerable:!0,get:function(){return o}});let n=r(2746);function o(e){let{re:t,groups:r}=e;return e=>{let o=t.exec(e);if(!o)return!1;let a=e=>{try{return decodeURIComponent(e)}catch(e){throw Object.defineProperty(new n.DecodeError("failed to decode param"),"__NEXT_ERROR_CODE",{value:"E528",enumerable:!1,configurable:!0})}},i={};for(let[e,t]of Object.entries(r)){let r=o[t.pos];void 0!==r&&(t.repeat?i[e]=r.split("/").map(e=>a(e)):i[e]=a(r))}return i}}},5679:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return _},defaultHead:function(){return f}});let n=r(4252),o=r(8365),a=r(7876),i=o._(r(4232)),u=n._(r(3776)),l=r(303),s=r(8831),c=r(6807);function f(e){void 0===e&&(e=!1);let t=[(0,a.jsx)("meta",{charSet:"utf-8"},"charset")];return e||t.push((0,a.jsx)("meta",{name:"viewport",content:"width=device-width"},"viewport")),t}function d(e,t){return"string"==typeof t||"number"==typeof t?e:t.type===i.default.Fragment?e.concat(i.default.Children.toArray(t.props.children).reduce((e,t)=>"string"==typeof t||"number"==typeof t?e:e.concat(t),[])):e.concat(t)}r(6079);let p=["name","httpEquiv","charSet","itemProp"];function h(e,t){let{inAmpMode:r}=t;return e.reduce(d,[]).reverse().concat(f(r).reverse()).filter(function(){let e=new Set,t=new Set,r=new Set,n={};return o=>{let a=!0,i=!1;if(o.key&&"number"!=typeof o.key&&o.key.indexOf("$")>0){i=!0;let t=o.key.slice(o.key.indexOf("$")+1);e.has(t)?a=!1:e.add(t)}switch(o.type){case"title":case"base":t.has(o.type)?a=!1:t.add(o.type);break;case"meta":for(let e=0,t=p.length;e{let r=e.key||t;return i.default.cloneElement(e,{key:r})})}let _=function(e){let{children:t}=e,r=(0,i.useContext)(l.AmpStateContext),n=(0,i.useContext)(s.HeadManagerContext);return(0,a.jsx)(u.default,{reduceComponentsToState:h,headManager:n,inAmpMode:(0,c.isInAmpMode)(r),children:t})};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5842:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(3718),r(7647);let n=r(9525);window.next={version:n.version,get router(){return n.router},emitter:n.emitter},(0,n.initialize)({}).then(()=>(0,n.hydrate)()).catch(console.error),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},5861:e=>{!function(){var t={229:function(e){var t,r,n,o=e.exports={};function a(){throw Error("setTimeout has not been defined")}function i(){throw Error("clearTimeout has not been defined")}try{t="function"==typeof setTimeout?setTimeout:a}catch(e){t=a}try{r="function"==typeof clearTimeout?clearTimeout:i}catch(e){r=i}function u(e){if(t===setTimeout)return setTimeout(e,0);if((t===a||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(r){try{return t.call(null,e,0)}catch(r){return t.call(this,e,0)}}}var l=[],s=!1,c=-1;function f(){s&&n&&(s=!1,n.length?l=n.concat(l):c=-1,l.length&&d())}function d(){if(!s){var e=u(f);s=!0;for(var t=l.length;t;){for(n=l,l=[];++c1)for(var r=1;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{PathParamsContext:function(){return i},PathnameContext:function(){return a},SearchParamsContext:function(){return o}});let n=r(4232),o=(0,n.createContext)(null),a=(0,n.createContext)(null),i=(0,n.createContext)(null)},6023:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hasBasePath",{enumerable:!0,get:function(){return o}});let n=r(3716);function o(e){return(0,n.pathHasPrefix)(e,"")}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6079:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"warnOnce",{enumerable:!0,get:function(){return r}});let r=e=>{}},6240:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{default:function(){return o},getProperError:function(){return a}});let n=r(8096);function o(e){return"object"==typeof e&&null!==e&&"name"in e&&"message"in e}function a(e){return o(e)?e:Object.defineProperty(Error((0,n.isPlainObject)(e)?function(e){let t=new WeakSet;return JSON.stringify(e,(e,r)=>{if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r})}(e):e+""),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0})}},6292:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removePathPrefix",{enumerable:!0,get:function(){return o}});let n=r(3716);function o(e,t){if(!(0,n.pathHasPrefix)(e,t))return e;let r=e.slice(t.length);return r.startsWith("/")?r:"/"+r}},6582:e=>{"use strict";e.exports=["chrome 64","edge 79","firefox 67","opera 51","safari 12"]},6711:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addLocale",{enumerable:!0,get:function(){return a}});let n=r(2889),o=r(3716);function a(e,t,r,a){if(!t||t===r)return e;let i=e.toLowerCase();return!a&&((0,o.pathHasPrefix)(i,"/api")||(0,o.pathHasPrefix)(i,"/"+t.toLowerCase()))?e:(0,n.addPathPrefix)(e,"/"+t)}},6807:(e,t)=>{"use strict";function r(e){let{ampFirst:t=!1,hybrid:r=!1,hasQuery:n=!1}=void 0===e?{}:e;return t||r&&n}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isInAmpMode",{enumerable:!0,get:function(){return r}})},6818:(e,t)=>{"use strict";let r;function n(e){var t;return(null==(t=function(){if(void 0===r){var e;r=(null==(e=window.trustedTypes)?void 0:e.createPolicy("nextjs",{createHTML:e=>e,createScript:e=>e,createScriptURL:e=>e}))||null}return r}())?void 0:t.createScriptURL(e))||e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"__unsafeCreateTrustedScriptURL",{enumerable:!0,get:function(){return n}}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6959:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{cancelIdleCallback:function(){return n},requestIdleCallback:function(){return r}});let r="undefined"!=typeof self&&self.requestIdleCallback&&self.requestIdleCallback.bind(window)||function(e){let t=Date.now();return self.setTimeout(function(){e({didTimeout:!1,timeRemaining:function(){return Math.max(0,50-(Date.now()-t))}})},1)},n="undefined"!=typeof self&&self.cancelIdleCallback&&self.cancelIdleCallback.bind(window)||function(e){return clearTimeout(e)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6999:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isNextRouterError",{enumerable:!0,get:function(){return a}});let n=r(4181),o=r(2591);function a(e){return(0,o.isRedirectError)(e)||(0,n.isHTTPAccessFallbackError)(e)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7176:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{createRouteLoader:function(){return m},getClientBuildManifest:function(){return h},isAssetError:function(){return c},markAssetError:function(){return s}}),r(4252),r(1827);let n=r(6818),o=r(6959),a=r(8757),i=r(536);function u(e,t,r){let n,o=t.get(e);if(o)return"future"in o?o.future:Promise.resolve(o);let a=new Promise(e=>{n=e});return t.set(e,{resolve:n,future:a}),r?r().then(e=>(n(e),e)).catch(r=>{throw t.delete(e),r}):a}let l=Symbol("ASSET_LOAD_ERROR");function s(e){return Object.defineProperty(e,l,{})}function c(e){return e&&l in e}let f=function(e){try{return e=document.createElement("link"),!!window.MSInputMethodContext&&!!document.documentMode||e.relList.supports("prefetch")}catch(e){return!1}}(),d=()=>(0,a.getDeploymentIdQueryOrEmptyString)();function p(e,t,r){return new Promise((n,a)=>{let i=!1;e.then(e=>{i=!0,n(e)}).catch(a),(0,o.requestIdleCallback)(()=>setTimeout(()=>{i||a(r)},t))})}function h(){return self.__BUILD_MANIFEST?Promise.resolve(self.__BUILD_MANIFEST):p(new Promise(e=>{let t=self.__BUILD_MANIFEST_CB;self.__BUILD_MANIFEST_CB=()=>{e(self.__BUILD_MANIFEST),t&&t()}}),3800,s(Object.defineProperty(Error("Failed to load client build manifest"),"__NEXT_ERROR_CODE",{value:"E273",enumerable:!1,configurable:!0})))}function _(e,t){return h().then(r=>{if(!(t in r))throw s(Object.defineProperty(Error("Failed to lookup route: "+t),"__NEXT_ERROR_CODE",{value:"E446",enumerable:!1,configurable:!0}));let o=r[t].map(t=>e+"/_next/"+(0,i.encodeURIPath)(t));return{scripts:o.filter(e=>e.endsWith(".js")).map(e=>(0,n.__unsafeCreateTrustedScriptURL)(e)+d()),css:o.filter(e=>e.endsWith(".css")).map(e=>e+d())}})}function m(e){let t=new Map,r=new Map,n=new Map,a=new Map;function i(e){{var t;let n=r.get(e.toString());return n?n:document.querySelector('script[src^="'+e+'"]')?Promise.resolve():(r.set(e.toString(),n=new Promise((r,n)=>{(t=document.createElement("script")).onload=r,t.onerror=()=>n(s(Object.defineProperty(Error("Failed to load script: "+e),"__NEXT_ERROR_CODE",{value:"E74",enumerable:!1,configurable:!0}))),t.crossOrigin=void 0,t.src=e,document.body.appendChild(t)})),n)}}function l(e){let t=n.get(e);return t||n.set(e,t=fetch(e,{credentials:"same-origin"}).then(t=>{if(!t.ok)throw Object.defineProperty(Error("Failed to load stylesheet: "+e),"__NEXT_ERROR_CODE",{value:"E189",enumerable:!1,configurable:!0});return t.text().then(t=>({href:e,content:t}))}).catch(e=>{throw s(e)})),t}return{whenEntrypoint:e=>u(e,t),onEntrypoint(e,r){(r?Promise.resolve().then(()=>r()).then(e=>({component:e&&e.default||e,exports:e}),e=>({error:e})):Promise.resolve(void 0)).then(r=>{let n=t.get(e);n&&"resolve"in n?r&&(t.set(e,r),n.resolve(r)):(r?t.set(e,r):t.delete(e),a.delete(e))})},loadRoute(r,n){return u(r,a,()=>{let o;return p(_(e,r).then(e=>{let{scripts:n,css:o}=e;return Promise.all([t.has(r)?[]:Promise.all(n.map(i)),Promise.all(o.map(l))])}).then(e=>this.whenEntrypoint(r).then(t=>({entrypoint:t,styles:e[1]}))),3800,s(Object.defineProperty(Error("Route did not complete loading: "+r),"__NEXT_ERROR_CODE",{value:"E12",enumerable:!1,configurable:!0}))).then(e=>{let{entrypoint:t,styles:r}=e,n=Object.assign({styles:r},t);return"error"in t?t:n}).catch(e=>{if(n)throw e;return{error:e}}).finally(()=>null==o?void 0:o())})},prefetch(t){let r;return(r=navigator.connection)&&(r.saveData||/2g/.test(r.effectiveType))?Promise.resolve():_(e,t).then(e=>Promise.all(f?e.scripts.map(e=>{var t,r,n;return t=e.toString(),r="script",new Promise((e,o)=>{let a='\n link[rel="prefetch"][href^="'+t+'"],\n link[rel="preload"][href^="'+t+'"],\n script[src^="'+t+'"]';if(document.querySelector(a))return e();n=document.createElement("link"),r&&(n.as=r),n.rel="prefetch",n.crossOrigin=void 0,n.onload=e,n.onerror=()=>o(s(Object.defineProperty(Error("Failed to prefetch: "+t),"__NEXT_ERROR_CODE",{value:"E268",enumerable:!1,configurable:!0}))),n.href=t,document.head.appendChild(n)})}):[])).then(()=>{(0,o.requestIdleCallback)(()=>this.loadRoute(t,!0).catch(()=>{}))}).catch(()=>{})}}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7188:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{INTERCEPTION_ROUTE_MARKERS:function(){return o},extractInterceptionRouteInformation:function(){return i},isInterceptionRouteAppPath:function(){return a}});let n=r(2959),o=["(..)(..)","(.)","(..)","(...)"];function a(e){return void 0!==e.split("/").find(e=>o.find(t=>e.startsWith(t)))}function i(e){let t,r,a;for(let n of e.split("/"))if(r=o.find(e=>n.startsWith(e))){[t,a]=e.split(r,2);break}if(!t||!r||!a)throw Object.defineProperty(Error("Invalid interception route: "+e+". Must be in the format //(..|...|..)(..)/"),"__NEXT_ERROR_CODE",{value:"E269",enumerable:!1,configurable:!0});switch(t=(0,n.normalizeAppPath)(t),r){case"(.)":a="/"===t?"/"+a:t+"/"+a;break;case"(..)":if("/"===t)throw Object.defineProperty(Error("Invalid interception route: "+e+". Cannot use (..) marker at the root level, use (.) instead."),"__NEXT_ERROR_CODE",{value:"E207",enumerable:!1,configurable:!0});a=t.split("/").slice(0,-1).concat(a).join("/");break;case"(...)":a="/"+a;break;case"(..)(..)":let i=t.split("/");if(i.length<=2)throw Object.defineProperty(Error("Invalid interception route: "+e+". Cannot use (..)(..) marker at the root level or one level up."),"__NEXT_ERROR_CODE",{value:"E486",enumerable:!1,configurable:!0});a=i.slice(0,-2).concat(a).join("/");break;default:throw Object.defineProperty(Error("Invariant: unexpected marker"),"__NEXT_ERROR_CODE",{value:"E112",enumerable:!1,configurable:!0})}return{interceptingRoute:t,interceptedRoute:a}}},7207:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{isRecoverableError:function(){return l},onRecoverableError:function(){return s}});let n=r(4252),o=r(3123),a=n._(r(6240)),i=r(4569),u=new WeakSet;function l(e){return u.has(e)}let s=(e,t)=>{let r=(0,a.default)(e)&&"cause"in e?e.cause:e;(0,o.isBailoutToCSRError)(r)||(0,i.reportGlobalError)(r)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7407:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{HTML_LIMITED_BOT_UA_RE:function(){return n.HTML_LIMITED_BOT_UA_RE},HTML_LIMITED_BOT_UA_RE_STRING:function(){return a},getBotType:function(){return l},isBot:function(){return u}});let n=r(2455),o=/google/i,a=n.HTML_LIMITED_BOT_UA_RE.source;function i(e){return n.HTML_LIMITED_BOT_UA_RE.test(e)}function u(e){return o.test(e)||i(e)}function l(e){return o.test(e)?"dom":i(e)?"html":void 0}},7539:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{VALID_LOADERS:function(){return r},imageConfigDefault:function(){return n}});let r=["default","imgix","cloudinary","akamai","custom"],n={deviceSizes:[640,750,828,1080,1200,1920,2048,3840],imageSizes:[16,32,48,64,96,128,256,384],path:"/_next/image",loader:"default",loaderFile:"",domains:[],disableStaticImages:!1,minimumCacheTTL:60,formats:["image/webp"],dangerouslyAllowSVG:!1,contentSecurityPolicy:"script-src 'none'; frame-src 'none'; sandbox;",contentDispositionType:"attachment",localPatterns:void 0,remotePatterns:[],qualities:void 0,unoptimized:!1}},7647:(e,t,r)=>{"use strict";e.exports=r(9393)},7952:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addPathSuffix",{enumerable:!0,get:function(){return o}});let n=r(3670);function o(e,t){if(!e.startsWith("/")||!t)return e;let{pathname:r,query:o,hash:a}=(0,n.parsePath)(e);return""+r+t+o+a}},8040:(e,t)=>{"use strict";function r(e){let t={};for(let[r,n]of e.entries()){let e=t[r];void 0===e?t[r]=n:Array.isArray(e)?e.push(n):t[r]=[e,n]}return t}function n(e){return"string"==typeof e?e:("number"!=typeof e||isNaN(e))&&"boolean"!=typeof e?"":String(e)}function o(e){let t=new URLSearchParams;for(let[r,o]of Object.entries(e))if(Array.isArray(o))for(let e of o)t.append(r,n(e));else t.set(r,n(o));return t}function a(e){for(var t=arguments.length,r=Array(t>1?t-1:0),n=1;n{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"interpolateAs",{enumerable:!0,get:function(){return a}});let n=r(5519),o=r(5214);function a(e,t,r){let a="",i=(0,o.getRouteRegex)(e),u=i.groups,l=(t!==e?(0,n.getRouteMatcher)(i)(t):"")||r;a=e;let s=Object.keys(u);return s.every(e=>{let t=l[e]||"",{repeat:r,optional:n}=u[e],o="["+(r?"...":"")+e+"]";return n&&(o=(t?"":"/")+"["+o+"]"),r&&!Array.isArray(t)&&(t=[t]),(n||e in l)&&(a=a.replace(o,r?t.map(e=>encodeURIComponent(e)).join("/"):encodeURIComponent(t))||"/")})||(a=""),{params:s,result:a}}},8096:(e,t)=>{"use strict";function r(e){return Object.prototype.toString.call(e)}function n(e){if("[object Object]"!==r(e))return!1;let t=Object.getPrototypeOf(e);return null===t||t.hasOwnProperty("isPrototypeOf")}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{getObjectClassLabel:function(){return r},isPlainObject:function(){return n}})},8147:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return a}}),r(4252);let n=r(7876);r(4232);let o=r(4294);function a(e){function t(t){return(0,n.jsx)(e,{router:(0,o.useRouter)(),...t})}return t.getInitialProps=e.getInitialProps,t.origGetInitialProps=e.origGetInitialProps,t}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8205:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizePathTrailingSlash",{enumerable:!0,get:function(){return a}});let n=r(4902),o=r(3670),a=e=>{if(!e.startsWith("/"))return e;let{pathname:t,query:r,hash:a}=(0,o.parsePath)(e);return/\.[^/]+\/?$/.test(t)?""+(0,n.removeTrailingSlash)(t)+r+a:t.endsWith("/")?""+t+r+a:t+"/"+r+a};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8213:(e,t)=>{"use strict";function r(e){return new URL(e,"http://n").searchParams}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"asPathToSearchParams",{enumerable:!0,get:function(){return r}})},8276:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{createKey:function(){return G},default:function(){return z},matchesMiddleware:function(){return D}});let n=r(4252),o=r(8365),a=r(4902),i=r(7176),u=r(3996),l=o._(r(6240)),s=r(5195),c=r(1862),f=n._(r(9871)),d=r(2746),p=r(9163),h=r(541),_=r(5519),m=r(5214),g=r(8480);r(2616);let b=r(3670),y=r(4591),E=r(3836),P=r(1025),v=r(2092),R=r(6023),O=r(1921),S=r(2326),T=r(3407),j=r(4980),A=r(4359),w=r(1533),C=r(7407),I=r(990),x=r(8069),N=r(3980),M=r(9308);function L(){return Object.assign(Object.defineProperty(Error("Route Cancelled"),"__NEXT_ERROR_CODE",{value:"E315",enumerable:!1,configurable:!0}),{cancelled:!0})}async function D(e){let t=await Promise.resolve(e.router.pageLoader.getMiddleware());if(!t)return!1;let{pathname:r}=(0,b.parsePath)(e.asPath),n=(0,R.hasBasePath)(r)?(0,P.removeBasePath)(r):r,o=(0,v.addBasePath)((0,y.addLocale)(n,e.locale));return t.some(e=>new RegExp(e.regexp).test(o))}function U(e){let t=(0,d.getLocationOrigin)();return e.startsWith(t)?e.substring(t.length):e}function F(e,t,r){let[n,o]=(0,O.resolveHref)(e,t,!0),a=(0,d.getLocationOrigin)(),i=n.startsWith(a),u=o&&o.startsWith(a);n=U(n),o=o?U(o):o;let l=i?n:(0,v.addBasePath)(n),s=r?U((0,O.resolveHref)(e,r)):o||n;return{url:l,as:u?s:(0,v.addBasePath)(s)}}function k(e,t){let r=(0,a.removeTrailingSlash)((0,s.denormalizePagePath)(e));return"/404"===r||"/_error"===r?e:(t.includes(r)||t.some(t=>{if((0,p.isDynamicRoute)(t)&&(0,m.getRouteRegex)(t).re.test(r))return e=t,!0}),(0,a.removeTrailingSlash)(e))}async function B(e){if(!await D(e)||!e.fetchData)return null;let t=await e.fetchData(),r=await function(e,t,r){let n={basePath:r.router.basePath,i18n:{locales:r.router.locales},trailingSlash:!0},o=t.headers.get("x-nextjs-rewrite"),u=o||t.headers.get("x-nextjs-matched-path"),l=t.headers.get(M.MATCHED_PATH_HEADER);if(!l||u||l.includes("__next_data_catchall")||l.includes("/_error")||l.includes("/404")||(u=l),u){if(u.startsWith("/")){let t=(0,h.parseRelativeUrl)(u),l=(0,T.getNextPathnameInfo)(t.pathname,{nextConfig:n,parseData:!0}),s=(0,a.removeTrailingSlash)(l.pathname);return Promise.all([r.router.pageLoader.getPageList(),(0,i.getClientBuildManifest)()]).then(a=>{let[i,{__rewrites:u}]=a,f=(0,y.addLocale)(l.pathname,l.locale);if((0,p.isDynamicRoute)(f)||!o&&i.includes((0,c.normalizeLocalePath)((0,P.removeBasePath)(f),r.router.locales).pathname)){let r=(0,T.getNextPathnameInfo)((0,h.parseRelativeUrl)(e).pathname,{nextConfig:n,parseData:!0});t.pathname=f=(0,v.addBasePath)(r.pathname)}if(!i.includes(s)){let e=k(s,i);e!==s&&(s=e)}let d=i.includes(s)?s:k((0,c.normalizeLocalePath)((0,P.removeBasePath)(t.pathname),r.router.locales).pathname,i);if((0,p.isDynamicRoute)(d)){let e=(0,_.getRouteMatcher)((0,m.getRouteRegex)(d))(f);Object.assign(t.query,e||{})}return{type:"rewrite",parsedAs:t,resolvedHref:d}})}let t=(0,b.parsePath)(e);return Promise.resolve({type:"redirect-external",destination:""+(0,j.formatNextPathnameInfo)({...(0,T.getNextPathnameInfo)(t.pathname,{nextConfig:n,parseData:!0}),defaultLocale:r.router.defaultLocale,buildId:""})+t.query+t.hash})}let s=t.headers.get("x-nextjs-redirect");if(s){if(s.startsWith("/")){let e=(0,b.parsePath)(s),t=(0,j.formatNextPathnameInfo)({...(0,T.getNextPathnameInfo)(e.pathname,{nextConfig:n,parseData:!0}),defaultLocale:r.router.defaultLocale,buildId:""});return Promise.resolve({type:"redirect-internal",newAs:""+t+e.query+e.hash,newUrl:""+t+e.query+e.hash})}return Promise.resolve({type:"redirect-external",destination:s})}return Promise.resolve({type:"next"})}(t.dataHref,t.response,e);return{dataHref:t.dataHref,json:t.json,response:t.response,text:t.text,cacheKey:t.cacheKey,effect:r}}let H=Symbol("SSG_DATA_NOT_FOUND");function X(e){try{return JSON.parse(e)}catch(e){return null}}function W(e){let{dataHref:t,inflightCache:r,isPrefetch:n,hasMiddleware:o,isServerRender:a,parseJSON:u,persistCache:l,isBackground:s,unstable_skipClientCache:c}=e,{href:f}=new URL(t,window.location.href),d=e=>{var s;return(function e(t,r,n){return fetch(t,{credentials:"same-origin",method:n.method||"GET",headers:Object.assign({},n.headers,{"x-nextjs-data":"1"})}).then(o=>!o.ok&&r>1&&o.status>=500?e(t,r-1,n):o)})(t,a?3:1,{headers:Object.assign({},n?{purpose:"prefetch"}:{},n&&o?{"x-middleware-prefetch":"1"}:{},{}),method:null!=(s=null==e?void 0:e.method)?s:"GET"}).then(r=>r.ok&&(null==e?void 0:e.method)==="HEAD"?{dataHref:t,response:r,text:"",json:{},cacheKey:f}:r.text().then(e=>{if(!r.ok){if(o&&[301,302,307,308].includes(r.status))return{dataHref:t,response:r,text:e,json:{},cacheKey:f};if(404===r.status){var n;if(null==(n=X(e))?void 0:n.notFound)return{dataHref:t,json:{notFound:H},response:r,text:e,cacheKey:f}}let u=Object.defineProperty(Error("Failed to load static props"),"__NEXT_ERROR_CODE",{value:"E124",enumerable:!1,configurable:!0});throw a||(0,i.markAssetError)(u),u}return{dataHref:t,json:u?X(e):null,response:r,text:e,cacheKey:f}})).then(e=>(l&&"no-cache"!==e.response.headers.get("x-middleware-cache")||delete r[f],e)).catch(e=>{throw c||delete r[f],("Failed to fetch"===e.message||"NetworkError when attempting to fetch resource."===e.message||"Load failed"===e.message)&&(0,i.markAssetError)(e),e})};return c&&l?d({}).then(e=>("no-cache"!==e.response.headers.get("x-middleware-cache")&&(r[f]=Promise.resolve(e)),e)):void 0!==r[f]?r[f]:r[f]=d(s?{method:"HEAD"}:{})}function G(){return Math.random().toString(36).slice(2,10)}function q(e){let{url:t,router:r}=e;if(t===(0,v.addBasePath)((0,y.addLocale)(r.asPath,r.locale)))throw Object.defineProperty(Error("Invariant: attempted to hard navigate to the same URL "+t+" "+location.href),"__NEXT_ERROR_CODE",{value:"E282",enumerable:!1,configurable:!0});window.location.href=t}let V=e=>{let{route:t,router:r}=e,n=!1,o=r.clc=()=>{n=!0};return()=>{if(n){let e=Object.defineProperty(Error('Abort fetching component for route: "'+t+'"'),"__NEXT_ERROR_CODE",{value:"E483",enumerable:!1,configurable:!0});throw e.cancelled=!0,e}o===r.clc&&(r.clc=null)}};class z{reload(){window.location.reload()}back(){window.history.back()}forward(){window.history.forward()}push(e,t,r){return void 0===r&&(r={}),{url:e,as:t}=F(this,e,t),this.change("pushState",e,t,r)}replace(e,t,r){return void 0===r&&(r={}),{url:e,as:t}=F(this,e,t),this.change("replaceState",e,t,r)}async _bfl(e,t,n,o){{if(!this._bfl_s&&!this._bfl_d){let t,a,{BloomFilter:u}=r(4069);try{({__routerFilterStatic:t,__routerFilterDynamic:a}=await (0,i.getClientBuildManifest)())}catch(t){if(console.error(t),o)return!0;return q({url:(0,v.addBasePath)((0,y.addLocale)(e,n||this.locale,this.defaultLocale)),router:this}),new Promise(()=>{})}(null==t?void 0:t.numHashes)&&(this._bfl_s=new u(t.numItems,t.errorRate),this._bfl_s.import(t)),(null==a?void 0:a.numHashes)&&(this._bfl_d=new u(a.numItems,a.errorRate),this._bfl_d.import(a))}let c=!1,f=!1;for(let{as:r,allowMatchCurrent:i}of[{as:e},{as:t}])if(r){let t=(0,a.removeTrailingSlash)(new URL(r,"http://n").pathname),d=(0,v.addBasePath)((0,y.addLocale)(t,n||this.locale));if(i||t!==(0,a.removeTrailingSlash)(new URL(this.asPath,"http://n").pathname)){var u,l,s;for(let e of(c=c||!!(null==(u=this._bfl_s)?void 0:u.contains(t))||!!(null==(l=this._bfl_s)?void 0:l.contains(d)),[t,d])){let t=e.split("/");for(let e=0;!f&&e{})}}}}return!1}async change(e,t,r,n,o){var s,c,f,O,S,T,j,C,N;let M,U;if(!(0,w.isLocalURL)(t))return q({url:t,router:this}),!1;let B=1===n._h;B||n.shallow||await this._bfl(r,void 0,n.locale);let X=B||n._shouldResolveHref||(0,b.parsePath)(t).pathname===(0,b.parsePath)(r).pathname,W={...this.state},G=!0!==this.isReady;this.isReady=!0;let V=this.isSsr;if(B||(this.isSsr=!1),B&&this.clc)return!1;let Y=W.locale;d.ST&&performance.mark("routeChange");let{shallow:K=!1,scroll:$=!0}=n,Q={shallow:K};this._inFlightRoute&&this.clc&&(V||z.events.emit("routeChangeError",L(),this._inFlightRoute,Q),this.clc(),this.clc=null),r=(0,v.addBasePath)((0,y.addLocale)((0,R.hasBasePath)(r)?(0,P.removeBasePath)(r):r,n.locale,this.defaultLocale));let J=(0,E.removeLocale)((0,R.hasBasePath)(r)?(0,P.removeBasePath)(r):r,W.locale);this._inFlightRoute=r;let Z=Y!==W.locale;if(!B&&this.onlyAHashChange(J)&&!Z){W.asPath=J,z.events.emit("hashChangeStart",r,Q),this.changeState(e,t,r,{...n,scroll:!1}),$&&this.scrollToHash(J);try{await this.set(W,this.components[W.route],null)}catch(e){throw(0,l.default)(e)&&e.cancelled&&z.events.emit("routeChangeError",e,J,Q),e}return z.events.emit("hashChangeComplete",r,Q),!0}let ee=(0,h.parseRelativeUrl)(t),{pathname:et,query:er}=ee;try{[M,{__rewrites:U}]=await Promise.all([this.pageLoader.getPageList(),(0,i.getClientBuildManifest)(),this.pageLoader.getMiddleware()])}catch(e){return q({url:r,router:this}),!1}this.urlIsNew(J)||Z||(e="replaceState");let en=r;et=et?(0,a.removeTrailingSlash)((0,P.removeBasePath)(et)):et;let eo=(0,a.removeTrailingSlash)(et),ea=r.startsWith("/")&&(0,h.parseRelativeUrl)(r).pathname;if(null==(s=this.components[et])?void 0:s.__appRouter)return q({url:r,router:this}),new Promise(()=>{});let ei=!!(ea&&eo!==ea&&(!(0,p.isDynamicRoute)(eo)||!(0,_.getRouteMatcher)((0,m.getRouteRegex)(eo))(ea))),eu=!n.shallow&&await D({asPath:r,locale:W.locale,router:this});if(B&&eu&&(X=!1),X&&"/_error"!==et&&(n._shouldResolveHref=!0,ee.pathname=k(et,M),ee.pathname!==et&&(et=ee.pathname,ee.pathname=(0,v.addBasePath)(et),eu||(t=(0,g.formatWithValidation)(ee)))),!(0,w.isLocalURL)(r))return q({url:r,router:this}),!1;en=(0,E.removeLocale)((0,P.removeBasePath)(en),W.locale),eo=(0,a.removeTrailingSlash)(et);let el=!1;if((0,p.isDynamicRoute)(eo)){let e=(0,h.parseRelativeUrl)(en),n=e.pathname,o=(0,m.getRouteRegex)(eo);el=(0,_.getRouteMatcher)(o)(n);let a=eo===n,i=a?(0,x.interpolateAs)(eo,n,er):{};if(el&&(!a||i.result))a?r=(0,g.formatWithValidation)(Object.assign({},e,{pathname:i.result,query:(0,I.omit)(er,i.params)})):Object.assign(er,el);else{let e=Object.keys(o.groups).filter(e=>!er[e]&&!o.groups[e].optional);if(e.length>0&&!eu)throw Object.defineProperty(Error((a?"The provided `href` ("+t+") value is missing query values ("+e.join(", ")+") to be interpolated properly. ":"The provided `as` value ("+n+") is incompatible with the `href` value ("+eo+"). ")+"Read more: https://nextjs.org/docs/messages/"+(a?"href-interpolation-failed":"incompatible-href-as")),"__NEXT_ERROR_CODE",{value:"E344",enumerable:!1,configurable:!0})}}B||z.events.emit("routeChangeStart",r,Q);let es="/404"===this.pathname||"/_error"===this.pathname;try{let a=await this.getRouteInfo({route:eo,pathname:et,query:er,as:r,resolvedAs:en,routeProps:Q,locale:W.locale,isPreview:W.isPreview,hasMiddleware:eu,unstable_skipClientCache:n.unstable_skipClientCache,isQueryUpdating:B&&!this.isFallback,isMiddlewareRewrite:ei});if(B||n.shallow||await this._bfl(r,"resolvedAs"in a?a.resolvedAs:void 0,W.locale),"route"in a&&eu){eo=et=a.route||eo,Q.shallow||(er=Object.assign({},a.query||{},er));let e=(0,R.hasBasePath)(ee.pathname)?(0,P.removeBasePath)(ee.pathname):ee.pathname;if(el&&et!==e&&Object.keys(el).forEach(e=>{el&&er[e]===el[e]&&delete er[e]}),(0,p.isDynamicRoute)(et)){let e=!Q.shallow&&a.resolvedAs?a.resolvedAs:(0,v.addBasePath)((0,y.addLocale)(new URL(r,location.href).pathname,W.locale),!0);(0,R.hasBasePath)(e)&&(e=(0,P.removeBasePath)(e));let t=(0,m.getRouteRegex)(et),n=(0,_.getRouteMatcher)(t)(new URL(e,location.href).pathname);n&&Object.assign(er,n)}}if("type"in a)if("redirect-internal"===a.type)return this.change(e,a.newUrl,a.newAs,n);else return q({url:a.destination,router:this}),new Promise(()=>{});let i=a.Component;if(i&&i.unstable_scriptLoader&&[].concat(i.unstable_scriptLoader()).forEach(e=>{(0,u.handleClientScriptLoad)(e.props)}),(a.__N_SSG||a.__N_SSP)&&a.props){if(a.props.pageProps&&a.props.pageProps.__N_REDIRECT){n.locale=!1;let t=a.props.pageProps.__N_REDIRECT;if(t.startsWith("/")&&!1!==a.props.pageProps.__N_REDIRECT_BASE_PATH){let r=(0,h.parseRelativeUrl)(t);r.pathname=k(r.pathname,M);let{url:o,as:a}=F(this,t,t);return this.change(e,o,a,n)}return q({url:t,router:this}),new Promise(()=>{})}if(W.isPreview=!!a.props.__N_PREVIEW,a.props.notFound===H){let e;try{await this.fetchComponent("/404"),e="/404"}catch(t){e="/_error"}if(a=await this.getRouteInfo({route:e,pathname:e,query:er,as:r,resolvedAs:en,routeProps:{shallow:!1},locale:W.locale,isPreview:W.isPreview,isNotFound:!0}),"type"in a)throw Object.defineProperty(Error("Unexpected middleware effect on /404"),"__NEXT_ERROR_CODE",{value:"E158",enumerable:!1,configurable:!0})}}B&&"/_error"===this.pathname&&(null==(f=self.__NEXT_DATA__.props)||null==(c=f.pageProps)?void 0:c.statusCode)===500&&(null==(O=a.props)?void 0:O.pageProps)&&(a.props.pageProps.statusCode=500);let s=n.shallow&&W.route===(null!=(S=a.route)?S:eo),d=null!=(T=n.scroll)?T:!B&&!s,g=null!=o?o:d?{x:0,y:0}:null,b={...W,route:eo,pathname:et,query:er,asPath:J,isFallback:!1};if(B&&es){if(a=await this.getRouteInfo({route:this.pathname,pathname:this.pathname,query:er,as:r,resolvedAs:en,routeProps:{shallow:!1},locale:W.locale,isPreview:W.isPreview,isQueryUpdating:B&&!this.isFallback}),"type"in a)throw Object.defineProperty(Error("Unexpected middleware effect on "+this.pathname),"__NEXT_ERROR_CODE",{value:"E225",enumerable:!1,configurable:!0});"/_error"===this.pathname&&(null==(C=self.__NEXT_DATA__.props)||null==(j=C.pageProps)?void 0:j.statusCode)===500&&(null==(N=a.props)?void 0:N.pageProps)&&(a.props.pageProps.statusCode=500);try{await this.set(b,a,g)}catch(e){throw(0,l.default)(e)&&e.cancelled&&z.events.emit("routeChangeError",e,J,Q),e}return!0}if(z.events.emit("beforeHistoryChange",r,Q),this.changeState(e,t,r,n),!(B&&!g&&!G&&!Z&&(0,A.compareRouterStates)(b,this.state))){try{await this.set(b,a,g)}catch(e){if(e.cancelled)a.error=a.error||e;else throw e}if(a.error)throw B||z.events.emit("routeChangeError",a.error,J,Q),a.error;B||z.events.emit("routeChangeComplete",r,Q),d&&/#.+$/.test(r)&&this.scrollToHash(r)}return!0}catch(e){if((0,l.default)(e)&&e.cancelled)return!1;throw e}}changeState(e,t,r,n){void 0===n&&(n={}),("pushState"!==e||(0,d.getURL)()!==r)&&(this._shallow=n.shallow,window.history[e]({url:t,as:r,options:n,__N:!0,key:this._key="pushState"!==e?this._key:G()},"",r))}async handleRouteInfoError(e,t,r,n,o,a){if(e.cancelled)throw e;if((0,i.isAssetError)(e)||a)throw z.events.emit("routeChangeError",e,n,o),q({url:n,router:this}),L();console.error(e);try{let n,{page:o,styleSheets:a}=await this.fetchComponent("/_error"),i={props:n,Component:o,styleSheets:a,err:e,error:e};if(!i.props)try{i.props=await this.getInitialProps(o,{err:e,pathname:t,query:r})}catch(e){console.error("Error in error page `getInitialProps`: ",e),i.props={}}return i}catch(e){return this.handleRouteInfoError((0,l.default)(e)?e:Object.defineProperty(Error(e+""),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0}),t,r,n,o,!0)}}async getRouteInfo(e){let{route:t,pathname:r,query:n,as:o,resolvedAs:i,routeProps:u,locale:s,hasMiddleware:f,isPreview:d,unstable_skipClientCache:p,isQueryUpdating:h,isMiddlewareRewrite:_,isNotFound:m}=e,b=t;try{var y,E,v,R;let e=this.components[b];if(u.shallow&&e&&this.route===b)return e;let t=V({route:b,router:this});f&&(e=void 0);let l=!e||"initial"in e?void 0:e,O={dataHref:this.pageLoader.getDataHref({href:(0,g.formatWithValidation)({pathname:r,query:n}),skipInterpolation:!0,asPath:m?"/404":i,locale:s}),hasMiddleware:!0,isServerRender:this.isSsr,parseJSON:!0,inflightCache:h?this.sbc:this.sdc,persistCache:!d,isPrefetch:!1,unstable_skipClientCache:p,isBackground:h},T=h&&!_?null:await B({fetchData:()=>W(O),asPath:m?"/404":i,locale:s,router:this}).catch(e=>{if(h)return null;throw e});if(T&&("/_error"===r||"/404"===r)&&(T.effect=void 0),h&&(T?T.json=self.__NEXT_DATA__.props:T={json:self.__NEXT_DATA__.props}),t(),(null==T||null==(y=T.effect)?void 0:y.type)==="redirect-internal"||(null==T||null==(E=T.effect)?void 0:E.type)==="redirect-external")return T.effect;if((null==T||null==(v=T.effect)?void 0:v.type)==="rewrite"){let t=(0,a.removeTrailingSlash)(T.effect.resolvedHref),o=await this.pageLoader.getPageList();if((!h||o.includes(t))&&(b=t,r=T.effect.resolvedHref,n={...n,...T.effect.parsedAs.query},i=(0,P.removeBasePath)((0,c.normalizeLocalePath)(T.effect.parsedAs.pathname,this.locales).pathname),e=this.components[b],u.shallow&&e&&this.route===b&&!f))return{...e,route:b}}if((0,S.isAPIRoute)(b))return q({url:o,router:this}),new Promise(()=>{});let j=l||await this.fetchComponent(b).then(e=>({Component:e.page,styleSheets:e.styleSheets,__N_SSG:e.mod.__N_SSG,__N_SSP:e.mod.__N_SSP})),A=null==T||null==(R=T.response)?void 0:R.headers.get("x-middleware-skip"),w=j.__N_SSG||j.__N_SSP;A&&(null==T?void 0:T.dataHref)&&delete this.sdc[T.dataHref];let{props:C,cacheKey:I}=await this._getData(async()=>{if(w){if((null==T?void 0:T.json)&&!A)return{cacheKey:T.cacheKey,props:T.json};let e=(null==T?void 0:T.dataHref)?T.dataHref:this.pageLoader.getDataHref({href:(0,g.formatWithValidation)({pathname:r,query:n}),asPath:i,locale:s}),t=await W({dataHref:e,isServerRender:this.isSsr,parseJSON:!0,inflightCache:A?{}:this.sdc,persistCache:!d,isPrefetch:!1,unstable_skipClientCache:p});return{cacheKey:t.cacheKey,props:t.json||{}}}return{headers:{},props:await this.getInitialProps(j.Component,{pathname:r,query:n,asPath:o,locale:s,locales:this.locales,defaultLocale:this.defaultLocale})}});return j.__N_SSP&&O.dataHref&&I&&delete this.sdc[I],this.isPreview||!j.__N_SSG||h||W(Object.assign({},O,{isBackground:!0,persistCache:!1,inflightCache:this.sbc})).catch(()=>{}),C.pageProps=Object.assign({},C.pageProps),j.props=C,j.route=b,j.query=n,j.resolvedAs=i,this.components[b]=j,j}catch(e){return this.handleRouteInfoError((0,l.getProperError)(e),r,n,o,u)}}set(e,t,r){return this.state=e,this.sub(t,this.components["/_app"].Component,r)}beforePopState(e){this._bps=e}onlyAHashChange(e){if(!this.asPath)return!1;let[t,r]=this.asPath.split("#",2),[n,o]=e.split("#",2);return!!o&&t===n&&r===o||t===n&&r!==o}scrollToHash(e){let[,t=""]=e.split("#",2);(0,N.disableSmoothScrollDuringRouteTransition)(()=>{if(""===t||"top"===t)return void window.scrollTo(0,0);let e=decodeURIComponent(t),r=document.getElementById(e);if(r)return void r.scrollIntoView();let n=document.getElementsByName(e)[0];n&&n.scrollIntoView()},{onlyHashChange:this.onlyAHashChange(e)})}urlIsNew(e){return this.asPath!==e}async prefetch(e,t,r){if(void 0===t&&(t=e),void 0===r&&(r={}),(0,C.isBot)(window.navigator.userAgent))return;let n=(0,h.parseRelativeUrl)(e),o=n.pathname,{pathname:i,query:u}=n,l=i,s=await this.pageLoader.getPageList(),c=t,f=void 0!==r.locale?r.locale||void 0:this.locale,d=await D({asPath:t,locale:f,router:this});n.pathname=k(n.pathname,s),(0,p.isDynamicRoute)(n.pathname)&&(i=n.pathname,n.pathname=i,Object.assign(u,(0,_.getRouteMatcher)((0,m.getRouteRegex)(n.pathname))((0,b.parsePath)(t).pathname)||{}),d||(e=(0,g.formatWithValidation)(n)));let y=await B({fetchData:()=>W({dataHref:this.pageLoader.getDataHref({href:(0,g.formatWithValidation)({pathname:l,query:u}),skipInterpolation:!0,asPath:c,locale:f}),hasMiddleware:!0,isServerRender:!1,parseJSON:!0,inflightCache:this.sdc,persistCache:!this.isPreview,isPrefetch:!0}),asPath:t,locale:f,router:this});if((null==y?void 0:y.effect.type)==="rewrite"&&(n.pathname=y.effect.resolvedHref,i=y.effect.resolvedHref,u={...u,...y.effect.parsedAs.query},c=y.effect.parsedAs.pathname,e=(0,g.formatWithValidation)(n)),(null==y?void 0:y.effect.type)==="redirect-external")return;let E=(0,a.removeTrailingSlash)(i);await this._bfl(t,c,r.locale,!0)&&(this.components[o]={__appRouter:!0}),await Promise.all([this.pageLoader._isSsg(E).then(t=>!!t&&W({dataHref:(null==y?void 0:y.json)?null==y?void 0:y.dataHref:this.pageLoader.getDataHref({href:e,asPath:c,locale:f}),isServerRender:!1,parseJSON:!0,inflightCache:this.sdc,persistCache:!this.isPreview,isPrefetch:!0,unstable_skipClientCache:r.unstable_skipClientCache||r.priority&&!0}).then(()=>!1).catch(()=>!1)),this.pageLoader[r.priority?"loadPage":"prefetch"](E)])}async fetchComponent(e){let t=V({route:e,router:this});try{let r=await this.pageLoader.loadPage(e);return t(),r}catch(e){throw t(),e}}_getData(e){let t=!1,r=()=>{t=!0};return this.clc=r,e().then(e=>{if(r===this.clc&&(this.clc=null),t){let e=Object.defineProperty(Error("Loading initial props cancelled"),"__NEXT_ERROR_CODE",{value:"E405",enumerable:!1,configurable:!0});throw e.cancelled=!0,e}return e})}getInitialProps(e,t){let{Component:r}=this.components["/_app"],n=this._wrapApp(r);return t.AppTree=n,(0,d.loadGetInitialProps)(r,{AppTree:n,Component:e,router:this,ctx:t})}get route(){return this.state.route}get pathname(){return this.state.pathname}get query(){return this.state.query}get asPath(){return this.state.asPath}get locale(){return this.state.locale}get isFallback(){return this.state.isFallback}get isPreview(){return this.state.isPreview}constructor(e,t,r,{initialProps:n,pageLoader:o,App:i,wrapApp:u,Component:l,err:s,subscription:c,isFallback:f,locale:_,locales:m,defaultLocale:b,domainLocales:y,isPreview:E}){this.sdc={},this.sbc={},this.isFirstPopStateEvent=!0,this._key=G(),this.onPopState=e=>{let t,{isFirstPopStateEvent:r}=this;this.isFirstPopStateEvent=!1;let n=e.state;if(!n){let{pathname:e,query:t}=this;this.changeState("replaceState",(0,g.formatWithValidation)({pathname:(0,v.addBasePath)(e),query:t}),(0,d.getURL)());return}if(n.__NA)return void window.location.reload();if(!n.__N||r&&this.locale===n.options.locale&&n.as===this.asPath)return;let{url:o,as:a,options:i,key:u}=n;this._key=u;let{pathname:l}=(0,h.parseRelativeUrl)(o);(!this.isSsr||a!==(0,v.addBasePath)(this.asPath)||l!==(0,v.addBasePath)(this.pathname))&&(!this._bps||this._bps(n))&&this.change("replaceState",o,a,Object.assign({},i,{shallow:i.shallow&&this._shallow,locale:i.locale||this.defaultLocale,_h:0}),t)};let P=(0,a.removeTrailingSlash)(e);this.components={},"/_error"!==e&&(this.components[P]={Component:l,initial:!0,props:n,err:s,__N_SSG:n&&n.__N_SSG,__N_SSP:n&&n.__N_SSP}),this.components["/_app"]={Component:i,styleSheets:[]},this.events=z.events,this.pageLoader=o;let R=(0,p.isDynamicRoute)(e)&&self.__NEXT_DATA__.autoExport;if(this.basePath="",this.sub=c,this.clc=null,this._wrapApp=u,this.isSsr=!0,this.isLocaleDomain=!1,this.isReady=!!(self.__NEXT_DATA__.gssp||self.__NEXT_DATA__.gip||self.__NEXT_DATA__.isExperimentalCompile||self.__NEXT_DATA__.appGip&&!self.__NEXT_DATA__.gsp||!R&&!self.location.search),this.state={route:P,pathname:e,query:t,asPath:R?e:r,isPreview:!!E,locale:void 0,isFallback:f},this._initialMatchesMiddlewarePromise=Promise.resolve(!1),!r.startsWith("//")){let n={locale:_},o=(0,d.getURL)();this._initialMatchesMiddlewarePromise=D({router:this,locale:_,asPath:o}).then(a=>(n._shouldResolveHref=r!==e,this.changeState("replaceState",a?o:(0,g.formatWithValidation)({pathname:(0,v.addBasePath)(e),query:t}),o,n),a))}window.addEventListener("popstate",this.onPopState)}}z.events=(0,f.default)()},8365:(e,t,r)=>{"use strict";function n(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(n=function(e){return e?r:t})(e)}function o(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=n(t);if(r&&r.has(e))return r.get(e);var o={__proto__:null},a=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if("default"!==i&&Object.prototype.hasOwnProperty.call(e,i)){var u=a?Object.getOwnPropertyDescriptor(e,i):null;u&&(u.get||u.set)?Object.defineProperty(o,i,u):o[i]=e[i]}return o.default=e,r&&r.set(e,o),o}r.r(t),r.d(t,{_:()=>o})},8480:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{formatUrl:function(){return a},formatWithValidation:function(){return u},urlObjectKeys:function(){return i}});let n=r(8365)._(r(8040)),o=/https?|ftp|gopher|file/;function a(e){let{auth:t,hostname:r}=e,a=e.protocol||"",i=e.pathname||"",u=e.hash||"",l=e.query||"",s=!1;t=t?encodeURIComponent(t).replace(/%3A/i,":")+"@":"",e.host?s=t+e.host:r&&(s=t+(~r.indexOf(":")?"["+r+"]":r),e.port&&(s+=":"+e.port)),l&&"object"==typeof l&&(l=String(n.urlQueryToSearchParams(l)));let c=e.search||l&&"?"+l||"";return a&&!a.endsWith(":")&&(a+=":"),e.slashes||(!a||o.test(a))&&!1!==s?(s="//"+(s||""),i&&"/"!==i[0]&&(i="/"+i)):s||(s=""),u&&"#"!==u[0]&&(u="#"+u),c&&"?"!==c[0]&&(c="?"+c),""+a+s+(i=i.replace(/[?#]/g,encodeURIComponent))+(c=c.replace("#","%23"))+u}let i=["auth","hash","host","hostname","href","path","pathname","port","protocol","query","search","slashes"];function u(e){return a(e)}},8677:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ImageConfigContext",{enumerable:!0,get:function(){return a}});let n=r(4252)._(r(4232)),o=r(7539),a=n.default.createContext(o.imageConfigDefault)},8714:(e,t)=>{"use strict";function r(e){return"("===e[0]&&e.endsWith(")")}function n(e){return e.startsWith("@")&&"@children"!==e}function o(e,t){if(e.includes(a)){let e=JSON.stringify(t);return"{}"!==e?a+"?"+e:a}return e}Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{DEFAULT_SEGMENT_KEY:function(){return i},PAGE_SEGMENT_KEY:function(){return a},addSearchParamsIfPageSegment:function(){return o},isGroupSegment:function(){return r},isParallelRouteSegment:function(){return n}});let a="__PAGE__",i="__DEFAULT__"},8757:(e,t)=>{"use strict";function r(){return""}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getDeploymentIdQueryOrEmptyString",{enumerable:!0,get:function(){return r}})},8831:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HeadManagerContext",{enumerable:!0,get:function(){return n}});let n=r(4252)._(r(4232)).default.createContext({})},9163:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isDynamicRoute",{enumerable:!0,get:function(){return i}});let n=r(7188),o=/\/[^/]*\[[^/]+\][^/]*(?=\/|$)/,a=/\/\[[^/]+\](?=\/|$)/;function i(e,t){return(void 0===t&&(t=!0),(0,n.isInterceptionRouteAppPath)(e)&&(e=(0,n.extractInterceptionRouteInformation)(e).interceptedRoute),t)?a.test(e):o.test(e)}},9308:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{ACTION_SUFFIX:function(){return f},APP_DIR_ALIAS:function(){return I},CACHE_ONE_YEAR:function(){return R},DOT_NEXT_ALIAS:function(){return w},ESLINT_DEFAULT_DIRS:function(){return Q},GSP_NO_RETURNED_VALUE:function(){return q},GSSP_COMPONENT_MEMBER_ERROR:function(){return Y},GSSP_NO_RETURNED_VALUE:function(){return V},INFINITE_CACHE:function(){return O},INSTRUMENTATION_HOOK_FILENAME:function(){return j},MATCHED_PATH_HEADER:function(){return o},MIDDLEWARE_FILENAME:function(){return S},MIDDLEWARE_LOCATION_REGEXP:function(){return T},NEXT_BODY_SUFFIX:function(){return h},NEXT_CACHE_IMPLICIT_TAG_ID:function(){return v},NEXT_CACHE_REVALIDATED_TAGS_HEADER:function(){return m},NEXT_CACHE_REVALIDATE_TAG_TOKEN_HEADER:function(){return g},NEXT_CACHE_SOFT_TAG_MAX_LENGTH:function(){return P},NEXT_CACHE_TAGS_HEADER:function(){return _},NEXT_CACHE_TAG_MAX_ITEMS:function(){return y},NEXT_CACHE_TAG_MAX_LENGTH:function(){return E},NEXT_DATA_SUFFIX:function(){return d},NEXT_INTERCEPTION_MARKER_PREFIX:function(){return n},NEXT_META_SUFFIX:function(){return p},NEXT_QUERY_PARAM_PREFIX:function(){return r},NEXT_RESUME_HEADER:function(){return b},NON_STANDARD_NODE_ENV:function(){return K},PAGES_DIR_ALIAS:function(){return A},PRERENDER_REVALIDATE_HEADER:function(){return a},PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER:function(){return i},PUBLIC_DIR_MIDDLEWARE_CONFLICT:function(){return k},ROOT_DIR_ALIAS:function(){return C},RSC_ACTION_CLIENT_WRAPPER_ALIAS:function(){return F},RSC_ACTION_ENCRYPTION_ALIAS:function(){return U},RSC_ACTION_PROXY_ALIAS:function(){return M},RSC_ACTION_VALIDATE_ALIAS:function(){return N},RSC_CACHE_WRAPPER_ALIAS:function(){return L},RSC_DYNAMIC_IMPORT_WRAPPER_ALIAS:function(){return D},RSC_MOD_REF_PROXY_ALIAS:function(){return x},RSC_PREFETCH_SUFFIX:function(){return u},RSC_SEGMENTS_DIR_SUFFIX:function(){return l},RSC_SEGMENT_SUFFIX:function(){return s},RSC_SUFFIX:function(){return c},SERVER_PROPS_EXPORT_ERROR:function(){return G},SERVER_PROPS_GET_INIT_PROPS_CONFLICT:function(){return H},SERVER_PROPS_SSG_CONFLICT:function(){return X},SERVER_RUNTIME:function(){return J},SSG_FALLBACK_EXPORT_ERROR:function(){return $},SSG_GET_INITIAL_PROPS_CONFLICT:function(){return B},STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR:function(){return W},UNSTABLE_REVALIDATE_RENAME_ERROR:function(){return z},WEBPACK_LAYERS:function(){return ee},WEBPACK_RESOURCE_QUERIES:function(){return et}});let r="nxtP",n="nxtI",o="x-matched-path",a="x-prerender-revalidate",i="x-prerender-revalidate-if-generated",u=".prefetch.rsc",l=".segments",s=".segment.rsc",c=".rsc",f=".action",d=".json",p=".meta",h=".body",_="x-next-cache-tags",m="x-next-revalidated-tags",g="x-next-revalidate-tag-token",b="next-resume",y=128,E=256,P=1024,v="_N_T_",R=31536e3,O=0xfffffffe,S="middleware",T=`(?:src/)?${S}`,j="instrumentation",A="private-next-pages",w="private-dot-next",C="private-next-root-dir",I="private-next-app-dir",x="private-next-rsc-mod-ref-proxy",N="private-next-rsc-action-validate",M="private-next-rsc-server-reference",L="private-next-rsc-cache-wrapper",D="private-next-rsc-track-dynamic-import",U="private-next-rsc-action-encryption",F="private-next-rsc-action-client-wrapper",k="You can not have a '_next' folder inside of your public folder. This conflicts with the internal '/_next' route. https://nextjs.org/docs/messages/public-next-folder-conflict",B="You can not use getInitialProps with getStaticProps. To use SSG, please remove your getInitialProps",H="You can not use getInitialProps with getServerSideProps. Please remove getInitialProps.",X="You can not use getStaticProps or getStaticPaths with getServerSideProps. To use SSG, please remove getServerSideProps",W="can not have getInitialProps/getServerSideProps, https://nextjs.org/docs/messages/404-get-initial-props",G="pages with `getServerSideProps` can not be exported. See more info here: https://nextjs.org/docs/messages/gssp-export",q="Your `getStaticProps` function did not return an object. Did you forget to add a `return`?",V="Your `getServerSideProps` function did not return an object. Did you forget to add a `return`?",z="The `unstable_revalidate` property is available for general use.\nPlease use `revalidate` instead.",Y="can not be attached to a page's component and must be exported from the page. See more info here: https://nextjs.org/docs/messages/gssp-component-member",K='You are using a non-standard "NODE_ENV" value in your environment. This creates inconsistencies in the project and is strongly advised against. Read more: https://nextjs.org/docs/messages/non-standard-node-env',$="Pages with `fallback` enabled in `getStaticPaths` can not be exported. See more info here: https://nextjs.org/docs/messages/ssg-fallback-true-export",Q=["app","pages","components","lib","src"],J={edge:"edge",experimentalEdge:"experimental-edge",nodejs:"nodejs"},Z={shared:"shared",reactServerComponents:"rsc",serverSideRendering:"ssr",actionBrowser:"action-browser",apiNode:"api-node",apiEdge:"api-edge",middleware:"middleware",instrument:"instrument",edgeAsset:"edge-asset",appPagesBrowser:"app-pages-browser",pagesDirBrowser:"pages-dir-browser",pagesDirEdge:"pages-dir-edge",pagesDirNode:"pages-dir-node"},ee={...Z,GROUP:{builtinReact:[Z.reactServerComponents,Z.actionBrowser],serverOnly:[Z.reactServerComponents,Z.actionBrowser,Z.instrument,Z.middleware],neutralTarget:[Z.apiNode,Z.apiEdge],clientOnly:[Z.serverSideRendering,Z.appPagesBrowser],bundled:[Z.reactServerComponents,Z.actionBrowser,Z.serverSideRendering,Z.appPagesBrowser,Z.shared,Z.instrument,Z.middleware],appPages:[Z.reactServerComponents,Z.serverSideRendering,Z.appPagesBrowser,Z.actionBrowser]}},et={edgeSSREntry:"__next_edge_ssr_entry__",metadata:"__next_metadata__",metadataRoute:"__next_metadata_route__",metadataImageMeta:"__next_metadata_image_meta__"}},9341:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return c}});let n=r(4252),o=r(7876),a=n._(r(4232)),i=n._(r(5679)),u={400:"Bad Request",404:"This page could not be found",405:"Method Not Allowed",500:"Internal Server Error"};function l(e){let{req:t,res:r,err:n}=e;return{statusCode:r&&r.statusCode?r.statusCode:n?n.statusCode:404,hostname:window.location.hostname}}let s={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{lineHeight:"48px"},h1:{display:"inline-block",margin:"0 20px 0 0",paddingRight:23,fontSize:24,fontWeight:500,verticalAlign:"top"},h2:{fontSize:14,fontWeight:400,lineHeight:"28px"},wrap:{display:"inline-block"}};class c extends a.default.Component{render(){let{statusCode:e,withDarkMode:t=!0}=this.props,r=this.props.title||u[e]||"An unexpected error has occurred";return(0,o.jsxs)("div",{style:s.error,children:[(0,o.jsx)(i.default,{children:(0,o.jsx)("title",{children:e?e+": "+r:"Application error: a client-side exception has occurred"})}),(0,o.jsxs)("div",{style:s.desc,children:[(0,o.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}"+(t?"@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}":"")}}),e?(0,o.jsx)("h1",{className:"next-error-h1",style:s.h1,children:e}):null,(0,o.jsx)("div",{style:s.wrap,children:(0,o.jsxs)("h2",{style:s.h2,children:[this.props.title||e?r:(0,o.jsxs)(o.Fragment,{children:["Application error: a client-side exception has occurred"," ",!!this.props.hostname&&(0,o.jsxs)(o.Fragment,{children:["while loading ",this.props.hostname]})," ","(see the browser console for more information)"]}),"."]})})]})]})}}c.displayName="ErrorPage",c.getInitialProps=l,c.origGetInitialProps=l,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9393:()=>{},9525:(e,t,r)=>{"use strict";let n,o,a,i,u,l,s,c,f,d,p,h;Object.defineProperty(t,"__esModule",{value:!0});let _=r(8365);Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{emitter:function(){return X},hydrate:function(){return el},initialize:function(){return V},router:function(){return n},version:function(){return H}});let m=r(4252),g=r(7876);r(1291);let b=m._(r(4232)),y=m._(r(8944)),E=r(8831),P=m._(r(9871)),v=r(9948),R=r(3980),O=r(9163),S=r(8040),T=r(2917),j=r(2746),A=r(3090),w=m._(r(4547)),C=m._(r(2792)),I=r(1318),x=r(4294),N=r(6240),M=r(8677),L=r(1025),D=r(6023),U=r(2850),F=r(9609),k=r(5931),B=r(7207);r(4609),r(6999);let H="15.4.2",X=(0,P.default)(),W=e=>[].slice.call(e),G=!1;class q extends b.default.Component{componentDidCatch(e,t){this.props.fn(e,t)}componentDidMount(){this.scrollToHash(),n.isSsr&&(o.isFallback||o.nextExport&&((0,O.isDynamicRoute)(n.pathname)||location.search||G)||o.props&&o.props.__N_SSG&&(location.search||G))&&n.replace(n.pathname+"?"+String((0,S.assign)((0,S.urlQueryToSearchParams)(n.query),new URLSearchParams(location.search))),a,{_h:1,shallow:!o.isFallback&&!G}).catch(e=>{if(!e.cancelled)throw e})}componentDidUpdate(){this.scrollToHash()}scrollToHash(){let{hash:e}=location;if(!(e=e&&e.substring(1)))return;let t=document.getElementById(e);t&&setTimeout(()=>t.scrollIntoView(),0)}render(){return this.props.children}}async function V(e){void 0===e&&(e={}),o=JSON.parse(document.getElementById("__NEXT_DATA__").textContent),window.__NEXT_DATA__=o,h=o.defaultLocale;let t=o.assetPrefix||"";if(self.__next_set_public_path__(""+t+"/_next/"),(0,T.setConfig)({serverRuntimeConfig:{},publicRuntimeConfig:o.runtimeConfig||{}}),a=(0,j.getURL)(),(0,D.hasBasePath)(a)&&(a=(0,L.removeBasePath)(a)),o.scriptLoader){let{initScriptLoader:e}=r(3996);e(o.scriptLoader)}i=new C.default(o.buildId,t);let s=e=>{let[t,r]=e;return i.routeLoader.onEntrypoint(t,r)};return window.__NEXT_P&&window.__NEXT_P.map(e=>setTimeout(()=>s(e),0)),window.__NEXT_P=[],window.__NEXT_P.push=s,(l=(0,w.default)()).getIsSsr=()=>n.isSsr,u=document.getElementById("__next"),{assetPrefix:t}}function z(e,t){return(0,g.jsx)(e,{...t})}function Y(e){var t;let{children:r}=e,o=b.default.useMemo(()=>(0,F.adaptForAppRouterInstance)(n),[]);return(0,g.jsx)(q,{fn:e=>$({App:f,err:e}).catch(e=>console.error("Error rendering page: ",e)),children:(0,g.jsx)(U.AppRouterContext.Provider,{value:o,children:(0,g.jsx)(k.SearchParamsContext.Provider,{value:(0,F.adaptForSearchParams)(n),children:(0,g.jsx)(F.PathnameContextProviderAdapter,{router:n,isAutoExport:null!=(t=self.__NEXT_DATA__.autoExport)&&t,children:(0,g.jsx)(k.PathParamsContext.Provider,{value:(0,F.adaptForPathParams)(n),children:(0,g.jsx)(v.RouterContext.Provider,{value:(0,x.makePublicRouterInstance)(n),children:(0,g.jsx)(E.HeadManagerContext.Provider,{value:l,children:(0,g.jsx)(M.ImageConfigContext.Provider,{value:{deviceSizes:[640,750,828,1080,1200,1920,2048,3840],imageSizes:[16,32,48,64,96,128,256,384],path:"/_next/image/",loader:"default",dangerouslyAllowSVG:!1,unoptimized:!0},children:r})})})})})})})})}let K=e=>t=>{let r={...t,Component:p,err:o.err,router:n};return(0,g.jsx)(Y,{children:z(e,r)})};function $(e){let{App:t,err:u}=e;return console.error(u),console.error("A client-side exception has occurred, see here for more info: https://nextjs.org/docs/messages/client-side-exception-occurred"),i.loadPage("/_error").then(n=>{let{page:o,styleSheets:a}=n;return(null==s?void 0:s.Component)===o?Promise.resolve().then(()=>_._(r(9341))).then(n=>Promise.resolve().then(()=>_._(r(472))).then(r=>(e.App=t=r.default,n))).then(e=>({ErrorComponent:e.default,styleSheets:[]})):{ErrorComponent:o,styleSheets:a}}).then(r=>{var i;let{ErrorComponent:l,styleSheets:s}=r,c=K(t),f={Component:l,AppTree:c,router:n,ctx:{err:u,pathname:o.page,query:o.query,asPath:a,AppTree:c}};return Promise.resolve((null==(i=e.props)?void 0:i.err)?e.props:(0,j.loadGetInitialProps)(t,f)).then(t=>ei({...e,err:u,Component:l,styleSheets:s,props:t}))})}function Q(e){let{callback:t}=e;return b.default.useLayoutEffect(()=>t(),[t]),null}let J={navigationStart:"navigationStart",beforeRender:"beforeRender",afterRender:"afterRender",afterHydrate:"afterHydrate",routeChange:"routeChange"},Z={hydration:"Next.js-hydration",beforeHydration:"Next.js-before-hydration",routeChangeToRender:"Next.js-route-change-to-render",render:"Next.js-render"},ee=null,et=!0;function er(){[J.beforeRender,J.afterHydrate,J.afterRender,J.routeChange].forEach(e=>performance.clearMarks(e))}function en(){j.ST&&(performance.mark(J.afterHydrate),performance.getEntriesByName(J.beforeRender,"mark").length&&(performance.measure(Z.beforeHydration,J.navigationStart,J.beforeRender),performance.measure(Z.hydration,J.beforeRender,J.afterHydrate)),d&&performance.getEntriesByName(Z.hydration).forEach(d),er())}function eo(){if(!j.ST)return;performance.mark(J.afterRender);let e=performance.getEntriesByName(J.routeChange,"mark");e.length&&(performance.getEntriesByName(J.beforeRender,"mark").length&&(performance.measure(Z.routeChangeToRender,e[0].name,J.beforeRender),performance.measure(Z.render,J.beforeRender,J.afterRender),d&&(performance.getEntriesByName(Z.render).forEach(d),performance.getEntriesByName(Z.routeChangeToRender).forEach(d))),er(),[Z.routeChangeToRender,Z.render].forEach(e=>performance.clearMeasures(e)))}function ea(e){let{callbacks:t,children:r}=e;return b.default.useLayoutEffect(()=>t.forEach(e=>e()),[t]),r}function ei(e){let t,r,{App:o,Component:a,props:i,err:l}=e,f="initial"in e?void 0:e.styleSheets;a=a||s.Component;let d={...i=i||s.props,Component:a,err:l,router:n};s=d;let p=!1,h=new Promise((e,t)=>{c&&c(),r=()=>{c=null,e()},c=()=>{p=!0,c=null;let e=Object.defineProperty(Error("Cancel rendering route"),"__NEXT_ERROR_CODE",{value:"E503",enumerable:!1,configurable:!0});e.cancelled=!0,t(e)}});function _(){r()}!function(){if(!f)return;let e=new Set(W(document.querySelectorAll("style[data-n-href]")).map(e=>e.getAttribute("data-n-href"))),t=document.querySelector("noscript[data-n-css]"),r=null==t?void 0:t.getAttribute("data-n-css");f.forEach(t=>{let{href:n,text:o}=t;if(!e.has(n)){let e=document.createElement("style");e.setAttribute("data-n-href",n),e.setAttribute("media","x"),r&&e.setAttribute("nonce",r),document.head.appendChild(e),e.appendChild(document.createTextNode(o))}})}();let m=(0,g.jsxs)(g.Fragment,{children:[(0,g.jsx)(Q,{callback:function(){if(f&&!p){let e=new Set(f.map(e=>e.href)),t=W(document.querySelectorAll("style[data-n-href]")),r=t.map(e=>e.getAttribute("data-n-href"));for(let n=0;n{let{href:t}=e,r=document.querySelector('style[data-n-href="'+t+'"]');r&&(n.parentNode.insertBefore(r,n.nextSibling),n=r)}),W(document.querySelectorAll("link[data-n-p]")).forEach(e=>{e.parentNode.removeChild(e)})}if(e.scroll){let{x:t,y:r}=e.scroll;(0,R.disableSmoothScrollDuringRouteTransition)(()=>{window.scrollTo(t,r)})}}}),(0,g.jsxs)(Y,{children:[z(o,d),(0,g.jsx)(A.Portal,{type:"next-route-announcer",children:(0,g.jsx)(I.RouteAnnouncer,{})})]})]});var E=u;j.ST&&performance.mark(J.beforeRender);let P=(t=et?en:eo,(0,g.jsx)(ea,{callbacks:[t,_],children:(0,g.jsx)(b.default.StrictMode,{children:m})}));return ee?(0,b.default.startTransition)(()=>{ee.render(P)}):(ee=y.default.hydrateRoot(E,P,{onRecoverableError:B.onRecoverableError}),et=!1),h}async function eu(e){if(e.err&&(void 0===e.Component||!e.isHydratePass))return void await $(e);try{await ei(e)}catch(r){let t=(0,N.getProperError)(r);if(t.cancelled)throw t;await $({...e,err:t})}}async function el(e){let t=o.err;try{let e=await i.routeLoader.whenEntrypoint("/_app");if("error"in e)throw e.error;let{component:t,exports:r}=e;f=t,r&&r.reportWebVitals&&(d=e=>{let t,{id:n,name:o,startTime:a,value:i,duration:u,entryType:l,entries:s,attribution:c}=e,f=Date.now()+"-"+(Math.floor(Math.random()*(9e12-1))+1e12);s&&s.length&&(t=s[0].startTime);let d={id:n||f,name:o,startTime:a||t,value:null==i?u:i,label:"mark"===l||"measure"===l?"custom":"web-vital"};c&&(d.attribution=c),r.reportWebVitals(d)});let n=await i.routeLoader.whenEntrypoint(o.page);if("error"in n)throw n.error;p=n.component}catch(e){t=(0,N.getProperError)(e)}window.__NEXT_PRELOADREADY&&await window.__NEXT_PRELOADREADY(o.dynamicIds),n=(0,x.createRouter)(o.page,o.query,a,{initialProps:o.props,pageLoader:i,App:f,Component:p,wrapApp:K,err:t,isFallback:!!o.isFallback,subscription:(e,t,r)=>eu(Object.assign({},e,{App:t,scroll:r})),locale:o.locale,locales:o.locales,defaultLocale:h,domainLocales:o.domainLocales,isPreview:o.isPreview}),G=await n._initialMatchesMiddlewarePromise;let r={App:f,initial:!0,Component:p,props:o.props,err:t,isHydratePass:!0};(null==e?void 0:e.beforeRender)&&await e.beforeRender(),eu(r)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9609:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),!function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}(t,{PathnameContextProviderAdapter:function(){return p},adaptForAppRouterInstance:function(){return c},adaptForPathParams:function(){return d},adaptForSearchParams:function(){return f}});let n=r(8365),o=r(7876),a=n._(r(4232)),i=r(5931),u=r(3069),l=r(8213),s=r(5214);function c(e){return{back(){e.back()},forward(){e.forward()},refresh(){e.reload()},hmrRefresh(){},push(t,r){let{scroll:n}=void 0===r?{}:r;e.push(t,void 0,{scroll:n})},replace(t,r){let{scroll:n}=void 0===r?{}:r;e.replace(t,void 0,{scroll:n})},prefetch(t){e.prefetch(t)}}}function f(e){return e.isReady&&e.query?(0,l.asPathToSearchParams)(e.asPath):new URLSearchParams}function d(e){if(!e.isReady||!e.query)return null;let t={};for(let r of Object.keys((0,s.getRouteRegex)(e.pathname).groups))t[r]=e.query[r];return t}function p(e){let{children:t,router:r,...n}=e,l=(0,a.useRef)(n.isAutoExport),s=(0,a.useMemo)(()=>{let e,t=l.current;if(t&&(l.current=!1),(0,u.isDynamicRoute)(r.pathname)&&(r.isFallback||t&&!r.isReady))return null;try{e=new URL(r.asPath,"http://f")}catch(e){return"/"}return e.pathname},[r.asPath,r.isFallback,r.isReady,r.pathname]);return(0,o.jsx)(i.PathnameContext.Provider,{value:s,children:t})}},9611:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"setAttributesFromProps",{enumerable:!0,get:function(){return a}});let r={acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv",noModule:"noModule"},n=["onLoad","onReady","dangerouslySetInnerHTML","children","onError","strategy","stylesheets"];function o(e){return["async","defer","noModule"].includes(e)}function a(e,t){for(let[a,i]of Object.entries(t)){if(!t.hasOwnProperty(a)||n.includes(a)||void 0===i)continue;let u=r[a]||a.toLowerCase();"SCRIPT"===e.tagName&&o(u)?e[u]=!!i:e.setAttribute(u,String(i)),(!1===i||"SCRIPT"===e.tagName&&o(u)&&(!i||"false"===i))&&(e.setAttribute(u,""),e.removeAttribute(u))}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9871:(e,t)=>{"use strict";function r(){let e=Object.create(null);return{on(t,r){(e[t]||(e[t]=[])).push(r)},off(t,r){e[t]&&e[t].splice(e[t].indexOf(r)>>>0,1)},emit(t){for(var r=arguments.length,n=Array(r>1?r-1:0),o=1;o{e(...n)})}}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return r}})},9948:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"RouterContext",{enumerable:!0,get:function(){return n}});let n=r(4252)._(r(4232)).default.createContext(null)}},e=>{e.O(0,[593],()=>e(e.s=5842)),_N_E=e.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/main-app-b3d2ebcb2857645a.js b/keploy/pkg/service/load/out/_next/static/chunks/main-app-b3d2ebcb2857645a.js deleted file mode 100644 index d0b6218..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/main-app-b3d2ebcb2857645a.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[358],{5972:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,8393,23)),Promise.resolve().then(n.t.bind(n,894,23)),Promise.resolve().then(n.t.bind(n,4970,23)),Promise.resolve().then(n.t.bind(n,6975,23)),Promise.resolve().then(n.t.bind(n,7555,23)),Promise.resolve().then(n.t.bind(n,4911,23)),Promise.resolve().then(n.t.bind(n,9665,23)),Promise.resolve().then(n.t.bind(n,1295,23)),Promise.resolve().then(n.bind(n,8175))},9393:()=>{}},e=>{var s=s=>e(e.s=s);e.O(0,[441,964],()=>(s(5415),s(5972))),_N_E=e.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/pages/_app-0a0020ddd67f79cf.js b/keploy/pkg/service/load/out/_next/static/chunks/pages/_app-0a0020ddd67f79cf.js deleted file mode 100644 index 5c39d7b..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/pages/_app-0a0020ddd67f79cf.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[636],{326:(_,n,p)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return p(472)}])}},_=>{var n=n=>_(_.s=n);_.O(0,[593,792],()=>(n(326),n(4294))),_N_E=_.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/pages/_error-03529f2c21436739.js b/keploy/pkg/service/load/out/_next/static/chunks/pages/_error-03529f2c21436739.js deleted file mode 100644 index f9fbf98..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/pages/_error-03529f2c21436739.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[731],{2164:(_,n,e)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return e(9341)}])}},_=>{_.O(0,[636,593,792],()=>_(_.s=2164)),_N_E=_.O()}]); \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/chunks/polyfills-42372ed130431b0a.js b/keploy/pkg/service/load/out/_next/static/chunks/polyfills-42372ed130431b0a.js deleted file mode 100644 index ab422b9..0000000 --- a/keploy/pkg/service/load/out/_next/static/chunks/polyfills-42372ed130431b0a.js +++ /dev/null @@ -1 +0,0 @@ -!function(){var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function e(t){var e={exports:{}};return t(e,e.exports),e.exports}var r,n,o=function(t){return t&&t.Math===Math&&t},i=o("object"==typeof globalThis&&globalThis)||o("object"==typeof window&&window)||o("object"==typeof self&&self)||o("object"==typeof t&&t)||o("object"==typeof t&&t)||function(){return this}()||Function("return this")(),a=function(t){try{return!!t()}catch(t){return!0}},u=!a(function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]}),s=!a(function(){var t=function(){}.bind();return"function"!=typeof t||t.hasOwnProperty("prototype")}),c=Function.prototype.call,f=s?c.bind(c):function(){return c.apply(c,arguments)},l={}.propertyIsEnumerable,h=Object.getOwnPropertyDescriptor,p=h&&!l.call({1:2},1)?function(t){var e=h(this,t);return!!e&&e.enumerable}:l,v={f:p},d=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},g=Function.prototype,y=g.call,m=s&&g.bind.bind(y,y),b=s?m:function(t){return function(){return y.apply(t,arguments)}},w=b({}.toString),S=b("".slice),E=function(t){return S(w(t),8,-1)},O=Object,x=b("".split),R=a(function(){return!O("z").propertyIsEnumerable(0)})?function(t){return"String"===E(t)?x(t,""):O(t)}:O,P=function(t){return null==t},A=TypeError,j=function(t){if(P(t))throw new A("Can't call method on "+t);return t},k=function(t){return R(j(t))},I="object"==typeof document&&document.all,T=void 0===I&&void 0!==I?function(t){return"function"==typeof t||t===I}:function(t){return"function"==typeof t},M=function(t){return"object"==typeof t?null!==t:T(t)},L=function(t,e){return arguments.length<2?T(r=i[t])?r:void 0:i[t]&&i[t][e];var r},U=b({}.isPrototypeOf),N=i.navigator,C=N&&N.userAgent,_=C?String(C):"",F=i.process,B=i.Deno,D=F&&F.versions||B&&B.version,z=D&&D.v8;z&&(n=(r=z.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!n&&_&&(!(r=_.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=_.match(/Chrome\/(\d+)/))&&(n=+r[1]);var W=n,q=i.String,H=!!Object.getOwnPropertySymbols&&!a(function(){var t=Symbol("symbol detection");return!q(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&W&&W<41}),$=H&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,K=Object,G=$?function(t){return"symbol"==typeof t}:function(t){var e=L("Symbol");return T(e)&&U(e.prototype,K(t))},V=String,Y=function(t){try{return V(t)}catch(t){return"Object"}},X=TypeError,J=function(t){if(T(t))return t;throw new X(Y(t)+" is not a function")},Q=function(t,e){var r=t[e];return P(r)?void 0:J(r)},Z=TypeError,tt=Object.defineProperty,et=function(t,e){try{tt(i,t,{value:e,configurable:!0,writable:!0})}catch(r){i[t]=e}return e},rt=e(function(t){var e="__core-js_shared__",r=t.exports=i[e]||et(e,{});(r.versions||(r.versions=[])).push({version:"3.38.1",mode:"global",copyright:"© 2014-2024 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.38.1/LICENSE",source:"https://github.com/zloirock/core-js"})}),nt=function(t,e){return rt[t]||(rt[t]=e||{})},ot=Object,it=function(t){return ot(j(t))},at=b({}.hasOwnProperty),ut=Object.hasOwn||function(t,e){return at(it(t),e)},st=0,ct=Math.random(),ft=b(1..toString),lt=function(t){return"Symbol("+(void 0===t?"":t)+")_"+ft(++st+ct,36)},ht=i.Symbol,pt=nt("wks"),vt=$?ht.for||ht:ht&&ht.withoutSetter||lt,dt=function(t){return ut(pt,t)||(pt[t]=H&&ut(ht,t)?ht[t]:vt("Symbol."+t)),pt[t]},gt=TypeError,yt=dt("toPrimitive"),mt=function(t,e){if(!M(t)||G(t))return t;var r,n=Q(t,yt);if(n){if(void 0===e&&(e="default"),r=f(n,t,e),!M(r)||G(r))return r;throw new gt("Can't convert object to primitive value")}return void 0===e&&(e="number"),function(t,e){var r,n;if("string"===e&&T(r=t.toString)&&!M(n=f(r,t)))return n;if(T(r=t.valueOf)&&!M(n=f(r,t)))return n;if("string"!==e&&T(r=t.toString)&&!M(n=f(r,t)))return n;throw new Z("Can't convert object to primitive value")}(t,e)},bt=function(t){var e=mt(t,"string");return G(e)?e:e+""},wt=i.document,St=M(wt)&&M(wt.createElement),Et=function(t){return St?wt.createElement(t):{}},Ot=!u&&!a(function(){return 7!==Object.defineProperty(Et("div"),"a",{get:function(){return 7}}).a}),xt=Object.getOwnPropertyDescriptor,Rt={f:u?xt:function(t,e){if(t=k(t),e=bt(e),Ot)try{return xt(t,e)}catch(t){}if(ut(t,e))return d(!f(v.f,t,e),t[e])}},Pt=u&&a(function(){return 42!==Object.defineProperty(function(){},"prototype",{value:42,writable:!1}).prototype}),At=String,jt=TypeError,kt=function(t){if(M(t))return t;throw new jt(At(t)+" is not an object")},It=TypeError,Tt=Object.defineProperty,Mt=Object.getOwnPropertyDescriptor,Lt="enumerable",Ut="configurable",Nt="writable",Ct={f:u?Pt?function(t,e,r){if(kt(t),e=bt(e),kt(r),"function"==typeof t&&"prototype"===e&&"value"in r&&Nt in r&&!r[Nt]){var n=Mt(t,e);n&&n[Nt]&&(t[e]=r.value,r={configurable:Ut in r?r[Ut]:n[Ut],enumerable:Lt in r?r[Lt]:n[Lt],writable:!1})}return Tt(t,e,r)}:Tt:function(t,e,r){if(kt(t),e=bt(e),kt(r),Ot)try{return Tt(t,e,r)}catch(t){}if("get"in r||"set"in r)throw new It("Accessors not supported");return"value"in r&&(t[e]=r.value),t}},_t=u?function(t,e,r){return Ct.f(t,e,d(1,r))}:function(t,e,r){return t[e]=r,t},Ft=Function.prototype,Bt=u&&Object.getOwnPropertyDescriptor,Dt=ut(Ft,"name"),zt={EXISTS:Dt,PROPER:Dt&&"something"===function(){}.name,CONFIGURABLE:Dt&&(!u||u&&Bt(Ft,"name").configurable)},Wt=b(Function.toString);T(rt.inspectSource)||(rt.inspectSource=function(t){return Wt(t)});var qt,Ht,$t,Kt=rt.inspectSource,Gt=i.WeakMap,Vt=T(Gt)&&/native code/.test(String(Gt)),Yt=nt("keys"),Xt=function(t){return Yt[t]||(Yt[t]=lt(t))},Jt={},Qt="Object already initialized",Zt=i.TypeError;if(Vt||rt.state){var te=rt.state||(rt.state=new(0,i.WeakMap));te.get=te.get,te.has=te.has,te.set=te.set,qt=function(t,e){if(te.has(t))throw new Zt(Qt);return e.facade=t,te.set(t,e),e},Ht=function(t){return te.get(t)||{}},$t=function(t){return te.has(t)}}else{var ee=Xt("state");Jt[ee]=!0,qt=function(t,e){if(ut(t,ee))throw new Zt(Qt);return e.facade=t,_t(t,ee,e),e},Ht=function(t){return ut(t,ee)?t[ee]:{}},$t=function(t){return ut(t,ee)}}var re,ne={set:qt,get:Ht,has:$t,enforce:function(t){return $t(t)?Ht(t):qt(t,{})},getterFor:function(t){return function(e){var r;if(!M(e)||(r=Ht(e)).type!==t)throw new Zt("Incompatible receiver, "+t+" required");return r}}},oe=e(function(t){var e=zt.CONFIGURABLE,r=ne.enforce,n=ne.get,o=String,i=Object.defineProperty,s=b("".slice),c=b("".replace),f=b([].join),l=u&&!a(function(){return 8!==i(function(){},"length",{value:8}).length}),h=String(String).split("String"),p=t.exports=function(t,n,a){"Symbol("===s(o(n),0,7)&&(n="["+c(o(n),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),a&&a.getter&&(n="get "+n),a&&a.setter&&(n="set "+n),(!ut(t,"name")||e&&t.name!==n)&&(u?i(t,"name",{value:n,configurable:!0}):t.name=n),l&&a&&ut(a,"arity")&&t.length!==a.arity&&i(t,"length",{value:a.arity});try{a&&ut(a,"constructor")&&a.constructor?u&&i(t,"prototype",{writable:!1}):t.prototype&&(t.prototype=void 0)}catch(t){}var p=r(t);return ut(p,"source")||(p.source=f(h,"string"==typeof n?n:"")),t};Function.prototype.toString=p(function(){return T(this)&&n(this).source||Kt(this)},"toString")}),ie=function(t,e,r,n){n||(n={});var o=n.enumerable,i=void 0!==n.name?n.name:e;if(T(r)&&oe(r,i,n),n.global)o?t[e]=r:et(e,r);else{try{n.unsafe?t[e]&&(o=!0):delete t[e]}catch(t){}o?t[e]=r:Ct.f(t,e,{value:r,enumerable:!1,configurable:!n.nonConfigurable,writable:!n.nonWritable})}return t},ae=Math.ceil,ue=Math.floor,se=Math.trunc||function(t){var e=+t;return(e>0?ue:ae)(e)},ce=function(t){var e=+t;return e!=e||0===e?0:se(e)},fe=Math.max,le=Math.min,he=function(t,e){var r=ce(t);return r<0?fe(r+e,0):le(r,e)},pe=Math.min,ve=function(t){var e=ce(t);return e>0?pe(e,9007199254740991):0},de=function(t){return ve(t.length)},ge=function(t){return function(e,r,n){var o=k(e),i=de(o);if(0===i)return!t&&-1;var a,u=he(n,i);if(t&&r!=r){for(;i>u;)if((a=o[u++])!=a)return!0}else for(;i>u;u++)if((t||u in o)&&o[u]===r)return t||u||0;return!t&&-1}},ye={includes:ge(!0),indexOf:ge(!1)},me=ye.indexOf,be=b([].push),we=function(t,e){var r,n=k(t),o=0,i=[];for(r in n)!ut(Jt,r)&&ut(n,r)&&be(i,r);for(;e.length>o;)ut(n,r=e[o++])&&(~me(i,r)||be(i,r));return i},Se=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],Ee=Se.concat("length","prototype"),Oe={f:Object.getOwnPropertyNames||function(t){return we(t,Ee)}},xe={f:Object.getOwnPropertySymbols},Re=b([].concat),Pe=L("Reflect","ownKeys")||function(t){var e=Oe.f(kt(t)),r=xe.f;return r?Re(e,r(t)):e},Ae=function(t,e,r){for(var n=Pe(e),o=Ct.f,i=Rt.f,a=0;aa;)Ct.f(t,r=o[a++],n[r]);return t},Be={f:Fe},De=L("document","documentElement"),ze="prototype",We="script",qe=Xt("IE_PROTO"),He=function(){},$e=function(t){return"<"+We+">"+t+""},Ke=function(t){t.write($e("")),t.close();var e=t.parentWindow.Object;return t=null,e},Ge=function(){try{re=new ActiveXObject("htmlfile")}catch(t){}var t,e,r;Ge="undefined"!=typeof document?document.domain&&re?Ke(re):(e=Et("iframe"),r="java"+We+":",e.style.display="none",De.appendChild(e),e.src=String(r),(t=e.contentWindow.document).open(),t.write($e("document.F=Object")),t.close(),t.F):Ke(re);for(var n=Se.length;n--;)delete Ge[ze][Se[n]];return Ge()};Jt[qe]=!0;var Ve=Object.create||function(t,e){var r;return null!==t?(He[ze]=kt(t),r=new He,He[ze]=null,r[qe]=t):r=Ge(),void 0===e?r:Be.f(r,e)},Ye=Ct.f,Xe=dt("unscopables"),Je=Array.prototype;void 0===Je[Xe]&&Ye(Je,Xe,{configurable:!0,value:Ve(null)});var Qe=function(t){Je[Xe][t]=!0};Ce({target:"Array",proto:!0},{at:function(t){var e=it(this),r=de(e),n=ce(t),o=n>=0?n:r+n;return o<0||o>=r?void 0:e[o]}}),Qe("at");var Ze=function(t,e){return b(i[t].prototype[e])},tr=(Ze("Array","at"),TypeError),er=function(t,e){if(!delete t[e])throw new tr("Cannot delete property "+Y(e)+" of "+Y(t))},rr=Math.min,nr=[].copyWithin||function(t,e){var r=it(this),n=de(r),o=he(t,n),i=he(e,n),a=arguments.length>2?arguments[2]:void 0,u=rr((void 0===a?n:he(a,n))-i,n-o),s=1;for(i0;)i in r?r[o]=r[i]:er(r,o),o+=s,i+=s;return r};Ce({target:"Array",proto:!0},{copyWithin:nr}),Qe("copyWithin"),Ze("Array","copyWithin"),Ce({target:"Array",proto:!0},{fill:function(t){for(var e=it(this),r=de(e),n=arguments.length,o=he(n>1?arguments[1]:void 0,r),i=n>2?arguments[2]:void 0,a=void 0===i?r:he(i,r);a>o;)e[o++]=t;return e}}),Qe("fill"),Ze("Array","fill");var or=function(t){if("Function"===E(t))return b(t)},ir=or(or.bind),ar=function(t,e){return J(t),void 0===e?t:s?ir(t,e):function(){return t.apply(e,arguments)}},ur=Array.isArray||function(t){return"Array"===E(t)},sr={};sr[dt("toStringTag")]="z";var cr="[object z]"===String(sr),fr=dt("toStringTag"),lr=Object,hr="Arguments"===E(function(){return arguments}()),pr=cr?E:function(t){var e,r,n;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(r=function(t,e){try{return t[e]}catch(t){}}(e=lr(t),fr))?r:hr?E(e):"Object"===(n=E(e))&&T(e.callee)?"Arguments":n},vr=function(){},dr=L("Reflect","construct"),gr=/^\s*(?:class|function)\b/,yr=b(gr.exec),mr=!gr.test(vr),br=function(t){if(!T(t))return!1;try{return dr(vr,[],t),!0}catch(t){return!1}},wr=function(t){if(!T(t))return!1;switch(pr(t)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return mr||!!yr(gr,Kt(t))}catch(t){return!0}};wr.sham=!0;var Sr=!dr||a(function(){var t;return br(br.call)||!br(Object)||!br(function(){t=!0})||t})?wr:br,Er=dt("species"),Or=Array,xr=function(t,e){return new(function(t){var e;return ur(t)&&(Sr(e=t.constructor)&&(e===Or||ur(e.prototype))||M(e)&&null===(e=e[Er]))&&(e=void 0),void 0===e?Or:e}(t))(0===e?0:e)},Rr=b([].push),Pr=function(t){var e=1===t,r=2===t,n=3===t,o=4===t,i=6===t,a=7===t,u=5===t||i;return function(s,c,f,l){for(var h,p,v=it(s),d=R(v),g=de(d),y=ar(c,f),m=0,b=l||xr,w=e?b(s,g):r||a?b(s,0):void 0;g>m;m++)if((u||m in d)&&(p=y(h=d[m],m,v),t))if(e)w[m]=p;else if(p)switch(t){case 3:return!0;case 5:return h;case 6:return m;case 2:Rr(w,h)}else switch(t){case 4:return!1;case 7:Rr(w,h)}return i?-1:n||o?o:w}},Ar={forEach:Pr(0),map:Pr(1),filter:Pr(2),some:Pr(3),every:Pr(4),find:Pr(5),findIndex:Pr(6),filterReject:Pr(7)},jr=Ar.find,kr="find",Ir=!0;kr in[]&&Array(1)[kr](function(){Ir=!1}),Ce({target:"Array",proto:!0,forced:Ir},{find:function(t){return jr(this,t,arguments.length>1?arguments[1]:void 0)}}),Qe(kr),Ze("Array","find");var Tr=Ar.findIndex,Mr="findIndex",Lr=!0;Mr in[]&&Array(1)[Mr](function(){Lr=!1}),Ce({target:"Array",proto:!0,forced:Lr},{findIndex:function(t){return Tr(this,t,arguments.length>1?arguments[1]:void 0)}}),Qe(Mr),Ze("Array","findIndex");var Ur=TypeError,Nr=function(t){if(t>9007199254740991)throw Ur("Maximum allowed index exceeded");return t},Cr=function(t,e,r,n,o,i,a,u){for(var s,c,f=o,l=0,h=!!a&&ar(a,u);l0&&ur(s)?(c=de(s),f=Cr(t,e,s,c,f,i-1)-1):(Nr(f+1),t[f]=s),f++),l++;return f},_r=Cr;Ce({target:"Array",proto:!0},{flatMap:function(t){var e,r=it(this),n=de(r);return J(t),(e=xr(r,0)).length=_r(e,r,r,n,0,1,t,arguments.length>1?arguments[1]:void 0),e}}),Qe("flatMap"),Ze("Array","flatMap"),Ce({target:"Array",proto:!0},{flat:function(){var t=arguments.length?arguments[0]:void 0,e=it(this),r=de(e),n=xr(e,0);return n.length=_r(n,e,e,r,0,void 0===t?1:ce(t)),n}}),Qe("flat"),Ze("Array","flat");var Fr,Br,Dr,zr=String,Wr=function(t){if("Symbol"===pr(t))throw new TypeError("Cannot convert a Symbol value to a string");return zr(t)},qr=b("".charAt),Hr=b("".charCodeAt),$r=b("".slice),Kr=function(t){return function(e,r){var n,o,i=Wr(j(e)),a=ce(r),u=i.length;return a<0||a>=u?t?"":void 0:(n=Hr(i,a))<55296||n>56319||a+1===u||(o=Hr(i,a+1))<56320||o>57343?t?qr(i,a):n:t?$r(i,a,a+2):o-56320+(n-55296<<10)+65536}},Gr={codeAt:Kr(!1),charAt:Kr(!0)},Vr=!a(function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}),Yr=Xt("IE_PROTO"),Xr=Object,Jr=Xr.prototype,Qr=Vr?Xr.getPrototypeOf:function(t){var e=it(t);if(ut(e,Yr))return e[Yr];var r=e.constructor;return T(r)&&e instanceof r?r.prototype:e instanceof Xr?Jr:null},Zr=dt("iterator"),tn=!1;[].keys&&("next"in(Dr=[].keys())?(Br=Qr(Qr(Dr)))!==Object.prototype&&(Fr=Br):tn=!0);var en=!M(Fr)||a(function(){var t={};return Fr[Zr].call(t)!==t});en&&(Fr={}),T(Fr[Zr])||ie(Fr,Zr,function(){return this});var rn={IteratorPrototype:Fr,BUGGY_SAFARI_ITERATORS:tn},nn=Ct.f,on=dt("toStringTag"),an=function(t,e,r){t&&!r&&(t=t.prototype),t&&!ut(t,on)&&nn(t,on,{configurable:!0,value:e})},un={},sn=rn.IteratorPrototype,cn=function(){return this},fn=function(t,e,r,n){var o=e+" Iterator";return t.prototype=Ve(sn,{next:d(+!n,r)}),an(t,o,!1),un[o]=cn,t},ln=function(t,e,r){try{return b(J(Object.getOwnPropertyDescriptor(t,e)[r]))}catch(t){}},hn=String,pn=TypeError,vn=function(t){if(function(t){return M(t)||null===t}(t))return t;throw new pn("Can't set "+hn(t)+" as a prototype")},dn=Object.setPrototypeOf||("__proto__"in{}?function(){var t,e=!1,r={};try{(t=ln(Object.prototype,"__proto__","set"))(r,[]),e=r instanceof Array}catch(t){}return function(r,n){return j(r),vn(n),M(r)?(e?t(r,n):r.__proto__=n,r):r}}():void 0),gn=zt.PROPER,yn=zt.CONFIGURABLE,mn=rn.IteratorPrototype,bn=rn.BUGGY_SAFARI_ITERATORS,wn=dt("iterator"),Sn="keys",En="values",On="entries",xn=function(){return this},Rn=function(t,e,r,n,o,i,a){fn(r,e,n);var u,s,c,l=function(t){if(t===o&&g)return g;if(!bn&&t&&t in v)return v[t];switch(t){case Sn:case En:case On:return function(){return new r(this,t)}}return function(){return new r(this)}},h=e+" Iterator",p=!1,v=t.prototype,d=v[wn]||v["@@iterator"]||o&&v[o],g=!bn&&d||l(o),y="Array"===e&&v.entries||d;if(y&&(u=Qr(y.call(new t)))!==Object.prototype&&u.next&&(Qr(u)!==mn&&(dn?dn(u,mn):T(u[wn])||ie(u,wn,xn)),an(u,h,!0)),gn&&o===En&&d&&d.name!==En&&(yn?_t(v,"name",En):(p=!0,g=function(){return f(d,this)})),o)if(s={values:l(En),keys:i?g:l(Sn),entries:l(On)},a)for(c in s)(bn||p||!(c in v))&&ie(v,c,s[c]);else Ce({target:e,proto:!0,forced:bn||p},s);return v[wn]!==g&&ie(v,wn,g,{name:o}),un[e]=g,s},Pn=function(t,e){return{value:t,done:e}},An=Gr.charAt,jn="String Iterator",kn=ne.set,In=ne.getterFor(jn);Rn(String,"String",function(t){kn(this,{type:jn,string:Wr(t),index:0})},function(){var t,e=In(this),r=e.string,n=e.index;return n>=r.length?Pn(void 0,!0):(t=An(r,n),e.index+=t.length,Pn(t,!1))});var Tn=function(t,e,r){var n,o;kt(t);try{if(!(n=Q(t,"return"))){if("throw"===e)throw r;return r}n=f(n,t)}catch(t){o=!0,n=t}if("throw"===e)throw r;if(o)throw n;return kt(n),r},Mn=function(t,e,r,n){try{return n?e(kt(r)[0],r[1]):e(r)}catch(e){Tn(t,"throw",e)}},Ln=dt("iterator"),Un=Array.prototype,Nn=function(t){return void 0!==t&&(un.Array===t||Un[Ln]===t)},Cn=function(t,e,r){u?Ct.f(t,e,d(0,r)):t[e]=r},_n=dt("iterator"),Fn=function(t){if(!P(t))return Q(t,_n)||Q(t,"@@iterator")||un[pr(t)]},Bn=TypeError,Dn=function(t,e){var r=arguments.length<2?Fn(t):e;if(J(r))return kt(f(r,t));throw new Bn(Y(t)+" is not iterable")},zn=Array,Wn=function(t){var e=it(t),r=Sr(this),n=arguments.length,o=n>1?arguments[1]:void 0,i=void 0!==o;i&&(o=ar(o,n>2?arguments[2]:void 0));var a,u,s,c,l,h,p=Fn(e),v=0;if(!p||this===zn&&Nn(p))for(a=de(e),u=r?new this(a):zn(a);a>v;v++)h=i?o(e[v],v):e[v],Cn(u,v,h);else for(u=r?new this:[],l=(c=Dn(e,p)).next;!(s=f(l,c)).done;v++)h=i?Mn(c,o,[s.value,v],!0):s.value,Cn(u,v,h);return u.length=v,u},qn=dt("iterator"),Hn=!1;try{var $n=0,Kn={next:function(){return{done:!!$n++}},return:function(){Hn=!0}};Kn[qn]=function(){return this},Array.from(Kn,function(){throw 2})}catch(t){}var Gn=function(t,e){try{if(!e&&!Hn)return!1}catch(t){return!1}var r=!1;try{var n={};n[qn]=function(){return{next:function(){return{done:r=!0}}}},t(n)}catch(t){}return r},Vn=!Gn(function(t){Array.from(t)});Ce({target:"Array",stat:!0,forced:Vn},{from:Wn});var Yn=i,Xn=ye.includes,Jn=a(function(){return!Array(1).includes()});Ce({target:"Array",proto:!0,forced:Jn},{includes:function(t){return Xn(this,t,arguments.length>1?arguments[1]:void 0)}}),Qe("includes"),Ze("Array","includes");var Qn=Ct.f,Zn="Array Iterator",to=ne.set,eo=ne.getterFor(Zn),ro=Rn(Array,"Array",function(t,e){to(this,{type:Zn,target:k(t),index:0,kind:e})},function(){var t=eo(this),e=t.target,r=t.index++;if(!e||r>=e.length)return t.target=null,Pn(void 0,!0);switch(t.kind){case"keys":return Pn(r,!1);case"values":return Pn(e[r],!1)}return Pn([r,e[r]],!1)},"values"),no=un.Arguments=un.Array;if(Qe("keys"),Qe("values"),Qe("entries"),u&&"values"!==no.name)try{Qn(no,"name",{value:"values"})}catch(t){}cr||ie(Object.prototype,"toString",cr?{}.toString:function(){return"[object "+pr(this)+"]"},{unsafe:!0}),Ze("Array","values");var oo=Array,io=a(function(){function t(){}return!(oo.of.call(t)instanceof t)});Ce({target:"Array",stat:!0,forced:io},{of:function(){for(var t=0,e=arguments.length,r=new(Sr(this)?this:oo)(e);e>t;)Cn(r,t,arguments[t++]);return r.length=e,r}});var ao=dt("hasInstance"),uo=Function.prototype;ao in uo||Ct.f(uo,ao,{value:oe(function(t){if(!T(this)||!M(t))return!1;var e=this.prototype;return M(e)?U(e,t):t instanceof this},ao)}),dt("hasInstance");var so=function(t,e,r){return r.get&&oe(r.get,e,{getter:!0}),r.set&&oe(r.set,e,{setter:!0}),Ct.f(t,e,r)},co=zt.EXISTS,fo=Function.prototype,lo=b(fo.toString),ho=/function\b(?:\s|\/\*[\S\s]*?\*\/|\/\/[^\n\r]*[\n\r]+)*([^\s(/]*)/,po=b(ho.exec);u&&!co&&so(fo,"name",{configurable:!0,get:function(){try{return po(ho,lo(this))[1]}catch(t){return""}}});var vo=b([].slice),go=Oe.f,yo="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],mo={f:function(t){return yo&&"Window"===E(t)?function(t){try{return go(t)}catch(t){return vo(yo)}}(t):go(k(t))}},bo=a(function(){if("function"==typeof ArrayBuffer){var t=new ArrayBuffer(8);Object.isExtensible(t)&&Object.defineProperty(t,"a",{value:8})}}),wo=Object.isExtensible,So=a(function(){wo(1)})||bo?function(t){return!!M(t)&&(!bo||"ArrayBuffer"!==E(t))&&(!wo||wo(t))}:wo,Eo=!a(function(){return Object.isExtensible(Object.preventExtensions({}))}),Oo=e(function(t){var e=Ct.f,r=!1,n=lt("meta"),o=0,i=function(t){e(t,n,{value:{objectID:"O"+o++,weakData:{}}})},a=t.exports={enable:function(){a.enable=function(){},r=!0;var t=Oe.f,e=b([].splice),o={};o[n]=1,t(o).length&&(Oe.f=function(r){for(var o=t(r),i=0,a=o.length;ii;i++)if((u=y(t[i]))&&U(Po,u))return u;return new Ro(!1)}n=Dn(t,o)}for(s=h?t.next:n.next;!(c=f(s,n)).done;){try{u=y(c.value)}catch(t){Tn(n,"throw",t)}if("object"==typeof u&&u&&U(Po,u))return u}return new Ro(!1)},jo=TypeError,ko=function(t,e){if(U(e,t))return t;throw new jo("Incorrect invocation")},Io=function(t,e,r){var n,o;return dn&&T(n=e.constructor)&&n!==r&&M(o=n.prototype)&&o!==r.prototype&&dn(t,o),t},To=function(t,e,r){var n=-1!==t.indexOf("Map"),o=-1!==t.indexOf("Weak"),u=n?"set":"add",s=i[t],c=s&&s.prototype,f=s,l={},h=function(t){var e=b(c[t]);ie(c,t,"add"===t?function(t){return e(this,0===t?0:t),this}:"delete"===t?function(t){return!(o&&!M(t))&&e(this,0===t?0:t)}:"get"===t?function(t){return o&&!M(t)?void 0:e(this,0===t?0:t)}:"has"===t?function(t){return!(o&&!M(t))&&e(this,0===t?0:t)}:function(t,r){return e(this,0===t?0:t,r),this})};if(Ue(t,!T(s)||!(o||c.forEach&&!a(function(){(new s).entries().next()}))))f=r.getConstructor(e,t,n,u),Oo.enable();else if(Ue(t,!0)){var p=new f,v=p[u](o?{}:-0,1)!==p,d=a(function(){p.has(1)}),g=Gn(function(t){new s(t)}),y=!o&&a(function(){for(var t=new s,e=5;e--;)t[u](e,e);return!t.has(-0)});g||((f=e(function(t,e){ko(t,c);var r=Io(new s,t,f);return P(e)||Ao(e,r[u],{that:r,AS_ENTRIES:n}),r})).prototype=c,c.constructor=f),(d||y)&&(h("delete"),h("has"),n&&h("get")),(y||v)&&h(u),o&&c.clear&&delete c.clear}return l[t]=f,Ce({global:!0,constructor:!0,forced:f!==s},l),an(f,t),o||r.setStrong(f,t,n),f},Mo=function(t,e,r){for(var n in e)ie(t,n,e[n],r);return t},Lo=dt("species"),Uo=function(t){var e=L(t);u&&e&&!e[Lo]&&so(e,Lo,{configurable:!0,get:function(){return this}})},No=Oo.fastKey,Co=ne.set,_o=ne.getterFor,Fo={getConstructor:function(t,e,r,n){var o=t(function(t,o){ko(t,i),Co(t,{type:e,index:Ve(null),first:null,last:null,size:0}),u||(t.size=0),P(o)||Ao(o,t[n],{that:t,AS_ENTRIES:r})}),i=o.prototype,a=_o(e),s=function(t,e,r){var n,o,i=a(t),s=c(t,e);return s?s.value=r:(i.last=s={index:o=No(e,!0),key:e,value:r,previous:n=i.last,next:null,removed:!1},i.first||(i.first=s),n&&(n.next=s),u?i.size++:t.size++,"F"!==o&&(i.index[o]=s)),t},c=function(t,e){var r,n=a(t),o=No(e);if("F"!==o)return n.index[o];for(r=n.first;r;r=r.next)if(r.key===e)return r};return Mo(i,{clear:function(){for(var t=a(this),e=t.first;e;)e.removed=!0,e.previous&&(e.previous=e.previous.next=null),e=e.next;t.first=t.last=null,t.index=Ve(null),u?t.size=0:this.size=0},delete:function(t){var e=this,r=a(e),n=c(e,t);if(n){var o=n.next,i=n.previous;delete r.index[n.index],n.removed=!0,i&&(i.next=o),o&&(o.previous=i),r.first===n&&(r.first=o),r.last===n&&(r.last=i),u?r.size--:e.size--}return!!n},forEach:function(t){for(var e,r=a(this),n=ar(t,arguments.length>1?arguments[1]:void 0);e=e?e.next:r.first;)for(n(e.value,e.key,this);e&&e.removed;)e=e.previous},has:function(t){return!!c(this,t)}}),Mo(i,r?{get:function(t){var e=c(this,t);return e&&e.value},set:function(t,e){return s(this,0===t?0:t,e)}}:{add:function(t){return s(this,t=0===t?0:t,t)}}),u&&so(i,"size",{configurable:!0,get:function(){return a(this).size}}),o},setStrong:function(t,e,r){var n=e+" Iterator",o=_o(e),i=_o(n);Rn(t,e,function(t,e){Co(this,{type:n,target:t,state:o(t),kind:e,last:null})},function(){for(var t=i(this),e=t.kind,r=t.last;r&&r.removed;)r=r.previous;return t.target&&(t.last=r=r?r.next:t.state.first)?Pn("keys"===e?r.key:"values"===e?r.value:[r.key,r.value],!1):(t.target=null,Pn(void 0,!0))},r?"entries":"values",!r,!0),Uo(e)}};To("Map",function(t){return function(){return t(this,arguments.length?arguments[0]:void 0)}},Fo);var Bo=Map.prototype,Do={Map:Map,set:b(Bo.set),get:b(Bo.get),has:b(Bo.has),remove:b(Bo.delete),proto:Bo},zo=Do.Map,Wo=Do.has,qo=Do.get,Ho=Do.set,$o=b([].push),Ko=a(function(){return 1!==zo.groupBy("ab",function(t){return t}).get("a").length});Ce({target:"Map",stat:!0,forced:Ko},{groupBy:function(t,e){j(t),J(e);var r=new zo,n=0;return Ao(t,function(t){var o=e(t,n++);Wo(r,o)?$o(qo(r,o),t):Ho(r,o,[t])}),r}});var Go={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0},Vo=Et("span").classList,Yo=Vo&&Vo.constructor&&Vo.constructor.prototype,Xo=Yo===Object.prototype?void 0:Yo,Jo=dt("iterator"),Qo=ro.values,Zo=function(t,e){if(t){if(t[Jo]!==Qo)try{_t(t,Jo,Qo)}catch(e){t[Jo]=Qo}if(an(t,e,!0),Go[e])for(var r in ro)if(t[r]!==ro[r])try{_t(t,r,ro[r])}catch(e){t[r]=ro[r]}}};for(var ti in Go)Zo(i[ti]&&i[ti].prototype,ti);Zo(Xo,"DOMTokenList");var ei=function(t,e,r){return function(n){var o=it(n),i=arguments.length,a=i>1?arguments[1]:void 0,u=void 0!==a,s=u?ar(a,i>2?arguments[2]:void 0):void 0,c=new t,f=0;return Ao(o,function(t){var n=u?s(t,f++):t;r?e(c,kt(n)[0],n[1]):e(c,n)}),c}};Ce({target:"Map",stat:!0,forced:!0},{from:ei(Do.Map,Do.set,!0)});var ri=function(t,e,r){return function(){for(var n=new t,o=arguments.length,i=0;i1?arguments[1]:void 0);return!1!==di(e,function(t,n){if(!r(t,n,e))return!1},!0)}});var gi=Do.Map,yi=Do.set;Ce({target:"Map",proto:!0,real:!0,forced:!0},{filter:function(t){var e=oi(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=new gi;return di(e,function(t,o){r(t,o,e)&&yi(n,o,t)}),n}}),Ce({target:"Map",proto:!0,real:!0,forced:!0},{find:function(t){var e=oi(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=di(e,function(t,n){if(r(t,n,e))return{value:t}},!0);return n&&n.value}}),Ce({target:"Map",proto:!0,real:!0,forced:!0},{findKey:function(t){var e=oi(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=di(e,function(t,n){if(r(t,n,e))return{key:n}},!0);return n&&n.key}}),Ce({target:"Map",proto:!0,real:!0,forced:!0},{includes:function(t){return!0===di(oi(this),function(e){if((r=e)===(n=t)||r!=r&&n!=n)return!0;var r,n},!0)}});var mi=Do.Map;Ce({target:"Map",stat:!0,forced:!0},{keyBy:function(t,e){var r=new(T(this)?this:mi);J(e);var n=J(r.set);return Ao(t,function(t){f(n,r,e(t),t)}),r}}),Ce({target:"Map",proto:!0,real:!0,forced:!0},{keyOf:function(t){var e=di(oi(this),function(e,r){if(e===t)return{key:r}},!0);return e&&e.key}});var bi=Do.Map,wi=Do.set;Ce({target:"Map",proto:!0,real:!0,forced:!0},{mapKeys:function(t){var e=oi(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=new bi;return di(e,function(t,o){wi(n,r(t,o,e),t)}),n}});var Si=Do.Map,Ei=Do.set;Ce({target:"Map",proto:!0,real:!0,forced:!0},{mapValues:function(t){var e=oi(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=new Si;return di(e,function(t,o){Ei(n,o,r(t,o,e))}),n}});var Oi=Do.set;Ce({target:"Map",proto:!0,real:!0,arity:1,forced:!0},{merge:function(t){for(var e=oi(this),r=arguments.length,n=0;n1?arguments[1]:void 0);return!0===di(e,function(t,n){if(r(t,n,e))return!0},!0)}});var Ri=TypeError,Pi=Do.get,Ai=Do.has,ji=Do.set;Ce({target:"Map",proto:!0,real:!0,forced:!0},{update:function(t,e){var r=oi(this),n=arguments.length;J(e);var o=Ai(r,t);if(!o&&n<3)throw new Ri("Updating absent value");var i=o?Pi(r,t):J(n>2?arguments[2]:void 0)(t,r);return ji(r,t,e(i,t,r)),r}});var ki=TypeError,Ii=function(t,e){var r,n=kt(this),o=J(n.get),i=J(n.has),a=J(n.set),u=arguments.length>2?arguments[2]:void 0;if(!T(e)&&!T(u))throw new ki("At least one callback required");return f(i,n,t)?(r=f(o,n,t),T(e)&&(r=e(r),f(a,n,t,r))):T(u)&&(r=u(),f(a,n,t,r)),r};Ce({target:"Map",proto:!0,real:!0,forced:!0},{upsert:Ii}),Ce({target:"Map",proto:!0,real:!0,name:"upsert",forced:!0},{updateOrInsert:Ii});var Ti=b(1..valueOf),Mi="\t\n\v\f\r                 \u2028\u2029\ufeff",Li=b("".replace),Ui=RegExp("^["+Mi+"]+"),Ni=RegExp("(^|[^"+Mi+"])["+Mi+"]+$"),Ci=function(t){return function(e){var r=Wr(j(e));return 1&t&&(r=Li(r,Ui,"")),2&t&&(r=Li(r,Ni,"$1")),r}},_i={start:Ci(1),end:Ci(2),trim:Ci(3)},Fi=Oe.f,Bi=Rt.f,Di=Ct.f,zi=_i.trim,Wi="Number",qi=i[Wi],Hi=qi.prototype,$i=i.TypeError,Ki=b("".slice),Gi=b("".charCodeAt),Vi=Ue(Wi,!qi(" 0o1")||!qi("0b1")||qi("+0x1")),Yi=function(t){var e,r=arguments.length<1?0:qi(function(t){var e=mt(t,"number");return"bigint"==typeof e?e:function(t){var e,r,n,o,i,a,u,s,c=mt(t,"number");if(G(c))throw new $i("Cannot convert a Symbol value to a number");if("string"==typeof c&&c.length>2)if(c=zi(c),43===(e=Gi(c,0))||45===e){if(88===(r=Gi(c,2))||120===r)return NaN}else if(48===e){switch(Gi(c,1)){case 66:case 98:n=2,o=49;break;case 79:case 111:n=8,o=55;break;default:return+c}for(a=(i=Ki(c,2)).length,u=0;uo)return NaN;return parseInt(i,n)}return+c}(e)}(t));return U(Hi,e=this)&&a(function(){Ti(e)})?Io(Object(r),this,Yi):r};Yi.prototype=Hi,Vi&&(Hi.constructor=Yi),Ce({global:!0,constructor:!0,wrap:!0,forced:Vi},{Number:Yi}),Vi&&function(t,e){for(var r,n=u?Fi(e):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,isFinite,isInteger,isNaN,isSafeInteger,parseFloat,parseInt,fromString,range".split(","),o=0;n.length>o;o++)ut(e,r=n[o])&&!ut(t,r)&&Di(t,r,Bi(e,r))}(Yn[Wi],qi),Ce({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{EPSILON:Math.pow(2,-52)});var Xi=i.isFinite;Ce({target:"Number",stat:!0},{isFinite:Number.isFinite||function(t){return"number"==typeof t&&Xi(t)}});var Ji=Math.floor,Qi=Number.isInteger||function(t){return!M(t)&&isFinite(t)&&Ji(t)===t};Ce({target:"Number",stat:!0},{isInteger:Qi}),Ce({target:"Number",stat:!0},{isNaN:function(t){return t!=t}});var Zi=Math.abs;Ce({target:"Number",stat:!0},{isSafeInteger:function(t){return Qi(t)&&Zi(t)<=9007199254740991}}),Ce({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{MAX_SAFE_INTEGER:9007199254740991}),Ce({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{MIN_SAFE_INTEGER:-9007199254740991});var ta=_i.trim,ea=b("".charAt),ra=i.parseFloat,na=i.Symbol,oa=na&&na.iterator,ia=1/ra(Mi+"-0")!=-Infinity||oa&&!a(function(){ra(Object(oa))})?function(t){var e=ta(Wr(t)),r=ra(e);return 0===r&&"-"===ea(e,0)?-0:r}:ra;Ce({target:"Number",stat:!0,forced:Number.parseFloat!==ia},{parseFloat:ia});var aa=_i.trim,ua=i.parseInt,sa=i.Symbol,ca=sa&&sa.iterator,fa=/^[+-]?0x/i,la=b(fa.exec),ha=8!==ua(Mi+"08")||22!==ua(Mi+"0x16")||ca&&!a(function(){ua(Object(ca))})?function(t,e){var r=aa(Wr(t));return ua(r,e>>>0||(la(fa,r)?16:10))}:ua;Ce({target:"Number",stat:!0,forced:Number.parseInt!==ha},{parseInt:ha});var pa=b(v.f),va=b([].push),da=u&&a(function(){var t=Object.create(null);return t[2]=2,!pa(t,2)}),ga=function(t){return function(e){for(var r,n=k(e),o=_e(n),i=da&&null===Qr(n),a=o.length,s=0,c=[];a>s;)r=o[s++],u&&!(i?r in n:pa(n,r))||va(c,t?[r,n[r]]:n[r]);return c}},ya={entries:ga(!0),values:ga(!1)},ma=ya.entries;Ce({target:"Object",stat:!0},{entries:function(t){return ma(t)}}),Ce({target:"Object",stat:!0,sham:!u},{getOwnPropertyDescriptors:function(t){for(var e,r,n=k(t),o=Rt.f,i=Pe(n),a={},u=0;i.length>u;)void 0!==(r=o(n,e=i[u++]))&&Cn(a,e,r);return a}});var ba=a(function(){_e(1)});Ce({target:"Object",stat:!0,forced:ba},{keys:function(t){return _e(it(t))}});var wa=Object.is||function(t,e){return t===e?0!==t||1/t==1/e:t!=t&&e!=e};Ce({target:"Object",stat:!0},{is:wa});var Sa=ya.values;Ce({target:"Object",stat:!0},{values:function(t){return Sa(t)}}),Ce({target:"Object",stat:!0},{hasOwn:ut});var Ea=Function.prototype,Oa=Ea.apply,xa=Ea.call,Ra="object"==typeof Reflect&&Reflect.apply||(s?xa.bind(Oa):function(){return xa.apply(Oa,arguments)}),Pa=!a(function(){Reflect.apply(function(){})});Ce({target:"Reflect",stat:!0,forced:Pa},{apply:function(t,e,r){return Ra(J(t),e,kt(r))}});var Aa=Function,ja=b([].concat),ka=b([].join),Ia={},Ta=s?Aa.bind:function(t){var e=J(this),r=e.prototype,n=vo(arguments,1),o=function(){var r=ja(n,vo(arguments));return this instanceof o?function(t,e,r){if(!ut(Ia,e)){for(var n=[],o=0;ob)","g");return"b"!==t.exec("b").groups.a||"bc"!=="b".replace(t,"$c")}),gs=Oe.f,ys=ne.enforce,ms=dt("match"),bs=i.RegExp,ws=bs.prototype,Ss=i.SyntaxError,Es=b(ws.exec),Os=b("".charAt),xs=b("".replace),Rs=b("".indexOf),Ps=b("".slice),As=/^\?<[^\s\d!#%&*+<=>@^][^\s!#%&*+<=>@^]*>/,js=/a/g,ks=/a/g,Is=new bs(js)!==js,Ts=cs.MISSED_STICKY,Ms=cs.UNSUPPORTED_Y,Ls=u&&(!Is||Ts||ps||ds||a(function(){return ks[ms]=!1,bs(js)!==js||bs(ks)===ks||"/a/i"!==String(bs(js,"i"))}));if(Ue("RegExp",Ls)){for(var Us=function(t,e){var r,n,o,i,a,u,s=U(ws,this),c=es(t),f=void 0===e,l=[],h=t;if(!s&&c&&f&&t.constructor===Us)return t;if((c||U(ws,t))&&(t=t.source,f&&(e=os(h))),t=void 0===t?"":Wr(t),e=void 0===e?"":Wr(e),h=t,ps&&"dotAll"in js&&(n=!!e&&Rs(e,"s")>-1)&&(e=xs(e,/s/g,"")),r=e,Ts&&"sticky"in js&&(o=!!e&&Rs(e,"y")>-1)&&Ms&&(e=xs(e,/y/g,"")),ds&&(i=function(t){for(var e,r=t.length,n=0,o="",i=[],a=Ve(null),u=!1,s=!1,c=0,f="";n<=r;n++){if("\\"===(e=Os(t,n)))e+=Os(t,++n);else if("]"===e)u=!1;else if(!u)switch(!0){case"["===e:u=!0;break;case"("===e:if(o+=e,"?:"===Ps(t,n+1,n+3))continue;Es(As,Ps(t,n+1))&&(n+=2,s=!0),c++;continue;case">"===e&&s:if(""===f||ut(a,f))throw new Ss("Invalid capture group name");a[f]=!0,i[i.length]=[f,c],s=!1,f="";continue}s?f+=e:o+=e}return[o,i]}(t),t=i[0],l=i[1]),a=Io(bs(t,e),s?this:ws,Us),(n||o||l.length)&&(u=ys(a),n&&(u.dotAll=!0,u.raw=Us(function(t){for(var e,r=t.length,n=0,o="",i=!1;n<=r;n++)"\\"!==(e=Os(t,n))?i||"."!==e?("["===e?i=!0:"]"===e&&(i=!1),o+=e):o+="[\\s\\S]":o+=e+Os(t,++n);return o}(t),r)),o&&(u.sticky=!0),l.length&&(u.groups=l)),t!==h)try{_t(a,"source",""===h?"(?:)":h)}catch(t){}return a},Ns=gs(bs),Cs=0;Ns.length>Cs;)ls(Us,bs,Ns[Cs++]);ws.constructor=Us,Us.prototype=ws,ie(i,"RegExp",Us,{constructor:!0})}Uo("RegExp");var _s=zt.PROPER,Fs="toString",Bs=RegExp.prototype,Ds=Bs[Fs];(a(function(){return"/a/b"!==Ds.call({source:"a",flags:"b"})})||_s&&Ds.name!==Fs)&&ie(Bs,Fs,function(){var t=kt(this);return"/"+Wr(t.source)+"/"+Wr(os(t))},{unsafe:!0});var zs=ne.get,Ws=RegExp.prototype,qs=TypeError;u&&ps&&so(Ws,"dotAll",{configurable:!0,get:function(){if(this!==Ws){if("RegExp"===E(this))return!!zs(this).dotAll;throw new qs("Incompatible receiver, RegExp required")}}});var Hs=ne.get,$s=nt("native-string-replace",String.prototype.replace),Ks=RegExp.prototype.exec,Gs=Ks,Vs=b("".charAt),Ys=b("".indexOf),Xs=b("".replace),Js=b("".slice),Qs=function(){var t=/a/,e=/b*/g;return f(Ks,t,"a"),f(Ks,e,"a"),0!==t.lastIndex||0!==e.lastIndex}(),Zs=cs.BROKEN_CARET,tc=void 0!==/()??/.exec("")[1];(Qs||tc||Zs||ps||ds)&&(Gs=function(t){var e,r,n,o,i,a,u,s=this,c=Hs(s),l=Wr(t),h=c.raw;if(h)return h.lastIndex=s.lastIndex,e=f(Gs,h,l),s.lastIndex=h.lastIndex,e;var p=c.groups,v=Zs&&s.sticky,d=f(rs,s),g=s.source,y=0,m=l;if(v&&(d=Xs(d,"y",""),-1===Ys(d,"g")&&(d+="g"),m=Js(l,s.lastIndex),s.lastIndex>0&&(!s.multiline||s.multiline&&"\n"!==Vs(l,s.lastIndex-1))&&(g="(?: "+g+")",m=" "+m,y++),r=new RegExp("^(?:"+g+")",d)),tc&&(r=new RegExp("^"+g+"$(?!\\s)",d)),Qs&&(n=s.lastIndex),o=f(Ks,v?r:s,m),v?o?(o.input=Js(o.input,y),o[0]=Js(o[0],y),o.index=s.lastIndex,s.lastIndex+=o[0].length):s.lastIndex=0:Qs&&o&&(s.lastIndex=s.global?o.index+o[0].length:n),tc&&o&&o.length>1&&f($s,o[0],r,function(){for(i=1;i]*>)/g,Oc=/\$([$&'`]|\d{1,2})/g,xc=function(t,e,r,n,o,i){var a=r+t.length,u=n.length,s=Oc;return void 0!==o&&(o=it(o),s=Ec),wc(i,s,function(i,s){var c;switch(bc(s,0)){case"$":return"$";case"&":return t;case"`":return Sc(e,0,r);case"'":return Sc(e,a);case"<":c=o[Sc(s,1,-1)];break;default:var f=+s;if(0===f)return i;if(f>u){var l=mc(f/10);return 0===l?i:l<=u?void 0===n[l-1]?bc(s,1):n[l-1]+bc(s,1):i}c=n[f-1]}return void 0===c?"":c})},Rc=dt("replace"),Pc=Math.max,Ac=Math.min,jc=b([].concat),kc=b([].push),Ic=b("".indexOf),Tc=b("".slice),Mc="$0"==="a".replace(/./,"$0"),Lc=!!/./[Rc]&&""===/./[Rc]("a","$0"),Uc=!a(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")});pc("replace",function(t,e,r){var n=Lc?"$":"$0";return[function(t,r){var n=j(this),o=P(t)?void 0:Q(t,Rc);return o?f(o,t,n,r):f(e,Wr(n),t,r)},function(t,o){var i=kt(this),a=Wr(t);if("string"==typeof o&&-1===Ic(o,n)&&-1===Ic(o,"$<")){var u=r(e,i,a,o);if(u.done)return u.value}var s=T(o);s||(o=Wr(o));var c,f=i.global;f&&(c=i.unicode,i.lastIndex=0);for(var l,h=[];null!==(l=yc(i,a))&&(kc(h,l),f);)""===Wr(l[0])&&(i.lastIndex=dc(a,ve(i.lastIndex),c));for(var p,v="",d=0,g=0;g=d&&(v+=Tc(a,d,b)+y,d=b+m.length)}return v+Tc(a,d)}]},!Uc||!Mc||Lc),pc("search",function(t,e,r){return[function(e){var r=j(this),n=P(e)?void 0:Q(e,t);return n?f(n,e,r):new RegExp(e)[t](Wr(r))},function(t){var n=kt(this),o=Wr(t),i=r(e,n,o);if(i.done)return i.value;var a=n.lastIndex;wa(a,0)||(n.lastIndex=0);var u=yc(n,o);return wa(n.lastIndex,a)||(n.lastIndex=a),null===u?-1:u.index}]});var Nc=dt("species"),Cc=function(t,e){var r,n=kt(t).constructor;return void 0===n||P(r=kt(n)[Nc])?e:La(r)},_c=cs.UNSUPPORTED_Y,Fc=Math.min,Bc=b([].push),Dc=b("".slice),zc=!a(function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var r="ab".split(t);return 2!==r.length||"a"!==r[0]||"b"!==r[1]}),Wc="c"==="abbc".split(/(b)*/)[1]||4!=="test".split(/(?:)/,-1).length||2!=="ab".split(/(?:ab)*/).length||4!==".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length;pc("split",function(t,e,r){var n="0".split(void 0,0).length?function(t,r){return void 0===t&&0===r?[]:f(e,this,t,r)}:e;return[function(e,r){var o=j(this),i=P(e)?void 0:Q(e,t);return i?f(i,e,o,r):f(n,Wr(o),e,r)},function(t,o){var i=kt(this),a=Wr(t);if(!Wc){var u=r(n,i,a,o,n!==e);if(u.done)return u.value}var s=Cc(i,RegExp),c=i.unicode,f=new s(_c?"^(?:"+i.source+")":i,(i.ignoreCase?"i":"")+(i.multiline?"m":"")+(i.unicode?"u":"")+(_c?"g":"y")),l=void 0===o?4294967295:o>>>0;if(0===l)return[];if(0===a.length)return null===yc(f,a)?[a]:[];for(var h=0,p=0,v=[];p0;(n>>>=1)&&(e+=e))1&n&&(r+=e);return r},Kc=b($c),Gc=b("".slice),Vc=Math.ceil,Yc=function(t){return function(e,r,n){var o,i,a=Wr(j(e)),u=ve(r),s=a.length,c=void 0===n?" ":Wr(n);return u<=s||""===c?a:((i=Kc(c,Vc((o=u-s)/c.length))).length>o&&(i=Gc(i,0,o)),t?a+i:i+a)}},Xc={start:Yc(!1),end:Yc(!0)},Jc=Xc.start,Qc=Array,Zc=RegExp.escape,tf=b("".charAt),ef=b("".charCodeAt),rf=b(1.1.toString),nf=b([].join),of=/^[0-9a-z]/i,af=/^[$()*+./?[\\\]^{|}]/,uf=RegExp("^[!\"#%&',\\-:;<=>@`~"+Mi+"]"),sf=b(of.exec),cf={"\t":"t","\n":"n","\v":"v","\f":"f","\r":"r"},ff=function(t){var e=rf(ef(t,0),16);return e.length<3?"\\x"+Jc(e,2,"0"):"\\u"+Jc(e,4,"0")},lf=!Zc||"\\x61b"!==Zc("ab");Ce({target:"RegExp",stat:!0,forced:lf},{escape:function(t){!function(t){if("string"==typeof t)return t;throw new qc("Argument is not a string")}(t);for(var e=t.length,r=Qc(e),n=0;n=56320||n+1>=e||56320!=(64512&ef(t,n+1))?r[n]=ff(o):(r[n]=o,r[++n]=tf(t,n))}}return nf(r,"")}}),To("Set",function(t){return function(){return t(this,arguments.length?arguments[0]:void 0)}},Fo);var hf=Set.prototype,pf={Set:Set,add:b(hf.add),has:b(hf.has),remove:b(hf.delete),proto:hf},vf=pf.has,df=function(t){return vf(t),t},gf=pf.Set,yf=pf.proto,mf=b(yf.forEach),bf=b(yf.keys),wf=bf(new gf).next,Sf=function(t,e,r){return r?ci({iterator:bf(t),next:wf},e):mf(t,e)},Ef=pf.Set,Of=pf.add,xf=function(t){var e=new Ef;return Sf(t,function(t){Of(e,t)}),e},Rf=ln(pf.proto,"size","get")||function(t){return t.size},Pf="Invalid size",Af=RangeError,jf=TypeError,kf=Math.max,If=function(t,e){this.set=t,this.size=kf(e,0),this.has=J(t.has),this.keys=J(t.keys)};If.prototype={getIterator:function(){return{iterator:t=kt(f(this.keys,this.set)),next:t.next,done:!1};var t},includes:function(t){return f(this.has,this.set,t)}};var Tf=function(t){kt(t);var e=+t.size;if(e!=e)throw new jf(Pf);var r=ce(e);if(r<0)throw new Af(Pf);return new If(t,r)},Mf=pf.has,Lf=pf.remove,Uf=function(t){var e=df(this),r=Tf(t),n=xf(e);return Rf(e)<=r.size?Sf(e,function(t){r.includes(t)&&Lf(n,t)}):ci(r.getIterator(),function(t){Mf(e,t)&&Lf(n,t)}),n},Nf=function(t){return{size:t,has:function(){return!1},keys:function(){return{next:function(){return{done:!0}}}}}},Cf=function(t){var e=L("Set");try{(new e)[t](Nf(0));try{return(new e)[t](Nf(-1)),!1}catch(t){return!0}}catch(t){return!1}};Ce({target:"Set",proto:!0,real:!0,forced:!Cf("difference")},{difference:Uf});var _f=pf.Set,Ff=pf.add,Bf=pf.has,Df=function(t){var e=df(this),r=Tf(t),n=new _f;return Rf(e)>r.size?ci(r.getIterator(),function(t){Bf(e,t)&&Ff(n,t)}):Sf(e,function(t){r.includes(t)&&Ff(n,t)}),n},zf=!Cf("intersection")||a(function(){return"3,2"!==String(Array.from(new Set([1,2,3]).intersection(new Set([3,2]))))});Ce({target:"Set",proto:!0,real:!0,forced:zf},{intersection:Df});var Wf=pf.has,qf=function(t){var e=df(this),r=Tf(t);if(Rf(e)<=r.size)return!1!==Sf(e,function(t){if(r.includes(t))return!1},!0);var n=r.getIterator();return!1!==ci(n,function(t){if(Wf(e,t))return Tn(n,"normal",!1)})};Ce({target:"Set",proto:!0,real:!0,forced:!Cf("isDisjointFrom")},{isDisjointFrom:qf});var Hf=function(t){var e=df(this),r=Tf(t);return!(Rf(e)>r.size)&&!1!==Sf(e,function(t){if(!r.includes(t))return!1},!0)};Ce({target:"Set",proto:!0,real:!0,forced:!Cf("isSubsetOf")},{isSubsetOf:Hf});var $f=pf.has,Kf=function(t){var e=df(this),r=Tf(t);if(Rf(e)1?arguments[1]:void 0);return!1!==Sf(e,function(t){if(!r(t,t,e))return!1},!0)}});var el=dt("iterator"),rl=Object,nl=L("Set"),ol=function(t){return function(t){return M(t)&&"number"==typeof t.size&&T(t.has)&&T(t.keys)}(t)?t:function(t){if(P(t))return!1;var e=rl(t);return void 0!==e[el]||"@@iterator"in e||ut(un,pr(e))}(t)?new nl(t):t};Ce({target:"Set",proto:!0,real:!0,forced:!0},{difference:function(t){return f(Uf,this,ol(t))}});var il=pf.Set,al=pf.add;Ce({target:"Set",proto:!0,real:!0,forced:!0},{filter:function(t){var e=df(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=new il;return Sf(e,function(t){r(t,t,e)&&al(n,t)}),n}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{find:function(t){var e=df(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=Sf(e,function(t){if(r(t,t,e))return{value:t}},!0);return n&&n.value}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{intersection:function(t){return f(Df,this,ol(t))}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{isDisjointFrom:function(t){return f(qf,this,ol(t))}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{isSubsetOf:function(t){return f(Hf,this,ol(t))}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{isSupersetOf:function(t){return f(Kf,this,ol(t))}});var ul=b([].join),sl=b([].push);Ce({target:"Set",proto:!0,real:!0,forced:!0},{join:function(t){var e=df(this),r=void 0===t?",":Wr(t),n=[];return Sf(e,function(t){sl(n,t)}),ul(n,r)}});var cl=pf.Set,fl=pf.add;Ce({target:"Set",proto:!0,real:!0,forced:!0},{map:function(t){var e=df(this),r=ar(t,arguments.length>1?arguments[1]:void 0),n=new cl;return Sf(e,function(t){fl(n,r(t,t,e))}),n}});var ll=TypeError;Ce({target:"Set",proto:!0,real:!0,forced:!0},{reduce:function(t){var e=df(this),r=arguments.length<2,n=r?void 0:arguments[1];if(J(t),Sf(e,function(o){r?(r=!1,n=o):n=t(n,o,o,e)}),r)throw new ll("Reduce of empty set with no initial value");return n}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{some:function(t){var e=df(this),r=ar(t,arguments.length>1?arguments[1]:void 0);return!0===Sf(e,function(t){if(r(t,t,e))return!0},!0)}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{symmetricDifference:function(t){return f(Xf,this,ol(t))}}),Ce({target:"Set",proto:!0,real:!0,forced:!0},{union:function(t){return f(Qf,this,ol(t))}});var hl=dt("species"),pl=dt("isConcatSpreadable"),vl=W>=51||!a(function(){var t=[];return t[pl]=!1,t.concat()[0]!==t}),dl=function(t){if(!M(t))return!1;var e=t[pl];return void 0!==e?!!e:ur(t)},gl=!(vl&&(W>=51||!a(function(){var t=[];return(t.constructor={})[hl]=function(){return{foo:1}},1!==t.concat(Boolean).foo})));Ce({target:"Array",proto:!0,arity:1,forced:gl},{concat:function(t){var e,r,n,o,i,a=it(this),u=xr(a,0),s=0;for(e=-1,n=arguments.length;e1?arguments[1]:void 0,n=e.length,o=void 0===r?n:ip(ve(r),n),i=Wr(t);return op(e,o-i.length,o)===i}}),Ze("String","endsWith");var sp=RangeError,cp=String.fromCharCode,fp=String.fromCodePoint,lp=b([].join);Ce({target:"String",stat:!0,arity:1,forced:!!fp&&1!==fp.length},{fromCodePoint:function(t){for(var e,r=[],n=arguments.length,o=0;n>o;){if(e=+arguments[o++],he(e,1114111)!==e)throw new sp(e+" is not a valid code point");r[o]=e<65536?cp(e):cp(55296+((e-=65536)>>10),e%1024+56320)}return lp(r,"")}});var hp=b("".indexOf);Ce({target:"String",proto:!0,forced:!rp("includes")},{includes:function(t){return!!~hp(Wr(j(this)),Wr(tp(t)),arguments.length>1?arguments[1]:void 0)}}),Ze("String","includes"),b(un.String);var pp=/Version\/10(?:\.\d+){1,2}(?: [\w./]+)?(?: Mobile\/\w+)? Safari\//.test(_),vp=Xc.start;Ce({target:"String",proto:!0,forced:pp},{padStart:function(t){return vp(this,t,arguments.length>1?arguments[1]:void 0)}}),Ze("String","padStart");var dp=Xc.end;Ce({target:"String",proto:!0,forced:pp},{padEnd:function(t){return dp(this,t,arguments.length>1?arguments[1]:void 0)}}),Ze("String","padEnd");var gp=b([].push),yp=b([].join);Ce({target:"String",stat:!0},{raw:function(t){var e=k(it(t).raw),r=de(e);if(!r)return"";for(var n=arguments.length,o=[],i=0;;){if(gp(o,Wr(e[i++])),i===r)return yp(o,"");i1?arguments[1]:void 0,e.length)),n=Wr(t);return bp(e,r,r+n.length)===n}}),Ze("String","startsWith");var Op=zt.PROPER,xp=function(t){return a(function(){return!!Mi[t]()||"​…᠎"!=="​…᠎"[t]()||Op&&Mi[t].name!==t})},Rp=_i.start,Pp=xp("trimStart")?function(){return Rp(this)}:"".trimStart;Ce({target:"String",proto:!0,name:"trimStart",forced:"".trimLeft!==Pp},{trimLeft:Pp}),Ce({target:"String",proto:!0,name:"trimStart",forced:"".trimStart!==Pp},{trimStart:Pp}),Ze("String","trimLeft");var Ap=_i.end,jp=xp("trimEnd")?function(){return Ap(this)}:"".trimEnd;Ce({target:"String",proto:!0,name:"trimEnd",forced:"".trimRight!==jp},{trimRight:jp}),Ce({target:"String",proto:!0,name:"trimEnd",forced:"".trimEnd!==jp},{trimEnd:jp}),Ze("String","trimRight");var kp=Object.getOwnPropertyDescriptor,Ip=function(t){if(!u)return i[t];var e=kp(i,t);return e&&e.value},Tp=dt("iterator"),Mp=!a(function(){var t=new URL("b?a=1&b=2&c=3","https://a"),e=t.searchParams,r=new URLSearchParams("a=1&a=2&b=3"),n="";return t.pathname="c%20d",e.forEach(function(t,r){e.delete("b"),n+=r+t}),r.delete("a",2),r.delete("b",void 0),!e.size&&!u||!e.sort||"https://a/c%20d?a=1&c=3"!==t.href||"3"!==e.get("c")||"a=1"!==String(new URLSearchParams("?a=1"))||!e[Tp]||"a"!==new URL("https://a@b").username||"b"!==new URLSearchParams(new URLSearchParams("a=b")).get("a")||"xn--e1aybc"!==new URL("https://тест").host||"#%D0%B1"!==new URL("https://a#б").hash||"a1c3"!==n||"x"!==new URL("https://x",void 0).host}),Lp=TypeError,Up=function(t,e){if(t0;)t[o]=t[--o];o!==i++&&(t[o]=n)}else for(var a=Np(r/2),u=Cp(vo(t,0,a),e),s=Cp(vo(t,a),e),c=u.length,f=s.length,l=0,h=0;l0&&0!=(t&r);r>>=1)e++;return e},pv=function(t){var e=null;switch(t.length){case 1:e=t[0];break;case 2:e=(31&t[0])<<6|63&t[1];break;case 3:e=(15&t[0])<<12|(63&t[1])<<6|63&t[2];break;case 4:e=(7&t[0])<<18|(63&t[1])<<12|(63&t[2])<<6|63&t[3]}return e>1114111?null:e},vv=function(t){for(var e=(t=nv(t,cv," ")).length,r="",n=0;ne){r+="%",n++;continue}var i=lv(t,n+1);if(i!=i){r+=o,n++;continue}n+=2;var a=hv(i);if(0===a)o=Jp(i);else{if(1===a||a>4){r+="�",n++;continue}for(var u=[i],s=1;se||"%"!==tv(t,n));){var c=lv(t,n+1);if(c!=c){n+=3;break}if(c>191||c<128)break;rv(u,c),n+=2,s++}if(u.length!==a){r+="�";continue}var f=pv(u);null===f?r+="�":o=Qp(f)}}r+=o,n++}return r},dv=/[!'()~]|%20/g,gv={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"},yv=function(t){return gv[t]},mv=function(t){return nv(Xp(t),dv,yv)},bv=fn(function(t,e){zp(this,{type:Dp,target:Wp(t).entries,index:0,kind:e})},Bp,function(){var t=qp(this),e=t.target,r=t.index++;if(!e||r>=e.length)return t.target=null,Pn(void 0,!0);var n=e[r];switch(t.kind){case"keys":return Pn(n.key,!1);case"values":return Pn(n.value,!1)}return Pn([n.key,n.value],!1)},!0),wv=function(t){this.entries=[],this.url=null,void 0!==t&&(M(t)?this.parseObject(t):this.parseQuery("string"==typeof t?"?"===tv(t,0)?uv(t,1):t:Wr(t)))};wv.prototype={type:Bp,bindURL:function(t){this.url=t,this.update()},parseObject:function(t){var e,r,n,o,i,a,u,s=this.entries,c=Fn(t);if(c)for(r=(e=Dn(t,c)).next;!(n=f(r,e)).done;){if(o=Dn(kt(n.value)),(a=f(i=o.next,o)).done||(u=f(i,o)).done||!f(i,o).done)throw new Yp("Expected sequence with length 2");rv(s,{key:Wr(a.value),value:Wr(u.value)})}else for(var l in t)ut(t,l)&&rv(s,{key:l,value:Wr(t[l])})},parseQuery:function(t){if(t)for(var e,r,n=this.entries,o=av(t,"&"),i=0;i0?arguments[0]:void 0));u||(this.size=t.entries.length)},Ev=Sv.prototype;if(Mo(Ev,{append:function(t,e){var r=Wp(this);Up(arguments.length,2),rv(r.entries,{key:Wr(t),value:Wr(e)}),u||this.length++,r.updateURL()},delete:function(t){for(var e=Wp(this),r=Up(arguments.length,1),n=e.entries,o=Wr(t),i=r<2?void 0:arguments[1],a=void 0===i?i:Wr(i),s=0;se.key?1:-1}),t.updateURL()},forEach:function(t){for(var e,r=Wp(this).entries,n=ar(t,arguments.length>1?arguments[1]:void 0),o=0;o1?Rv(arguments[1]):{})}}),T($p)){var Pv=function(t){return ko(this,Gp),new $p(t,arguments.length>1?Rv(arguments[1]):{})};Gp.constructor=Pv,Pv.prototype=Gp,Ce({global:!0,constructor:!0,dontCallGetSet:!0,forced:!0},{Request:Pv})}}var Av={URLSearchParams:Sv,getState:Wp},jv=URLSearchParams,kv=jv.prototype,Iv=b(kv.append),Tv=b(kv.delete),Mv=b(kv.forEach),Lv=b([].push),Uv=new jv("a=1&a=2&b=3");Uv.delete("a",1),Uv.delete("b",void 0),Uv+""!="a=2"&&ie(kv,"delete",function(t){var e=arguments.length,r=e<2?void 0:arguments[1];if(e&&void 0===r)return Tv(this,t);var n=[];Mv(this,function(t,e){Lv(n,{key:e,value:t})}),Up(e,1);for(var o,i=Wr(t),a=Wr(r),u=0,s=0,c=!1,f=n.length;uo;)for(var s,c=R(arguments[o++]),l=i?$v(_e(c),i(c)):_e(c),h=l.length,p=0;h>p;)s=l[p++],u&&!f(a,c,s)||(r[s]=c[s]);return r}:qv,Gv=2147483647,Vv=/[^\0-\u007E]/,Yv=/[.\u3002\uFF0E\uFF61]/g,Xv="Overflow: input needs wider integers to process",Jv=RangeError,Qv=b(Yv.exec),Zv=Math.floor,td=String.fromCharCode,ed=b("".charCodeAt),rd=b([].join),nd=b([].push),od=b("".replace),id=b("".split),ad=b("".toLowerCase),ud=function(t){return t+22+75*(t<26)},sd=function(t,e,r){var n=0;for(t=r?Zv(t/700):t>>1,t+=Zv(t/e);t>455;)t=Zv(t/35),n+=36;return Zv(n+36*t/(t+38))},cd=function(t){var e=[];t=function(t){for(var e=[],r=0,n=t.length;r=55296&&o<=56319&&r=i&&nZv((Gv-a)/l))throw new Jv(Xv);for(a+=(f-i)*l,i=f,r=0;rGv)throw new Jv(Xv);if(n===i){for(var h=a,p=36;;){var v=p<=u?1:p>=u+26?26:p-u;if(h?@[\\\]^|]/,qd=/[\0\t\n\r #/:<>?@[\\\]^|]/,Hd=/^[\u0000-\u0020]+/,$d=/(^|[^\u0000-\u0020])[\u0000-\u0020]+$/,Kd=/[\t\n\r]/g,Gd=function(t){var e,r,n,o;if("number"==typeof t){for(e=[],r=0;r<4;r++)Td(e,t%256),t=md(t/256);return Ed(e,".")}if("object"==typeof t){for(e="",n=function(t){for(var e=null,r=1,n=null,o=0,i=0;i<8;i++)0!==t[i]?(o>r&&(e=n,r=o),n=null,o=0):(null===n&&(n=i),++o);return o>r?n:e}(t),r=0;r<8;r++)o&&0===t[r]||(o&&(o=!1),n===r?(e+=r?":":"::",o=!0):(e+=Od(t[r],16),r<7&&(e+=":")));return"["+e+"]"}return t},Vd={},Yd=Kv({},Vd,{" ":1,'"':1,"<":1,">":1,"`":1}),Xd=Kv({},Yd,{"#":1,"?":1,"{":1,"}":1}),Jd=Kv({},Xd,{"/":1,":":1,";":1,"=":1,"@":1,"[":1,"\\":1,"]":1,"^":1,"|":1}),Qd=function(t,e){var r=fd(t,0);return r>32&&r<127&&!ut(e,t)?t:encodeURIComponent(t)},Zd={ftp:21,file:null,http:80,https:443,ws:80,wss:443},tg=function(t,e){var r;return 2===t.length&&Sd(Nd,wd(t,0))&&(":"===(r=wd(t,1))||!e&&"|"===r)},eg=function(t){var e;return t.length>1&&tg(kd(t,0,2))&&(2===t.length||"/"===(e=wd(t,2))||"\\"===e||"?"===e||"#"===e)},rg=function(t){return"."===t||"%2e"===Id(t)},ng={},og={},ig={},ag={},ug={},sg={},cg={},fg={},lg={},hg={},pg={},vg={},dg={},gg={},yg={},mg={},bg={},wg={},Sg={},Eg={},Og={},xg=function(t,e,r){var n,o,i,a=Wr(t);if(e){if(o=this.parse(a))throw new gd(o);this.searchParams=null}else{if(void 0!==r&&(n=new xg(r,!0)),o=this.parse(a,null,n))throw new gd(o);(i=vd(new pd)).bindURL(this),this.searchParams=i}};xg.prototype={type:"URL",parse:function(t,e,r){var n,o,i,a,u,s=this,c=e||ng,f=0,l="",h=!1,p=!1,v=!1;for(t=Wr(t),e||(s.scheme="",s.username="",s.password="",s.host=null,s.port=null,s.path=[],s.query=null,s.fragment=null,s.cannotBeABaseURL=!1,t=Pd(t,Hd,""),t=Pd(t,$d,"$1")),t=Pd(t,Kd,""),n=Wn(t);f<=n.length;){switch(o=n[f],c){case ng:if(!o||!Sd(Nd,o)){if(e)return Md;c=ig;continue}l+=Id(o),c=og;break;case og:if(o&&(Sd(Cd,o)||"+"===o||"-"===o||"."===o))l+=Id(o);else{if(":"!==o){if(e)return Md;l="",c=ig,f=0;continue}if(e&&(s.isSpecial()!==ut(Zd,l)||"file"===l&&(s.includesCredentials()||null!==s.port)||"file"===s.scheme&&!s.host))return;if(s.scheme=l,e)return void(s.isSpecial()&&Zd[s.scheme]===s.port&&(s.port=null));l="","file"===s.scheme?c=gg:s.isSpecial()&&r&&r.scheme===s.scheme?c=ag:s.isSpecial()?c=fg:"/"===n[f+1]?(c=ug,f++):(s.cannotBeABaseURL=!0,Rd(s.path,""),c=Sg)}break;case ig:if(!r||r.cannotBeABaseURL&&"#"!==o)return Md;if(r.cannotBeABaseURL&&"#"===o){s.scheme=r.scheme,s.path=vo(r.path),s.query=r.query,s.fragment="",s.cannotBeABaseURL=!0,c=Og;break}c="file"===r.scheme?gg:sg;continue;case ag:if("/"!==o||"/"!==n[f+1]){c=sg;continue}c=lg,f++;break;case ug:if("/"===o){c=hg;break}c=wg;continue;case sg:if(s.scheme=r.scheme,o===Wv)s.username=r.username,s.password=r.password,s.host=r.host,s.port=r.port,s.path=vo(r.path),s.query=r.query;else if("/"===o||"\\"===o&&s.isSpecial())c=cg;else if("?"===o)s.username=r.username,s.password=r.password,s.host=r.host,s.port=r.port,s.path=vo(r.path),s.query="",c=Eg;else{if("#"!==o){s.username=r.username,s.password=r.password,s.host=r.host,s.port=r.port,s.path=vo(r.path),s.path.length--,c=wg;continue}s.username=r.username,s.password=r.password,s.host=r.host,s.port=r.port,s.path=vo(r.path),s.query=r.query,s.fragment="",c=Og}break;case cg:if(!s.isSpecial()||"/"!==o&&"\\"!==o){if("/"!==o){s.username=r.username,s.password=r.password,s.host=r.host,s.port=r.port,c=wg;continue}c=hg}else c=lg;break;case fg:if(c=lg,"/"!==o||"/"!==wd(l,f+1))continue;f++;break;case lg:if("/"!==o&&"\\"!==o){c=hg;continue}break;case hg:if("@"===o){h&&(l="%40"+l),h=!0,i=Wn(l);for(var d=0;d65535)return Ud;s.port=s.isSpecial()&&m===Zd[s.scheme]?null:m,l=""}if(e)return;c=bg;continue}return Ud}l+=o;break;case gg:if(s.scheme="file","/"===o||"\\"===o)c=yg;else{if(!r||"file"!==r.scheme){c=wg;continue}switch(o){case Wv:s.host=r.host,s.path=vo(r.path),s.query=r.query;break;case"?":s.host=r.host,s.path=vo(r.path),s.query="",c=Eg;break;case"#":s.host=r.host,s.path=vo(r.path),s.query=r.query,s.fragment="",c=Og;break;default:eg(Ed(vo(n,f),""))||(s.host=r.host,s.path=vo(r.path),s.shortenPath()),c=wg;continue}}break;case yg:if("/"===o||"\\"===o){c=mg;break}r&&"file"===r.scheme&&!eg(Ed(vo(n,f),""))&&(tg(r.path[0],!0)?Rd(s.path,r.path[0]):s.host=r.host),c=wg;continue;case mg:if(o===Wv||"/"===o||"\\"===o||"?"===o||"#"===o){if(!e&&tg(l))c=wg;else if(""===l){if(s.host="",e)return;c=bg}else{if(a=s.parseHost(l))return a;if("localhost"===s.host&&(s.host=""),e)return;l="",c=bg}continue}l+=o;break;case bg:if(s.isSpecial()){if(c=wg,"/"!==o&&"\\"!==o)continue}else if(e||"?"!==o)if(e||"#"!==o){if(o!==Wv&&(c=wg,"/"!==o))continue}else s.fragment="",c=Og;else s.query="",c=Eg;break;case wg:if(o===Wv||"/"===o||"\\"===o&&s.isSpecial()||!e&&("?"===o||"#"===o)){if(".."===(u=Id(u=l))||"%2e."===u||".%2e"===u||"%2e%2e"===u?(s.shortenPath(),"/"===o||"\\"===o&&s.isSpecial()||Rd(s.path,"")):rg(l)?"/"===o||"\\"===o&&s.isSpecial()||Rd(s.path,""):("file"===s.scheme&&!s.path.length&&tg(l)&&(s.host&&(s.host=""),l=wd(l,0)+":"),Rd(s.path,l)),l="","file"===s.scheme&&(o===Wv||"?"===o||"#"===o))for(;s.path.length>1&&""===s.path[0];)Ad(s.path);"?"===o?(s.query="",c=Eg):"#"===o&&(s.fragment="",c=Og)}else l+=Qd(o,Xd);break;case Sg:"?"===o?(s.query="",c=Eg):"#"===o?(s.fragment="",c=Og):o!==Wv&&(s.path[0]+=Qd(o,Vd));break;case Eg:e||"#"!==o?o!==Wv&&("'"===o&&s.isSpecial()?s.query+="%27":s.query+="#"===o?"%23":Qd(o,Vd)):(s.fragment="",c=Og);break;case Og:o!==Wv&&(s.fragment+=Qd(o,Yd))}f++}},parseHost:function(t){var e,r,n;if("["===wd(t,0)){if("]"!==wd(t,t.length-1))return Ld;if(e=function(t){var e,r,n,o,i,a,u,s=[0,0,0,0,0,0,0,0],c=0,f=null,l=0,h=function(){return wd(t,l)};if(":"===h()){if(":"!==wd(t,1))return;l+=2,f=++c}for(;h();){if(8===c)return;if(":"!==h()){for(e=r=0;r<4&&Sd(zd,h());)e=16*e+yd(h(),16),l++,r++;if("."===h()){if(0===r)return;if(l-=r,c>6)return;for(n=0;h();){if(o=null,n>0){if(!("."===h()&&n<4))return;l++}if(!Sd(_d,h()))return;for(;Sd(_d,h());){if(i=yd(h(),10),null===o)o=i;else{if(0===o)return;o=10*o+i}if(o>255)return;l++}s[c]=256*s[c]+o,2!=++n&&4!==n||c++}if(4!==n)return;break}if(":"===h()){if(l++,!h())return}else if(h())return;s[c++]=e}else{if(null!==f)return;l++,f=++c}}if(null!==f)for(a=c-f,c=7;0!==c&&a>0;)u=s[c],s[c--]=s[f+a-1],s[f+--a]=u;else if(8!==c)return;return s}(kd(t,1,-1)),!e)return Ld;this.host=e}else if(this.isSpecial()){if(t=function(t){var e,r,n=[],o=id(od(ad(t),Yv,"."),".");for(e=0;e4)return t;for(r=[],n=0;n1&&"0"===wd(o,0)&&(i=Sd(Fd,o)?16:8,o=kd(o,8===i?1:2)),""===o)a=0;else{if(!Sd(10===i?Dd:8===i?Bd:zd,o))return t;a=yd(o,i)}Rd(r,a)}for(n=0;n=bd(256,5-e))return null}else if(a>255)return null;for(u=xd(r),n=0;n1?arguments[1]:void 0,n=ld(e,new xg(t,!1,r));u||(e.href=n.serialize(),e.origin=n.getOrigin(),e.protocol=n.getProtocol(),e.username=n.getUsername(),e.password=n.getPassword(),e.host=n.getHost(),e.hostname=n.getHostname(),e.port=n.getPort(),e.pathname=n.getPathname(),e.search=n.getSearch(),e.searchParams=n.getSearchParams(),e.hash=n.getHash())},Pg=Rg.prototype,Ag=function(t,e){return{get:function(){return hd(this)[t]()},set:e&&function(t){return hd(this)[e](t)},configurable:!0,enumerable:!0}};if(u&&(so(Pg,"href",Ag("serialize","setHref")),so(Pg,"origin",Ag("getOrigin")),so(Pg,"protocol",Ag("getProtocol","setProtocol")),so(Pg,"username",Ag("getUsername","setUsername")),so(Pg,"password",Ag("getPassword","setPassword")),so(Pg,"host",Ag("getHost","setHost")),so(Pg,"hostname",Ag("getHostname","setHostname")),so(Pg,"port",Ag("getPort","setPort")),so(Pg,"pathname",Ag("getPathname","setPathname")),so(Pg,"search",Ag("getSearch","setSearch")),so(Pg,"searchParams",Ag("getSearchParams")),so(Pg,"hash",Ag("getHash","setHash"))),ie(Pg,"toJSON",function(){return hd(this).serialize()},{enumerable:!0}),ie(Pg,"toString",function(){return hd(this).serialize()},{enumerable:!0}),dd){var jg=dd.createObjectURL,kg=dd.revokeObjectURL;jg&&ie(Rg,"createObjectURL",ar(jg,dd)),kg&&ie(Rg,"revokeObjectURL",ar(kg,dd))}an(Rg,"URL"),Ce({global:!0,constructor:!0,forced:!Mp,sham:!u},{URL:Rg});var Ig=L("URL"),Tg=Mp&&a(function(){Ig.canParse()}),Mg=a(function(){return 1!==Ig.canParse.length});Ce({target:"URL",stat:!0,forced:!Tg||Mg},{canParse:function(t){var e=Up(arguments.length,1),r=Wr(t),n=e<2||void 0===arguments[1]?void 0:Wr(arguments[1]);try{return!!new Ig(r,n)}catch(t){return!1}}});var Lg=L("URL");Ce({target:"URL",stat:!0,forced:!Mp},{parse:function(t){var e=Up(arguments.length,1),r=Wr(t),n=e<2||void 0===arguments[1]?void 0:Wr(arguments[1]);try{return new Lg(r,n)}catch(t){return null}}}),Ce({target:"URL",proto:!0,enumerable:!0},{toJSON:function(){return f(URL.prototype.toString,this)}});var Ug=WeakMap.prototype,Ng={WeakMap:WeakMap,set:b(Ug.set),get:b(Ug.get),has:b(Ug.has),remove:b(Ug.delete)},Cg=Ng.has,_g=function(t){return Cg(t),t},Fg=Ng.get,Bg=Ng.has,Dg=Ng.set;Ce({target:"WeakMap",proto:!0,real:!0,forced:!0},{emplace:function(t,e){var r,n,o=_g(this);return Bg(o,t)?(r=Fg(o,t),"update"in e&&(r=e.update(r,t,o),Dg(o,t,r)),r):(n=e.insert(t,o),Dg(o,t,n),n)}}),Ce({target:"WeakMap",stat:!0,forced:!0},{from:ei(Ng.WeakMap,Ng.set,!0)}),Ce({target:"WeakMap",stat:!0,forced:!0},{of:ri(Ng.WeakMap,Ng.set,!0)});var zg=Ng.remove;Ce({target:"WeakMap",proto:!0,real:!0,forced:!0},{deleteAll:function(){for(var t,e=_g(this),r=!0,n=0,o=arguments.length;n2&&(n=r,M(o=arguments[2])&&"cause"in o&&_t(n,"cause",o.cause));var s=[];return Ao(t,ny,{that:s}),_t(r,"errors",s),r};dn?dn(oy,ry):Ae(oy,ry,{name:!0});var iy=oy.prototype=Ve(ry.prototype,{constructor:d(1,oy),message:d(1,""),name:d(1,"AggregateError")});Ce({global:!0,constructor:!0,arity:2},{AggregateError:oy});var ay,uy,sy,cy,fy=function(t){return _.slice(0,t.length)===t},ly=fy("Bun/")?"BUN":fy("Cloudflare-Workers")?"CLOUDFLARE":fy("Deno/")?"DENO":fy("Node.js/")?"NODE":i.Bun&&"string"==typeof Bun.version?"BUN":i.Deno&&"object"==typeof Deno.version?"DENO":"process"===E(i.process)?"NODE":i.window&&i.document?"BROWSER":"REST",hy="NODE"===ly,py=/(?:ipad|iphone|ipod).*applewebkit/i.test(_),vy=i.setImmediate,dy=i.clearImmediate,gy=i.process,yy=i.Dispatch,my=i.Function,by=i.MessageChannel,wy=i.String,Sy=0,Ey={},Oy="onreadystatechange";a(function(){ay=i.location});var xy=function(t){if(ut(Ey,t)){var e=Ey[t];delete Ey[t],e()}},Ry=function(t){return function(){xy(t)}},Py=function(t){xy(t.data)},Ay=function(t){i.postMessage(wy(t),ay.protocol+"//"+ay.host)};vy&&dy||(vy=function(t){Up(arguments.length,1);var e=T(t)?t:my(t),r=vo(arguments,1);return Ey[++Sy]=function(){Ra(e,void 0,r)},uy(Sy),Sy},dy=function(t){delete Ey[t]},hy?uy=function(t){gy.nextTick(Ry(t))}:yy&&yy.now?uy=function(t){yy.now(Ry(t))}:by&&!py?(cy=(sy=new by).port2,sy.port1.onmessage=Py,uy=ar(cy.postMessage,cy)):i.addEventListener&&T(i.postMessage)&&!i.importScripts&&ay&&"file:"!==ay.protocol&&!a(Ay)?(uy=Ay,i.addEventListener("message",Py,!1)):uy=Oy in Et("script")?function(t){De.appendChild(Et("script"))[Oy]=function(){De.removeChild(this),xy(t)}}:function(t){setTimeout(Ry(t),0)});var jy={set:vy,clear:dy},ky=function(){this.head=null,this.tail=null};ky.prototype={add:function(t){var e={item:t,next:null},r=this.tail;r?r.next=e:this.head=e,this.tail=e},get:function(){var t=this.head;if(t)return null===(this.head=t.next)&&(this.tail=null),t.item}};var Iy,Ty,My,Ly,Uy,Ny=ky,Cy=/ipad|iphone|ipod/i.test(_)&&"undefined"!=typeof Pebble,_y=/web0s(?!.*chrome)/i.test(_),Fy=jy.set,By=i.MutationObserver||i.WebKitMutationObserver,Dy=i.document,zy=i.process,Wy=i.Promise,qy=Ip("queueMicrotask");if(!qy){var Hy=new Ny,$y=function(){var t,e;for(hy&&(t=zy.domain)&&t.exit();e=Hy.get();)try{e()}catch(t){throw Hy.head&&Iy(),t}t&&t.enter()};py||hy||_y||!By||!Dy?!Cy&&Wy&&Wy.resolve?((Ly=Wy.resolve(void 0)).constructor=Wy,Uy=ar(Ly.then,Ly),Iy=function(){Uy($y)}):hy?Iy=function(){zy.nextTick($y)}:(Fy=ar(Fy,i),Iy=function(){Fy($y)}):(Ty=!0,My=Dy.createTextNode(""),new By($y).observe(My,{characterData:!0}),Iy=function(){My.data=Ty=!Ty}),qy=function(t){Hy.head||Iy(),Hy.add(t)}}var Ky,Gy,Vy,Yy=qy,Xy=function(t){try{return{error:!1,value:t()}}catch(t){return{error:!0,value:t}}},Jy=i.Promise,Qy=dt("species"),Zy=!1,tm=T(i.PromiseRejectionEvent),em=Ue("Promise",function(){var t=Kt(Jy),e=t!==String(Jy);if(!e&&66===W)return!0;if(!W||W<51||!/native code/.test(t)){var r=new Jy(function(t){t(1)}),n=function(t){t(function(){},function(){})};if((r.constructor={})[Qy]=n,!(Zy=r.then(function(){})instanceof n))return!0}return!(e||"BROWSER"!==ly&&"DENO"!==ly||tm)}),rm={CONSTRUCTOR:em,REJECTION_EVENT:tm,SUBCLASSING:Zy},nm=TypeError,om=function(t){var e,r;this.promise=new t(function(t,n){if(void 0!==e||void 0!==r)throw new nm("Bad Promise constructor");e=t,r=n}),this.resolve=J(e),this.reject=J(r)},im={f:function(t){return new om(t)}},am=jy.set,um="Promise",sm=rm.CONSTRUCTOR,cm=rm.REJECTION_EVENT,fm=rm.SUBCLASSING,lm=ne.getterFor(um),hm=ne.set,pm=Jy&&Jy.prototype,vm=Jy,dm=pm,gm=i.TypeError,ym=i.document,mm=i.process,bm=im.f,wm=bm,Sm=!!(ym&&ym.createEvent&&i.dispatchEvent),Em="unhandledrejection",Om=function(t){var e;return!(!M(t)||!T(e=t.then))&&e},xm=function(t,e){var r,n,o,i=e.value,a=1===e.state,u=a?t.ok:t.fail,s=t.resolve,c=t.reject,l=t.domain;try{u?(a||(2===e.rejection&&km(e),e.rejection=1),!0===u?r=i:(l&&l.enter(),r=u(i),l&&(l.exit(),o=!0)),r===t.promise?c(new gm("Promise-chain cycle")):(n=Om(r))?f(n,r,s,c):s(r)):c(i)}catch(t){l&&!o&&l.exit(),c(t)}},Rm=function(t,e){t.notified||(t.notified=!0,Yy(function(){for(var r,n=t.reactions;r=n.get();)xm(r,t);t.notified=!1,e&&!t.rejection&&Am(t)}))},Pm=function(t,e,r){var n,o;Sm?((n=ym.createEvent("Event")).promise=e,n.reason=r,n.initEvent(t,!1,!0),i.dispatchEvent(n)):n={promise:e,reason:r},!cm&&(o=i["on"+t])?o(n):t===Em&&function(t,e){try{1===arguments.length?console.error(t):console.error(t,e)}catch(t){}}("Unhandled promise rejection",r)},Am=function(t){f(am,i,function(){var e,r=t.facade,n=t.value;if(jm(t)&&(e=Xy(function(){hy?mm.emit("unhandledRejection",n,r):Pm(Em,r,n)}),t.rejection=hy||jm(t)?2:1,e.error))throw e.value})},jm=function(t){return 1!==t.rejection&&!t.parent},km=function(t){f(am,i,function(){var e=t.facade;hy?mm.emit("rejectionHandled",e):Pm("rejectionhandled",e,t.value)})},Im=function(t,e,r){return function(n){t(e,n,r)}},Tm=function(t,e,r){t.done||(t.done=!0,r&&(t=r),t.value=e,t.state=2,Rm(t,!0))},Mm=function(t,e,r){if(!t.done){t.done=!0,r&&(t=r);try{if(t.facade===e)throw new gm("Promise can't be resolved itself");var n=Om(e);n?Yy(function(){var r={done:!1};try{f(n,e,Im(Mm,r,t),Im(Tm,r,t))}catch(e){Tm(r,e,t)}}):(t.value=e,t.state=1,Rm(t,!1))}catch(e){Tm({done:!1},e,t)}}};if(sm&&(vm=function(t){ko(this,dm),J(t),f(Ky,this);var e=lm(this);try{t(Im(Mm,e),Im(Tm,e))}catch(t){Tm(e,t)}},(Ky=function(t){hm(this,{type:um,done:!1,notified:!1,parent:!1,reactions:new Ny,rejection:!1,state:0,value:null})}).prototype=ie(dm=vm.prototype,"then",function(t,e){var r=lm(this),n=bm(Cc(this,vm));return r.parent=!0,n.ok=!T(t)||t,n.fail=T(e)&&e,n.domain=hy?mm.domain:void 0,0===r.state?r.reactions.add(n):Yy(function(){xm(n,r)}),n.promise}),Gy=function(){var t=new Ky,e=lm(t);this.promise=t,this.resolve=Im(Mm,e),this.reject=Im(Tm,e)},im.f=bm=function(t){return t===vm||void 0===t?new Gy(t):wm(t)},T(Jy)&&pm!==Object.prototype)){Vy=pm.then,fm||ie(pm,"then",function(t,e){var r=this;return new vm(function(t,e){f(Vy,r,t,e)}).then(t,e)},{unsafe:!0});try{delete pm.constructor}catch(t){}dn&&dn(pm,dm)}Ce({global:!0,constructor:!0,wrap:!0,forced:sm},{Promise:vm}),an(vm,um,!1),Uo(um);var Lm=rm.CONSTRUCTOR||!Gn(function(t){Jy.all(t).then(void 0,function(){})});Ce({target:"Promise",stat:!0,forced:Lm},{all:function(t){var e=this,r=im.f(e),n=r.resolve,o=r.reject,i=Xy(function(){var r=J(e.resolve),i=[],a=0,u=1;Ao(t,function(t){var s=a++,c=!1;u++,f(r,e,t).then(function(t){c||(c=!0,i[s]=t,--u||n(i))},o)}),--u||n(i)});return i.error&&o(i.value),r.promise}});var Um=Jy&&Jy.prototype;if(Ce({target:"Promise",proto:!0,forced:rm.CONSTRUCTOR,real:!0},{catch:function(t){return this.then(void 0,t)}}),T(Jy)){var Nm=L("Promise").prototype.catch;Um.catch!==Nm&&ie(Um,"catch",Nm,{unsafe:!0})}Ce({target:"Promise",stat:!0,forced:Lm},{race:function(t){var e=this,r=im.f(e),n=r.reject,o=Xy(function(){var o=J(e.resolve);Ao(t,function(t){f(o,e,t).then(r.resolve,n)})});return o.error&&n(o.value),r.promise}}),Ce({target:"Promise",stat:!0,forced:rm.CONSTRUCTOR},{reject:function(t){var e=im.f(this);return(0,e.reject)(t),e.promise}});var Cm=function(t,e){if(kt(t),M(e)&&e.constructor===t)return e;var r=im.f(t);return(0,r.resolve)(e),r.promise};Ce({target:"Promise",stat:!0,forced:rm.CONSTRUCTOR},{resolve:function(t){return Cm(this,t)}}),Ce({target:"Promise",stat:!0,forced:Lm},{allSettled:function(t){var e=this,r=im.f(e),n=r.resolve,o=r.reject,i=Xy(function(){var r=J(e.resolve),o=[],i=0,a=1;Ao(t,function(t){var u=i++,s=!1;a++,f(r,e,t).then(function(t){s||(s=!0,o[u]={status:"fulfilled",value:t},--a||n(o))},function(t){s||(s=!0,o[u]={status:"rejected",reason:t},--a||n(o))})}),--a||n(o)});return i.error&&o(i.value),r.promise}});var _m="No one promise resolved";Ce({target:"Promise",stat:!0,forced:Lm},{any:function(t){var e=this,r=L("AggregateError"),n=im.f(e),o=n.resolve,i=n.reject,a=Xy(function(){var n=J(e.resolve),a=[],u=0,s=1,c=!1;Ao(t,function(t){var l=u++,h=!1;s++,f(n,e,t).then(function(t){h||c||(c=!0,o(t))},function(t){h||c||(h=!0,a[l]=t,--s||i(new r(a,_m)))})}),--s||i(new r(a,_m))});return a.error&&i(a.value),n.promise}}),Ce({target:"Promise",stat:!0},{withResolvers:function(){var t=im.f(this);return{promise:t.promise,resolve:t.resolve,reject:t.reject}}});var Fm=Jy&&Jy.prototype,Bm=!!Jy&&a(function(){Fm.finally.call({then:function(){}},function(){})});if(Ce({target:"Promise",proto:!0,real:!0,forced:Bm},{finally:function(t){var e=Cc(this,L("Promise")),r=T(t);return this.then(r?function(r){return Cm(e,t()).then(function(){return r})}:t,r?function(r){return Cm(e,t()).then(function(){throw r})}:t)}}),T(Jy)){var Dm=L("Promise").prototype.finally;Fm.finally!==Dm&&ie(Fm,"finally",Dm,{unsafe:!0})}var zm=i.Promise,Wm=!1,qm=!zm||!zm.try||Xy(function(){zm.try(function(t){Wm=8===t},8)}).error||!Wm;Ce({target:"Promise",stat:!0,forced:qm},{try:function(t){var e=arguments.length>1?vo(arguments,1):[],r=im.f(this),n=Xy(function(){return Ra(J(t),void 0,e)});return(n.error?r.reject:r.resolve)(n.value),r.promise}}),Ze("Promise","finally");var Hm="URLSearchParams"in self,$m="Symbol"in self&&"iterator"in Symbol,Km="FileReader"in self&&"Blob"in self&&function(){try{return new Blob,!0}catch(t){return!1}}(),Gm="FormData"in self,Vm="ArrayBuffer"in self;if(Vm)var Ym=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],Xm=ArrayBuffer.isView||function(t){return t&&Ym.indexOf(Object.prototype.toString.call(t))>-1};function Jm(t){if("string"!=typeof t&&(t=String(t)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(t))throw new TypeError("Invalid character in header field name");return t.toLowerCase()}function Qm(t){return"string"!=typeof t&&(t=String(t)),t}function Zm(t){var e={next:function(){var e=t.shift();return{done:void 0===e,value:e}}};return $m&&(e[Symbol.iterator]=function(){return e}),e}function tb(t){this.map={},t instanceof tb?t.forEach(function(t,e){this.append(e,t)},this):Array.isArray(t)?t.forEach(function(t){this.append(t[0],t[1])},this):t&&Object.getOwnPropertyNames(t).forEach(function(e){this.append(e,t[e])},this)}function eb(t){if(t.bodyUsed)return Promise.reject(new TypeError("Already read"));t.bodyUsed=!0}function rb(t){return new Promise(function(e,r){t.onload=function(){e(t.result)},t.onerror=function(){r(t.error)}})}function nb(t){var e=new FileReader,r=rb(e);return e.readAsArrayBuffer(t),r}function ob(t){if(t.slice)return t.slice(0);var e=new Uint8Array(t.byteLength);return e.set(new Uint8Array(t)),e.buffer}function ib(){return this.bodyUsed=!1,this._initBody=function(t){var e;this._bodyInit=t,t?"string"==typeof t?this._bodyText=t:Km&&Blob.prototype.isPrototypeOf(t)?this._bodyBlob=t:Gm&&FormData.prototype.isPrototypeOf(t)?this._bodyFormData=t:Hm&&URLSearchParams.prototype.isPrototypeOf(t)?this._bodyText=t.toString():Vm&&Km&&(e=t)&&DataView.prototype.isPrototypeOf(e)?(this._bodyArrayBuffer=ob(t.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):Vm&&(ArrayBuffer.prototype.isPrototypeOf(t)||Xm(t))?this._bodyArrayBuffer=ob(t):this._bodyText=t=Object.prototype.toString.call(t):this._bodyText="",this.headers.get("content-type")||("string"==typeof t?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):Hm&&URLSearchParams.prototype.isPrototypeOf(t)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},Km&&(this.blob=function(){var t=eb(this);if(t)return t;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?eb(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(nb)}),this.text=function(){var t=eb(this);if(t)return t;if(this._bodyBlob)return function(t){var e=new FileReader,r=rb(e);return e.readAsText(t),r}(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(function(t){for(var e=new Uint8Array(t),r=new Array(e.length),n=0;n-1?e:t}(e.method||this.method||"GET"),this.mode=e.mode||this.mode||null,this.signal=e.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&r)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(r)}function sb(t){var e=new FormData;return t.trim().split("&").forEach(function(t){if(t){var r=t.split("="),n=r.shift().replace(/\+/g," "),o=r.join("=").replace(/\+/g," ");e.append(decodeURIComponent(n),decodeURIComponent(o))}}),e}function cb(t,e){e||(e={}),this.type="default",this.status=void 0===e.status?200:e.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in e?e.statusText:"OK",this.headers=new tb(e.headers),this.url=e.url||"",this._initBody(t)}ub.prototype.clone=function(){return new ub(this,{body:this._bodyInit})},ib.call(ub.prototype),ib.call(cb.prototype),cb.prototype.clone=function(){return new cb(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new tb(this.headers),url:this.url})},cb.error=function(){var t=new cb(null,{status:0,statusText:""});return t.type="error",t};var fb=[301,302,303,307,308];cb.redirect=function(t,e){if(-1===fb.indexOf(e))throw new RangeError("Invalid status code");return new cb(null,{status:e,headers:{location:t}})};var lb=self.DOMException;try{new lb}catch(t){(lb=function(t,e){this.message=t,this.name=e;var r=Error(t);this.stack=r.stack}).prototype=Object.create(Error.prototype),lb.prototype.constructor=lb}function hb(t,e){return new Promise(function(r,n){var o=new ub(t,e);if(o.signal&&o.signal.aborted)return n(new lb("Aborted","AbortError"));var i=new XMLHttpRequest;function a(){i.abort()}i.onload=function(){var t,e,n={status:i.status,statusText:i.statusText,headers:(t=i.getAllResponseHeaders()||"",e=new tb,t.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach(function(t){var r=t.split(":"),n=r.shift().trim();if(n){var o=r.join(":").trim();e.append(n,o)}}),e)};n.url="responseURL"in i?i.responseURL:n.headers.get("X-Request-URL"),r(new cb("response"in i?i.response:i.responseText,n))},i.onerror=function(){n(new TypeError("Network request failed"))},i.ontimeout=function(){n(new TypeError("Network request failed"))},i.onabort=function(){n(new lb("Aborted","AbortError"))},i.open(o.method,o.url,!0),"include"===o.credentials?i.withCredentials=!0:"omit"===o.credentials&&(i.withCredentials=!1),"responseType"in i&&Km&&(i.responseType="blob"),o.headers.forEach(function(t,e){i.setRequestHeader(e,t)}),o.signal&&(o.signal.addEventListener("abort",a),i.onreadystatechange=function(){4===i.readyState&&o.signal.removeEventListener("abort",a)}),i.send(void 0===o._bodyInit?null:o._bodyInit)})}hb.polyfill=!0,self.fetch||(self.fetch=hb,self.Headers=tb,self.Request=ub,self.Response=cb);var pb=Object.getOwnPropertySymbols,vb=Object.prototype.hasOwnProperty,db=Object.prototype.propertyIsEnumerable,gb=function(){try{if(!Object.assign)return!1;var t=new String("abc");if(t[5]="de","5"===Object.getOwnPropertyNames(t)[0])return!1;for(var e={},r=0;r<10;r++)e["_"+String.fromCharCode(r)]=r;if("0123456789"!==Object.getOwnPropertyNames(e).map(function(t){return e[t]}).join(""))return!1;var n={};return"abcdefghijklmnopqrst".split("").forEach(function(t){n[t]=t}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},n)).join("")}catch(t){return!1}}()?Object.assign:function(t,e){for(var r,n,o=function(t){if(null==t)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(t)}(t),i=1;i{"use strict";var e={},t={};function r(o){var n=t[o];if(void 0!==n)return n.exports;var a=t[o]={exports:{}},i=!0;try{e[o].call(a.exports,a,a.exports,r),i=!1}finally{i&&delete t[o]}return a.exports}r.m=e,(()=>{var e=[];r.O=(t,o,n,a)=>{if(o){a=a||0;for(var i=e.length;i>0&&e[i-1][2]>a;i--)e[i]=e[i-1];e[i]=[o,n,a];return}for(var u=1/0,i=0;i=a)&&Object.keys(r.O).every(e=>r.O[e](o[c]))?o.splice(c--,1):(l=!1,a{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},(()=>{var e,t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;r.t=function(o,n){if(1&n&&(o=this(o)),8&n||"object"==typeof o&&o&&(4&n&&o.__esModule||16&n&&"function"==typeof o.then))return o;var a=Object.create(null);r.r(a);var i={};e=e||[null,t({}),t([]),t(t)];for(var u=2&n&&o;"object"==typeof u&&!~e.indexOf(u);u=t(u))Object.getOwnPropertyNames(u).forEach(e=>i[e]=()=>o[e]);return i.default=()=>o,r.d(a,i),a}})(),r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,o)=>(r.f[o](e,t),t),[])),r.u=e=>{},r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={},t="_N_E:";r.l=(o,n,a,i)=>{if(e[o])return void e[o].push(n);if(void 0!==a)for(var u,l,c=document.getElementsByTagName("script"),d=0;d{u.onerror=u.onload=null,clearTimeout(p);var n=e[o];if(delete e[o],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(e=>e(r)),t)return t(r)},p=setTimeout(f.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=f.bind(null,u.onerror),u.onload=f.bind(null,u.onload),l&&document.head.appendChild(u)}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:e=>e},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("nextjs#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="/_next/",(()=>{var e={68:0,838:0};r.f.j=(t,o)=>{var n=r.o(e,t)?e[t]:void 0;if(0!==n)if(n)o.push(n[2]);else if(/^(6|83)8$/.test(t))e[t]=0;else{var a=new Promise((r,o)=>n=e[t]=[r,o]);o.push(n[2]=a);var i=r.p+r.u(t),u=Error();r.l(i,o=>{if(r.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var a=o&&("load"===o.type?"missing":o.type),i=o&&o.target&&o.target.src;u.message="Loading chunk "+t+" failed.\n("+a+": "+i+")",u.name="ChunkLoadError",u.type=a,u.request=i,n[1](u)}},"chunk-"+t,t)}},r.O.j=t=>0===e[t];var t=(t,o)=>{var n,a,[i,u,l]=o,c=0;if(i.some(t=>0!==e[t])){for(n in u)r.o(u,n)&&(r.m[n]=u[n]);if(l)var d=l(r)}for(t&&t(o);c:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.self-start{align-self:flex-start}.justify-self-end{justify-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[2px\]{border-radius:2px}.rounded-full{border-radius:3.40282e+38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-\[1\.5px\]{border-style:var(--tw-border-style);border-width:1.5px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-gray-200{border-color:var(--color-gray-200)}.border-green-200{border-color:var(--color-green-200)}.border-orange-200{border-color:var(--color-orange-200)}.border-orange-300{border-color:var(--color-orange-300)}.border-red-200{border-color:var(--color-red-200)}.border-transparent{border-color:#0000}.border-yellow-200{border-color:var(--color-yellow-200)}.border-l-green-500{border-left-color:var(--color-green-500)}.border-l-red-500{border-left-color:var(--color-red-500)}.border-l-yellow-500{border-left-color:var(--color-yellow-500)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-green-100{background-color:var(--color-green-100)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-orange-200\/60{background-color:#ffd7a899}@supports (color:color-mix(in lab,red,red)){.bg-orange-200\/60{background-color:color-mix(in oklab,var(--color-orange-200)60%,transparent)}}.bg-orange-600{background-color:var(--color-orange-600)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-600{background-color:var(--color-red-600)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-500{background-color:var(--color-yellow-500)}.fill-current{fill:currentColor}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-12{padding:calc(var(--spacing)*12)}.p-\[3px\]{padding:3px}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-6{padding-block:calc(var(--spacing)*6)}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-8{padding-left:calc(var(--spacing)*8)}.text-center{text-align:center}.text-left{text-align:left}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.text-black{color:var(--color-black)}.text-blue-600{color:var(--color-blue-600)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-orange-400{color:var(--color-orange-400)}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-orange-700{color:var(--color-orange-700)}.text-orange-800{color:var(--color-orange-800)}.text-orange-900{color:var(--color-orange-900)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.capitalize{text-transform:capitalize}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.underline-offset-4{text-underline-offset:4px}.opacity-50{opacity:.5}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.shadow,.shadow-lg{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a)}.shadow-md,.shadow-none{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.shadow-sm,.shadow-xl{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,box-shadow\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.running{animation-play-state:running}.group-data-\[disabled\=true\]\:pointer-events-none:is(:where(.group)[data-disabled=true] *){pointer-events:none}.group-data-\[disabled\=true\]\:opacity-50:is(:where(.group)[data-disabled=true] *){opacity:.5}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.first\:rounded-l-md:first-child{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.last\:rounded-r-md:last-child{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}@media (hover:hover){.hover\:border-orange-300:hover{border-color:var(--color-orange-300)}.hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.hover\:bg-orange-100:hover{background-color:var(--color-orange-100)}.hover\:bg-orange-100\/50:hover{background-color:#ffedd580}@supports (color:color-mix(in lab,red,red)){.hover\:bg-orange-100\/50:hover{background-color:color-mix(in oklab,var(--color-orange-100)50%,transparent)}}.hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-transparent:hover{background-color:#0000}.hover\:text-orange-700:hover{color:var(--color-orange-700)}.hover\:text-orange-900:hover{color:var(--color-orange-900)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:z-10:focus{z-index:10}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:z-10:focus-visible{z-index:10}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:outline-1:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.has-data-\[slot\=card-action\]\:grid-cols-\[1fr_auto\]:has([data-slot=card-action]){grid-template-columns:1fr auto}.has-\[\>svg\]\:px-2\.5:has(>svg){padding-inline:calc(var(--spacing)*2.5)}.has-\[\>svg\]\:px-3:has(>svg){padding-inline:calc(var(--spacing)*3)}.has-\[\>svg\]\:px-4:has(>svg){padding-inline:calc(var(--spacing)*4)}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[dragging\=true\]\:z-10[data-dragging=true]{z-index:10}.data-\[dragging\=true\]\:opacity-80[data-dragging=true]{opacity:.8}.data-\[inset\]\:pl-8[data-inset]{padding-left:calc(var(--spacing)*8)}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.data-\[size\=default\]\:h-9[data-size=default]{height:calc(var(--spacing)*9)}.data-\[size\=sm\]\:h-8[data-size=sm]{height:calc(var(--spacing)*8)}:is(.\*\:data-\[slot\=select-value\]\:line-clamp-1>*)[data-slot=select-value]{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.\*\:data-\[slot\=select-value\]\:flex>*)[data-slot=select-value]{display:flex}:is(.\*\:data-\[slot\=select-value\]\:items-center>*)[data-slot=select-value]{align-items:center}:is(.\*\:data-\[slot\=select-value\]\:gap-2>*)[data-slot=select-value]{gap:calc(var(--spacing)*2)}:is(.\*\*\:data-\[slot\=table-cell\]\:first\:w-8 *)[data-slot=table-cell]:first-child{width:calc(var(--spacing)*8)}.data-\[state\=active\]\:border-orange-300[data-state=active]{border-color:var(--color-orange-300)}.data-\[state\=active\]\:bg-orange-50[data-state=active]{background-color:var(--color-orange-50)}.data-\[state\=active\]\:bg-orange-100[data-state=active]{background-color:var(--color-orange-100)}.data-\[state\=active\]\:text-orange-900[data-state=active]{color:var(--color-orange-900)}.data-\[state\=active\]\:shadow-sm[data-state=active]{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=open\]\:animate-in[data-state=open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[variant\=outline\]\:border-l-0[data-variant=outline]{border-left-style:var(--tw-border-style);border-left-width:0}.data-\[variant\=outline\]\:shadow-xs[data-variant=outline]{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[variant\=outline\]\:first\:border-l[data-variant=outline]:first-child{border-left-style:var(--tw-border-style);border-left-width:1px}@media (min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:pt-6{padding-top:calc(var(--spacing)*6)}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:gap-6{gap:calc(var(--spacing)*6)}}@media (min-width:64rem){.lg\:ml-0{margin-left:calc(var(--spacing)*0)}.lg\:flex{display:flex}.lg\:w-fit{width:fit-content}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:80rem){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}@container card (min-width:540px){.\@\[540px\]\/card\:block{display:block}.\@\[540px\]\/card\:hidden{display:none}}.\[\&_\.recharts-dot\[stroke\=\'\#fff\'\]\]\:stroke-transparent .recharts-dot[stroke=\#fff]{stroke:#0000}.\[\&_\.recharts-layer\]\:outline-hidden .recharts-layer{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.\[\&_\.recharts-layer\]\:outline-hidden .recharts-layer{outline-offset:2px;outline:2px solid #0000}}.\[\&_\.recharts-sector\]\:outline-hidden .recharts-sector{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.\[\&_\.recharts-sector\]\:outline-hidden .recharts-sector{outline-offset:2px;outline:2px solid #0000}}.\[\&_\.recharts-sector\[stroke\=\'\#fff\'\]\]\:stroke-transparent .recharts-sector[stroke=\#fff]{stroke:#0000}.\[\&_\.recharts-surface\]\:outline-hidden .recharts-surface{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.\[\&_\.recharts-surface\]\:outline-hidden .recharts-surface{outline-offset:2px;outline:2px solid #0000}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_tr\]\:border-b tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-style:var(--tw-border-style);border-width:0}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:calc(var(--spacing)*0)}.\[\.border-b\]\:pb-6.border-b{padding-bottom:calc(var(--spacing)*6)}.\[\.border-t\]\:pt-6.border-t{padding-top:calc(var(--spacing)*6)}:is(.\*\:\[span\]\:last\:flex>*):is(span):last-child{display:flex}:is(.\*\:\[span\]\:last\:items-center>*):is(span):last-child{align-items:center}:is(.\*\:\[span\]\:last\:gap-2>*):is(span):last-child{gap:calc(var(--spacing)*2)}.\[\&\>\[role\=checkbox\]\]\:translate-y-\[2px\]>[role=checkbox]{--tw-translate-y:2px;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&\>svg\]\:h-2\.5>svg{height:calc(var(--spacing)*2.5)}.\[\&\>svg\]\:h-3>svg{height:calc(var(--spacing)*3)}.\[\&\>svg\]\:w-2\.5>svg{width:calc(var(--spacing)*2.5)}.\[\&\>svg\]\:w-3>svg{width:calc(var(--spacing)*3)}.\[\&\>tr\]\:last\:border-b-0>tr:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--radius:.625rem;--color-background:var(--background);--color-foreground:var(--foreground);--color-sidebar-ring:var(--sidebar-ring);--color-sidebar-border:var(--sidebar-border);--color-sidebar-accent-foreground:var(--sidebar-accent-foreground);--color-sidebar-accent:var(--sidebar-accent);--color-sidebar-primary-foreground:var(--sidebar-primary-foreground);--color-sidebar-primary:var(--sidebar-primary);--color-sidebar-foreground:var(--sidebar-foreground);--color-sidebar:var(--sidebar);--color-chart-5:var(--chart-5);--color-chart-4:var(--chart-4);--color-chart-3:var(--chart-3);--color-chart-2:var(--chart-2);--color-chart-1:var(--chart-1);--color-ring:var(--ring);--color-input:var(--input);--color-border:var(--border);--color-destructive:var(--destructive);--color-accent-foreground:var(--accent-foreground);--color-accent:var(--accent);--color-muted-foreground:var(--muted-foreground);--color-muted:var(--muted);--color-secondary-foreground:var(--secondary-foreground);--color-secondary:var(--secondary);--color-primary-foreground:var(--primary-foreground);--color-primary:var(--primary);--color-popover-foreground:var(--popover-foreground);--color-popover:var(--popover);--color-card-foreground:var(--card-foreground);--color-card:var(--card);--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);--card:oklch(99.5% .01 25);--card-foreground:oklch(15% .01 25);--popover:oklch(99.5% .01 25);--popover-foreground:oklch(15% .01 25);--primary:oklch(65% .21 25);--primary-foreground:oklch(100% 0 0);--secondary:oklch(97% .015 28);--secondary-foreground:oklch(20% .02 25);--muted:oklch(96% .015 30);--muted-foreground:oklch(45% .02 25);--accent:oklch(94% .02 28);--accent-foreground:oklch(20% .02 25);--destructive:oklch(64% .21 25.33);--border:oklch(90% .015 28);--input:oklch(97% .015 28);--ring:oklch(65% .21 25);--chart-1:oklch(70% .21 35);--chart-2:oklch(77% .09 34.19);--chart-3:oklch(58% .08 254.16);--chart-4:oklch(50% .08 259.49);--chart-5:oklch(42% .1 264.03);--sidebar:oklch(18% .04 35);--sidebar-foreground:oklch(15% .01 25);--sidebar-primary:oklch(65% .21 25);--sidebar-primary-foreground:oklch(100% 0 0);--sidebar-accent:oklch(95% .025 30);--sidebar-accent-foreground:oklch(20% .02 25);--sidebar-border:oklch(92% .02 28);--sidebar-ring:oklch(65% .21 25);--background:oklch(99% .02 25);--foreground:oklch(15% .01 25)}.dark{--background:oklch(15% .04 35);--foreground:oklch(95% .01 35);--card:oklch(18% .03 35);--card-foreground:oklch(92% 0 0);--popover:oklch(16% .03 35);--popover-foreground:oklch(92% 0 0);--primary:oklch(70% .21 35);--primary-foreground:oklch(100% 0 0);--secondary:oklch(20% .04 35);--secondary-foreground:oklch(92% 0 0);--muted:oklch(20% .04 35);--muted-foreground:oklch(72% 0 0);--accent:oklch(25% .06 35);--accent-foreground:oklch(88% .06 254.13);--destructive:oklch(64% .21 25.33);--border:oklch(25% .04 35);--input:oklch(25% .04 35);--ring:oklch(70% .21 35);--chart-1:oklch(70% .21 35);--chart-2:oklch(77% .09 34.19);--chart-3:oklch(58% .08 254.16);--chart-4:oklch(50% .08 259.49);--chart-5:oklch(42% .1 264.03);--sidebar:oklch(18% .04 35);--sidebar-foreground:oklch(92% 0 0);--sidebar-primary:oklch(70% .21 35);--sidebar-primary-foreground:oklch(100% 0 0);--sidebar-accent:oklch(25% .06 35);--sidebar-accent-foreground:oklch(88% .06 254.13);--sidebar-border:oklch(25% .04 35);--sidebar-ring:oklch(70% .21 35)}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-duration{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0))}} \ No newline at end of file diff --git a/keploy/pkg/service/load/out/_next/static/media/26a46d62cd723877-s.woff2 b/keploy/pkg/service/load/out/_next/static/media/26a46d62cd723877-s.woff2 deleted file mode 100644 index 3a27e632eb14fffc6fce03483049986ba13f6802..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18820 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

uw6FG`p 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 diff --git a/keploy/pkg/service/load/out/_next/static/media/e4af272ccee01ff0-s.p.woff2 b/keploy/pkg/service/load/out/_next/static/media/e4af272ccee01ff0-s.p.woff2 deleted file mode 100644 index 91dc3e8529921cabc74fb1dacf5253260b9c8cca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48432 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 Tp~gD%=1~4;zp%;UV+#NP+I>lo diff --git a/keploy/pkg/service/load/out/favicon.ico b/keploy/pkg/service/load/out/favicon.ico deleted file mode 100644 index 30bd11d1eb8219ac5610f55be65714d1dcc645f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243774 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 diff --git a/keploy/pkg/service/load/out/index.html b/keploy/pkg/service/load/out/index.html deleted file mode 100644 index 95ad288..0000000 --- a/keploy/pkg/service/load/out/index.html +++ /dev/null @@ -1 +0,0 @@ -KLT Dashboard

\ No newline at end of file diff --git a/keploy/pkg/service/load/out/index.txt b/keploy/pkg/service/load/out/index.txt deleted file mode 100644 index 6d3f186..0000000 --- a/keploy/pkg/service/load/out/index.txt +++ /dev/null @@ -1,22 +0,0 @@ -1:"$Sreact.fragment" -2:I[2597,["177","static/chunks/app/layout-bc503d5738af696e.js"],"default"] -3:I[3313,["177","static/chunks/app/layout-bc503d5738af696e.js"],"default"] -4:I[7555,[],""] -5:I[1295,[],""] -6:I[894,[],"ClientPageRoot"] -7:I[7704,["547","static/chunks/547-586c3cf76649ec2c.js","974","static/chunks/app/page-a3cd1a73fe99bbf3.js"],"default"] -a:I[9665,[],"OutletBoundary"] -c:I[4911,[],"AsyncMetadataOutlet"] -e:I[9665,[],"ViewportBoundary"] -10:I[9665,[],"MetadataBoundary"] -11:"$Sreact.suspense" -13:I[8393,[],""] -:HL["/_next/static/media/e4af272ccee01ff0-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}] -:HL["/_next/static/css/bc1768f92951bee0.css","style"] -0:{"P":null,"b":"S6A95ZgpgxS8RHL4D2qnv","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/bc1768f92951bee0.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":[["$","head",null,{"children":[["$","link",null,{"rel":"icon","href":"/favicon.ico"}],["$","link",null,{"rel":"manifest","href":"/manifest.json"}],["$","meta",null,{"name":"theme-color","content":"#000000"}],["$","meta",null,{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","body",null,{"className":"__className_e8ce0c","children":[["$","$L2",null,{}],["$","$L3",null,{}],["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]]}]]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L6",null,{"Component":"$7","searchParams":{},"params":{},"promises":["$@8","$@9"]}],null,["$","$La",null,{"children":["$Lb",["$","$Lc",null,{"promise":"$@d"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Le",null,{"children":"$Lf"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L10",null,{"children":["$","div",null,{"hidden":true,"children":["$","$11",null,{"fallback":null,"children":"$L12"}]}]}]]}],false]],"m":"$undefined","G":["$13",[]],"s":false,"S":true} -8:{} -9:"$0:f:0:1:2:children:1:props:children:0:props:params" -f:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] -b:null -d:{"metadata":[["$","title","0",{"children":"KLT Dashboard"}],["$","meta","1",{"name":"description","content":"Keploy's Load Testing Dashboard"}]],"error":null,"digest":"$undefined"} -12:"$d:metadata" diff --git a/keploy/pkg/service/load/out/manifest.json b/keploy/pkg/service/load/out/manifest.json deleted file mode 100644 index 3671e62..0000000 --- a/keploy/pkg/service/load/out/manifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "KLT Dashboard", - "short_name": "KLT Dashboard", - "description": "Keploy's Load Testing Dashboard", - "start_url": "/", - "display": "standalone", - "background_color": "#ffffff", - "theme_color": "#000000", - "orientation": "portrait-primary", - "icons": [ - { - "src": "/favicon.ico", - "sizes": "any", - "type": "image/x-icon" - } - ] -} diff --git a/keploy/pkg/service/load/out/sw.js b/keploy/pkg/service/load/out/sw.js deleted file mode 100644 index eb1f49b..0000000 --- a/keploy/pkg/service/load/out/sw.js +++ /dev/null @@ -1,188 +0,0 @@ -// Service Worker for offline caching -const CACHE_NAME = 'klt-dashboard-v4'; -const STATIC_CACHE_URLS = [ - '/', - '/index.html', - '/favicon.ico', - '/manifest.json' -]; - -// Install event - cache static resources -self.addEventListener('install', (event) => { - console.log('Service Worker installing...'); - event.waitUntil( - caches.open(CACHE_NAME) - .then((cache) => { - console.log('Caching static resources'); - // Try to cache the basic URLs, but don't fail if some are missing - return Promise.all( - STATIC_CACHE_URLS.map(url => { - return cache.add(url).catch(err => { - console.warn('Failed to cache:', url, err); - }); - }) - ); - }) - .then(() => { - console.log('Service Worker installation complete'); - // Force the waiting service worker to become the active service worker - return self.skipWaiting(); - }) - .catch(err => { - console.error('Service Worker installation failed:', err); - }) - ); -}); - -// Activate event - clean up old caches -self.addEventListener('activate', (event) => { - console.log('Service Worker activating...'); - event.waitUntil( - caches.keys() - .then((cacheNames) => { - return Promise.all( - cacheNames.map((cacheName) => { - if (cacheName !== CACHE_NAME) { - console.log('Deleting old cache:', cacheName); - return caches.delete(cacheName); - } - }) - ); - }) - .then(() => { - console.log('Service Worker activation complete'); - // Ensure the new service worker takes control immediately - return self.clients.claim(); - }) - ); -}); - -// Fetch event - Selective caching to avoid interfering with app functionality -self.addEventListener('fetch', (event) => { - // Skip cross-origin requests - if (!event.request.url.startsWith(self.location.origin)) { - return; - } - - // Skip chrome-extension and other non-http requests - if (!event.request.url.startsWith('http')) { - return; - } - - // Skip requests that shouldn't be cached (to avoid interfering with app logic) - const url = new URL(event.request.url); - - // Skip API calls, WebSocket connections, and other dynamic requests - if ( - url.pathname.startsWith('/metrics/') || - event.request.headers.get('cache-control') === 'no-cache' || - event.request.headers.get('cache-control') === 'no-store' - ) { - console.log('🚫 Skipping service worker for:', event.request.url); - return; // Let the browser handle these normally - } - - // Only cache static assets and navigation requests - if (shouldCacheRequest(event.request)) { - event.respondWith(networkFirstStrategy(event.request)); - } -}); - -// Determine if a request should be cached -function shouldCacheRequest(request) { - const url = new URL(request.url); - - // Cache navigation requests (HTML pages) - if (request.destination === 'document') { - return true; - } - - // Cache static assets - if ( - request.destination === 'script' || - request.destination === 'style' || - request.destination === 'image' || - request.destination === 'font' || - url.pathname.includes('/_next/static/') || - url.pathname.endsWith('.css') || - url.pathname.endsWith('.js') || - url.pathname.endsWith('.ico') || - url.pathname.endsWith('.png') || - url.pathname.endsWith('.jpg') || - url.pathname.endsWith('.svg') || - url.pathname.endsWith('.woff') || - url.pathname.endsWith('.woff2') - ) { - return true; - } - - // Cache manifest and service worker files - if ( - url.pathname === '/manifest.json' || - url.pathname === '/sw.js' - ) { - return true; - } - - return false; -} - -// Network first strategy - Always try network first, fallback to cache only when server is down -async function networkFirstStrategy(request) { - try { - const response = await fetch(request); - - if (response && response.status === 200) { - // Only cache successful responses for static content - if (shouldCacheResponse(request, response)) { - const cache = await caches.open(CACHE_NAME); - cache.put(request, response.clone()); - console.log('📦 Cached fresh content:', request.url); - } - - return response; - } else { - // Bad response, try cache - const cachedResponse = await caches.match(request); - return cachedResponse || response; - } - } catch (error) { - // Network failed (server down), use cache - const cachedResponse = await caches.match(request); - - if (cachedResponse) { - console.log('📱 Serving offline content:', request.url); - return cachedResponse; - } - - // If it's a navigation request and no cache, try to serve the main page - if (request.destination === 'document') { - const mainPage = await caches.match('/'); - if (mainPage) { - console.log('🏠 Serving main page as fallback'); - return mainPage; - } - } - - throw error; - } -} - -// Determine if a response should be cached -function shouldCacheResponse(request, response) { - // Don't cache if response has cache-control: no-store - const cacheControl = response.headers.get('cache-control'); - if (cacheControl && cacheControl.includes('no-store')) { - return false; - } - - // Cache static assets and navigation requests - return shouldCacheRequest(request); -} - -// Listen for messages from the main thread -self.addEventListener('message', (event) => { - if (event.data && event.data.type === 'SKIP_WAITING') { - self.skipWaiting(); - } -}); diff --git a/keploy/pkg/service/load/scheduler.go b/keploy/pkg/service/load/scheduler.go deleted file mode 100644 index 8db9053..0000000 --- a/keploy/pkg/service/load/scheduler.go +++ /dev/null @@ -1,138 +0,0 @@ -package load - -import ( - "context" - "fmt" - "sync" - "time" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/service/testsuite" - "go.uber.org/zap" - "golang.org/x/time/rate" -) - -type Scheduler struct { - config *config.Config - logger *zap.Logger - loadOptions *testsuite.LoadOptions - ts *testsuite.TestSuite - collector *MetricsCollector - limiter *rate.Limiter - cancelAll context.CancelFunc - wg sync.WaitGroup - vuCownter int -} - -func NewScheduler(logger *zap.Logger, config *config.Config, loadOptions *testsuite.LoadOptions, ts *testsuite.TestSuite, collector *MetricsCollector) *Scheduler { - // setting the rate limiter based on the RPS specified in loadOptions will be passed later to the TSExecutor execute function. - var lim *rate.Limiter - if loadOptions.RPS > 0 { - lim = rate.NewLimiter(rate.Limit(loadOptions.RPS), loadOptions.RPS) - } - - return &Scheduler{ - loadOptions: loadOptions, - ts: ts, - collector: collector, - limiter: lim, - logger: logger, - config: config, - vuCownter: 0, - } -} - -func (s *Scheduler) Run(parent context.Context, exporter *Exporter, dashboardExposer *DashboardExposer) error { - // setting the context with a timeout based on the duration specified in loadOptions. - // will be given to the VUWorker goroutines along with the waitgroup to synchronize. - duration, err := time.ParseDuration(s.loadOptions.Duration) - if err != nil { - s.logger.Error("Failed to parse duration", zap.String("duration", s.loadOptions.Duration), zap.Error(err)) - return err - } - ctx, cancel := context.WithTimeout(parent, duration) - s.cancelAll = cancel - defer cancel() - - // check if the loadOptions has a valid profile set, if not return an error. - switch s.loadOptions.Profile { - case "constant_vus": - return s.runConstant(ctx, s.ts, exporter, dashboardExposer) - case "ramping_vus": - return s.runRamping(ctx, s.ts, exporter, dashboardExposer) - default: - return fmt.Errorf("unknown load profile %q", s.loadOptions.Profile) - } -} - -func (s *Scheduler) runConstant(ctx context.Context, ts *testsuite.TestSuite, exporter *Exporter, dashboardExposer *DashboardExposer) error { - exporter.ltToken.CreatedAt = time.Now() - exporter.StartServer(ctx) - dashboardExposer.Expose(ctx) - err := s.spawnVUGoroutines(ctx, ts, s.loadOptions.VUs, exporter) - if err != nil { - s.logger.Error("Failed to spawn VU goroutines", zap.Int("vus", s.loadOptions.VUs), zap.Error(err)) - return err - } - - // if context is done it waits for all VU goroutines to finish reporting back to the MetricCollector. - <-ctx.Done() - s.wg.Wait() - - return nil -} - -func (s *Scheduler) runRamping(ctx context.Context, ts *testsuite.TestSuite, exporter *Exporter, dashboardExposer *DashboardExposer) error { - exporter.ltToken.CreatedAt = time.Now() - exporter.StartServer(ctx) - dashboardExposer.Expose(ctx) - start := time.Now() - current := 0 - cumulative := start - for _, stg := range s.loadOptions.Stages { - // spawning VU goroutines based on the target specified in the stage. - target := stg.Target - delta := target - current - if delta > 0 { - if err := s.spawnVUGoroutines(ctx, ts, delta, exporter); err != nil { - return err - } - } - - stageDuration, err := time.ParseDuration(stg.Duration) - if err != nil { - return err - } - cumulative = cumulative.Add(stageDuration) - select { - case <-ctx.Done(): - s.wg.Wait() - return nil - case <-time.After(time.Until(cumulative)): - } - current = target - } - - // if context is done it waits for all VU goroutines to finish reporting back to the MetricCollector. - - <-ctx.Done() - s.wg.Wait() - - return nil -} - -func (s *Scheduler) spawnVUGoroutines(ctx context.Context, ts *testsuite.TestSuite, n int, exporter *Exporter) error { - startID := s.vuCownter - endID := s.vuCownter + n - for id := startID; id < endID; id++ { - s.wg.Add(1) - // spawning VUWorker goroutines with the context, test suite, metrics collector, rate limiter and waitgroup. - // the VUWorker will execute the test suite steps and report the results back to the MetricsCollector. - go func(id int) { - vuWorker := NewVUWorker(s.config, s.logger, id, ts, s.collector, s.limiter, &s.wg, exporter) - vuWorker.vuWorker(ctx) - }(id) - } - s.vuCownter += n - return nil -} diff --git a/keploy/pkg/service/load/service.go b/keploy/pkg/service/load/service.go deleted file mode 100644 index d8dd2d3..0000000 --- a/keploy/pkg/service/load/service.go +++ /dev/null @@ -1,9 +0,0 @@ -package load - -import ( - "context" -) - -type Service interface { - Start(ctx context.Context) error -} diff --git a/keploy/pkg/service/load/threshold_evaluator.go b/keploy/pkg/service/load/threshold_evaluator.go deleted file mode 100644 index 72f2511..0000000 --- a/keploy/pkg/service/load/threshold_evaluator.go +++ /dev/null @@ -1,299 +0,0 @@ -package load - -import ( - "fmt" - "math" - "sort" - "strings" - "time" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/service/testsuite" - "go.uber.org/zap" -) - -type ThresholdReport struct { - Metric string `json:"metric"` - Condition string `json:"condition"` - Severity string `json:"severity"` - Comment string `json:"comment,omitempty"` - Actual interface{} `json:"actual"` - Pass bool `json:"pass"` -} - -type StepThresholdReport struct { - StepName string `json:"step_name"` - TotalRequests int `json:"total_requests"` - TotalFailures int `json:"total_failures"` - TotalBytesIn int64 `json:"total_bytes_in"` - TotalBytesOut int64 `json:"total_bytes_out"` - P95Latency time.Duration `json:"p95_latency"` - Thresholds []ThresholdReport `json:"thresholds"` -} - -type ThresholdEvaluator struct { - config *config.Config - logger *zap.Logger - ts *testsuite.TestSuite -} - -func NewThresholdEvaluator(cfg *config.Config, logger *zap.Logger, ts *testsuite.TestSuite) *ThresholdEvaluator { - return &ThresholdEvaluator{ - config: cfg, - logger: logger, - ts: ts, - } -} - -func (te *ThresholdEvaluator) Evaluate(steps []StepMetrics) []StepThresholdReport { - thresholds := te.ts.Spec.Load.Thresholds - if len(thresholds) == 0 { - te.logger.Info("No thresholds defined in TestSuite, skipping evaluation") - return nil - } - - var reports []StepThresholdReport - - for _, step := range steps { - var allResponseTimes = step.StepResponseTime - totalRequests := step.StepCount - totalFailures := step.StepFailure - totalBytesIn := step.StepBytesIn - totalBytesOut := step.StepBytesOut - - // Calculate P95 latency - // getting the index and value of 95th percentile from the response times. - var p95Idx int - var p95 time.Duration - if len(allResponseTimes) > 0 { - sort.Slice(allResponseTimes, func(i, j int) bool { return allResponseTimes[i] < allResponseTimes[j] }) - idx := int(math.Ceil(float64(len(allResponseTimes))*0.95)) - 1 - if idx < 0 { - idx = 0 - } - p95Idx = idx - p95 = allResponseTimes[p95Idx] - } - - // Calculate failed rate as a percentage - // If totalRequests is 0, we avoid division by zero by setting failedRate to 0. - var failedRate float64 - if totalRequests > 0 { - failedRate = (float64(totalFailures) / float64(totalRequests)) * 100 - } - - // Convert bytes to MB for reporting - // 1 MB = 1024 * 1024 bytes - dataReceivedMB := float64(totalBytesIn) / (1024 * 1024) - dataSentMB := float64(totalBytesOut) / (1024 * 1024) - - var stepReport StepThresholdReport - stepReport.StepName = step.StepName - stepReport.TotalRequests = totalRequests - stepReport.TotalFailures = totalFailures - stepReport.TotalBytesIn = totalBytesIn - stepReport.TotalBytesOut = totalBytesOut - stepReport.P95Latency = p95 - stepReport.Thresholds = make([]ThresholdReport, 0, len(thresholds)) - - for _, th := range thresholds { - switch th.Metric { - case "http_req_duration_p95": - pass := compareDuration(p95, th.Condition) - te.logger.Debug("Threshold check", - zap.String("step", step.StepName), - zap.String("metric", th.Metric), - zap.String("condition", th.Condition), - zap.String("actual", p95.String()), - zap.Bool("pass", pass), - zap.String("severity", th.Severity), - zap.String("comment", th.Comment), - ) - if !pass { - te.logger.Debug(fmt.Sprintf("Threshold failed: %s %s (actual: %s) for step %s", th.Metric, th.Condition, p95, step.StepName)) - } - stepReport.Thresholds = append(stepReport.Thresholds, ThresholdReport{ - Metric: th.Metric, - Condition: th.Condition, - Actual: p95.String(), - Pass: pass, - Severity: th.Severity, - Comment: th.Comment, - }) - case "http_req_failed_rate": - pass := compareFloat(failedRate, th.Condition) - te.logger.Debug("Threshold check", - zap.String("step", step.StepName), - zap.String("metric", th.Metric), - zap.String("condition", th.Condition), - zap.Float64("actual", failedRate), - zap.Bool("pass", pass), - zap.String("severity", th.Severity), - zap.String("comment", th.Comment), - ) - if !pass { - te.logger.Debug(fmt.Sprintf("Threshold failed: %s %s (actual: %.2f%%) for step %s", th.Metric, th.Condition, failedRate, step.StepName)) - } - stepReport.Thresholds = append(stepReport.Thresholds, ThresholdReport{ - Metric: th.Metric, - Condition: th.Condition, - Actual: failedRate, - Pass: pass, - Severity: th.Severity, - Comment: th.Comment, - }) - case "data_received": - pass := compareFloat(dataReceivedMB, th.Condition) - te.logger.Debug("Threshold check", - zap.String("step", step.StepName), - zap.String("metric", th.Metric), - zap.String("condition", th.Condition), - zap.Float64("actual", dataReceivedMB), - zap.Bool("pass", pass), - zap.String("severity", th.Severity), - zap.String("comment", th.Comment), - ) - if !pass { - te.logger.Debug(fmt.Sprintf("Threshold failed: %s %s (actual: %.2f MB) for step %s", th.Metric, th.Condition, dataReceivedMB, step.StepName)) - } - stepReport.Thresholds = append(stepReport.Thresholds, ThresholdReport{ - Metric: th.Metric, - Condition: th.Condition, - Actual: dataReceivedMB, - Pass: pass, - Severity: th.Severity, - Comment: th.Comment, - }) - case "data_sent": - pass := compareFloat(dataSentMB, th.Condition) - te.logger.Debug("Threshold check", - zap.String("step", step.StepName), - zap.String("metric", th.Metric), - zap.String("condition", th.Condition), - zap.Float64("actual", dataSentMB), - zap.Bool("pass", pass), - zap.String("severity", th.Severity), - zap.String("comment", th.Comment), - ) - if !pass { - te.logger.Debug(fmt.Sprintf("Threshold failed: %s %s (actual: %.2f MB) for step %s", th.Metric, th.Condition, dataSentMB, step.StepName)) - } - stepReport.Thresholds = append(stepReport.Thresholds, ThresholdReport{ - Metric: th.Metric, - Condition: th.Condition, - Actual: dataSentMB, - Pass: pass, - Severity: th.Severity, - Comment: th.Comment, - }) - default: - te.logger.Warn("Unknown threshold metric", zap.String("metric", th.Metric)) - } - } - reports = append(reports, stepReport) - } - return reports -} - -// compareDuration compares a time.Duration value with a condition string. -// The condition string can be in the format of "<", "<=", ">", ">=", "=", -// and can include a duration value like "500ms", "1s", etc. -// Returns true if the condition is met, false otherwise. -func compareDuration(val time.Duration, cond string) bool { - cond = strings.TrimSpace(cond) - if cond == "" { - return true - } - // seperate the given string into operator and value - // e.g. "<= 500ms" -> op = "<=", cmpStr = "500ms" - var op string - var cmpStr string - if strings.HasPrefix(cond, "<=") { - op = "<=" - cmpStr = strings.TrimSpace(cond[2:]) - } else if strings.HasPrefix(cond, "<") { - op = "<" - cmpStr = strings.TrimSpace(cond[1:]) - } else if strings.HasPrefix(cond, ">=") { - op = ">=" - cmpStr = strings.TrimSpace(cond[2:]) - } else if strings.HasPrefix(cond, ">") { - op = ">" - cmpStr = strings.TrimSpace(cond[1:]) - } else if strings.HasPrefix(cond, "=") { - op = "=" - cmpStr = strings.TrimSpace(cond[1:]) - } else { - return false - } - cmpDur, err := time.ParseDuration(cmpStr) - if err != nil { - return false - } - switch op { - case "<": - return val < cmpDur - case "<=": - return val <= cmpDur - case ">": - return val > cmpDur - case ">=": - return val >= cmpDur - case "=": - return val == cmpDur - } - return false -} - -// compareFloat compares a float64 value with a condition string. -// The condition string can be in the format of "<", "<=", ">", ">=", "=", -// and can include a value like "50%", "100MB", etc. -// Returns true if the condition is met, false otherwise. -func compareFloat(val float64, cond string) bool { - cond = strings.TrimSpace(cond) - if cond == "" { - return true - } - // separate the given string into operator and value - // e.g. "<= 50%" -> op = "<=", cmpStr = "50%" - // e.g. "> 100MB" -> op = ">", cmpStr = "100MB" - var op string - var cmpStr string - if strings.HasPrefix(cond, "<=") { - op = "<=" - cmpStr = strings.TrimSpace(cond[2:]) - } else if strings.HasPrefix(cond, "<") { - op = "<" - cmpStr = strings.TrimSpace(cond[1:]) - } else if strings.HasPrefix(cond, ">=") { - op = ">=" - cmpStr = strings.TrimSpace(cond[2:]) - } else if strings.HasPrefix(cond, ">") { - op = ">" - cmpStr = strings.TrimSpace(cond[1:]) - } else if strings.HasPrefix(cond, "=") { - op = "=" - cmpStr = strings.TrimSpace(cond[1:]) - } else { - return false - } - // Remove % or MB if present - cmpStr = strings.TrimSuffix(cmpStr, "%") - cmpStr = strings.TrimSuffix(cmpStr, "MB") - cmpStr = strings.TrimSpace(cmpStr) - cmpVal := 0.0 - fmt.Sscanf(cmpStr, "%f", &cmpVal) - switch op { - case "<": - return val < cmpVal - case "<=": - return val <= cmpVal - case ">": - return val > cmpVal - case ">=": - return val >= cmpVal - case "=": - return val == cmpVal - } - return false -} diff --git a/keploy/pkg/service/load/utils.go b/keploy/pkg/service/load/utils.go deleted file mode 100644 index a4ac056..0000000 --- a/keploy/pkg/service/load/utils.go +++ /dev/null @@ -1 +0,0 @@ -package load diff --git a/keploy/pkg/service/load/vu_worker.go b/keploy/pkg/service/load/vu_worker.go deleted file mode 100644 index cc63ab3..0000000 --- a/keploy/pkg/service/load/vu_worker.go +++ /dev/null @@ -1,118 +0,0 @@ -package load - -import ( - "context" - "sync" - "time" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/service/testsuite" - "go.uber.org/zap" - "golang.org/x/time/rate" -) - -type VUWorker struct { - config *config.Config - logger *zap.Logger - VUID int - ts *testsuite.TestSuite - mc *MetricsCollector - limiter *rate.Limiter - waitG *sync.WaitGroup - exporter *Exporter -} - -type VUReport struct { - VUID int `json:"vu_id"` - TSExecCount int `json:"ts_exec_count"` - TSExecFailure int `json:"ts_exec_failure"` - TSExecTime []time.Duration `json:"ts_exec_time"` - Steps []StepReport `json:"steps"` -} - -type StepReport struct { - StepName string `json:"step_name"` - StepCount int `json:"step_count"` - StepFailure int `json:"step_failure"` - StepResponseTime []time.Duration `json:"step_response_time"` - StepBytesIn int64 `json:"step_bytes_in"` - StepBytesOut int64 `json:"step_bytes_out"` -} - -func NewVUWorker(cfg *config.Config, logger *zap.Logger, id int, ts *testsuite.TestSuite, col *MetricsCollector, lim *rate.Limiter, wg *sync.WaitGroup, exp *Exporter) *VUWorker { - return &VUWorker{ - config: cfg, - logger: logger, - VUID: id, - ts: ts, - mc: col, - limiter: lim, - waitG: wg, - exporter: exp, - } -} - -func (w *VUWorker) vuWorker(ctx context.Context) { - VUReport := VUReport{ - VUID: w.VUID, - TSExecCount: 0, - TSExecFailure: 0, - TSExecTime: make([]time.Duration, 0), - Steps: make([]StepReport, len(w.ts.Spec.Steps)), - } - w.logger.Debug("Running virtual user", zap.Int("vuID", w.VUID)) - - for i, step := range w.ts.Spec.Steps { - VUReport.Steps[i] = StepReport{ - StepName: step.Name, - StepCount: 0, - StepResponseTime: make([]time.Duration, 0), - } - } - - tsExec, err := testsuite.NewTSExecutor(w.config, w.logger, true) - if err != nil { - w.logger.Error("Failed to create TestSuite executor", zap.Int("vuID", w.VUID), zap.Error(err)) - } - - // Set the TestSuite for the executor manually after skipping the parsing. - tsExec.Testsuite = w.ts - - for { - select { - case <-ctx.Done(): - // if the context duration is done, waits for reporting to the MetricsCollector. - w.mc.CollectVUReport(&VUReport) - w.logger.Debug("Virtual user context done", zap.Int("vuID", w.VUID)) - w.waitG.Done() - return - default: - execReport, err := tsExec.Execute(ctx, w.limiter) - w.exporter.mu.Lock() - if err != nil { // an execution failure occurs if any parameters needed is missing like the base-url, not on the step failure. - w.logger.Error("Failed to execute TestSuite", zap.Int("vuID", w.VUID), zap.Error(err)) - VUReport.TSExecCount++ - VUReport.TSExecFailure++ - // TODO: stop or continue - // return - } else { - w.logger.Debug("Virtual user executed TestSuite", zap.Int("vuID", w.VUID)) - VUReport.TSExecCount++ - VUReport.TSExecTime = append(VUReport.TSExecTime, execReport.ExecutionTime) - - // collecting per step results. - for i, step := range execReport.StepsResult { - if step.Status == "failed" { - VUReport.Steps[i].StepFailure++ - } - VUReport.Steps[i].StepCount++ - VUReport.Steps[i].StepResponseTime = append(VUReport.Steps[i].StepResponseTime, step.ResponseTime) - VUReport.Steps[i].StepBytesIn += step.ReqBytes - VUReport.Steps[i].StepBytesOut += step.ResBytes - } - } - w.exporter.mu.Unlock() - w.exporter.GetMetrics(VUReport) - } - } -} diff --git a/keploy/pkg/service/orchestrator/orchestrator.go b/keploy/pkg/service/orchestrator/orchestrator.go deleted file mode 100644 index b130018..0000000 --- a/keploy/pkg/service/orchestrator/orchestrator.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build linux - -// Package orchestrator acts as a main brain for both the record and replay services -package orchestrator - -import ( - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/service/record" - "go.keploy.io/server/v2/pkg/service/replay" - "go.keploy.io/server/v2/pkg/service/tools" - - "go.uber.org/zap" -) - -type Orchestrator struct { - logger *zap.Logger - record record.Service - replay replay.Service - tools tools.Service - config *config.Config -} - -func New(logger *zap.Logger, record record.Service, tools tools.Service, replay replay.Service, config *config.Config) *Orchestrator { - return &Orchestrator{ - logger: logger, - record: record, - replay: replay, - tools: tools, - config: config, - } -} diff --git a/keploy/pkg/service/orchestrator/rerecord.go b/keploy/pkg/service/orchestrator/rerecord.go deleted file mode 100644 index a3ff909..0000000 --- a/keploy/pkg/service/orchestrator/rerecord.go +++ /dev/null @@ -1,334 +0,0 @@ -//go:build linux - -package orchestrator - -import ( - "bufio" - "context" - "fmt" - "os" - "sort" - "strconv" - "time" - - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "golang.org/x/sync/errgroup" -) - -func (o *Orchestrator) ReRecord(ctx context.Context) error { - // creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller - var stopReason string - var err error - - defer func() { - select { - case <-ctx.Done(): - default: - err := utils.Stop(o.logger, stopReason) - if err != nil { - utils.LogError(o.logger, err, "failed to stop recording") - } - } - }() - - // Get all the testsets - testSets, err := o.replay.GetAllTestSetIDs(ctx) - if err != nil { - errMsg := "Failed to get all testset IDs" - utils.LogError(o.logger, err, errMsg) - return err - } - - // Check for templates - o.checkForTemplates(ctx, testSets) - // Sort the testsets to ensure that the testcases are re-recorded in the same order - sort.SliceStable(testSets, func(i, j int) bool { - return testSets[i] < testSets[j] - }) - - var SelectedTests []string - - for _, testSet := range testSets { - if ctx.Err() != nil { - break - } - - if _, ok := o.config.Test.SelectedTests[testSet]; !ok && len(o.config.Test.SelectedTests) != 0 { - continue - } - - SelectedTests = append(SelectedTests, testSet) - - o.logger.Info("Re-recording testcases for the given testset", zap.String("testset", testSet)) - // Note: Here we've used child context without cancel to avoid the cancellation of the parent context. - // When we use errgroup and get an error from any of the go routines spawned by errgroup, it cancels the parent context. - // We don't want to stop the execution if there is an error in any of the test-set recording sessions, it should just skip that test-set and continue with the next one. - errGrp, _ := errgroup.WithContext(ctx) - recordCtx := context.WithoutCancel(ctx) - recordCtx, recordCtxCancel := context.WithCancel(recordCtx) - - var errCh = make(chan error, 1) - var replayErrCh = make(chan error, 1) - - //Keeping two back-to-back selects is used to not do blocking operation if parent ctx is done - - select { - case <-ctx.Done(): - default: - errGrp.Go(func() error { - defer utils.Recover(o.logger) - err := o.record.Start(recordCtx, true) - errCh <- err - return nil - }) - } - - select { - case <-ctx.Done(): - default: - errGrp.Go(func() error { - defer utils.Recover(o.logger) - allRecorded, err := o.replayTests(recordCtx, testSet) - - if allRecorded && err == nil { - o.logger.Info("Re-recorded testcases successfully for the given testset", zap.String("testset", testSet)) - } - if !allRecorded { - o.logger.Warn("Failed to re-record some testcases", zap.String("testset", testSet)) - stopReason = "failed to re-record some testcases" - } - - replayErrCh <- err - return nil - }) - } - - var err error - select { - case err = <-errCh: - if err != nil { - stopReason = "error while starting the recording" - utils.LogError(o.logger, err, stopReason, zap.String("testset", testSet)) - } - case err = <-replayErrCh: - if err != nil { - stopReason = "error while replaying the testcases" - utils.LogError(o.logger, err, stopReason, zap.String("testset", testSet)) - } - case <-ctx.Done(): - } - - if err == nil || ctx.Err() == nil { - // Sleep for 3 seconds to ensure that the recording has completed - time.Sleep(3 * time.Second) - } - - recordCtxCancel() - - // Wait for the recording to stop - err = errGrp.Wait() - if err != nil { - utils.LogError(o.logger, err, "failed to stop re-recording") - } - - // Check if the global context is done after each iteration - if ctx.Err() != nil { - break - } - } - - if stopReason != "" { - utils.LogError(o.logger, err, stopReason) - return fmt.Errorf("%s", stopReason) - } - - if ctx.Err() != nil { - stopReason = "context cancelled" - o.logger.Warn("Re-record was cancelled, keploy might have not recorded few test cases") - return nil - } - - stopReason = "Re-recorded all the selected testsets successfully" - if !o.config.InCi { - o.logger.Info("Re-record was successfull. Do you want to remove the older testsets? (y/n)", zap.Any("testsets", SelectedTests)) - reader := bufio.NewReader(os.Stdin) - input, err := reader.ReadString('\n') - if err != nil { - o.logger.Warn("Failed to read input. The older testsets will be kept.") - return nil - } - - if len(input) == 0 { - o.logger.Warn("Empty input. The older testsets will be kept.") - return nil - } - // Trimming the newline character for cleaner switch statement - input = input[:len(input)-1] - switch input { - case "y", "Y": - for _, testSet := range SelectedTests { - err := o.replay.DeleteTestSet(ctx, testSet) - if err != nil { - o.logger.Warn("Failed to delete the testset", zap.String("testset", testSet)) - } - } - o.logger.Info("Deleted the older testsets successfully") - case "n", "N": - o.logger.Info("skipping the deletion of older testsets") - default: - o.logger.Warn("Invalid input. The older testsets will be kept.") - } - } - return nil -} - -func (o *Orchestrator) replayTests(ctx context.Context, testSet string) (bool, error) { - //replay the recorded testcases - tcs, err := o.replay.GetTestCases(ctx, testSet) - if err != nil { - errMsg := "failed to get all testcases" - utils.LogError(o.logger, err, errMsg, zap.String("testset", testSet)) - return false, fmt.Errorf("%s", errMsg) - } - - if len(tcs) == 0 { - o.logger.Warn("No testcases found for the given testset", zap.String("testset", testSet)) - return false, nil - } - - host, port, err := pkg.ExtractHostAndPort(tcs[0].Curl) - if err != nil { - errMsg := "failed to extract host and port" - utils.LogError(o.logger, err, "") - o.logger.Debug("", zap.String("curl", tcs[0].Curl)) - return false, fmt.Errorf("%s", errMsg) - } - cmdType := utils.CmdType(o.config.CommandType) - var userIP string - delay := o.config.Test.Delay - time.Sleep(time.Duration(delay) * time.Second) - if utils.IsDockerCmd(cmdType) { - host = o.config.ContainerName - userIP, err = o.record.GetContainerIP(ctx, o.config.AppID) - if err != nil { - utils.LogError(o.logger, err, "failed to get the app ip") - return false, err - } - } - timeout := time.Duration(120+delay) * time.Second - - o.logger.Debug("", zap.String("host", host), zap.String("port", port), zap.Any("WaitTimeout", timeout), zap.Any("CommandType", cmdType)) - - if err := pkg.WaitForPort(ctx, host, port, timeout); err != nil { - utils.LogError(o.logger, err, "Waiting for port failed", zap.String("host", host), zap.String("port", port)) - return false, err - } - - // Read the template and secret values once per test set - testSetConf, err := o.replay.GetTestSetConf(ctx, testSet) - if err != nil { - o.logger.Debug("failed to read template values") - } - - utils.TemplatizedValues = map[string]interface{}{} - utils.SecretValues = map[string]interface{}{} - - if testSetConf != nil { - if testSetConf.Template != nil { - utils.TemplatizedValues = testSetConf.Template - } - - if testSetConf.Secret != nil { - utils.SecretValues = testSetConf.Secret - } - } - - allTcRecorded := true - var simErr bool - for _, tc := range tcs { - if ctx.Err() != nil { - return false, ctx.Err() - } - if utils.IsDockerCmd(cmdType) { - tc.HTTPReq.URL, err = utils.ReplaceHost(tc.HTTPReq.URL, userIP) - if err != nil { - utils.LogError(o.logger, err, "failed to replace host to docker container's IP") - break - } - o.logger.Debug("", zap.Any("replaced URL in case of docker env", tc.HTTPReq.URL)) - } - - if o.config.ReRecord.Host != "" { - tc.HTTPReq.URL, err = utils.ReplaceHost(tc.HTTPReq.URL, o.config.ReRecord.Host) - if err != nil { - utils.LogError(o.logger, err, "failed to replace host to provided host by the user") - break - } - } - - if o.config.ReRecord.Port != 0 { - tc.HTTPReq.URL, err = utils.ReplacePort(tc.HTTPReq.URL, strconv.Itoa(int(o.config.ReRecord.Port))) - if err != nil { - utils.LogError(o.logger, err, "failed to replace port to provided port by the user") - break - } - } - resp, err := pkg.SimulateHTTP(ctx, tc, testSet, o.logger, o.config.Test.APITimeout) - if err != nil { - utils.LogError(o.logger, err, "failed to simulate HTTP request") - if resp == nil { - allTcRecorded = false - } - simErr = true - continue // Proceed with the next command - } - - o.logger.Info("Re-recorded the testcase successfully", zap.String("testcase", tc.Name), zap.String("of testset", testSet)) - } - - if simErr { - return allTcRecorded, fmt.Errorf("got error while simulating HTTP request. Please make sure the related services are up and running") - } - - return allTcRecorded, nil -} - -// checkForTemplates checks if the testcases are already templatized. If not, it asks the user if they want to templatize the testcases before re-recording -func (o *Orchestrator) checkForTemplates(ctx context.Context, testSets []string) { - // Check if the testcases are already templatized. - var nonTemplatized []string - for _, testSet := range testSets { - if _, ok := o.config.Test.SelectedTests[testSet]; !ok && len(o.config.Test.SelectedTests) != 0 { - continue - } - - conf, err := o.replay.GetTestSetConf(ctx, testSet) - if err != nil || conf == nil || conf.Template == nil { - nonTemplatized = append(nonTemplatized, testSet) - } - } - - if len(nonTemplatized) == 0 { - return - } - - o.config.Templatize.TestSets = nonTemplatized - o.logger.Warn("The following testSets are not templatized. Do you want to templatize them to handle noisy fields?(y/n)", zap.Any("testSets:", nonTemplatized)) - reader := bufio.NewReader(os.Stdin) - input, err := reader.ReadString('\n') - if err != nil { - o.logger.Warn("failed to read input. Skipping templatization") - } - if input == "n\n" || input == "N\n" { - o.logger.Info("skipping templatization") - return - } - - if input == "y\n" || input == "Y\n" { - if err := o.tools.Templatize(ctx); err != nil { - utils.LogError(o.logger, err, "failed to templatize test cases, skipping templatization") - } - } -} diff --git a/keploy/pkg/service/orchestrator/service.go b/keploy/pkg/service/orchestrator/service.go deleted file mode 100644 index 01fc9fb..0000000 --- a/keploy/pkg/service/orchestrator/service.go +++ /dev/null @@ -1,7 +0,0 @@ -package orchestrator - -import "context" - -type Service interface { - ReRecord(ctx context.Context) error -} diff --git a/keploy/pkg/service/record/record.go b/keploy/pkg/service/record/record.go deleted file mode 100755 index bb9f429..0000000 --- a/keploy/pkg/service/record/record.go +++ /dev/null @@ -1,390 +0,0 @@ -// Package record provides functionality for recording and managing test cases and mocks. -package record - -import ( - "context" - "errors" - "fmt" - "strconv" - "strings" - - "time" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/models" - - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "golang.org/x/sync/errgroup" -) - -type Recorder struct { - logger *zap.Logger - testDB TestDB - mockDB MockDB - telemetry Telemetry - instrumentation Instrumentation - testSetConf TestSetConfig - config *config.Config -} - -func New(logger *zap.Logger, testDB TestDB, mockDB MockDB, telemetry Telemetry, instrumentation Instrumentation, testSetConf TestSetConfig, config *config.Config) Service { - return &Recorder{ - logger: logger, - testDB: testDB, - mockDB: mockDB, - telemetry: telemetry, - instrumentation: instrumentation, - testSetConf: testSetConf, - config: config, - } -} - -func (r *Recorder) Start(ctx context.Context, reRecord bool) error { - - // creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller - errGrp, _ := errgroup.WithContext(ctx) - ctx = context.WithValue(ctx, models.ErrGroupKey, errGrp) - - runAppErrGrp, _ := errgroup.WithContext(ctx) - runAppCtx := context.WithoutCancel(ctx) - runAppCtx, runAppCtxCancel := context.WithCancel(runAppCtx) - - hookErrGrp, _ := errgroup.WithContext(ctx) - hookCtx := context.WithoutCancel(ctx) - hookCtx, hookCtxCancel := context.WithCancel(hookCtx) - hookCtx = context.WithValue(hookCtx, models.ErrGroupKey, hookErrGrp) - // reRecordCtx, reRecordCancel := context.WithCancel(ctx) - // defer reRecordCancel() // Cancel the context when the function returns - - var stopReason string - - // defining all the channels and variables required for the record - var runAppError models.AppError - var appErrChan = make(chan models.AppError, 1) - var insertTestErrChan = make(chan error, 10) - var insertMockErrChan = make(chan error, 10) - var appID uint64 - var newTestSetID string - var testCount = 0 - var mockCountMap = make(map[string]int) - - // defering the stop function to stop keploy in case of any error in record or in case of context cancellation - defer func() { - select { - case <-ctx.Done(): - default: - if !reRecord { - err := utils.Stop(r.logger, stopReason) - if err != nil { - utils.LogError(r.logger, err, "failed to stop recording") - } - } - } - runAppCtxCancel() - err := runAppErrGrp.Wait() - if err != nil { - utils.LogError(r.logger, err, "failed to stop application") - } - hookCtxCancel() - err = hookErrGrp.Wait() - if err != nil { - utils.LogError(r.logger, err, "failed to stop hooks") - } - err = errGrp.Wait() - if err != nil { - utils.LogError(r.logger, err, "failed to stop recording") - } - r.telemetry.RecordedTestSuite(newTestSetID, testCount, mockCountMap) - }() - - defer close(appErrChan) - defer close(insertTestErrChan) - defer close(insertMockErrChan) - - newTestSetID, err := r.GetNextTestSetID(ctx) - if err != nil { - stopReason = "failed to get new test-set id" - utils.LogError(r.logger, err, stopReason) - return fmt.Errorf("%s", stopReason) - } - - // Create config.yaml if metadata is provided - if r.config.Record.Metadata != "" { - r.createConfigWithMetadata(ctx, newTestSetID) - } - - //checking for context cancellation as we don't want to start the instrumentation if the context is cancelled - select { - case <-ctx.Done(): - return nil - default: - } - - // Instrument will setup the environment and start the hooks and proxy - appID, err = r.Instrument(hookCtx) - if err != nil { - stopReason = "failed to instrument the application" - utils.LogError(r.logger, err, stopReason) - return fmt.Errorf("%s", stopReason) - } - - r.config.AppID = appID - - // fetching test cases and mocks from the application and inserting them into the database - frames, err := r.GetTestAndMockChans(ctx, appID) - if err != nil { - stopReason = "failed to get data frames" - utils.LogError(r.logger, err, stopReason) - if ctx.Err() == context.Canceled { - return err - } - return fmt.Errorf("%s", stopReason) - } - - errGrp.Go(func() error { - for testCase := range frames.Incoming { - err := r.testDB.InsertTestCase(ctx, testCase, newTestSetID, true) - if err != nil { - if ctx.Err() == context.Canceled { - continue - } - insertTestErrChan <- err - } else { - testCount++ - r.telemetry.RecordedTestAndMocks() - } - } - return nil - }) - - errGrp.Go(func() error { - for mock := range frames.Outgoing { - err := r.mockDB.InsertMock(ctx, mock, newTestSetID) - if err != nil { - if ctx.Err() == context.Canceled { - continue - } - insertMockErrChan <- err - } else { - mockCountMap[mock.GetKind()]++ - r.telemetry.RecordedTestCaseMock(mock.GetKind()) - } - } - return nil - }) - - if !r.config.E2E { - runAppErrGrp.Go(func() error { - runAppError = r.instrumentation.Run(runAppCtx, appID, models.RunOptions{}) - if runAppError.AppErrorType == models.ErrCtxCanceled { - return nil - } - appErrChan <- runAppError - return nil - }) - } - - // setting a timer for recording - if r.config.Record.RecordTimer != 0 { - errGrp.Go(func() error { - r.logger.Info("Setting a timer of " + r.config.Record.RecordTimer.String() + " for recording") - timer := time.After(r.config.Record.RecordTimer) - select { - case <-timer: - r.logger.Warn("Time up! Stopping keploy") - err := utils.Stop(r.logger, "Time up! Stopping keploy") - if err != nil { - utils.LogError(r.logger, err, "failed to stop recording") - return errors.New("failed to stop recording") - } - case <-ctx.Done(): - return nil - } - return nil - }) - } - - // Waiting for the error to occur in any of the go routines - select { - case appErr := <-appErrChan: - switch appErr.AppErrorType { - case models.ErrCommandError: - stopReason = "error in running the user application, hence stopping keploy" - case models.ErrUnExpected: - stopReason = "user application terminated unexpectedly hence stopping keploy, please check application logs if this behaviour is not expected" - case models.ErrInternal: - stopReason = "internal error occured while hooking into the application, hence stopping keploy" - case models.ErrAppStopped: - stopReason = "user application terminated unexpectedly hence stopping keploy, please check application logs if this behaviour is not expected" - r.logger.Warn(stopReason, zap.Error(appErr)) - return nil - case models.ErrCtxCanceled: - return nil - case models.ErrTestBinStopped: - stopReason = "keploy test mode binary stopped, hence stopping keploy" - return nil - default: - stopReason = "unknown error received from application, hence stopping keploy" - } - - case err = <-insertTestErrChan: - stopReason = "error while inserting test case into db, hence stopping keploy" - case err = <-insertMockErrChan: - stopReason = "error while inserting mock into db, hence stopping keploy" - case <-ctx.Done(): - return nil - } - utils.LogError(r.logger, err, stopReason) - return fmt.Errorf("%s", stopReason) -} - -func (r *Recorder) Instrument(ctx context.Context) (uint64, error) { - var stopReason string - // setting up the environment for recording - appID, err := r.instrumentation.Setup(ctx, r.config.Command, models.SetupOptions{Container: r.config.ContainerName, DockerNetwork: r.config.NetworkName, DockerDelay: r.config.BuildDelay}) - if err != nil { - stopReason = "failed setting up the environment" - utils.LogError(r.logger, err, stopReason) - return 0, fmt.Errorf("%s", stopReason) - } - r.config.AppID = appID - - // checking for context cancellation as we don't want to start the hooks and proxy if the context is cancelled - select { - case <-ctx.Done(): - return appID, nil - default: - // Starting the hooks and proxy - hooks := models.HookOptions{ - Mode: models.MODE_RECORD, - EnableTesting: r.config.EnableTesting, - Rules: r.config.BypassRules, - E2E: r.config.E2E, - Port: r.config.Port, - } - err = r.instrumentation.Hook(ctx, appID, hooks) - if err != nil { - stopReason = "failed to start the hooks and proxy" - utils.LogError(r.logger, err, stopReason) - if ctx.Err() == context.Canceled { - return appID, err - } - return appID, fmt.Errorf("%s", stopReason) - } - } - return appID, nil -} - -func (r *Recorder) GetTestAndMockChans(ctx context.Context, appID uint64) (FrameChan, error) { - incomingOpts := models.IncomingOptions{ - Filters: r.config.Record.Filters, - BasePath: r.config.Record.BasePath, - } - incomingChan, err := r.instrumentation.GetIncoming(ctx, appID, incomingOpts) - if err != nil { - return FrameChan{}, fmt.Errorf("failed to get incoming test cases: %w", err) - } - - outgoingOpts := models.OutgoingOptions{ - Rules: r.config.BypassRules, - MongoPassword: r.config.Test.MongoPassword, - FallBackOnMiss: r.config.Test.FallBackOnMiss, - Backdate: time.Now(), - } - - outgoingChan, err := r.instrumentation.GetOutgoing(ctx, appID, outgoingOpts) - if err != nil { - return FrameChan{}, fmt.Errorf("failed to get outgoing mocks: %w", err) - } - - return FrameChan{ - Incoming: incomingChan, - Outgoing: outgoingChan, - }, nil -} - -func (r *Recorder) RunApplication(ctx context.Context, appID uint64, opts models.RunOptions) models.AppError { - return r.instrumentation.Run(ctx, appID, opts) -} - -func (r *Recorder) GetNextTestSetID(ctx context.Context) (string, error) { - testSetIDs, err := r.testDB.GetAllTestSetIDs(ctx) - if err != nil { - return "", fmt.Errorf("failed to get test set IDs: %w", err) - } - - if r.config.Record.Metadata == "" { - return pkg.NextID(testSetIDs, models.TestSetPattern), nil - } - r.config.Record.Metadata = utils.TrimSpaces(r.config.Record.Metadata) - meta, err := utils.ParseMetadata(r.config.Record.Metadata) - if err != nil || meta == nil { - return pkg.NextID(testSetIDs, models.TestSetPattern), nil - } - - nameVal, ok := meta["name"] - requestedName, isStr := nameVal.(string) - if !ok || !isStr || requestedName == "" { - return pkg.NextID(testSetIDs, models.TestSetPattern), nil - } - - existingIDs := make(map[string]struct{}, len(testSetIDs)) - for _, id := range testSetIDs { - existingIDs[id] = struct{}{} - } - - if _, occupied := existingIDs[requestedName]; !occupied { - return requestedName, nil - } - - var highestSuffix int - namePrefix := requestedName + "-" - for id := range existingIDs { - if !strings.HasPrefix(id, namePrefix) { - continue - } - suffixPart := id[len(namePrefix):] - if n, err := strconv.Atoi(suffixPart); err == nil && n > highestSuffix { - highestSuffix = n - } - } - - newSuffix := highestSuffix + 1 - assignedName := fmt.Sprintf("%s-%d", requestedName, newSuffix) - - r.logger.Warn(fmt.Sprintf( - "Test set name '%s' already exists, using '%s' instead. You can change this name if you want.", - requestedName, assignedName, - )) - - return assignedName, nil -} - -func (r *Recorder) GetContainerIP(ctx context.Context, id uint64) (string, error) { - return r.instrumentation.GetContainerIP(ctx, id) -} - -func (r *Recorder) createConfigWithMetadata(ctx context.Context, testSetID string) { - // Parse metadata from the config - metadata, err := utils.ParseMetadata(r.config.Record.Metadata) - if err != nil { - utils.LogError(r.logger, err, "failed to parse metadata", zap.String("metadata", r.config.Record.Metadata)) - return - } - testSet := &models.TestSet{ - PreScript: "", - PostScript: "", - Template: make(map[string]interface{}), - Metadata: metadata, - } - - err = r.testSetConf.Write(ctx, testSetID, testSet) - if err != nil { - utils.LogError(r.logger, err, "Failed to create test-set config file with metadata", zap.String("testSet", testSetID)) - return - } - - r.logger.Info("Created test-set config file with metadata") -} diff --git a/keploy/pkg/service/record/service.go b/keploy/pkg/service/record/service.go deleted file mode 100755 index 243ffb6..0000000 --- a/keploy/pkg/service/record/service.go +++ /dev/null @@ -1,51 +0,0 @@ -package record - -import ( - "context" - - "go.keploy.io/server/v2/pkg/models" -) - -type Instrumentation interface { - //Setup prepares the environment for the recording - Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) - //Hook will load hooks and start the proxy server. - Hook(ctx context.Context, id uint64, opts models.HookOptions) error - GetIncoming(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) - GetOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) (<-chan *models.Mock, error) - // Run is blocking call and will execute until error - Run(ctx context.Context, id uint64, opts models.RunOptions) models.AppError - GetContainerIP(ctx context.Context, id uint64) (string, error) -} - -type Service interface { - Start(ctx context.Context, reRecord bool) error - GetContainerIP(ctx context.Context, id uint64) (string, error) -} - -type TestDB interface { - GetAllTestSetIDs(ctx context.Context) ([]string, error) - InsertTestCase(ctx context.Context, tc *models.TestCase, testSetID string, enableLog bool) error - // GetTestCases(ctx context.Context, testID string) ([]*models.TestCase, error) -} - -type MockDB interface { - InsertMock(ctx context.Context, mock *models.Mock, testSetID string) error -} - -type TestSetConfig interface { - Read(ctx context.Context, testSetID string) (*models.TestSet, error) - Write(ctx context.Context, testSetID string, testSet *models.TestSet) error -} - -type Telemetry interface { - RecordedTestSuite(testSet string, testsTotal int, mockTotal map[string]int) - RecordedTestCaseMock(mockType string) - RecordedMocks(mockTotal map[string]int) - RecordedTestAndMocks() -} - -type FrameChan struct { - Incoming <-chan *models.TestCase - Outgoing <-chan *models.Mock -} diff --git a/keploy/pkg/service/record/utils.go b/keploy/pkg/service/record/utils.go deleted file mode 100644 index aa96e8b..0000000 --- a/keploy/pkg/service/record/utils.go +++ /dev/null @@ -1,3 +0,0 @@ -//go:build linux - -package record diff --git a/keploy/pkg/service/replay/hooks.go b/keploy/pkg/service/replay/hooks.go deleted file mode 100644 index af60b20..0000000 --- a/keploy/pkg/service/replay/hooks.go +++ /dev/null @@ -1,282 +0,0 @@ -// Package replay provides the hooks for the replay service -package replay - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/golang-jwt/jwt/v4" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/service" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type Hooks struct { - logger *zap.Logger - cfg *config.Config - tsConfigDB TestSetConfig - storage Storage - auth service.Auth - instrumentation Instrumentation - mock *mock -} - -func NewHooks(logger *zap.Logger, cfg *config.Config, tsConfigDB TestSetConfig, storage Storage, auth service.Auth, instrumentation Instrumentation, mock *mock) TestHooks { - return &Hooks{ - cfg: cfg, - logger: logger, - tsConfigDB: tsConfigDB, - storage: storage, - auth: auth, - instrumentation: instrumentation, - mock: mock, - } -} - -func (h *Hooks) SimulateRequest(ctx context.Context, _ uint64, tc *models.TestCase, testSetID string) (interface{}, error) { - switch tc.Kind { - case models.HTTP: - h.logger.Debug("Simulating HTTP request", zap.Any("Test case", tc)) - return pkg.SimulateHTTP(ctx, tc, testSetID, h.logger, h.cfg.Test.APITimeout) - - case models.GRPC_EXPORT: - h.logger.Debug("Simulating gRPC request", zap.Any("Test case", tc)) - return pkg.SimulateGRPC(ctx, tc, testSetID, h.logger) - - default: - return nil, fmt.Errorf("unsupported test case kind: %s", tc.Kind) - } -} - -func (h *Hooks) BeforeTestRun(ctx context.Context, testRunID string) error { - h.logger.Debug("BeforeTestRun hook executed", zap.String("testRunID", testRunID)) - return nil -} - -func (h *Hooks) AfterTestSetRun(ctx context.Context, testSetID string, status bool) error { - - if h.cfg.Test.DisableMockUpload { - return nil - } - - if h.cfg.Test.BasePath != "" { - h.logger.Debug("Mocking is disabled when basePath is given", zap.String("testSetID", testSetID), zap.String("basePath", h.cfg.Test.BasePath)) - return nil - } - - if !status { - return nil - } - - token, err := h.auth.GetToken(ctx) - if err != nil || token == "" { - h.logger.Error("Failed to Authenticate user, skipping mock upload", zap.Error(err)) - return nil - } - h.mock.setToken(token) - - err = h.mock.upload(ctx, testSetID) - if err != nil { - h.logger.Warn("Failed to upload mock, hence skipping", zap.String("testSetID", testSetID), zap.Error(err)) - } - - return nil -} - -func (h *Hooks) BeforeTestSetRun(ctx context.Context, testSetID string) error { - - if h.cfg.Test.BasePath != "" { - h.logger.Debug("Mocking is disabled when basePath is given", zap.String("testSetID", testSetID), zap.String("basePath", h.cfg.Test.BasePath)) - return nil - } - - if h.cfg.Test.UseLocalMock { - h.logger.Debug("Using local mock file, as UseLocalMock is selected", zap.String("testSetID", testSetID)) - return nil - } - - token, err := h.auth.GetToken(ctx) - if err != nil { - h.logger.Warn("Failed to Authenticate user, continuing with local mock if present", zap.Error(err)) - return nil - } - h.mock.setToken(token) - - // Check if test-set config is present - tsConfig, err := h.tsConfigDB.Read(ctx, testSetID) - if err != nil || tsConfig == nil || tsConfig.MockRegistry == nil { - h.logger.Debug("test set config for upload mock not found, continuing with local mock", zap.String("testSetID", testSetID), zap.Error(err)) - return nil - } - - if tsConfig.MockRegistry.Mock == "" { - h.logger.Warn("Mock is empty in test-set config, continuing with local mock if present", zap.String("testSetID", testSetID)) - return nil - } - - if tsConfig.MockRegistry.App == "" { - h.logger.Warn("App name is empty in test-set config, continuing with local mock if present", zap.String("testSetID", testSetID)) - return nil - } - - // Check if mock file is already downloaded by previous test runs - localMockPath := filepath.Join(h.cfg.Path, testSetID, "mocks.yaml") - mockContent, err := os.ReadFile(localMockPath) - if err == nil { - if tsConfig.MockRegistry.Mock == utils.Hash(mockContent) { - h.logger.Debug("Mock file already exists, downloading from cloud is not necessary", zap.String("testSetID", testSetID), zap.String("mockPath", localMockPath)) - return nil - } - } - - if tsConfig.MockRegistry.App != h.cfg.AppName { - h.logger.Warn("App name in the keploy.yml does not match with the app name in the config.yml in the test-set", zap.String("test-set-config-AppName", tsConfig.MockRegistry.App), zap.String("global-config-Appname", h.cfg.AppName)) - h.logger.Warn("Using app name from the test-set's config.yml for mock retrieval", zap.String("appName", tsConfig.MockRegistry.App)) - } - - h.logger.Info("Downloading mock file from cloud...", zap.String("testSetID", testSetID)) - cloudFile, err := h.storage.Download(ctx, tsConfig.MockRegistry.Mock, tsConfig.MockRegistry.App, tsConfig.MockRegistry.User, token) - if err != nil { - h.logger.Error("Failed to download mock file", zap.Error(err)) - return err - } - - // Save the downloaded mock file to local - file, err := os.Create(localMockPath) - if err != nil { - h.logger.Error("Failed to create local file", zap.String("path", localMockPath), zap.Error(err)) - return err - } - defer func() { - err := file.Close() - if err != nil { - utils.LogError(h.logger, err, "failed to close the http response body") - } - }() - - done := make(chan struct{}) - - // Spinner goroutine - go func() { - spinnerChars := []rune{'|', '/', '-', '\\'} - i := 0 - for { - select { - case <-done: - fmt.Print("\r") // Clear spinner line after done - return - default: - fmt.Printf("\rDownloading... %c", spinnerChars[i%len(spinnerChars)]) - i++ - time.Sleep(100 * time.Millisecond) - } - } - }() - - _, err = io.Copy(file, cloudFile) - if err != nil { - close(done) - return err - } - close(done) - h.logger.Info("Mock file downloaded successfully") - - err = utils.AddToGitIgnore(h.logger, h.cfg.Path, "/*/mocks.yaml") - if err != nil { - utils.LogError(h.logger, err, "failed to add /*/mocks.yaml to .gitignore file") - } - - return nil -} - -func (h *Hooks) AfterTestRun(_ context.Context, testRunID string, testSetIDs []string, coverage models.TestCoverage) error { - h.logger.Debug("AfterTestRun hook executed", zap.String("testRunID", testRunID), zap.Any("testSetIDs", testSetIDs), zap.Any("coverage", coverage)) - return nil -} - -func (h *Hooks) GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) { - consumedMocks, err := h.instrumentation.GetConsumedMocks(ctx, id) - if err != nil { - h.logger.Error("failed to get consumed mocks", zap.Error(err)) - return nil, err - } - return consumedMocks, nil -} - -// Function to parse and extract claims from a JWT token without verification -func extractClaimsWithoutVerification(tokenString string) (jwt.MapClaims, error) { - token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) - if err != nil { - return nil, err - } - - if claims, ok := token.Claims.(jwt.MapClaims); ok { - return claims, nil - } - return nil, fmt.Errorf("unable to parse claims") -} - -type getPlanRes struct { - Plan models.Plan `json:"plan"` - Error string `json:"error"` -} - -func getLatestPlan(ctx context.Context, logger *zap.Logger, serverUrl, token string) (string, error) { - logger.Debug("Getting the latest plan", zap.String("serverUrl", serverUrl), zap.String("token", token)) - - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/subscription/plan", serverUrl), nil) - if err != nil { - logger.Error("failed to create request", zap.Error(err)) - return "", fmt.Errorf("failed to get plan") - } - req.Header.Set("Authorization", "Bearer "+token) - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - logger.Error("http request failed", zap.Error(err)) - return "", fmt.Errorf("failed to get plan") - } - defer func() { - if cerr := resp.Body.Close(); cerr != nil { - logger.Error("failed to close response body", zap.Error(cerr)) - } - }() - - body, err := io.ReadAll(resp.Body) - if err != nil { - logger.Error("failed to read response body", zap.Error(err)) - return "", fmt.Errorf("failed to get plan") - } - - var res getPlanRes - if err := json.Unmarshal(body, &res); err != nil { - logger.Error("failed to unmarshal response", zap.Error(err)) - return "", fmt.Errorf("failed to get plan") - } - - if resp.StatusCode != http.StatusOK { - logger.Error("non-200 response from subscription/plan", zap.Int("status", resp.StatusCode), zap.String("api_error", res.Error)) - if res.Error != "" { - return "", fmt.Errorf("%s", res.Error) - } - return "", fmt.Errorf("failed to get plan") - } - - if res.Plan.Type == "" { - logger.Error("plan type missing in successful response", zap.Any("plan", res.Plan)) - return "", fmt.Errorf("plan not found") - } - - return res.Plan.Type, nil -} diff --git a/keploy/pkg/service/replay/integration_test.go b/keploy/pkg/service/replay/integration_test.go deleted file mode 100644 index 0d812a9..0000000 --- a/keploy/pkg/service/replay/integration_test.go +++ /dev/null @@ -1,272 +0,0 @@ -package replay - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -// TestChannelVsDelayComparison demonstrates the improvement of using channels over delays -func TestChannelVsDelayComparison(t *testing.T) { - // This test demonstrates the performance improvement of channel-based notification - // over the previous delay-based approach - - mockInstr := NewMockInstrumentation() - appID := uint64(12345) - - // Test 1: Channel-based approach (current implementation) - channelStartTime := time.Now() - - unloadCh := mockInstr.GetHookUnloadDone(appID) - inst := &InstrumentState{ - AppID: appID, - HookCancel: func() {}, - UnloadDone: unloadCh, - } - - channelComplete := make(chan bool, 1) - go func() { - <-inst.UnloadDone - channelComplete <- true - }() - - // Simulate unload happening quickly - go func() { - time.Sleep(10 * time.Millisecond) // Simulate quick unload - mockInstr.CloseUnloadChannel(appID) - }() - - select { - case <-channelComplete: - channelDuration := time.Since(channelStartTime) - t.Logf("Channel-based approach completed in: %v", channelDuration) - - // Should complete quickly (within reasonable time) - assert.Less(t, channelDuration, 100*time.Millisecond, "Channel approach should be fast") - - case <-time.After(200 * time.Millisecond): - t.Error("Channel approach should not timeout") - } - - // Test 2: Simulate old delay-based approach for comparison - delayStartTime := time.Now() - - // Previous implementation used fixed delay (2000ms as seen in commit) - oldDelay := 2000 * time.Millisecond - - // Simulate the old delay - time.Sleep(oldDelay) - delayDuration := time.Since(delayStartTime) - - t.Logf("Old delay-based approach would take: %v", delayDuration) - - // The channel approach should be significantly faster - // (This test just demonstrates the concept; in practice, the improvement depends on actual unload time) - assert.Greater(t, delayDuration, 100*time.Millisecond, "Old delay approach was slow") -} - -// TestHookReloadSequence demonstrates the complete hook reload sequence with proper signaling -func TestHookReloadSequence(t *testing.T) { - // This test simulates the exact sequence that happens in replay.go - // when reloading hooks between test sets - - mockInstr := NewMockInstrumentation() - - // Simulate multiple test sets - testSets := []struct { - name string - appID uint64 - }{ - {"test-set-1", 12345}, - {"test-set-2", 12346}, - {"test-set-3", 12347}, - } - - var currentInst *InstrumentState - totalReloadTime := time.Duration(0) - - for i, testSet := range testSets { - t.Logf("Processing %s", testSet.name) - - // For test sets after the first one, reload hooks - if i > 0 && currentInst != nil { - reloadStartTime := time.Now() - - // Step 1: Cancel current hooks (simulated) - t.Logf("Canceling hooks for previous test set") - - // Step 2: Wait for unload completion using channel - t.Logf("Waiting for hooks to be completely unloaded") - - unloadComplete := make(chan bool, 1) - go func(inst *InstrumentState) { - <-inst.UnloadDone - unloadComplete <- true - }(currentInst) - - // Simulate the unload happening - go func() { - time.Sleep(20 * time.Millisecond) // Simulate unload time - mockInstr.CloseUnloadChannel(currentInst.AppID) - }() - - // Wait for unload completion - select { - case <-unloadComplete: - t.Logf("Hooks unload completed") - case <-time.After(500 * time.Millisecond): - t.Errorf("Unload should complete for %s", testSet.name) - continue - } - - reloadDuration := time.Since(reloadStartTime) - totalReloadTime += reloadDuration - t.Logf("Hook reload for %s completed in: %v", testSet.name, reloadDuration) - } - - // Step 3: Create new instrument state for new test set - unloadCh := mockInstr.GetHookUnloadDone(testSet.appID) - currentInst = &InstrumentState{ - AppID: testSet.appID, - HookCancel: func() {}, - UnloadDone: unloadCh, - } - - t.Logf("New instrument state created for %s with AppID: %d", testSet.name, testSet.appID) - - // Verify new channel is not closed - select { - case <-currentInst.UnloadDone: - t.Errorf("New channel for %s should not be closed", testSet.name) - default: - // Expected behavior - } - } - - t.Logf("Total time for all hook reloads: %v", totalReloadTime) - - // With proper channel signaling, total reload time should be reasonable - // (much less than what it would be with fixed delays) - expectedMaxTime := time.Duration(len(testSets)-1) * 100 * time.Millisecond // Allow 100ms per reload - assert.Less(t, totalReloadTime, expectedMaxTime, "Total reload time should be reasonable") -} - -// TestProperResourceCleanup verifies that channel-based signaling ensures proper resource cleanup -func TestProperResourceCleanup(t *testing.T) { - // This test verifies the key benefit of the channel approach: - // ensuring that resources are properly cleaned up before proceeding - - mockInstr := NewMockInstrumentation() - appID := uint64(12345) - - // Simulate resource state - resourceCleaned := false - - unloadCh := mockInstr.GetHookUnloadDone(appID) - inst := &InstrumentState{ - AppID: appID, - HookCancel: func() {}, - UnloadDone: unloadCh, - } - - // Simulate waiting for unload with resource cleanup - cleanupComplete := make(chan bool, 1) - go func() { - // Wait for unload signal - <-inst.UnloadDone - - // Verify resource was cleaned up before signal - if resourceCleaned { - cleanupComplete <- true - } else { - cleanupComplete <- false - } - }() - - // Simulate cleanup process - go func() { - time.Sleep(30 * time.Millisecond) // Simulate cleanup time - resourceCleaned = true // Mark resource as cleaned - mockInstr.CloseUnloadChannel(appID) // Signal completion only after cleanup - }() - - // Verify cleanup happened before signal - select { - case success := <-cleanupComplete: - assert.True(t, success, "Resource should be cleaned up before unload signal") - case <-time.After(200 * time.Millisecond): - t.Error("Cleanup should complete within timeout") - } - - // This demonstrates the key advantage: we only proceed after confirming cleanup is done - t.Log("Verified: Channel-based approach ensures proper resource cleanup before proceeding") -} - -// TestRaceConditionPrevention tests that channels prevent race conditions -func TestRaceConditionPrevention(t *testing.T) { - // This test demonstrates how channels prevent race conditions that could occur - // with time-based delays (where new operations might start before cleanup is complete) - - mockInstr := NewMockInstrumentation() - appID1 := uint64(12345) - appID2 := uint64(12346) - - // First operation - unloadCh1 := mockInstr.GetHookUnloadDone(appID1) - inst1 := &InstrumentState{ - AppID: appID1, - HookCancel: func() {}, - UnloadDone: unloadCh1, - } - - operationOrder := make([]string, 0) - orderMutex := make(chan struct{}, 1) - - // Start first operation cleanup - go func() { - <-inst1.UnloadDone - orderMutex <- struct{}{} - operationOrder = append(operationOrder, "first_cleanup_complete") - <-orderMutex - }() - - // Start second operation (should wait for first to complete) - go func() { - time.Sleep(10 * time.Millisecond) // Small delay to ensure order - orderMutex <- struct{}{} - operationOrder = append(operationOrder, "second_operation_start") - <-orderMutex - - // Create second instrument state - unloadCh2 := mockInstr.GetHookUnloadDone(appID2) - _ = &InstrumentState{ - AppID: appID2, - HookCancel: func() {}, - UnloadDone: unloadCh2, - } - - orderMutex <- struct{}{} - operationOrder = append(operationOrder, "second_operation_complete") - <-orderMutex - }() - - // Complete first operation - go func() { - time.Sleep(50 * time.Millisecond) // Simulate cleanup time - mockInstr.CloseUnloadChannel(appID1) - }() - - // Wait for completion - time.Sleep(200 * time.Millisecond) - - // Verify order: first cleanup should complete before second operation starts its resource allocation - t.Logf("Operation order: %v", operationOrder) - - // With proper channel signaling, we ensure ordered execution - assert.Contains(t, operationOrder, "first_cleanup_complete", "First operation should complete") - assert.Contains(t, operationOrder, "second_operation_complete", "Second operation should complete") - - // This test demonstrates that channels provide proper synchronization - t.Log("Verified: Channel-based approach prevents race conditions") -} diff --git a/keploy/pkg/service/replay/mock.go b/keploy/pkg/service/replay/mock.go deleted file mode 100644 index c21f98e..0000000 --- a/keploy/pkg/service/replay/mock.go +++ /dev/null @@ -1,297 +0,0 @@ -package replay - -import ( - "bytes" - "context" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "time" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -var osReadFile224 = os.ReadFile -var osCreate224 = os.Create -var timeSleep224 = time.Sleep -var extractClaimsWithoutVerification224 = extractClaimsWithoutVerification -var getLatestPlan224 = getLatestPlan - -type mock struct { - cfg *config.Config - storage Storage - logger *zap.Logger - tsConfigDB TestSetConfig - token string -} - -func (m *mock) setToken(token string) { - m.token = token -} - -func (m *mock) download(ctx context.Context, testSetID string) error { - // Add nil check protection to prevent segmentation fault - if m.storage == nil { - m.logger.Error("Storage service is not initialized, cannot download mocks") - return fmt.Errorf("storage service is not initialized") - } - - // Check if test-set config is present - tsConfig, err := m.tsConfigDB.Read(ctx, testSetID) - if err != nil || tsConfig == nil || tsConfig.MockRegistry == nil { - m.logger.Error("test set mock config not found", zap.String("testSetID", testSetID), zap.Error(err)) - return fmt.Errorf("mock registry config not found") - } - - if tsConfig.MockRegistry.Mock == "" { - m.logger.Error("Mock is empty in test-set config", zap.String("testSetID", testSetID)) - return fmt.Errorf("mock is empty in test-set config") - } - - if tsConfig.MockRegistry.App == "" { - m.logger.Warn("App name is empty in test-set config", zap.String("testSetID", testSetID)) - return fmt.Errorf("app name is empty in test-set config") - } - - // Check if mock file is already downloaded by previous test runs - localMockPath := filepath.Join(m.cfg.Path, testSetID, "mocks.yaml") - mockContent, err := osReadFile224(localMockPath) - if err == nil { - if tsConfig.MockRegistry.Mock == utils.Hash(mockContent) { - m.logger.Info("Mock file already exists, downloading from cloud is not necessary", zap.String("testSetID", testSetID), zap.String("mockPath", localMockPath)) - return nil - } - var response string - - if len(mockContent) == 0 { - m.logger.Warn("Local mock file is empty, proceeding with download from keploy registry", zap.String("testSetID", testSetID)) - response = "y" - } else { - m.logger.Warn("Local mock file is different from the one in the Keploy registry.") - // Prompt user for confirmation to override the local mock file - fmt.Print("The mock file present locally is different from the one in the Keploy registry. Do you want to override the local mock file with the version from the registry? (y/n): ") - } - - // Create a channel to listen for context cancellation (Ctrl+C) - cancelChan := make(chan struct{}) - - // Start a goroutine to wait for user input asynchronously - go func() { - - if len(mockContent) == 0 { - cancelChan <- struct{}{} - return - } - - _, err := fmt.Scanln(&response) - if err != nil { - response = "n" // Default to 'no' if there's an error reading input - } - cancelChan <- struct{}{} - }() - - select { - case <-cancelChan: - // Normalize user input - response = strings.ToLower(strings.TrimSpace(response)) - if response != "y" && response != "yes" { - m.logger.Info("Keeping the local mock file", zap.String("testSetID", testSetID)) - return nil - } - - m.logger.Info("Overriding the local mock file with the version from the Keploy registry", zap.String("testSetID", testSetID)) - - case <-ctx.Done(): // context cancellation (Ctrl+C) - m.logger.Warn("Download interrupted by user") - return ctx.Err() // Return the context cancellation error - } - } - - if tsConfig.MockRegistry.App != m.cfg.AppName { - m.logger.Warn("App name in the keploy.yml does not match with the app name in the config.yml in the test-set", zap.String("test-set-config-AppName", tsConfig.MockRegistry.App), zap.String("global-config-Appname", m.cfg.AppName)) - m.logger.Warn("Using app name from the test-set's config.yml for mock retrieval", zap.String("appName", tsConfig.MockRegistry.App)) - } - - m.logger.Info("Downloading mock file from cloud...", zap.String("testSetID", testSetID)) - cloudFile, err := m.storage.Download(ctx, tsConfig.MockRegistry.Mock, tsConfig.MockRegistry.App, tsConfig.MockRegistry.User, m.token) - if err != nil { - m.logger.Error("Failed to download mock file", zap.Error(err)) - return err - } - - // Save the downloaded mock file to local - file, err := osCreate224(localMockPath) - if err != nil { - m.logger.Error("Failed to create local file", zap.String("path", localMockPath), zap.Error(err)) - return err - } - defer func() { - err := file.Close() - if err != nil { - utils.LogError(m.logger, err, "failed to close the http response body") - } - }() - - done := make(chan struct{}) - - // Spinner goroutine - go func() { - spinnerChars := []rune{'|', '/', '-', '\\'} - i := 0 - for { - select { - case <-done: - fmt.Print("\r") // Clear spinner line after done - return - default: - fmt.Printf("\rDownloading... %c", spinnerChars[i%len(spinnerChars)]) - i++ - timeSleep224(100 * time.Millisecond) - } - } - }() - - _, err = io.Copy(file, cloudFile) - if err != nil { - close(done) - return err - } - close(done) - m.logger.Info("Mock file downloaded successfully") - - err = utils.AddToGitIgnore(m.logger, m.cfg.Path, "/*/mocks.yaml") - if err != nil { - utils.LogError(m.logger, err, "failed to add /*/mocks.yaml to .gitignore file") - } - - return nil -} - -func (m *mock) upload(ctx context.Context, testSetID string) error { - // Add nil check protection to prevent segmentation fault - if m.storage == nil { - m.logger.Error("Storage service is not initialized, cannot upload mocks") - return fmt.Errorf("storage service is not initialized") - } - - claims, err := extractClaimsWithoutVerification224(m.token) - var role, username string - var ok bool - if err != nil { - m.logger.Error("Failed to extract claim from token for mock upload", zap.Error(err)) - return err - } - - if role, ok = claims["role"].(string); !ok || role == "" { - m.logger.Error("Role not found in the token, skipping mock upload") - return fmt.Errorf("failed to upload mock file: role not found in the token") - } - - if username, ok = claims["username"].(string); !ok { - m.logger.Error("Username not found in the token, skipping mock upload") - return fmt.Errorf("failed to upload mock file: username not found in the token") - } - - // get the plan of the current user - plan, err := getLatestPlan224(ctx, m.logger, m.cfg.APIServerURL, m.token) - if err != nil { - m.logger.Error("Failed to get latest plan of the user", zap.Error(err)) - return err - } - - m.logger.Debug("The latest plan", zap.Any("Plan", plan)) - - // Inspect local mock file - localMockPath := filepath.Join(m.cfg.Path, testSetID, "mocks.yaml") - mockFileContent, err := osReadFile224(localMockPath) - if err != nil { - m.logger.Error("Failed to read mock file for mock upload", zap.String("path", localMockPath), zap.Error(err)) - return err - } - - // If mock file is empty, return error - if len(mockFileContent) == 0 { - m.logger.Warn("Mock file is empty, skipping upload", zap.String("testSetID", testSetID), zap.String("mockPath", localMockPath)) - return nil - } - - mockHash := utils.Hash(mockFileContent) - mockFileReader := bytes.NewReader(mockFileContent) - - // Cross verify the local mock file with the test-set config - tsConfig, err := m.tsConfigDB.Read(ctx, testSetID) - // If test-set config is not found, upload the mock file - if err != nil || tsConfig == nil || tsConfig.MockRegistry == nil { - // create ts config - var prescript, postscript string - var template map[string]interface{} - var metadata map[string]interface{} - if tsConfig != nil { - prescript = tsConfig.PreScript - postscript = tsConfig.PostScript - template = tsConfig.Template - metadata = tsConfig.Metadata - } - tsConfig = &models.TestSet{ - PreScript: prescript, - PostScript: postscript, - Template: template, - MockRegistry: &models.MockRegistry{ - Mock: mockHash, - App: m.cfg.AppName, - }, - Metadata: metadata, - } - - if plan == "Free" { - if username == "" { - m.logger.Error("Username not found in the token for Free plan") - return fmt.Errorf("failed to upload mock file: username not found in the token") - } - tsConfig.MockRegistry.User = username - } - - m.logger.Info("uploading mock file...", zap.Any("testSet", testSetID)) - - err = m.storage.Upload(ctx, mockFileReader, mockHash, m.cfg.AppName, m.token) - if err != nil { - m.logger.Error("Failed to upload mock file", zap.Error(err)) - return err - } - - err := m.tsConfigDB.Write(ctx, testSetID, tsConfig) - if err != nil { - m.logger.Error("Failed to write test set config", zap.Error(err)) - return err - } - return nil - } - - // If mock file is already uploaded, skip the upload - if tsConfig.MockRegistry.Mock == mockHash { - m.logger.Info("Mock file is already uploaded, skipping upload", zap.String("testSetID", testSetID), zap.String("mockPath", localMockPath)) - return nil - } - - // If mock file is changed, upload the new mock file - m.logger.Debug("Mock file has changed, uploading new mock", zap.String("testSetID", testSetID), zap.String("mockPath", localMockPath)) - - m.logger.Info("uploading mock file...", zap.Any("testSet", testSetID)) - err = m.storage.Upload(ctx, mockFileReader, mockHash, m.cfg.AppName, m.token) - if err != nil { - m.logger.Error("Failed to upload mock file", zap.Error(err)) - return err - } - - err = utils.AddToGitIgnore(m.logger, m.cfg.Path, "/*/mocks.yaml") - if err != nil { - utils.LogError(m.logger, err, "failed to add /*/mocks.yaml to .gitignore file") - } - - return nil -} diff --git a/keploy/pkg/service/replay/replay.go b/keploy/pkg/service/replay/replay.go deleted file mode 100644 index 365bf69..0000000 --- a/keploy/pkg/service/replay/replay.go +++ /dev/null @@ -1,1793 +0,0 @@ -package replay - -import ( - // "bytes" - - "context" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "sort" - "strconv" - "strings" - "syscall" - - "time" - - "facette.io/natsort" - "github.com/k0kubun/pp/v3" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg" - matcherUtils "go.keploy.io/server/v2/pkg/matcher" - grpcMatcher "go.keploy.io/server/v2/pkg/matcher/grpc" - httpMatcher "go.keploy.io/server/v2/pkg/matcher/http" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/platform/coverage" - "go.keploy.io/server/v2/pkg/platform/coverage/golang" - "go.keploy.io/server/v2/pkg/platform/coverage/java" - "go.keploy.io/server/v2/pkg/platform/coverage/javascript" - "go.keploy.io/server/v2/pkg/platform/coverage/python" - "go.keploy.io/server/v2/pkg/service" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - "golang.org/x/sync/errgroup" -) - -var completeTestReport = make(map[string]TestReportVerdict) -var totalTests int -var totalTestPassed int -var totalTestFailed int -var totalTestIgnored int -var totalTestTimeTaken time.Duration -var failedTCsBySetID = make(map[string][]string) - -var HookImpl TestHooks - -type Replayer struct { - logger *zap.Logger - testDB TestDB - mockDB MockDB - reportDB ReportDB - testSetConf TestSetConfig - telemetry Telemetry - instrumentation Instrumentation - config *config.Config - auth service.Auth - mock *mock - instrument bool - isLastTestSet bool - isLastTestCase bool -} - -func NewReplayer(logger *zap.Logger, testDB TestDB, mockDB MockDB, reportDB ReportDB, testSetConf TestSetConfig, telemetry Telemetry, instrumentation Instrumentation, auth service.Auth, storage Storage, config *config.Config) Service { - - // TODO: add some comment. - mock := &mock{ - cfg: config, - storage: storage, - logger: logger, - tsConfigDB: testSetConf, - } - - // set the request emulator for simulating test case requests, if not set - if HookImpl == nil { - SetTestHooks(NewHooks(logger, config, testSetConf, storage, auth, instrumentation, mock)) - } - - instrument := config.Command != "" - return &Replayer{ - logger: logger, - testDB: testDB, - mockDB: mockDB, - reportDB: reportDB, - testSetConf: testSetConf, - telemetry: telemetry, - instrumentation: instrumentation, - config: config, - instrument: instrument, - auth: auth, - mock: mock, - } -} - -func (r *Replayer) Start(ctx context.Context) error { - - // creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller - g, ctx := errgroup.WithContext(ctx) - ctx = context.WithValue(ctx, models.ErrGroupKey, g) - - var hookCancel context.CancelFunc - var stopReason = "replay completed successfully" - - // defering the stop function to stop keploy in case of any error in record or in case of context cancellation - defer func() { - select { - case <-ctx.Done(): - break - default: - r.logger.Info("stopping Keploy", zap.String("reason", stopReason)) - } - if hookCancel != nil { - hookCancel() - } - err := g.Wait() - if err != nil { - utils.LogError(r.logger, err, "failed to stop replaying") - } - }() - - testSetIDs, err := r.testDB.GetAllTestSetIDs(ctx) - if err != nil { - stopReason = fmt.Sprintf("failed to get all test set ids: %v", err) - utils.LogError(r.logger, err, stopReason) - return fmt.Errorf("%s", stopReason) - } - - if len(testSetIDs) == 0 { - recordCmd := models.HighlightGrayString("keploy record") - errMsg := fmt.Sprintf("No test sets found in the keploy folder. Please record testcases using %s command", recordCmd) - utils.LogError(r.logger, err, errMsg) - return fmt.Errorf("%s", errMsg) - } - - testRunID, err := r.GetNextTestRunID(ctx) - if err != nil { - stopReason = fmt.Sprintf("failed to get next test run id: %v", err) - utils.LogError(r.logger, err, stopReason) - return fmt.Errorf("%s", stopReason) - } - - var language config.Language - var executable string - // only find language to calculate coverage if instrument is true - if r.instrument { - language, executable = utils.DetectLanguage(r.logger, r.config.Command) - // if language is not provided and language detected is known - // then set the language to detected language - if r.config.Test.Language == "" { - if language == models.Unknown { - r.logger.Warn("failed to detect language, skipping coverage caluclation. please use --language to manually set the language") - r.config.Test.SkipCoverage = true - } else { - r.logger.Warn(fmt.Sprintf("%s language detected. please use --language to manually set the language if needed", language)) - } - r.config.Test.Language = language - } else if language != r.config.Test.Language && language != models.Unknown { - utils.LogError(r.logger, nil, "language detected is different from the language provided") - r.config.Test.SkipCoverage = true - } - } - - r.logger.Debug("language detected", zap.String("language", r.config.Test.Language.String()), zap.String("executable", executable)) - - var cov coverage.Service - switch r.config.Test.Language { - case models.Go: - cov = golang.New(ctx, r.logger, r.reportDB, r.config.Command, r.config.Test.CoverageReportPath, r.config.CommandType) - case models.Python: - // if the executable is not starting with "python" or "python3" then skipCoverage - if !strings.HasPrefix(executable, "python") && !strings.HasPrefix(executable, "python3") { - r.logger.Warn("python command not python or python3, skipping coverage calculation") - r.config.Test.SkipCoverage = true - } - cov = python.New(ctx, r.logger, r.reportDB, r.config.Command, executable) - case models.Javascript: - cov = javascript.New(ctx, r.logger, r.reportDB, r.config.Command) - case models.Java: - cov = java.New(ctx, r.logger, r.reportDB, r.config.Command, r.config.Test.JacocoAgentPath, executable) - default: - r.config.Test.SkipCoverage = true - } - if !r.config.Test.SkipCoverage { - if utils.CmdType(r.config.CommandType) == utils.Native { - r.config.Command, err = cov.PreProcess(r.config.Test.DisableLineCoverage) - - if err != nil { - r.config.Test.SkipCoverage = true - } - } - err = os.Setenv("CLEAN", "true") // related to javascript coverage calculation - if err != nil { - r.config.Test.SkipCoverage = true - r.logger.Warn("failed to set CLEAN env variable, skipping coverage caluclation", zap.Error(err)) - } - } - - // Instrument will load the hooks and start the proxy - inst, err := r.Instrument(ctx) - if err != nil { - stopReason = fmt.Sprintf("failed to instrument: %v", err) - utils.LogError(r.logger, err, stopReason) - if ctx.Err() == context.Canceled { - return err - } - return fmt.Errorf("%s", stopReason) - } - - hookCancel = inst.HookCancel - - var testSetResult bool - testRunResult := true - abortTestRun := false - var flakyTestSets []string - var testSets []string - for _, testSetID := range testSetIDs { - if _, ok := r.config.Test.SelectedTests[testSetID]; !ok && len(r.config.Test.SelectedTests) != 0 { - continue - } - testSets = append(testSets, testSetID) - } - if len(testSets) == 0 { - testSets = testSetIDs - } - - // Sort the testsets. - natsort.Sort(testSets) - - err = HookImpl.BeforeTestRun(ctx, testRunID) - if err != nil { - stopReason = fmt.Sprintf("failed to run before test run hook: %v", err) - utils.LogError(r.logger, err, stopReason) - } - - // setting the appId for the first test-set. - inst.AppID = r.config.AppID - for i, testSet := range testSets { - testSetResult = false - - // Reload hooks before each test set if this is not the first test set - // This ensures fresh eBPF state and prevents issues between test runs - if i > 0 && r.instrument { - - // Cancel the current hooks and wait for cleanup to complete - if hookCancel != nil { - hookCancel() - // Wait for hooks to be completely unloaded using the channel signal - // This ensures that all eBPF resources are properly released before we reload - r.logger.Debug("Waiting for hooks to be completely unloaded", zap.String("testSet", testSet)) - <-inst.UnloadDone - r.logger.Debug("Hooks unload completed", zap.String("testSet", testSet)) - } - - r.logger.Info("Reloading hooks for test set", zap.String("testSet", testSet), zap.Int("testSetIndex", i+1), zap.Int("totalTestSets", len(testSets))) - - // Reload hooks for the new test set with retry mechanism - newInst, err := r.reloadHooks(ctx, inst.AppID) - if err != nil { - stopReason = fmt.Sprintf("failed to reload hooks for test set %s: %v", testSet, err) - utils.LogError(r.logger, err, stopReason) - if ctx.Err() == context.Canceled { - return err - } - return fmt.Errorf("%s", stopReason) - } - hookCancel = newInst.HookCancel - // Update the inst with the new hook cancel function, app ID, and unload done channel - inst.HookCancel = newInst.HookCancel - inst.AppID = newInst.AppID - inst.UnloadDone = newInst.UnloadDone - r.logger.Info("Successfully reloaded hooks for test set", zap.String("testSet", testSet), zap.Uint64("newAppID", newInst.AppID)) - } - - err := HookImpl.BeforeTestSetRun(ctx, testSet) - if err != nil { - stopReason = fmt.Sprintf("failed to run before test hook: %v", err) - utils.LogError(r.logger, err, stopReason) - if ctx.Err() == context.Canceled { - return err - } - return fmt.Errorf("%s", stopReason) - } - - if !r.config.Test.SkipCoverage { - err = os.Setenv("TESTSETID", testSet) // related to java coverage calculation - if err != nil { - r.config.Test.SkipCoverage = true - r.logger.Warn("failed to set TESTSETID env variable, skipping coverage caluclation", zap.Error(err)) - } - } - - // check if its the last testset running - - - if i == len(testSets)-1 { - r.isLastTestSet = true - } - - var ( - initTotal, initPassed, initFailed, initIgnored int - initTimeTaken time.Duration - ) - - initTotal = totalTests - initPassed = totalTestPassed - initFailed = totalTestFailed - initIgnored = totalTestIgnored - initTimeTaken = totalTestTimeTaken - - var initialFailedTCs map[string]bool - flaky := false // only be changed during replay with --must-pass flag set - for attempt := 1; attempt <= int(r.config.Test.MaxFlakyChecks); attempt++ { - - // clearing testcase from map is required for 2 reasons: - // 1st: in next attempt, we need to append results in a fresh array, - // rather than appending in the old array which would contain outdated tc results. - // 2nd: in must-pass mode, we delete the failed testcases from the map - // if the array has some failed testcases, which has already been removed, then not cleaning - // the array would mean deleting the already deleted failed testcases again (error). - r.reportDB.ClearTestCaseResults(ctx, testRunID, testSet) - - // overwrite with values before testset run, so after all reruns we don't get a cummulative value - // gathered from rerunning, instead only metrics from the last rerun would get added to the variables. - totalTests = initTotal - totalTestPassed = initPassed - totalTestFailed = initFailed - totalTestIgnored = initIgnored - totalTestTimeTaken = initTimeTaken - - r.logger.Info("running", zap.String("test-set", models.HighlightString(testSet)), zap.Int("attempt", attempt)) - testSetStatus, err := r.RunTestSet(ctx, testSet, testRunID, inst.AppID, false) - if err != nil { - stopReason = fmt.Sprintf("failed to run test set: %v", err) - utils.LogError(r.logger, err, stopReason) - if ctx.Err() == context.Canceled { - return err - } - return fmt.Errorf("%s", stopReason) - } - switch testSetStatus { - case models.TestSetStatusAppHalted: - testSetResult = false - abortTestRun = true - case models.TestSetStatusInternalErr: - testSetResult = false - abortTestRun = true - case models.TestSetStatusFaultUserApp: - testSetResult = false - abortTestRun = true - case models.TestSetStatusUserAbort: - return nil - case models.TestSetStatusFailed: - testSetResult = false - case models.TestSetStatusPassed: - testSetResult = true - case models.TestSetStatusIgnored: - testSetResult = false - case models.TestSetStatusNoTestsToRun: - testSetResult = false - } - - if testSetStatus != models.TestSetStatusIgnored { - testRunResult = testRunResult && testSetResult - if abortTestRun { - break - } - } - - tcResults, err := r.reportDB.GetTestCaseResults(ctx, testRunID, testSet) - if err != nil { - if testSetStatus != models.TestSetStatusNoTestsToRun { - utils.LogError(r.logger, err, "failed to get testcase results") - } - break - } - failedTcIDs := getFailedTCs(tcResults) - failedTCsBySetID[testSet] = failedTcIDs - - // checking for flakiness when --must-pass flag is not set - // else if --must-pass is set, delete the failed testcases and rerun - if !r.config.Test.MustPass { - // populate the map only once at first iteration for flakiness test - if attempt == 1 { - initialFailedTCs = make(map[string]bool) - for _, id := range failedTcIDs { - initialFailedTCs[id] = true - } - continue - } - // checking if there is no mismatch in failed testcases across max retries - // check both length and value - if len(failedTcIDs) != len(initialFailedTCs) { - utils.LogError(r.logger, nil, "the testset is flaky, rerun the testset with --must-pass flag to remove flaky testcases", zap.String("testSet", testSet)) - // don't run more attempts if the testset is flaky - flakyTestSets = append(flakyTestSets, testSet) - break - } - for _, id := range failedTcIDs { - if _, ok := initialFailedTCs[id]; !ok { - flaky = true - utils.LogError(r.logger, nil, "the testset is flaky, rerun the testset with --must-pass flag to remove flaky testcases", zap.String("testSet", testSet)) - break - } - } - if flaky { - // don't run more attempts if the testset is flaky - flakyTestSets = append(flakyTestSets, testSet) - break - } - continue - } - - // this would be executed only when --must-pass flag is set - // we would be removing failed testcases - if r.config.Test.MaxFailAttempts == 0 { - utils.LogError(r.logger, nil, "no. of testset failure occured during rerun reached maximum limit, testset still failing, increase count of maxFailureAttempts", zap.String("testSet", testSet)) - break - } - if len(failedTcIDs) == 0 { - // if no testcase failed in this attempt move to next attempt - continue - } - - r.logger.Info("deleting failing testcases", zap.String("testSet", testSet), zap.Any("testCaseIDs", failedTcIDs)) - - if err := r.testDB.DeleteTests(ctx, testSet, failedTcIDs); err != nil { - utils.LogError(r.logger, err, "failed to delete failing testcases", zap.String("testSet", testSet), zap.Any("testCaseIDs", failedTcIDs)) - break - } - // after deleting rerun it maxFlakyChecks times to be sure that no further testcase fails - // and if it does then delete those failing testcases and rerun it again maxFlakyChecks times - r.config.Test.MaxFailAttempts-- - attempt = 0 - } - - if abortTestRun { - break - } - - err = HookImpl.AfterTestSetRun(ctx, testSet, testSetResult) - if err != nil { - utils.LogError(r.logger, err, "failed to execute after test set run hook", zap.Any("testSet", testSet)) - } - - if i == 0 && !r.config.Test.SkipCoverage { - err = os.Setenv("CLEAN", "false") // related to javascript coverage calculation - if err != nil { - r.config.Test.SkipCoverage = true - r.logger.Warn("failed to set CLEAN env variable, skipping coverage caluclation.", zap.Error(err)) - } - err = os.Setenv("APPEND", "--append") // related to python coverage calculation - if err != nil { - r.config.Test.SkipCoverage = true - r.logger.Warn("failed to set APPEND env variable, skipping coverage caluclation.", zap.Error(err)) - } - } - } - if !r.config.Test.SkipCoverage && r.config.Test.Language == models.Java { - err = java.MergeAndGenerateJacocoReport(ctx, r.logger) - if err != nil { - r.config.Test.SkipCoverage = true - } - } - - if len(flakyTestSets) > 0 { - r.logger.Warn("flaky testsets detected, please rerun the specific testsets with --must-pass flag to remove flaky testcases", zap.Any("testSets", flakyTestSets)) - } - - testRunStatus := "fail" - if testRunResult { - testRunStatus = "pass" - } - - if testRunResult && r.config.Test.DisableMockUpload { - r.logger.Warn("To enable storing mocks in cloud, please use --disableMockUpload=false flag or test:disableMockUpload:false in config file") - } - - r.telemetry.TestRun(totalTestPassed, totalTestFailed, len(testSets), testRunStatus) - - if !abortTestRun { - r.printSummary(ctx, testRunResult) - coverageData := models.TestCoverage{} - var err error - if !r.config.Test.SkipCoverage { - r.logger.Info("calculating coverage for the test run and inserting it into the report") - coverageData, err = cov.GetCoverage() - if err == nil { - r.logger.Sugar().Infoln(models.HighlightPassingString("Total Coverage Percentage: ", coverageData.TotalCov)) - err = cov.AppendCoverage(&coverageData, testRunID) - if err != nil { - utils.LogError(r.logger, err, "failed to update report with the coverage data") - } - - } else { - r.logger.Warn("failed to calculate coverage for the test run", zap.Any("error", err)) - } - } - - //executing afterTestRun hook, executed after running all the test-sets - err = HookImpl.AfterTestRun(ctx, testRunID, testSets, coverageData) - if err != nil { - utils.LogError(r.logger, err, "failed to execute after test run hook") - } - } - - // return non-zero error code so that pipeline processes - // know that there is a failure in tests - if !testRunResult { - utils.ErrCode = 1 - } - return nil -} - -func (r *Replayer) Instrument(ctx context.Context) (*InstrumentState, error) { - if !r.instrument { - r.logger.Info("Keploy will not mock the outgoing calls when base path is provided", zap.Any("base path", r.config.Test.BasePath)) - return &InstrumentState{}, nil - } - appID, err := r.instrumentation.Setup(ctx, r.config.Command, models.SetupOptions{Container: r.config.ContainerName, DockerNetwork: r.config.NetworkName, DockerDelay: r.config.BuildDelay}) - if err != nil { - if errors.Is(err, context.Canceled) { - return &InstrumentState{}, err - } - return &InstrumentState{}, fmt.Errorf("failed to setup instrumentation: %w", err) - } - r.config.AppID = appID - - var cancel context.CancelFunc - // starting the hooks and proxy - select { - case <-ctx.Done(): - return &InstrumentState{}, context.Canceled - default: - hookCtx := context.WithoutCancel(ctx) - hookCtx, cancel = context.WithCancel(hookCtx) - err = r.instrumentation.Hook(hookCtx, appID, models.HookOptions{Mode: models.MODE_TEST, EnableTesting: r.config.EnableTesting, Rules: r.config.BypassRules}) - if err != nil { - cancel() - if errors.Is(err, context.Canceled) { - return &InstrumentState{}, err - } - return &InstrumentState{}, fmt.Errorf("failed to start the hooks and proxy: %w", err) - } - } - return &InstrumentState{AppID: appID, HookCancel: cancel, UnloadDone: r.instrumentation.GetHookUnloadDone(appID)}, nil -} - -// reloadHooks cancels existing hooks and reloads them for the next test set. -// This ensures that any stale eBPF state is cleared and fresh hooks are loaded, -// which can help with reliability and prevent issues between test set runs. -// This method handles the app context preservation to avoid app deletion during reload. -func (r *Replayer) reloadHooks(ctx context.Context, appID uint64) (*InstrumentState, error) { - if !r.instrument { - return &InstrumentState{}, nil - } - - r.logger.Debug("Reloading eBPF hooks", zap.Uint64("appID", appID)) - - // The challenge is that calling Hook again will set up new cleanup that deletes the app - // when the context is canceled. We need to create a fresh setup. - - // First, set up the app again since it might have been deleted during cleanup - newAppID, err := r.instrumentation.Setup(ctx, r.config.Command, models.SetupOptions{ - Container: r.config.ContainerName, - DockerNetwork: r.config.NetworkName, - DockerDelay: r.config.BuildDelay, - }) - if err != nil { - return &InstrumentState{}, fmt.Errorf("failed to setup instrumentation during hook reload: %w", err) - } - - // Update the config with the new app ID - r.config.AppID = newAppID - - // Create a retry mechanism in case of temporary race conditions - var lastErr error - maxRetries := 5 - baseDelay := 200 * time.Millisecond - - for attempt := 1; attempt <= maxRetries; attempt++ { - // Check for context cancellation - select { - case <-ctx.Done(): - return &InstrumentState{}, context.Canceled - default: - } - - // Add a small delay before each attempt to let any remaining cleanup finish - if attempt > 1 { - delay := baseDelay * time.Duration(attempt) // Linear backoff - r.logger.Debug("Retrying hook reload", zap.Int("attempt", attempt), zap.Duration("delay", delay)) - time.Sleep(delay) - } - - // Start fresh hooks with the new app ID - hookCtx := context.WithoutCancel(ctx) - hookCtx, cancel := context.WithCancel(hookCtx) - - err := r.instrumentation.Hook(hookCtx, newAppID, models.HookOptions{ - Mode: models.MODE_TEST, - EnableTesting: r.config.EnableTesting, - Rules: r.config.BypassRules, - }) - if err != nil { - cancel() - lastErr = err - if errors.Is(err, context.Canceled) { - return &InstrumentState{}, err - } - // If this failed due to a race condition, wait and retry with exponential backoff - if attempt < maxRetries { - delay := baseDelay * time.Duration(attempt*attempt) // Quadratic backoff - r.logger.Debug("Hook reload failed, retrying", zap.Int("attempt", attempt), zap.Duration("delay", delay), zap.Error(err)) - time.Sleep(delay) - continue - } - return &InstrumentState{}, fmt.Errorf("failed to reload hooks after %d attempts: %w", maxRetries, lastErr) - } - - // Success - return the new hook state with the new app ID - r.logger.Debug("Successfully reloaded eBPF hooks", zap.Uint64("oldAppID", appID), zap.Uint64("newAppID", newAppID), zap.Int("attempt", attempt)) - return &InstrumentState{AppID: newAppID, HookCancel: cancel, UnloadDone: r.instrumentation.GetHookUnloadDone(newAppID)}, nil - } - - // This should never be reached, but just in case - return &InstrumentState{}, fmt.Errorf("failed to reload hooks after %d attempts: %w", maxRetries, lastErr) -} - -func (r *Replayer) GetNextTestRunID(ctx context.Context) (string, error) { - testRunIDs, err := r.reportDB.GetAllTestRunIDs(ctx) - if err != nil { - if errors.Is(err, context.Canceled) { - return "", err - } - return "", fmt.Errorf("failed to get all test run ids: %w", err) - } - return pkg.NextID(testRunIDs, models.TestRunTemplateName), nil -} - -func (r *Replayer) GetAllTestSetIDs(ctx context.Context) ([]string, error) { - return r.testDB.GetAllTestSetIDs(ctx) -} - -func (r *Replayer) GetTestCases(ctx context.Context, testID string) ([]*models.TestCase, error) { - return r.testDB.GetTestCases(ctx, testID) -} - -func (r *Replayer) RunTestSet(ctx context.Context, testSetID string, testRunID string, appID uint64, serveTest bool) (models.TestSetStatus, error) { - - // creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller - runTestSetErrGrp, runTestSetCtx := errgroup.WithContext(ctx) - runTestSetCtx = context.WithValue(runTestSetCtx, models.ErrGroupKey, runTestSetErrGrp) - runTestSetCtx, runTestSetCtxCancel := context.WithCancel(runTestSetCtx) - - startTime := time.Now() - - exitLoopChan := make(chan bool, 2) - defer func() { - runTestSetCtxCancel() - err := runTestSetErrGrp.Wait() - if err != nil { - utils.LogError(r.logger, err, "error in testLoopErrGrp") - } - close(exitLoopChan) - }() - - testCases, err := r.testDB.GetTestCases(runTestSetCtx, testSetID) - if err != nil { - return models.TestSetStatusFailed, fmt.Errorf("failed to get test cases: %w", err) - } - - if len(testCases) == 0 { - r.logger.Warn("no valid test cases found to run for test set", zap.String("test-set", testSetID)) - - testReport := &models.TestReport{ - Version: models.GetVersion(), - TestSet: testSetID, - Status: string(models.TestSetStatusNoTestsToRun), - Total: 0, - Ignored: 0, - } - err = r.reportDB.InsertReport(runTestSetCtx, testRunID, testSetID, testReport) - if err != nil { - utils.LogError(r.logger, err, "failed to insert report") - return models.TestSetStatusFailed, err - } - return models.TestSetStatusNoTestsToRun, nil - } - - if _, ok := r.config.Test.IgnoredTests[testSetID]; ok && len(r.config.Test.IgnoredTests[testSetID]) == 0 { - testReport := &models.TestReport{ - Version: models.GetVersion(), - TestSet: testSetID, - Status: string(models.TestSetStatusIgnored), - Total: len(testCases), - Ignored: len(testCases), - } - - err = r.reportDB.InsertReport(runTestSetCtx, testRunID, testSetID, testReport) - if err != nil { - utils.LogError(r.logger, err, "failed to insert report") - return models.TestSetStatusFailed, err - } - - verdict := TestReportVerdict{ - total: testReport.Total, - failed: 0, - passed: 0, - ignored: testReport.Ignored, - status: true, - duration: time.Duration(0), - } - - completeTestReport[testSetID] = verdict - totalTests += testReport.Total - totalTestIgnored += testReport.Ignored - - return models.TestSetStatusIgnored, nil - } - - var conf *models.TestSet - conf, err = r.testSetConf.Read(runTestSetCtx, testSetID) - if err != nil { - if strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "The system cannot find the file specified") { - r.logger.Info("test-set config file not found, continuing execution...", zap.String("test-set", testSetID)) - } else { - return models.TestSetStatusFailed, fmt.Errorf("failed to read test set config: %w", err) - } - } - if conf == nil { - conf = &models.TestSet{} - } - - if conf.PreScript != "" { - r.logger.Info("Running Pre-script", zap.String("script", conf.PreScript), zap.String("test-set", testSetID)) - err := r.executeScript(runTestSetCtx, conf.PreScript) - if err != nil { - return models.TestSetStatusFaultScript, fmt.Errorf("failed to execute pre-script: %w", err) - } - } - - var appErrChan = make(chan models.AppError, 1) - var appErr models.AppError - var success int - var failure int - var ignored int - var totalConsumedMocks = map[string]models.MockState{} - - testSetStatus := models.TestSetStatusPassed - testSetStatusByErrChan := models.TestSetStatusRunning - - cmdType := utils.CmdType(r.config.CommandType) - var userIP string - - filteredMocks, unfilteredMocks, err := r.GetMocks(ctx, testSetID, models.BaseTime, time.Now()) - if err != nil { - return models.TestSetStatusFailed, err - } - - pkg.InitSortCounter(int64(max(len(filteredMocks), len(unfilteredMocks)))) - - err = r.instrumentation.MockOutgoing(runTestSetCtx, appID, models.OutgoingOptions{ - Rules: r.config.BypassRules, - MongoPassword: r.config.Test.MongoPassword, - SQLDelay: time.Duration(r.config.Test.Delay), - FallBackOnMiss: r.config.Test.FallBackOnMiss, - Mocking: r.config.Test.Mocking, - Backdate: testCases[0].HTTPReq.Timestamp, - }) - if err != nil { - utils.LogError(r.logger, err, "failed to mock outgoing") - return models.TestSetStatusFailed, err - } - - // filtering is redundant, but we need to set the mocks - err = r.FilterAndSetMocks(ctx, appID, filteredMocks, unfilteredMocks, models.BaseTime, time.Now(), totalConsumedMocks) - if err != nil { - return models.TestSetStatusFailed, err - } - - if r.instrument { - if !serveTest { - runTestSetErrGrp.Go(func() error { - defer utils.Recover(r.logger) - appErr = r.RunApplication(runTestSetCtx, appID, models.RunOptions{ - AppCommand: conf.AppCommand, - }) - if appErr.AppErrorType == models.ErrCtxCanceled { - return nil - } - appErrChan <- appErr - return nil - }) - } - - // Checking for errors in the mocking and application - runTestSetErrGrp.Go(func() error { - defer utils.Recover(r.logger) - select { - case err := <-appErrChan: - switch err.AppErrorType { - case models.ErrCommandError: - testSetStatusByErrChan = models.TestSetStatusFaultUserApp - case models.ErrUnExpected: - testSetStatusByErrChan = models.TestSetStatusAppHalted - case models.ErrAppStopped: - testSetStatusByErrChan = models.TestSetStatusAppHalted - case models.ErrCtxCanceled: - return nil - case models.ErrInternal: - testSetStatusByErrChan = models.TestSetStatusInternalErr - default: - testSetStatusByErrChan = models.TestSetStatusAppHalted - } - utils.LogError(r.logger, err, "application failed to run") - case <-runTestSetCtx.Done(): - testSetStatusByErrChan = models.TestSetStatusUserAbort - } - exitLoopChan <- true - runTestSetCtxCancel() - return nil - }) - - // Delay for user application to run - select { - case <-time.After(time.Duration(r.config.Test.Delay) * time.Second): - case <-runTestSetCtx.Done(): - return models.TestSetStatusUserAbort, context.Canceled - } - - if utils.IsDockerCmd(cmdType) { - userIP, err = r.instrumentation.GetContainerIP(ctx, appID) - if err != nil { - return models.TestSetStatusFailed, err - } - } - } - - selectedTests := matcherUtils.ArrayToMap(r.config.Test.SelectedTests[testSetID]) - ignoredTests := matcherUtils.ArrayToMap(r.config.Test.IgnoredTests[testSetID]) - - testCasesCount := len(testCases) - - if len(selectedTests) != 0 { - testCasesCount = len(selectedTests) - } - - // Inserting the initial report for the test set - testReport := &models.TestReport{ - Version: models.GetVersion(), - Total: testCasesCount, - Status: string(models.TestStatusRunning), - } - - err = r.reportDB.InsertReport(runTestSetCtx, testRunID, testSetID, testReport) - if err != nil { - utils.LogError(r.logger, err, "failed to insert report") - return models.TestSetStatusFailed, err - } - - // var to exit the loop - var exitLoop bool - // var to store the error in the loop - var loopErr error - utils.TemplatizedValues = conf.Template - utils.SecretValues = conf.Secret - - // Add secret files to .gitignore if they exist - if len(utils.SecretValues) > 0 { - err = utils.AddToGitIgnore(r.logger, r.config.Path, "/*/secret.yaml") - if err != nil { - r.logger.Warn("Failed to add secret files to .gitignore", zap.Error(err)) - } - } - - for idx, testCase := range testCases { - - // check if its the last test case running - if idx == len(testCases)-1 && r.isLastTestSet { - r.isLastTestCase = true - testCase.IsLast = true - } - - if _, ok := selectedTests[testCase.Name]; !ok && len(selectedTests) != 0 { - continue - } - - if _, ok := ignoredTests[testCase.Name]; ok { - testCaseResult := &models.TestResult{ - Kind: models.HTTP, - Name: testSetID, - Status: models.TestStatusIgnored, - TestCaseID: testCase.Name, - TestCasePath: filepath.Join(r.config.Path, testSetID), - MockPath: filepath.Join(r.config.Path, testSetID, "mocks.yaml"), - } - loopErr = r.reportDB.InsertTestCaseResult(runTestSetCtx, testRunID, testSetID, testCaseResult) - if loopErr != nil { - utils.LogError(r.logger, err, "failed to insert test case result") - break - } - ignored++ - continue - } - - // replace the request URL's BasePath/origin if provided - if r.config.Test.BasePath != "" { - newURL, err := ReplaceBaseURL(r.config.Test.BasePath, testCase.HTTPReq.URL) - if err != nil { - r.logger.Warn("failed to replace the request basePath", zap.String("testcase", testCase.Name), zap.String("basePath", r.config.Test.BasePath), zap.Error(err)) - } else { - testCase.HTTPReq.URL = newURL - } - r.logger.Debug("test case request origin", zap.String("testcase", testCase.Name), zap.String("TestCaseURL", testCase.HTTPReq.URL), zap.String("basePath", r.config.Test.BasePath)) - } - - // Checking for errors in the mocking and application - select { - case <-exitLoopChan: - testSetStatus = testSetStatusByErrChan - exitLoop = true - default: - } - - if exitLoop { - break - } - - var testStatus models.TestStatus - var testResult *models.Result - var testPass bool - var loopErr error - - err = r.FilterAndSetMocks(runTestSetCtx, appID, filteredMocks, unfilteredMocks, testCase.HTTPReq.Timestamp, testCase.HTTPResp.Timestamp, totalConsumedMocks) - if err != nil { - utils.LogError(r.logger, err, "failed to filter and set mocks") - break - } - - // Handle Docker environment IP replacement - if utils.IsDockerCmd(cmdType) { - err = r.replaceHostInTestCase(testCase, userIP, "docker container's IP") - if err != nil { - break - } - } - - // Handle user-provided host replacement - if r.config.Test.Host != "" { - err = r.replaceHostInTestCase(testCase, r.config.Test.Host, "host provided by the user") - if err != nil { - break - } - } - - // Handle user-provided port replacement - if r.config.Test.Port != 0 { - err = r.replacePortInTestCase(testCase, strconv.Itoa(int(r.config.Test.Port))) - if err != nil { - break - } - } - - started := time.Now().UTC() - resp, loopErr := HookImpl.SimulateRequest(runTestSetCtx, appID, testCase, testSetID) - if loopErr != nil { - utils.LogError(r.logger, loopErr, "failed to simulate request") - failure++ - testSetStatus = models.TestSetStatusFailed - testCaseResult := r.CreateFailedTestResult(testCase, testSetID, started, loopErr.Error()) - loopErr = r.reportDB.InsertTestCaseResult(runTestSetCtx, testRunID, testSetID, testCaseResult) - if loopErr != nil { - utils.LogError(r.logger, loopErr, "failed to insert test case result for simulation error") - break - } - continue - } - - var consumedMocks []models.MockState - if r.instrument { - consumedMocks, err = HookImpl.GetConsumedMocks(runTestSetCtx, appID) - if err != nil { - utils.LogError(r.logger, err, "failed to get consumed filtered mocks") - } - for _, m := range consumedMocks { - totalConsumedMocks[m.Name] = m - } - } - - r.logger.Debug("test case kind", zap.Any("kind", testCase.Kind)) - - switch testCase.Kind { - case models.HTTP: - httpResp, ok := resp.(*models.HTTPResp) - if !ok { - r.logger.Error("invalid response type for HTTP test case") - failure++ - testSetStatus = models.TestSetStatusFailed - testCaseResult := r.CreateFailedTestResult(testCase, testSetID, started, "invalid response type for HTTP test case") - loopErr = r.reportDB.InsertTestCaseResult(runTestSetCtx, testRunID, testSetID, testCaseResult) - if loopErr != nil { - utils.LogError(r.logger, loopErr, fmt.Sprintf("failed to insert test case result for type assertion error in %s test case", testCase.Kind)) - break - } - continue - } - testPass, testResult = r.compareHTTPResp(testCase, httpResp, testSetID) - - case models.GRPC_EXPORT: - grpcResp, ok := resp.(*models.GrpcResp) - if !ok { - r.logger.Error("invalid response type for gRPC test case") - failure++ - testSetStatus = models.TestSetStatusFailed - testCaseResult := r.CreateFailedTestResult(testCase, testSetID, started, "invalid response type for gRPC test case") - loopErr = r.reportDB.InsertTestCaseResult(runTestSetCtx, testRunID, testSetID, testCaseResult) - if loopErr != nil { - utils.LogError(r.logger, loopErr, "failed to insert test case result for type assertion error") - break - } - continue - } - testPass, testResult = r.compareGRPCResp(testCase, grpcResp, testSetID) - } - - if !testPass { - // log the consumed mocks during the test run of the test case for test set - r.logger.Info("result", zap.Any("testcase id", models.HighlightFailingString(testCase.Name)), zap.Any("testset id", models.HighlightFailingString(testSetID)), zap.Any("passed", models.HighlightFailingString(testPass))) - r.logger.Debug("Consumed Mocks", zap.Any("mocks", consumedMocks)) - } else { - r.logger.Info("result", zap.Any("testcase id", models.HighlightPassingString(testCase.Name)), zap.Any("testset id", models.HighlightPassingString(testSetID)), zap.Any("passed", models.HighlightPassingString(testPass))) - } - if testPass { - testStatus = models.TestStatusPassed - success++ - } else { - testStatus = models.TestStatusFailed - failure++ - testSetStatus = models.TestSetStatusFailed - } - - if testResult != nil { - var testCaseResult *models.TestResult - - switch testCase.Kind { - case models.HTTP: - httpResp := resp.(*models.HTTPResp) - - testCaseResult = &models.TestResult{ - Kind: models.HTTP, - Name: testSetID, - Status: testStatus, - Started: started.Unix(), - Completed: time.Now().UTC().Unix(), - TestCaseID: testCase.Name, - Req: models.HTTPReq{ - Method: testCase.HTTPReq.Method, - ProtoMajor: testCase.HTTPReq.ProtoMajor, - ProtoMinor: testCase.HTTPReq.ProtoMinor, - URL: testCase.HTTPReq.URL, - URLParams: testCase.HTTPReq.URLParams, - Header: testCase.HTTPReq.Header, - Body: testCase.HTTPReq.Body, - Binary: testCase.HTTPReq.Binary, - Form: testCase.HTTPReq.Form, - Timestamp: testCase.HTTPReq.Timestamp, - }, - Res: *httpResp, - TestCasePath: filepath.Join(r.config.Path, testSetID), - MockPath: filepath.Join(r.config.Path, testSetID, "mocks.yaml"), - Noise: testCase.Noise, - Result: *testResult, - } - case models.GRPC_EXPORT: - grpcResp := resp.(*models.GrpcResp) - - testCaseResult = &models.TestResult{ - Kind: models.GRPC_EXPORT, - Name: testSetID, - Status: testStatus, - Started: started.Unix(), - Completed: time.Now().UTC().Unix(), - TestCaseID: testCase.Name, - GrpcReq: testCase.GrpcReq, - GrpcRes: *grpcResp, - TestCasePath: filepath.Join(r.config.Path, testSetID), - MockPath: filepath.Join(r.config.Path, testSetID, "mocks.yaml"), - Noise: testCase.Noise, - Result: *testResult, - } - } - - if testCaseResult != nil { - loopErr = r.reportDB.InsertTestCaseResult(runTestSetCtx, testRunID, testSetID, testCaseResult) - if loopErr != nil { - utils.LogError(r.logger, loopErr, "failed to insert test case result") - break - } - } else { - utils.LogError(r.logger, nil, "test case result is nil") - break - } - } else { - utils.LogError(r.logger, nil, "test result is nil") - break - } - - // We need to sleep for a second to avoid mismatching of mocks during keploy testing via test-bench - if r.config.EnableTesting { - r.logger.Debug("sleeping for a second to avoid mismatching of mocks during keploy testing via test-bench") - time.Sleep(time.Second) - } - } - - if conf.PostScript != "" { - //Execute the Post-script after each test-set if provided - r.logger.Info("Running Post-script", zap.String("script", conf.PostScript), zap.String("test-set", testSetID)) - err = r.executeScript(runTestSetCtx, conf.PostScript) - if err != nil { - return models.TestSetStatusFaultScript, fmt.Errorf("failed to execute post-script: %w", err) - } - } - - timeTaken := time.Since((startTime)) - - testCaseResults, err := r.reportDB.GetTestCaseResults(runTestSetCtx, testRunID, testSetID) - if err != nil { - if runTestSetCtx.Err() != context.Canceled { - utils.LogError(r.logger, err, "failed to get test case results") - testSetStatus = models.TestSetStatusInternalErr - } - } - - // Checking errors for final iteration - // Checking for errors in the loop - if loopErr != nil && !errors.Is(loopErr, context.Canceled) { - testSetStatus = models.TestSetStatusInternalErr - } else { - // Checking for errors in the mocking and application - select { - case <-exitLoopChan: - testSetStatus = testSetStatusByErrChan - default: - } - } - - testReport = &models.TestReport{ - Version: models.GetVersion(), - TestSet: testSetID, - Status: string(testSetStatus), - Total: testCasesCount, - Success: success, - Failure: failure, - Ignored: ignored, - Tests: testCaseResults, - } - - // final report should have reason for sudden stop of the test run so this should get canceled - reportCtx := context.WithoutCancel(runTestSetCtx) - err = r.reportDB.InsertReport(reportCtx, testRunID, testSetID, testReport) - if err != nil { - utils.LogError(r.logger, err, "failed to insert report") - return models.TestSetStatusInternalErr, fmt.Errorf("failed to insert report") - } - - err = utils.AddToGitIgnore(r.logger, r.config.Path, "/reports/") - if err != nil { - utils.LogError(r.logger, err, "failed to create .gitignore file") - } - - // remove the unused mocks by the test cases of a testset (if the base path is not provided ) - if r.config.Test.RemoveUnusedMocks && testSetStatus == models.TestSetStatusPassed && r.instrument { - r.logger.Debug("consumed mocks from the completed testset", zap.Any("for test-set", testSetID), zap.Any("consumed mocks", totalConsumedMocks)) - // delete the unused mocks from the data store - r.logger.Info("deleting unused mocks from the data store", zap.Any("for test-set", testSetID)) - err = r.mockDB.UpdateMocks(runTestSetCtx, testSetID, totalConsumedMocks) - if err != nil { - utils.LogError(r.logger, err, "failed to delete unused mocks") - } - } - - // TODO Need to decide on whether to use global variable or not - verdict := TestReportVerdict{ - total: testReport.Total, - failed: testReport.Failure, - passed: testReport.Success, - ignored: testReport.Ignored, - status: testSetStatus == models.TestSetStatusPassed, - duration: timeTaken, - } - - completeTestReport[testSetID] = verdict - totalTests += testReport.Total - totalTestPassed += testReport.Success - totalTestFailed += testReport.Failure - totalTestIgnored += testReport.Ignored - totalTestTimeTaken += timeTaken - - timeTakenStr := timeWithUnits(timeTaken) - - if testSetStatus == models.TestSetStatusFailed || testSetStatus == models.TestSetStatusPassed { - if testSetStatus == models.TestSetStatusFailed { - pp.SetColorScheme(models.GetFailingColorScheme()) - } else { - pp.SetColorScheme(models.GetPassingColorScheme()) - } - if testReport.Ignored > 0 { - if _, err := pp.Printf("\n <=========================================> \n TESTRUN SUMMARY. For test-set: %s\n"+"\tTotal tests: %s\n"+"\tTotal test passed: %s\n"+"\tTotal test failed: %s\n"+"\tTotal test ignored: %s\n"+"\tTime Taken: %s\n <=========================================> \n\n", testReport.TestSet, testReport.Total, testReport.Success, testReport.Failure, testReport.Ignored, timeTakenStr); err != nil { - utils.LogError(r.logger, err, "failed to print testrun summary") - } - } else { - if _, err := pp.Printf("\n <=========================================> \n TESTRUN SUMMARY. For test-set: %s\n"+"\tTotal tests: %s\n"+"\tTotal test passed: %s\n"+"\tTotal test failed: %s\n"+"\tTime Taken: %s\n <=========================================> \n\n", testReport.TestSet, testReport.Total, testReport.Success, testReport.Failure, timeTakenStr); err != nil { - utils.LogError(r.logger, err, "failed to print testrun summary") - } - } - } - - r.telemetry.TestSetRun(testReport.Success, testReport.Failure, testSetID, string(testSetStatus)) - - if r.config.Test.UpdateTemplate || r.config.Test.BasePath != "" { - utils.RemoveDoubleQuotes(utils.TemplatizedValues) // Write the templatized values to the yaml. - if len(utils.TemplatizedValues) > 0 { - err = r.testSetConf.Write(ctx, testSetID, &models.TestSet{ - PreScript: conf.PreScript, - PostScript: conf.PostScript, - Template: utils.TemplatizedValues, - }) - if err != nil { - utils.LogError(r.logger, err, "failed to write the templatized values to the yaml") - } - } - } - - return testSetStatus, nil -} - -func (r *Replayer) GetMocks(ctx context.Context, testSetID string, afterTime time.Time, beforeTime time.Time) (filtered, unfiltered []*models.Mock, err error) { - filtered, err = r.mockDB.GetFilteredMocks(ctx, testSetID, afterTime, beforeTime) - if err != nil { - utils.LogError(r.logger, err, "failed to get filtered mocks") - return nil, nil, err - } - unfiltered, err = r.mockDB.GetUnFilteredMocks(ctx, testSetID, afterTime, beforeTime) - if err != nil { - utils.LogError(r.logger, err, "failed to get unfiltered mocks") - return nil, nil, err - } - return filtered, unfiltered, err -} - -func (r *Replayer) FilterAndSetMocks(ctx context.Context, appID uint64, filtered, unfiltered []*models.Mock, afterTime, beforeTime time.Time, totalConsumedMocks map[string]models.MockState) error { - if !r.instrument { - r.logger.Debug("Keploy will not filter and set mocks when base path is provided", zap.Any("base path", r.config.Test.BasePath)) - return nil - } - - filtered = pkg.FilterTcsMocks(ctx, r.logger, filtered, afterTime, beforeTime) - unfiltered = pkg.FilterConfigMocks(ctx, r.logger, unfiltered, afterTime, beforeTime) - - filterOutDeleted := func(in []*models.Mock) []*models.Mock { - out := make([]*models.Mock, 0, len(in)) - for _, m := range in { - // treat empty/missing names as never consumed - if m == nil || m.Name == "" { - out = append(out, m) - continue - } - // we are picking mocks that are not consumed till now (not present in map), - // and, mocks that are updated. - if k, ok := totalConsumedMocks[m.Name]; !ok || k.Usage != models.Deleted { - if ok { - m.TestModeInfo.IsFiltered = k.IsFiltered - m.TestModeInfo.SortOrder = k.SortOrder - } - out = append(out, m) - } - } - return out - } - - filtered = filterOutDeleted(filtered) - unfiltered = filterOutDeleted(unfiltered) - - err := r.instrumentation.SetMocks(ctx, appID, filtered, unfiltered) - if err != nil { - utils.LogError(r.logger, err, "failed to set mocks") - return err - } - - return nil -} - -func (r *Replayer) GetTestSetStatus(ctx context.Context, testRunID string, testSetID string) (models.TestSetStatus, error) { - testReport, err := r.reportDB.GetReport(ctx, testRunID, testSetID) - if err != nil { - return models.TestSetStatusFailed, fmt.Errorf("failed to get report: %w", err) - } - status, err := models.StringToTestSetStatus(testReport.Status) - if err != nil { - return models.TestSetStatusFailed, fmt.Errorf("failed to convert string to test set status: %w", err) - } - return status, nil -} - -func (r *Replayer) compareHTTPResp(tc *models.TestCase, actualResponse *models.HTTPResp, testSetID string) (bool, *models.Result) { - noiseConfig := r.config.Test.GlobalNoise.Global - if tsNoise, ok := r.config.Test.GlobalNoise.Testsets[testSetID]; ok { - noiseConfig = LeftJoinNoise(r.config.Test.GlobalNoise.Global, tsNoise) - } - return httpMatcher.Match(tc, actualResponse, noiseConfig, r.config.Test.IgnoreOrdering, r.logger) -} - -func (r *Replayer) compareGRPCResp(tc *models.TestCase, actualResp *models.GrpcResp, testSetID string) (bool, *models.Result) { - noiseConfig := r.config.Test.GlobalNoise.Global - if tsNoise, ok := r.config.Test.GlobalNoise.Testsets[testSetID]; ok { - noiseConfig = LeftJoinNoise(r.config.Test.GlobalNoise.Global, tsNoise) - } - - return grpcMatcher.Match(tc, actualResp, noiseConfig, r.logger) - -} - -func (r *Replayer) printSummary(_ context.Context, _ bool) { - if totalTests > 0 { - testSuiteNames := make([]string, 0, len(completeTestReport)) - for testSuiteName := range completeTestReport { - testSuiteNames = append(testSuiteNames, testSuiteName) - } - sort.SliceStable(testSuiteNames, func(i, j int) bool { - testSuitePartsI := strings.Split(testSuiteNames[i], "-") - testSuitePartsJ := strings.Split(testSuiteNames[j], "-") - if len(testSuitePartsI) < 3 || len(testSuitePartsJ) < 3 { - return testSuiteNames[i] < testSuiteNames[j] - } - testSuiteIDNumberI, err1 := strconv.Atoi(testSuitePartsI[2]) - testSuiteIDNumberJ, err2 := strconv.Atoi(testSuitePartsJ[2]) - if err1 != nil || err2 != nil { - return false - } - return testSuiteIDNumberI < testSuiteIDNumberJ - }) - - totalTestTimeTakenStr := timeWithUnits(totalTestTimeTaken) - - if totalTestIgnored > 0 { - if _, err := pp.Printf("\n <=========================================> \n COMPLETE TESTRUN SUMMARY. \n\tTotal tests: %s\n"+"\tTotal test passed: %s\n"+"\tTotal test failed: %s\n"+"\tTotal test ignored: %s\n"+"\tTotal time taken: %s\n", totalTests, totalTestPassed, totalTestFailed, totalTestIgnored, totalTestTimeTakenStr); err != nil { - utils.LogError(r.logger, err, "failed to print test run summary") - return - } - } else { - if _, err := pp.Printf("\n <=========================================> \n COMPLETE TESTRUN SUMMARY. \n\tTotal tests: %s\n"+"\tTotal test passed: %s\n"+"\tTotal test failed: %s\n"+"\tTotal time taken: %s\n", totalTests, totalTestPassed, totalTestFailed, totalTestTimeTakenStr); err != nil { - utils.LogError(r.logger, err, "failed to print test run summary") - return - } - } - - header := "\n\tTest Suite Name\t\tTotal Test\tPassed\t\tFailed" - if totalTestIgnored > 0 { - header += "\t\tIgnored" - } - header += "\t\tTime Taken" - if totalTestFailed > 0 { - header += "\tFailed Testcases" - } - header += "\t\n" - - _, err := pp.Printf(header) - if err != nil { - utils.LogError(r.logger, err, "failed to print test suite summary header") - return - } - - for _, testSuiteName := range testSuiteNames { - report := completeTestReport[testSuiteName] - if report.status { - pp.SetColorScheme(models.GetPassingColorScheme()) - } else { - pp.SetColorScheme(models.GetFailingColorScheme()) - } - - testSetTimeTakenStr := timeWithUnits(report.duration) - - var format strings.Builder - args := []interface{}{} - - // Using a more dynamic way to build format string and arguments - // to ensure correct tabbing and conditional column - format.WriteString("\n\t%s\t\t%s\t\t%s\t\t%s") - args = append(args, testSuiteName, report.total, report.passed, report.failed) - - if totalTestIgnored > 0 && !r.config.Test.MustPass { - format.WriteString("\t\t%s") - args = append(args, report.ignored) - } - - format.WriteString("\t\t%s") // Time Taken - args = append(args, testSetTimeTakenStr) - - if totalTestFailed > 0 && !r.config.Test.MustPass { - failedCasesStr := "-" - if failedCases, ok := failedTCsBySetID[testSuiteName]; ok && len(failedCases) > 0 { - failedCasesStr = strings.Join(failedCases, ", ") - } - format.WriteString("\t%s") - args = append(args, failedCasesStr) - } - - if _, err := pp.Printf(format.String(), args...); err != nil { - utils.LogError(r.logger, err, "failed to print test suite details") - return - } - } - if _, err := pp.Printf("\n<=========================================> \n\n"); err != nil { - utils.LogError(r.logger, err, "failed to print separator") - return - } - } -} - -func (r *Replayer) RunApplication(ctx context.Context, appID uint64, opts models.RunOptions) models.AppError { - return r.instrumentation.Run(ctx, appID, opts) -} - -func (r *Replayer) GetTestSetConf(ctx context.Context, testSet string) (*models.TestSet, error) { - return r.testSetConf.Read(ctx, testSet) -} - -func (r *Replayer) DenoiseTestCases(ctx context.Context, testSetID string, noiseParams []*models.NoiseParams) ([]*models.NoiseParams, error) { - - testCases, err := r.testDB.GetTestCases(ctx, testSetID) - if err != nil { - return nil, fmt.Errorf("failed to get test cases: %w", err) - } - - for _, v := range testCases { - for _, noiseParam := range noiseParams { - if v.Name == noiseParam.TestCaseID { - // append the noise map - if noiseParam.Ops == string(models.OpsAdd) { - v.Noise = mergeMaps(v.Noise, noiseParam.Assertion) - } else { - // remove from the original noise map - v.Noise = removeFromMap(v.Noise, noiseParam.Assertion) - } - err = r.testDB.UpdateTestCase(ctx, v, testSetID, true) - if err != nil { - return nil, fmt.Errorf("failed to update test case: %w", err) - } - noiseParam.AfterNoise = v.Noise - } - } - } - - return noiseParams, nil -} - -func (r *Replayer) Normalize(ctx context.Context) error { - - var testRun string - if r.config.Normalize.TestRun == "" { - testRunIDs, err := r.reportDB.GetAllTestRunIDs(ctx) - if err != nil { - if errors.Is(err, context.Canceled) { - return err - } - return fmt.Errorf("failed to get all test run ids: %w", err) - } - testRun = pkg.LastID(testRunIDs, models.TestRunTemplateName) - } - - if len(r.config.Normalize.SelectedTests) == 0 { - testSetIDs, err := r.testDB.GetAllTestSetIDs(ctx) - if err != nil { - if errors.Is(err, context.Canceled) { - return err - } - return fmt.Errorf("failed to get all test set ids: %w", err) - } - for _, testSetID := range testSetIDs { - r.config.Normalize.SelectedTests = append(r.config.Normalize.SelectedTests, config.SelectedTests{TestSet: testSetID}) - } - } - - for _, testSet := range r.config.Normalize.SelectedTests { - testSetID := testSet.TestSet - testCases := testSet.Tests - err := r.NormalizeTestCases(ctx, testRun, testSetID, testCases, nil) - if err != nil { - return err - } - } - r.logger.Info("Normalized test cases successfully. Please run keploy tests to verify the changes.") - return nil -} - -func (r *Replayer) NormalizeTestCases(ctx context.Context, testRun string, testSetID string, selectedTestCaseIDs []string, testCaseResults []models.TestResult) error { - - if len(testCaseResults) == 0 { - testReport, err := r.reportDB.GetReport(ctx, testRun, testSetID) - if err != nil { - return fmt.Errorf("failed to get test report: %w", err) - } - testCaseResults = testReport.Tests - } - - testCaseResultMap := make(map[string]models.TestResult) - testCases, err := r.testDB.GetTestCases(ctx, testSetID) - if err != nil { - return fmt.Errorf("failed to get test cases: %w", err) - } - selectedTestCases := make([]*models.TestCase, 0, len(selectedTestCaseIDs)) - - if len(selectedTestCaseIDs) == 0 { - selectedTestCases = testCases - } else { - for _, testCase := range testCases { - if _, ok := matcherUtils.ArrayToMap(selectedTestCaseIDs)[testCase.Name]; ok { - selectedTestCases = append(selectedTestCases, testCase) - } - } - } - - for _, testCaseResult := range testCaseResults { - testCaseResultMap[testCaseResult.TestCaseID] = testCaseResult - } - - for _, testCase := range selectedTestCases { - if _, ok := testCaseResultMap[testCase.Name]; !ok { - r.logger.Info("test case not found in the test report", zap.String("test-case-id", testCase.Name), zap.String("test-set-id", testSetID)) - continue - } - if testCaseResultMap[testCase.Name].Status == models.TestStatusPassed { - continue - } - // Handle normalization based on test case kind - switch testCase.Kind { - case models.HTTP: - // Store the original timestamp to preserve it during normalization - originalTimestamp := testCase.HTTPResp.Timestamp - testCase.HTTPResp = testCaseResultMap[testCase.Name].Res - // Restore the original timestamp after normalization - testCase.HTTPResp.Timestamp = originalTimestamp - - case models.GRPC_EXPORT: - // Store the original timestamp to preserve it during normalization - originalTimestamp := testCase.GrpcResp.Timestamp - testCase.GrpcResp = testCaseResultMap[testCase.Name].GrpcRes - // Restore the original timestamp after normalization - testCase.GrpcResp.Timestamp = originalTimestamp - } - err = r.testDB.UpdateTestCase(ctx, testCase, testSetID, true) - if err != nil { - return fmt.Errorf("failed to update test case: %w", err) - } - } - return nil -} - -func (r *Replayer) executeScript(ctx context.Context, script string) error { - - if script == "" { - return nil - } - - // Define the function to cancel the command - cmdCancel := func(cmd *exec.Cmd) func() error { - return func() error { - return utils.InterruptProcessTree(r.logger, cmd.Process.Pid, syscall.SIGINT) - } - } - - cmdErr := utils.ExecuteCommand(ctx, r.logger, script, cmdCancel, 25*time.Second) - if cmdErr.Err != nil { - return fmt.Errorf("failed to execute script: %w", cmdErr.Err) - } - return nil -} - -func (r *Replayer) DeleteTestSet(ctx context.Context, testSetID string) error { - return r.testDB.DeleteTestSet(ctx, testSetID) -} - -func (r *Replayer) DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error { - return r.testDB.DeleteTests(ctx, testSetID, testCaseIDs) -} - -func SetTestHooks(testHooks TestHooks) { - HookImpl = testHooks -} - -// CreateFailedTestResult creates a test result for failed test cases -func (r *Replayer) CreateFailedTestResult(testCase *models.TestCase, testSetID string, started time.Time, errorMessage string) *models.TestResult { - testCaseResult := &models.TestResult{ - Kind: testCase.Kind, - Name: testSetID, - Status: models.TestStatusFailed, - Started: started.Unix(), - Completed: time.Now().UTC().Unix(), - TestCaseID: testCase.Name, - TestCasePath: filepath.Join(r.config.Path, testSetID), - MockPath: filepath.Join(r.config.Path, testSetID, "mocks.yaml"), - Noise: testCase.Noise, - } - - var result *models.Result - - switch testCase.Kind { - case models.HTTP: - actualResponse := &models.HTTPResp{ - StatusCode: 0, - Header: make(map[string]string), - Body: errorMessage, - } - - _, result = r.compareHTTPResp(testCase, actualResponse, testSetID) - - testCaseResult.Req = models.HTTPReq{ - Method: testCase.HTTPReq.Method, - ProtoMajor: testCase.HTTPReq.ProtoMajor, - ProtoMinor: testCase.HTTPReq.ProtoMinor, - URL: testCase.HTTPReq.URL, - URLParams: testCase.HTTPReq.URLParams, - Header: testCase.HTTPReq.Header, - Body: testCase.HTTPReq.Body, - Binary: testCase.HTTPReq.Binary, - Form: testCase.HTTPReq.Form, - Timestamp: testCase.HTTPReq.Timestamp, - } - testCaseResult.Res = *actualResponse - - case models.GRPC_EXPORT: - actualResponse := &models.GrpcResp{ - Headers: models.GrpcHeaders{ - PseudoHeaders: make(map[string]string), - OrdinaryHeaders: make(map[string]string), - }, - Body: models.GrpcLengthPrefixedMessage{ - DecodedData: errorMessage, - }, - Trailers: models.GrpcHeaders{ - PseudoHeaders: make(map[string]string), - OrdinaryHeaders: make(map[string]string), - }, - } - - _, result = r.compareGRPCResp(testCase, actualResponse, testSetID) - - testCaseResult.GrpcReq = testCase.GrpcReq - testCaseResult.GrpcRes = *actualResponse - } - - if result != nil { - testCaseResult.Result = *result - } - - return testCaseResult -} - -func (r *Replayer) replaceHostInTestCase(testCase *models.TestCase, newHost, logContext string) error { - var err error - switch testCase.Kind { - case models.HTTP: - testCase.HTTPReq.URL, err = utils.ReplaceHost(testCase.HTTPReq.URL, newHost) - if err != nil { - utils.LogError(r.logger, err, fmt.Sprintf("failed to replace host to %s", logContext)) - return err - } - r.logger.Debug("", zap.Any(fmt.Sprintf("replaced %s", logContext), testCase.HTTPReq.URL)) - - case models.GRPC_EXPORT: - testCase.GrpcReq.Headers.PseudoHeaders[":authority"], err = utils.ReplaceGrpcHost(testCase.GrpcReq.Headers.PseudoHeaders[":authority"], newHost) - if err != nil { - utils.LogError(r.logger, err, fmt.Sprintf("failed to replace host to %s", logContext)) - return err - } - r.logger.Debug("", zap.Any(fmt.Sprintf("replaced %s", logContext), testCase.GrpcReq.Headers.PseudoHeaders[":authority"])) - } - return nil -} - -func (r *Replayer) replacePortInTestCase(testCase *models.TestCase, newPort string) error { - var err error - switch testCase.Kind { - case models.HTTP: - testCase.HTTPReq.URL, err = utils.ReplacePort(testCase.HTTPReq.URL, newPort) - case models.GRPC_EXPORT: - testCase.GrpcReq.Headers.PseudoHeaders[":authority"], err = utils.ReplaceGrpcPort(testCase.GrpcReq.Headers.PseudoHeaders[":authority"], newPort) - } - if err != nil { - utils.LogError(r.logger, err, "failed to replace port") - return err - } - return nil -} - -func (r *Replayer) GetSelectedTestSets(ctx context.Context) ([]string, error) { - // get all the testset ids - testSetIDs, err := r.testDB.GetAllTestSetIDs(ctx) - if err != nil { - if errors.Is(err, context.Canceled) { - return nil, err - } - utils.LogError(r.logger, err, "failed to get all test set ids") - return nil, fmt.Errorf("mocks downloading failed to due to error in getting test set ids") - } - - var testSets []string - for _, testSetID := range testSetIDs { - if _, ok := r.config.Test.SelectedTests[testSetID]; !ok && len(r.config.Test.SelectedTests) != 0 { - continue - } - testSets = append(testSets, testSetID) - } - if len(testSets) == 0 { - testSets = testSetIDs - } - - // Sort the testsets. - natsort.Sort(testSets) - return testSets, nil -} - -func (r *Replayer) authenticateUser(ctx context.Context) error { - //authenticate the user - token, err := r.auth.GetToken(ctx) - if err != nil { - r.logger.Error("Failed to Authenticate user", zap.Error(err)) - r.logger.Warn("Looks like you haven't logged in, skipping mock upload/download") - r.logger.Warn("Please login using `keploy login` to perform mock upload/download action") - return fmt.Errorf("mocks downloading failed to due to authentication error") - } - - r.mock.setToken(token) - return nil -} - -func (r *Replayer) DownloadMocks(ctx context.Context) error { - // Authenticate the user for mock registry - err := r.authenticateUser(ctx) - if err != nil { - return err - } - - testSets, err := r.GetSelectedTestSets(ctx) - if err != nil { - utils.LogError(r.logger, err, "failed to get selected test sets") - return fmt.Errorf("mocks downloading failed to due to error in getting selected test sets") - } - - for _, testSetID := range testSets { - r.logger.Info("Downloading mocks for the testset", zap.String("testset", testSetID)) - - err := r.mock.download(ctx, testSetID) - if err != nil { - if errors.Is(err, context.Canceled) { - return err - } - utils.LogError(r.logger, err, "failed to download mocks", zap.Any("testset", testSetID)) - continue - } - - } - return nil -} - -func (r *Replayer) UploadMocks(ctx context.Context) error { - // Authenticate the user for mock registry - err := r.authenticateUser(ctx) - if err != nil { - return err - } - - testSets, err := r.GetSelectedTestSets(ctx) - if err != nil { - utils.LogError(r.logger, err, "failed to get selected test sets") - return fmt.Errorf("mocks uploading failed to due to error in getting selected test sets") - } - - for _, testSetID := range testSets { - r.logger.Info("Uploading mocks for the testset", zap.String("testset", testSetID)) - - err := r.mock.upload(ctx, testSetID) - if err != nil { - if errors.Is(err, context.Canceled) { - return err - } - utils.LogError(r.logger, err, "failed to upload mocks", zap.Any("testset", testSetID)) - continue - } - - } - - return nil -} diff --git a/keploy/pkg/service/replay/replay_test.go b/keploy/pkg/service/replay/replay_test.go deleted file mode 100644 index 1085da2..0000000 --- a/keploy/pkg/service/replay/replay_test.go +++ /dev/null @@ -1,265 +0,0 @@ -package replay - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestReplay_UnloadDoneChannelUsage(t *testing.T) { - // Test simulating the hook reload logic that uses the UnloadDone channel - - // Create mock instrumentation - mockInstr := NewMockInstrumentation() - appID := uint64(12345) - - // Get initial instrument state - unloadCh := mockInstr.GetHookUnloadDone(appID) - inst := &InstrumentState{ - AppID: appID, - HookCancel: func() {}, // Mock cancel function - UnloadDone: unloadCh, - } - - // Simulate the hook reload scenario - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - // Test the waiting logic similar to what's in replay.go - waitComplete := make(chan bool, 1) - - go func() { - // This simulates the actual wait logic from replay.go: - // <- inst.UnloadDone - <-inst.UnloadDone - waitComplete <- true - }() - - // Verify that the goroutine is waiting - select { - case <-waitComplete: - t.Error("Should not complete before unload signal") - case <-time.After(50 * time.Millisecond): - // Expected behavior - still waiting - } - - // Simulate hooks being unloaded (this would happen when hookCancel() is called) - mockInstr.CloseUnloadChannel(appID) - - // Now the wait should complete - select { - case result := <-waitComplete: - assert.True(t, result, "Wait should complete successfully") - case <-ctx.Done(): - t.Error("Wait should complete before context timeout") - } -} - -func TestReplay_MultipleTestSetReload(t *testing.T) { - // Test simulating multiple test set reloads with different channels - - mockInstr := NewMockInstrumentation() - - // Simulate processing multiple test sets - testSets := []string{"test-set-1", "test-set-2", "test-set-3"} - - var currentInst *InstrumentState - - for i, testSet := range testSets { - appID := uint64(12345 + i) // Different app ID for each test set - - // Get new instrument state for this test set - unloadCh := mockInstr.GetHookUnloadDone(appID) - newInst := &InstrumentState{ - AppID: appID, - HookCancel: func() {}, - UnloadDone: unloadCh, - } - - // If this is not the first test set, simulate waiting for previous unload - if i > 0 && currentInst != nil { - - // Start waiting for unload in background - unloadComplete := make(chan bool, 1) - go func(inst *InstrumentState) { - <-inst.UnloadDone - unloadComplete <- true - }(currentInst) - - // Simulate canceling previous hooks - // In real code: hookCancel() would trigger the unload - mockInstr.CloseUnloadChannel(currentInst.AppID) - - // Wait for unload to complete - select { - case <-unloadComplete: - // Expected behavior - case <-time.After(100 * time.Millisecond): - t.Errorf("Unload should complete for test set %s", testSet) - } - } - - // Update to new instrument state - currentInst = newInst - - // Verify the new channel is not closed - select { - case <-currentInst.UnloadDone: - t.Errorf("New channel for test set %s should not be closed", testSet) - default: - // Expected behavior - } - } -} - -func TestReplay_ConcurrentUnloadWaiters(t *testing.T) { - // Test multiple goroutines waiting on the same unload channel - // This simulates scenarios where multiple operations might be waiting - - mockInstr := NewMockInstrumentation() - appID := uint64(12345) - - unloadCh := mockInstr.GetHookUnloadDone(appID) - inst := &InstrumentState{ - AppID: appID, - HookCancel: func() {}, - UnloadDone: unloadCh, - } - - // Start multiple waiters - numWaiters := 3 - waitComplete := make(chan int, numWaiters) - - for i := 0; i < numWaiters; i++ { - go func(waiterID int) { - <-inst.UnloadDone - waitComplete <- waiterID - }(i) - } - - // Verify all are waiting - select { - case <-waitComplete: - t.Error("No waiter should complete before unload signal") - case <-time.After(50 * time.Millisecond): - // Expected behavior - } - - // Signal unload completion - mockInstr.CloseUnloadChannel(appID) - - // All waiters should complete - completedWaiters := make(map[int]bool) - for i := 0; i < numWaiters; i++ { - select { - case waiterID := <-waitComplete: - completedWaiters[waiterID] = true - case <-time.After(100 * time.Millisecond): - t.Errorf("Waiter %d should complete", i) - } - } - - // Verify all waiters completed - assert.Equal(t, numWaiters, len(completedWaiters), "All waiters should complete") -} - -func TestReplay_UnloadDoneChannelTimeout(t *testing.T) { - // Test scenario where unload takes too long and times out - - mockInstr := NewMockInstrumentation() - appID := uint64(12345) - - unloadCh := mockInstr.GetHookUnloadDone(appID) - inst := &InstrumentState{ - AppID: appID, - HookCancel: func() {}, - UnloadDone: unloadCh, - } - - // Create a context with short timeout - ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) - defer cancel() - - // Wait for either unload or timeout - select { - case <-inst.UnloadDone: - t.Error("Unload should not complete without signal") - case <-ctx.Done(): - // Expected behavior - timeout occurred - assert.Equal(t, context.DeadlineExceeded, ctx.Err(), "Should timeout") - } -} - -func TestReplay_UnloadDoneChannelImmediate(t *testing.T) { - // Test scenario where channel is already closed (immediate completion) - - mockInstr := NewMockInstrumentation() - appID := uint64(12345) - - // Get the channel first - unloadCh := mockInstr.GetHookUnloadDone(appID) - - // Then close it immediately - mockInstr.CloseUnloadChannel(appID) - - inst := &InstrumentState{ - AppID: appID, - HookCancel: func() {}, - UnloadDone: unloadCh, - } - - // Wait should complete immediately - done := make(chan bool, 1) - go func() { - <-inst.UnloadDone - done <- true - }() - - select { - case result := <-done: - assert.True(t, result, "Should complete immediately") - case <-time.After(100 * time.Millisecond): - t.Error("Should complete immediately when channel is already closed") - } -} - -func TestReplay_InstrumentStateChannelIntegrity(t *testing.T) { - // Test that the InstrumentState properly maintains the channel reference - - mockInstr := NewMockInstrumentation() - appID := uint64(12345) - - unloadCh := mockInstr.GetHookUnloadDone(appID) - inst := &InstrumentState{ - AppID: appID, - HookCancel: func() {}, - UnloadDone: unloadCh, - } - - // Verify channel is accessible through the struct - assert.NotNil(t, inst.UnloadDone, "UnloadDone channel should be accessible") - - // Verify it's the same channel - directCh := mockInstr.GetHookUnloadDone(appID) - assert.Equal(t, directCh, inst.UnloadDone, "Should be the same channel") - - // Test channel operations work through the struct - testComplete := make(chan bool, 1) - go func() { - <-inst.UnloadDone - testComplete <- true - }() - - // Close through mock - mockInstr.CloseUnloadChannel(appID) - - // Should complete through struct reference - select { - case <-testComplete: - // Expected behavior - case <-time.After(100 * time.Millisecond): - t.Error("Channel operation through struct should work") - } -} diff --git a/keploy/pkg/service/replay/service.go b/keploy/pkg/service/replay/service.go deleted file mode 100644 index cb8ee56..0000000 --- a/keploy/pkg/service/replay/service.go +++ /dev/null @@ -1,103 +0,0 @@ -package replay - -import ( - "context" - "io" - "time" - - "go.keploy.io/server/v2/pkg/models" -) - -type Instrumentation interface { - //Setup prepares the environment for the recording - Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) - //Hook will load hooks and start the proxy server. - Hook(ctx context.Context, id uint64, opts models.HookOptions) error - // GetHookUnloadDone returns a channel that signals when hooks are completely unloaded - GetHookUnloadDone(id uint64) <-chan struct{} - MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error - // SetMocks Allows for setting mocks between test runs for better filtering and matching - SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error - // GetConsumedMocks to log the names of the mocks that were consumed during the test run of failed test cases - GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) - // Run is blocking call and will execute until error - Run(ctx context.Context, id uint64, opts models.RunOptions) models.AppError - - GetContainerIP(ctx context.Context, id uint64) (string, error) -} - -type Service interface { - Start(ctx context.Context) error - Instrument(ctx context.Context) (*InstrumentState, error) - GetNextTestRunID(ctx context.Context) (string, error) - GetAllTestSetIDs(ctx context.Context) ([]string, error) - RunTestSet(ctx context.Context, testSetID string, testRunID string, appID uint64, serveTest bool) (models.TestSetStatus, error) - GetTestSetStatus(ctx context.Context, testRunID string, testSetID string) (models.TestSetStatus, error) - GetTestCases(ctx context.Context, testID string) ([]*models.TestCase, error) - GetTestSetConf(ctx context.Context, testSetID string) (*models.TestSet, error) - RunApplication(ctx context.Context, appID uint64, opts models.RunOptions) models.AppError - Normalize(ctx context.Context) error - DenoiseTestCases(ctx context.Context, testSetID string, noiseParams []*models.NoiseParams) ([]*models.NoiseParams, error) - NormalizeTestCases(ctx context.Context, testRun string, testSetID string, selectedTestCaseIDs []string, testResult []models.TestResult) error - DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error - DeleteTestSet(ctx context.Context, testSetID string) error - - DownloadMocks(ctx context.Context) error - UploadMocks(ctx context.Context) error -} - -type TestDB interface { - GetAllTestSetIDs(ctx context.Context) ([]string, error) - GetTestCases(ctx context.Context, testSetID string) ([]*models.TestCase, error) - UpdateTestCase(ctx context.Context, testCase *models.TestCase, testSetID string, enableLog bool) error - DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error - DeleteTestSet(ctx context.Context, testSetID string) error -} - -type MockDB interface { - GetFilteredMocks(ctx context.Context, testSetID string, afterTime time.Time, beforeTime time.Time) ([]*models.Mock, error) - GetUnFilteredMocks(ctx context.Context, testSetID string, afterTime time.Time, beforeTime time.Time) ([]*models.Mock, error) - UpdateMocks(ctx context.Context, testSetID string, mockNames map[string]models.MockState) error -} - -type ReportDB interface { - GetAllTestRunIDs(ctx context.Context) ([]string, error) - GetTestCaseResults(ctx context.Context, testRunID string, testSetID string) ([]models.TestResult, error) - GetReport(ctx context.Context, testRunID string, testSetID string) (*models.TestReport, error) - ClearTestCaseResults(_ context.Context, testRunID string, testSetID string) - InsertTestCaseResult(ctx context.Context, testRunID string, testSetID string, result *models.TestResult) error // 1 - InsertReport(ctx context.Context, testRunID string, testSetID string, testReport *models.TestReport) error // 2 - UpdateReport(ctx context.Context, testRunID string, testCoverage any) error -} - -type TestSetConfig interface { - Read(ctx context.Context, testSetID string) (*models.TestSet, error) - Write(ctx context.Context, testSetID string, testSet *models.TestSet) error - ReadSecret(ctx context.Context, testSetID string) (map[string]interface{}, error) -} - -type Telemetry interface { - TestSetRun(success int, failure int, testSet string, runStatus string) - TestRun(success int, failure int, testSets int, runStatus string) - MockTestRun(utilizedMocks int) -} - -type TestHooks interface { - SimulateRequest(ctx context.Context, appID uint64, tc *models.TestCase, testSetID string) (interface{}, error) - GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) - BeforeTestRun(ctx context.Context, testRunID string) error - BeforeTestSetRun(ctx context.Context, testSetID string) error - AfterTestSetRun(ctx context.Context, testSetID string, status bool) error - AfterTestRun(ctx context.Context, testRunID string, testSetIDs []string, coverage models.TestCoverage) error // hook executed after running all the test-sets -} - -type Storage interface { - Upload(ctx context.Context, file io.Reader, mockName string, appName string, jwtToken string) error // 3 - Download(ctx context.Context, mockName string, appName string, userName string, jwtToken string) (io.Reader, error) -} - -type InstrumentState struct { - AppID uint64 - HookCancel context.CancelFunc - UnloadDone <-chan struct{} // Channel that will be closed when hooks are completely unloaded -} diff --git a/keploy/pkg/service/replay/service_test.go b/keploy/pkg/service/replay/service_test.go deleted file mode 100644 index be4b8f2..0000000 --- a/keploy/pkg/service/replay/service_test.go +++ /dev/null @@ -1,267 +0,0 @@ -package replay - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "go.keploy.io/server/v2/pkg/models" -) - -// MockInstrumentation is a mock implementation of the Instrumentation interface -type MockInstrumentation struct { - unloadDoneChannels map[uint64]chan struct{} -} - -func NewMockInstrumentation() *MockInstrumentation { - return &MockInstrumentation{ - unloadDoneChannels: make(map[uint64]chan struct{}), - } -} - -func (m *MockInstrumentation) Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) { - return 12345, nil // Mock return value -} - -func (m *MockInstrumentation) Hook(ctx context.Context, id uint64, opts models.HookOptions) error { - return nil // Mock return value -} - -func (m *MockInstrumentation) GetHookUnloadDone(id uint64) <-chan struct{} { - if ch, exists := m.unloadDoneChannels[id]; exists { - return ch - } - // Create a new channel for this app ID - ch := make(chan struct{}) - m.unloadDoneChannels[id] = ch - return ch -} - -func (m *MockInstrumentation) MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error { - return nil // Mock return value -} - -func (m *MockInstrumentation) SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error { - return nil // Mock return value -} - -func (m *MockInstrumentation) GetConsumedMocks(ctx context.Context, id uint64) ([]models.MockState, error) { - return []models.MockState{}, nil // Mock return value -} - -func (m *MockInstrumentation) Run(ctx context.Context, id uint64, opts models.RunOptions) models.AppError { - return models.AppError{} // Mock return value -} - -func (m *MockInstrumentation) GetContainerIP(ctx context.Context, id uint64) (string, error) { - return "127.0.0.1", nil // Mock return value -} - -// CloseUnloadChannel simulates hooks being unloaded by closing the channel -func (m *MockInstrumentation) CloseUnloadChannel(id uint64) { - if ch, exists := m.unloadDoneChannels[id]; exists { - close(ch) - } -} - -func TestInstrumentState_UnloadDoneChannel(t *testing.T) { - // Test the InstrumentState struct with UnloadDone channel - unloadCh := make(chan struct{}) - - state := &InstrumentState{ - AppID: 12345, - HookCancel: func() {}, // dummy cancel function - UnloadDone: unloadCh, - } - - // Verify the channel is present and not closed - assert.NotNil(t, state.UnloadDone, "UnloadDone channel should be present") - - select { - case <-state.UnloadDone: - t.Error("UnloadDone channel should not be closed initially") - default: - // Expected behavior - } - - // Close the channel to simulate unload completion - close(unloadCh) - - // Verify the channel is now closed - select { - case <-state.UnloadDone: - // Expected behavior - channel is closed - case <-time.After(100 * time.Millisecond): - t.Error("UnloadDone channel should be closed after unload") - } -} - -func TestMockInstrumentation_GetHookUnloadDone(t *testing.T) { - mockInstr := NewMockInstrumentation() - - appID := uint64(12345) - - // Get the channel - ch := mockInstr.GetHookUnloadDone(appID) - assert.NotNil(t, ch, "GetHookUnloadDone should return a channel") - - // Verify the channel is not closed initially - select { - case <-ch: - t.Error("Channel should not be closed initially") - default: - // Expected behavior - } - - // Multiple calls should return the same channel - ch2 := mockInstr.GetHookUnloadDone(appID) - assert.Equal(t, ch, ch2, "Multiple calls should return the same channel") -} - -func TestMockInstrumentation_GetHookUnloadDone_DifferentApps(t *testing.T) { - mockInstr := NewMockInstrumentation() - - appID1 := uint64(12345) - appID2 := uint64(67890) - - // Get channels for different app IDs - ch1 := mockInstr.GetHookUnloadDone(appID1) - ch2 := mockInstr.GetHookUnloadDone(appID2) - - assert.NotNil(t, ch1, "First app should return a channel") - assert.NotNil(t, ch2, "Second app should return a channel") - assert.NotEqual(t, ch1, ch2, "Different app IDs should return different channels") -} - -func TestMockInstrumentation_CloseUnloadChannel(t *testing.T) { - mockInstr := NewMockInstrumentation() - - appID := uint64(12345) - - // Get the channel - ch := mockInstr.GetHookUnloadDone(appID) - - // Verify not closed initially - select { - case <-ch: - t.Error("Channel should not be closed initially") - default: - // Expected behavior - } - - // Close the channel - mockInstr.CloseUnloadChannel(appID) - - // Verify the channel is now closed - select { - case <-ch: - // Expected behavior - channel is closed - case <-time.After(100 * time.Millisecond): - t.Error("Channel should be closed after CloseUnloadChannel") - } -} - -func TestInstrument_ChannelIntegration(t *testing.T) { - // Test scenario similar to how the channel is used in the replay logic - mockInstr := NewMockInstrumentation() - appID := uint64(12345) - - // Simulate getting the instrument state - unloadCh := mockInstr.GetHookUnloadDone(appID) - state := &InstrumentState{ - AppID: appID, - HookCancel: func() {}, - UnloadDone: unloadCh, - } - - // Simulate waiting for unload in a goroutine (similar to replay logic) - done := make(chan bool, 1) - go func() { - select { - case <-state.UnloadDone: - done <- true - case <-time.After(200 * time.Millisecond): - done <- false - } - }() - - // Simulate unload happening after a delay - go func() { - time.Sleep(50 * time.Millisecond) - mockInstr.CloseUnloadChannel(appID) - }() - - // Verify that the channel was closed and detected - result := <-done - assert.True(t, result, "Should detect channel closure within timeout") -} - -func TestInstrument_MultipleUnloadWaiters(t *testing.T) { - // Test multiple goroutines waiting on the same unload channel - mockInstr := NewMockInstrumentation() - appID := uint64(12345) - - unloadCh := mockInstr.GetHookUnloadDone(appID) - - // Create multiple waiters - numWaiters := 3 - results := make(chan bool, numWaiters) - - for i := 0; i < numWaiters; i++ { - go func() { - select { - case <-unloadCh: - results <- true - case <-time.After(200 * time.Millisecond): - results <- false - } - }() - } - - // Close the channel after a short delay - go func() { - time.Sleep(50 * time.Millisecond) - mockInstr.CloseUnloadChannel(appID) - }() - - // All waiters should detect the closure - for i := 0; i < numWaiters; i++ { - result := <-results - assert.True(t, result, "Waiter %d should detect channel closure", i) - } -} - -func TestInstrument_ChannelReuse(t *testing.T) { - // Test scenario where we get a new channel after the previous one was closed - mockInstr := NewMockInstrumentation() - appID := uint64(12345) - - // First channel - ch1 := mockInstr.GetHookUnloadDone(appID) - mockInstr.CloseUnloadChannel(appID) - - // Verify first channel is closed - select { - case <-ch1: - // Expected behavior - case <-time.After(100 * time.Millisecond): - t.Error("First channel should be closed") - } - - // Get a new channel (simulating a new load after unload) - // Note: In the real implementation, a new channel would be created for each load - // For this test, we'll manually create a new one - delete(mockInstr.unloadDoneChannels, appID) // Remove the old closed channel - ch2 := mockInstr.GetHookUnloadDone(appID) - - // Verify second channel is different and not closed - assert.NotEqual(t, ch1, ch2, "New channel should be different from the closed one") - - select { - case <-ch2: - t.Error("New channel should not be closed initially") - default: - // Expected behavior - } -} diff --git a/keploy/pkg/service/replay/utils.go b/keploy/pkg/service/replay/utils.go deleted file mode 100644 index 8899d46..0000000 --- a/keploy/pkg/service/replay/utils.go +++ /dev/null @@ -1,118 +0,0 @@ -package replay - -import ( - "fmt" - "net/url" - "path" - "time" - - // "encoding/json" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/models" -) - -type TestReportVerdict struct { - total int - passed int - failed int - ignored int - status bool - duration time.Duration -} - -func LeftJoinNoise(globalNoise config.GlobalNoise, tsNoise config.GlobalNoise) config.GlobalNoise { - noise := globalNoise - - if _, ok := noise["body"]; !ok { - noise["body"] = make(map[string][]string) - } - if tsNoiseBody, ok := tsNoise["body"]; ok { - for field, regexArr := range tsNoiseBody { - noise["body"][field] = regexArr - } - } - - if _, ok := noise["header"]; !ok { - noise["header"] = make(map[string][]string) - } - if tsNoiseHeader, ok := tsNoise["header"]; ok { - for field, regexArr := range tsNoiseHeader { - noise["header"][field] = regexArr - } - } - - return noise -} - -// ReplaceBaseURL replaces the baseUrl of the old URL with the new URL's. -func ReplaceBaseURL(newURL, oldURL string) (string, error) { - parsedOldURL, err := url.Parse(oldURL) - if err != nil { - return "", fmt.Errorf("failed to parse the old URL: %v", err) - } - - parsedNewURL, err := url.Parse(newURL) - if err != nil { - return "", fmt.Errorf("failed to parse the new URL: %v", err) - } - // if scheme is empty, then add the scheme from the old URL in order to parse it correctly - if parsedNewURL.Scheme == "" { - parsedNewURL.Scheme = parsedOldURL.Scheme - parsedNewURL, err = url.Parse(parsedNewURL.String()) - if err != nil { - return "", fmt.Errorf("failed to parse the scheme added new URL: %v", err) - } - } - - parsedOldURL.Scheme = parsedNewURL.Scheme - parsedOldURL.Host = parsedNewURL.Host - apiPath := path.Join(parsedNewURL.Path, parsedOldURL.Path) - - parsedOldURL.Path = apiPath - parsedOldURL.RawPath = apiPath - replacedURL := parsedOldURL.String() - decodedURL, err := url.PathUnescape(replacedURL) - if err != nil { - return "", fmt.Errorf("failed to decode the URL: %v", err) - } - return decodedURL, nil -} - -func mergeMaps(map1, map2 map[string][]string) map[string][]string { - for key, values := range map2 { - if _, exists := map1[key]; exists { - map1[key] = append(map1[key], values...) - } else { - map1[key] = values - } - } - return map1 -} - -func removeFromMap(map1, map2 map[string][]string) map[string][]string { - for key := range map2 { - delete(map1, key) - } - return map1 -} - -func timeWithUnits(duration time.Duration) string { - if duration.Seconds() < 1 { - return fmt.Sprintf("%v ms", duration.Milliseconds()) - } else if duration.Minutes() < 1 { - return fmt.Sprintf("%.2f s", duration.Seconds()) - } else if duration.Hours() < 1 { - return fmt.Sprintf("%.2f min", duration.Minutes()) - } - return fmt.Sprintf("%.2f hr", duration.Hours()) -} - -func getFailedTCs(results []models.TestResult) []string { - ids := make([]string, 0, len(results)) - for _, r := range results { - if r.Status == models.TestStatusFailed { - ids = append(ids, r.TestCaseID) - } - } - return ids -} diff --git a/keploy/pkg/service/report/report.go b/keploy/pkg/service/report/report.go deleted file mode 100644 index 433c41f..0000000 --- a/keploy/pkg/service/report/report.go +++ /dev/null @@ -1,283 +0,0 @@ -package report - -import ( - "context" - "fmt" - "sort" - "strconv" - "strings" - - "github.com/k0kubun/pp/v3" - "go.keploy.io/server/v2/config" - matcherUtils "go.keploy.io/server/v2/pkg/matcher" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/service/tools" - "go.uber.org/zap" -) - -type Report struct { - logger *zap.Logger - config *config.Config - reportDB ReportDB - testDB TestDB -} - -const ( - ReportSuffix = "-report" - TestRunPrefix = "test-run-" -) - -func New(logger *zap.Logger, cfg *config.Config, reportDB ReportDB, testDB TestDB) *Report { - return &Report{ - logger: logger, - config: cfg, - reportDB: reportDB, - testDB: testDB, - } -} - -// GenerateReport orchestrates the entire report generation process -func (r *Report) GenerateReport(ctx context.Context) error { - latestRunID, err := r.getLatestTestRunID(ctx) - - if err != nil { - return err - } - - testSetIDs := r.extractTestSetIDs() - if len(testSetIDs) == 0 { - r.logger.Info("No test sets selected for report generation, Generating report for all test sets") - - var err error - testSetIDs, err = r.testDB.GetReportTestSets(ctx, latestRunID) - if err != nil { - r.logger.Error("failed to get all test set ids", zap.Error(err)) - return err - } - - if len(testSetIDs) == 0 { - r.logger.Warn("No test sets found for report generation") - return nil - } - } - - if latestRunID == "" { - r.logger.Warn("no test runs found") - return nil - } - - failedTests, err := r.collectFailedTests(ctx, latestRunID, testSetIDs) - if err != nil { - return err - } - - if len(failedTests) == 0 { - r.logger.Info("No failed tests found in the latest test run") - return nil - } - - err = r.printFailedTestReports(failedTests) - if err != nil { - r.logger.Error("failed to print failed test reports", zap.Error(err)) - return err - } - - r.logger.Info(fmt.Sprintf("✂️ CLI output truncated - see the %s report file for the complete diff.", latestRunID)) - - r.logger.Info("Report generation completed successfully") - - return nil -} - -// extractTestSetIDs extracts and cleans test set IDs from config -func (r *Report) extractTestSetIDs() []string { - var testSetIDs []string - for testSet := range r.config.Report.SelectedTestSets { - testSetIDs = append(testSetIDs, strings.TrimSpace(testSet)) - } - return testSetIDs -} - -// getLatestTestRunID retrieves and determines the latest test run ID -func (r *Report) getLatestTestRunID(ctx context.Context) (string, error) { - testRunIDs, err := r.reportDB.GetAllTestRunIDs(ctx) - if err != nil { - r.logger.Error("failed to get all test run ids", zap.Error(err)) - return "", err - } - - if len(testRunIDs) == 0 { - return "", nil - } - - sort.Slice(testRunIDs, func(i, j int) bool { - numi, erri := strconv.Atoi(strings.TrimPrefix(testRunIDs[i], TestRunPrefix)) - numj, errj := strconv.Atoi(strings.TrimPrefix(testRunIDs[j], TestRunPrefix)) - if erri != nil && errj != nil { - return testRunIDs[i] < testRunIDs[j] - } - if erri != nil { - return true // i is less if it can't be parsed - } - if errj != nil { - return false // j is less if it can't be parsed - } - return numi < numj - }) - - return testRunIDs[len(testRunIDs)-1], nil -} - -// collectFailedTests gathers all failed tests from the specified test sets -func (r *Report) collectFailedTests(ctx context.Context, runID string, testSetIDs []string) ([]models.TestResult, error) { - var failedTests []models.TestResult - - for _, testSetID := range testSetIDs { - cleanTestSetID := strings.TrimSuffix(testSetID, ReportSuffix) - - results, err := r.reportDB.GetReport(ctx, runID, cleanTestSetID) - if err != nil { - r.logger.Error("failed to get test case results for test set", - zap.String("test_set_id", cleanTestSetID), zap.Error(err)) - continue - } - - if results == nil { - r.logger.Warn("no results found for test set", zap.String("test_set_id", cleanTestSetID)) - continue - } - - failedTests = append(failedTests, r.extractFailedTestsFromResults(results.Tests)...) - } - - return failedTests, nil -} - -// extractFailedTestsFromResults filters out only the failed tests from results -func (r *Report) extractFailedTestsFromResults(tests []models.TestResult) []models.TestResult { - var failedTests []models.TestResult - for _, result := range tests { - if result.Status == models.TestStatusFailed { - failedTests = append(failedTests, result) - } - } - return failedTests -} - -// printFailedTestReports generates and prints reports for all failed tests -func (r *Report) printFailedTestReports(failedTests []models.TestResult) error { - for _, test := range failedTests { - if err := r.printSingleTestReport(test); err != nil { - return err - } - } - return nil -} - -// printSingleTestReport generates and prints a report for a single failed test -func (r *Report) printSingleTestReport(test models.TestResult) error { - logDiffs := matcherUtils.NewDiffsPrinter(test.Name) - printer := r.createFormattedPrinter() - - logs := r.generateTestHeader(test, printer) - - if err := r.addStatusCodeDiffs(test, &logDiffs); err != nil { - return err - } - - if err := r.addHeaderDiffs(test, &logDiffs); err != nil { - return err - } - - if err := r.addBodyDiffs(test, &logDiffs); err != nil { - return err - } - - if err := r.printAndRenderDiffs(printer, logs, &logDiffs); err != nil { - return err - } - - return nil -} - -// createFormattedPrinter creates a configured pretty printer -func (r *Report) createFormattedPrinter() *pp.PrettyPrinter { - printer := pp.New() - printer.WithLineInfo = false - printer.SetColorScheme(models.GetFailingColorScheme()) - return printer -} - -// generateTestHeader creates the test report header -func (r *Report) generateTestHeader(test models.TestResult, printer *pp.PrettyPrinter) string { - return printer.Sprintf("Testrun failed for testcase with id: %s\n\n--------------------------------------------------------------------\n\n", - test.TestCaseID) -} - -// addStatusCodeDiffs adds status code differences to the diff printer -func (r *Report) addStatusCodeDiffs(test models.TestResult, logDiffs *matcherUtils.DiffsPrinter) error { - if !test.Result.StatusCode.Normal { - logDiffs.PushStatusDiff( - fmt.Sprint(test.Result.StatusCode.Expected), - fmt.Sprint(test.Result.StatusCode.Actual), - ) - } - return nil -} - -// addHeaderDiffs adds header differences to the diff printer -func (r *Report) addHeaderDiffs(test models.TestResult, logDiffs *matcherUtils.DiffsPrinter) error { - for _, headerResult := range test.Result.HeadersResult { - if !headerResult.Normal { - actualValue := strings.Join(headerResult.Actual.Value, ", ") - expectedValue := strings.Join(headerResult.Expected.Value, ", ") - logDiffs.PushHeaderDiff(expectedValue, actualValue, headerResult.Actual.Key, nil) - } - } - return nil -} - -// addBodyDiffs adds body differences to the diff printer -func (r *Report) addBodyDiffs(test models.TestResult, logDiffs *matcherUtils.DiffsPrinter) error { - for _, bodyResult := range test.Result.BodyResult { - if !bodyResult.Normal { - actualValue, err := r.renderTemplateValue(bodyResult.Actual) - if err != nil { - return fmt.Errorf("failed to render actual body: %w", err) - } - - expectedValue, err := r.renderTemplateValue(bodyResult.Expected) - if err != nil { - return fmt.Errorf("failed to render expected body: %w", err) - } - - logDiffs.PushBodyDiff(fmt.Sprint(expectedValue), fmt.Sprint(actualValue), nil) - } - } - return nil -} - -// renderTemplateValue renders a templated value and returns the result -func (r *Report) renderTemplateValue(value interface{}) (interface{}, error) { - _, renderedValue, err := tools.RenderIfTemplatized(value) - if err != nil { - r.logger.Error("failed to render template value", zap.Error(err)) - return nil, err - } - return renderedValue, nil -} - -// printAndRenderDiffs prints the logs and renders the differences -func (r *Report) printAndRenderDiffs(printer *pp.PrettyPrinter, logs string, logDiffs *matcherUtils.DiffsPrinter) error { - if _, err := printer.Printf(logs); err != nil { - r.logger.Error("failed to print the logs", zap.Error(err)) - return err - } - - if err := logDiffs.Render(); err != nil { - r.logger.Error("failed to render the diffs", zap.Error(err)) - return err - } - - return nil -} diff --git a/keploy/pkg/service/report/service.go b/keploy/pkg/service/report/service.go deleted file mode 100644 index 38ce081..0000000 --- a/keploy/pkg/service/report/service.go +++ /dev/null @@ -1,20 +0,0 @@ -package report - -import ( - "context" - - "go.keploy.io/server/v2/pkg/models" -) - -type Service interface { - GenerateReport(ctx context.Context) error -} - -type ReportDB interface { - GetAllTestRunIDs(ctx context.Context) ([]string, error) - GetReport(ctx context.Context, testRunID string, testSetID string) (*models.TestReport, error) -} - -type TestDB interface { - GetReportTestSets(ctx context.Context, reportID string) ([]string, error) -} diff --git a/keploy/pkg/service/secure/secure.go b/keploy/pkg/service/secure/secure.go deleted file mode 100644 index d66f0b1..0000000 --- a/keploy/pkg/service/secure/secure.go +++ /dev/null @@ -1,1222 +0,0 @@ -package secure - -import ( - "context" - "fmt" - "net/http" - "os" - "os/signal" - "path/filepath" - "regexp" - "strings" - "syscall" - "time" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/service/testsuite" - "go.uber.org/zap" -) - -type SecurityCheck struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Severity string `json:"severity"` // "CRITICAL", "HIGH", "MEDIUM", "LOW" - Type string `json:"type"` // "header", "body", "cookie", "url" - Target string `json:"target"` // "request", "response" - where to apply the check - Key string `json:"key"` // Header name, JSON path, regex pattern, etc. - Value string `json:"value,omitempty"` // Expected value or pattern to match - Operation string `json:"operation"` // "exists", "equals", "contains", "regex", "not_exists" - Status string `json:"status"` // "enabled", "disabled" -} - -type SecurityResult struct { - CheckID string `json:"check_id"` - CheckName string `json:"check_name"` - Status string `json:"status"` // "passed", "failed", "warning" - Severity string `json:"severity"` - Description string `json:"description"` - Details string `json:"details,omitempty"` - Recommendation string `json:"recommendation,omitempty"` - // Step information - StepName string `json:"step_name"` - StepMethod string `json:"step_method"` - StepURL string `json:"step_url"` - StatusCode int `json:"status_code,omitempty"` - Target string `json:"target"` // "request" or "response" - where the check was applied -} - -type StepSecurityResults struct { - StepName string `json:"step_name"` - StepMethod string `json:"step_method"` - StepURL string `json:"step_url"` - Results []SecurityResult `json:"results"` - Passed int `json:"passed"` - Failed int `json:"failed"` - Warnings int `json:"warnings"` -} - -type SecurityReport struct { - TestSuite string `json:"test_suite"` - Timestamp string `json:"timestamp"` - TotalChecks int `json:"total_checks"` - Passed int `json:"passed"` - Failed int `json:"failed"` - Warnings int `json:"warnings"` - Steps []StepSecurityResults `json:"steps"` - Summary map[string]int `json:"summary"` // severity -> count -} - -type StepRequest struct { - Method string - Headers http.Header - Body string -} - -type StepResponse struct { - StatusCode int - Headers http.Header - Body string -} - -type Step struct { - Endpoint string - StepName string - StepRequest StepRequest - StepResponse StepResponse -} - -// Built-in security checks -// Only 10 Checks, commneted checks are for code testing purpose. -var BuiltInSecurityChecks = []SecurityCheck{ - { - ID: "https-enforcement", - Name: "HTTPS Enforcement", - Description: "Check if Strict-Transport-Security header is present", - Severity: "HIGH", - Type: "header", - Target: "response", - Key: "Strict-Transport-Security", - Operation: "exists", - Status: "enabled", - }, - { - ID: "x-content-type-options", - Name: "X-Content-Type-Options", - Description: "Check for X-Content-Type-Options nosniff header", - Severity: "HIGH", - Type: "header", - Target: "response", - Key: "X-Content-Type-Options", - Value: "nosniff", - Operation: "equals", - Status: "enabled", - }, - { - ID: "x-frame-options", - Name: "X-Frame-Options", - Description: "Check for X-Frame-Options header to prevent clickjacking", - Severity: "HIGH", - Type: "header", - Target: "response", - Key: "X-Frame-Options", - Operation: "exists", - Status: "enabled", - }, - // { - // ID: "content-security-policy", - // Name: "Content Security Policy", - // Description: "Check for Content-Security-Policy header", - // Severity: "HIGH", - // Type: "header", - // Target: "response", - // Key: "Content-Security-Policy", - // Operation: "exists", - // Status: "enabled", - // }, - // { - // ID: "email-exposure", - // Name: "Email Exposure", - // Description: "Check for email addresses in response body", - // Severity: "CRITICAL", - // Type: "body", - // Target: "response", - // Key: ".+@.+\\..+", - // Operation: "regex", - // Status: "enabled", - // }, - // { - // ID: "credit-card-exposure", - // Name: "Credit Card Exposure", - // Description: "Check for credit card numbers in response body", - // Severity: "CRITICAL", - // Type: "body", - // Target: "response", - // Key: "\\b(?:\\d[ -]*?){13,16}\\b", - // Operation: "regex", - // Status: "enabled", - // }, - // { - // ID: "api-key-exposure", - // Name: "API Key Exposure", - // Description: "Check for API keys in response body", - // Severity: "CRITICAL", - // Type: "body", - // Target: "response", - // Key: "sk_(live|test)_[a-zA-Z0-9]{24}", - // Operation: "regex", - // Status: "enabled", - // }, - { - ID: "secure-cookie", - Name: "Secure Cookie", - Description: "Check if cookies have Secure flag", - Severity: "HIGH", - Type: "cookie", - Target: "response", - Key: "Secure", - Operation: "exists", - Status: "enabled", - }, - { - ID: "httponly-cookie", - Name: "HttpOnly Cookie", - Description: "Check if cookies have HttpOnly flag", - Severity: "HIGH", - Type: "cookie", - Target: "response", - Key: "HttpOnly", - Operation: "exists", - Status: "enabled", - }, - { - ID: "samesite-cookie", - Name: "SameSite Cookie", - Description: "Check if cookies have SameSite attribute", - Severity: "MEDIUM", - Type: "cookie", - Target: "response", - Key: "SameSite", - Operation: "exists", - Status: "enabled", - }, - { - ID: "cors-misconfiguration", - Name: "CORS Misconfiguration", - Description: "Check for overly permissive CORS policy", - Severity: "MEDIUM", - Type: "header", - Target: "response", - Key: "Access-Control-Allow-Origin", - Value: "*", - Operation: "not_equals", - Status: "enabled", - }, - // { - // ID: "java-stack-trace", - // Name: "Java Stack Trace", - // Description: "Check for Java stack traces in response", - // Severity: "MEDIUM", - // Type: "body", - // Target: "response", - // Key: "java\\.lang\\.Exception|at com\\.|at java\\.", - // Operation: "regex", - // Status: "enabled", - // }, - // { - // ID: "python-stack-trace", - // Name: "Python Stack Trace", - // Description: "Check for Python stack traces in response", - // Severity: "MEDIUM", - // Type: "body", - // Target: "response", - // Key: "Traceback \\(most recent call last\\)", - // Operation: "regex", - // Status: "enabled", - // }, - // { - // ID: "nodejs-error", - // Name: "Node.js Error", - // Description: "Check for Node.js errors in response", - // Severity: "MEDIUM", - // Type: "body", - // Target: "response", - // Key: "Error: ENOENT|TypeError:|ReferenceError:", - // Operation: "regex", - // Status: "enabled", - // }, - { - ID: "server-version-leak", - Name: "Server Version Leak", - Description: "Check for server version information in headers", - Severity: "MEDIUM", - Type: "header", - Target: "response", - Key: "Server", - Operation: "not_exists", - Status: "enabled", - }, - { - ID: "x-powered-by-leak", - Name: "X-Powered-By Leak", - Description: "Check for X-Powered-By header disclosure", - Severity: "MEDIUM", - Type: "header", - Target: "response", - Key: "X-Powered-By", - Operation: "not_exists", - Status: "enabled", - }, - // Request-based security checks - { - ID: "authorization-header-present", - Name: "Authorization Header Present", - Description: "Check if Authorization header is present in request", - Severity: "HIGH", - Type: "header", - Target: "request", - Key: "Authorization", - Operation: "exists", - Status: "enabled", - }, - // { - // ID: "api-key-in-request-body", - // Name: "API Key in Request Body", - // Description: "Check for API keys in request body", - // Severity: "CRITICAL", - // Type: "body", - // Target: "request", - // Key: "api[_-]?key|apikey|access[_-]?token|secret[_-]?key", - // Operation: "regex", - // Status: "enabled", - // }, - // { - // ID: "password-in-request-body", - // Name: "Password in Request Body", - // Description: "Check for passwords in request body", - // Severity: "CRITICAL", - // Type: "body", - // Target: "request", - // Key: "\"password\"\\s*:\\s*\"[^\"]+\"", - // Operation: "regex", - // Status: "enabled", - // }, - // { - // ID: "sql-injection-in-request", - // Name: "SQL Injection in Request", - // Description: "Check for potential SQL injection patterns in request body", - // Severity: "HIGH", - // Type: "body", - // Target: "request", - // Key: "('|(\\-\\-)|;|\\||\\*|(%27)|(%2D%2D)|(%7C)|(%2A))", - // Operation: "regex", - // Status: "enabled", - // }, -} - -type SecurityChecker struct { - config *config.Config - logger *zap.Logger - testsuite *testsuite.TestSuite - ruleset string -} - -func NewSecurityChecker(cfg *config.Config, logger *zap.Logger) (*SecurityChecker, error) { - testsuitePath := filepath.Join(cfg.TestSuite.TSPath, cfg.TestSuite.TSFile) - logger.Info("Parsing TestSuite File", zap.String("path", testsuitePath)) - - testsuite, err := testsuite.TSParser(testsuitePath) - if err != nil { - logger.Error("Failed to parse TestSuite file", zap.Error(err)) - return nil, fmt.Errorf("failed to parse TestSuite file: %w", err) - } - - return &SecurityChecker{ - config: cfg, - logger: logger, - testsuite: &testsuite, - ruleset: testsuite.Spec.Security.Ruleset, - }, nil -} - -func (s *SecurityChecker) Start(ctx context.Context) (*SecurityReport, error) { - if s.ruleset == "" { - s.ruleset = "basic" // Default to basic ruleset if not specified - } - // CLI override - if ruleSetValue := ctx.Value("rule-set"); ruleSetValue != nil { - if ruleSetStr, ok := ruleSetValue.(string); ok && ruleSetStr != "basic" { - s.ruleset = ruleSetStr - } - } - - // Create and execute TestSuite to get step data - tsExecutor, err := testsuite.NewTSExecutor(s.config, s.logger, true) - if err != nil { - return nil, fmt.Errorf("failed to create testsuite executor: %w", err) - } - - tsExecutor.Testsuite = s.testsuite - - // Execute the testsuite - executionReport, err := tsExecutor.Execute(ctx, nil) - if err != nil { - return nil, fmt.Errorf("failed to execute testsuite: %w", err) - } - - // Convert execution results to Step structures for security analysis - steps := s.convertExecutionReportToSteps(executionReport, tsExecutor) - - // Run security checks on the steps - securityReport := s.runSecurityChecks(ctx, steps) - - // Print the security report - s.printSecurityReport(securityReport) - - return securityReport, nil -} - -func (s *SecurityChecker) AddCustomCheck(ctx context.Context) error { - // Set up signal handling for graceful exit on Ctrl+C - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) - defer signal.Stop(signalChan) - - // Create a context that can be cancelled by signals - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - // Handle signals in a goroutine - go func() { - select { - case <-signalChan: - fmt.Println("\n\n⚠️ Operation cancelled by user.") - cancel() - case <-ctx.Done(): - return - } - }() - - fmt.Println("\n🔒 Add Custom Security Check") - fmt.Println("=" + strings.Repeat("=", 50)) - - var check SecurityCheck - - // Get check ID with validation loop - for { - input, err := readInputWithCancel(ctx, "Enter check ID (unique identifier): ") - if err != nil { - return err - } - - if input != "" { - check.ID = input - break - } - fmt.Println("❌ Error: Check ID is required. Please try again.") - } - - // Get check name with validation loop - for { - input, err := readInputWithCancel(ctx, "Enter check name: ") - if err != nil { - return err - } - - if input != "" { - check.Name = input - break - } - fmt.Println("❌ Error: Check name is required. Please try again.") - } - - // Get check description with validation loop - for { - input, err := readInputWithCancel(ctx, "Enter check description: ") - if err != nil { - return err - } - - if input != "" { - check.Description = input - break - } - fmt.Println("❌ Error: Check description is required. Please try again.") - } - - // Get severity with validation loop - for { - input, err := readInputWithCancel(ctx, "Enter severity (CRITICAL/HIGH/MEDIUM/LOW): ") - if err != nil { - return err - } - - severity := strings.ToUpper(input) - if severity == "CRITICAL" || severity == "HIGH" || severity == "MEDIUM" || severity == "LOW" { - check.Severity = severity - break - } - fmt.Println("❌ Error: Invalid severity. Must be one of: CRITICAL, HIGH, MEDIUM, LOW. Please try again.") - } - - // Get check type with validation loop - for { - input, err := readInputWithCancel(ctx, "Enter check type (header/body/cookie/url): ") - if err != nil { - return err - } - - checkType := strings.ToLower(input) - if checkType == "header" || checkType == "body" || checkType == "cookie" || checkType == "url" { - check.Type = checkType - break - } - fmt.Println("❌ Error: Invalid type. Must be one of: header, body, cookie, url. Please try again.") - } - - // Get target with validation loop - for { - input, err := readInputWithCancel(ctx, "Enter target (request/response): ") - if err != nil { - return err - } - - target := strings.ToLower(input) - if target == "request" || target == "response" { - check.Target = target - break - } - fmt.Println("❌ Error: Invalid target. Must be one of: request, response. Please try again.") - } - - // Get key with validation loop - for { - input, err := readInputWithCancel(ctx, "Enter key (header name, regex pattern, etc.): ") - if err != nil { - return err - } - - if input != "" { - check.Key = input - break - } - fmt.Println("❌ Error: Key is required. Please try again.") - } - - // Get expected value (optional) - input, err := readInputWithCancel(ctx, "Enter expected value (optional, press Enter to skip): ") - if err != nil { - return err - } - check.Value = input - - // Get operation with validation loop - validOps := []string{"exists", "equals", "contains", "regex", "not_exists", "not_equals"} - for { - input, err := readInputWithCancel(ctx, "Enter operation (exists/equals/contains/regex/not_exists/not_equals): ") - if err != nil { - return err - } - - operation := strings.ToLower(input) - isValidOp := false - for _, op := range validOps { - if operation == op { - isValidOp = true - break - } - } - if isValidOp { - check.Operation = operation - break - } - fmt.Printf("❌ Error: Invalid operation. Must be one of: %s. Please try again.\n", strings.Join(validOps, ", ")) - } - - // Set default status - check.Status = "enabled" - - if err := s.saveCustomCheck(ctx, check); err != nil { - return fmt.Errorf("failed to save custom check: %w", err) - } - - fmt.Printf("\n✅ Custom security check '%s' added successfully!\n", check.Name) - return nil -} - -func (s *SecurityChecker) RemoveCustomCheck(ctx context.Context) error { - // Set up signal handling for graceful exit on Ctrl+C - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) - defer signal.Stop(signalChan) - - // Create a context that can be cancelled by signals - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - // Handle signals in a goroutine - go func() { - select { - case <-signalChan: - fmt.Println("\n\n⚠️ Operation cancelled by user.") - cancel() - case <-ctx.Done(): - return - } - }() - - fmt.Println("\n🔒 Remove Custom Security Check") - fmt.Println("=" + strings.Repeat("=", 50)) - - customChecks, err := s.loadCustomChecks(ctx) - if err != nil { - return fmt.Errorf("failed to load custom checks: %w", err) - } - - if len(customChecks) == 0 { - fmt.Println("No custom security checks found.") - return nil - } - - fmt.Println("\nExisting custom checks:") - for i, check := range customChecks { - fmt.Printf("%d. [%s] %s - %s\n", i+1, check.ID, check.Name, check.Severity) - } - - // Check the ID from the CLI before asking - checkID := "" - if idValue := ctx.Value("id"); idValue != nil { - if idStr, ok := idValue.(string); ok { - checkID = idStr - } - } - - // Get check ID with validation loop - for { - if checkID == "" { - input, err := readInputWithCancel(ctx, "\nEnter the ID of the check to remove: ") - if err != nil { - return err - } - checkID = input - } - - if checkID == "" { - fmt.Println("❌ Error: Check ID is required. Please try again.") - continue - } - - // Check if the ID exists - found := false - for _, check := range customChecks { - if check.ID == checkID { - found = true - break - } - } - - if found { - break - } - - fmt.Printf("❌ Error: Custom check with ID '%s' not found. Please try again.\n", checkID) - checkID = "" // Reset to ask again - } - - // Remove the check - var updatedChecks []SecurityCheck - for _, check := range customChecks { - if check.ID != checkID { - updatedChecks = append(updatedChecks, check) - } - } - - if err := s.saveCustomChecks(ctx, updatedChecks); err != nil { - return fmt.Errorf("failed to save updated custom checks: %w", err) - } - - fmt.Printf("\n✅ Custom security check '%s' removed successfully!\n", checkID) - return nil -} - -func (s *SecurityChecker) UpdateCustomCheck(ctx context.Context) error { - // Set up signal handling for graceful exit on Ctrl+C - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) - defer signal.Stop(signalChan) - - // Create a context that can be cancelled by signals - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - // Handle signals in a goroutine - go func() { - select { - case <-signalChan: - fmt.Println("\n\n⚠️ Operation cancelled by user.") - cancel() - case <-ctx.Done(): - return - } - }() - - fmt.Println("\n🔒 Update Custom Security Check") - fmt.Println("=" + strings.Repeat("=", 50)) - - customChecks, err := s.loadCustomChecks(ctx) - if err != nil { - return fmt.Errorf("failed to load custom checks: %w", err) - } - - if len(customChecks) == 0 { - fmt.Println("No custom security checks found.") - return nil - } - - fmt.Println("\nExisting custom checks:") - for i, check := range customChecks { - fmt.Printf("%d. [%s] %s - %s\n", i+1, check.ID, check.Name, check.Severity) - } - - // Check the ID from the CLI before asking - checkID := "" - if idValue := ctx.Value("id"); idValue != nil { - if idStr, ok := idValue.(string); ok { - checkID = idStr - } - } - - // Get check ID with validation loop - var checkIndex = -1 - for { - if checkID == "" { - input, err := readInputWithCancel(ctx, "\nEnter the ID of the check to update: ") - if err != nil { - return err - } - checkID = input - } - - if checkID == "" { - fmt.Println("❌ Error: Check ID is required. Please try again.") - continue - } - - // Find the check - for i, check := range customChecks { - if check.ID == checkID { - checkIndex = i - break - } - } - - if checkIndex != -1 { - break - } - - fmt.Printf("❌ Error: Custom check with ID '%s' not found. Please try again.\n", checkID) - checkID = "" // Reset to ask again - checkIndex = -1 - } - - check := &customChecks[checkIndex] - fmt.Printf("\nUpdating check: %s\n", check.Name) - fmt.Println("Press Enter to keep current value, or enter new value:") - - // Update name - input, err := readInputWithCancel(ctx, fmt.Sprintf("Name [%s]: ", check.Name)) - if err != nil { - return err - } - if newName := strings.TrimSpace(input); newName != "" { - check.Name = newName - } - - // Update description - input, err = readInputWithCancel(ctx, fmt.Sprintf("Description [%s]: ", check.Description)) - if err != nil { - return err - } - if newDesc := strings.TrimSpace(input); newDesc != "" { - check.Description = newDesc - } - - // Get severity with validation loop - for { - input, err := readInputWithCancel(ctx, fmt.Sprintf("Severity [%s] (CRITICAL/HIGH/MEDIUM/LOW): ", check.Severity)) - if err != nil { - return err - } - newSeverity := strings.ToUpper(strings.TrimSpace(input)) - - if newSeverity == "" { - // Keep current value - break - } - - if newSeverity == "CRITICAL" || newSeverity == "HIGH" || newSeverity == "MEDIUM" || newSeverity == "LOW" { - check.Severity = newSeverity - break - } - - fmt.Println("❌ Error: Invalid severity. Must be one of: CRITICAL, HIGH, MEDIUM, LOW. Please try again.") - } - - // Get type with validation loop - for { - input, err := readInputWithCancel(ctx, fmt.Sprintf("Type [%s] (header/body/cookie/url): ", check.Type)) - if err != nil { - return err - } - newType := strings.ToLower(strings.TrimSpace(input)) - - if newType == "" { - // Keep current value - break - } - - if newType == "header" || newType == "body" || newType == "cookie" || newType == "url" { - check.Type = newType - break - } - - fmt.Println("❌ Error: Invalid type. Must be one of: header, body, cookie, url. Please try again.") - } - - // Update key - input, err = readInputWithCancel(ctx, fmt.Sprintf("Key [%s]: ", check.Key)) - if err != nil { - return err - } - if newKey := strings.TrimSpace(input); newKey != "" { - check.Key = newKey - } - - // Update value - input, err = readInputWithCancel(ctx, fmt.Sprintf("Value [%s]: ", check.Value)) - if err != nil { - return err - } - if newValue := strings.TrimSpace(input); newValue != "" { - check.Value = newValue - } - - // Get operation with validation loop - validOps := []string{"exists", "equals", "contains", "regex", "not_exists", "not_equals"} - for { - input, err := readInputWithCancel(ctx, fmt.Sprintf("Operation [%s] (exists/equals/contains/regex/not_exists/not_equals): ", check.Operation)) - if err != nil { - return err - } - newOp := strings.ToLower(strings.TrimSpace(input)) - - if newOp == "" { - // Keep current value - break - } - - isValidOp := false - for _, op := range validOps { - if newOp == op { - isValidOp = true - break - } - } - - if isValidOp { - check.Operation = newOp - break - } - - fmt.Printf("❌ Error: Invalid operation. Must be one of: %s. Please try again.\n", strings.Join(validOps, ", ")) - } - - // Get status with validation loop - for { - input, err := readInputWithCancel(ctx, fmt.Sprintf("Status [%s] (enabled/disabled): ", check.Status)) - if err != nil { - return err - } - newStatus := strings.ToLower(strings.TrimSpace(input)) - - if newStatus == "" { - // Keep current value - break - } - - if newStatus == "enabled" || newStatus == "disabled" { - check.Status = newStatus - break - } - - fmt.Println("❌ Error: Invalid status. Must be 'enabled' or 'disabled'. Please try again.") - } - - if err := s.saveCustomChecks(ctx, customChecks); err != nil { - return fmt.Errorf("failed to save updated custom checks: %w", err) - } - - fmt.Printf("\n✅ Custom security check '%s' updated successfully!\n", check.Name) - return nil -} - -func (s *SecurityChecker) ListChecks(ctx context.Context) error { - if s.ruleset == "" { - s.ruleset = "basic" // Default to basic ruleset if not specified - } - // CLI override - if ruleSetValue := ctx.Value("rule-set"); ruleSetValue != nil { - if ruleSetStr, ok := ruleSetValue.(string); ok && ruleSetStr != "basic" { - s.ruleset = ruleSetStr - } - } - - switch s.ruleset { - case "basic", "built-in": - fmt.Println("\n🔒 Built-in Security Checks") - fmt.Println("=" + strings.Repeat("=", 50)) - - for _, check := range BuiltInSecurityChecks { - // Get effective status - check both the Status field and disable list - effectiveStatus := s.getEffectiveStatus(check) - - statusIcon := "✅ Enabled" - if effectiveStatus == "disabled" { - statusIcon = "❌ Disabled" - } - - fmt.Printf("\n[%s] %s - %s (%s)\n", check.ID, check.Name, check.Severity, statusIcon) - fmt.Printf(" Type: %s | Operation: %s | Status: %s\n", check.Type, check.Operation, check.Status) - fmt.Printf(" Description: %s\n", check.Description) - if check.Key != "" { - fmt.Printf(" Key: %s\n", check.Key) - } - if check.Value != "" { - fmt.Printf(" Value: %s\n", check.Value) - } - } - - fmt.Printf("\nRuleset: %s\n", s.ruleset) - if len(s.testsuite.Spec.Security.Disable) > 0 { - fmt.Printf("Disabled checks: %v\n", s.testsuite.Spec.Security.Disable) - } - - case "custom": - fmt.Println("\n🔒 Custom Security Checks") - fmt.Println("=" + strings.Repeat("=", 50)) - - customChecks, err := s.loadCustomChecks(ctx) - if err != nil { - fmt.Printf("Error loading custom checks: %v\n", err) - return nil - } - - if len(customChecks) == 0 { - fmt.Println("No custom security checks found.") - } else { - for _, check := range customChecks { - statusIcon := "✅ Enabled" - if check.Status == "disabled" { - statusIcon = "❌ Disabled" - } - - fmt.Printf("\n[%s] %s - %s (%s)\n", check.ID, check.Name, check.Severity, statusIcon) - fmt.Printf(" Type: %s | Operation: %s | Status: %s\n", check.Type, check.Operation, check.Status) - fmt.Printf(" Description: %s\n", check.Description) - if check.Key != "" { - fmt.Printf(" Key: %s\n", check.Key) - } - if check.Value != "" { - fmt.Printf(" Value: %s\n", check.Value) - } - } - } - fmt.Printf("\nCustom checks file: %s\n", ctx.Value("checks-path")) - - default: - return fmt.Errorf("invalid rule-set value: %s. Valid values are: basic, custom.", s.ruleset) - } - - return nil -} - -// ================================================================================================= - -func (s *SecurityChecker) runSecurityChecks(ctx context.Context, steps []Step) *SecurityReport { - report := &SecurityReport{ - TestSuite: s.testsuite.Name, - Timestamp: time.Now().Format(time.RFC3339), - Steps: make([]StepSecurityResults, 0), - Summary: make(map[string]int), - } - - // Get enabled checks based on ruleset - enabledChecks := s.getEnabledChecks(ctx) - - for _, step := range steps { - stepResults := StepSecurityResults{ - StepName: step.StepName, - StepMethod: step.StepRequest.Method, - StepURL: step.Endpoint, - Results: make([]SecurityResult, 0), - } - - for _, check := range enabledChecks { - result := s.executeCheck(check, step) - if result != nil { - stepResults.Results = append(stepResults.Results, *result) - report.Summary[result.Severity]++ - - switch result.Status { - case "passed": - report.Passed++ - stepResults.Passed++ - case "failed": - report.Failed++ - stepResults.Failed++ - case "warning": - report.Warnings++ - stepResults.Warnings++ - } - } - } - - report.Steps = append(report.Steps, stepResults) - } - - // Calculate total checks across all steps - totalChecks := 0 - for _, stepResult := range report.Steps { - totalChecks += len(stepResult.Results) - } - report.TotalChecks = totalChecks - - return report -} - -func (s *SecurityChecker) executeCheck(check SecurityCheck, step Step) *SecurityResult { - result := &SecurityResult{ - CheckID: check.ID, - CheckName: check.Name, - Severity: check.Severity, - Description: check.Description, - StepName: step.StepName, - StepMethod: step.StepRequest.Method, - StepURL: step.Endpoint, - StatusCode: step.StepResponse.StatusCode, - Target: check.Target, - } - - target := check.Target - if target == "" { - target = "response" - } - - switch check.Type { - case "header": - return s.checkHeader(check, step, result, target) - case "body": - return s.checkBody(check, step, result, target) - case "cookie": - return s.checkCookie(check, step, result) - case "url": - return s.checkURL(check, step, result) - } - - return nil -} - -func (s *SecurityChecker) checkHeader(check SecurityCheck, step Step, result *SecurityResult, target string) *SecurityResult { - var headerValue string - - if target == "request" { - headerValue = step.StepRequest.Headers.Get(check.Key) - } else { - headerValue = step.StepResponse.Headers.Get(check.Key) - } - - switch check.Operation { - case "exists": - if headerValue == "" { - result.Status = "failed" - result.Details = fmt.Sprintf("Missing %s header in %s", check.Key, target) - result.Recommendation = fmt.Sprintf("Add %s header to %s to improve security", check.Key, target) - } else { - result.Status = "passed" - result.Details = fmt.Sprintf("%s header present in %s: %s", check.Key, target, headerValue) - } - - case "equals": - if headerValue == "" { - result.Status = "failed" - result.Details = fmt.Sprintf("Missing %s header in %s", check.Key, target) - result.Recommendation = fmt.Sprintf("Add %s: %s header to %s", check.Key, check.Value, target) - } else if !strings.EqualFold(headerValue, check.Value) { - result.Status = "failed" - result.Details = fmt.Sprintf("%s header in %s has incorrect value: %s (expected: %s)", check.Key, target, headerValue, check.Value) - result.Recommendation = fmt.Sprintf("Set %s header in %s to %s", check.Key, target, check.Value) - } else { - result.Status = "passed" - result.Details = fmt.Sprintf("%s header in %s correctly set to %s", check.Key, target, check.Value) - } - - case "contains": - if headerValue == "" { - result.Status = "failed" - result.Details = fmt.Sprintf("Missing %s header in %s", check.Key, target) - result.Recommendation = fmt.Sprintf("Add %s header containing %s to %s", check.Key, check.Value, target) - } else if !strings.Contains(strings.ToLower(headerValue), strings.ToLower(check.Value)) { - result.Status = "failed" - result.Details = fmt.Sprintf("%s header in %s doesn't contain expected value: %s (looking for: %s)", check.Key, target, headerValue, check.Value) - result.Recommendation = fmt.Sprintf("Update %s header in %s to include %s", check.Key, target, check.Value) - } else { - result.Status = "passed" - result.Details = fmt.Sprintf("%s header in %s contains expected value", check.Key, target) - } - - case "not_exists": - if headerValue != "" { - result.Status = "failed" - result.Details = fmt.Sprintf("%s header should not be present in %s but found: %s", check.Key, target, headerValue) - result.Recommendation = fmt.Sprintf("Remove %s header from %s", check.Key, target) - } else { - result.Status = "passed" - result.Details = fmt.Sprintf("%s header correctly not present in %s", check.Key, target) - } - - case "not_equals": - if headerValue == check.Value { - result.Status = "failed" - result.Details = fmt.Sprintf("%s header in %s has insecure value: %s", check.Key, target, headerValue) - result.Recommendation = fmt.Sprintf("Change %s header value in %s from %s to a more secure configuration", check.Key, target, check.Value) - } else { - result.Status = "passed" - result.Details = fmt.Sprintf("%s header in %s has secure value", check.Key, target) - } - } - - return result -} - -func (s *SecurityChecker) checkBody(check SecurityCheck, step Step, result *SecurityResult, target string) *SecurityResult { - var body string - - if target == "request" { - body = step.StepRequest.Body - } else { - body = step.StepResponse.Body - } - - switch check.Operation { - case "regex": - regex, err := regexp.Compile(check.Key) - if err != nil { - s.logger.Error("Invalid regex pattern", zap.String("pattern", check.Key), zap.Error(err)) - return nil - } - - // Skip if key is in allowlist - if s.isInAllowList("keys", check.Name) { - result.Status = "passed" - result.Details = "Check skipped - in allowlist" - return result - } - - matches := regex.FindAllString(body, -1) - if len(matches) > 0 { - result.Status = "failed" - result.Details = fmt.Sprintf("Found %d potential matches in %s body", len(matches), target) - result.Recommendation = fmt.Sprintf("Remove sensitive data from %s body", target) - } else { - result.Status = "passed" - result.Details = fmt.Sprintf("No sensitive data patterns found in %s body", target) - } - - case "contains": - if strings.Contains(body, check.Key) { - result.Status = "failed" - result.Details = fmt.Sprintf("%s body contains: %s", target, check.Key) - result.Recommendation = fmt.Sprintf("Remove sensitive information from %s body", target) - } else { - result.Status = "passed" - result.Details = fmt.Sprintf("%s body doesn't contain sensitive information", target) - } - - case "not_contains": - if !strings.Contains(body, check.Key) { - result.Status = "passed" - result.Details = fmt.Sprintf("%s body correctly doesn't contain sensitive information", target) - } else { - result.Status = "failed" - result.Details = fmt.Sprintf("%s body should not contain: %s", target, check.Key) - result.Recommendation = fmt.Sprintf("Remove sensitive information from %s body", target) - } - } - - return result -} - -func (s *SecurityChecker) checkCookie(check SecurityCheck, step Step, result *SecurityResult) *SecurityResult { - // For cookies, we typically only check response cookies (Set-Cookie headers) - // since request cookies are usually sent via Cookie header which could be checked as headers - cookies := step.StepResponse.Headers["Set-Cookie"] - if len(cookies) == 0 { - result.Status = "passed" - result.Details = "No cookies set in response" - return result - } - - switch check.Operation { - case "exists": - found := false - for _, cookie := range cookies { - if strings.Contains(cookie, check.Key) { - found = true - break - } - } - - if !found { - result.Status = "failed" - result.Details = fmt.Sprintf("Cookies missing %s attribute", check.Key) - result.Recommendation = fmt.Sprintf("Add %s attribute to cookies", check.Key) - } else { - result.Status = "passed" - result.Details = fmt.Sprintf("Cookies have %s attribute", check.Key) - } - } - - return result -} - -func (s *SecurityChecker) checkURL(check SecurityCheck, step Step, result *SecurityResult) *SecurityResult { - switch check.Operation { - case "regex": - regex, err := regexp.Compile(check.Key) - if err != nil { - s.logger.Error("Invalid regex pattern", zap.String("pattern", check.Key), zap.Error(err)) - return nil - } - - if regex.MatchString(step.Endpoint) { - result.Status = "failed" - result.Details = fmt.Sprintf("URL matches insecure pattern: %s", check.Key) - result.Recommendation = "Review URL structure for security issues" - } else { - result.Status = "passed" - result.Details = "URL doesn't match insecure patterns" - } - - case "contains": - if strings.Contains(step.Endpoint, check.Key) { - result.Status = "failed" - result.Details = fmt.Sprintf("URL contains insecure element: %s", check.Key) - result.Recommendation = "Remove insecure elements from URL" - } else { - result.Status = "passed" - result.Details = "URL doesn't contain insecure elements" - } - } - - return result -} diff --git a/keploy/pkg/service/secure/service.go b/keploy/pkg/service/secure/service.go deleted file mode 100644 index 3687656..0000000 --- a/keploy/pkg/service/secure/service.go +++ /dev/null @@ -1,11 +0,0 @@ -package secure - -import "context" - -type Service interface { - Start(ctx context.Context) (*SecurityReport, error) - AddCustomCheck(ctx context.Context) error - RemoveCustomCheck(ctx context.Context) error - UpdateCustomCheck(ctx context.Context) error - ListChecks(ctx context.Context) error -} diff --git a/keploy/pkg/service/secure/utils.go b/keploy/pkg/service/secure/utils.go deleted file mode 100644 index 34fbd6f..0000000 --- a/keploy/pkg/service/secure/utils.go +++ /dev/null @@ -1,337 +0,0 @@ -package secure - -import ( - "bufio" - "context" - "fmt" - "net/http" - "os" - "path/filepath" - "strings" - - "go.keploy.io/server/v2/pkg/service/testsuite" - "go.uber.org/zap" - "gopkg.in/yaml.v3" -) - -// convertExecutionReportToSteps converts testsuite execution results to Step structures for security checks -func (s *SecurityChecker) convertExecutionReportToSteps(report *testsuite.ExecutionReport, executor *testsuite.TSExecutor) []Step { - steps := make([]Step, 0, len(report.StepsResult)) - - for i, stepResult := range report.StepsResult { - // Skip failed steps - don't run security checks on failed steps - if stepResult.Status == "failed" { - s.logger.Info("Skipping security checks for failed step", - zap.String("stepName", stepResult.StepName), - zap.String("failureReason", stepResult.FailureReason)) - continue - } - - // Get the corresponding test step from the testsuite - if i >= len(executor.Testsuite.Spec.Steps) { - continue - } - testStep := executor.Testsuite.Spec.Steps[i] - - requestHeaders := make(http.Header) - for key, value := range testStep.Headers { - interpolatedValue := executor.InterpolateVariables(value) - requestHeaders.Add(key, interpolatedValue) - } - - // Get interpolated body - interpolatedBody := executor.InterpolateVariables(testStep.Body) - - responseHeaders := stepResult.Header - if responseHeaders == nil { - responseHeaders = make(http.Header) - } - - step := Step{ - Endpoint: stepResult.URL, - StepName: stepResult.StepName, - StepRequest: StepRequest{ - Method: stepResult.Method, - Headers: requestHeaders, - Body: interpolatedBody, - }, - StepResponse: StepResponse{ - StatusCode: stepResult.StatusCode, - Headers: responseHeaders, - Body: stepResult.Body, - }, - } - - steps = append(steps, step) - } - - return steps -} - -// readInputWithCancel reads a line of input from stdin with cancellation support -func readInputWithCancel(ctx context.Context, prompt string) (string, error) { - fmt.Print(prompt) - - inputChan := make(chan string, 1) - errorChan := make(chan error, 1) - - go func() { - scanner := bufio.NewScanner(os.Stdin) - if scanner.Scan() { - inputChan <- scanner.Text() - } else { - if err := scanner.Err(); err != nil { - errorChan <- err - } else { - errorChan <- fmt.Errorf("EOF") - } - } - }() - - select { - case <-ctx.Done(): - return "", fmt.Errorf("operation cancelled") - case input := <-inputChan: - return strings.TrimSpace(input), nil - case err := <-errorChan: - return "", err - } -} - -func (s *SecurityChecker) isInAllowList(category, value string) bool { - if s.testsuite.Spec.Security.AllowList.Headers != nil && category == "headers" { - for _, header := range s.testsuite.Spec.Security.AllowList.Headers { - if strings.EqualFold(header, value) { - return true - } - } - } - - if s.testsuite.Spec.Security.AllowList.Keys != nil && category == "keys" { - for _, key := range s.testsuite.Spec.Security.AllowList.Keys { - if strings.Contains(strings.ToLower(value), strings.ToLower(key)) { - return true - } - } - } - - return false -} - -func (s *SecurityChecker) printSecurityReport(report *SecurityReport) { - fmt.Printf("\n🔒 Security Analysis Report\n") - fmt.Printf("Test Suite: %s\n", report.TestSuite) - fmt.Printf("Timestamp: %s\n", report.Timestamp) - fmt.Printf("Total Checks: %d\n", report.TotalChecks) - fmt.Printf("✅ Passed: %d\n", report.Passed) - fmt.Printf("❌ Failed: %d\n", report.Failed) - fmt.Printf("⚠️ Warnings: %d\n\n", report.Warnings) - - // Print results grouped by step - for i, stepResult := range report.Steps { - fmt.Printf("=== STEP %d: %s ===\n", i+1, stepResult.StepName) - fmt.Printf("Method: %s | URL: %s\n", stepResult.StepMethod, stepResult.StepURL) - fmt.Printf("✅ Passed: %d | ❌ Failed: %d | ⚠️ Warnings: %d\n\n", - stepResult.Passed, stepResult.Failed, stepResult.Warnings) - - // Group step results by severity - severities := []string{"CRITICAL", "HIGH", "MEDIUM", "LOW"} - for _, severity := range severities { - results := s.filterResultsBySeverity(stepResult.Results, severity) - if len(results) > 0 { - fmt.Printf("--- %s SEVERITY ---\n", severity) - for _, result := range results { - var status string - switch result.Status { - case "passed": - status = "✅" - case "failed": - status = "❌" - case "warning": - status = "⚠️" - default: - status = "❓" // Unknown status - } - - fmt.Printf("%s [%s] %s\n", status, result.CheckID, result.CheckName) - fmt.Printf(" Target: %s | %s\n", result.Target, result.Details) - if result.Recommendation != "" { - fmt.Printf(" 💡 %s\n", result.Recommendation) - } - fmt.Println() - } - } - } - fmt.Println("================================================") - } -} - -func (s *SecurityChecker) filterResultsBySeverity(results []SecurityResult, severity string) []SecurityResult { - filtered := make([]SecurityResult, 0) - for _, result := range results { - if result.Severity == severity { - filtered = append(filtered, result) - } - } - return filtered -} - -func (s *SecurityChecker) getEnabledChecks(ctx context.Context) []SecurityCheck { - var enabled []SecurityCheck - - switch s.ruleset { - case "basic": - // Use built-in checks only - for _, check := range BuiltInSecurityChecks { - // Check effective status considering both Status field and disable list - effectiveStatus := s.getEffectiveStatus(check) - if effectiveStatus == "disabled" { - continue - } - enabled = append(enabled, check) - } - - case "custom": - // Use custom checks only - customChecks, err := s.loadCustomChecks(ctx) - if err != nil { - s.logger.Error("Failed to load custom checks", zap.Error(err)) - // Fallback to built-in checks if custom checks can't be loaded - for _, check := range BuiltInSecurityChecks { - effectiveStatus := s.getEffectiveStatus(check) - if effectiveStatus == "disabled" { - continue - } - enabled = append(enabled, check) - } - } else { - if len(customChecks) == 0 { - s.logger.Warn("No custom checks found, consider adding some or using 'basic' ruleset") - } - for _, check := range customChecks { - effectiveStatus := s.getEffectiveStatus(check) - if effectiveStatus == "disabled" { - continue - } - enabled = append(enabled, check) - } - } - - default: - // Default to built-in checks for unknown rulesets - s.logger.Warn("Unknown ruleset, falling back to basic", zap.String("ruleset", s.ruleset)) - for _, check := range BuiltInSecurityChecks { - effectiveStatus := s.getEffectiveStatus(check) - if effectiveStatus == "disabled" { - continue - } - enabled = append(enabled, check) - } - } - - s.logger.Debug("Loaded security checks", - zap.String("ruleset", s.ruleset), - zap.Int("totalChecks", len(enabled))) - - return enabled -} - -// getEffectiveStatus returns the effective status of a check considering both -// the Status field and the disable list in the testsuite -func (s *SecurityChecker) getEffectiveStatus(check SecurityCheck) string { - // If explicitly disabled in testsuite, it's disabled regardless of Status field - if s.isCheckDisabled(check.ID) { - return "disabled" - } - - // If Status field is set to disabled, it's disabled - if check.Status == "disabled" { - return "disabled" - } - - // Default to enabled - return "enabled" -} - -func (s *SecurityChecker) isCheckDisabled(checkID string) bool { - if s.testsuite.Spec.Security.Disable != nil { - for _, disabledID := range s.testsuite.Spec.Security.Disable { - if fmt.Sprintf("%v", disabledID) == checkID { - return true - } - } - } - return false -} - -// Helper methods for custom checks file management -func (s *SecurityChecker) saveCustomCheck(ctx context.Context, check SecurityCheck) error { - customChecks, err := s.loadCustomChecks(ctx) - if err != nil && !os.IsNotExist(err) { - return err - } - - // Check if check with same ID already exists - for _, existingCheck := range customChecks { - if existingCheck.ID == check.ID { - return fmt.Errorf("custom check with ID '%s' already exists", check.ID) - } - } - - customChecks = append(customChecks, check) - return s.saveCustomChecks(ctx, customChecks) -} - -func (s *SecurityChecker) loadCustomChecks(ctx context.Context) ([]SecurityCheck, error) { - customPath := s.getCustomChecksPath(ctx) - - data, err := os.ReadFile(customPath) - if err != nil { - if os.IsNotExist(err) { - return []SecurityCheck{}, nil - } - return nil, err - } - - var customChecks []SecurityCheck - if err := yaml.Unmarshal(data, &customChecks); err != nil { - return nil, fmt.Errorf("failed to parse custom checks file: %w", err) - } - - return customChecks, nil -} - -func (s *SecurityChecker) saveCustomChecks(ctx context.Context, checks []SecurityCheck) error { - customPath := s.getCustomChecksPath(ctx) - - // Create directory if it doesn't exist - if err := os.MkdirAll(filepath.Dir(customPath), 0755); err != nil { - return fmt.Errorf("failed to create custom checks directory: %w", err) - } - - data, err := yaml.Marshal(checks) - if err != nil { - return fmt.Errorf("failed to marshal custom checks: %w", err) - } - - if err := os.WriteFile(customPath, data, 0644); err != nil { - return fmt.Errorf("failed to write custom checks file: %w", err) - } - - return nil -} - -func (s *SecurityChecker) getCustomChecksPath(ctx context.Context) string { - path, _ := ctx.Value("checks-path").(string) - - // CLI Override - if path != "keploy/secure/custom-checks.yaml" { - return path - } - - if s.testsuite.Spec.Security.CustomPath != "" { - return s.testsuite.Spec.Security.CustomPath - } - - return path -} diff --git a/keploy/pkg/service/service.go b/keploy/pkg/service/service.go deleted file mode 100644 index a39572d..0000000 --- a/keploy/pkg/service/service.go +++ /dev/null @@ -1,9 +0,0 @@ -// Package service provides the service interface for the service package. -package service - -import "context" - -type Auth interface { - GetToken(ctx context.Context) (string, error) - Login(ctx context.Context) bool -} diff --git a/keploy/pkg/service/testsuite/service.go b/keploy/pkg/service/testsuite/service.go deleted file mode 100644 index 35690e9..0000000 --- a/keploy/pkg/service/testsuite/service.go +++ /dev/null @@ -1,11 +0,0 @@ -package testsuite - -import ( - "context" - - "golang.org/x/time/rate" -) - -type Service interface { - Execute(ctx context.Context, limiter *rate.Limiter) (*ExecutionReport, error) -} diff --git a/keploy/pkg/service/testsuite/testsuite.go b/keploy/pkg/service/testsuite/testsuite.go deleted file mode 100644 index 5a55d36..0000000 --- a/keploy/pkg/service/testsuite/testsuite.go +++ /dev/null @@ -1,501 +0,0 @@ -package testsuite - -import ( - "context" - "crypto/tls" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "regexp" - "strings" - "time" - - "go.keploy.io/server/v2/config" - "go.uber.org/zap" - "golang.org/x/time/rate" - "gopkg.in/yaml.v3" -) - -// TestSuite represents the structure of a test suite YAML file -type TestSuite struct { - Version string `yaml:"version"` - Kind string `yaml:"kind"` - Name string `yaml:"name"` - Spec TestSuiteSpec `yaml:"spec"` -} - -// TestSuiteSpec contains the metadata and steps for a test suite -type TestSuiteSpec struct { - Metadata TestSuiteMetadata `yaml:"metadata"` - Security Security `yaml:"security,omitempty"` - Load LoadOptions `yaml:"load,omitempty"` - Steps []TestStep `yaml:"steps"` -} - -// TestSuiteMetadata contains description and other metadata for a test suite -type TestSuiteMetadata struct { - Description string `yaml:"description"` -} - -// Security contains security-related configurations for the test suite -type Security struct { - Ruleset string `yaml:"ruleset"` - CustomPath string `yaml:"custom_path,omitempty"` - SeverityThreshold string `yaml:"severity_threshold"` - Disable []string `yaml:"disable,omitempty"` - AllowList AllowList `yaml:"allow_list"` -} - -type AllowList struct { - Headers []string `yaml:"headers,omitempty"` - Keys []string `yaml:"keys,omitempty"` -} - -// LoadOptions represents load testing options -type LoadOptions struct { - Profile string `yaml:"profile"` - VUs int `yaml:"vus"` - Duration string `yaml:"duration"` - RPS int `yaml:"rps"` - Stages []LoadStage `yaml:"stages,omitempty"` - Thresholds []Threshold `yaml:"thresholds,omitempty"` -} - -// LoadStage represents a single stage in a load test -type LoadStage struct { - Duration string `yaml:"duration"` - Target int `yaml:"target"` -} - -// Threshold represents a performance threshold in load testing -type Threshold struct { - Metric string `yaml:"metric"` - Condition string `yaml:"condition"` - Severity string `yaml:"severity"` - Comment string `yaml:"comment,omitempty"` -} - -// TestStep represents a single API call step in the test suite -type TestStep struct { - Name string `yaml:"name"` - Method string `yaml:"method"` - URL string `yaml:"url"` - Body string `yaml:"body,omitempty"` - Headers map[string]string `yaml:"headers,omitempty"` - Extract map[string]string `yaml:"extract,omitempty"` - Assert []TSAssertion `yaml:"assert,omitempty"` -} - -// Assertion represents an assertion to validate API responses -type TSAssertion struct { - Type string `yaml:"type"` - Key string `yaml:"key,omitempty"` - ExpectedString string `yaml:"expected_string,omitempty"` -} - -// StepResult represents the result of executing a single test step -type StepResult struct { - StepName string `json:"step_name"` - Method string `json:"method"` - URL string `json:"url"` - Status string `json:"status"` - StatusCode int `json:"status_code,omitempty"` - Header http.Header `json:"header,omitempty"` - Body string `json:"body,omitempty"` - ResponseTime time.Duration `json:"response_time"` - FailureReason string `json:"failure_reason,omitempty"` - ExtractedVars map[string]string `json:"extracted_vars,omitempty"` - ReqBytes int64 `json:"req_bytes"` - ResBytes int64 `json:"res_bytes"` -} - -// ExecutionReport represents the summary of the test suite execution -type ExecutionReport struct { - SuiteName string `json:"suite_name"` - TotalSteps int `json:"total_steps"` - FailedSteps int `json:"failed_steps"` - StepsResult []StepResult `json:"steps_result"` - ExecutionTime time.Duration `json:"execution_time"` // Total execution of the test suite -} - -type TSExecutor struct { - config *config.Config - logger *zap.Logger - Testsuite *TestSuite - client *http.Client - baseURL string - tsPath string - tsFile string - variables map[string]string -} - -func NewTSExecutor(cfg *config.Config, logger *zap.Logger, skipParsing bool) (*TSExecutor, error) { - var testsuite *TestSuite - if !skipParsing { - if cfg.TestSuite.TSPath == "" { - logger.Error("test suite path is not set") - return nil, fmt.Errorf("test suite path is not set, use --ts-path flag to set it") - } - - if cfg.TestSuite.TSFile == "" { - logger.Error("test suite file is not set") - return nil, fmt.Errorf("test suite file is not set, use --ts-file flag to set it") - } - - testsuitePath := filepath.Join(cfg.TestSuite.TSPath, cfg.TestSuite.TSFile) - logger.Debug("parsing test suite file", zap.String("file", testsuitePath)) - - ts, err := TSParser(testsuitePath) - if err != nil { - logger.Error("failed to parse test suite", zap.Error(err)) - return nil, err - } - testsuite = &ts - logger.Info("test suite parsed successfully", zap.String("file", testsuitePath)) - } - - return &TSExecutor{ - config: cfg, - logger: logger, - Testsuite: testsuite, - client: &http.Client{ - Timeout: time.Duration(30) * time.Second, - Transport: &http.Transport{ - // disable tls check - //nolint:gosec - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - }, - baseURL: cfg.TestSuite.BaseURL, - tsPath: cfg.TestSuite.TSPath, - tsFile: cfg.TestSuite.TSFile, - variables: make(map[string]string), - }, nil -} - -func (e *TSExecutor) Execute(ctx context.Context, limiter *rate.Limiter) (*ExecutionReport, error) { - if e.baseURL == "" { - e.logger.Error("base URL is not set for the test suite execution") - return nil, fmt.Errorf("base URL is not set for the test suite execution") - } - - if e.client == nil { - e.logger.Error("HTTP client is not initialized for the test suite execution") - return nil, fmt.Errorf("HTTP client is not initialized for the test suite execution") - } - - if e.Testsuite == nil { - e.logger.Error("test suite is not set for execution") - return nil, fmt.Errorf("test suite is not set for execution, please provide a valid test suite using --ts-file or -f flag") - } - - if ctx.Value("command") == "testsuite" { - e.logger.Info("executing test suite", zap.String("path", e.tsPath), zap.String("baseURL", e.baseURL)) - } - - e.logger.Debug("test suite details", - zap.String("name", e.Testsuite.Name), - zap.String("version", e.Testsuite.Version), - zap.String("kind", e.Testsuite.Kind), - zap.String("description", e.Testsuite.Spec.Metadata.Description), - ) - e.logger.Debug("number of steps in the test suite", zap.Int("steps", len(e.Testsuite.Spec.Steps))) - e.logger.Debug("base URL for the test suite", zap.String("baseURL", e.baseURL)) - - er := &ExecutionReport{ - SuiteName: e.Testsuite.Name, - TotalSteps: len(e.Testsuite.Spec.Steps), - FailedSteps: 0, - StepsResult: make([]StepResult, 0, len(e.Testsuite.Spec.Steps)), - ExecutionTime: time.Duration(0), - } - - startTime := time.Now() - - for _, step := range e.Testsuite.Spec.Steps { - e.logger.Debug("executing step", zap.String("name", step.Name), zap.String("method", step.Method), zap.String("url", step.URL)) - if limiter != nil { - if err := limiter.Wait(ctx); err != nil { - e.logger.Debug("Rate limiter wait warn", zap.Error(err)) - continue - } - } - result, err := e.executeStep(step) - if err != nil { - e.logger.Error("failed to execute step", zap.String("step", step.Name), zap.Error(err)) - } - e.logger.Debug("step executed", zap.String("step", step.Name), zap.String("status", result.Status), zap.Any("result", result)) - er.StepsResult = append(er.StepsResult, *result) - if result.Status == "failed" { - er.FailedSteps++ - } - } - - er.ExecutionTime = time.Since(startTime) - - if ctx.Value("command") == "testsuite" { - fmt.Println("Test Suite Execution Report:") - fmt.Printf(" Suite Name: %s\n", er.SuiteName) - fmt.Printf(" Base URL: %s\n", e.baseURL) - fmt.Printf(" Total Steps: %d\n", er.TotalSteps) - fmt.Printf(" Failed Steps: %d\n", er.FailedSteps) - fmt.Printf(" Execution Time: %s\n", er.ExecutionTime) - fmt.Println(" Steps Result:") - for _, stepResult := range er.StepsResult { - fmt.Printf(" Step Name: %s\n", stepResult.StepName) - fmt.Printf(" Status: %s\n", stepResult.Status) - if stepResult.FailureReason != "" { - fmt.Printf(" Failure Reason: %s\n", stepResult.FailureReason) - } - } - - reportDir := filepath.Join(e.tsPath, "ts_reports") - if err := os.MkdirAll(reportDir, 0755); err != nil { - e.logger.Error("failed to create report directory", zap.String("dir", reportDir), zap.Error(err)) - return nil, fmt.Errorf("failed to create report directory: %v", err) - } - reportFile := filepath.Join(reportDir, fmt.Sprintf("%s_report_%s", time.Now().Format("20060102150405"), e.tsFile)) - file, err := os.Create(reportFile) - if err != nil { - e.logger.Error("failed to create report file", zap.String("file", reportFile), zap.Error(err)) - return nil, fmt.Errorf("failed to create report file: %v", err) - } - defer file.Close() - - data, err := yaml.Marshal(er) - if err != nil { - e.logger.Error("failed to marshal report data", zap.String("file", reportFile), zap.Error(err)) - return nil, fmt.Errorf("failed to marshal report data: %v", err) - } - _, err = file.Write(data) - if err != nil { - e.logger.Error("failed to write report data to file", zap.String("file", reportFile), zap.Error(err)) - return nil, fmt.Errorf("failed to write report data to file: %v", err) - } - e.logger.Info("test suite execution report saved", zap.String("file", reportFile)) - } - - return er, nil -} - -// executeStep executes a single test step and returns the result -func (e *TSExecutor) executeStep(step TestStep) (*StepResult, error) { - interpolatedURL := e.InterpolateVariables(step.URL) - interpolatedBody := e.InterpolateVariables(step.Body) - - result := &StepResult{ - StepName: step.Name, - Method: step.Method, - URL: interpolatedURL, - Status: "failed", // Default to failed, will update to passed if successful - ExtractedVars: make(map[string]string), - } - - fullURL := e.baseURL + interpolatedURL - e.logger.Debug("sending request", zap.String("url", fullURL), zap.String("method", step.Method)) - - req, err := http.NewRequest(step.Method, fullURL, strings.NewReader(interpolatedBody)) - if err != nil { - result.FailureReason = fmt.Sprintf("failed to create request: %v", err) - return result, err - } - if req.Body != nil { - bodyBytes, err := io.ReadAll(strings.NewReader(interpolatedBody)) - if err != nil { - result.FailureReason = fmt.Sprintf("failed to read request body: %v", err) - return result, err - } - result.ReqBytes = int64(len(bodyBytes)) - } else { - result.ReqBytes = 0 - } - - for key, value := range step.Headers { - interpolatedValue := e.InterpolateVariables(value) - req.Header.Add(key, interpolatedValue) - } - - const hardcodedJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4YmQxNjFhYS1iYjEyLTExZjAtOTgyYS1jNjc4ZGU1NzY2ZWYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNzYyNzc5NTU5LCJleHAiOjE3NjUzNzE1NTl9.6eYubLPlOcbpGyoM89848DKxom_AvvruU2tskv7l3Aw" - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", hardcodedJWT)) - - startTime := time.Now() - - resp, err := e.client.Do(req) - if err != nil { - result.FailureReason = fmt.Sprintf("failed to send request: %v", err) - return result, err - } - defer resp.Body.Close() - - result.ResponseTime = time.Since(startTime) - - body, err := io.ReadAll(resp.Body) - if err != nil { - result.FailureReason = fmt.Sprintf("failed to read response body: %v", err) - return result, err - } - result.ResBytes = int64(len(body)) - - result.StatusCode = resp.StatusCode - result.Header = resp.Header - result.Body = string(body) - - assertionsPassed := true - for _, assertion := range step.Assert { - interpolatedExpectedString := e.InterpolateVariables(assertion.ExpectedString) - assertionCopy := assertion - assertionCopy.ExpectedString = interpolatedExpectedString - - passed, reason := e.processAssertion(assertionCopy, resp, body) - if !passed { - assertionsPassed = false - result.FailureReason = reason - e.logger.Debug("assertion failed", - zap.String("type", assertion.Type), - zap.String("reason", reason)) - break - } - e.logger.Debug("assertion passed", zap.String("type", assertion.Type)) - } - - if assertionsPassed && len(step.Extract) > 0 { - extracted, err := e.extractVariables(step.Extract, body) - if err != nil { - result.FailureReason = fmt.Sprintf("failed to extract variables: %v", err) - return result, err - } - result.ExtractedVars = extracted - - for k, v := range extracted { - e.variables[k] = v - e.logger.Debug("variable extracted", zap.String("name", k), zap.String("value", v)) - } - } - - if assertionsPassed { - result.Status = "passed" - } - - return result, nil -} - -// Helper function to process assertions -func (e *TSExecutor) processAssertion(assertion TSAssertion, resp *http.Response, body []byte) (bool, string) { - switch assertion.Type { - case "status_code": - expectedCode := assertion.ExpectedString - actualCode := fmt.Sprintf("%d", resp.StatusCode) - if expectedCode != actualCode { - return false, fmt.Sprintf("expected status code %s but got %s", expectedCode, actualCode) - } - case "json_equal": - var jsonData interface{} - if err := json.Unmarshal(body, &jsonData); err != nil { - return false, fmt.Sprintf("failed to parse JSON response: %v", err) - } - - actualValue, err := extractJsonValue(jsonData, assertion.Key) - if err != nil { - return false, fmt.Sprintf("failed to extract JSON value for key %s: %v", assertion.Key, err) - } - - actualString := fmt.Sprintf("%v", actualValue) - - if actualString != assertion.ExpectedString { - return false, fmt.Sprintf("for key %s, expected value '%s' but got '%s'", - assertion.Key, assertion.ExpectedString, actualString) - } - case "json_array_contains": - var jsonData interface{} - if err := json.Unmarshal(body, &jsonData); err != nil { - return false, fmt.Sprintf("failed to parse JSON response: %v", err) - } - - arrayValue, err := extractJsonValue(jsonData, assertion.Key) - if err != nil { - return false, fmt.Sprintf("failed to extract JSON value for key %s: %v", assertion.Key, err) - } - - // Check if the extracted value is an array - array, ok := arrayValue.([]interface{}) - if !ok { - return false, fmt.Sprintf("value at key %s is not an array", assertion.Key) - } - - // Parse the expected string as JSON - var expectedJSON interface{} - if err := json.Unmarshal([]byte(assertion.ExpectedString), &expectedJSON); err != nil { - return false, fmt.Sprintf("failed to parse expected JSON: %v", err) - } - - // Check if any element in the array matches the expected JSON - for _, element := range array { - elementBytes, err := json.Marshal(element) - if err != nil { - continue - } - expectedBytes, err := json.Marshal(expectedJSON) - if err != nil { - continue - } - if string(elementBytes) == string(expectedBytes) { - return true, "" - } - } - - return false, fmt.Sprintf("array at key %s does not contain expected JSON object: %s", assertion.Key, assertion.ExpectedString) - default: - return false, fmt.Sprintf("unsupported assertion type: %s", assertion.Type) - } - - return true, "" -} - -// Helper function to interpolate variables in strings -func (e *TSExecutor) InterpolateVariables(input string) string { - if len(e.variables) == 0 || input == "" { - return input - } - - result := input - variableRegex := regexp.MustCompile(`\{\{(\w+)\}\}`) - - matches := variableRegex.FindAllStringSubmatch(input, -1) - for _, match := range matches { - if len(match) == 2 { - placeholder := match[0] // {{varname}} - varName := match[1] // varname - - if value, exists := e.variables[varName]; exists { - result = strings.Replace(result, placeholder, value, -1) - } - } - } - - return result -} - -// Helper function to extract variables from response -func (e *TSExecutor) extractVariables(extractMap map[string]string, body []byte) (map[string]string, error) { - var jsonData interface{} - if err := json.Unmarshal(body, &jsonData); err != nil { - return nil, fmt.Errorf("failed to parse JSON response: %v", err) - } - - result := make(map[string]string) - - for varName, jsonPath := range extractMap { - value, err := extractJsonValue(jsonData, jsonPath) - if err != nil { - return nil, fmt.Errorf("failed to extract variable %s from path %s: %v", - varName, jsonPath, err) - } - - result[varName] = fmt.Sprintf("%v", value) - } - - return result, nil -} diff --git a/keploy/pkg/service/testsuite/utils.go b/keploy/pkg/service/testsuite/utils.go deleted file mode 100644 index 0af7324..0000000 --- a/keploy/pkg/service/testsuite/utils.go +++ /dev/null @@ -1,109 +0,0 @@ -package testsuite - -import ( - "fmt" - "os" - "strings" - - "gopkg.in/yaml.v3" -) - -// TSParser parses a YAML file into a TestSuite struct -func TSParser(path string) (TestSuite, error) { - var ts TestSuite - - fileInfo, err := os.Stat(path) - if err != nil { - return ts, fmt.Errorf("error accessing file: %w", err) - } - - if fileInfo.IsDir() { - return ts, fmt.Errorf("path is a directory, expected a file") - } - - data, err := os.ReadFile(path) - if err != nil { - return ts, fmt.Errorf("error reading file: %w", err) - } - - err = yaml.Unmarshal(data, &ts) - if err != nil { - return ts, fmt.Errorf("error parsing YAML: %w", err) - } - - if ts.Kind != "TestSuite" { - return ts, fmt.Errorf("invalid Kind: expected 'TestSuite', got '%s'", ts.Kind) - } - - if len(ts.Spec.Steps) == 0 { - return ts, fmt.Errorf("no test steps found in the test suite") - } - - return ts, nil -} - -// Helper function to extract JSON values using dot notation -func extractJsonValue(data interface{}, path string) (interface{}, error) { - // Handle special case for root path ($) - if path == "$" { - return data, nil - } - - parts := strings.Split(path, ".") - current := data - - for _, part := range parts { - // Check if it's an array index - if strings.HasSuffix(part, "]") && strings.Contains(part, "[") { - // Extract the base name and index - openBracket := strings.Index(part, "[") - closeBracket := strings.Index(part, "]") - - if openBracket > 0 && closeBracket > openBracket { - baseName := part[:openBracket] - indexStr := part[openBracket+1 : closeBracket] - index := 0 - - if _, err := fmt.Sscanf(indexStr, "%d", &index); err != nil { - return nil, fmt.Errorf("invalid array index %s", indexStr) - } - - switch v := current.(type) { - case map[string]interface{}: - array, ok := v[baseName] - if !ok { - return nil, fmt.Errorf("key %s not found in JSON", baseName) - } - - arraySlice, ok := array.([]interface{}) - if !ok { - return nil, fmt.Errorf("%s is not an array", baseName) - } - - if index < 0 || index >= len(arraySlice) { - return nil, fmt.Errorf("index %d is out of bounds for array %s", index, baseName) - } - - current = arraySlice[index] - default: - return nil, fmt.Errorf("can't access %s in non-object value", part) - } - } else { - return nil, fmt.Errorf("invalid array syntax in path part %s", part) - } - } else { - switch v := current.(type) { - case map[string]interface{}: - var ok bool - current, ok = v[part] - if !ok { - return nil, fmt.Errorf("key %s not found in JSON", part) - } - default: - return nil, fmt.Errorf("can't access %s in non-object value", part) - } - } - } - - return current, nil -} diff --git a/keploy/pkg/service/tools/mock_Service.go b/keploy/pkg/service/tools/mock_Service.go deleted file mode 100644 index 1ea6dd7..0000000 --- a/keploy/pkg/service/tools/mock_Service.go +++ /dev/null @@ -1,149 +0,0 @@ -// Code generated by mockery v2.53.2. DO NOT EDIT. - -package tools - -import ( - context "context" - sync "sync" - - mock "github.com/stretchr/testify/mock" -) - -// MockService is an autogenerated mock type for the Service type -type MockService struct { - mock.Mock -} - -// CreateConfig provides a mock function with given fields: ctx, filePath, config -func (_m *MockService) CreateConfig(ctx context.Context, filePath string, config string) error { - ret := _m.Called(ctx, filePath, config) - - if len(ret) == 0 { - panic("no return value specified for CreateConfig") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, filePath, config) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Export provides a mock function with given fields: ctx -func (_m *MockService) Export(ctx context.Context) error { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Export") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Import provides a mock function with given fields: ctx, path, basePath -func (_m *MockService) Import(ctx context.Context, path string, basePath string) error { - ret := _m.Called(ctx, path, basePath) - - if len(ret) == 0 { - panic("no return value specified for Import") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, path, basePath) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Login provides a mock function with given fields: ctx -func (_m *MockService) Login(ctx context.Context) bool { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Login") - } - - var r0 bool - if rf, ok := ret.Get(0).(func(context.Context) bool); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// SendTelemetry provides a mock function with given fields: event, output -func (_m *MockService) SendTelemetry(event string, output ...*sync.Map) { - _va := make([]interface{}, len(output)) - for _i := range output { - _va[_i] = output[_i] - } - var _ca []interface{} - _ca = append(_ca, event) - _ca = append(_ca, _va...) - _m.Called(_ca...) -} - -// Templatize provides a mock function with given fields: ctx -func (_m *MockService) Templatize(ctx context.Context) error { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Templatize") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Update provides a mock function with given fields: ctx -func (_m *MockService) Update(ctx context.Context) error { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockService(t interface { - mock.TestingT - Cleanup(func()) -}) *MockService { - mock := &MockService{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/keploy/pkg/service/tools/mock_TestDB.go b/keploy/pkg/service/tools/mock_TestDB.go deleted file mode 100644 index 8a3a839..0000000 --- a/keploy/pkg/service/tools/mock_TestDB.go +++ /dev/null @@ -1,143 +0,0 @@ -// Code generated by mockery v2.53.2. DO NOT EDIT. - -package tools - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - models "go.keploy.io/server/v2/pkg/models" -) - -// MockTestDB is an autogenerated mock type for the TestDB type -type MockTestDB struct { - mock.Mock -} - -// DeleteTestSet provides a mock function with given fields: ctx, testSetID -func (_m *MockTestDB) DeleteTestSet(ctx context.Context, testSetID string) error { - ret := _m.Called(ctx, testSetID) - - if len(ret) == 0 { - panic("no return value specified for DeleteTestSet") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, testSetID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// DeleteTests provides a mock function with given fields: ctx, testSetID, testCaseIDs -func (_m *MockTestDB) DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error { - ret := _m.Called(ctx, testSetID, testCaseIDs) - - if len(ret) == 0 { - panic("no return value specified for DeleteTests") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { - r0 = rf(ctx, testSetID, testCaseIDs) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GetAllTestSetIDs provides a mock function with given fields: ctx -func (_m *MockTestDB) GetAllTestSetIDs(ctx context.Context) ([]string, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetAllTestSetIDs") - } - - var r0 []string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) []string); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]string) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetTestCases provides a mock function with given fields: ctx, testSetID -func (_m *MockTestDB) GetTestCases(ctx context.Context, testSetID string) ([]*models.TestCase, error) { - ret := _m.Called(ctx, testSetID) - - if len(ret) == 0 { - panic("no return value specified for GetTestCases") - } - - var r0 []*models.TestCase - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) ([]*models.TestCase, error)); ok { - return rf(ctx, testSetID) - } - if rf, ok := ret.Get(0).(func(context.Context, string) []*models.TestCase); ok { - r0 = rf(ctx, testSetID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*models.TestCase) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, testSetID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateTestCase provides a mock function with given fields: ctx, testCase, testSetID, enableLog -func (_m *MockTestDB) UpdateTestCase(ctx context.Context, testCase *models.TestCase, testSetID string, enableLog bool) error { - ret := _m.Called(ctx, testCase, testSetID, enableLog) - - if len(ret) == 0 { - panic("no return value specified for UpdateTestCase") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *models.TestCase, string, bool) error); ok { - r0 = rf(ctx, testCase, testSetID, enableLog) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewMockTestDB creates a new instance of MockTestDB. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockTestDB(t interface { - mock.TestingT - Cleanup(func()) -}) *MockTestDB { - mock := &MockTestDB{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/keploy/pkg/service/tools/mock_TestSetConfig.go b/keploy/pkg/service/tools/mock_TestSetConfig.go deleted file mode 100644 index 0b66756..0000000 --- a/keploy/pkg/service/tools/mock_TestSetConfig.go +++ /dev/null @@ -1,107 +0,0 @@ -// Code generated by mockery v2.53.2. DO NOT EDIT. - -package tools - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - models "go.keploy.io/server/v2/pkg/models" -) - -// MockTestSetConfig is an autogenerated mock type for the TestSetConfig type -type MockTestSetConfig struct { - mock.Mock -} - -// Read provides a mock function with given fields: ctx, testSetID -func (_m *MockTestSetConfig) Read(ctx context.Context, testSetID string) (*models.TestSet, error) { - ret := _m.Called(ctx, testSetID) - - if len(ret) == 0 { - panic("no return value specified for Read") - } - - var r0 *models.TestSet - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*models.TestSet, error)); ok { - return rf(ctx, testSetID) - } - if rf, ok := ret.Get(0).(func(context.Context, string) *models.TestSet); ok { - r0 = rf(ctx, testSetID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*models.TestSet) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, testSetID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ReadSecret provides a mock function with given fields: ctx, testSetID -func (_m *MockTestSetConfig) ReadSecret(ctx context.Context, testSetID string) (map[string]interface{}, error) { - ret := _m.Called(ctx, testSetID) - - if len(ret) == 0 { - panic("no return value specified for ReadSecret") - } - - var r0 map[string]interface{} - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (map[string]interface{}, error)); ok { - return rf(ctx, testSetID) - } - if rf, ok := ret.Get(0).(func(context.Context, string) map[string]interface{}); ok { - r0 = rf(ctx, testSetID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]interface{}) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, testSetID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Write provides a mock function with given fields: ctx, testSetID, testSet -func (_m *MockTestSetConfig) Write(ctx context.Context, testSetID string, testSet *models.TestSet) error { - ret := _m.Called(ctx, testSetID, testSet) - - if len(ret) == 0 { - panic("no return value specified for Write") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, *models.TestSet) error); ok { - r0 = rf(ctx, testSetID, testSet) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewMockTestSetConfig creates a new instance of MockTestSetConfig. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockTestSetConfig(t interface { - mock.TestingT - Cleanup(func()) -}) *MockTestSetConfig { - mock := &MockTestSetConfig{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/keploy/pkg/service/tools/service.go b/keploy/pkg/service/tools/service.go deleted file mode 100644 index b4085d7..0000000 --- a/keploy/pkg/service/tools/service.go +++ /dev/null @@ -1,37 +0,0 @@ -// Package tools provides utility functions for the service package. -package tools - -import ( - "context" - "sync" - - "go.keploy.io/server/v2/pkg/models" -) - -type Service interface { - Update(ctx context.Context) error - CreateConfig(ctx context.Context, filePath string, config string) error - SendTelemetry(event string, output ...*sync.Map) - Login(ctx context.Context) bool - Export(ctx context.Context) error - Import(ctx context.Context, path, basePath string) error - Templatize(ctx context.Context) error -} - -type teleDB interface { - SendTelemetry(event string, output ...*sync.Map) -} - -type TestSetConfig interface { - Read(ctx context.Context, testSetID string) (*models.TestSet, error) - Write(ctx context.Context, testSetID string, testSet *models.TestSet) error - ReadSecret(ctx context.Context, testSetID string) (map[string]interface{}, error) -} - -type TestDB interface { - GetAllTestSetIDs(ctx context.Context) ([]string, error) - GetTestCases(ctx context.Context, testSetID string) ([]*models.TestCase, error) - UpdateTestCase(ctx context.Context, testCase *models.TestCase, testSetID string, enableLog bool) error - DeleteTests(ctx context.Context, testSetID string, testCaseIDs []string) error - DeleteTestSet(ctx context.Context, testSetID string) error -} diff --git a/keploy/pkg/service/tools/templatize.go b/keploy/pkg/service/tools/templatize.go deleted file mode 100644 index 29c5ad7..0000000 --- a/keploy/pkg/service/tools/templatize.go +++ /dev/null @@ -1,1106 +0,0 @@ -package tools - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/url" - "reflect" - "regexp" - "strconv" - "strings" - "text/template" - - "github.com/7sDream/geko" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -func (t *Tools) Templatize(ctx context.Context) error { - - testSets := t.config.Templatize.TestSets - if len(testSets) == 0 { - all, err := t.testDB.GetAllTestSetIDs(ctx) - if err != nil { - utils.LogError(t.logger, err, "failed to get all test sets") - return err - } - testSets = all - } - - if len(testSets) == 0 { - t.logger.Warn("No test sets found to templatize") - return nil - } - - for _, testSetID := range testSets { - - testSet, err := t.testSetConf.Read(ctx, testSetID) - if err == nil && (testSet != nil && testSet.Template != nil) { - utils.TemplatizedValues = testSet.Template - } else { - utils.TemplatizedValues = make(map[string]interface{}) - } - - if err == nil && (testSet != nil && testSet.Secret != nil) { - utils.SecretValues = testSet.Secret - } else { - utils.SecretValues = make(map[string]interface{}) - } - - // Get test cases from the database - tcs, err := t.testDB.GetTestCases(ctx, testSetID) - if err != nil { - utils.LogError(t.logger, err, "failed to get test cases") - return err - } - - if len(tcs) == 0 { - t.logger.Warn("The test set is empty. Please record some test cases to templatize.", zap.String("testSet", testSetID)) - continue - } - - err = t.ProcessTestCases(ctx, tcs, testSetID) - if err != nil { - utils.LogError(t.logger, err, "failed to process test cases") - return err - } - } - return nil -} - -func (t *Tools) ProcessTestCases(ctx context.Context, tcs []*models.TestCase, testSetID string) error { - - // In test cases, we often use placeholders like {{float .id}} for templatized variables. Ideally, we should wrap - // them in double quotes, i.e., "{{float .id}}", to prevent errors during JSON unmarshaling. However, we avoid doing - // this to prevent user confusion. If a user sees "{{float .id}}", they might wonder whether it's a string or a float. - // - // To maintain clarity, we remove these placeholders during marshalling and reintroduce them during unmarshalling. - // - // Note: This conversion is applied only to `reqBody` and `respBody` because all other fields are strings, and - // templatized variables in those cases are simply concatenated. - // - // Example: - // - // Request: - // method: GET - // url: http://localhost:8080/api/employees/{{string .id}} - // - // Response: - // status_code: 200 - // header: - // Content-Type: application/json - // Date: Fri, 19 Jan 2024 06:06:03 GMT - // body: '{"id":{{float .id}},"firstName":"0","lastName":"0","email":"0"}' - // - // Notice that even if we omit quotes in the URL, marshalling does not fail. However, when unmarshalling `respBody`, - // it will throw an error if placeholders like `{{float .id}}` are not properly handled. - for _, tc := range tcs { - tc.HTTPReq.Body = addQuotesInTemplates(tc.HTTPReq.Body) - tc.HTTPResp.Body = addQuotesInTemplates(tc.HTTPResp.Body) - } - - // Process test cases for different scenarios and update the tcs and utils.TemplatizedValues - // Case 1: Response Body of one test case to Request Headers of other test cases - // (use case: Authorization token) - t.processRespBodyToReqHeader(ctx, tcs) - - // Case 2: Request Headers of one test case to Request Headers of other test cases - // (use case: Authorization token if Login API is not present in the test set) - t.processReqHeadersToReqHeader(ctx, tcs) - - // Case 3: Response Body of one test case to Response Headers of other - // (use case: POST - GET scenario) - t.processRespBodyToReqURL(ctx, tcs) - - // Case 4: Compare the req and resp body of one to other. - // (use case: POST - PUT scenario) - t.processRespBodyToReqBody(ctx, tcs) - - // Case 5: Compare the req and resp for same test case for any common fields. - // (use case: POST) request and response both have same fields. - t.processBody(ctx, tcs) - - // Case 6: Compare the req url with the response body of same test for any common fields. - // (use case: GET) URL might container same fields as response body. - t.processReqURLToRespBodySameTest(ctx, tcs) - - // case 7: Compare the resp body of one test with the response body of other tests for any common fields. - // (use case: POST - GET scenario) - t.processRespBodyToRespBody(ctx, tcs) - - // case 7: Compare the req body of one test with the response body of other tests for any common fields. - // (use case: POST - GET scenario) - t.processReqBodyToRespBody(ctx, tcs) - - // case 8: Compare the req body of one test with the req URL of other tests for any common fields. - // (use case: POST - GET scenario) - t.processReqBodyToReqURL(ctx, tcs) - - // case 9: Compare the req body of one test with the req body of other tests for any common fields. - // (use case: POST - PUT scenario) - t.processReqBodyToReqBody(ctx, tcs) - - // case 10: Compare the req URL of one test with the req body of other tests for any common fields. - // (use case: GET - PUT scenario) - t.processReqURLToReqBody(ctx, tcs) - - // case 11: Compare the req URL of one test with the req URL of other tests for any common fields - // (use case: GET - PUT scenario) - t.processReqURLToReqURL(ctx, tcs) - - // case 12: Compare the req URL of one test with the resp Body of other tests for any common fields - // (use case: GET - PUT scenario) - t.processReqURLToRespBody(ctx, tcs) - - for _, tc := range tcs { - tc.HTTPReq.Body = removeQuotesInTemplates(tc.HTTPReq.Body) - tc.HTTPResp.Body = removeQuotesInTemplates(tc.HTTPResp.Body) - err := t.testDB.UpdateTestCase(ctx, tc, testSetID, false) - if err != nil { - utils.LogError(t.logger, err, "failed to update test case") - return err - } - } - - utils.RemoveDoubleQuotes(utils.TemplatizedValues) - - var existingMetadata map[string]interface{} - existingTestSet, err := t.testSetConf.Read(ctx, testSetID) - if err == nil && existingTestSet != nil && existingTestSet.Metadata != nil { - existingMetadata = existingTestSet.Metadata - } - - err = t.testSetConf.Write(ctx, testSetID, &models.TestSet{ - PreScript: "", - PostScript: "", - Template: utils.TemplatizedValues, - Metadata: existingMetadata, - }) - if err != nil { - utils.LogError(t.logger, err, "failed to write test set") - return err - } - - if len(utils.SecretValues) > 0 { - err = utils.AddToGitIgnore(t.logger, t.config.Path, "/*/secret.yaml") - if err != nil { - t.logger.Warn("Failed to add secret files to .gitignore", zap.Error(err)) - } - } - - return nil -} - -func (t *Tools) processRespBodyToReqHeader(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) - if err != nil { - t.logger.Error("failed to parse response body, skipping RespBodyToReqHeader Template processing", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - if jsonResponse == nil { - t.logger.Debug("Skipping RespBodyToReqHeader Template processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - for j := i + 1; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - addTemplates(t.logger, tcs[j].HTTPReq.Header, jsonResponse) - } - tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) - } -} - -func (t *Tools) processReqHeadersToReqHeader(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - for j := i + 1; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - compareReqHeaders(t.logger, tcs[j].HTTPReq.Header, tcs[i].HTTPReq.Header) - } - } -} - -func (t *Tools) processRespBodyToReqURL(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) - if err != nil || jsonResponse == nil { - t.logger.Debug("Skipping response to URL processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - for j := i + 1; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - addTemplates(t.logger, &tcs[j].HTTPReq.URL, jsonResponse) - } - tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) - } -} - -func (t *Tools) processRespBodyToReqBody(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) - if err != nil || jsonResponse == nil { - t.logger.Debug("Skipping response to request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - for j := i + 1; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - jsonRequest, err := parseIntoJSON(tcs[j].HTTPReq.Body) - if err != nil || jsonRequest == nil { - t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) - continue - } - addTemplates(t.logger, jsonRequest, jsonResponse) - tcs[j].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) - } - tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) - } -} - -func (t *Tools) processBody(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs); i++ { - select { - case <-ctx.Done(): - return - default: - } - jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) - if err != nil || jsonResponse == nil { - t.logger.Debug("Skipping response to request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - jsonRequest, err := parseIntoJSON(tcs[i].HTTPReq.Body) - if err != nil || jsonRequest == nil { - t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - addTemplates(t.logger, jsonResponse, jsonRequest) - tcs[i].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) - tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) - } -} - -func (t *Tools) processReqURLToRespBodySameTest(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs); i++ { - select { - case <-ctx.Done(): - return - default: - } - jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) - if err != nil || jsonResponse == nil { - t.logger.Debug("Skipping response to URL processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - addTemplates(t.logger, &tcs[i].HTTPReq.URL, jsonResponse) - tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) - } -} - -func (t *Tools) processRespBodyToRespBody(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - jsonResponse, err := parseIntoJSON(tcs[i].HTTPResp.Body) - if err != nil || jsonResponse == nil { - t.logger.Debug("Skipping response to request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - for j := i + 1; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - jsonResponse2, err := parseIntoJSON(tcs[j].HTTPResp.Body) - if err != nil || jsonResponse2 == nil { - t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) - continue - } - addTemplates(t.logger, jsonResponse2, jsonResponse) - tcs[j].HTTPResp.Body = marshalJSON(jsonResponse2, t.logger) - } - tcs[i].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) - } -} - -func (t *Tools) processReqBodyToRespBody(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - jsonRequest, err := parseIntoJSON(tcs[i].HTTPReq.Body) - if err != nil || jsonRequest == nil { - t.logger.Debug("Skipping response to request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - for j := i + 1; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - jsonResponse, err := parseIntoJSON(tcs[j].HTTPResp.Body) - if err != nil || jsonResponse == nil { - t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) - continue - } - addTemplates(t.logger, jsonResponse, jsonRequest) - tcs[j].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) - } - tcs[i].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) - } -} - -func (t *Tools) processReqBodyToReqURL(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - jsonRequest, err := parseIntoJSON(tcs[i].HTTPReq.Body) - if err != nil || jsonRequest == nil { - t.logger.Debug("Skipping response to URL processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - for j := i + 1; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - addTemplates(t.logger, &tcs[j].HTTPReq.URL, jsonRequest) - } - tcs[i].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) - } -} - -func (t *Tools) processReqBodyToReqBody(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - jsonRequest, err := parseIntoJSON(tcs[i].HTTPReq.Body) - if err != nil || jsonRequest == nil { - t.logger.Debug("Skipping response to request body processing for test case", zap.Any("testcase", tcs[i].Name), zap.Error(err)) - continue - } - for j := i + 1; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - jsonRequest2, err := parseIntoJSON(tcs[j].HTTPReq.Body) - if err != nil || jsonRequest2 == nil { - t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) - continue - } - addTemplates(t.logger, jsonRequest2, jsonRequest) - tcs[j].HTTPReq.Body = marshalJSON(jsonRequest2, t.logger) - } - tcs[i].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) - } -} - -func (t *Tools) processReqURLToReqBody(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - for j := i + 1; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - jsonRequest, err := parseIntoJSON(tcs[j].HTTPReq.Body) - if err != nil || jsonRequest == nil { - t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) - continue - } - addTemplates(t.logger, jsonRequest, &tcs[i].HTTPReq.URL) - tcs[j].HTTPReq.Body = marshalJSON(jsonRequest, t.logger) - } - } -} - -func (t *Tools) processReqURLToRespBody(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - for j := 0; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - jsonResponse, err := parseIntoJSON(tcs[j].HTTPResp.Body) - if err != nil || jsonResponse == nil { - t.logger.Debug("Skipping request body processing for test case", zap.Any("testcase", tcs[j].Name), zap.Error(err)) - continue - } - addTemplates(t.logger, jsonResponse, &tcs[i].HTTPReq.URL) - tcs[j].HTTPResp.Body = marshalJSON(jsonResponse, t.logger) - } - } -} - -func (t *Tools) processReqURLToReqURL(ctx context.Context, tcs []*models.TestCase) { - for i := 0; i < len(tcs)-1; i++ { - for j := i + 1; j < len(tcs); j++ { - select { - case <-ctx.Done(): - return - default: - } - addTemplates(t.logger, &tcs[j].HTTPReq.URL, &tcs[i].HTTPReq.URL) - } - } -} - -// Utility function to safely marshal JSON and log errors -var jsonMarshal987 = json.Marshal - -// Utility function to safely marshal JSON and log errors -func marshalJSON(data interface{}, logger *zap.Logger) string { - jsonData, err := jsonMarshal987(data) - if err != nil { - utils.LogError(logger, err, "failed to marshal JSON data") - return "" - } - return string(jsonData) -} - -func parseIntoJSON(response string) (interface{}, error) { - if response == "" { - return nil, nil - } - // geko lib will maintain the order of the keys in the json. - result, err := geko.JSONUnmarshal([]byte(response)) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal the response: %v", err) - } - return result, nil -} - -func RenderIfTemplatized(val interface{}) (bool, interface{}, error) { - stringVal, ok := val.(string) - if !ok { - return false, val, nil - } - - // Check if the value is a template. - // Applied this nolint to ignore the staticcheck error here because of readability - // nolint:staticcheck - if !(strings.Contains(stringVal, "{{") && strings.Contains(stringVal, "}}")) { - return false, val, nil - } - - // Attempt to get the value from the template. - renderedVal, err := render(stringVal) - if err != nil { - // This captures execution errors from the render function. - return false, val, err - } - - // If render() failed to parse the template, it returns the original string. - // We check if the value has actually changed to determine if it was a real template. - if reflect.DeepEqual(renderedVal, val) { - return false, val, nil - } - - return true, renderedVal, nil -} - -func addTemplates(logger *zap.Logger, interface1 interface{}, interface2 interface{}) bool { - switch v := interface1.(type) { - case geko.ObjectItems: - keys := v.Keys() - vals := v.Values() - for i := range keys { - var err error - var isTemplatized bool - original := vals[i] - isTemplatized, vals[i], err = RenderIfTemplatized(vals[i]) - if err != nil { - return false - } - switch vals[i].(type) { - case string: - x := vals[i].(string) - addTemplates(logger, &x, interface2) - vals[i] = x - case float32, float64, int, int64: - x := interface{}(vals[i]) - addTemplates(logger, &x, interface2) - vals[i] = x - default: - addTemplates(logger, vals[i], interface2) - } - if isTemplatized { - v.SetValueByIndex(i, original) - } else { - v.SetValueByIndex(i, vals[i]) - } - } - case geko.Array: - for i, val := range v.List { - var err error - var isTemplatized bool - original := val - isTemplatized, val, err = RenderIfTemplatized(val) - if err != nil { - return false - } - switch x := val.(type) { - case string: - addTemplates(logger, &x, interface2) - v.List[i] = x - case float32, float64, int, int64: - x = interface{}(x) - addTemplates(logger, &x, interface2) - v.List[i] = x - default: - addTemplates(logger, v.List[i], interface2) - } - if isTemplatized { - v.Set(i, original) - } else { - v.Set(i, v.List[i]) - } - } - case map[string]string: - for key, val := range v { - var isTemplatized bool - original := val - isTemplatized, val1, err := RenderIfTemplatized(val) - if err != nil { - utils.LogError(logger, err, "failed to render for template") - return false - } - // just a type assertion check though it should always be string. - val, ok := (val1).(string) - if !ok { - continue - } - // Saving the auth type to add it to the template latet. - authType := "" - if key == "Authorization" && len(strings.Split(val, " ")) > 1 { - authType = strings.Split(val, " ")[0] - val = strings.Split(val, " ")[1] - } - ok = addTemplates1(logger, &val, interface2) - if !ok { - continue - } - if key == "Authorization" && len(strings.Split(val, " ")) > 1 { - val = authType + " " + val - } - - if isTemplatized { - v[key] = original - } else { - v[key] = val - } - } - case *string: - original := *v - isTemplatized, tempVal, err := RenderIfTemplatized(*v) - if err != nil { - utils.LogError(logger, err, "failed to render for template") - return false - } - var ok bool - // just a type assertion check though it should always be string. - *v, ok = (tempVal).(string) - if !ok { - return false - } - - // passing this v as reference so that it can be changed in the addTemplates1 function if required. - ok = addTemplates1(logger, v, interface2) - if ok { - return true - } - - originalURL, err := url.Parse(original) - if err != nil { - return false - } - - url, err := url.Parse(*v) - if err != nil || url.Scheme == "" || url.Host == "" { - return false - } - - // Checking the special case of the URL for path and query parameters. - urlParts := strings.Split(url.Path, "/") - originalURLParts := strings.Split(originalURL.Path, "/") - // checking if the last part of the URL is a template. - ok = addTemplates1(logger, &urlParts[len(urlParts)-1], interface2) - if isTemplatized { - urlParts[len(urlParts)-1] = originalURLParts[len(originalURLParts)-1] - } - - url.Path = strings.Join(urlParts, "/") - - if url.RawQuery != "" { - // Safely parse and templatize query parameter values. - queryParamsStr := strings.Split(url.RawQuery, "&") - for i, paramStr := range queryParamsStr { - // Use SplitN to safely handle params with or without values. - parts := strings.SplitN(paramStr, "=", 2) - // Only process parameters that have a value. - if len(parts) == 2 { - key, val := parts[0], parts[1] - addTemplates1(logger, &val, interface2) // Attempt to templatize the value. - queryParamsStr[i] = key + "=" + val // Reconstruct the parameter string. - } - // If len(parts) is 1, it's a key-only parameter (e.g., "?flag"), which we leave untouched. - } - // Reconstruct the raw query and update the URL. - url.RawQuery = strings.Join(queryParamsStr, "&") - *v = url.String() - return true - } - // reconstruct the URL with the templatized path. - *v = url.String() - return ok - case *interface{}: - switch w := (*v).(type) { - case float64, int64, int, float32: - var val string - switch x := w.(type) { - case float64: - val = utils.ToString(x) - case int64: - val = utils.ToString(x) - case int: - val = utils.ToString(x) - case float32: - val = utils.ToString(x) - } - addTemplates1(logger, &val, interface2) - parts := strings.Split(val, " ") - if len(parts) > 1 { // if the value is a template. - parts1 := strings.Split(parts[0], "{{") - if len(parts1) > 1 { - val = parts1[0] + "{{" + getType(w) + " " + parts[1] + "}}" - } - *v = val - return true - } - default: - logger.Error("unsupported type while templatizing", zap.Any("type", w)) - return false - } - } - return false -} - -// TODO: add better comment here and rename this function. -// Here we simplify the second interface and finally add the templates. -func addTemplates1(logger *zap.Logger, val1 *string, body interface{}) bool { - switch b := body.(type) { - case geko.ObjectItems: - keys := b.Keys() - vals := b.Values() - for i, key := range keys { - var err error - var isTemplatized bool - original := vals[i] - isTemplatized, vals[i], err = RenderIfTemplatized(vals[i]) - if err != nil { - utils.LogError(logger, err, "failed to render for template") - return false - } - var ok bool - switch vals[i].(type) { - case string: - x := vals[i].(string) - ok = addTemplates1(logger, val1, &x) - vals[i] = x - case float32: - x := vals[i].(float32) - ok = addTemplates1(logger, val1, &x) - vals[i] = x - case int: - x := vals[i].(int) - ok = addTemplates1(logger, val1, &x) - vals[i] = x - case int64: - x := vals[i].(int64) - ok = addTemplates1(logger, val1, &x) - vals[i] = x - case float64: - x := vals[i].(float64) - ok = addTemplates1(logger, val1, &x) - vals[i] = x - default: - ok = addTemplates1(logger, val1, vals[i]) - } - // we can't change if the type of vals[i] is also object item. - if ok && reflect.TypeOf(vals[i]) != reflect.TypeOf(b) { - newKey := insertUnique(key, *val1, utils.TemplatizedValues) - vals[i] = fmt.Sprintf("{{%s .%v }}", getType(vals[i]), newKey) - // Now change the value of the key in the object. - b.SetValueByIndex(i, vals[i]) - *val1 = fmt.Sprintf("{{%s .%v }}", getType(*val1), newKey) - return true - } - if isTemplatized { - vals[i] = original - } - - } - case geko.Array: - for i, v := range b.List { - switch x := v.(type) { - case string: - addTemplates1(logger, val1, &x) - b.List[i] = x - case float32: - addTemplates1(logger, val1, &x) - b.List[i] = x - case int: - addTemplates1(logger, val1, &x) - b.List[i] = x - case int64: - addTemplates1(logger, val1, &x) - b.List[i] = x - case float64: - addTemplates1(logger, val1, &x) - b.List[i] = x - default: - addTemplates1(logger, val1, b.List[i]) - } - b.Set(i, b.List[i]) - } - case map[string]string: - for key, val2 := range b { - var isTemplatized bool - original := val2 - isTemplatized, tempVal, err := RenderIfTemplatized(val2) - if err != nil { - utils.LogError(logger, err, "failed to render for template") - return false - } - val2, ok := (tempVal).(string) - if !ok { - continue - } - if *val1 == val2 { - newKey := insertUnique(key, val2, utils.TemplatizedValues) - b[key] = fmt.Sprintf("{{%s .%v }}", getType(val2), newKey) - *val1 = fmt.Sprintf("{{%s .%v }}", getType(*val1), newKey) - return true - } - if isTemplatized { - b[key] = original - } - - } - return false - case *string: - _, tempVal, err := RenderIfTemplatized(b) - if err != nil { - utils.LogError(logger, err, "failed to render for template") - return false - } - b, ok := (tempVal).(*string) - if !ok { - return false - } - if *val1 == *b { - return true - } - case map[string]interface{}: - for key, val2 := range b { - var err error - var isTemplatized bool - original := val2 - isTemplatized, val2, err = RenderIfTemplatized(val2) - if err != nil { - utils.LogError(logger, err, "failed to render for template") - return false - } - var ok bool - switch x := val2.(type) { - case string: - ok = addTemplates1(logger, val1, &x) - case float32: - ok = addTemplates1(logger, val1, &x) - case int: - ok = addTemplates1(logger, val1, &x) - case int64: - ok = addTemplates1(logger, val1, &x) - case float64: - ok = addTemplates1(logger, val1, &x) - default: - ok = addTemplates1(logger, val1, val2) - } - - if ok { - newKey := insertUnique(key, *val1, utils.TemplatizedValues) - if newKey == "" { - newKey = key - } - b[key] = fmt.Sprintf("{{%s .%v}}", getType(b[key]), newKey) - *val1 = fmt.Sprintf("{{%s .%v}}", getType(*val1), newKey) - } else { - if isTemplatized { - b[key] = original - } - } - } - case *float64, *int64, *int, *float32: - var val string - switch x := b.(type) { - case *float64: - val = utils.ToString(*x) - case *int64: - val = utils.ToString(*x) - case *int: - val = utils.ToString(*x) - case *float32: - val = utils.ToString(*x) - } - if *val1 == val { - return true - } - case []interface{}: - for i, val := range b { - switch x := val.(type) { - case string: - addTemplates1(logger, val1, &x) - b[i] = x - case float32: - addTemplates1(logger, val1, &x) - b[i] = x - case int: - addTemplates1(logger, val1, &x) - b[i] = x - case int64: - addTemplates1(logger, val1, &x) - b[i] = x - case float64: - addTemplates1(logger, val1, &x) - b[i] = x - default: - addTemplates1(logger, val1, b[i]) - } - b[i] = val - } - } - return false -} - -func getType(val interface{}) string { - switch val.(type) { - case string, *string: - return "string" - case int64, int, int32, *int64, *int, *int32: - return "int" - case float64, float32, *float64, *float32: - return "float" - } - //TODO: handle the default case properly, return some errot. - return "" -} - -// This function returns a unique key for each value, for instance if id already exists, it will return id1. -func insertUnique(baseKey, value string, myMap map[string]interface{}) string { - // If the key has more than one word seperated by a delimiter, remove the delimiter and add the key to the map. - if strings.Contains(baseKey, "-") { - baseKey = strings.ReplaceAll(baseKey, "-", "") - } - if myMap[baseKey] == value { - return baseKey - } - key := baseKey - i := 0 - for myMap[key] != value { - if _, exists := myMap[key]; !exists { - myMap[key] = value - break - } - i++ - key = baseKey + strconv.Itoa(i) - } - return key -} - -// TODO: Make this function generic for one value of string containing more than one template value. -// Duplicate function is being used in Simulate function as well. - -// render function gives the value of the templatized field. -func render(val string) (interface{}, error) { - // This is a map of helper functions that is used to convert the values to their appropriate types. - funcMap := template.FuncMap{ - "int": utils.ToInt, - "string": utils.ToString, - "float": utils.ToFloat, - } - - tmpl, err := template.New("template").Funcs(funcMap).Parse(val) - if err != nil { - // If parsing fails, it's likely not a valid template string, but a literal string - // that happens to contain "{{" and "}}". In this case, we should not treat it as an - // error but return the original value, as no substitution is possible. - return val, nil - } - - data := make(map[string]interface{}) - - for k, v := range utils.TemplatizedValues { - data[k] = v - } - - if len(utils.SecretValues) > 0 { - data["secret"] = utils.SecretValues - } - - var output bytes.Buffer - err = tmpl.Execute(&output, data) - if err != nil { - // An execution error (e.g., missing key) is a genuine problem and should be propagated. - return val, fmt.Errorf("failed to execute the template: %v", err) - } - - outputString := output.String() - - // Convert the string output to the appropriate type based on the template function used. - switch { - case strings.Contains(val, "{{int"): - return utils.ToInt(outputString), nil - case strings.Contains(val, "{{float"): - return utils.ToFloat(outputString), nil - } - - return outputString, nil -} - -// Compare the headers of 2 utils.TemplatizedValues requests and add the templates. -func compareReqHeaders(logger *zap.Logger, req1 map[string]string, req2 map[string]string) { - for key, val1 := range req1 { - // Check if the value is already present in the templatized values. - var isTemplatized1 bool - original1 := val1 - isTemplatized1, tempVal, err := RenderIfTemplatized(val1) - if err != nil { - utils.LogError(logger, err, "failed to render for template") - return - } - val, ok := (tempVal).(string) - if !ok { - continue - } - val1 = val - if val2, ok := req2[key]; ok { - var isTemplatized2 bool - original2 := val2 - isTemplatized2, tempVal, err := RenderIfTemplatized(val2) - if err != nil { - utils.LogError(logger, err, "failed to render for template") - return - } - val, ok = (tempVal).(string) - if !ok { - continue - } - val2 = val - if val1 == val2 { - // Trim the extra space in the string. - val2 = strings.Trim(val2, " ") - newKey := insertUnique(key, val2, utils.TemplatizedValues) - if newKey == "" { - newKey = key - } - req2[key] = fmt.Sprintf("{{%s .%v }}", getType(val2), newKey) - req1[key] = fmt.Sprintf("{{%s .%v }}", getType(val2), newKey) - } else { - if isTemplatized2 { - req2[key] = original2 - } - if isTemplatized1 { - req1[key] = original1 - } - } - } - } -} - -// removeQuotesInTemplates removes temporary quotes from non-string template placeholders before saving the test case. -// For example, it converts `{"id":"{{int .id}}}"` back to `{"id":{{int .id}}}`. -// String templates like `{"name":"{{string .name}}"}` are left untouched as they represent string literals. -func removeQuotesInTemplates(jsonStr string) string { - // Regular expression to find any quoted template placeholder and capture the template itself. - re := regexp.MustCompile(`"(\{\{[^{}]*\}\})"`) - - result := re.ReplaceAllStringFunc(jsonStr, func(match string) string { - // If the template is explicitly a string type, keep the surrounding quotes. - if strings.Contains(match, "{{string") { - return match - } - - // For all other templates (e.g., int, float, or implicit type), remove the quotes. - // The first capture group (submatches[1]) contains the template part, e.g., "{{.id}}". - submatches := re.FindStringSubmatch(match) - if len(submatches) >= 2 { - return submatches[1] - } - - // Fallback to simply trimming quotes, though it should not be reached with the current regex. - return strings.Trim(match, `"`) - }) - - return result -} - -// addQuotesInTemplates wraps template placeholders in quotes to ensure the JSON is valid for unmarshaling. -// It carefully parses the string to only wrap templates that are outside of existing JSON string literals, -// preventing corruption of JSON strings that contain "{{...}}" as part of their content. -func addQuotesInTemplates(jsonStr string) string { - var result strings.Builder - inString := false - i := 0 - for i < len(jsonStr) { - char := rune(jsonStr[i]) - - // Check for a double quote character. - if char == '"' { - // Count preceding backslashes to determine if the quote is escaped. - slashes := 0 - for k := i - 1; k >= 0 && jsonStr[k] == '\\'; k-- { - slashes++ - } - // If the number of preceding backslashes is even, this is a non-escaped quote. - if slashes%2 == 0 { - inString = !inString - } - } - - // Check for the start of a template `{{` when not inside a string literal. - if !inString && char == '{' && i+1 < len(jsonStr) && jsonStr[i+1] == '{' { - // Find the corresponding end of the template `}}`. - // This simple search assumes "}}" does not appear inside a template expression. - endIndex := strings.Index(jsonStr[i:], "}}") - if endIndex != -1 { - endIndex += i + 2 // Adjust index to be relative to the start of jsonStr. - template := jsonStr[i:endIndex] - - // Wrap the found template in quotes to make it a valid JSON string value for the parser. - result.WriteString(`"`) - result.WriteString(template) - result.WriteString(`"`) - i = endIndex - continue - } - } - - result.WriteRune(char) - i++ - } - return result.String() -} diff --git a/keploy/pkg/service/tools/templatize_test.go b/keploy/pkg/service/tools/templatize_test.go deleted file mode 100644 index bf35d24..0000000 --- a/keploy/pkg/service/tools/templatize_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package tools - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -// TestRenderIfTemplatized_NonTemplateString_002 tests the RenderIfTemplatized function for non-template strings. -func TestRenderIfTemplatized_NonTemplateString_002(t *testing.T) { - val := "plain string" - isTemplatized, renderedVal, err := RenderIfTemplatized(val) - - require.NoError(t, err) - assert.False(t, isTemplatized) - assert.Equal(t, val, renderedVal) -} - -// TestAddQuotesInTemplates_WrapTemplates_003 tests the addQuotesInTemplates function for wrapping templates in quotes. -func TestAddQuotesInTemplates_WrapTemplates_003(t *testing.T) { - input := `{"id":{{int .id}}}` - expected := `{"id":"{{int .id}}"}` - result := addQuotesInTemplates(input) - - assert.Equal(t, expected, result) -} - -// TestRemoveQuotesInTemplates_RemoveQuotes_004 tests the removeQuotesInTemplates function for removing quotes from templates. -func TestRemoveQuotesInTemplates_RemoveQuotes_004(t *testing.T) { - input := `{"id":"{{int .id}}"}` - expected := `{"id":{{int .id}}}` - result := removeQuotesInTemplates(input) - - assert.Equal(t, expected, result) -} - -// TestRender_ParseAndExecuteTemplates_005 tests the render function for parsing and executing templates. -func TestRender_ParseAndExecuteTemplates_005(t *testing.T) { - utils.TemplatizedValues = map[string]interface{}{"id": 123} - val := "{{int .id}}" - result, err := render(val) - - require.NoError(t, err) - assert.Equal(t, 123, result) -} - -// TestInsertUnique_GenerateUniqueKeys_006 tests the insertUnique function for generating unique keys. -func TestInsertUnique_GenerateUniqueKeys_006(t *testing.T) { - myMap := map[string]interface{}{"id": "123"} - key := insertUnique("id", "123", myMap) - - assert.Equal(t, "id", key) - - key = insertUnique("id", "456", myMap) - assert.Equal(t, "id1", key) -} - -// TestRenderIfTemplatized_TemplateProcessing_001 tests the RenderIfTemplatized function for valid template strings. -func TestRenderIfTemplatized_TemplateProcessing_001(t *testing.T) { - utils.TemplatizedValues = map[string]interface{}{"id": 123} - val := "{{int .id}}" - isTemplatized, renderedVal, err := RenderIfTemplatized(val) - - require.NoError(t, err) - assert.True(t, isTemplatized) - assert.Equal(t, 123, renderedVal) -} - -// TestUtilityFunctions_VariousScenarios_523 groups tests for several small helper functions -// to verify their behavior with different kinds of inputs. -func TestUtilityFunctions_VariousScenarios_523(t *testing.T) { - t.Run("getType should return correct type names", func(t *testing.T) { - assert.Equal(t, "string", getType("a string")) - assert.Equal(t, "string", getType(new(string))) - assert.Equal(t, "int", getType(int64(1))) - assert.Equal(t, "int", getType(1)) - assert.Equal(t, "int", getType(new(int))) - assert.Equal(t, "float", getType(float64(1.1))) - assert.Equal(t, "float", getType(float32(1.1))) - assert.Equal(t, "float", getType(new(float64))) - assert.Equal(t, "", getType(true)) // default case - }) - - t.Run("insertUnique should generate unique keys correctly", func(t *testing.T) { - m := make(map[string]interface{}) - // New key - key := insertUnique("id", "123", m) - assert.Equal(t, "id", key) - assert.Equal(t, "123", m["id"]) - // Existing key, same value - key = insertUnique("id", "123", m) - assert.Equal(t, "id", key) - // Existing key, different value - key = insertUnique("id", "456", m) - assert.Equal(t, "id1", key) - assert.Equal(t, "456", m["id1"]) - // Key with hyphen - key = insertUnique("user-id", "abc", m) - assert.Equal(t, "userid", key) - assert.Equal(t, "abc", m["userid"]) - }) - - t.Run("marshalJSON should handle errors", func(t *testing.T) { - // A channel cannot be marshaled to JSON - data := make(chan int) - result := marshalJSON(data, zap.NewNop()) - assert.Equal(t, "", result) - }) - - t.Run("parseIntoJSON should handle various inputs", func(t *testing.T) { - res, err := parseIntoJSON("") - assert.NoError(t, err) - assert.Nil(t, res) - - res, err = parseIntoJSON("{'key': 'val'}") // invalid JSON - assert.Error(t, err) - assert.Nil(t, res) - - res, err = parseIntoJSON(`{"key": "val"}`) - assert.NoError(t, err) - assert.NotNil(t, res) - }) -} - -// TestQuoteHandlingInTemplates_729 verifies that template placeholders are correctly -// quoted for JSON parsing and unquoted for storage, while respecting explicit string templates. -func TestQuoteHandlingInTemplates_729(t *testing.T) { - t.Run("addQuotesInTemplates", func(t *testing.T) { - // Wraps non-string template - input1 := `{"id":{{int .id}},"name":"test"}` - expected1 := `{"id":"{{int .id}}","name":"test"}` - assert.Equal(t, expected1, addQuotesInTemplates(input1)) - - // Ignores templates already inside a string - input2 := `{"query":"SELECT * FROM users WHERE name = '{{.name}}'"}` - assert.Equal(t, input2, addQuotesInTemplates(input2)) - - // Handles escaped quotes - input3 := `{"key":"value with \" and {{template}}"}` - expected3 := `{"key":"value with \" and {{template}}"}` - assert.Equal(t, expected3, addQuotesInTemplates(input3)) - }) - - t.Run("removeQuotesInTemplates", func(t *testing.T) { - // Removes quotes from non-string template - input1 := `{"id":"{{int .id}}"}` - expected1 := `{"id":{{int .id}}}` - assert.Equal(t, expected1, removeQuotesInTemplates(input1)) - - // Keeps quotes for string template - input2 := `{"name":"{{string .name}}"}` - assert.Equal(t, input2, removeQuotesInTemplates(input2)) - - // Handles mixed case - input3 := `{"id":"{{int .id}}","name":"{{string .name}}"}` - expected3 := `{"id":{{int .id}},"name":"{{string .name}}"}` - assert.Equal(t, expected3, removeQuotesInTemplates(input3)) - }) -} diff --git a/keploy/pkg/service/tools/tools.go b/keploy/pkg/service/tools/tools.go deleted file mode 100644 index 59fbc23..0000000 --- a/keploy/pkg/service/tools/tools.go +++ /dev/null @@ -1,330 +0,0 @@ -package tools - -import ( - "archive/tar" - "compress/gzip" - "context" - "errors" - "fmt" - "io" - "io/fs" - "net/http" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - "sync" - - "github.com/charmbracelet/glamour" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/service" - "go.keploy.io/server/v2/pkg/service/export" - postmanimport "go.keploy.io/server/v2/pkg/service/import" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" - yamlLib "gopkg.in/yaml.v3" -) - -func NewTools(logger *zap.Logger, testsetConfig TestSetConfig, testDB TestDB, telemetry teleDB, auth service.Auth, config *config.Config) Service { - return &Tools{ - logger: logger, - telemetry: telemetry, - auth: auth, - testSetConf: testsetConfig, - testDB: testDB, - config: config, - } -} - -type Tools struct { - logger *zap.Logger - telemetry teleDB - testSetConf TestSetConfig - testDB TestDB - config *config.Config - auth service.Auth -} - -var ErrGitHubAPIUnresponsive = errors.New("GitHub API is unresponsive") - -func (t *Tools) SendTelemetry(event string, output ...*sync.Map) { - t.telemetry.SendTelemetry(event, output...) -} - -func (t *Tools) Export(ctx context.Context) error { - return export.Export(ctx, t.logger) -} - -func (t *Tools) Import(ctx context.Context, path, basePath string) error { - postmanImport := postmanimport.NewPostmanImporter(ctx, t.logger) - return postmanImport.Import(path, basePath) -} - -// Update initiates the tools process for the Keploy binary file. -func (t *Tools) Update(ctx context.Context) error { - currentVersion := "v" + utils.Version - isKeployInDocker := len(os.Getenv("KEPLOY_INDOCKER")) > 0 - if isKeployInDocker { - fmt.Println("As you are using docker version of keploy, please pull the latest Docker image of keploy to update keploy") - return nil - } - if strings.HasSuffix(currentVersion, "-dev") { - fmt.Println("you are using a development version of Keploy. Skipping update") - return nil - } - - releaseInfo, err := utils.GetLatestGitHubRelease(ctx, t.logger) - if err != nil { - if errors.Is(err, ErrGitHubAPIUnresponsive) { - return errors.New("gitHub API is unresponsive. Update process cannot continue") - } - return fmt.Errorf("failed to fetch latest GitHub release version: %v", err) - } - - latestVersion := releaseInfo.TagName - changelog := releaseInfo.Body - - if currentVersion == latestVersion { - fmt.Println("✅You are already on the latest version of Keploy: " + latestVersion) - return nil - } - - t.logger.Info("Updating to Version: " + latestVersion) - - downloadURL := "" - - if runtime.GOOS == "linux" { - if runtime.GOARCH == "amd64" { - downloadURL = "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" - } else { - downloadURL = "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_arm64.tar.gz" - } - } - - if runtime.GOOS == "darwin" { - downloadURL = "https://github.com/keploy/keploy/releases/latest/download/keploy_darwin_all.tar.gz" - } - - err = t.downloadAndUpdate(ctx, t.logger, downloadURL) - if err != nil { - return err - } - - t.logger.Info("Update Successful!") - - changelog = "\n" + string(changelog) - var renderer *glamour.TermRenderer - - var termRendererOpts []glamour.TermRendererOption - termRendererOpts = append(termRendererOpts, glamour.WithAutoStyle(), glamour.WithWordWrap(0)) - - renderer, err = glamour.NewTermRenderer(termRendererOpts...) - if err != nil { - utils.LogError(t.logger, err, "failed to initialize renderer") - return err - } - changelog, err = renderer.Render(changelog) - if err != nil { - utils.LogError(t.logger, err, "failed to render release notes") - return err - } - fmt.Println(changelog) - return nil -} - -func (t *Tools) downloadAndUpdate(ctx context.Context, logger *zap.Logger, downloadURL string) error { - // Create a new request with context - req, err := http.NewRequestWithContext(ctx, "GET", downloadURL, nil) - if err != nil { - return fmt.Errorf("failed to create request: %v", err) - } - - // Create a HTTP client and execute the request - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("failed to download file: %v", err) - } - defer func() { - if cerr := resp.Body.Close(); cerr != nil { - utils.LogError(logger, cerr, "failed to close response body") - } - }() - - // Create a temporary file to store the downloaded tar.gz - tmpFile, err := os.CreateTemp("", "keploy-download-*.tar.gz") - if err != nil { - return fmt.Errorf("failed to create temporary file: %v", err) - } - defer func() { - if err := tmpFile.Close(); err != nil { - utils.LogError(logger, err, "failed to close temporary file") - } - if err := os.Remove(tmpFile.Name()); err != nil { - utils.LogError(logger, err, "failed to remove temporary file") - } - }() - - // Write the downloaded content to the temporary file - _, err = io.Copy(tmpFile, resp.Body) - if err != nil { - return fmt.Errorf("failed to write to temporary file: %v", err) - } - - // Extract the tar.gz file - if err := extractTarGz(tmpFile.Name(), "/tmp"); err != nil { - return fmt.Errorf("failed to extract tar.gz file: %v", err) - } - - // Determine the path based on the alias "keploy" - aliasPath := "/usr/local/bin/keploy" // Default path - - keployPath, err := exec.LookPath("keploy") - if err == nil && keployPath != "" { - aliasPath = keployPath - } - - // Check if the aliasPath is a valid path - _, err = os.Stat(aliasPath) - if os.IsNotExist(err) { - return fmt.Errorf("alias path %s does not exist", aliasPath) - } - - // Check if the aliasPath is a directory - if fileInfo, err := os.Stat(aliasPath); err == nil && fileInfo.IsDir() { - return fmt.Errorf("alias path %s is a directory, not a file", aliasPath) - } - - // Move the extracted binary to the alias path - if err := os.Rename("/tmp/keploy", aliasPath); err != nil { - return fmt.Errorf("failed to move keploy binary to %s: %v", aliasPath, err) - } - - if err := os.Chmod(aliasPath, 0777); err != nil { - return fmt.Errorf("failed to set execute permission on %s: %v", aliasPath, err) - } - - return nil -} - -func extractTarGz(gzipPath, destDir string) error { - file, err := os.Open(gzipPath) - if err != nil { - return err - } - - defer func() { - if err := file.Close(); err != nil { - utils.LogError(nil, err, "failed to close file") - } - }() - - gzipReader, err := gzip.NewReader(file) - if err != nil { - return err - } - - defer func() { - if err := gzipReader.Close(); err != nil { - utils.LogError(nil, err, "failed to close gzip reader") - } - }() - - tarReader := tar.NewReader(gzipReader) - - for { - header, err := tarReader.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - fileName := filepath.Clean(header.Name) - if strings.Contains(fileName, "..") { - return fmt.Errorf("invalid file path: %s", fileName) - } - - target := filepath.Join(destDir, header.Name) - - switch header.Typeflag { - case tar.TypeDir: - if err := os.MkdirAll(target, 0777); err != nil { - return err - } - case tar.TypeReg: - outFile, err := os.Create(target) - if err != nil { - return err - } - if _, err := io.Copy(outFile, tarReader); err != nil { - if err := outFile.Close(); err != nil { - return err - } - return err - } - if err := outFile.Close(); err != nil { - return err - } - } - } - return nil -} - -func (t *Tools) CreateConfig(_ context.Context, filePath string, configData string) error { - var node yamlLib.Node - var data []byte - var err error - - if configData != "" { - data = []byte(configData) - } else { - configData, err = config.Merge(config.InternalConfig, config.GetDefaultConfig()) - if err != nil { - utils.LogError(t.logger, err, "failed to create default config string") - return nil - } - data = []byte(configData) - } - - if err := yamlLib.Unmarshal(data, &node); err != nil { - utils.LogError(t.logger, err, "failed to unmarshal the config") - return nil - } - results, err := yamlLib.Marshal(node.Content[0]) - if err != nil { - utils.LogError(t.logger, err, "failed to marshal the config") - return nil - } - - finalOutput := append(results, []byte(utils.ConfigGuide)...) - finalOutput = append([]byte(utils.GetVersionAsComment()), finalOutput...) - - err = os.WriteFile(filePath, finalOutput, fs.ModePerm) - if err != nil { - utils.LogError(t.logger, err, "failed to write config file") - return nil - } - - err = os.Chmod(filePath, 0777) // Set permissions to 777 - if err != nil { - utils.LogError(t.logger, err, "failed to set the permission of config file") - return nil - } - - return nil -} - -func (t *Tools) IgnoreTests(_ context.Context, _ string, _ []string) error { - return nil -} - -func (t *Tools) IgnoreTestSet(_ context.Context, _ string) error { - return nil -} - -func (t *Tools) Login(ctx context.Context) bool { - return t.auth.Login(ctx) -} diff --git a/keploy/pkg/service/utgen/ai.go b/keploy/pkg/service/utgen/ai.go deleted file mode 100644 index acc2e13..0000000 --- a/keploy/pkg/service/utgen/ai.go +++ /dev/null @@ -1,391 +0,0 @@ -package utgen - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "strings" - "time" - - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/service" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type AIClient struct { - Model string - APIBase string - APIVersion string - APIKey string - APIServerURL string - Auth service.Auth - Logger *zap.Logger - SessionID string - FunctionUnderTest string -} - -type Prompt struct { - System string `json:"system"` - User string `json:"user"` -} - -type CompletionParams struct { - Model string `json:"model"` - Messages []Message `json:"messages"` - MaxTokens int `json:"max_tokens,omitempty"` - MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` - Stream *bool `json:"stream,omitempty"` - Temperature float32 `json:"temperature,omitempty"` - ReasoningEffort ReasoningType `json:"reasoning_effort,omitempty"` -} - -type ReasoningType string - -const ( - HighReasioning ReasoningType = "high" // HighReasioning represents high reasoning effort. - LowReasioning ReasoningType = "low" // LowReasioning represents low reasoning effort. - MediumReasioning ReasoningType = "medium" // MediumReasioning represents medium reasoning effort. -) - -type Message struct { - Role string `json:"role"` - Content string `json:"content"` -} - -type ModelResponse struct { - ID string `json:"id"` - Choices []Choice `json:"choices"` - Created int `json:"created"` - Model string `json:"model,omitempty"` - Object string `json:"object"` - SystemFingerprint string `json:"system_fingerprint,omitempty"` - Usage *Usage `json:"usage,omitempty"` -} - -type Usage struct { - PromptTokens int `json:"prompt_tokens"` - CompletionTokens int `json:"completion_tokens"` - TotalTokens int `json:"total_tokens"` -} - -type ResponseChunk struct { - Choices []Choice `json:"choices"` -} - -type Delta struct { - Content string `json:"content"` -} - -type AIResponse struct { - IsSuccess bool `json:"isSuccess"` - Error string `json:"error"` - FinalContent string `json:"finalContent"` - PromptTokens int `json:"promptTokens"` - CompletionTokens int `json:"completionTokens"` - APIKey string `json:"apiKey"` -} - -type AIRequest struct { - MaxTokens int `json:"maxTokens"` - Prompt Prompt `json:"prompt"` - SessionID string `json:"sessionId"` - Iteration int `json:"iteration"` - RequestPurpose PurposeType `json:"requestPurpose"` -} - -// PurposeType defines the type of purpose for the AI request. -type PurposeType string - -const ( - // TestForFunction represents a purpose type where the request is to test a function. - TestForFunction PurposeType = "TestForFunction" - - // TestForFile represents a purpose type where the request is to test a file. - TestForFile PurposeType = "TestForFile" -) - -type CompletionResponse struct { - ID string `json:"id"` - Object string `json:"object"` - Created int64 `json:"created"` - Model string `json:"model"` - Choices []Choice `json:"choices"` - Usage UsageData `json:"usage"` -} - -type Choice struct { - Index int `json:"index"` - FinishReason string `json:"finish_reason"` - Message Message `json:"message"` - Delta Delta `json:"delta"` -} - -type UsageData struct { - PromptTokens int `json:"prompt_tokens"` - CompletionTokens int `json:"completion_tokens"` - TotalTokens int `json:"total_tokens"` -} - -func NewAIClient(genConfig config.UtGen, apiKey, apiServerURL string, auth service.Auth, sessionID string, logger *zap.Logger) *AIClient { - return &AIClient{ - Model: genConfig.Model, - APIBase: genConfig.APIBaseURL, - APIVersion: genConfig.APIVersion, - Logger: logger, - APIKey: apiKey, - APIServerURL: apiServerURL, - Auth: auth, - SessionID: sessionID, - FunctionUnderTest: genConfig.FunctionUnderTest, - } -} - -func (ai *AIClient) Call(ctx context.Context, completionParams CompletionParams, aiRequest AIRequest, stream bool) (string, error) { - - var apiBaseURL string - - var apiKey string - if ai.APIBase == ai.APIServerURL { - - token, err := ai.Auth.GetToken(ctx) - if err != nil { - return "", fmt.Errorf("error getting token: %v", err) - } - - ai.Logger.Debug("Making AI request to API server", zap.String("api_server_url", ai.APIServerURL), zap.String("token", token)) - httpClient := &http.Client{} - aiRequestBytes, err := json.Marshal(aiRequest) - if err != nil { - return "", fmt.Errorf("error marshalling AI request: %v", err) - } - - req, err := http.NewRequest("POST", fmt.Sprintf("%s/ai/call", ai.APIServerURL), bytes.NewBuffer(aiRequestBytes)) - if err != nil { - return "", fmt.Errorf("error creating request: %v", err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+token) - - resp, err := httpClient.Do(req) - if err != nil { - return "", fmt.Errorf("error making request: %v", err) - } - - bodyBytes, _ := io.ReadAll(resp.Body) - var aiResponse AIResponse - err = json.Unmarshal(bodyBytes, &aiResponse) - if err != nil { - return "", fmt.Errorf("error unmarshalling response body: %v", err) - } - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("unexpected status code: %v, response body: %s", resp.StatusCode, aiResponse.Error) - } - defer func() { - err := resp.Body.Close() - if err != nil { - utils.LogError(ai.Logger, err, "failed to close response body for authentication") - } - }() - - return aiResponse.FinalContent, nil - } else if ai.APIBase != "" { - apiBaseURL = ai.APIBase - } else { - apiBaseURL = "https://api.openai.com/v1" - } - - if completionParams.Model == "" { - completionParams.Model = "gpt-4o" - if ai.Model != "" { - completionParams.Model = ai.Model - } - } - - if len(completionParams.Messages) == 0 { - var messages []Message - if aiRequest.Prompt.System == "" { - messages = []Message{ - {Role: "user", Content: aiRequest.Prompt.User}, - } - } else { - messages = []Message{ - {Role: "system", Content: aiRequest.Prompt.System}, - {Role: "user", Content: aiRequest.Prompt.User}, - } - } - - completionParams.Messages = messages - } - - requestBody, err := json.Marshal(completionParams) - if err != nil { - return "", fmt.Errorf("error marshalling request body: %v", err) - } - - queryParams := "" - if ai.APIVersion != "" { - queryParams = "?api-version=" + ai.APIVersion - } - - req, err := http.NewRequestWithContext(ctx, "POST", apiBaseURL+"/chat/completions"+queryParams, bytes.NewBuffer(requestBody)) - if err != nil { - return "", fmt.Errorf("error creating request: %v", err) - } - - if ai.APIKey == "" { - apiKey = os.Getenv("API_KEY") - } else { - apiKey = ai.APIKey - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+apiKey) - req.Header.Set("api-key", apiKey) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", fmt.Errorf("error making request: %v", err) - } - defer func() { - if err := resp.Body.Close(); err != nil { - utils.LogError(ai.Logger, err, "Error closing response body") - } - }() - - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := io.ReadAll(resp.Body) - bodyString := string(bodyBytes) - return "", fmt.Errorf("unexpected status code: %v, response body: %s", resp.StatusCode, bodyString) - } - - var contentBuilder strings.Builder - reader := bufio.NewReader(resp.Body) - - if ai.Logger.Level() == zap.DebugLevel { - fmt.Println("Streaming results from LLM model...") - } - - fmt.Println("Streaming results from LLM model...") - if stream { - for { - line, err := reader.ReadString('\n') - if err != nil && err != io.EOF { - utils.LogError(ai.Logger, err, "Error reading stream") - return "", err - } - line = strings.TrimSpace(strings.TrimPrefix(line, "data: ")) - if line == "[DONE]" { - break - } - - if line == "" { - continue - } - - var chunk ModelResponse - err = json.Unmarshal([]byte(line), &chunk) - if err != nil { - utils.LogError(ai.Logger, err, "Error unmarshalling chunk") - continue - } - - if len(chunk.Choices) > 0 { - if chunk.Choices[0].Delta != (Delta{}) { - contentBuilder.WriteString(chunk.Choices[0].Delta.Content) - if ai.Logger.Level() == zap.DebugLevel { - fmt.Print(chunk.Choices[0].Delta.Content) - } - } - } - - if err == io.EOF { - break - } - time.Sleep(10 * time.Millisecond) - } - } else { - responseData, err := io.ReadAll(resp.Body) - if err != nil { - return "", fmt.Errorf("error reading response: %v", err) - } - var completionResponse CompletionResponse - err = json.Unmarshal(responseData, &completionResponse) - if err != nil { - return "", fmt.Errorf("error unmarshalling response: %v", err) - } - if len(completionResponse.Choices) > 0 { - finalContent := completionResponse.Choices[0].Message.Content - return finalContent, nil - } - } - finalContent := contentBuilder.String() - - return finalContent, nil -} - -func (ai *AIClient) SendCoverageUpdate(ctx context.Context, sessionID string, oldCoverage, newCoverage float64, iterationCount int) error { - // Construct the request body with session ID, old coverage, and new coverage - requestPurpose := TestForFile - if len(ai.FunctionUnderTest) > 0 { - requestPurpose = TestForFunction - } - requestBody, err := json.Marshal(map[string]interface{}{ - "sessionId": sessionID, - "initalCoverage": oldCoverage, - "finalCoverage": newCoverage, - "iteration": iterationCount, - "requestPurpose": requestPurpose, - }) - if err != nil { - return fmt.Errorf("error marshalling request body: %v", err) - } - - // Determine the base URL - var apiBaseURL string - if ai.APIBase != "" { - apiBaseURL = ai.APIBase - } - // Create a POST request - req, err := http.NewRequestWithContext(ctx, "POST", apiBaseURL+"/ai/coverage/update", bytes.NewBuffer(requestBody)) - if err != nil { - return fmt.Errorf("error creating request: %v", err) - } - - token, err := ai.Auth.GetToken(ctx) - - if err != nil { - return fmt.Errorf("error getting token: %v", err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+token) - - // Execute the request - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("error making request: %v", err) - } - defer func() { - if err := resp.Body.Close(); err != nil { - utils.LogError(ai.Logger, err, "Error closing response body") - } - }() - - // Check for successful response - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := io.ReadAll(resp.Body) - bodyString := string(bodyBytes) - return fmt.Errorf("unexpected status code: %v, response body: %s", resp.StatusCode, bodyString) - } - - ai.Logger.Debug("Coverage update sent successfully", zap.String("session_id", sessionID)) - return nil -} diff --git a/keploy/pkg/service/utgen/assets/config.go b/keploy/pkg/service/utgen/assets/config.go deleted file mode 100644 index 0b9a461..0000000 --- a/keploy/pkg/service/utgen/assets/config.go +++ /dev/null @@ -1,58 +0,0 @@ -// Package settings provides prompt settings for the test generation -package settings - -import ( - "bytes" - "embed" - "log" - "sync" - - "github.com/spf13/viper" -) - -// SingletonSettings manages the singleton instance of the configuration settings -type SingletonSettings struct { - viper *viper.Viper -} - -var instance *SingletonSettings -var once sync.Once - -//go:embed *.toml -var settings embed.FS - -// NewSingletonSettings initializes the singleton settings instance -func NewSingletonSettings() *SingletonSettings { - once.Do(func() { - - settingsFiles := []string{ - "test_generation.toml", - "language.toml", - "indentation.toml", - "insert_line.toml", - } - - v := viper.New() - v.SetConfigType("toml") - for _, file := range settingsFiles { - fileContent, err := settings.ReadFile(file) - if err != nil { - log.Fatalf("Failed to read settings file %s: %v", file, err) - } - v.SetConfigFile(file) - if err := v.MergeConfig(bytes.NewBuffer(fileContent)); err != nil { - log.Fatalf("Error loading config file : %v", err) - } - } - - instance = &SingletonSettings{ - viper: v, - } - }) - return instance -} - -// GetSettings returns the singleton settings instance -func GetSettings() *viper.Viper { - return NewSingletonSettings().viper -} diff --git a/keploy/pkg/service/utgen/assets/indentation.toml b/keploy/pkg/service/utgen/assets/indentation.toml deleted file mode 100644 index fba1bb9..0000000 --- a/keploy/pkg/service/utgen/assets/indentation.toml +++ /dev/null @@ -1,41 +0,0 @@ -[indentation] -system="""\ -""" - -user="""\ -## Overview -You are a code assistant designed to analyze a {{ .language }} test file. -Your task is to provide specific feedback on this file, including the programming language, the testing framework required, the number of tests, and the indentation level of the test headers. -You will be given the test file, named `{{ .test_file_name }}` with existing tests, with line numbers added for clarity. -These line numbers are not part of the original code. -========= -{{ .test_file | trim }} -========= - - -Analyze the provided test file and generate a YAML object matching the $TestsAnalysis type, according to these Pydantic definitions: -===== - -class TestsAnalysis(BaseModel): - language: str = Field(description="The programming language used by the test file") - testing_framework: str = Field(description="The testing framework needed to run the tests in the test file") - number_of_tests: int = Field(description="The number of tests in the test file") - test_headers_indentation: int = Field(description="The indentation of the test headers in the test file.\ - For example, "def test_..." has an indentation of 0, " def test_..." has an indentation of 2, " def test_..." has an indentation of 4, and so on.") - -===== - - -Example output: -```yaml -language: {{ .language }} -testing_framework: ... -number_of_tests: ... -test_headers_indentation: ... -``` - -The Response should be only a valid YAML object, without any introduction text or follow-up text. - -Answer: -```yaml -""" diff --git a/keploy/pkg/service/utgen/assets/insert_line.toml b/keploy/pkg/service/utgen/assets/insert_line.toml deleted file mode 100644 index 4605c16..0000000 --- a/keploy/pkg/service/utgen/assets/insert_line.toml +++ /dev/null @@ -1,37 +0,0 @@ -[insert_line] -system="""\ -""" - -user="""\ -## Overview -You are a code assistant designed to analyze a {{ .language }} test file. -Your task is to provide specific feedback on this file, including the programming language, the testing framework required, the number of tests, and the line number after which new tests should be inserted to be part of the existing test suite. -You will be given the test file, named `{{ .test_file_name }}`, with line numbers added for clarity and existing tests if there are any. -========= -{{ .test_file_numbered | trim }} -========= - - -Analyze the provided test file and generate a YAML object matching the $TestsAnalysis type, according to these Pydantic definitions: -===== -class TestsAnalysis(BaseModel): - language: str = Field(description="The programming language used by the test file") - testing_framework: str = Field(description="The testing framework needed to run the tests in the test file") - number_of_tests: int = Field(description="The number of tests in the test file") - relevant_line_number_to_insert_after: int = Field(description="The line number in the test file, **after which** the new tests should be inserted, so they will be a part of the existing test suite. Place the new tests after the last test in the suite.") -===== - - -Example output: -```yaml -language: {{ .language }} -testing_framework: ... -number_of_tests: ... -relevant_line_number_to_insert_after: ... -``` - -The Response should be only a valid YAML object, without any introduction text or follow-up text. - -Answer: -```yaml -""" diff --git a/keploy/pkg/service/utgen/assets/language.toml b/keploy/pkg/service/utgen/assets/language.toml deleted file mode 100644 index c13992e..0000000 --- a/keploy/pkg/service/utgen/assets/language.toml +++ /dev/null @@ -1,439 +0,0 @@ -[bad_extensions] -default = [ - 'app', - 'bin', - 'bmp', - 'bz2', - 'class', - 'csv', - 'dat', - 'db', - 'dll', - 'dylib', - 'egg', - 'eot', - 'exe', - 'gif', - 'gitignore', - 'glif', - 'gradle', - 'gz', - 'ico', - 'jar', - 'jpeg', - 'jpg', - 'lo', - 'lock', - 'log', - 'mp3', - 'mp4', - 'nar', - 'o', - 'ogg', - 'otf', - 'p', - 'pdf', - 'png', - 'pickle', - 'pkl', - 'pyc', - 'pyd', - 'pyo', - 'rkt', - 'so', - 'ss', - 'svg', - 'tar', - 'tgz', - 'tsv', - 'ttf', - 'war', - 'webm', - 'woff', - 'woff2', - 'xz', - 'zip', - 'zst', - 'snap', - 'lockb' -] -extra = [ - 'md', - 'txt' -] - -[language_extension_map_org] -ABAP = [".abap", ] -"AGS Script" = [".ash", ] -AMPL = [".ampl", ] -ANTLR = [".g4", ] -"API Blueprint" = [".apib", ] -APL = [".apl", ".dyalog", ] -ASP = [".asp", ".asax", ".ascx", ".ashx", ".asmx", ".aspx", ".axd", ] -ATS = [".dats", ".hats", ".sats", ] -ActionScript = [".as", ] -Ada = [".adb", ".ada", ".ads", ] -Agda = [".agda", ] -Alloy = [".als", ] -ApacheConf = [".apacheconf", ".vhost", ] -AppleScript = [".applescript", ".scpt", ] -Arc = [".arc", ] -Arduino = [".ino", ] -AsciiDoc = [".asciidoc", ".adoc", ] -AspectJ = [".aj", ] -Assembly = [".asm", ".a51", ".nasm", ] -Augeas = [".aug", ] -AutoHotkey = [".ahk", ".ahkl", ] -AutoIt = [".au3", ] -Awk = [".awk", ".auk", ".gawk", ".mawk", ".nawk", ] -Batchfile = [".bat", ".cmd", ] -Befunge = [".befunge", ] -Bison = [".bison", ] -BitBake = [".bb", ] -BlitzBasic = [".decls", ] -BlitzMax = [".bmx", ] -Bluespec = [".bsv", ] -Boo = [".boo", ] -Brainfuck = [".bf", ] -Brightscript = [".brs", ] -Bro = [".bro", ] -C = [".c", ".cats", ".h", ".idc", ".w", ] -"C#" = [".cs", ".cake", ".cshtml", ".csx", ] -"C++" = [".cpp", ".c++", ".cc", ".cp", ".cxx", ".h++", ".hh", ".hpp", ".hxx", ".inl", ".ipp", ".tcc", ".tpp", ".C", ".H", ] -C-ObjDump = [".c-objdump", ] -"C2hs Haskell" = [".chs", ] -CLIPS = [".clp", ] -CMake = [".cmake", ".cmake.in", ] -COBOL = [".cob", ".cbl", ".ccp", ".cobol", ".cpy", ] -CSS = [".css", ] -CSV = [".csv", ] -"Cap'n Proto" = [".capnp", ] -CartoCSS = [".mss", ] -Ceylon = [".ceylon", ] -Chapel = [".chpl", ] -ChucK = [".ck", ] -Cirru = [".cirru", ] -Clarion = [".clw", ] -Clean = [".icl", ".dcl", ] -Click = [".click", ] -Clojure = [".clj", ".boot", ".cl2", ".cljc", ".cljs", ".cljs.hl", ".cljscm", ".cljx", ".hic", ] -CoffeeScript = [".coffee", "._coffee", ".cjsx", ".cson", ".iced", ] -ColdFusion = [".cfm", ".cfml", ] -"ColdFusion CFC" = [".cfc", ] -"Common Lisp" = [".lisp", ".asd", ".lsp", ".ny", ".podsl", ".sexp", ] -"Component Pascal" = [".cps", ] -Coq = [".coq", ] -Cpp-ObjDump = [".cppobjdump", ".c++-objdump", ".c++objdump", ".cpp-objdump", ".cxx-objdump", ] -Creole = [".creole", ] -Crystal = [".cr", ] -Csound = [".csd", ] -Cucumber = [".feature", ] -Cuda = [".cu", ".cuh", ] -Cycript = [".cy", ] -Cython = [".pyx", ".pxd", ".pxi", ] -D = [".di", ] -D-ObjDump = [".d-objdump", ] -"DIGITAL Command Language" = [".com", ] -DM = [".dm", ] -"DNS Zone" = [".zone", ".arpa", ] -"Darcs Patch" = [".darcspatch", ".dpatch", ] -Dart = [".dart", ] -Diff = [".diff", ".patch", ] -Dockerfile = [".dockerfile", "Dockerfile", ] -Dogescript = [".djs", ] -Dylan = [".dylan", ".dyl", ".intr", ".lid", ] -E = [".E", ] -ECL = [".ecl", ".eclxml", ] -Eagle = [".sch", ".brd", ] -"Ecere Projects" = [".epj", ] -Eiffel = [".e", ] -Elixir = [".ex", ".exs", ] -Elm = [".elm", ] -"Emacs Lisp" = [".el", ".emacs", ".emacs.desktop", ] -EmberScript = [".em", ".emberscript", ] -Erlang = [".erl", ".escript", ".hrl", ".xrl", ".yrl", ] -"F#" = [".fs", ".fsi", ".fsx", ] -FLUX = [".flux", ] -FORTRAN = [".f90", ".f", ".f03", ".f08", ".f77", ".f95", ".for", ".fpp", ] -Factor = [".factor", ] -Fancy = [".fy", ".fancypack", ] -Fantom = [".fan", ] -Formatted = [".eam.fs", ] -Forth = [".fth", ".4th", ".forth", ".frt", ] -FreeMarker = [".ftl", ] -G-code = [".g", ".gco", ".gcode", ] -GAMS = [".gms", ] -GAP = [".gap", ".gi", ] -GAS = [".s", ] -GDScript = [".gd", ] -GLSL = [".glsl", ".fp", ".frag", ".frg", ".fsh", ".fshader", ".geo", ".geom", ".glslv", ".gshader", ".shader", ".vert", ".vrx", ".vsh", ".vshader", ] -Genshi = [".kid", ] -"Gentoo Ebuild" = [".ebuild", ] -"Gentoo Eclass" = [".eclass", ] -"Gettext Catalog" = [".po", ".pot", ] -Glyph = [".glf", ] -Gnuplot = [".gp", ".gnu", ".gnuplot", ".plot", ".plt", ] -Go = [".go", ] -Golo = [".golo", ] -Gosu = [".gst", ".gsx", ".vark", ] -Grace = [".grace", ] -Gradle = [".gradle", ] -"Grammatical Framework" = [".gf", ] -GraphQL = [".graphql", ] -"Graphviz (DOT)" = [".dot", ".gv", ] -Groff = [".man", ".1", ".1in", ".1m", ".1x", ".2", ".3", ".3in", ".3m", ".3qt", ".3x", ".4", ".5", ".6", ".7", ".8", ".9", ".me", ".rno", ".roff", ] -Groovy = [".groovy", ".grt", ".gtpl", ".gvy", ] -"Groovy Server Pages" = [".gsp", ] -HCL = [".hcl", ".tf", ] -HLSL = [".hlsl", ".fxh", ".hlsli", ] -HTML = [".html", ".htm", ".html.hl", ".xht", ".xhtml", ] -"HTML+Django" = [".mustache", ".jinja", ] -"HTML+EEX" = [".eex", ] -"HTML+ERB" = [".erb", ".erb.deface", ] -"HTML+PHP" = [".phtml", ] -HTTP = [".http", ] -Haml = [".haml", ".haml.deface", ] -Handlebars = [".handlebars", ".hbs", ] -Harbour = [".hb", ] -Haskell = [".hs", ".hsc", ] -Haxe = [".hx", ".hxsl", ] -Hy = [".hy", ] -IDL = [".dlm", ] -"IGOR Pro" = [".ipf", ] -INI = [".ini", ".cfg", ".prefs", ".properties", ] -"IRC log" = [".irclog", ".weechatlog", ] -Idris = [".idr", ".lidr", ] -"Inform 7" = [".ni", ".i7x", ] -"Inno Setup" = [".iss", ] -Io = [".io", ] -Ioke = [".ik", ] -Isabelle = [".thy", ] -J = [".ijs", ] -JFlex = [".flex", ".jflex", ] -JSON = [".json", ".geojson", ".lock", ".topojson", ] -JSON5 = [".json5", ] -JSONLD = [".jsonld", ] -JSONiq = [".jq", ] -JSX = [".jsx", ] -Jade = [".jade", ] -Jasmin = [".j", ] -Java = [".java", ] -"Java Server Pages" = [".jsp", ] -JavaScript = [".js", "._js", ".bones", ".es6", ".jake", ".jsb", ".jscad", ".jsfl", ".jsm", ".jss", ".njs", ".pac", ".sjs", ".ssjs", ".xsjs", ".xsjslib", ] -Julia = [".jl", ] -"Jupyter Notebook" = [".ipynb", ] -KRL = [".krl", ] -KiCad = [".kicad_pcb", ] -Kit = [".kit", ] -Kotlin = [".kt", ".ktm", ".kts", ] -LFE = [".lfe", ] -LLVM = [".ll", ] -LOLCODE = [".lol", ] -LSL = [".lsl", ".lslp", ] -LabVIEW = [".lvproj", ] -Lasso = [".lasso", ".las", ".lasso8", ".lasso9", ".ldml", ] -Latte = [".latte", ] -Lean = [".lean", ".hlean", ] -Less = [".less", ] -Lex = [".lex", ] -LilyPond = [".ly", ".ily", ] -"Linker Script" = [".ld", ".lds", ] -Liquid = [".liquid", ] -"Literate Agda" = [".lagda", ] -"Literate CoffeeScript" = [".litcoffee", ] -"Literate Haskell" = [".lhs", ] -LiveScript = [".ls", "._ls", ] -Logos = [".xm", ".x", ".xi", ] -Logtalk = [".lgt", ".logtalk", ] -LookML = [".lookml", ] -Lua = [".lua", ".nse", ".pd_lua", ".rbxs", ".wlua", ] -M = [".mumps", ] -M4 = [".m4", ] -MAXScript = [".mcr", ] -MTML = [".mtml", ] -MUF = [".muf", ] -Makefile = [".mak", ".mk", ".mkfile", "Makefile", ] -Mako = [".mako", ".mao", ] -Maple = [".mpl", ] -Markdown = [".md", ".markdown", ".mkd", ".mkdn", ".mkdown", ".ron", ] -Mask = [".mask", ] -Mathematica = [".mathematica", ".cdf", ".ma", ".mt", ".nb", ".nbp", ".wl", ".wlt", ] -Matlab = [".matlab", ] -Max = [".maxpat", ".maxhelp", ".maxproj", ".mxt", ".pat", ] -MediaWiki = [".mediawiki", ".wiki", ] -Metal = [".metal", ] -MiniD = [".minid", ] -Mirah = [".druby", ".duby", ".mir", ".mirah", ] -Modelica = [".mo", ] -"Module Management System" = [".mms", ".mmk", ] -Monkey = [".monkey", ] -MoonScript = [".moon", ] -Myghty = [".myt", ] -NSIS = [".nsi", ".nsh", ] -NetLinx = [".axs", ".axi", ] -"NetLinx+ERB" = [".axs.erb", ".axi.erb", ] -NetLogo = [".nlogo", ] -Nginx = [".nginxconf", ] -Nimrod = [".nim", ".nimrod", ] -Ninja = [".ninja", ] -Nit = [".nit", ] -Nix = [".nix", ] -Nu = [".nu", ] -NumPy = [".numpy", ".numpyw", ".numsc", ] -OCaml = [".ml", ".eliom", ".eliomi", ".ml4", ".mli", ".mll", ".mly", ] -ObjDump = [".objdump", ] -"Objective-C++" = [".mm", ] -Objective-J = [".sj", ] -Octave = [".oct", ] -Omgrofl = [".omgrofl", ] -Opa = [".opa", ] -Opal = [".opal", ] -OpenCL = [".cl", ".opencl", ] -"OpenEdge ABL" = [".p", ] -OpenSCAD = [".scad", ] -Org = [".org", ] -Ox = [".ox", ".oxh", ".oxo", ] -Oxygene = [".oxygene", ] -Oz = [".oz", ] -PAWN = [".pwn", ] -PHP = [".php", ".aw", ".ctp", ".php3", ".php4", ".php5", ".phps", ".phpt", ] -"POV-Ray SDL" = [".pov", ] -Pan = [".pan", ] -Papyrus = [".psc", ] -Parrot = [".parrot", ] -"Parrot Assembly" = [".pasm", ] -"Parrot Internal Representation" = [".pir", ] -Pascal = [".pas", ".dfm", ".dpr", ".lpr", ] -Perl = [".pl", ".al", ".perl", ".ph", ".plx", ".pm", ".psgi", ".t", ] -Perl6 = [".6pl", ".6pm", ".nqp", ".p6", ".p6l", ".p6m", ".pl6", ".pm6", ] -Pickle = [".pkl", ] -PigLatin = [".pig", ] -Pike = [".pike", ".pmod", ] -Pod = [".pod", ] -PogoScript = [".pogo", ] -Pony = [".pony", ] -PostScript = [".ps", ".eps", ] -PowerShell = [".ps1", ".psd1", ".psm1", ] -Processing = [".pde", ] -Prolog = [".prolog", ".yap", ] -"Propeller Spin" = [".spin", ] -"Protocol Buffer" = [".proto", ] -"Public Key" = [".pub", ] -"Pure Data" = [".pd", ] -PureBasic = [".pb", ".pbi", ] -PureScript = [".purs", ] -Python = [".py", ".bzl", ".gyp", ".lmi", ".pyde", ".pyp", ".pyt", ".pyw", ".tac", ".wsgi", ".xpy", ] -"Python traceback" = [".pytb", ] -QML = [".qml", ".qbs", ] -QMake = [".pri", ] -R = [".r", ".rd", ".rsx", ] -RAML = [".raml", ] -RDoc = [".rdoc", ] -REALbasic = [".rbbas", ".rbfrm", ".rbmnu", ".rbres", ".rbtbar", ".rbuistate", ] -RHTML = [".rhtml", ] -RMarkdown = [".rmd", ] -Racket = [".rkt", ".rktd", ".rktl", ".scrbl", ] -"Ragel in Ruby Host" = [".rl", ] -"Raw token data" = [".raw", ] -Rebol = [".reb", ".r2", ".r3", ".rebol", ] -Red = [".red", ".reds", ] -Redcode = [".cw", ] -"Ren'Py" = [".rpy", ] -RenderScript = [".rsh", ] -RobotFramework = [".robot", ] -Rouge = [".rg", ] -Ruby = [".rb", ".builder", ".gemspec", ".god", ".irbrc", ".jbuilder", ".mspec", ".podspec", ".rabl", ".rake", ".rbuild", ".rbw", ".rbx", ".ru", ".ruby", ".thor", ".watchr", ] -Rust = [".rs", ".rs.in", ] -SAS = [".sas", ] -SCSS = [".scss", ] -SMT = [".smt2", ".smt", ] -SPARQL = [".sparql", ".rq", ] -SQF = [".sqf", ".hqf", ] -SQL = [".pls", ".pck", ".pkb", ".pks", ".plb", ".plsql", ".sql", ".cql", ".ddl", ".prc", ".tab", ".udf", ".viw", ".db2", ] -STON = [".ston", ] -SVG = [".svg", ] -Sage = [".sage", ".sagews", ] -SaltStack = [".sls", ] -Sass = [".sass", ] -Scala = [".scala", ".sbt", ] -Scaml = [".scaml", ] -Scheme = [".scm", ".sld", ".sps", ".ss", ] -Scilab = [".sci", ".sce", ] -Self = [".self", ] -Shell = [".sh", ".bash", ".bats", ".command", ".ksh", ".sh.in", ".tmux", ".tool", ".zsh", ] -ShellSession = [".sh-session", ] -Shen = [".shen", ] -Slash = [".sl", ] -Slim = [".slim", ] -Smali = [".smali", ] -Smalltalk = [".st", ] -Smarty = [".tpl", ] -Solidity = [".sol", ] -SourcePawn = [".sp", ".sma", ] -Squirrel = [".nut", ] -Stan = [".stan", ] -"Standard ML" = [".ML", ".fun", ".sig", ".sml", ] -Stata = [".do", ".ado", ".doh", ".ihlp", ".mata", ".matah", ".sthlp", ] -Stylus = [".styl", ] -SuperCollider = [".scd", ] -Swift = [".swift", ] -SystemVerilog = [".sv", ".svh", ".vh", ] -TOML = [".toml", ] -TXL = [".txl", ] -Tcl = [".tcl", ".adp", ".tm", ] -Tcsh = [".tcsh", ".csh", ] -TeX = [".tex", ".aux", ".bbx", ".bib", ".cbx", ".dtx", ".ins", ".lbx", ".ltx", ".mkii", ".mkiv", ".mkvi", ".sty", ".toc", ] -Tea = [".tea", ] -Text = [".txt", ".no", ] -Textile = [".textile", ] -Thrift = [".thrift", ] -Turing = [".tu", ] -Turtle = [".ttl", ] -Twig = [".twig", ] -TypeScript = [".ts", ".tsx", ] -"Unified Parallel C" = [".upc", ] -"Unity3D Asset" = [".anim", ".asset", ".mat", ".meta", ".prefab", ".unity", ] -Uno = [".uno", ] -UnrealScript = [".uc", ] -UrWeb = [".ur", ".urs", ] -VCL = [".vcl", ] -VHDL = [".vhdl", ".vhd", ".vhf", ".vhi", ".vho", ".vhs", ".vht", ".vhw", ] -Vala = [".vala", ".vapi", ] -Verilog = [".veo", ] -VimL = [".vim", ] -"Visual Basic" = [".vb", ".bas", ".frm", ".frx", ".vba", ".vbhtml", ".vbs", ] -Volt = [".volt", ] -Vue = [".vue", ] -"Web Ontology Language" = [".owl", ] -WebAssembly = [".wat", ] -WebIDL = [".webidl", ] -X10 = [".x10", ] -XC = [".xc", ] -XML = [".xml", ".ant", ".axml", ".ccxml", ".clixml", ".cproject", ".csl", ".csproj", ".ct", ".dita", ".ditamap", ".ditaval", ".dll.config", ".dotsettings", ".filters", ".fsproj", ".fxml", ".glade", ".grxml", ".iml", ".ivy", ".jelly", ".jsproj", ".kml", ".launch", ".mdpolicy", ".mxml", ".nproj", ".nuspec", ".odd", ".osm", ".plist", ".props", ".ps1xml", ".psc1", ".pt", ".rdf", ".rss", ".scxml", ".srdf", ".storyboard", ".stTheme", ".sublime-snippet", ".targets", ".tmCommand", ".tml", ".tmLanguage", ".tmPreferences", ".tmSnippet", ".tmTheme", ".ui", ".urdf", ".ux", ".vbproj", ".vcxproj", ".vssettings", ".vxml", ".wsdl", ".wsf", ".wxi", ".wxl", ".wxs", ".x3d", ".xacro", ".xaml", ".xib", ".xlf", ".xliff", ".xmi", ".xml.dist", ".xproj", ".xsd", ".xul", ".zcml", ] -XPages = [".xsp-config", ".xsp.metadata", ] -XProc = [".xpl", ".xproc", ] -XQuery = [".xquery", ".xq", ".xql", ".xqm", ".xqy", ] -XS = [".xs", ] -XSLT = [".xslt", ".xsl", ] -Xojo = [".xojo_code", ".xojo_menu", ".xojo_report", ".xojo_script", ".xojo_toolbar", ".xojo_window", ] -Xtend = [".xtend", ] -YAML = [".yml", ".reek", ".rviz", ".sublime-syntax", ".syntax", ".yaml", ".yaml-tmlanguage", ] -YANG = [".yang", ] -Yacc = [".y", ".yacc", ".yy", ] -Zephir = [".zep", ] -Zig = [".zig", ] -Zimpl = [".zimpl", ".zmpl", ".zpl", ] -desktop = [".desktop", ".desktop.in", ] -eC = [".ec", ".eh", ] -edn = [".edn", ] -fish = [".fish", ] -mupad = [".mu", ] -nesC = [".nc", ] -ooc = [".ooc", ] -reStructuredText = [".rst", ".rest", ".rest.txt", ".rst.txt", ] -wisp = [".wisp", ] -xBase = [".prg", ".prw", ] - -[docs_blacklist_extensions] -# Disable docs for these extensions of text files and scripts that are not programming languages of function, classes and methods -docs_blacklist = ['sql', 'txt', 'yaml', 'json', 'xml', 'md', 'rst', 'rest', 'rest.txt', 'rst.txt', 'mdpolicy', 'mdown', 'markdown', 'mdwn', 'mkd', 'mkdn', 'mkdown', 'sh'] \ No newline at end of file diff --git a/keploy/pkg/service/utgen/assets/test_generation.toml b/keploy/pkg/service/utgen/assets/test_generation.toml deleted file mode 100644 index c2b9d99..0000000 --- a/keploy/pkg/service/utgen/assets/test_generation.toml +++ /dev/null @@ -1,355 +0,0 @@ -[test_generation] -system="""\ -""" - -user="""\ -## Overview -You are a code assistant designed to accept a {{ .language }} source file and a {{ .language }} test file. -Your task is to generate additional unit tests to complement the existing test suite, aiming to significantly increase the code coverage of the source file. - -### Requirements for Creating Tests: - -- **Analyze the Provided Code:** - - Understand its purpose, inputs, outputs, and key logic or calculations. - - **Identify Return Types:** - - Determine the data types of return values for each function or method. - - Use return type information to guide the creation of relevant test cases. - -- **Refactor for Testability:** - - **Refactor the provided source code to improve testability**, including making external dependencies easily mockable, especially for asynchronous interactions. - - Ensure refactoring enhances testability without altering functionality or breaking existing behavior. - - Provide refactored code in the `refactored_source_code` field if changes are made. - - **Refactoring Techniques:** - - Use dependency injection to manage dependencies. - - Separate concerns to isolate different parts of the code. - - Implement interfaces or abstract classes to make components easily mockable. - -- **Utilize the Code Coverage Report:** - - Identify specific parts of the code not yet covered by tests. - - Focus on uncovered lines, branches, and conditions. - - **Highlight Critical Areas:** - - Prioritize testing for high-risk or critical sections of the code. - - **Coverage Metrics:** - - Aim for a minimum coverage threshold (e.g., 80%) and provide guidance on interpreting coverage metrics. - -- **Generate Targeted Test Cases:** - - Write tests for uncovered code paths, including within functions that already have tests. - - Include edge cases, error conditions, and scenarios with complex or async logic. - - **Boundary Conditions:** - - Test boundary values and limits. - - **Concurrency and Performance:** - - Include tests that assess concurrency or performance where applicable. - - **Security and Validation:** - - Write tests that validate input sanitization, authentication, and authorization where applicable. - - **Data Type Specific Tests:** - - **Validate Return Types:** - - Ensure that functions return data of the expected type. - - Create tests that check the integrity and structure of the returned data. - - **Type-Based Scenarios:** - - Generate test cases based on different data types (e.g., strings, integers, objects, arrays) to cover various input and output scenarios. - -- **Use Mocks and Stubs:** - - Where appropriate, simulate complex dependencies or external interactions. - - For asynchronous operations, use async-compatible mocking methods. - - Test for async edge cases, ensuring proper event loop handling and responses. - - **Mocking Strategies:** - - Use appropriate libraries (e.g., `unittest.mock` for Python, Mockito for Java). - - Simulate external API calls with predefined responses. - - Mock asynchronous functions using libraries compatible with async operations. - - Dont Mock Databases/Redis/Any Client - -- **Maximize Coverage:** - - Try to include as many functions and code paths as possible. - - Cover all branches, error handling paths, and edge cases. - - **Comprehensive Data Coverage:** - - Ensure that all possible data types and structures returned by functions are adequately tested. - - Include tests for both typical and atypical data types where applicable. - -- **Ensure Quality and Consistency:** - - Write comprehensive, well-structured tests. - - Follow the style and conventions of the existing test suite. - - Ensure test names are unique within the test suite. - - **Best Practices:** - - Adhere to naming conventions (e.g., `test_methodName_condition_expectedResult`). - - Add docstrings or comments within tests to explain their purpose. - - Avoid redundant tests by cross-referencing test behaviors. - - **Data Type Validation:** - - Incorporate checks to verify that returned data types match expected types. - -- **Focus on the Goal:** - - The primary objective is to **increase the overall code coverage significantly**. - - Do not include the code coverage report or any policies in your response. - -{{ if .function_under_test }} -- **Focus Function:** - - You must generate test cases specifically targeting the function named `{{ .function_under_test }}`. - - Ensure that the tests for this function cover all logic paths, edge cases, and error handling scenarios. - - **Data Type Consideration:** - - Analyze the return type of `{{ .function_under_test }}` and create tests that validate the correctness and integrity of the returned data. -{{ end }} - -{{ if .additional_command }} -- {{ .additional_command }} -{{ end }} - -## Source File -Here is the source file that you will be writing tests against, called `{{ .source_file_name }}`. Line numbers have been added for clarity and are not part of the original code. -========= -{{ .source_file_numbered | trim }} -========= - -## Test File -Here is the file that contains the existing tests, called `{{ .test_file_name }}`. -========= -{{ .test_file | trim }} -========= - -## Installed Packages -The following packages are already installed in the environment. Use these when writing tests to avoid redundant installations: - -========= -{{ .installed_packages | trim }} -========= - -{{ if .additional_includes_section | trim }} -{{ .additional_includes_section | trim }} -{{ end }} - -{{ if .failed_tests_section | trim }} -{{ .failed_tests_section | trim }} -{{ end }} - -{{if .module_name | trim}} -The module name of the go project is - -{{.module_name | trim}} - -{{end}} - -## Code Coverage -The following is the existing code coverage report. Use this to determine what tests to write, as you should only write tests that increase the overall coverage: -========= -{{ .code_coverage_report| trim }} -========= - -## Refactoring Guidelines -To improve testability without altering functionality, consider the following refactoring techniques: -- **Dependency Injection:** Pass dependencies as parameters to functions or constructors. -- **Separation of Concerns:** Isolate different parts of the code to simplify testing. -- **Use of Interfaces/Abstract Classes:** Define interfaces or abstract classes for components to facilitate mocking. - -Provide any refactored source code in the `refactored_source_code` field if changes are made. - -## Mocking Strategies -When simulating dependencies or external interactions: -- Use appropriate mocking libraries based on the language (e.g., `unittest.mock` for Python, Mockito for Java). -- Simulate external API calls with predefined responses. -- Mock asynchronous functions using libraries compatible with async operations. - -Ensure that mocks accurately represent the behavior of the actual dependencies to maintain test reliability. - -## Best Practices and Standards -- **Naming Conventions:** Follow a consistent naming pattern for tests, such as `test_methodName_condition_expectedResult`. -- **Test Documentation:** Include docstrings or comments to explain the purpose and logic of each test case. -- **Avoid Redundancy:** Ensure new tests are not duplicating existing ones by cross-referencing test behaviors. -- **Data Type Validation:** Incorporate checks to verify that returned data types match expected types. - -## Feedback Mechanism -- **Review and Iterate:** Periodically review generated tests to identify gaps or areas for improvement. -- **User Feedback Integration:** Allow users to provide feedback on the usefulness and coverage of generated tests to refine the generation logic. - -## Handling Complex Scenarios -Address more intricate testing scenarios to ensure comprehensive coverage: -- **Integration Tests:** Consider how integration tests fit into the overall testing strategy alongside unit tests. -- **Stateful Components:** Provide guidance on testing components that maintain state or have side effects. - -## YAML Response Structure -Ensure the YAML output adheres to the expected schema and is optimized for readability and maintainability: -- **Consistent Formatting:** Maintain uniform indentation and structure. -- **Modular Sections:** Organize the YAML into manageable sections. -- **Validation:** Ensure the YAML is free from syntax errors and conforms to the required schema. - -## Response -The output must be a YAML object equivalent to type $NewTests, according to the following Pydantic definitions: -===== -class SingleTest(BaseModel): - test_behavior: str = Field(description="Short description of the behavior the test covers") -{{ if or (eq .language "python") (eq .language "java") }} - test_name: str = Field(description="A short test name, in snake case, that reflects the behaviour to test") -{{ else }} - test_name: str = Field(description="A short unique test name, that should reflect the test objective") -{{ end }} - test_code: str = Field(description="A single test function, that tests the behavior described in 'test_behavior'. The test should be a written like its a part of the existing test suite, if there is one, and it can use existing helper functions, setup, or teardown code.") - new_imports_code: str = Field(description="Code for new imports that are required for the new test function, and are not already present in the test file.") - library_installation_code: str = Field(description="If new libraries are needed, specify the installation commands for each library separately.") - test_tags: str = Field(description="A single label that best describes the test, out of: ['happy path', 'edge case','other']") - -class NewTests(BaseModel): - language: str = Field(description="The programming language of the source code") - existing_test_function_signature: str = Field(description="A single line repeating a signature header of one of the existing test functions") - new_tests: List[SingleTest] = Field(min_items=1, max_items={{ .max_tests }}, description="A list of new test functions to append to the existing test suite, aiming to increase the code coverage. Each test should run as-is, without requiring any additional inputs or setup code.") - refactored_source_code: str = Field(description="The refactored source code that improves testability while retaining original functionality.") - -===== - -Example output: -```yaml -language: {{ .language }} -existing_test_function_signature: | - ... -new_tests: -- test_behavior: | - Test that the function returns the correct output for a single element list -{{- if (or (eq .language "python") (eq .language "java")) }} - test_name: | - test_single_element_list -{{- else }} - test_name: | - ... -{{- end }} - test_code: | -{{- if eq .language "python" }} - def ... -{{- else }} - ... -{{- end }} - new_imports_code: | - {{- if eq .language "python" }} - "import pytest" - "from my_module import my_function" - {{- else if eq .language "java" }} - "import org.junit.jupiter.api.Test;" - "import my.package.MyFunction;" - {{- else if eq .language "go" }} - "import "testing" " - "import "my_module"" - {{- else if eq .language "javascript" }} - "const assert = require('assert');" - "const myFunction = require('my_module').myFunction;" - {{- else if eq .language "typescript" }} - "import { assert } from 'assert';" - "import { myFunction } from 'my_module';" - {{- end }} - library_installation_code: | - {{- if eq .language "python" }} - pip install pytest - {{- else if eq .language "java" }} - # Add the following to your Maven pom.xml: - # - # org.junit.jupiter - # junit-jupiter-api - # 5.7.0 - # test - # - {{- else if eq .language "go" }} - go get github.com/my_module - {{- else if eq .language "javascript" }} - npm install assert - {{- else if eq .language "typescript" }} - npm install assert - {{- end }} - test_tags: happy path - -refactored_source_code: | - # Here is the modified source code that retains original functionality but improves testability. - ... -``` - -additions: - additional_instructions_for_tests: | - {{- if or (eq .language "javascript") (eq .language "typescript") }} - In JavaScript and TypeScript, to handle asynchronous tests, please use testing frameworks like Jest or Mocha that natively support async/await. Ensure that you: - - Import the necessary testing library (e.g., Jest). - - Use `async` functions for tests that involve asynchronous operations. - - Utilize appropriate hooks (`beforeAll`, `afterAll`, `beforeEach`, `afterEach`) for setup and teardown. - - Handle promises correctly to avoid unhandled rejections. - - Example for Jest: - ```javascript - const { someAsyncFunction } = require('./sourceFile'); - - test('should handle async operation correctly', async () => { - const result = await someAsyncFunction(); - expect(result).toBe(expectedValue); - }); - ``` - In TypeScript, ensure type definitions are correctly handled in your tests. - - {{- else if eq .language "go" }} - For Golang, ensure that your test functions follow the naming convention `TestFunctionName` and accept a `*testing.T` parameter. Import the `testing` package and utilize it to manage test cases. - - Example: - ```go - import ( - "testing" - ) - - func TestFunctionName(t *testing.T) { - result := FunctionUnderTest() - if result != expected { - t.Errorf("Expected %v, got %v", expected, result) - } - } - ``` - {{- else if eq .language "python" }} - In Python, to handle asynchronous tests and resolve issues like unclosed client sessions, please: - - Use `pytest-asyncio` for managing async tests. - - Decorate async test functions with `@pytest.mark.asyncio`. - - Import `pytest` and `pytest-asyncio` in your test files. - - Example: - ```python - import pytest - @pytest.mark.asyncio - async def test_async_function(): - result = await some_async_function() - assert result == expected_value - ``` - - {{- else if eq .language "java" }} - In Java, to handle asynchronous tests: - - Use JUnit 5 or similar frameworks that support async testing. - - Import necessary JUnit libraries. - - Utilize `CompletableFuture` or other async constructs. - - Annotate test methods with `@Test`. - - Example: - ```java - import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.*; - - public class MyAsyncTests { - - @Test - public void testAsyncFunction() throws Exception { - CompletableFuture future = someAsyncOperation(); - String result = future.get(); - assertEquals("expectedValue", result); - } - } - ``` - {{- end }} - -Use block scalar('|') to format each YAML output. - -{{- if .import_details | trim }} -Imported Structs Overview: -Below is a list of all structs and functions imported and utilized within this file. These structs are integral to the functionality, -and they should be carefully considered when creating mocks and tests. This list serves as a reference for dependencies -and external types that interact with the main code. - -{{ .import_details | trim }} - -{{- end }} - -# Configuration for handling refactored code output - -[refactor] - -# Response to send if the refactored_source_code field looks like `no refactor response` or is empty -response_if_no_refactor = "blank output don't refactor code" - - -Response (should be a valid YAML, and nothing else): -```yaml -""" diff --git a/keploy/pkg/service/utgen/coverage.go b/keploy/pkg/service/utgen/coverage.go deleted file mode 100644 index 888609d..0000000 --- a/keploy/pkg/service/utgen/coverage.go +++ /dev/null @@ -1,249 +0,0 @@ -package utgen - -import ( - "bytes" - "encoding/xml" - "fmt" - "os" - "strconv" - "strings" - "time" - - "go.keploy.io/server/v2/pkg/models" -) - -// CoverageProcessor handles the processing of coverage reports -type CoverageProcessor struct { - ReportPath string - SrcPath string - Format string -} - -// NewCoverageProcessor initializes a CoverageProcessor object -func NewCoverageProcessor(reportPath, srcpath, format string) *CoverageProcessor { - return &CoverageProcessor{ - ReportPath: reportPath, - SrcPath: srcpath, - Format: format, - } -} - -// ProcessCoverageReport verifies the report and parses it based on its type -func (cp *CoverageProcessor) ProcessCoverageReport(latestTime int64) (*models.CoverageResult, error) { - err := cp.VerifyReportUpdate(latestTime) - if err != nil { - return nil, err - } - return cp.ParseCoverageReport() -} - -// VerifyReportUpdate verifies the coverage report's existence and update time -func (cp *CoverageProcessor) VerifyReportUpdate(latestTime int64) error { - if _, err := os.Stat(cp.ReportPath); os.IsNotExist(err) { - return fmt.Errorf("fatal: coverage report \"%s\" was not generated", cp.ReportPath) - } - - fileInfo, err := os.Stat(cp.ReportPath) - if err != nil { - return err - } - fileModTimeMs := fileInfo.ModTime().UnixNano() / int64(time.Millisecond) - - if fileModTimeMs < latestTime { - time.Sleep(2 * time.Second) - fileInfo, err = os.Stat(cp.ReportPath) - if err != nil { - return err - } - fileModTimeMs = fileInfo.ModTime().UnixNano() / int64(time.Millisecond) - if fileModTimeMs < latestTime { - return fmt.Errorf("fatal: the coverage report file was not updated after the test command. file_mod_time_ms: %d, time_of_test_command: %d", fileModTimeMs, latestTime) - } - } - return nil -} - -// ParseCoverageReport parses the coverage report based on its type -func (cp *CoverageProcessor) ParseCoverageReport() (*models.CoverageResult, error) { - switch cp.Format { - case "cobertura": - return cp.ParseCoverageReportCobertura() - case "jacoco": - return cp.ParseCoverageReportJacoco() - case "lcov": - return nil, fmt.Errorf("parsing for %s coverage reports is not implemented yet", cp.Format) - default: - return nil, fmt.Errorf("unsupported coverage report type: %s", cp.Format) - } -} - -func (cp *CoverageProcessor) ParseCoverageReportCobertura() (*models.CoverageResult, error) { - - filesToCover := make([]string, 0) - // Open the XML file - xmlFile, err := os.Open(cp.ReportPath) - if err != nil { - return nil, err - } - - defer func() { - if err := xmlFile.Close(); err != nil { - return - } - }() - - // Decode the XML file into a Coverage struct - var cov models.Cobertura - if err := xml.NewDecoder(xmlFile).Decode(&cov); err != nil { - return nil, err - } - - // Find coverage for the specified file - var linesCovered, linesMissed []int - var totalLines, coveredLines int - var filteredClasses []models.Class - for _, pkg := range cov.Packages { - for _, cls := range pkg.Classes { - if cp.SrcPath == "." { - filesToCover = append(filesToCover, cls.FileName) - } - if strings.HasSuffix(cls.FileName, cp.SrcPath) { - for _, line := range cls.Lines { - totalLines++ - if line.Hits > 0 { - coveredLines++ - linesCovered = append(linesCovered, line.Number) - } else { - linesMissed = append(linesMissed, line.Number) - } - } - filteredClasses = append(filteredClasses, cls) - break - } - } - } - - var coveragePercentage float64 - if totalLines > 0 { - coveragePercentage = float64(len(linesCovered)) / float64(totalLines) - } - - // Reconstruct the coverage report with only the filtered class - filteredCov := models.Cobertura{ - Packages: []models.Package{ - { - Classes: filteredClasses, - }, - }, - } - - // Encode the filtered coverage report to XML - var filteredBuf bytes.Buffer - xmlEncoder := xml.NewEncoder(&filteredBuf) - xmlEncoder.Indent("", " ") - if err := xmlEncoder.Encode(filteredCov); err != nil { - return nil, err - } - - coverageResult := &models.CoverageResult{ - LinesCovered: linesCovered, - LinesMissed: linesMissed, - Coverage: coveragePercentage, - Files: filesToCover, - ReportContent: filteredBuf.String(), - } - - return coverageResult, nil -} - -func (cp *CoverageProcessor) ParseCoverageReportJacoco() (*models.CoverageResult, error) { - - filesToCover := make([]string, 0) - // Open the XML file - xmlFile, err := os.Open(cp.ReportPath) - if err != nil { - return nil, err - } - - defer func() { - if err := xmlFile.Close(); err != nil { - return - } - }() - - // Decode the XML file into a Coverage struct - var jacoco models.Jacoco - if err := xml.NewDecoder(xmlFile).Decode(&jacoco); err != nil { - return nil, err - } - - // Find coverage for the specified file - var linesCovered, linesMissed []int - var totalLines, coveredLines int - var filteredSourceFiles []models.JacocoSourceFile - - for _, pkg := range jacoco.Packages { - for _, src := range pkg.SourceFiles { - if cp.SrcPath == "." { - filesToCover = append(filesToCover, src.Name) - } - if strings.HasSuffix(src.Name, cp.SrcPath) { - for _, line := range src.Lines { - totalLines++ - missedInstructions, errMissed := strconv.Atoi(line.MissedInstructions) - coveredInstructions, errCovered := strconv.Atoi(line.CoveredInstructions) - if errMissed != nil || errCovered != nil { - // Handle conversion error - continue - } - if coveredInstructions > 0 { - coveredLines++ - lineNumber, err := strconv.Atoi(line.Number) - if err == nil { - linesCovered = append(linesCovered, lineNumber) - } - } else if missedInstructions > 0 { - // Use missedInstructions to check if a line has any missed instructions - lineNumber, err := strconv.Atoi(line.Number) - if err == nil { - linesMissed = append(linesMissed, lineNumber) - } - } - } - filteredSourceFiles = append(filteredSourceFiles, src) - break - } - } - } - var coveragePercentage float64 - if totalLines > 0 { - coveragePercentage = float64(len(linesCovered)) / float64(totalLines) - } - - // Reconstruct the coverage report with only the filtered class - filteredCov := models.Jacoco{ - Packages: []models.JacocoPackage{ - { - SourceFiles: filteredSourceFiles, - }, - }, - } - - // Encode the filtered coverage report to XML - var filteredBuf bytes.Buffer - xmlEncoder := xml.NewEncoder(&filteredBuf) - xmlEncoder.Indent("", " ") - if err := xmlEncoder.Encode(filteredCov); err != nil { - return nil, err - } - - coverageResult := &models.CoverageResult{ - LinesCovered: linesCovered, - LinesMissed: linesMissed, - Coverage: coveragePercentage, - Files: filesToCover, - ReportContent: filteredBuf.String(), - } - - return coverageResult, nil -} diff --git a/keploy/pkg/service/utgen/gen.go b/keploy/pkg/service/utgen/gen.go deleted file mode 100644 index a446af8..0000000 --- a/keploy/pkg/service/utgen/gen.go +++ /dev/null @@ -1,813 +0,0 @@ -// Package utgen is a service that generates unit tests for a given source code file. -package utgen - -import ( - "context" - "fmt" - "math" - "os" - "path/filepath" - "strings" - "time" - - "github.com/google/uuid" - "github.com/k0kubun/pp/v3" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/models" - "go.keploy.io/server/v2/pkg/service" - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -type Coverage struct { - Path string - Format string - Desired float64 - Current float64 - Content string -} - -type Cursor struct { - Line int - Indentation int -} - -type UnitTestGenerator struct { - srcPath string - testPath string - cmd string - dir string - cov *Coverage - lang string - cur *Cursor - failedTests []*models.FailedUT - prompt *Prompt - ai *AIClient - logger *zap.Logger - promptBuilder *PromptBuilder - injector *Injector - maxIterations int - Files []string - tel Telemetry - additionalPrompt string - totalTestCase int - testCasePassed int - testCaseFailed int - noCoverageTest int - flakiness bool -} - -var discardedTestsFilename = "discardedTests.txt" - -func NewUnitTestGenerator( - cfg *config.Config, - tel Telemetry, - auth service.Auth, - logger *zap.Logger, -) (*UnitTestGenerator, error) { - genConfig := cfg.Gen - - generator := &UnitTestGenerator{ - srcPath: genConfig.SourceFilePath, - testPath: genConfig.TestFilePath, - cmd: genConfig.TestCommand, - dir: genConfig.TestDir, - maxIterations: genConfig.MaxIterations, - logger: logger, - tel: tel, - ai: NewAIClient(genConfig, "", cfg.APIServerURL, auth, uuid.NewString(), logger), - cov: &Coverage{ - Path: genConfig.CoverageReportPath, - Format: genConfig.CoverageFormat, - Desired: genConfig.DesiredCoverage, - }, - additionalPrompt: genConfig.AdditionalPrompt, - cur: &Cursor{}, - flakiness: genConfig.Flakiness, - } - return generator, nil -} - -func (g *UnitTestGenerator) Start(ctx context.Context) error { - g.tel.GenerateUT() - - // Check for context cancellation before proceeding - select { - case <-ctx.Done(): - return fmt.Errorf("process cancelled by user") - default: - // Continue if no cancellation - } - - // To find the source files if the source path is not provided - if g.srcPath == "" { - if err := g.runCoverage(); err != nil { - return err - } - if len(g.Files) == 0 { - return fmt.Errorf("couldn't identify the source files. Please mention source file and test file using flags") - } - } - const paddingHeight = 1 - columnWidths3 := []int{29, 29, 29} - columnWidths2 := []int{40, 40} - - for i := 0; i < len(g.Files)+1; i++ { - newTestFile := false - var err error - - // Respect context cancellation in each iteration - select { - case <-ctx.Done(): - return fmt.Errorf("process cancelled by user") - default: - } - - // If the source file path is not provided, iterate over all the source files and test files - if i < len(g.Files) { - g.srcPath = g.Files[i] - g.testPath, err = getTestFilePath(g.srcPath, g.dir) - if err != nil || g.testPath == "" { - g.logger.Error("Error getting test file path", zap.Error(err)) - continue - } - isCreated, err := createTestFile(g.testPath, g.srcPath) - if err != nil { - g.logger.Error("Error creating test file", zap.Error(err)) - continue - } - newTestFile = isCreated - } - - g.logger.Info(fmt.Sprintf("Generating tests for file: %s", g.srcPath)) - isEmpty, err := utils.IsFileEmpty(g.testPath) - if err != nil { - g.logger.Error("Error checking if test file is empty", zap.Error(err)) - return err - } - if isEmpty { - newTestFile = true - } - if !newTestFile { - if err = g.runCoverage(); err != nil { - return err - } - } else { - g.cov.Current = 0 - } - - iterationCount := 1 - g.lang = GetCodeLanguage(g.srcPath) - g.promptBuilder, err = NewPromptBuilder(g.srcPath, g.testPath, g.cov.Content, "", "", g.lang, g.additionalPrompt, g.ai.FunctionUnderTest, g.logger) - g.injector = NewInjectorBuilder(g.logger, g.lang) - - if err != nil { - utils.LogError(g.logger, err, "Error creating prompt builder") - return err - } - if !isEmpty { - if err := g.setCursor(ctx); err != nil { - utils.LogError(g.logger, err, "Error during initial test suite analysis") - return err - } - } - initialCoverage := g.cov.Current - // Respect context cancellation in the inner loop - for g.cov.Current < (g.cov.Desired/100) && iterationCount <= g.maxIterations { - passedTests, noCoverageTest, failedBuild, totalTest := 0, 0, 0, 0 - select { - case <-ctx.Done(): - return fmt.Errorf("process cancelled by user") - default: - } - - pp.SetColorScheme(models.GetPassingColorScheme()) - if _, err := pp.Printf("Current Coverage: %s%% for file %s\n", math.Round(g.cov.Current*100), g.srcPath); err != nil { - utils.LogError(g.logger, err, "failed to print coverage") - } - if _, err := pp.Printf("Desired Coverage: %s%% for file %s\n", g.cov.Desired, g.srcPath); err != nil { - utils.LogError(g.logger, err, "failed to print coverage") - } - - // Check for failed tests: - failedTestRunsValue := "" - if len(g.failedTests) > 0 { - for _, failedTest := range g.failedTests { - code := failedTest.TestCode - errorMessage := failedTest.ErrorMsg - failedTestRunsValue += fmt.Sprintf("Failed Test:\n\n%s\n\n", code) - if errorMessage != "" { - failedTestRunsValue += fmt.Sprintf("Error message for test above:\n%s\n\n\n", errorMessage) - } else { - failedTestRunsValue += "\n\n" - } - } - } - - g.promptBuilder.InstalledPackages, err = g.injector.libraryInstalled() - g.promptBuilder.ImportDetails = g.injector.getModelDetails(g.srcPath) - g.promptBuilder.ModuleName, _ = g.injector.GetModuleName(g.srcPath) - if err != nil { - utils.LogError(g.logger, err, "Error getting installed packages") - } - g.prompt, err = g.promptBuilder.BuildPrompt("test_generation", failedTestRunsValue) - if err != nil { - utils.LogError(g.logger, err, "Error building prompt") - return err - } - g.failedTests = []*models.FailedUT{} - testsDetails, err := g.GenerateTests(ctx, iterationCount) - if err != nil { - utils.LogError(g.logger, err, "Error generating tests") - return err - } - if testsDetails == nil { - g.logger.Info("No tests generated") - continue - } - - var originalSrcCode string - var codeModified bool - - // Check if code refactoring is needed - if len(testsDetails.RefactoredSourceCode) != 0 { - // Read the original source code - originalSrcCode, err = readFile(g.srcPath) - if err != nil { - return fmt.Errorf("failed to read the source code: %w", err) - } - - // modify the source code for refactoring. - if !(strings.Contains(testsDetails.RefactoredSourceCode, "blank output don't refactor code") || strings.Contains(testsDetails.RefactoredSourceCode, "no refactoring")) { - if err := os.WriteFile(g.srcPath, []byte(testsDetails.RefactoredSourceCode), 0644); err != nil { - return fmt.Errorf("failed to refactor source code:%w", err) - } - codeModified = true - } - } - - var overallCovInc = false - - g.logger.Info("Validating new generated tests one by one") - g.totalTestCase += len(testsDetails.NewTests) - totalTest = len(testsDetails.NewTests) - - for _, generatedTest := range testsDetails.NewTests { - installedPackages, err := g.injector.libraryInstalled() - if err != nil { - g.logger.Warn("Error getting installed packages", zap.Error(err)) - } - select { - case <-ctx.Done(): - return fmt.Errorf("process cancelled by user") - default: - } - coverageInc, err := g.ValidateTest(generatedTest, &passedTests, &noCoverageTest, &failedBuild, installedPackages) - if err != nil { - utils.LogError(g.logger, err, "Error validating test") - - rerr := revertSourceCode(g.srcPath, originalSrcCode, codeModified) - if rerr != nil { - utils.LogError(g.logger, rerr, "Error reverting source code") - } - - return err - } - - // if any test increases the coverage, set the flag to true - overallCovInc = overallCovInc || coverageInc - } - // if any of the test couldn't increase the coverage, revert the source code - if !overallCovInc { - err := revertSourceCode(g.srcPath, originalSrcCode, codeModified) - if err != nil { - utils.LogError(g.logger, err, "Error reverting source code") - } - } else { - g.promptBuilder.Src.Code = testsDetails.RefactoredSourceCode - } - if g.cov.Current < (g.cov.Desired/100) && g.cov.Current > 0 { - if err := g.runCoverage(); err != nil { - utils.LogError(g.logger, err, "Error running coverage") - return err - } - } - - if len(g.failedTests) > 0 { - err := g.saveFailedTestCasesToFile() - if err != nil { - utils.LogError(g.logger, err, "Error adding failed test cases to file") - } - } - - fmt.Printf("\n<=========================================>\n") - fmt.Printf(("Tests generated in Session") + "\n") - fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") - fmt.Printf("| %s | %s | %s |\n", - centerAlignText("Total Test Cases", 29), - centerAlignText("Test Cases Passed", 29), - centerAlignText("Test Cases Failed", 29)) - fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") - fmt.Print(addHeightPadding(paddingHeight, 3, columnWidths3)) - fmt.Printf("| \033[33m%s\033[0m | \033[32m%s\033[0m | \033[33m%s\033[0m |\n", - centerAlignText(fmt.Sprintf("%d", totalTest), 29), - centerAlignText(fmt.Sprintf("%d", passedTests), 29), - centerAlignText(fmt.Sprintf("%d", failedBuild+noCoverageTest), 29)) - fmt.Print(addHeightPadding(paddingHeight, 3, columnWidths3)) - fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") - fmt.Printf(("Discarded tests in session") + "\n") - fmt.Printf("+------------------------------------------+------------------------------------------+\n") - fmt.Printf("| %s | %s |\n", - centerAlignText("Build failures", 40), - centerAlignText("No Coverage output", 40)) - fmt.Printf("+------------------------------------------+------------------------------------------+\n") - fmt.Print(addHeightPadding(paddingHeight, 2, columnWidths2)) - fmt.Printf("| \033[35m%s\033[0m | \033[92m%s\033[0m |\n", - centerAlignText(fmt.Sprintf("%d", failedBuild), 40), - centerAlignText(fmt.Sprintf("%d", noCoverageTest), 40)) - fmt.Print(addHeightPadding(paddingHeight, 2, columnWidths2)) - fmt.Printf("+------------------------------------------+------------------------------------------+\n") - fmt.Printf("<=========================================>\n") - err = g.ai.SendCoverageUpdate(ctx, g.ai.SessionID, initialCoverage, g.cov.Current, iterationCount) - if err != nil { - utils.LogError(g.logger, err, "Error sending coverage update") - } - - iterationCount++ - } - - if g.cov.Current == 0 && newTestFile { - err := os.Remove(g.testPath) - if err != nil { - g.logger.Error("Error removing test file", zap.Error(err)) - } - } - - pp.SetColorScheme(models.GetPassingColorScheme()) - if g.cov.Current >= (g.cov.Desired / 100) { - if _, err := pp.Printf("For File %s Reached above target coverage of %s%% (Current Coverage: %s%%) in %s iterations.\n", g.srcPath, g.cov.Desired, math.Round(g.cov.Current*100), iterationCount); err != nil { - utils.LogError(g.logger, err, "failed to print coverage") - } - } else if iterationCount > g.maxIterations { - if _, err := pp.Printf("For File %s Reached maximum iteration limit without achieving desired coverage. Current Coverage: %s%%\n", g.srcPath, math.Round(g.cov.Current*100)); err != nil { - utils.LogError(g.logger, err, "failed to print coverage") - } - } - } - fmt.Printf("\n<=========================================>\n") - fmt.Printf(("COMPLETE TEST GENERATE SUMMARY") + "\n") - fmt.Printf(("Total Test Summary") + "\n") - - fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") - fmt.Printf("| %s | %s | %s |\n", - centerAlignText("Total Test Cases", 29), - centerAlignText("Test Cases Passed", 29), - centerAlignText("Test Cases Failed", 29)) - - fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") - fmt.Print(addHeightPadding(paddingHeight, 3, columnWidths3)) - fmt.Printf("| \033[33m%s\033[0m | \033[32m%s\033[0m | \033[33m%s\033[0m |\n", - centerAlignText(fmt.Sprintf("%d", g.totalTestCase), 29), - centerAlignText(fmt.Sprintf("%d", g.testCasePassed), 29), - centerAlignText(fmt.Sprintf("%d", g.testCaseFailed+g.noCoverageTest), 29)) - fmt.Print(addHeightPadding(paddingHeight, 3, columnWidths3)) - fmt.Printf("+-------------------------------+-------------------------------+-------------------------------+\n") - - fmt.Printf(("Discarded Cases Summary") + "\n") - fmt.Printf("+------------------------------------------+------------------------------------------+\n") - fmt.Printf("| %s | %s |\n", - centerAlignText("Build failures", 40), - centerAlignText("No Coverage output", 40)) - - fmt.Printf("+------------------------------------------+------------------------------------------+\n") - fmt.Print(addHeightPadding(paddingHeight, 2, columnWidths2)) - fmt.Printf("| \033[35m%s\033[0m | \033[92m%s\033[0m |\n", - centerAlignText(fmt.Sprintf("%d", g.testCaseFailed), 40), - centerAlignText(fmt.Sprintf("%d", g.noCoverageTest), 40)) - fmt.Print(addHeightPadding(paddingHeight, 2, columnWidths2)) - fmt.Printf("+------------------------------------------+------------------------------------------+\n") - - fmt.Printf("<=========================================>\n") - return nil -} - -func revertSourceCode(srcPath, originalSrcCode string, codeModified bool) error { - if !codeModified { - return nil - } - - if err := os.WriteFile(srcPath, []byte(originalSrcCode), 0644); err != nil { - return fmt.Errorf("failed to revert source code to the original state:%w", err) - } - return nil -} - -func centerAlignText(text string, width int) string { - text = strings.Trim(text, "\"") - - textLen := len(text) - if textLen >= width { - return text - } - - leftPadding := (width - textLen) / 2 - rightPadding := width - textLen - leftPadding - - return fmt.Sprintf("%s%s%s", strings.Repeat(" ", leftPadding), text, strings.Repeat(" ", rightPadding)) -} - -func addHeightPadding(rows int, columns int, columnWidths []int) string { - padding := "" - for i := 0; i < rows; i++ { - for j := 0; j < columns; j++ { - if j == columns-1 { - padding += fmt.Sprintf("| %-*s |\n", columnWidths[j], "") - } else { - padding += fmt.Sprintf("| %-*s ", columnWidths[j], "") - } - } - } - return padding -} - -func statusUpdater(stop <-chan bool) { - messages := []string{ - "Running tests... Please wait.", - "Still running tests... Hang tight!", - "Tests are still executing... Almost there!", - } - i := 0 - for { - select { - case <-stop: - fmt.Printf("\r\033[K") - return - default: - fmt.Printf("\r\033[K%s", messages[i%len(messages)]) - time.Sleep(5 * time.Second) - i++ - } - } -} - -func (g *UnitTestGenerator) runCoverage() error { - // Perform an initial build/test command to generate coverage report and get a baseline - if g.srcPath != "" { - g.logger.Info(fmt.Sprintf("Running test command to generate coverage report: '%s'", g.cmd)) - } - - stopStatus := make(chan bool) - go statusUpdater(stopStatus) - - startTime := time.Now() - - _, _, exitCode, lastUpdatedTime, err := RunCommand(g.cmd, g.dir, g.logger) - duration := time.Since(startTime) - stopStatus <- true - g.logger.Info(fmt.Sprintf("Test command completed in %v", formatDuration(duration))) - - if err != nil { - g.logger.Warn("Test command failed. Ensure no tests are failing, and rerun the command.") - return fmt.Errorf("error running test command: %s", g.cmd) - } - if exitCode != 0 { - utils.LogError(g.logger, err, "Error running test command") - } - coverageProcessor := NewCoverageProcessor(g.cov.Path, getFilename(g.srcPath), g.cov.Format) - coverageResult, err := coverageProcessor.ProcessCoverageReport(lastUpdatedTime) - if err != nil { - utils.LogError(g.logger, err, "Error in coverage processing") - return fmt.Errorf("error in coverage processing: %w", err) - } - g.cov.Current = coverageResult.Coverage - g.cov.Content = coverageResult.ReportContent - if g.srcPath == "" { - g.Files = coverageResult.Files - } - return nil -} - -func (g *UnitTestGenerator) GenerateTests(ctx context.Context, iterationCount int) (*models.UTDetails, error) { - fmt.Println("Generating Tests...") - - select { - case <-ctx.Done(): - err := ctx.Err() - return &models.UTDetails{}, err - default: - } - - requestPurpose := TestForFile - if len(g.ai.FunctionUnderTest) > 0 { - requestPurpose = TestForFunction - } - - updatedTestContent, err := readFile(g.testPath) - if err != nil { - g.logger.Error("Error reading updated test file content", zap.Error(err)) - return &models.UTDetails{}, err - } - g.promptBuilder.Test.Code = updatedTestContent - g.promptBuilder.CovReportContent = g.cov.Content - g.prompt, err = g.promptBuilder.BuildPrompt("test_generation", "") - if err != nil { - utils.LogError(g.logger, err, "Error building prompt") - return &models.UTDetails{}, err - } - - aiRequest := AIRequest{ - MaxTokens: 4096, - Prompt: *g.prompt, - SessionID: g.ai.SessionID, - Iteration: iterationCount, - RequestPurpose: requestPurpose, - } - - response, err := g.ai.Call(ctx, CompletionParams{}, aiRequest, false) - if err != nil { - return &models.UTDetails{}, err - } - - testsDetails, err := unmarshalYamlTestDetails(response) - if err != nil { - utils.LogError(g.logger, err, "Error unmarshalling test details") - return &models.UTDetails{}, err - } - - return testsDetails, nil -} - -func (g *UnitTestGenerator) setCursor(ctx context.Context) error { - fmt.Println("Getting indentation for new Tests...") - indentation, err := g.getIndentation(ctx) - if err != nil { - return fmt.Errorf("failed to analyze test headers indentation: %w", err) - } - fmt.Println("Getting Line number for new Tests...") - line, err := g.getLine(ctx) - if err != nil { - return fmt.Errorf("failed to analyze relevant line number to insert new tests: %w", err) - } - g.cur.Indentation = indentation - g.cur.Line = line - return nil -} - -func (g *UnitTestGenerator) getIndentation(ctx context.Context) (int, error) { - indentation := -1 - allowedAttempts := 3 - counterAttempts := 0 - for indentation == -1 && counterAttempts < allowedAttempts { - prompt, err := g.promptBuilder.BuildPrompt("indentation", "") - if err != nil { - return 0, fmt.Errorf("error building prompt: %w", err) - } - - aiRequest := AIRequest{ - MaxTokens: 4096, - Prompt: *prompt, - SessionID: g.ai.SessionID, - } - response, err := g.ai.Call(ctx, CompletionParams{}, aiRequest, false) - if err != nil { - utils.LogError(g.logger, err, "Error calling AI model") - return 0, err - } - testsDetails, err := unmarshalYamlTestHeaders(response) - if err != nil { - utils.LogError(g.logger, err, "Error unmarshalling test headers") - return 0, err - } - - indentation, err = convertToInt(testsDetails.Indentation) - if err != nil { - return 0, fmt.Errorf("error converting test_headers_indentation to int: %w", err) - } - counterAttempts++ - } - if indentation == -1 { - return 0, fmt.Errorf("failed to analyze the test headers indentation") - } - return indentation, nil -} - -func (g *UnitTestGenerator) getLine(ctx context.Context) (int, error) { - line := -1 - allowedAttempts := 3 - counterAttempts := 0 - for line == -1 && counterAttempts < allowedAttempts { - prompt, err := g.promptBuilder.BuildPrompt("insert_line", "") - if err != nil { - return 0, fmt.Errorf("error building prompt: %w", err) - } - - aiRequest := AIRequest{ - MaxTokens: 4096, - Prompt: *prompt, - SessionID: g.ai.SessionID, - } - response, err := g.ai.Call(ctx, CompletionParams{}, aiRequest, false) - if err != nil { - utils.LogError(g.logger, err, "Error calling AI model") - return 0, err - } - testsDetails, err := unmarshalYamlTestLine(response) - if err != nil { - utils.LogError(g.logger, err, "Error unmarshalling test line") - return 0, err - } - - line, err = convertToInt(testsDetails.Line) - if err != nil { - return 0, fmt.Errorf("error converting relevant_line_number_to_insert_after to int: %w", err) - } - counterAttempts++ - } - if line == -1 { - return 0, fmt.Errorf("failed to analyze the relevant line number to insert new tests") - } - return line, nil -} - -func (g *UnitTestGenerator) ValidateTest( - generatedTest models.UT, - passedTests, - noCoverageTest, - failedBuild *int, - installedPackages []string, -) (bool, error) { - testCode := strings.TrimSpace(generatedTest.TestCode) - InsertAfter := g.cur.Line - Indent := g.cur.Indentation - testCodeIndented := testCode - if Indent != 0 { - initialIndent := len(testCode) - len(strings.TrimLeft(testCode, " ")) - deltaIndent := Indent - initialIndent - if deltaIndent > 0 { - lines := strings.Split(testCode, "\n") - for i, line := range lines { - lines[i] = strings.Repeat(" ", deltaIndent) + line - } - testCodeIndented = strings.Join(lines, "\n") - } - } - testCodeIndented = "\n" + g.injector.addCommentToTest(strings.TrimSpace(testCodeIndented)) + "\n" - originalContent, err := readFile(g.testPath) - if err != nil { - return false, fmt.Errorf("failed to read test file: %w", err) - } - originalContentLines := strings.Split(originalContent, "\n") - testCodeLines := strings.Split(testCodeIndented, "\n") - if InsertAfter > len(originalContentLines) { - InsertAfter = len(originalContentLines) - } - processedTestLines := append(originalContentLines[:InsertAfter], testCodeLines...) - processedTestLines = append(processedTestLines, originalContentLines[InsertAfter:]...) - processedTest := strings.Join(processedTestLines, "\n") - if err := os.WriteFile(g.testPath, []byte(processedTest), 0644); err != nil { - return false, fmt.Errorf("failed to write test file: %w", err) - } - importLen, err := g.injector.updateImports(g.testPath, generatedTest.NewImportsCode) - if err != nil { - g.logger.Warn("Error updating imports", zap.Error(err)) - } - newInstalledPackages, err := g.injector.installLibraries(generatedTest.LibraryInstallationCode, installedPackages) - if err != nil { - g.logger.Debug("Error installing libraries", zap.Error(err)) - } - - g.logger.Info(fmt.Sprintf("Running Test with command: '%s'", g.cmd)) - stdout, stderr, exitCode, timeOfTestCommand, _ := RunCommand(g.cmd, g.dir, g.logger) - if exitCode != 0 { - g.logger.Info("Test Run Failed") - if err := os.WriteFile(g.testPath, []byte(originalContent), 0644); err != nil { - return false, fmt.Errorf("failed to revert test file: %w", err) - } - err = g.injector.uninstallLibraries(newInstalledPackages) - if err != nil { - g.logger.Warn("Error uninstalling libraries", zap.Error(err)) - } - // Mark test as failed - g.failedTests = append(g.failedTests, &models.FailedUT{ - TestCode: generatedTest.TestCode, - ErrorMsg: extractErrorMessage(stdout, stderr, g.lang), - NewImportsCode: generatedTest.NewImportsCode, - LibraryInstallationCode: generatedTest.LibraryInstallationCode, - }) - g.testCaseFailed++ - *failedBuild++ - return false, nil - } - - coverageProcessor := NewCoverageProcessor(g.cov.Path, getFilename(g.srcPath), g.cov.Format) - coverageResult, err := coverageProcessor.ProcessCoverageReport(timeOfTestCommand) - if err != nil { - utils.LogError(g.logger, err, "Error in coverage processing") - return false, fmt.Errorf("error in coverage processing: %w", err) - } - initialCoverage := g.cov.Current - g.cov.Current = coverageResult.Coverage - g.cov.Content = coverageResult.ReportContent - if g.srcPath == "" { - g.Files = coverageResult.Files - } - - coverageIncreased := g.cov.Current > initialCoverage - if !coverageIncreased { - g.logger.Info("No coverage increase detected after initial test run.") - // Revert test file to original content - if err := os.WriteFile(g.testPath, []byte(originalContent), 0644); err != nil { - return false, fmt.Errorf("failed to revert test file: %w", err) - } - // Uninstall any installed libraries - err = g.injector.uninstallLibraries(newInstalledPackages) - if err != nil { - g.logger.Warn("Error uninstalling libraries", zap.Error(err)) - } - // Mark test as ineffective - g.noCoverageTest++ - *noCoverageTest++ - return false, nil - } - - if g.flakiness { - // Run the Test Five Times to Check for Flakiness - g.logger.Info("Coverage increased. Running additional test iterations to check for flakiness.") - for i := 0; i < 5; i++ { - g.logger.Info(fmt.Sprintf("Flakiness Check - Iteration no: %d", i+1)) - stdout, stderr, exitCode, _, _ := RunCommand(g.cmd, g.dir, g.logger) - if exitCode != 0 { - g.logger.Info(fmt.Sprintf("Flaky test detected on iteration %d: %s", i+1, stderr)) - // Revert test file to original content - if err := os.WriteFile(g.testPath, []byte(originalContent), 0644); err != nil { - return false, fmt.Errorf("failed to revert test file: %w", err) - } - // Uninstall any installed libraries - err = g.injector.uninstallLibraries(newInstalledPackages) - if err != nil { - g.logger.Warn("Error uninstalling libraries", zap.Error(err)) - } - g.failedTests = append(g.failedTests, &models.FailedUT{ - TestCode: generatedTest.TestCode, - ErrorMsg: extractErrorMessage(stdout, stderr, g.lang), - NewImportsCode: generatedTest.NewImportsCode, - LibraryInstallationCode: generatedTest.LibraryInstallationCode, - }) - g.testCaseFailed++ - *failedBuild++ - return false, nil - } - } - } - g.testCasePassed++ - *passedTests++ - g.cov.Current = coverageResult.Coverage - g.logger.Info("Generated test passed and increased coverage") - g.cur.Line = g.cur.Line + len(testCodeLines) + importLen - return true, nil -} - -func (g *UnitTestGenerator) saveFailedTestCasesToFile() error { - dir, err := os.Getwd() - if err != nil { - return fmt.Errorf("error getting current directory: %w", err) - } - - newFilePath := filepath.Join(dir, discardedTestsFilename) - - fileHandle, err := os.OpenFile(newFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return fmt.Errorf("error opening discarded tests file: %w", err) - } - - defer func() { - err := fileHandle.Close() - if err != nil { - g.logger.Error("Error closing file handle", zap.Error(err)) - } - }() - - var builder strings.Builder - - // Writing Test Cases To File - for _, failedTest := range g.failedTests { - builder.WriteString("\n" + strings.Repeat("-", 20) + "Test Case" + strings.Repeat("-", 20) + "\n") - if len(failedTest.NewImportsCode) > 0 { - builder.WriteString(fmt.Sprintf("Import Statements:\n%s\n", failedTest.NewImportsCode)) - } - if len(failedTest.LibraryInstallationCode) > 0 { - builder.WriteString(fmt.Sprintf("Required Library Installation\n%s\n", failedTest.LibraryInstallationCode)) - } - builder.WriteString(fmt.Sprintf("Test Implementation:\n%s\n\n", failedTest.TestCode)) - if len(failedTest.ErrorMsg) > 0 { - builder.WriteString(fmt.Sprintf("Error Message:\n%s\n", failedTest.ErrorMsg)) - } - builder.WriteString(strings.Repeat("-", 49) + "\n") - } - - _, err = fileHandle.WriteString(fmt.Sprintf("%s\n", builder.String())) - if err != nil { - return fmt.Errorf("error writing to discarded tests file: %w", err) - } - return nil -} diff --git a/keploy/pkg/service/utgen/injector.go b/keploy/pkg/service/utgen/injector.go deleted file mode 100644 index 4019ebd..0000000 --- a/keploy/pkg/service/utgen/injector.go +++ /dev/null @@ -1,830 +0,0 @@ -package utgen - -import ( - "bufio" - "bytes" - "fmt" - "go/ast" - "go/parser" - "go/token" - "io/fs" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - - "go.uber.org/zap" -) - -type Injector struct { - logger *zap.Logger - language string -} - -func NewInjectorBuilder(logger *zap.Logger, language string) *Injector { - injectBuilder := &Injector{ - logger: logger, - language: language, - } - - return injectBuilder -} - -func (i *Injector) libraryInstalled() ([]string, error) { - switch strings.ToLower(i.language) { - case "go": - out, err := exec.Command("go", "list", "-m", "all").Output() - if err != nil { - return nil, fmt.Errorf("failed to get Go dependencies: %w", err) - } - return i.extractGoPackageNames(out), nil - - case "java": - out, err := exec.Command("mvn", "dependency:list", "-DincludeScope=compile", "-Dstyle.color=never", "-B").Output() - if err != nil { - return nil, fmt.Errorf("failed to get Java dependencies: %w", err) - } - return i.extractJavaDependencies(out), nil - - case "python": - out, err := exec.Command("pip", "freeze").Output() - if err != nil { - i.logger.Info("Error getting Python dependencies with `pip` command, trying `pip3` command") - out, err = exec.Command("pip3", "freeze").Output() - if err != nil { - return nil, fmt.Errorf("failed to get Python dependencies: %w", err) - } - } - return i.extractPackageNames(out), nil - - case "typescript", "javascript": - cmd := exec.Command("sh", "-c", "npm list --depth=0 --parseable | sed 's|.*/||'") - out, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("failed to get JavaScript/TypeScript dependencies: %w", err) - } - return extractString(out), nil - - default: - return nil, fmt.Errorf("unsupported language: %s", i.language) - } -} - -func (i *Injector) extractGoPackageNames(output []byte) []string { - lines := strings.Split(string(output), "\n") - var packages []string - for _, line := range lines { - if len(line) > 0 { - parts := strings.Split(line, " ") - if len(parts) > 0 { - packages = append(packages, parts[0]) - } - } - } - return packages -} - -func (i *Injector) extractPackageNames(output []byte) []string { - lines := strings.Split(string(output), "\n") - var packages []string - for _, line := range lines { - parts := strings.Split(line, "==") - if len(parts) > 0 { - packages = append(packages, parts[0]) - } - } - return packages -} - -func (i *Injector) installLibraries(libraryCommands string, installedPackages []string) ([]string, error) { - var newInstalledPackages []string - libraryCommands = strings.TrimSpace(libraryCommands) - if libraryCommands == "" || libraryCommands == "\"\"" { - return newInstalledPackages, nil - } - - commands := strings.Split(libraryCommands, "\n") - for _, command := range commands { - command = strings.ReplaceAll(command, "-", "") - packageName := i.extractPackageName(command) - if isStringInarray(installedPackages, packageName) { - continue - } - _, _, exitCode, _, err := RunCommand(command, "", i.logger) - if exitCode != 0 || err != nil { - return newInstalledPackages, fmt.Errorf("failed to install library: %s", command) - } - installedPackages = append(installedPackages, packageName) - newInstalledPackages = append(newInstalledPackages, packageName) - } - return newInstalledPackages, nil -} - -func (i *Injector) extractPackageName(command string) string { - fields := strings.Fields(command) - if len(fields) < 3 { - return "" - } - return fields[2] -} - -func (i *Injector) uninstallLibraries(installedPackages []string) error { - for _, command := range installedPackages { - i.logger.Info(fmt.Sprintf("Uninstalling library: %s", command)) - - var uninstallCommand string - switch strings.ToLower(i.language) { - case "go": - uninstallCommand = fmt.Sprintf("go mod edit -droprequire %s && go mod tidy", command) - case "python": - uninstallCommand = fmt.Sprintf("pip uninstall -y %s", command) - case "javascript": - uninstallCommand = fmt.Sprintf("npm uninstall %s", command) - case "java": - uninstallCommand = fmt.Sprintf("mvn dependency:purge-local-repository -DreResolve=false -Dinclude=%s", command) - } - if uninstallCommand != "" { - i.logger.Info(fmt.Sprintf("Uninstalling library with command: %s", uninstallCommand)) - _, _, exitCode, _, err := RunCommand(uninstallCommand, "", i.logger) - if exitCode != 0 || err != nil { - i.logger.Warn(fmt.Sprintf("Failed to uninstall library: %s", uninstallCommand), zap.Error(err)) - } - } - } - return nil -} - -func (i *Injector) updateJavaScriptImports(importedContent string, newImports []string) (string, int, error) { - importRegex := regexp.MustCompile(`(?m)^\s*(import\s+.*?from\s+['"].*?['"];?|const\s+.*?=\s+require\(['"].*?['"]\);?)`) - existingImportsSet := make(map[string]bool) - sanitisedImports := []string{} - existingImports := importRegex.FindAllString(importedContent, -1) - for _, imp := range existingImports { - imp = strings.TrimSpace(imp) - cleanedImport := strings.ReplaceAll(imp, " ", "") - if cleanedImport != "" && !existingImportsSet[cleanedImport] { - existingImportsSet[cleanedImport] = true - sanitisedImports = append(sanitisedImports, imp) - } - } - - for _, imp := range newImports { - imp = strings.Trim(imp, `"- `) - cleanedImport := strings.ReplaceAll(imp, " ", "") - if importRegex.MatchString(imp) && !existingImportsSet[cleanedImport] { - existingImportsSet[cleanedImport] = true - sanitisedImports = append(sanitisedImports, imp) - } - } - updatedImports := strings.Join(sanitisedImports, "\n") + "\n\n" - - contentWithoutImports := importRegex.ReplaceAllString(importedContent, "") - contentWithoutImports = strings.TrimLeft(contentWithoutImports, "\n") - - updatedContent := updatedImports + "\n" + contentWithoutImports - - originalLines := strings.Split(importedContent, "\n") - updatedLines := strings.Split(updatedContent, "\n") - importLength := len(updatedLines) - len(originalLines) - - if importLength < 0 { - importLength = 0 - } - - return updatedContent, importLength, nil -} - -func (i *Injector) updateImports(filePath string, imports string) (int, error) { - importLines := strings.Split(imports, "\n") - var newImports []string - - for _, imp := range importLines { - trimmedImp := strings.TrimSpace(imp) - if strings.Contains(trimmedImp, "No new imports") || strings.Contains(trimmedImp, "None") { - continue - } - newImports = append(newImports, trimmedImp) - } - contentBytes, err := os.ReadFile(filePath) - if err != nil { - return 0, err - } - content := string(contentBytes) - - var updatedContent string - var importLength int - switch strings.ToLower(i.language) { - case "go": - updatedContent, importLength, err = i.updateGoImports(content, newImports) - case "java": - updatedContent, importLength, err = i.updateJavaImports(content, newImports) - case "python": - updatedContent, err = i.updatePythonImports(content, newImports) - case "typescript": - updatedContent, importLength, err = i.updateTypeScriptImports(content, newImports) - case "javascript": - updatedContent, importLength, err = i.updateJavaScriptImports(content, newImports) - default: - return 0, fmt.Errorf("unsupported language: %s", i.language) - } - if err != nil { - return 0, err - } - err = os.WriteFile(filePath, []byte(updatedContent), fs.ModePerm) - - if err != nil { - return 0, err - } - - return importLength, nil -} - -func (i *Injector) updateGoImports(codeBlock string, newImports []string) (string, int, error) { - importRegex := regexp.MustCompile(`(?ms)import\s*(\([\s\S]*?\)|"[^"]+")`) - existingImportsSet := make(map[string]bool) - matches := importRegex.FindStringSubmatch(codeBlock) - if matches != nil { - importBlock := matches[0] - importLines := strings.Split(importBlock, "\n") - allImports := []string{} - existingImports := i.extractGoImports(importLines, true) - for _, imp := range existingImports { - trimmedImp := strings.TrimSpace(imp) - if trimmedImp != "" { - existingImportsSet[trimmedImp] = true - } - allImports = append(allImports, imp) - } - newImports = i.extractGoImports(newImports, false) - for _, importStatement := range newImports { - importStatement = strings.TrimSpace(importStatement) - if !existingImportsSet[importStatement] { - existingImportsSet[importStatement] = true - allImports = append(allImports, importStatement) - } - } - importBlockNew := i.createGoImportBlock(allImports) - updatedContent := importRegex.ReplaceAllString(codeBlock, importBlockNew) - return updatedContent, len(strings.Split(importBlockNew, "\n")) - len(importLines), nil - } - packageRegex := regexp.MustCompile(`package\s+\w+`) - - pkgMatch := packageRegex.FindStringIndex(codeBlock) - if pkgMatch == nil { - return "", 0, fmt.Errorf("could not find package declaration") - } - newImports = i.extractGoImports(newImports, false) - importBlock := i.createGoImportBlock(newImports) - insertPos := pkgMatch[1] - updatedContent := codeBlock[:insertPos] + "\n\n" + importBlock + "\n" + codeBlock[insertPos:] - return updatedContent, len(strings.Split(importBlock, "\n")) + 1, nil - -} - -func (i *Injector) extractGoImports(importLines []string, ignoreSpace bool) []string { - imports := []string{} - for _, line := range importLines { - line = strings.TrimSpace(line) - if line == "import (" || line == ")" { - continue - } - if line == "" { - if ignoreSpace { - imports = append(imports, "") - } - continue - } - line = strings.TrimPrefix(line, "import ") - line = strings.Trim(line, `"`) - imports = append(imports, line) - } - return imports -} - -func (i *Injector) createGoImportBlock(imports []string) string { - importBlock := "import (\n" - for _, importLine := range imports { - importLine = strings.TrimSpace(importLine) - if importLine == "" { - importBlock += "\n" - continue - } - importLine = strings.Trim(importLine, `"`) - importBlock += fmt.Sprintf(` "%s"`+"\n", importLine) - } - importBlock += ")" - return importBlock -} - -func (i *Injector) updateJavaImports(codeContent string, newImports []string) (string, int, error) { - importRegex := regexp.MustCompile(`(?m)^import\s+.*?;`) - existingImportsSet := make(map[string]bool) - existingImportMatches := importRegex.FindAllStringIndex(codeContent, -1) - - for _, match := range existingImportMatches { - imp := codeContent[match[0]:match[1]] - existingImportsSet[imp] = true - } - - importsToAdd := []string{} - for _, importStatement := range newImports { - importStatement = strings.ReplaceAll(importStatement, "-", "") - importStatement = strings.TrimSpace(importStatement) - importStatement = strings.Trim(importStatement, "\"") - if importRegex.MatchString(importStatement) && !existingImportsSet[importStatement] { - existingImportsSet[importStatement] = true - importsToAdd = append(importsToAdd, importStatement) - } - } - if len(importsToAdd) > 0 { - insertPos := 0 - if len(existingImportMatches) > 0 { - lastImportMatch := existingImportMatches[len(existingImportMatches)-1] - insertPos = lastImportMatch[1] // position after last existing import - } else { - packageRegex := regexp.MustCompile(`(?m)^package\s+.*?;`) - pkgMatch := packageRegex.FindStringIndex(codeContent) - if pkgMatch != nil { - insertPos = pkgMatch[1] - } else { - insertPos = 0 - } - } - - importedContent := "\n" + strings.Join(importsToAdd, "\n") + "\n" - - updatedContent := codeContent[:insertPos] + importedContent + codeContent[insertPos:] - - return updatedContent, len(importsToAdd), nil - } - return codeContent, 0, nil - -} - -func (i *Injector) updatePythonImports(content string, newImports []string) (string, error) { - scanner := bufio.NewScanner(strings.NewReader(content)) - existingImportsMap := make(map[string]map[string]bool) - codeLines := []string{} - importLines := []string{} - - ignoredPrefixes := "# checking coverage for file - do not remove" - - for scanner.Scan() { - line := scanner.Text() - trimmedLine := strings.TrimSpace(line) - - if trimmedLine == "" { - continue - } - shouldIgnore := (strings.HasPrefix(trimmedLine, "import ") || strings.HasPrefix(trimmedLine, "from ")) && strings.Contains(trimmedLine, ignoredPrefixes) - if shouldIgnore { - parts := strings.Split(trimmedLine, "#") - coreImport := strings.TrimSpace(parts[0]) - - if strings.HasPrefix(coreImport, "from ") { - fields := strings.Fields(coreImport) - moduleName := fields[1] - importPart := coreImport[strings.Index(coreImport, "import")+len("import "):] - importedItems := strings.Split(importPart, ",") - - if _, exists := existingImportsMap[moduleName]; !exists { - existingImportsMap[moduleName] = make(map[string]bool) - } - for _, item := range importedItems { - cleanedItem := strings.TrimSpace(item) - if cleanedItem != "" { - existingImportsMap[moduleName][cleanedItem] = true - } - } - } - codeLines = append(codeLines, line) - continue - } - - if strings.HasPrefix(trimmedLine, "import ") || strings.HasPrefix(trimmedLine, "from ") { - codeLines = append(codeLines, line) - } else { - codeLines = append(codeLines, line) - } - } - - for _, imp := range newImports { - imp = strings.TrimSpace(imp) - if imp == "\"\"" || len(imp) == 0 { - continue - } - if strings.HasPrefix(imp, "from ") { - fields := strings.Fields(imp) - moduleName := fields[1] - importIndex := -1 - for i, field := range fields { - if field == "import" { - importIndex = i - break - } - } - if importIndex == -1 { - continue - } - importPart := strings.Join(fields[importIndex+1:], " ") - importedItems := strings.Split(importPart, ",") - if _, exists := existingImportsMap[moduleName]; !exists { - existingImportsMap[moduleName] = make(map[string]bool) - } - for _, item := range importedItems { - cleanedItem := strings.TrimSpace(item) - if cleanedItem != "" { - existingImportsMap[moduleName][strings.TrimSpace(item)] = true - } - } - } else if strings.HasPrefix(imp, "import ") { - fields := strings.Fields(imp) - moduleName := fields[1] - if _, exists := existingImportsMap[moduleName]; !exists { - existingImportsMap[moduleName] = make(map[string]bool) - } - } - } - for i, line := range codeLines { - trimmedLine := strings.TrimSpace(line) - - if strings.HasPrefix(trimmedLine, "from ") { - fields := strings.Fields(trimmedLine) - moduleName := fields[1] - - if itemsMap, exists := existingImportsMap[moduleName]; exists && len(itemsMap) > 0 { - items := mapKeysToSortedSlice(itemsMap) - importLine := fmt.Sprintf("from %s import %s", moduleName, strings.Join(items, ", ")) - - if strings.Contains(trimmedLine, ignoredPrefixes) { - importLine += " " + ignoredPrefixes - } - codeLines[i] = importLine - delete(existingImportsMap, moduleName) - } - } - } - - for module, itemsMap := range existingImportsMap { - if len(itemsMap) > 0 { - items := mapKeysToSortedSlice(itemsMap) - importLine := fmt.Sprintf("from %s import %s", module, strings.Join(items, ", ")) - importLine += " " + ignoredPrefixes - importLines = append(importLines, importLine) - } - } - nonEmptyCodeLines := []string{} - for _, line := range codeLines { - if strings.TrimSpace(line) != "" { - nonEmptyCodeLines = append(nonEmptyCodeLines, line) - } - } - - nonEmptyImportLines := []string{} - for _, line := range importLines { - if strings.TrimSpace(line) != "" { - nonEmptyImportLines = append(nonEmptyImportLines, line) - } - } - - updatedContent := strings.Join(nonEmptyImportLines, "\n") + "\n" + strings.Join(nonEmptyCodeLines, "\n") - return updatedContent, nil -} - -func (i *Injector) updateTypeScriptImports(importedContent string, newImports []string) (string, int, error) { - importRegex := regexp.MustCompile(`(?m)^import\s+.*?;`) - existingImportsSet := make(map[string]bool) - sanitisedImports := []string{} - existingImports := importRegex.FindAllString(importedContent, -1) - for _, imp := range existingImports { - imp = strings.TrimSpace(imp) - cleanedImport := strings.ReplaceAll(imp, " ", "") - if cleanedImport != "" && !existingImportsSet[cleanedImport] { - existingImportsSet[cleanedImport] = true - sanitisedImports = append(sanitisedImports, imp) - } - } - - for _, imp := range newImports { - imp = strings.Trim(imp, `"- `) - cleanedImport := strings.ReplaceAll(imp, " ", "") - if importRegex.MatchString(imp) && !existingImportsSet[cleanedImport] { - existingImportsSet[cleanedImport] = true - sanitisedImports = append(sanitisedImports, imp) - } - } - updatedImports := strings.Join(sanitisedImports, "\n") + "\n\n" - - contentWithoutImports := importRegex.ReplaceAllString(importedContent, "") - contentWithoutImports = strings.TrimLeft(contentWithoutImports, "\n") - - updatedContent := updatedImports + "\n" + contentWithoutImports - - originalLines := strings.Split(importedContent, "\n") - updatedLines := strings.Split(updatedContent, "\n") - importLength := len(updatedLines) - len(originalLines) - - if importLength < 0 { - importLength = 0 - } - return updatedContent, importLength, nil -} - -func (i *Injector) extractJavaDependencies(output []byte) []string { - lines := strings.Split(string(output), "\n") - var dependencies []string - inDependencySection := false - - depRegex := regexp.MustCompile(`^\[INFO\]\s+[\+\|\\\-]{1,2}\s+([\w\.\-]+:[\w\.\-]+):jar:([\w\.\-]+):([\w\.\-]+)`) - - for _, line := range lines { - cleanedLine := strings.TrimSpace(line) - if strings.HasPrefix(cleanedLine, "[INFO]") { - cleanedLine = "[INFO]" + strings.TrimSpace(cleanedLine[6:]) - } - if strings.Contains(cleanedLine, "maven-dependency-plugin") && strings.Contains(cleanedLine, ":list") { - inDependencySection = true - continue - } - - if inDependencySection && (strings.Contains(cleanedLine, "BUILD SUCCESS") || strings.Contains(cleanedLine, "---")) { - inDependencySection = false - continue - } - - if inDependencySection && strings.HasPrefix(cleanedLine, "[INFO]") { - matches := depRegex.FindStringSubmatch(cleanedLine) - if len(matches) >= 4 { - groupArtifact := matches[1] - version := matches[2] - dep := fmt.Sprintf("%s:%s", groupArtifact, version) - dependencies = append(dependencies, dep) - } else { - cleanedLine = strings.TrimPrefix(cleanedLine, "[INFO]") - cleanedLine = strings.TrimSpace(cleanedLine) - - cleanedLine = strings.TrimPrefix(cleanedLine, "+-") - cleanedLine = strings.TrimPrefix(cleanedLine, "\\-") - cleanedLine = strings.TrimPrefix(cleanedLine, "|") - - cleanedLine = strings.TrimSpace(cleanedLine) - - depParts := strings.Split(cleanedLine, ":") - if len(depParts) >= 5 { - dep := fmt.Sprintf("%s:%s:%s", depParts[0], depParts[1], depParts[3]) - dependencies = append(dependencies, dep) - } - } - } - } - return dependencies -} - -func (i *Injector) addCommentToTest(testCode string) string { - comment := " Test generated using Keploy" - switch i.language { - case "python": - comment = "#" + comment - case "go", "javascript", "typescript", "java": - comment = "//" + comment - } - return fmt.Sprintf("%s\n%s", comment, testCode) -} - -func (i *Injector) getModelDetails(sourceFilePath string) string { - switch i.language { - case "go": - return i.getGoImportData(sourceFilePath) - default: - return "" - } -} - -func (i *Injector) getGoImportData(sourceFilePath string) string { - builtInTypes := map[string]struct{}{ - "string": {}, - "int": {}, - "float64": {}, - "bool": {}, - "error": {}, - "byte": {}, - "rune": {}, - "uint": {}, - "int64": {}, - "uint64": {}, - "complex64": {}, - "complex128": {}, - "float32": {}, - "int32": {}, - } - - fset := token.NewFileSet() - node, err := parser.ParseFile(fset, sourceFilePath, nil, parser.AllErrors) - if err != nil { - return "" - } - - imports := make(map[string]string) - for _, imp := range node.Imports { - pkgPath := strings.Trim(imp.Path.Value, "\"") - var alias string - - if imp.Name != nil { - if imp.Name.Name == "_" || imp.Name.Name == "." { - continue - } - alias = imp.Name.Name - } else { - parts := strings.Split(pkgPath, "/") - alias = parts[len(parts)-1] - } - - imports[alias] = pkgPath - } - // Set to store unique structs with their package paths - structSet := make(map[string]struct{}) - funcSet := make(map[string]struct{}) - - var collectStructs func(expr ast.Expr) - collectStructs = func(expr ast.Expr) { - switch t := expr.(type) { - case *ast.Ident: - structName := t.Name - if _, isBuiltIn := builtInTypes[structName]; isBuiltIn { - return - } - structKey := fmt.Sprintf("%s.%s", node.Name.Name, structName) - structSet[structKey] = struct{}{} - - case *ast.SelectorExpr: - if ident, ok := t.X.(*ast.Ident); ok { - pkgAlias := ident.Name - structName := t.Sel.Name - if pkgPath, exists := imports[pkgAlias]; exists { - structKey := fmt.Sprintf("%s.%s", pkgPath, structName) - structSet[structKey] = struct{}{} - } else { - structKey := fmt.Sprintf("%s.%s", pkgAlias, structName) - structSet[structKey] = struct{}{} - } - } - - case *ast.StarExpr: - collectStructs(t.X) - - case *ast.ArrayType: - collectStructs(t.Elt) - - case *ast.MapType: - collectStructs(t.Key) - collectStructs(t.Value) - - case *ast.StructType: - packageName := node.Name.Name - structSet[fmt.Sprintf("%s.", packageName)] = struct{}{} - } - } - - // Traverse the AST to collect structs - ast.Inspect(node, func(n ast.Node) bool { - switch x := n.(type) { - case *ast.TypeSpec: - if _, ok := x.Type.(*ast.StructType); ok { - structName := x.Name.Name - packageName := node.Name.Name - structKey := fmt.Sprintf("%s.%s", packageName, structName) - structSet[structKey] = struct{}{} - } - - case *ast.CompositeLit: - collectStructs(x.Type) - - case *ast.ValueSpec: - if x.Type != nil { - collectStructs(x.Type) - } - for _, val := range x.Values { - collectStructs(val) - } - - case *ast.Field: - collectStructs(x.Type) - - case *ast.FuncDecl: - if x.Type.Params != nil { - for _, field := range x.Type.Params.List { - collectStructs(field.Type) - } - } - if x.Type.Results != nil { - for _, field := range x.Type.Results.List { - collectStructs(field.Type) - } - } - case *ast.CallExpr: - if sel, ok := x.Fun.(*ast.SelectorExpr); ok { - if ident, ok := sel.X.(*ast.Ident); ok { - pkgAlias := ident.Name - funcName := sel.Sel.Name - if pkgPath, exists := imports[pkgAlias]; exists { - // Construct the fully qualified function name - funcKey := fmt.Sprintf("%s.%s", pkgPath, funcName) - funcSet[funcKey] = struct{}{} - } - } - } else if ident, ok := x.Fun.(*ast.Ident); ok { - moduleName, relativePath := i.GetModuleName(sourceFilePath) - packageName, _ := GetPackageName(sourceFilePath) - - if packageName != "main" { - relativePath = TrimParentFolder(relativePath) - } - var funcKey string - // Construct the function key conditionally to handle empty relativePath - if packageName == "main" { - // If the package is `main`, use the module name without extra path details - funcKey = fmt.Sprintf("%s/%s.%s", moduleName, relativePath, ident.Name) - } else { - if relativePath == "" { - funcKey = fmt.Sprintf("%s/%s.%s", moduleName, packageName, ident.Name) - } else { - funcKey = fmt.Sprintf("%s/%s/%s.%s", moduleName, relativePath, packageName, ident.Name) - } - } - funcSet[funcKey] = struct{}{} - } - - default: - } - return true - }) - - data := "" - for structKey := range structSet { - var out bytes.Buffer - cmd := exec.Command("go", "doc", structKey) - cmd.Stdout = &out - if err := cmd.Run(); err != nil { - continue - } - data += (out.String()) + "\n" - } - for funcKey := range funcSet { - var out bytes.Buffer - cmd := exec.Command("go", "doc", funcKey) - cmd.Stdout = &out - if err := cmd.Run(); err != nil { - continue - } - data += (out.String()) + "\n" - } - return data -} - -func (i *Injector) GetModuleName(sourceFilePath string) (string, string) { - file, err := os.Open("go.mod") - if err != nil { - return "", "" - } - defer func() { - if err := file.Close(); err != nil { - i.logger.Error("Error closing file", zap.Error(err)) - } - }() - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - if strings.HasPrefix(line, "module ") { - curDir, _ := os.Getwd() - dirPath := filepath.Dir(sourceFilePath) - - relativePath, _ := filepath.Rel(curDir, dirPath) - - if relativePath == "." { - return strings.TrimSpace(strings.TrimPrefix(line, "module ")), "" - } - - return strings.TrimSpace(strings.TrimPrefix(line, "module ")), relativePath - } - } - - return "", "" -} - -func GetPackageName(sourceFilePath string) (string, error) { - fset := token.NewFileSet() - node, err := parser.ParseFile(fset, sourceFilePath, nil, parser.PackageClauseOnly) - if err != nil { - return "", err - } - return node.Name.Name, nil -} - -func TrimParentFolder(path string) string { - parts := strings.Split(path, string(filepath.Separator)) - if len(parts) > 1 { - return filepath.Join(parts[:len(parts)-1]...) // Exclude the last part (file name's parent directory) - } - return path -} diff --git a/keploy/pkg/service/utgen/prompt.go b/keploy/pkg/service/utgen/prompt.go deleted file mode 100644 index bb4749d..0000000 --- a/keploy/pkg/service/utgen/prompt.go +++ /dev/null @@ -1,207 +0,0 @@ -package utgen - -import ( - "bytes" - "fmt" - "html" - "html/template" - "os" - "strings" - - settings "go.keploy.io/server/v2/pkg/service/utgen/assets" - "go.uber.org/zap" -) - -const MAX_TESTS_PER_RUN = 6 - -const ADDITIONAL_INCLUDES_TEXT = ` -## Additional Includes -The following is a set of included files used as context for the source code above. This is usually included libraries needed as context to write better tests: -====== -{{.IncludedFiles}} -====== -` - -const ADDITIONAL_INSTRUCTIONS_TEXT = ` -## Additional Instructions -====== -{{.AdditionalInstructions}} -====== -` - -const FAILED_TESTS_TEXT = ` -## Previous Iterations Failed Tests -Below is a list of failed tests that you generated in previous iterations. Do not generate the same tests again, and take the failed tests into account when generating new tests. -====== -{{.FailedTestRuns}} -====== -` - -type Source struct { - Name string - Code string - CodeNumbered string -} - -type Test struct { - Name string - Code string - CodeNumbered string -} - -type PromptBuilder struct { - Src *Source - Test *Test - CovReportContent string - IncludedFiles string - AdditionalInstructions string - Language string - Logger *zap.Logger - AdditionalPrompt string - InstalledPackages []string - FunctionUnderTest string - ImportDetails string - ModuleName string -} - -func NewPromptBuilder(srcPath, testPath, covReportContent, includedFiles, additionalInstructions, language, additionalPrompt, functionUnderTest string, logger *zap.Logger) (*PromptBuilder, error) { - var err error - src := &Source{ - Name: srcPath, - } - test := &Test{ - Name: testPath, - } - promptBuilder := &PromptBuilder{ - Src: src, - Test: test, - Language: language, - CovReportContent: covReportContent, - Logger: logger, - AdditionalPrompt: additionalPrompt, - FunctionUnderTest: functionUnderTest, - } - promptBuilder.Src.Code, err = readFile(srcPath) - if err != nil { - return nil, err - } - promptBuilder.Test.Code, err = readFile(testPath) - if err != nil { - return nil, err - } - promptBuilder.IncludedFiles, err = formatSection(includedFiles, ADDITIONAL_INCLUDES_TEXT) - if err != nil { - return nil, err - } - promptBuilder.AdditionalInstructions, err = formatSection(additionalInstructions, ADDITIONAL_INSTRUCTIONS_TEXT) - if err != nil { - return nil, err - } - return promptBuilder, nil -} - -func readFile(filePath string) (string, error) { - content, err := os.ReadFile(filePath) - if err != nil { - return "", fmt.Errorf("error reading %s: %v", filePath, err) - } - return string(content), nil -} - -func formatSection(content, templateText string) (string, error) { - if content == "" { - return "", nil - } - tmpl, err := template.New("section").Parse(templateText) - if err != nil { - return "", fmt.Errorf("Error parsing section template: %v", err) - } - var buffer bytes.Buffer - err = tmpl.Execute(&buffer, map[string]string{ - "IncludedFiles": content, - "AdditionalInstructions": content, - "FailedTestRuns": content, - }) - if err != nil { - return "", fmt.Errorf("Error executing section template: %v", err) - } - return buffer.String(), nil -} - -func (pb *PromptBuilder) BuildPrompt(file, failedTestRuns string) (*Prompt, error) { - pb.Src.CodeNumbered = numberLines(pb.Src.Code) - pb.Test.CodeNumbered = numberLines(pb.Test.Code) - variables := map[string]interface{}{ - "source_file_name": pb.Src.Name, - "test_file_name": pb.Test.Name, - "source_file_numbered": pb.Src.CodeNumbered, - "test_file_numbered": pb.Test.CodeNumbered, - "source_file": pb.Src.Code, - "test_file": pb.Test.Code, - "code_coverage_report": pb.CovReportContent, - "additional_includes_section": pb.IncludedFiles, - "failed_tests_section": failedTestRuns, - "additional_instructions_text": pb.AdditionalInstructions, - "language": pb.Language, - "max_tests": MAX_TESTS_PER_RUN, - "additional_command": pb.AdditionalPrompt, - "function_under_test": pb.FunctionUnderTest, - "installed_packages": formatInstalledPackages(pb.InstalledPackages), - "import_details": pb.ImportDetails, - "module_name": pb.ModuleName, - } - - settings := settings.GetSettings() - - prompt := &Prompt{} - - systemPrompt, err := renderTemplate(settings.GetString(file+".system"), variables) - if err != nil { - prompt.System = "" - prompt.User = "" - return prompt, fmt.Errorf("Error rendering system prompt: %v", err) - } - prompt.System = systemPrompt - - userPrompt, err := renderTemplate(settings.GetString(file+".user"), variables) - if err != nil { - prompt.System = "" - prompt.User = "" - return prompt, fmt.Errorf("Error rendering user prompt: %v", err) - } - userPrompt = html.UnescapeString(userPrompt) - prompt.User = userPrompt - return prompt, nil -} - -func formatInstalledPackages(packages []string) string { - var sb strings.Builder - for _, pkg := range packages { - sb.WriteString(fmt.Sprintf("- %s\n", pkg)) - } - return sb.String() -} - -func renderTemplate(templateText string, variables map[string]interface{}) (string, error) { - funcMap := template.FuncMap{ - "trim": strings.TrimSpace, - } - tmpl, err := template.New("prompt").Funcs(funcMap).Parse(templateText) - if err != nil { - return "", err - } - var buffer bytes.Buffer - err = tmpl.Execute(&buffer, variables) - if err != nil { - return "", err - } - return buffer.String(), nil -} - -func numberLines(text string) string { - lines := strings.Split(text, "\n") - for i, line := range lines { - lines[i] = fmt.Sprintf("%d %s", i+1, line) - } - return strings.Join(lines, "\n") -} diff --git a/keploy/pkg/service/utgen/service.go b/keploy/pkg/service/utgen/service.go deleted file mode 100644 index 2f99a72..0000000 --- a/keploy/pkg/service/utgen/service.go +++ /dev/null @@ -1,13 +0,0 @@ -package utgen - -import ( - "context" -) - -type Service interface { - Start(ctx context.Context) error -} - -type Telemetry interface { - GenerateUT() -} diff --git a/keploy/pkg/service/utgen/utils.go b/keploy/pkg/service/utgen/utils.go deleted file mode 100644 index 3d2dc88..0000000 --- a/keploy/pkg/service/utgen/utils.go +++ /dev/null @@ -1,330 +0,0 @@ -package utgen - -import ( - "bufio" - "bytes" - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "sort" - "strconv" - "strings" - "time" - - "go.keploy.io/server/v2/pkg/models" - settings "go.keploy.io/server/v2/pkg/service/utgen/assets" - "go.uber.org/zap" - - "gopkg.in/yaml.v2" -) - -func GetCodeLanguage(sourceFilePath string) string { - // Retrieve the mapping of languages to their file extensions from settings - // Create a map to hold the language extensions - languageExtensionMapOrg := make(map[string][]string) - - setting := settings.GetSettings() - - // Unmarshal the language_extension_map_org section into the map - if err := setting.UnmarshalKey("language_extension_map_org", &languageExtensionMapOrg); err != nil { - log.Fatalf("Error unmarshaling language extension map, %s", err) - } - - // Initialize a dictionary to map file extensions to their corresponding languages - extensionToLanguage := make(map[string]string) - - // Populate the extensionToLanguage dictionary - for language, extensions := range languageExtensionMapOrg { - for _, ext := range extensions { - extensionToLanguage[ext] = language - } - } - - // Extract the file extension from the source file path - parts := strings.Split(sourceFilePath, ".") - extensionS := "." + parts[len(parts)-1] - // Initialize the default language name as 'unknown' - languageName := "unknown" - - // Check if the extracted file extension is in the dictionary - if val, ok := extensionToLanguage[extensionS]; ok { - languageName = val - } - - // Return the language name in lowercase - return strings.ToLower(languageName) -} - -func unmarshalYamlTestDetails(yamlStr string) (*models.UTDetails, error) { - yamlStr = strings.TrimSpace(yamlStr) - yamlStr = strings.TrimPrefix(yamlStr, "```yaml") - yamlStr = strings.TrimSuffix(yamlStr, "```") - var data *models.UTDetails - err := yaml.Unmarshal([]byte(yamlStr), &data) - if err != nil { - return nil, fmt.Errorf("error unmarshaling yaml: %s", err) - } - return data, nil -} - -func unmarshalYamlTestHeaders(yamlStr string) (*models.UTIndentationInfo, error) { - yamlStr = strings.TrimSpace(yamlStr) - yamlStr = strings.TrimPrefix(yamlStr, "```yaml") - yamlStr = strings.TrimSuffix(yamlStr, "```") - - var data *models.UTIndentationInfo - err := yaml.Unmarshal([]byte(yamlStr), &data) - if err != nil { - return nil, fmt.Errorf("error unmarshaling yaml: %s", err) - } - return data, nil -} - -func unmarshalYamlTestLine(yamlStr string) (*models.UTInsertionInfo, error) { - yamlStr = strings.TrimSpace(yamlStr) - yamlStr = strings.TrimPrefix(yamlStr, "```yaml") - yamlStr = strings.TrimSuffix(yamlStr, "```") - var data *models.UTInsertionInfo - err := yaml.Unmarshal([]byte(yamlStr), &data) - if err != nil { - return nil, fmt.Errorf("error unmarshaling yaml: %s", err) - } - return data, nil -} - -func convertToInt(value interface{}) (int, error) { - switch v := value.(type) { - case int: - return v, nil - case float64: - return int(v), nil - case string: - return strconv.Atoi(v) - default: - return 0, fmt.Errorf("unsupported type for conversion to int: %T", value) - } -} - -func extractErrorMessage(outputMessage, failMessage, language string) string { - const maxLines = 15 - var pattern string - message := failMessage - switch language { - case "python": - pattern = `^=+ ERRORS =+$` - message = outputMessage - case "java": - pattern = `^\[ERROR\].*` - message = outputMessage - case "go": - pattern = `(?i)(^FAIL|panic:|undefined:)` - case "javascript": - pattern = `(?i)● .*` - } - re := regexp.MustCompile(pattern) - scanner := bufio.NewScanner(strings.NewReader(message)) - var result []string - matching := false - - for scanner.Scan() { - line := scanner.Text() - if re.MatchString(line) { - matching = true - } - if matching { - result = append(result, line) - if len(result) >= maxLines { - break - } - } - } - return strings.Join(result, "\n") -} - -func getFilename(filePath string) string { - return filepath.Base(filePath) -} - -func formatDuration(duration time.Duration) string { - if duration >= time.Minute { - minutes := int(duration.Minutes()) - seconds := duration.Seconds() - float64(minutes*60) - return fmt.Sprintf("%dm%.2fs", minutes, seconds) - } - return fmt.Sprintf("%.2fs", duration.Seconds()) -} - -func extractString(output []byte) []string { - lines := strings.Split(string(output), "\n") - var dependencies []string - for _, line := range lines { - trimmed := strings.TrimSpace(line) - if trimmed != "" { - dependencies = append(dependencies, trimmed) - } - } - return dependencies -} - -func isStringInarray(array []string, text string) bool { - for _, elem := range array { - if strings.EqualFold(elem, text) { - return true - } - } - return false -} - -func mapKeysToSortedSlice(itemsMap map[string]bool) []string { - items := []string{} - for item := range itemsMap { - items = append(items, item) - } - sort.Strings(items) - return items -} - -func RunCommand(command string, cwd string, logger *zap.Logger) (stdout string, stderr string, exitCode int, commandStartTime int64, err error) { - // Get the current time before running the test command, in milliseconds - commandStartTime = time.Now().UnixNano() / int64(time.Millisecond) - - var cmd *exec.Cmd - - if runtime.GOOS == "windows" { - cmdArgs := strings.Fields(command) - cmd = exec.Command(cmdArgs[0], cmdArgs[1:]...) - } else { - // Create the command with the specified working directory - cmd = exec.Command("sh", "-c", command) - if cwd != "" { - cmd.Dir = cwd - } - } - - // Capture the stdout and stderr - var outBuf, errBuf bytes.Buffer - - // Set the output of the command - cmd.Stdout = &outBuf - cmd.Stderr = &errBuf - - if logger.Level() == zap.DebugLevel { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - } - - // Run the command - err = cmd.Run() - - // Get the exit code - exitCode = cmd.ProcessState.ExitCode() - - // Return the captured output and other information - stdout = outBuf.String() - stderr = errBuf.String() - return stdout, stderr, exitCode, commandStartTime, err -} - -func getTestFilePath(sourceFilePath, testDirectory string) (string, error) { - - language := GetCodeLanguage(sourceFilePath) - - // Extract the base name and extension of the source file - baseName := filepath.Base(sourceFilePath) - extension := filepath.Ext(sourceFilePath) - - // Remove the extension from the base name - baseNameWithoutExt := strings.TrimSuffix(baseName, extension) - - var testFileBaseNames []string - - switch language { - case "go": - testFileBaseNames = []string{baseNameWithoutExt + "_test" + extension} - case "javascript": - testFileBaseNames = []string{baseNameWithoutExt + ".test" + extension} - case "python": - testFileBaseNames = []string{baseNameWithoutExt + "_test" + extension, "test_" + baseName} - default: - return "", fmt.Errorf("unsupported language: %s", language) - } - - // Find the most specific existing test file - testFilePath, err := findTestFile(testDirectory, testFileBaseNames) - if err != nil { - return "", err - } - - // If a test file was found, return it - if testFilePath != "" { - return testFilePath, nil - } - - // Construct the relative path for the new test file - relativeDir := strings.TrimPrefix(filepath.Dir(sourceFilePath), "src") - testFilePath = filepath.Join(testDirectory, relativeDir, testFileBaseNames[0]) - - return testFilePath, nil -} - -func findTestFile(testDirectory string, testFileBaseNames []string) (string, error) { - var bestMatch string - - err := filepath.Walk(testDirectory, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.IsDir() { - for _, testFileBaseName := range testFileBaseNames { - if strings.HasSuffix(path, testFileBaseName) { - if bestMatch == "" || len(path) < len(bestMatch) { - bestMatch = path - } - } - } - } - return nil - }) - - if err != nil { - return "", err - } - - return bestMatch, nil -} - -func createTestFile(testFilePath string, sourceFilePath string) (bool, error) { - // Ensure the directory exists - err := os.MkdirAll(filepath.Dir(testFilePath), os.ModePerm) - if err != nil { - return false, err - } - - // Check if the test file exists - if _, err := os.Stat(testFilePath); os.IsNotExist(err) { - // Create the test file if it does not exist - file, err := os.OpenFile(testFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - return false, err - } - defer func() { - if err := file.Close(); err != nil { - return - } - }() - - // Write initial content to the test file - _, err = file.WriteString(fmt.Sprintf("// Unit test for %s\n", filepath.Base(sourceFilePath))) - if err != nil { - return false, err - } - - return true, nil - } - - return false, nil -} diff --git a/keploy/pkg/util.go b/keploy/pkg/util.go deleted file mode 100755 index 1cf5ead..0000000 --- a/keploy/pkg/util.go +++ /dev/null @@ -1,637 +0,0 @@ -// Package pkg provides utility functions for Keploy. -package pkg - -import ( - "bufio" - "bytes" - "compress/gzip" - "context" - "encoding/json" - "encoding/xml" - "fmt" - "io" - "io/fs" - "net" - "net/http" - "net/url" - "os" - "sort" - "strconv" - "strings" - "sync/atomic" - "time" - - "github.com/andybalholm/brotli" - "go.keploy.io/server/v2/pkg/models" - - "go.keploy.io/server/v2/utils" - "go.uber.org/zap" -) - -var Emoji = "\U0001F430" + " Keploy:" - -var SortCounter int64 = -1 - -func InitSortCounter(counter int64) { - atomic.StoreInt64(&SortCounter, counter) -} - -func GetNextSortNum() int64 { - return atomic.AddInt64(&SortCounter, 1) -} - -// URLParams returns the Url and Query parameters from the request url. -func URLParams(r *http.Request) map[string]string { - qp := r.URL.Query() - result := make(map[string]string) - - for key, values := range qp { - result[key] = strings.Join(values, ", ") - } - - return result -} - -// ToYamlHTTPHeader converts the http header into yaml format -func ToYamlHTTPHeader(httpHeader http.Header) map[string]string { - header := map[string]string{} - for i, j := range httpHeader { - header[i] = strings.Join(j, ",") - } - return header -} - -func ToHTTPHeader(mockHeader map[string]string) http.Header { - header := http.Header{} - for i, j := range mockHeader { - match := IsTime(j) - if match { - //Values like "Tue, 17 Jan 2023 16:34:58 IST" should be considered as single element - header[i] = []string{j} - continue - } - header[i] = strings.Split(j, ",") - } - return header -} - -// IsTime verifies whether a given string represents a valid date or not. -func IsTime(stringDate string) bool { - date := strings.TrimSpace(stringDate) - if secondsFloat, err := strconv.ParseFloat(date, 64); err == nil { - seconds := int64(secondsFloat / 1e9) - nanoseconds := int64(secondsFloat) % 1e9 - expectedTime := time.Unix(seconds, nanoseconds) - currentTime := time.Now() - if currentTime.Sub(expectedTime) < 24*time.Hour && currentTime.Sub(expectedTime) > -24*time.Hour { - return true - } - } - for _, dateFormat := range dateFormats { - _, err := time.Parse(dateFormat, date) - if err == nil { - return true - } - } - return false -} - -func SimulateHTTP(ctx context.Context, tc *models.TestCase, testSet string, logger *zap.Logger, apiTimeout uint64) (*models.HTTPResp, error) { - var resp *models.HTTPResp - - //TODO: adjust this logic in the render function in order to remove the redundant code - // convert testcase to string and render the template values. - // Render any template values in the test case before simulation. - if len(utils.TemplatizedValues) > 0 || len(utils.SecretValues) > 0 { - testCaseStr, err := json.Marshal(tc) - if err != nil { - utils.LogError(logger, err, "failed to marshal the testcase for templating") - return nil, err - } - - // Prepare the data for template execution. - templateData := make(map[string]interface{}) - for k, v := range utils.TemplatizedValues { - templateData[k] = v - } - if len(utils.SecretValues) > 0 { - templateData["secret"] = utils.SecretValues - } - - renderedStr, err := utils.RenderTemplatesInString(logger, string(testCaseStr), templateData) - if err != nil { - utils.LogError(logger, err, "failed to render some template values", zap.Any("TestCase", tc.Name), zap.Any("TestSet", testSet)) - } - - // Unmarshal the rendered string back into the test case struct. - err = json.Unmarshal([]byte(renderedStr), &tc) - if err != nil { - utils.LogError(logger, err, "failed to unmarshal the rendered testcase", zap.Any("RenderedString", renderedStr)) - return nil, err - } - } - - reqBody := []byte(tc.HTTPReq.Body) - var err error - - if tc.HTTPReq.Header["Content-Encoding"] != "" { - reqBody, err = Compress(logger, tc.HTTPReq.Header["Content-Encoding"], reqBody) - if err != nil { - utils.LogError(logger, err, "failed to compress the request body") - return nil, err - } - } - - logger.Info("starting test for of", zap.Any("test case", models.HighlightString(tc.Name)), zap.Any("test set", models.HighlightString(testSet))) - req, err := http.NewRequestWithContext(ctx, string(tc.HTTPReq.Method), tc.HTTPReq.URL, bytes.NewBuffer(reqBody)) - if err != nil { - utils.LogError(logger, err, "failed to create a http request from the yaml document") - return nil, err - } - req.Header = ToHTTPHeader(tc.HTTPReq.Header) - req.ProtoMajor = tc.HTTPReq.ProtoMajor - req.ProtoMinor = tc.HTTPReq.ProtoMinor - req.Header.Set("KEPLOY-TEST-ID", tc.Name) - req.Header.Set("KEPLOY-TEST-SET-ID", testSet) - // send if its the last testcase - if tc.IsLast { - req.Header.Set("KEPLOY-LAST-TESTCASE", "true") - } - logger.Debug(fmt.Sprintf("Sending request to user app:%v", req)) - - // override host header if present in the request - hostHeader := tc.HTTPReq.Header["Host"] - if hostHeader != "" { - logger.Debug("overriding host header", zap.String("host", hostHeader)) - req.Host = hostHeader - } - - // Creating the client and disabling redirects - var client *http.Client - - _, hasAcceptEncoding := req.Header["Accept-Encoding"] - disableCompression := !hasAcceptEncoding - - keepAlive, ok := req.Header["Connection"] - if ok && strings.EqualFold(keepAlive[0], "keep-alive") { - logger.Debug("simulating request with conn:keep-alive") - client = &http.Client{ - Timeout: time.Second * time.Duration(apiTimeout), - CheckRedirect: func(_ *http.Request, _ []*http.Request) error { - return http.ErrUseLastResponse - }, - Transport: &http.Transport{ - DisableCompression: disableCompression, - }, - } - } else if ok && strings.EqualFold(keepAlive[0], "close") { - logger.Debug("simulating request with conn:close") - client = &http.Client{ - Timeout: time.Second * time.Duration(apiTimeout), - CheckRedirect: func(_ *http.Request, _ []*http.Request) error { - return http.ErrUseLastResponse - }, - Transport: &http.Transport{ - DisableKeepAlives: true, - DisableCompression: disableCompression, - }, - } - } else { - logger.Debug("simulating request with conn:keep-alive (maxIdleConn=1)") - client = &http.Client{ - Timeout: time.Second * time.Duration(apiTimeout), - CheckRedirect: func(_ *http.Request, _ []*http.Request) error { - return http.ErrUseLastResponse - }, - Transport: &http.Transport{ - DisableKeepAlives: false, - MaxIdleConns: 1, - DisableCompression: disableCompression, - }, - } - } - - httpResp, errHTTPReq := client.Do(req) - if errHTTPReq != nil { - utils.LogError(logger, errHTTPReq, "failed to send testcase request to app") - return nil, errHTTPReq - } - - defer func() { - if httpResp != nil && httpResp.Body != nil { - if err := httpResp.Body.Close(); err != nil { - utils.LogError(logger, err, "failed to close response body") - } - } - }() - - respBody, errReadRespBody := io.ReadAll(httpResp.Body) - if errReadRespBody != nil { - utils.LogError(logger, errReadRespBody, "failed reading response body") - return nil, errReadRespBody - } - - if httpResp.Header.Get("Content-Encoding") != "" { - respBody, err = Decompress(logger, httpResp.Header.Get("Content-Encoding"), respBody) - if err != nil { - utils.LogError(logger, err, "failed to decode response body") - return nil, err - } - } - - statusMessage := http.StatusText(httpResp.StatusCode) - - resp = &models.HTTPResp{ - StatusCode: httpResp.StatusCode, - StatusMessage: statusMessage, - Body: string(respBody), - Header: ToYamlHTTPHeader(httpResp.Header), - } - - return resp, errHTTPReq -} - -func ParseHTTPRequest(requestBytes []byte) (*http.Request, error) { - // Parse the request using the http.ReadRequest function - request, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(requestBytes))) - if err != nil { - return nil, err - } - request.Header.Set("Host", request.Host) - - return request, nil -} - -func ParseHTTPResponse(data []byte, request *http.Request) (*http.Response, error) { - buffer := bytes.NewBuffer(data) - reader := bufio.NewReader(buffer) - response, err := http.ReadResponse(reader, request) - if err != nil { - return nil, err - } - return response, nil -} - -func MakeCurlCommand(tc models.HTTPReq) string { - curl := fmt.Sprintf("curl --request %s \\\n", string(tc.Method)) - curl = curl + fmt.Sprintf(" --url %s \\\n", tc.URL) - header := ToHTTPHeader(tc.Header) - - for k, v := range ToYamlHTTPHeader(header) { - if k != "Content-Length" { - curl = curl + fmt.Sprintf(" --header '%s: %s' \\\n", k, v) - } - } - if len(tc.Form) > 0 { - for _, form := range tc.Form { - key := form.Key - if len(form.Values) == 0 { - continue - } - value := form.Values[0] - curl = curl + fmt.Sprintf(" --form '%s=%s' \\\n", key, value) - } - } else if tc.Body != "" { - curl = curl + fmt.Sprintf(" --data %s", strconv.Quote(tc.Body)) - } - return curl -} - -func ReadSessionIndices(path string, Logger *zap.Logger) ([]string, error) { - indices := []string{} - - dir, err := os.OpenFile(path, os.O_RDONLY, fs.FileMode(os.O_RDONLY)) - if err != nil { - Logger.Debug("creating a folder for the keploy generated testcases", zap.Error(err)) - return indices, err - } - defer func() { - if closeErr := dir.Close(); closeErr != nil { - Logger.Debug("failed to close directory", zap.Error(closeErr)) - } - }() - - files, err := dir.ReadDir(0) - if err != nil { - Logger.Debug("failed to read directory contents", zap.Error(err)) - return indices, err - } - - for _, v := range files { - if v.Name() != "reports" && v.IsDir() { - indices = append(indices, v.Name()) - } - } - - return indices, nil -} - -func NextID(IDs []string, identifier string) string { - latestIndx := 0 - for _, ID := range IDs { - namePackets := strings.Split(ID, "-") - if len(namePackets) == 3 { - Indx, err := strconv.Atoi(namePackets[2]) - if err != nil { - continue - } - if latestIndx < Indx+1 { - latestIndx = Indx + 1 - } - } - } - return fmt.Sprintf("%s%v", identifier, latestIndx) -} - -func LastID(IDs []string, identifier string) string { - latestIndx := 0 - for _, ID := range IDs { - namePackets := strings.Split(ID, "-") - if len(namePackets) == 3 { - Indx, err := strconv.Atoi(namePackets[2]) - if err != nil { - continue - } - if latestIndx < Indx { - latestIndx = Indx - } - } - } - return fmt.Sprintf("%s%v", identifier, latestIndx) -} - -var ( - dateFormats = []string{ - time.Layout, - time.ANSIC, - time.UnixDate, - time.RubyDate, - time.RFC822, - time.RFC822Z, - time.RFC850, - time.RFC1123, - time.RFC1123Z, - time.RFC3339, - time.RFC3339Nano, - time.Kitchen, - time.Stamp, - time.StampMilli, - time.StampMicro, - time.StampNano, - time.DateTime, - time.DateOnly, - time.TimeOnly, - } -) - -// ExtractPort extracts the port from a given URL string, defaulting to 80 if no port is specified. -func ExtractPort(rawURL string) (uint32, error) { - parsedURL, err := url.Parse(rawURL) - if err != nil { - return 0, err - } - - host := parsedURL.Host - if strings.Contains(host, ":") { - // Split the host by ":" and return the port part - parts := strings.Split(host, ":") - port, err := strconv.ParseUint(parts[len(parts)-1], 10, 32) - if err != nil { - return 0, fmt.Errorf("invalid port in URL: %s", rawURL) - } - return uint32(port), nil - } - - // Default ports based on scheme - switch parsedURL.Scheme { - case "https": - return 443, nil - default: - return 80, nil - } -} - -func ExtractHostAndPort(curlCmd string) (string, string, error) { - // Split the command string to find the URL - parts := strings.Split(curlCmd, " ") - for _, part := range parts { - if strings.HasPrefix(part, "http") { - u, err := url.Parse(part) - if err != nil { - return "", "", err - } - host := u.Hostname() - port := u.Port() - if port == "" { - if u.Scheme == "https" { - port = "443" - } else { - port = "80" - } - } - return host, port, nil - } - } - return "", "", fmt.Errorf("no URL found in CURL command") -} - -func WaitForPort(ctx context.Context, host string, port string, timeout time.Duration) error { - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - - timer := time.NewTimer(timeout) - defer timer.Stop() - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), 800*time.Millisecond) - if err == nil { - err := conn.Close() - if err != nil { - return err - } - return nil - } - case <-timer.C: - msg := "Please add delay if your application takes more time to start" - return fmt.Errorf("timeout after %v waiting for port %s:%s, %s", timeout, host, port, msg) - } - } -} - -func FilterTcsMocks(ctx context.Context, logger *zap.Logger, m []*models.Mock, afterTime time.Time, beforeTime time.Time) []*models.Mock { - filteredMocks, _ := filterByTimeStamp(ctx, logger, m, afterTime, beforeTime) - - sort.SliceStable(filteredMocks, func(i, j int) bool { - return filteredMocks[i].Spec.ReqTimestampMock.Before(filteredMocks[j].Spec.ReqTimestampMock) - }) - - return filteredMocks -} - -func FilterConfigMocks(ctx context.Context, logger *zap.Logger, m []*models.Mock, afterTime time.Time, beforeTime time.Time) []*models.Mock { - filteredMocks, unfilteredMocks := filterByTimeStamp(ctx, logger, m, afterTime, beforeTime) - - sort.SliceStable(filteredMocks, func(i, j int) bool { - return filteredMocks[i].Spec.ReqTimestampMock.Before(filteredMocks[j].Spec.ReqTimestampMock) - }) - - sort.SliceStable(unfilteredMocks, func(i, j int) bool { - return unfilteredMocks[i].Spec.ReqTimestampMock.Before(unfilteredMocks[j].Spec.ReqTimestampMock) - }) - - return append(filteredMocks, unfilteredMocks...) -} - -func filterByTimeStamp(_ context.Context, logger *zap.Logger, m []*models.Mock, afterTime time.Time, beforeTime time.Time) ([]*models.Mock, []*models.Mock) { - - filteredMocks := make([]*models.Mock, 0) - unfilteredMocks := make([]*models.Mock, 0) - - if afterTime.Equal(time.Time{}) { - return m, unfilteredMocks - } - - if beforeTime.Equal(time.Time{}) { - return m, unfilteredMocks - } - - isNonKeploy := false - - for _, mock := range m { - // doing deep copy to prevent data race, which was happening due to the write to isFiltered - // field in this for loop, and write in mockmanager functions. - tmp := *mock - p := &tmp - if p.Version != "api.keploy.io/v1beta1" && p.Version != "api.keploy.io/v1beta2" { - isNonKeploy = true - } - if p.Spec.ReqTimestampMock.Equal(time.Time{}) || p.Spec.ResTimestampMock.Equal(time.Time{}) { - logger.Debug("request or response timestamp of mock is missing") - p.TestModeInfo.IsFiltered = true - filteredMocks = append(filteredMocks, p) - continue - } - - if p.Spec.ReqTimestampMock.After(afterTime) && p.Spec.ResTimestampMock.Before(beforeTime) { - p.TestModeInfo.IsFiltered = true - filteredMocks = append(filteredMocks, p) - continue - } - p.TestModeInfo.IsFiltered = false - unfilteredMocks = append(unfilteredMocks, p) - } - if isNonKeploy { - logger.Debug("Few mocks in the mock File are not recorded by keploy ignoring them") - } - return filteredMocks, unfilteredMocks -} - -func GuessContentType(data []byte) models.BodyType { - // Use net/http library's DetectContentType for basic MIME type detection - mimeType := http.DetectContentType(data) - - // Additional checks to further specify the content type - switch { - case IsJSON(data): - return models.JSON - case IsXML(data): - return models.XML - case strings.Contains(mimeType, "text/html"): - return models.HTML - case strings.Contains(mimeType, "text/plain"): - if IsCSV(data) { - return models.CSV - } - return models.Plain - } - - return models.UnknownType -} - -func IsJSON(body []byte) bool { - var js interface{} - return json.Unmarshal(body, &js) == nil -} - -func IsXML(data []byte) bool { - var xm xml.Name - return xml.Unmarshal(data, &xm) == nil -} - -// IsCSV checks if data can be parsed as CSV by looking for common characteristics -func IsCSV(data []byte) bool { - // Very simple CSV check: look for commas in the first line - content := string(data) - if lines := strings.Split(content, "\n"); len(lines) > 0 { - return strings.Contains(lines[0], ",") - } - return false -} - -func Decompress(logger *zap.Logger, encoding string, data []byte) ([]byte, error) { - switch encoding { - case "br": - logger.Debug("decompressing brotli compressed data") - reader := brotli.NewReader(bytes.NewReader(data)) - decodedData, err := io.ReadAll(reader) - if err != nil { - utils.LogError(logger, err, "failed to read the brotli compressed data") - return nil, err - } - return decodedData, nil - case "gzip": - logger.Debug("decoding gzip compressed data") - reader, err := gzip.NewReader(bytes.NewReader(data)) - if err != nil { - utils.LogError(logger, err, "failed to create gzip reader") - return nil, err - } - defer reader.Close() - decodedData, err := io.ReadAll(reader) - if err != nil { - utils.LogError(logger, err, "failed to read the gzip compressed data") - return nil, err - } - return decodedData, nil - } - return data, nil -} - -func Compress(logger *zap.Logger, encoding string, data []byte) ([]byte, error) { - switch encoding { - case "gzip": - logger.Debug("compressing data using gzip") - var compressedBuffer bytes.Buffer - gw := gzip.NewWriter(&compressedBuffer) - _, err := gw.Write(data) - if err != nil { - utils.LogError(logger, err, "failed to write compressed data to gzip writer") - return nil, err - } - err = gw.Close() - if err != nil { - utils.LogError(logger, err, "failed to close gzip writer") - return nil, err - } - return compressedBuffer.Bytes(), nil - case "br": - logger.Debug("compressing data using brotli") - var compressedBuffer bytes.Buffer - bw := brotli.NewWriter(&compressedBuffer) - _, err := bw.Write(data) - if err != nil { - utils.LogError(logger, err, "failed to write compressed data to brotli writer") - return nil, err - } - err = bw.Close() - if err != nil { - utils.LogError(logger, err, "failed to close brotli writer") - return nil, err - } - return compressedBuffer.Bytes(), nil - } - return data, nil -} diff --git a/keploy/pkg/util_test.go b/keploy/pkg/util_test.go deleted file mode 100644 index 72dad75..0000000 --- a/keploy/pkg/util_test.go +++ /dev/null @@ -1,247 +0,0 @@ -package pkg - -import ( - "context" - "fmt" - "io" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -// TestSimulateHTTP_NewRequestError_303 ensures that SimulateHTTP returns an error -// when http.NewRequestWithContext fails. This is triggered by providing an invalid -// HTTP method string in the test case. -func TestSimulateHTTP_NewRequestError_303(t *testing.T) { - // Arrange - ctx := context.Background() - logger := zap.NewNop() - tc := &models.TestCase{ - Name: "test-case-new-request-error", - HTTPReq: models.HTTPReq{ - Method: "INVALID METHOD", // Invalid method - URL: "http://example.com/test", - Body: `{"key":"value"}`, - }, - } - - // Act - resp, err := SimulateHTTP(ctx, tc, "test-set", logger, 10) - - // Assert - require.Error(t, err) - assert.Contains(t, err.Error(), "invalid method") - assert.Nil(t, resp) -} - -// TestIsTime_VariousFormats_808 covers multiple scenarios for the IsTime function, -// including standard date formats (RFC3339, UnixDate), numeric timestamps as strings, -// and invalid inputs to ensure it correctly identifies time-like strings. -func TestIsTime_VariousFormats_808(t *testing.T) { - testCases := []struct { - name string - input string - expected bool - }{ - {"RFC3339", "2023-01-17T16:34:58Z", true}, - {"UnixDate", "Tue Jan 17 16:34:58 UTC 2023", true}, - {"NumericTimestamp", fmt.Sprintf("%f", float64(time.Now().UnixNano())), true}, - {"AlmostNow", fmt.Sprintf("%f", float64(time.Now().Add(-time.Hour).UnixNano())), true}, - {"TooOldNumeric", fmt.Sprintf("%f", float64(time.Now().Add(-48*time.Hour).UnixNano())), false}, - {"InvalidString", "not a date", false}, - {"EmptyString", "", false}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, IsTime(tc.input)) - }) - } -} - -// TestToHTTPHeader_WithTimeValue_909 verifies that the ToHTTPHeader function correctly -// converts a map of strings to an http.Header object. It specifically checks that -// header values recognized as timestamps are not split by commas, while other -// comma-separated values are correctly split into slices. -func TestToHTTPHeader_WithTimeValue_909(t *testing.T) { - // Arrange - mockHeader := map[string]string{ - "Date": "Tue, 17 Jan 2023 16:34:58 IST", - "X-Custom-Header": "value1,value2", - "Content-Type": "application/json", - } - - // Act - httpHeader := ToHTTPHeader(mockHeader) - - // Assert - require.NotNil(t, httpHeader) - assert.Equal(t, []string{"Tue, 17 Jan 2023 16:34:58 IST"}, httpHeader["Date"]) - assert.Equal(t, []string{"value1", "value2"}, httpHeader["X-Custom-Header"]) - assert.Equal(t, []string{"application/json"}, httpHeader["Content-Type"]) -} - -// TestParseHTTPRequest_And_Response_111 contains sub-tests for ParseHTTPRequest and -// ParseHTTPResponse, validating both success and failure cases for parsing raw -// HTTP data into their respective struct representations. -func TestParseHTTPRequest_And_Response_111(t *testing.T) { - t.Run("ParseHTTPRequest_Valid", func(t *testing.T) { - rawReq := "GET /test HTTP/1.1\r\nHost: example.com\r\n\r\n" - req, err := ParseHTTPRequest([]byte(rawReq)) - require.NoError(t, err) - assert.Equal(t, "GET", req.Method) - assert.Equal(t, "/test", req.URL.Path) - assert.Equal(t, "example.com", req.Host) - }) - - t.Run("ParseHTTPRequest_Invalid", func(t *testing.T) { - rawReq := "this is not a valid request" - _, err := ParseHTTPRequest([]byte(rawReq)) - require.Error(t, err) - }) - - t.Run("ParseHTTPResponse_Valid", func(t *testing.T) { - rawResp := "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, world!" - req, _ := http.NewRequest("GET", "http://example.com", nil) - resp, err := ParseHTTPResponse([]byte(rawResp), req) - require.NoError(t, err) - defer func() { - if err := resp.Body.Close(); err != nil { - t.Logf("failed to close response body: %v", err) - } - }() - assert.Equal(t, 200, resp.StatusCode) - body, _ := io.ReadAll(resp.Body) - assert.Equal(t, "Hello, world!", string(body)) - }) - - t.Run("ParseHTTPResponse_Invalid", func(t *testing.T) { - rawResp := "this is not a valid response" - req, _ := http.NewRequest("GET", "http://example.com", nil) - _, err := ParseHTTPResponse([]byte(rawResp), req) - require.Error(t, err) - }) -} - -// TestCompressDecompress_AllEncodings_555 provides comprehensive testing for the -// Compress and Decompress functions. It checks: -// - Successful round-trip (compress then decompress) for 'gzip' and 'br' (brotli). -// - No-op behavior for unknown encodings. -// - Error handling for invalid compressed data. -func TestCompressDecompress_AllEncodings_555(t *testing.T) { - logger := zap.NewNop() - - t.Run("Gzip", func(t *testing.T) { - originalData := []byte("hello world, this is a test") - compressedData, err := Compress(logger, "gzip", originalData) - require.NoError(t, err) - assert.NotEqual(t, originalData, compressedData) - decompressedData, err := Decompress(logger, "gzip", compressedData) - require.NoError(t, err) - assert.Equal(t, originalData, decompressedData) - }) - - t.Run("Brotli", func(t *testing.T) { - originalData := []byte("hello world, this is a brotli test") - compressedData, err := Compress(logger, "br", originalData) - require.NoError(t, err) - assert.NotEqual(t, originalData, compressedData) - decompressedData, err := Decompress(logger, "br", compressedData) - require.NoError(t, err) - assert.Equal(t, originalData, decompressedData) - }) - - t.Run("UnknownEncoding", func(t *testing.T) { - originalData := []byte("hello world") - compressedData, err := Compress(logger, "unknown", originalData) - require.NoError(t, err) - assert.Equal(t, originalData, compressedData) - decompressedData, err := Decompress(logger, "unknown", originalData) - require.NoError(t, err) - assert.Equal(t, originalData, decompressedData) - }) - - t.Run("DecompressError", func(t *testing.T) { - invalidGzipData := []byte("not gzip") - _, err := Decompress(logger, "gzip", invalidGzipData) - require.Error(t, err) - - invalidBrotliData := []byte{0xce, 0xb2, 0xcf, 0x81} - _, err = Decompress(logger, "br", invalidBrotliData) - require.Error(t, err) - }) -} - -// TestFilterMocks_678 validates the filtering and sorting of mocks in Test and Config modes. -func TestFilterMocks_678(t *testing.T) { - logger := zap.NewNop() - ctx := context.Background() - - now := time.Now() - mock1 := &models.Mock{Name: "mock1", Version: "api.keploy.io/v1beta1", Spec: models.MockSpec{ReqTimestampMock: now.Add(-2 * time.Hour), ResTimestampMock: now.Add(-2 * time.Hour)}} - mock2 := &models.Mock{Name: "mock2", Version: "api.keploy.io/v1beta1", Spec: models.MockSpec{ReqTimestampMock: now, ResTimestampMock: now}} - mock3 := &models.Mock{Name: "mock3", Version: "api.keploy.io/v1beta1", Spec: models.MockSpec{ReqTimestampMock: now.Add(2 * time.Hour), ResTimestampMock: now.Add(2 * time.Hour)}} - mockNoTime := &models.Mock{Name: "mockNoTime", Version: "api.keploy.io/v1beta1", Spec: models.MockSpec{}} - mockNonKeploy := &models.Mock{Name: "mockNonKeploy", Version: "v1", Spec: models.MockSpec{ReqTimestampMock: now, ResTimestampMock: now}} - - allMocks := []*models.Mock{mock3, mock1, mock2, mockNoTime, mockNonKeploy} - - t.Run("filterByTimeStamp_NoFilters", func(t *testing.T) { - filtered, unfiltered := filterByTimeStamp(ctx, logger, allMocks, time.Time{}, time.Time{}) - assert.Len(t, filtered, 5) - assert.Len(t, unfiltered, 0) - }) - - t.Run("filterByTimeStamp_ValidRange", func(t *testing.T) { - after := now.Add(-1 * time.Hour) - before := now.Add(1 * time.Hour) - filtered, unfiltered := filterByTimeStamp(ctx, logger, allMocks, after, before) - - // Check filtered mocks - require.Len(t, filtered, 3) - filteredNames := []string{} - for _, m := range filtered { - filteredNames = append(filteredNames, m.Name) - assert.True(t, m.TestModeInfo.IsFiltered) - } - assert.ElementsMatch(t, []string{"mock2", "mockNoTime", "mockNonKeploy"}, filteredNames) - - // Check unfiltered mocks - require.Len(t, unfiltered, 2) - unfilteredNames := []string{} - for _, m := range unfiltered { - unfilteredNames = append(unfilteredNames, m.Name) - assert.False(t, m.TestModeInfo.IsFiltered) - } - assert.ElementsMatch(t, []string{"mock1", "mock3"}, unfilteredNames) - }) - - t.Run("FilterTcsMocks", func(t *testing.T) { - after := now.Add(-1 * time.Hour) - before := now.Add(1 * time.Hour) - result := FilterTcsMocks(ctx, logger, allMocks, after, before) - require.Len(t, result, 3) - assert.Equal(t, "mockNoTime", result[0].Name) // Zero time comes first - assert.Equal(t, "mock2", result[1].Name) - assert.Equal(t, "mockNonKeploy", result[2].Name) - }) - - t.Run("FilterConfigMocks", func(t *testing.T) { - after := now.Add(-1 * time.Hour) - before := now.Add(1 * time.Hour) - result := FilterConfigMocks(ctx, logger, allMocks, after, before) - require.Len(t, result, 5) - // Sorted filtered part - assert.Equal(t, "mockNoTime", result[0].Name) - assert.Equal(t, "mock2", result[1].Name) - assert.Equal(t, "mockNonKeploy", result[2].Name) - // Sorted unfiltered part - assert.Equal(t, "mock1", result[3].Name) - assert.Equal(t, "mock3", result[4].Name) - }) -} diff --git a/keploy/utils/ctx.go b/keploy/utils/ctx.go deleted file mode 100644 index f9a744d..0000000 --- a/keploy/utils/ctx.go +++ /dev/null @@ -1,69 +0,0 @@ -// Package utils provides utility functions for the Keploy application. -package utils - -import ( - "context" - "errors" - "fmt" - "os" - "os/signal" - "syscall" - - "go.uber.org/zap" -) - -var cancel context.CancelFunc - -func NewCtx() context.Context { - // Create a context that can be canceled - ctx, cancel := context.WithCancel(context.Background()) - - SetCancel(cancel) - // Set up a channel to listen for signals - sigs := make(chan os.Signal, 1) - // os.Interrupt is more portable than syscall.SIGINT - // there is no equivalent for syscall.SIGTERM in os.Signal - signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) - - // Start a goroutine that will cancel the context when a signal is received - go func() { - sig := <-sigs // this received signal will be inside keploy docker container if running in docker else on the host. - fmt.Printf("Signal received: %s, canceling context...\n", sig) - cancel() - }() - - return ctx -} - -// Stop requires a reason to stop the server. -// this is to ensure that the server is not stopped accidentally. -// and to trace back the stopper -func Stop(logger *zap.Logger, reason string) error { - // Stop the server. - if logger == nil { - return errors.New("logger is not set") - } - if cancel == nil { - err := errors.New("cancel function is not set") - LogError(logger, err, "failed stopping keploy") - return err - } - - if reason == "" { - err := errors.New("cannot stop keploy without a reason") - LogError(logger, err, "failed stopping keploy") - return err - } - - logger.Info("stopping Keploy", zap.String("reason", reason)) - ExecCancel() - return nil -} - -func ExecCancel() { - cancel() -} - -func SetCancel(c context.CancelFunc) { - cancel = c -} diff --git a/keploy/utils/inc.go b/keploy/utils/inc.go deleted file mode 100644 index be4da2a..0000000 --- a/keploy/utils/inc.go +++ /dev/null @@ -1,23 +0,0 @@ -package utils - -import "sync" - -type AutoInc struct { - sync.Mutex // ensures autoInc is goroutine-safe - id int -} - -func (a *AutoInc) Next() (id int) { - a.Lock() - defer a.Unlock() - - id = a.id - a.id++ - return -} - -func (a *AutoInc) Reset() { - a.Lock() - a.id = 0 - a.Unlock() -} diff --git a/keploy/utils/log/colors.go b/keploy/utils/log/colors.go deleted file mode 100644 index da80aa2..0000000 --- a/keploy/utils/log/colors.go +++ /dev/null @@ -1,48 +0,0 @@ -// Package log provides utility functions for logging. -package log - -import ( - "bytes" - - "go.uber.org/zap" - "go.uber.org/zap/buffer" - "go.uber.org/zap/zapcore" -) - -type color struct { - *zapcore.EncoderConfig - zapcore.Encoder -} - -func NewColor(cfg zapcore.EncoderConfig, enableColor bool) (enc zapcore.Encoder) { - if enableColor { - return color{ - EncoderConfig: &cfg, - Encoder: zapcore.NewConsoleEncoder(cfg), - } - } - // fmt.Println("Color is disabled") - return zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) -} - -// EncodeEntry overrides ConsoleEncoder's EncodeEntry -func (c color) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (buf *buffer.Buffer, err error) { - buff, err := c.Encoder.EncodeEntry(ent, fields) // Utilize the existing implementation of zap - if err != nil { - return nil, err - } - - bytesArr := bytes.ReplaceAll(buff.Bytes(), []byte("\\u001b"), []byte("\u001b")) - buff.Reset() - buff.AppendString(string(bytesArr)) - return buff, err -} - -// Clone overrides ConsoleEncoder's Clone -func (c color) Clone() zapcore.Encoder { - clone := c.Encoder.Clone() - return color{ - EncoderConfig: c.EncoderConfig, - Encoder: clone, - } -} diff --git a/keploy/utils/log/logger.go b/keploy/utils/log/logger.go deleted file mode 100644 index 19ee06d..0000000 --- a/keploy/utils/log/logger.go +++ /dev/null @@ -1,99 +0,0 @@ -package log - -import ( - "fmt" - "os" - "time" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -var Emoji = "\U0001F430" + " Keploy:" - -// TODO find better way than global variable - -var LogCfg zap.Config - -func New() (*zap.Logger, *os.File, error) { - _ = zap.RegisterEncoder("colorConsole", func(config zapcore.EncoderConfig) (zapcore.Encoder, error) { - return NewColor(config, true), nil - }) - _ = zap.RegisterEncoder("nonColorConsole", func(config zapcore.EncoderConfig) (zapcore.Encoder, error) { - return NewColor(config, false), nil - }) - - logFile, err := os.OpenFile("keploy-logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0777) - if err != nil { - return nil, nil, fmt.Errorf("failed to open log file: %v", err) - } - - err = os.Chmod("keploy-logs.txt", 0777) - if err != nil { - return nil, nil, fmt.Errorf("failed to set the log file permission to 777: %v", err) - } - - writer := zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(logFile)) - - LogCfg = zap.NewDevelopmentConfig() - - LogCfg.Encoding = "colorConsole" - - // Customize the encoder config to put the emoji at the beginning. - LogCfg.EncoderConfig.EncodeTime = customTimeEncoder - LogCfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder - - LogCfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel) - LogCfg.DisableStacktrace = true - LogCfg.EncoderConfig.EncodeCaller = nil - - core := zapcore.NewCore( - zapcore.NewConsoleEncoder(LogCfg.EncoderConfig), - writer, - LogCfg.Level, - ) - - logger := zap.New(core) - - return logger, logFile, nil -} - -func ChangeLogLevel(level zapcore.Level) (*zap.Logger, error) { - LogCfg.Level = zap.NewAtomicLevelAt(level) - if level == zap.DebugLevel { - LogCfg.DisableStacktrace = false - LogCfg.EncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder - } - - logger, err := LogCfg.Build() - if err != nil { - return nil, fmt.Errorf("failed to build config for logger: %v", err) - } - return logger, nil -} - -func AddMode(mode string) (*zap.Logger, error) { - // Get the current logger configuration - cfg := LogCfg - // Update the time encoder with the new values - cfg.EncoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { - emoji := "\U0001F430" - mode := fmt.Sprintf("Keploy(%s):", mode) - enc.AppendString(emoji + " " + mode + " " + t.Format(time.RFC3339) + " ") - } - // Rebuild the logger with the updated configuration - newLogger, err := cfg.Build() - if err != nil { - return nil, fmt.Errorf("failed to add mode to logger: %v", err) - } - return newLogger, nil -} - -func ChangeColorEncoding() (*zap.Logger, error) { - LogCfg.Encoding = "nonColorConsole" - logger, err := LogCfg.Build() - if err != nil { - return nil, fmt.Errorf("failed to build config for logger: %v", err) - } - return logger, nil -} diff --git a/keploy/utils/log/time.go b/keploy/utils/log/time.go deleted file mode 100644 index 48509f7..0000000 --- a/keploy/utils/log/time.go +++ /dev/null @@ -1,12 +0,0 @@ -package log - -import ( - "time" - - "go.uber.org/zap/zapcore" -) - -func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { - emoji := "\U0001F430" + " Keploy:" - enc.AppendString(emoji + " " + t.Format(time.RFC3339) + " ") -} diff --git a/keploy/utils/mask_others.go b/keploy/utils/mask_others.go deleted file mode 100644 index 3630178..0000000 --- a/keploy/utils/mask_others.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !windows - -package utils - -import "syscall" - -func SetUmask() int { - return syscall.Umask(0) -} - -func RestoreUmask(oldMask int) { - syscall.Umask(oldMask) -} diff --git a/keploy/utils/mask_windows.go b/keploy/utils/mask_windows.go deleted file mode 100644 index 7b9eb3e..0000000 --- a/keploy/utils/mask_windows.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build windows - -package utils - -func SetUmask() int { - return 0 -} - -func RestoreUmask(oldMask int) { - // Do nothing -} diff --git a/keploy/utils/signal_others.go b/keploy/utils/signal_others.go deleted file mode 100644 index 9475a1b..0000000 --- a/keploy/utils/signal_others.go +++ /dev/null @@ -1,104 +0,0 @@ -//go:build linux || darwin - -package utils - -import ( - "context" - "fmt" - "os" - "os/exec" - "strconv" - "syscall" - "time" - - "go.uber.org/zap" -) - -func SendSignal(logger *zap.Logger, pid int, sig syscall.Signal) error { - err := syscall.Kill(pid, sig) - if err != nil { - // ignore the ESRCH error as it means the process is already dead - if errno, ok := err.(syscall.Errno); ok && errno == syscall.ESRCH { - return nil - } - logger.Error("failed to send signal to process", zap.Int("pid", pid), zap.Error(err)) - return err - } - logger.Debug("signal sent to process successfully", zap.Int("pid", pid), zap.String("signal", sig.String())) - - return nil -} - -func ExecuteCommand(ctx context.Context, logger *zap.Logger, userCmd string, cancel func(cmd *exec.Cmd) func() error, waitDelay time.Duration) CmdError { - // Run the app as the user who invoked sudo - username := os.Getenv("SUDO_USER") - - // Get the current hard limit for the number of open file descriptors - var rlimit syscall.Rlimit - hardLimit := 0 - var err error - if username != "" { - // get sudoers rlimit - (reason) https://github.com/keploy/keploy/issues/2899 - out, err := exec.Command("sudo", "-u", username, "sh", "-c", "ulimit -Hn").Output() - if err != nil { - logger.Warn("failed to get the hard limit for the number of open file descriptors for the user", zap.String("username", username), zap.Error(err)) - } else { - output := string(out)[:len(out)-1] - if output != "unlimited" { - limit, err := strconv.Atoi(output) - if err != nil { - logger.Warn("failed to parse the hard limit for the number of open file descriptors for the user", zap.String("username", username), zap.Error(err)) - } else { - hardLimit = limit - } - } - } - } else { - err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit) - if err != nil { - logger.Warn("failed to get the hard limit for the number of open file descriptors for the root user", zap.Error(err)) - } else { - hardLimit = int(rlimit.Max) - } - } - - if hardLimit != 0 { - userCmd = fmt.Sprintf("ulimit -S -n %d && %s", hardLimit, userCmd) - } - - cmd := exec.CommandContext(ctx, "sh", "-c", userCmd) - if username != "" { - // print all environment variables - logger.Debug("env inherited from the cmd", zap.Any("env", os.Environ())) - // Run the command as the user who invoked sudo to preserve the user environment variables and PATH - cmd = exec.CommandContext(ctx, "sudo", "-E", "-u", os.Getenv("SUDO_USER"), "env", "PATH="+os.Getenv("PATH"), "sh", "-c", userCmd) - } - - // Set the cancel function for the command - cmd.Cancel = cancel(cmd) - - // wait after sending the interrupt signal, before sending the kill signal - cmd.WaitDelay = waitDelay - - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setpgid: true, - } - - // Set the output of the command - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - logger.Debug("", zap.Any("executing cli", cmd.String())) - - err = cmd.Start() - if err != nil { - return CmdError{Type: Init, Err: err} - } - - err = cmd.Wait() - if err != nil { - return CmdError{Type: Runtime, Err: err} - } - - return CmdError{} -} diff --git a/keploy/utils/signal_windows.go b/keploy/utils/signal_windows.go deleted file mode 100644 index f2713ba..0000000 --- a/keploy/utils/signal_windows.go +++ /dev/null @@ -1,44 +0,0 @@ -//go:build windows - -package utils - -import ( - "context" - "errors" - "os/exec" - "syscall" - "time" - - "go.uber.org/zap" - "golang.org/x/sys/windows" -) - -func SendSignal(logger *zap.Logger, pid int, sig syscall.Signal) error { - handle, err := syscall.OpenProcess(syscall.PROCESS_TERMINATE, false, uint32(pid)) - if err != nil { - if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER { - // ERROR_INVALID_PARAMETER means the process does not exist - return nil - } - logger.Error("failed to open process", zap.Int("pid", pid), zap.Error(err)) - return err - } - defer syscall.CloseHandle(handle) - - var retVal int32 - if sig == syscall.SIGKILL || sig == syscall.SIGTERM { - retVal = 1 // Default exit code for termination - } - - if err := syscall.TerminateProcess(handle, uint32(retVal)); err != nil { - logger.Error("failed to terminate process", zap.Int("pid", pid), zap.Error(err)) - return err - } - - logger.Debug("signal sent to process successfully", zap.Int("pid", pid), zap.String("signal", sig.String())) - return nil -} - -func ExecuteCommand(ctx context.Context, logger *zap.Logger, userCmd string, cancel func(cmd *exec.Cmd) func() error, waitDelay time.Duration) CmdError { - return CmdError{Type: Init, Err: errors.New("not implemented")} -} diff --git a/keploy/utils/utils.go b/keploy/utils/utils.go deleted file mode 100644 index 0353732..0000000 --- a/keploy/utils/utils.go +++ /dev/null @@ -1,1292 +0,0 @@ -package utils - -import ( - "bufio" - "bytes" - "context" - "crypto/sha256" - "debug/elf" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "net" - "net/http" - "net/url" - "os" - "os/user" - "path/filepath" - "regexp" - "runtime/debug" - "strconv" - "strings" - "syscall" - "text/template" - "time" - - "golang.org/x/text/cases" - "golang.org/x/text/language" - - "github.com/getsentry/sentry-go" - netLib "github.com/shirou/gopsutil/v3/net" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" - "helm.sh/helm/v3/pkg/strvals" -) - -var WarningSign = "\U000026A0" - -var TemplatizedValues = map[string]interface{}{} -var SecretValues = map[string]interface{}{} - -var ErrCode = 0 - -func ReplaceHost(currentURL string, ipAddress string) (string, error) { - // Parse the current URL - parsedURL, err := url.Parse(currentURL) - - if err != nil { - // Return the original URL if parsing fails - return currentURL, err - } - - if ipAddress == "" { - return currentURL, fmt.Errorf("failed to replace url in case of docker env") - } - - // Replace hostname with the IP address - parsedURL.Host = strings.Replace(parsedURL.Host, parsedURL.Hostname(), ipAddress, 1) - // Return the modified URL - return parsedURL.String(), nil -} - -func ReplaceGrpcHost(authority string, ipAddress string) (string, error) { - // Check if ipAddress is empty - if ipAddress == "" { - return authority, fmt.Errorf("failed to replace authority in case of docker env: empty IP address") - } - - // Split authority into host and port - parts := strings.Split(authority, ":") - if len(parts) != 2 { - return authority, fmt.Errorf("invalid authority format, expected host:port but got %s", authority) - } - - // Replace the host part with ipAddress, keeping the port - return ipAddress + ":" + parts[1], nil -} - -func ReplaceGrpcPort(authority string, port string) (string, error) { - // Check if port is empty - if port == "" { - return authority, fmt.Errorf("failed to replace port in case of docker env: empty port") - } - - // Split authority into host and port - parts := strings.Split(authority, ":") - if len(parts) == 0 { - return authority, fmt.Errorf("invalid authority format, got empty string") - } - - // If there's no port in the authority, append the new port - if len(parts) == 1 { - return parts[0] + ":" + port, nil - } - - // Replace the port part, keeping the host - return parts[0] + ":" + port, nil -} - -// ReplaceBaseURL replaces the base URL (scheme + host) of the given URL with the provided baseURL. -// It returns the updated URL as a string or an error if the operation fails. -func ReplaceBaseURL(currentURL string, baseURL string) (string, error) { - // Parse the current URL - parsedURL, err := url.Parse(currentURL) - if err != nil { - return currentURL, err - } - - // Check if baseURL is valid - if baseURL == "" { - return currentURL, fmt.Errorf("failed to replace baseURL: baseURL is empty") - } - - // Parse the new baseURL - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return currentURL, fmt.Errorf("invalid baseURL: %w", err) - } - - // Replace the scheme and host - parsedURL.Scheme = newBaseURL.Scheme - parsedURL.Host = newBaseURL.Host - - // Return the updated URL as a string - return parsedURL.String(), nil -} - -func ReplacePort(currentURL string, port string) (string, error) { - if port == "" { - return currentURL, fmt.Errorf("failed to replace port in case of docker env") - } - - parsedURL, err := url.Parse(currentURL) - - if err != nil { - return currentURL, err - } - - if parsedURL.Port() == "" { - parsedURL.Host = parsedURL.Host + ":" + port - } else { - parsedURL.Host = strings.Replace(parsedURL.Host, parsedURL.Port(), port, 1) - } - - return parsedURL.String(), nil -} - -// GetReqMeta returns the metadata of the request -func GetReqMeta(req *http.Request) map[string]string { - reqMeta := map[string]string{} - if req != nil { - // get request metadata - reqMeta = map[string]string{ - "method": req.Method, - "url": req.URL.String(), - "host": req.Host, - } - } - return reqMeta -} - -func IsPassThrough(logger *zap.Logger, req *http.Request, destPort uint, opts models.OutgoingOptions) bool { - passThrough := false - - for _, bypass := range opts.Rules { - if bypass.Host != "" { - regex, err := regexp.Compile(bypass.Host) - if err != nil { - LogError(logger, err, "failed to compile the host regex", zap.Any("metadata", GetReqMeta(req))) - continue - } - passThrough = regex.MatchString(req.Host) - if !passThrough { - continue - } - } - if bypass.Path != "" { - regex, err := regexp.Compile(bypass.Path) - if err != nil { - LogError(logger, err, "failed to compile the path regex", zap.Any("metadata", GetReqMeta(req))) - continue - } - passThrough = regex.MatchString(req.URL.String()) - if !passThrough { - continue - } - } - - if passThrough { - if bypass.Port == 0 || bypass.Port == destPort { - return true - } - passThrough = false - } - } - - return passThrough -} - -func kebabToCamel(s string) string { - parts := strings.Split(s, "-") - for i := 1; i < len(parts); i++ { - parts[i] = cases.Title(language.English).String(parts[i]) - } - return strings.Join(parts, "") -} - -func BindFlagsToViper(logger *zap.Logger, cmd *cobra.Command, viperKeyPrefix string) error { - var bindErr error - cmd.Flags().VisitAll(func(flag *pflag.Flag) { - camelCaseName := kebabToCamel(flag.Name) - err := viper.BindPFlag(camelCaseName, flag) - if err != nil { - LogError(logger, err, "failed to bind flag Name to flag") - bindErr = err - } - // Construct the Viper key and the env variable name - if viperKeyPrefix == "" { - viperKeyPrefix = cmd.Name() - } - viperKey := viperKeyPrefix + "." + camelCaseName - envVarName := strings.ToUpper(viperKeyPrefix + "_" + camelCaseName) - envVarName = strings.ReplaceAll(envVarName, ".", "_") // Why do we need this? - - // Bind the flag to Viper with the constructed key - err = viper.BindPFlag(viperKey, flag) - if err != nil { - LogError(logger, err, "failed to bind flag to config") - bindErr = err - } - - // Tell Viper to also read this flag's value from the corresponding env variable - err = viper.BindEnv(viperKey, envVarName) - logger.Debug("Binding flag to viper", zap.String("viperKey", viperKey), zap.String("envVarName", envVarName)) - if err != nil { - LogError(logger, err, "failed to bind environment variables to config") - bindErr = err - } - }) - return bindErr -} - -//func ModifyToSentryLogger(ctx context.Context, logger *zap.Logger, client *sentry.Client, configDb *configdb.ConfigDb) *zap.Logger { -// cfg := zapsentry.Configuration{ -// Level: zapcore.ErrorLevel, //when to send message to sentry -// EnableBreadcrumbs: true, // enable sending breadcrumbs to Sentry -// BreadcrumbLevel: zapcore.InfoLevel, // at what level should we sent breadcrumbs to sentry -// Tags: map[string]string{ -// "component": "system", -// }, -// } -// -// core, err := zapsentry.NewCore(cfg, zapsentry.NewSentryClientFromClient(client)) -// //in case of err it will return noop core. So we don't need to attach it to log. -// if err != nil { -// logger.Debug("failed to init zap", zap.Error(err)) -// return logger -// } -// -// logger = zapsentry.AttachCoreToLogger(core, logger) -// kernelVersion := "" -// if runtime.GOOS == "linux" { -// cmd := exec.CommandContext(ctx, "uname", "-r") -// kernelBytes, err := cmd.Output() -// if err != nil { -// logger.Debug("failed to get kernel version", zap.Error(err)) -// } else { -// kernelVersion = string(kernelBytes) -// } -// } -// -// arch := runtime.GOARCH -// installationID, err := configDb.GetInstallationId(ctx) -// if err != nil { -// logger.Debug("failed to get installationID", zap.Error(err)) -// } -// sentry.ConfigureScope(func(scope *sentry.Scope) { -// scope.SetTag("Keploy Version", Version) -// scope.SetTag("Linux Kernel Version", kernelVersion) -// scope.SetTag("Architecture", arch) -// scope.SetTag("Installation ID", installationID) -// }) -// return logger -//} - -// LogError logs the error with the provided fields if the error is not context.Canceled. -func LogError(logger *zap.Logger, err error, msg string, fields ...zap.Field) { - if logger == nil { - fmt.Println("Failed to log error. Logger is nil.") - return - } - if !errors.Is(err, context.Canceled) { - logger.Error(msg, append(fields, zap.Error(err))...) - } -} - -// RemoveDoubleQuotes removes all double quotes from the values in the provided template map. -// This function handles cases where the templating engine fails to parse values containing both single and double quotes. -// For example: -// Input: '"Not/A)Brand";v="8", "Chromium";v="126", "Brave";v="126"' -// Output: Not/A)Brand;v=8, Chromium;v=126, Brave;v=126 -func RemoveDoubleQuotes(tempMap map[string]interface{}) { - // Remove double quotes - for key, val := range tempMap { - if str, ok := val.(string); ok { - tempMap[key] = strings.ReplaceAll(str, `"`, "") - } - } -} - -func DeleteFileIfNotExists(logger *zap.Logger, name string) (err error) { - //Check if file exists - _, err = os.Stat(name) - if os.IsNotExist(err) { - return nil - } - //If it does, remove it. - err = os.Remove(name) - if err != nil { - LogError(logger, err, "Error removing file") - return err - } - - return nil -} - -type GitHubRelease struct { - TagName string `json:"tag_name"` - Body string `json:"body"` -} - -var ErrGitHubAPIUnresponsive = errors.New("GitHub API is unresponsive") - -var Emoji = "\U0001F430" + " Keploy:" -var ConfigGuide = ` -# Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file. -` - -// AskForConfirmation asks the user for confirmation. A user must type in "yes" or "no" and -// then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as -// confirmations. If the input is not recognized or interrupted, exit gracefully as "no". -func AskForConfirmation(ctx context.Context, s string) (bool, error) { - reader := bufio.NewReader(os.Stdin) - - fmt.Printf("%s [y/n]: ", s) - - respCh := make(chan string, 1) - errCh := make(chan error, 1) - - go func() { - response, err := reader.ReadString('\n') - if err != nil { - errCh <- err - } else { - respCh <- response - } - }() - - select { - case <-ctx.Done(): - // Cobra caught SIGINT (Ctrl+C) and cancelled its root context - return false, nil - case err := <-errCh: - return false, err - case response := <-respCh: - response = strings.ToLower(strings.TrimSpace(response)) - if response == "y" || response == "yes" { - return true, nil - } - return false, nil - } -} - -func CheckFileExists(path string) bool { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false - } - return true -} - -var Version string -var VersionIdenitfier string -var LogFile *os.File - -func GetVersionAsComment() string { - return fmt.Sprintf("# Generated by Keploy (%s)\n", Version) -} - -func attachLogFileToSentry(logger *zap.Logger, logFilePath string) error { - file, err := os.Open(logFilePath) - if err != nil { - return fmt.Errorf("error opening log file: %s", err.Error()) - } - defer func() { - if err := file.Close(); err != nil { - LogError(logger, err, "Error closing log file") - } - }() - - content, err := io.ReadAll(file) - if err != nil { - return fmt.Errorf("error reading log file: %s", err.Error()) - } - - sentry.ConfigureScope(func(scope *sentry.Scope) { - scope.SetExtra("logfile", string(content)) - }) - sentry.Flush(time.Second * 5) - return nil -} - -// HandleRecovery handles the common logic for recovering from a panic. -func HandleRecovery(logger *zap.Logger, r interface{}, errMsg string) { - err := attachLogFileToSentry(logger, "./keploy-logs.txt") - if err != nil { - LogError(logger, err, "failed to attach log file to sentry") - } - sentry.CaptureException(errors.New(fmt.Sprint(r))) - // Get the stack trace - stackTrace := debug.Stack() - LogError(logger, nil, errMsg, zap.String("stack trace", string(stackTrace))) -} - -// Recover recovers from a panic and logs the stack trace to Sentry. -// It also stops the global context. -func Recover(logger *zap.Logger) { - if logger == nil { - fmt.Println(Emoji + "Failed to recover from panic. Logger is nil.") - return - } - sentry.Flush(2 * time.Second) - if r := recover(); r != nil { - HandleRecovery(logger, r, "Recovered from panic") - err := Stop(logger, fmt.Sprintf("Recovered from: %s", r)) - if err != nil { - LogError(logger, err, "failed to stop the global context") - } - sentry.Flush(2 * time.Second) - } -} - -// GenerateGithubActions generates a GitHub Actions workflow file for Keploy -func GenerateGithubActions(logger *zap.Logger, appCmd string) { - // Determine the path based on the alias "keploy" - logger.Debug("Generating GitHub Actions workflow file") - // Define the content of the GitHub Actions workflow file - actionsFileContent := `name: Keploy -on: - push: - branches: - - main - pull_request: - types: [opened, reopened, synchronize] -jobs: - e2e-test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Test-Report - uses: keploy/testgpt@main - with: - working-directory: ./ - keploy-path: ./ - command: ` + appCmd + ` -` - - // Define the file path where the GitHub Actions workflow file will be saved - filePath := "/githubactions/keploy.yml" - - //create the file path - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - logger.Error("Error creating directory for GitHub Actions workflow file", zap.Error(err)) - return - } - // Write the content to the file - if err := os.WriteFile(filePath, []byte(actionsFileContent), 0644); err != nil { - logger.Error("Error writing GitHub Actions workflow file", zap.Error(err)) - return - } - - logger.Info("GitHub Actions workflow file generated successfully", zap.String("path", filePath)) -} - -// GetLatestGitHubRelease fetches the latest version and release body from GitHub releases with a timeout. -func GetLatestGitHubRelease(ctx context.Context, logger *zap.Logger) (GitHubRelease, error) { - // GitHub repository details - repoOwner := "keploy" - repoName := "keploy" - - apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", repoOwner, repoName) - - client := http.Client{ - Timeout: 4 * time.Second, - } - - req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) - if err != nil { - return GitHubRelease{}, err - } - - resp, err := client.Do(req) - if err != nil { - var netErr net.Error - if errors.As(err, &netErr) && netErr.Timeout() { - return GitHubRelease{}, ErrGitHubAPIUnresponsive - } - return GitHubRelease{}, err - } - defer func() { - if err := resp.Body.Close(); err != nil { - LogError(logger, err, "failed to close response body") - } - }() - - var release GitHubRelease - if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { - return GitHubRelease{}, err - } - return release, nil -} - -// FindDockerCmd checks if the cli is related to docker or not, it also returns if it is a docker compose file -func FindDockerCmd(cmd string) CmdType { - if cmd == "" { - return Empty - } - // Convert command to lowercase for case-insensitive comparison - cmdLower := strings.TrimSpace(strings.ToLower(cmd)) - - // Define patterns for Docker and Docker Compose - dockerRunPatterns := []string{"docker run", "sudo docker run", "docker container run", "sudo docker container run"} - dockerStartPatterns := []string{"docker start", "sudo docker start", "docker container start", "sudo docker container start"} - dockerComposePatterns := []string{"docker-compose", "sudo docker-compose", "docker compose", "sudo docker compose"} - - // Check for Docker Compose command patterns and file extensions - for _, pattern := range dockerComposePatterns { - if strings.HasPrefix(cmdLower, pattern) { - return DockerCompose - } - } - // Check for Docker start command patterns - for _, pattern := range dockerStartPatterns { - if strings.HasPrefix(cmdLower, pattern) { - return DockerStart - } - } - // Check for Docker run command patterns - for _, pattern := range dockerRunPatterns { - if strings.HasPrefix(cmdLower, pattern) { - return DockerRun - } - } - return Native -} - -type CmdType string - -// CmdType constants -const ( - DockerRun CmdType = "docker-run" - DockerStart CmdType = "docker-start" - DockerCompose CmdType = "docker-compose" - Native CmdType = "native" - Empty CmdType = "" -) - -func ToInt(value interface{}) int { - switch v := value.(type) { - case int: - return v - case string: - i, err := strconv.Atoi(v) - if err != nil { - fmt.Printf("failed to convert string to int: %v", err) - return 0 - } - return i - case float64: - return int(v) - - } - return 0 -} - -// ToString remove all types of value to strings for comparison. -func ToString(val interface{}) string { - switch v := val.(type) { - case int: - return strconv.Itoa(v) - case float64: - return strconv.FormatFloat(v, 'f', -1, 64) - case float32: - return strconv.FormatFloat(float64(v), 'f', -1, 32) - case int64: - return strconv.FormatInt(v, 10) - case int32: - return strconv.FormatInt(int64(v), 10) - case string: - return v - } - return "" -} - -func ToFloat(value interface{}) float64 { - switch v := value.(type) { - case float64: - return v - case string: - f, err := strconv.ParseFloat(v, 64) - if err != nil { - fmt.Printf("failed to convert string to float: %v", err) - return 0 - } - return f - case int: - return float64(v) - } - return 0 -} - -// Keys returns an array containing the keys of the given map. -func Keys(m map[string][]string) []string { - keys := make([]string, 0, len(m)) - for k := range m { - keys = append(keys, k) - } - return keys -} - -func SentryInit(logger *zap.Logger, dsn string) { - err := sentry.Init(sentry.ClientOptions{ - Dsn: dsn, - TracesSampleRate: 1.0, - }) - if err != nil { - logger.Debug("Could not initialise sentry.", zap.Error(err)) - } -} - -//func FetchHomeDirectory(isNewConfigPath bool) string { -// var configFolder = "/.keploy-config" -// -// if isNewConfigPath { -// configFolder = "/.keploy" -// } -// if runtime.GOOS == "windows" { -// home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") -// if home == "" { -// home = os.Getenv("USERPROFILE") -// } -// return home + configFolder -// } -// -// return os.Getenv("HOME") + configFolder -//} - -func GetAbsPath(path string) (string, error) { - absPath, err := filepath.Abs(path) - if err != nil { - return "", err - } - return absPath, nil -} - -func ToAbsPath(logger *zap.Logger, originalPath string) string { - path := originalPath - //if user provides relative path - if len(path) > 0 && path[0] != '/' { - absPath, err := filepath.Abs(path) - if err != nil { - LogError(logger, err, "failed to get the absolute path from relative path") - } - path = absPath - } else if len(path) == 0 { // if user doesn't provide any path - cdirPath, err := os.Getwd() - if err != nil { - LogError(logger, err, "failed to get the path of current directory") - } - path = cdirPath - } - path += "/keploy" - return path -} - -// makeDirectory creates a directory if not exists with all user access -func makeDirectory(path string) error { - err := os.MkdirAll(path, 0777) - if err != nil { - return err - } - return nil -} - -// SetCoveragePath takes a goCovPath and sets the coverage path accordingly. -// It returns an error if the path is a file or if the path does not exist. -func SetCoveragePath(logger *zap.Logger, goCovPath string) (string, error) { - if goCovPath == "" { - // Calculate the current path and create a coverage-reports directory - currentPath, err := GetAbsPath("") - if err != nil { - LogError(logger, err, "failed to get the current working directory") - return "", err - } - goCovPath = currentPath + "/coverage-reports" - if err := makeDirectory(goCovPath); err != nil { - LogError(logger, err, "failed to create coverage-reports directory", zap.String("CoverageReportPath", goCovPath)) - return "", err - } - return goCovPath, nil - } - - goCovPath, err := GetAbsPath(goCovPath) - if err != nil { - LogError(logger, err, "failed to get the absolute path for the coverage report path", zap.String("CoverageReportPath", goCovPath)) - return "", err - } - // Check if the path is a directory - dirInfo, err := os.Stat(goCovPath) - if err != nil { - if os.IsNotExist(err) { - LogError(logger, err, "the provided path does not exist", zap.String("CoverageReportPath", goCovPath)) - return "", err - } - LogError(logger, err, "failed to check the coverage report path", zap.String("CoverageReportPath", goCovPath)) - return "", err - } - if !dirInfo.IsDir() { - msg := "the coverage report path is not a directory. Please provide a valid path to a directory for go coverage reports" - - LogError(logger, nil, msg, zap.String("CoverageReportPath", goCovPath)) - return "", errors.New("the path provided is not a directory") - } - - return goCovPath, nil -} - -type ErrType string - -// ErrType constants to get the type of error, during init or runtime -const ( - Init ErrType = "init" - Runtime ErrType = "runtime" -) - -type CmdError struct { - Type ErrType - Err error -} - -// InterruptProcessTree interrupts an entire process tree using the given signal -func InterruptProcessTree(logger *zap.Logger, ppid int, sig syscall.Signal) error { - // Find all descendant PIDs of the given PID & then signal them. - // Any shell doesn't signal its children when it receives a signal. - // Children may have their own process groups, so we need to signal them separately. - - logger.Debug("Interrupting process tree", zap.Int("pid", ppid), zap.String("signal", sig.String())) - - children, err := findChildPIDs(ppid) - if err != nil { - return err - } - - children = append(children, ppid) - - logger.Debug("Found child PIDs", zap.Ints("children", children)) - - uniqueProcess, err := uniqueProcessGroups(children) - if err != nil { - logger.Error("failed to find unique process groups", zap.Int("pid", ppid), zap.Error(err)) - uniqueProcess = children - } - - logger.Debug("Unique process groups", zap.Ints("uniqueProcess", uniqueProcess)) - - // Send signal to interrupt each process and wait for them to exit one by one - for _, pid := range uniqueProcess { - err := SendSignal(logger, -pid, sig) - if err != nil { - logger.Error("error sending signal to the process group id", zap.Int("pgid", pid), zap.Error(err)) - continue - } - // Wait for this particular process to exit with a timeout of 3 seconds - err = waitForProcessExit(pid, 3*time.Second, logger) - if err != nil { - logger.Error("error waiting for process to exit", zap.Int("pid", pid), zap.Error(err)) - } - } - return nil -} - -// waitForProcessExit waits for the process to exit or times out after the specified duration -func waitForProcessExit(pid int, timeout time.Duration, logger *zap.Logger) error { - // Create a timeout channel - timeoutCh := time.After(timeout) - - // Loop to check if the process is running - for { - select { - case <-timeoutCh: - // If the timeout is reached, log the timeout and break - logger.Warn("Timed out waiting for process to exit", zap.Int("pid", pid)) - return nil - default: - // Check if the process is running - isRunning, err := isProcessRunning(pid) - if err != nil { - return err - } - - if !isRunning { - logger.Debug("Process exited", zap.Int("pid", pid)) - return nil - } - - // Wait for 1 second before checking again - time.Sleep(300 * time.Millisecond) - } - } -} - -// isProcessRunning checks if a particular process is still running using os.FindProcess -func isProcessRunning(pid int) (bool, error) { - // Try to find the process - process, err := os.FindProcess(pid) - if err != nil { - return false, err // Process lookup failed - } - - // Attempt to signal the process (0 means no action, just error check) - err = process.Signal(syscall.Signal(0)) - if err != nil { - // If error occurs when signaling, it means the process is no longer running - if errors.Is(err, os.ErrProcessDone) || err == syscall.ESRCH { - return false, nil - } - // If there’s another error, consider the process as still running - return true, nil - } - - // No error means the process is running - return true, nil -} - -func uniqueProcessGroups(pids []int) ([]int, error) { - uniqueGroups := make(map[int]bool) - var uniqueGPIDs []int - - for _, pid := range pids { - pgid, err := getProcessGroupID(pid) - if err != nil { - return nil, err - } - if !uniqueGroups[pgid] { - uniqueGroups[pgid] = true - uniqueGPIDs = append(uniqueGPIDs, pgid) - } - } - - return uniqueGPIDs, nil -} - -func getProcessGroupID(pid int) (int, error) { - statusPath := filepath.Join("/proc", strconv.Itoa(pid), "status") - statusBytes, err := os.ReadFile(statusPath) - if err != nil { - return 0, err - } - - status := string(statusBytes) - for _, line := range strings.Split(status, "\n") { - if strings.HasPrefix(line, "NSpgid:") { - return extractIDFromStatusLine(line), nil - } - } - - return 0, nil -} - -// extractIDFromStatusLine extracts the ID from a status line in the format "Key:\tValue". -func extractIDFromStatusLine(line string) int { - fields := strings.Fields(line) - if len(fields) == 2 { - id, err := strconv.Atoi(fields[1]) - if err == nil { - return id - } - } - return -1 -} - -// findChildPIDs takes a parent PID and returns a slice of all descendant PIDs. -func findChildPIDs(parentPID int) ([]int, error) { - var childPIDs []int - - // Recursive helper function to find all descendants of a given PID. - var findDescendants func(int) - findDescendants = func(pid int) { - procDirs, err := os.ReadDir("/proc") - if err != nil { - return - } - - for _, procDir := range procDirs { - if !procDir.IsDir() { - continue - } - - childPid, err := strconv.Atoi(procDir.Name()) - if err != nil { - continue - } - - statusPath := filepath.Join("/proc", procDir.Name(), "status") - statusBytes, err := os.ReadFile(statusPath) - if err != nil { - continue - } - - status := string(statusBytes) - for _, line := range strings.Split(status, "\n") { - if strings.HasPrefix(line, "PPid:") { - fields := strings.Fields(line) - if len(fields) == 2 { - ppid, err := strconv.Atoi(fields[1]) - if err != nil { - break - } - if ppid == pid { - childPIDs = append(childPIDs, childPid) - findDescendants(childPid) - } - } - break - } - } - } - } - - // Start the recursion with the initial parent PID. - findDescendants(parentPID) - - return childPIDs, nil -} - -func GetPIDFromPort(_ context.Context, logger *zap.Logger, port int) (uint32, error) { - logger.Debug("Getting pid using port", zap.Int("port", port)) - - connections, err := netLib.Connections("inet") - if err != nil { - return 0, err - } - - for _, conn := range connections { - if conn.Status == "LISTEN" && conn.Laddr.Port == uint32(port) { - if conn.Pid > 0 { - return uint32(conn.Pid), nil - } - return 0, fmt.Errorf("pid %d is out of bounds", conn.Pid) - } - } - - // If we get here, no process was found using the given port - return 0, fmt.Errorf("no process found using port %d", port) -} - -func EnsureRmBeforeName(cmd string) string { - parts := strings.Split(cmd, " ") - rmIndex := -1 - nameIndex := -1 - - for i, part := range parts { - if part == "--rm" { - rmIndex = i - } else if part == "--name" { - nameIndex = i - break // Assuming --name will always have an argument, we can break here - } - } - if rmIndex == -1 && nameIndex != -1 { - parts = append(parts[:nameIndex], append([]string{"--rm"}, parts[nameIndex:]...)...) - } - - return strings.Join(parts, " ") -} - -func isGoBinary(logger *zap.Logger, filePath string) bool { - f, err := elf.Open(filePath) - if err != nil { - logger.Debug(fmt.Sprintf("failed to open file %s", filePath), zap.Error(err)) - return false - } - if err := f.Close(); err != nil { - LogError(logger, err, "failed to close file", zap.String("file", filePath)) - } - - // Check for section names typical to Go binaries - sections := []string{".go.buildinfo", ".gopclntab"} - for _, section := range sections { - if sect := f.Section(section); sect != nil { - fmt.Println(section) - return true - } - } - return false -} - -// DetectLanguage detects the language of the test command and returns the executable -func DetectLanguage(logger *zap.Logger, cmd string) (config.Language, string) { - if cmd == "" { - return models.Unknown, "" - } - fields := strings.Fields(cmd) - - // Find the actual executable by skipping environment variable assignments - executable := "" - for _, field := range fields { - // Skip environment variable assignments (KEY=VALUE format) - if strings.Contains(field, "=") && !strings.HasPrefix(field, "/") { - continue - } - // This is the actual executable - executable = field - break - } - - if executable == "" { - return models.Unknown, "" - } - - // Check for Python - pythonRegex := regexp.MustCompile(`(?i)(^|.*/)(python(\d+(\.\d+)*)?)$`) - if pythonRegex.MatchString(executable) { - return models.Python, executable - } - - // Check for Node.js - if executable == "node" || executable == "npm" || executable == "yarn" { - return models.Javascript, executable - } - - // Check for Java - if executable == "java" { - return models.Java, executable - } - - // Check for Go - if executable == "go" || (isGoBinary(logger, executable)) { - return models.Go, executable - } - - return models.Unknown, executable -} - -// FileExists checks if a file exists and is not a directory at the given path. -func FileExists(path string) (bool, error) { - fileInfo, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - return false, nil - } - return false, err - } - return fileInfo.Mode().IsRegular(), nil -} - -// ExpandPath expands a given path, replacing the tilde with the user's home directory -func ExpandPath(path string) (string, error) { - if strings.HasPrefix(path, "~/") { - homeDir, err := getHomeDir() - if err != nil { - return "", err - } - return strings.Replace(path, "~", homeDir, 1), nil - } - return path, nil -} - -// getHomeDir retrieves the appropriate home directory based on the execution context -func getHomeDir() (string, error) { - if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { - if usr, err := user.Lookup(sudoUser); err == nil { - return usr.HomeDir, nil - } - } - // Fallback to the current user's home directory - if usr, err := user.Current(); err == nil { - return usr.HomeDir, nil - } - // Fallback if neither method works - return "", errors.New("failed to retrieve current user info") -} - -func IsDockerCmd(kind CmdType) bool { - return (kind == DockerRun || kind == DockerStart || kind == DockerCompose) -} - -func AddToGitIgnore(logger *zap.Logger, path string, ignoreString string) error { - gitignorePath := path + "/.gitignore" - - file, err := os.OpenFile(gitignorePath, os.O_RDWR|os.O_CREATE, 0644) - if err != nil { - return fmt.Errorf("error opening or creating .gitignore file: %v", err) - } - - defer func() { - if err := file.Close(); err != nil { - logger.Error("error closing .gitignore file: %v", zap.Error(err)) - } - }() - - scanner := bufio.NewScanner(file) - found := false - for scanner.Scan() { - if strings.TrimSpace(scanner.Text()) == ignoreString { - found = true - break - } - } - - if !found { - if _, err := file.WriteString("\n" + ignoreString + "\n"); err != nil { - return fmt.Errorf("error writing to .gitignore file: %v", err) - } - return nil - } - - return nil -} - -func Hash(data []byte) string { - hasher := sha256.New() - hasher.Write(data) - return hex.EncodeToString(hasher.Sum(nil)) -} - -func GetLastDirectory() (string, error) { - // Get the current working directory - dir, err := os.Getwd() - if err != nil { - return "", err - } - - // Extract the base (last directory) - lastDir := filepath.Base(dir) - return lastDir, nil -} - -func IsFileEmpty(filePath string) (bool, error) { - fileInfo, err := os.Stat(filePath) - if err != nil { - return false, err - } - return fileInfo.Size() == 0, nil -} - -func IsXMLResponse(resp *models.HTTPResp) bool { - if resp == nil || resp.Header == nil { - return false - } - - contentType, exists := resp.Header["Content-Type"] - if !exists || contentType == "" { - return false - } - return strings.Contains(contentType, "application/xml") || strings.Contains(contentType, "text/xml") -} - -// TrimSpaces removes unwanted spaces around unescaped ',' and '=' -func TrimSpaces(input string) string { - var output strings.Builder - var lastWasEscape bool // tracks if the previous rune was a backslash - var skippingSpaces bool // set when we just wrote a separator - - for _, ch := range input { - // after writing a separator, drop any spaces - if skippingSpaces { - if ch == ' ' { - continue - } - skippingSpaces = false - } - - // handle escape character - if ch == '\\' && !lastWasEscape { - lastWasEscape = true - output.WriteRune(ch) - continue - } - - // if this is an unescaped separator, trim before & skip after - if (ch == ',' || ch == '=') && !lastWasEscape { - // remove trailing spaces before the separator - trimmed := strings.TrimRight(output.String(), " ") - output.Reset() - output.WriteString(trimmed) - - // write the separator itself - output.WriteRune(ch) - - // skip any spaces that follow - skippingSpaces = true - lastWasEscape = false - continue - } - - // normal character (or escaped separator) - output.WriteRune(ch) - lastWasEscape = false - } - - return output.String() -} - -func ParseMetadata(metadataStr string) (map[string]interface{}, error) { - if metadataStr == "" { - return nil, nil - } - m := make(map[string]interface{}) - if err := strvals.ParseInto(metadataStr, m); err != nil { - return nil, fmt.Errorf("cannot parse metadata: %w", err) - } - return m, nil -} - -// RenderTemplatesInString finds all template placeholders (e.g., {{.name}} or {{string .name}}) in a string, -// executes them with the provided data, and replaces them with the result. -// It is robust against strings that contain non-template curly braces by using a strict regex. -func RenderTemplatesInString(logger *zap.Logger, input string, templateData map[string]interface{}) (string, error) { - // This regex is specifically designed to match valid Keploy templates: - // - It must start with {{ and optional whitespace. - // - It can optionally have a function call ("string", "int", "float") followed by whitespace. - // - It MUST contain a dot (.) to indicate a field access. - // - It non-greedily matches characters until the closing braces. - // This prevents it from matching invalid syntax like {{u^2}}. - re := regexp.MustCompile(`\{\{\s*(?:string\s+|int\s+|float\s+)?\.[^{}]*?\}\}`) - - funcMap := template.FuncMap{ - "int": ToInt, - "string": ToString, - "float": ToFloat, - } - - var firstErr error - - result := re.ReplaceAllStringFunc(input, func(match string) string { - // Only parse and execute the matched placeholder, not the entire string. - tmpl, err := template.New("sub").Funcs(funcMap).Parse(match) - if err != nil { - - logger.Debug("failed to parse a valid-looking template placeholder", zap.String("placeholder", match), zap.Error(err)) - return match - } - - var output bytes.Buffer - err = tmpl.Execute(&output, templateData) - if err != nil { - if firstErr == nil { - firstErr = fmt.Errorf("failed to execute template placeholder '%s': %v", match, err) - } - return match - } - - return output.String() - }) - - return result, firstErr -} - -// // XMLToMap converts an XML string into a map[string]interface{} -// func XMLToMap(data string) (map[string]any, error) { -// mv, err := mxj.NewMapXml([]byte(data)) -// if err != nil { -// return nil, err -// } -// return mv, nil -// } - -// // MapToXML converts a map[string]interface{} into an XML string -// func MapToXML(data map[string]any) (string, error) { -// mv := mxj.Map(data) -// xmlBytes, err := mv.Xml() -// if err != nil { -// return "", err -// } -// return string(xmlBytes), nil -// } diff --git a/keploy/utils/utils_test.go b/keploy/utils/utils_test.go deleted file mode 100644 index 2f57d9b..0000000 --- a/keploy/utils/utils_test.go +++ /dev/null @@ -1,348 +0,0 @@ -package utils - -import ( - "testing" - - "os" - "path/filepath" - "strings" - - "net/http" - "net/url" - - "context" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.keploy.io/server/v2/config" - "go.keploy.io/server/v2/pkg/models" - "go.uber.org/zap" -) - -// TestReplaceHost_ValidAndInvalidInputs_001 tests ReplaceHost function with valid and invalid inputs. -func TestReplaceHost_ValidAndInvalidInputs_001(t *testing.T) { - validURL := "http://example.com" - invalidURL := "://invalid-url" - ipAddress := "192.168.1.1" - - // Test valid URL - result, err := ReplaceHost(validURL, ipAddress) - require.NoError(t, err) - assert.Equal(t, "http://192.168.1.1", result) - - // Test invalid URL - result, err = ReplaceHost(invalidURL, ipAddress) - require.Error(t, err) - assert.Equal(t, invalidURL, result) - - // Test empty IP address - result, err = ReplaceHost(validURL, "") - require.Error(t, err) - assert.Equal(t, validURL, result) -} - -// TestReplaceBaseURL_ValidAndInvalidInputs_002 tests ReplaceBaseURL function with valid and invalid inputs. -func TestReplaceBaseURL_ValidAndInvalidInputs_002(t *testing.T) { - validURL := "http://example.com/path" - baseURL := "https://newbase.com" - invalidBaseURL := "://invalid-base-url" - - // Test valid baseURL - result, err := ReplaceBaseURL(validURL, baseURL) - require.NoError(t, err) - assert.Equal(t, "https://newbase.com/path", result) - - // Test invalid baseURL - result, err = ReplaceBaseURL(validURL, invalidBaseURL) - require.Error(t, err) - assert.Equal(t, validURL, result) - - // Test empty baseURL - result, err = ReplaceBaseURL(validURL, "") - require.Error(t, err) - assert.Equal(t, validURL, result) -} - -// TestPathAndFileFunctions_AllCases_114 tests functions related to path manipulation and file system interactions. -func TestPathAndFileFunctions_AllCases_114(t *testing.T) { - logger := zap.NewNop() - - t.Run("GetAbsPath and ToAbsPath", func(t *testing.T) { - absPath, err := GetAbsPath(".") - require.NoError(t, err) - assert.True(t, filepath.IsAbs(absPath)) - - toAbs := ToAbsPath(logger, "some/relative/path") - assert.True(t, filepath.IsAbs(toAbs)) - assert.True(t, strings.HasSuffix(toAbs, "/some/relative/path/keploy")) - - toAbsEmpty := ToAbsPath(logger, "") - assert.True(t, filepath.IsAbs(toAbsEmpty)) - assert.True(t, strings.HasSuffix(toAbsEmpty, "/keploy")) - }) - - t.Run("makeDirectory and DeleteFileIfNotExists", func(t *testing.T) { - tempDir := t.TempDir() - newDir := filepath.Join(tempDir, "newdir") - err := makeDirectory(newDir) - require.NoError(t, err) - _, err = os.Stat(newDir) - assert.NoError(t, err) - - newFile := filepath.Join(tempDir, "newfile.txt") - err = os.WriteFile(newFile, []byte("content"), 0644) - require.NoError(t, err) - err = DeleteFileIfNotExists(logger, newFile) - require.NoError(t, err) - _, err = os.Stat(newFile) - assert.True(t, os.IsNotExist(err)) - - err = DeleteFileIfNotExists(logger, "/non/existent/file") - require.NoError(t, err) - }) - - t.Run("CheckFileExists and FileExists", func(t *testing.T) { - tempDir := t.TempDir() - existingFile := filepath.Join(tempDir, "exists.txt") - err := os.WriteFile(existingFile, []byte("content"), 0644) - require.NoError(t, err) - - assert.True(t, CheckFileExists(existingFile)) - assert.False(t, CheckFileExists(filepath.Join(tempDir, "notexists.txt"))) - - exists, err := FileExists(existingFile) - require.NoError(t, err) - assert.True(t, exists) - - exists, err = FileExists(tempDir) // is a directory - require.NoError(t, err) - assert.False(t, exists) - }) - - t.Run("IsFileEmpty", func(t *testing.T) { - tempDir := t.TempDir() - emptyFile := filepath.Join(tempDir, "empty.txt") - err := os.WriteFile(emptyFile, []byte{}, 0644) - require.NoError(t, err) - nonEmptyFile := filepath.Join(tempDir, "nonempty.txt") - err = os.WriteFile(nonEmptyFile, []byte("data"), 0644) - require.NoError(t, err) - - isEmpty, err := IsFileEmpty(emptyFile) - require.NoError(t, err) - assert.True(t, isEmpty) - - isEmpty, err = IsFileEmpty(nonEmptyFile) - require.NoError(t, err) - assert.False(t, isEmpty) - - _, err = IsFileEmpty("nonexistent.txt") - require.Error(t, err) - }) - - t.Run("GetLastDirectory", func(t *testing.T) { - // This is hard to test reliably, but we can check it doesn't error - _, err := GetLastDirectory() - assert.NoError(t, err) - }) -} - -// TestTypeConversionFunctions_AllCases_116 tests the type conversion utility functions ToInt, ToString, and ToFloat. -func TestTypeConversionFunctions_AllCases_116(t *testing.T) { - t.Run("ToInt", func(t *testing.T) { - assert.Equal(t, 123, ToInt(123)) - assert.Equal(t, 45, ToInt("45")) - assert.Equal(t, 78, ToInt(78.9)) - assert.Equal(t, 0, ToInt("abc")) - assert.Equal(t, 0, ToInt(nil)) - }) - t.Run("ToString", func(t *testing.T) { - assert.Equal(t, "123", ToString(123)) - assert.Equal(t, "45.6", ToString(45.6)) - assert.Equal(t, "hello", ToString("hello")) - assert.Equal(t, "789", ToString(int64(789))) - assert.Equal(t, "1234", ToString(int32(1234))) - assert.Equal(t, "3.14", ToString(float32(3.14))) - assert.Equal(t, "", ToString(nil)) - }) - t.Run("ToFloat", func(t *testing.T) { - assert.Equal(t, 123.0, ToFloat(123)) - assert.Equal(t, 45.6, ToFloat("45.6")) - assert.Equal(t, 78.9, ToFloat(78.9)) - assert.Equal(t, 0.0, ToFloat("abc")) - assert.Equal(t, 0.0, ToFloat(nil)) - }) -} - -// TestConfigAndViper_AllCases_119 tests configuration related functions, including flag binding with Viper. -func TestConfigAndViper_AllCases_119(t *testing.T) { - logger := zap.NewNop() - - t.Run("BindFlagsToViper", func(t *testing.T) { - viper.Reset() - cmd := &cobra.Command{Use: "testcmd"} - cmd.Flags().String("my-flag", "default", "a test flag") - cmd.Flags().Int("another-flag", 123, "another flag") - - err := BindFlagsToViper(logger, cmd, "keploy") - require.NoError(t, err) - - assert.Equal(t, "default", viper.GetString("keploy.myFlag")) - assert.Equal(t, 123, viper.GetInt("keploy.anotherFlag")) - - // Test env var binding - os.Setenv("KEPLOY_MYFLAG", "from_env") - defer os.Unsetenv("KEPLOY_MYFLAG") - // Re-bind to pick up env var - err = BindFlagsToViper(logger, cmd, "keploy") - require.NoError(t, err) - assert.Equal(t, "from_env", viper.GetString("keploy.myFlag")) - }) - - t.Run("SetCoveragePath", func(t *testing.T) { - tempDir := t.TempDir() - // Case 1: Empty path - covPath, err := SetCoveragePath(logger, "") - require.NoError(t, err) - assert.Contains(t, covPath, "coverage-reports") - os.RemoveAll(covPath) // clean up - - // Case 2: Valid directory - covPath, err = SetCoveragePath(logger, tempDir) - require.NoError(t, err) - assert.Equal(t, tempDir, covPath) - - // Case 3: Path is a file - file, err := os.Create(filepath.Join(tempDir, "file.txt")) - require.NoError(t, err) - file.Close() - _, err = SetCoveragePath(logger, file.Name()) - require.Error(t, err) - - // Case 4: Path does not exist - _, err = SetCoveragePath(logger, filepath.Join(tempDir, "nonexistent")) - require.Error(t, err) - }) -} - -// TestIsPassThrough_AllCases_121 tests the IsPassThrough function with various rules and request combinations. -func TestIsPassThrough_AllCases_121(t *testing.T) { - logger := zap.NewNop() - req, _ := http.NewRequest("GET", "http://example.com/path/123", nil) - req.Host = "example.com:8080" - - tests := []struct { - name string - opts models.OutgoingOptions - destPort uint - want bool - }{ - { - name: "match host and port", - opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: "example.com", Port: 8080}}}, - destPort: 8080, - want: true, - }, - { - name: "match host regex", - opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: `^ex.*\.com$`, Port: 8080}}}, - destPort: 8080, - want: true, - }, - { - name: "match path regex", - opts: models.OutgoingOptions{Rules: []config.BypassRule{{Path: `/path/\d+$`}}}, - destPort: 80, - want: true, - }, - { - name: "match host but not port", - opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: "example.com", Port: 9090}}}, - destPort: 8080, - want: false, - }, - { - name: "no match", - opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: "google.com"}}}, - destPort: 8080, - want: false, - }, - { - name: "match with port 0", - opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: "example.com", Port: 0}}}, - destPort: 8080, - want: true, - }, - { - name: "invalid host regex", - opts: models.OutgoingOptions{Rules: []config.BypassRule{{Host: `[`}}}, - destPort: 8080, - want: false, - }, - { - name: "invalid path regex", - opts: models.OutgoingOptions{Rules: []config.BypassRule{{Path: `[`}}}, - destPort: 8080, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Since req.URL.String() includes the host, we need to parse it correctly for path matching - parsedURL, _ := url.Parse(req.URL.String()) - req.URL = parsedURL - req.Host = "example.com" // Host should not contain port for regex matching against host rule - - got := IsPassThrough(logger, req, tt.destPort, tt.opts) - assert.Equal(t, tt.want, got) - }) - } -} - -// TestAskForConfirmation_AllCases_122 tests the user confirmation prompt function under various conditions. -func TestAskForConfirmation_AllCases_122(t *testing.T) { - originalStdin := os.Stdin - defer func() { os.Stdin = originalStdin }() - - runTest := func(input string, expected bool, expectErr bool) { - r, w, _ := os.Pipe() - os.Stdin = r - go func() { - defer w.Close() - w.Write([]byte(input + "\n")) - }() - - // Capture stdout to prevent printing to console during test - oldStdout := os.Stdout - devNull, _ := os.Open(os.DevNull) - os.Stdout = devNull - defer func() { - os.Stdout = oldStdout - devNull.Close() - }() - - got, err := AskForConfirmation(context.Background(), "Confirm?") - if expectErr { - require.Error(t, err) - } else { - require.NoError(t, err) - assert.Equal(t, expected, got) - } - } - - t.Run("confirm with y", func(t *testing.T) { runTest("y", true, false) }) - t.Run("confirm with yes", func(t *testing.T) { runTest("yes", true, false) }) - t.Run("decline with n", func(t *testing.T) { runTest("n", false, false) }) - t.Run("decline with other", func(t *testing.T) { runTest("anything else", false, false) }) - - t.Run("context cancelled", func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - cancel() - got, err := AskForConfirmation(ctx, "Confirm?") - require.NoError(t, err) - assert.False(t, got) - }) -} From 4e883dad3d4625980cfc8ac3b0ad4c36bdf81eaa Mon Sep 17 00:00:00 2001 From: Sarthak160 Date: Mon, 10 Nov 2025 13:39:14 +0000 Subject: [PATCH 3/3] chore: increase delay Signed-off-by: Sarthak160 --- .github/workflows/keploy-e2e.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/keploy-e2e.yml b/.github/workflows/keploy-e2e.yml index 20697dc..21435f3 100644 --- a/.github/workflows/keploy-e2e.yml +++ b/.github/workflows/keploy-e2e.yml @@ -54,8 +54,8 @@ jobs: --container-name="user_service" \ --path="./user_service" \ --config-path="./user_service" \ - --delay 15 \ - -t test-set-0 + --delay 20 \ + -t atg - name: Upload Keploy artifacts (user_service) if: always()